From b40b7c2e3e001f7f3f2e494c58cca3e003daa0dd Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:25:21 +0000 Subject: [PATCH 1/9] fix(sec-01): contain the SPA catch-all against pre-auth path traversal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The catch-all is the only pre-auth request handler in the app, so an escaping path there is an unauthenticated arbitrary-file read (F1, head of Chain A — it is how an attacker obtains data/ipmideck.db and data/encryption.key). Add a module-level pure _resolve_spa_file(full_path, root) that canonicalises with resolve() and requires is_relative_to(root) + is_file(), so containment does not depend on how the escape is spelled. Empty/over-long paths and an embedded NUL return None (Path.resolve() raises ValueError on a NUL byte, and an over-long/UNC-shaped path can stall resolution on a network lookup); a None result means "serve index.html", so no hostile path becomes a 500. The unmatched-api/ 404 branch is retained and kept FIRST — it is the FIX-04 disabled-module contract and the obvious form of this patch drops it. Verified against a live uvicorn over a raw socket: the three spellings that leaked pyproject.toml at 4322 bytes now all return the 647-byte index.html; /api/nonexistent-xyz and /api/modules/disabled/foo still 404; /%00x is 200, not 500. The SPA still boots in chromium with a mounted root and no page errors. The 58-route contract snapshot is unchanged. --- backend/main.py | 40 ++- tests/integration/test_live_attack_chains.py | 304 +++++++++++++++++++ tests/unit/test_spa_containment.py | 100 ++++++ 3 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 tests/integration/test_live_attack_chains.py create mode 100644 tests/unit/test_spa_containment.py diff --git a/backend/main.py b/backend/main.py index 89dd1d8..d9bac77 100644 --- a/backend/main.py +++ b/backend/main.py @@ -391,6 +391,34 @@ async def websocket_endpoint( app.include_router(module_router, prefix="/api/admin/modules", tags=["Modules"], dependencies=[Depends(require_auth)]) +def _resolve_spa_file(full_path: str, root: Path) -> Path | None: + """Resolve a SPA request path to a contained file under ``root``. + + SEC-01 (F1): the catch-all is the only pre-auth request handler in the app, + so an escaping path here is an unauthenticated arbitrary-file read. Returns + the canonicalised file when it is BOTH inside ``root`` and an existing file; + returns None in every other case, meaning "fall back to index.html". + + None is returned (rather than raising) so the handler never leaks the reason + a path was refused, and never turns a hostile path into a 500: + ``Path.resolve()`` raises ValueError on an embedded NUL byte, and an + over-long / UNC-shaped path can stall resolution on a network lookup. + """ + if not full_path or len(full_path) > 1024: + return None + if "\x00" in full_path: + return None + try: + candidate = (root / full_path).resolve() + except (ValueError, OSError): + return None + if not candidate.is_relative_to(root): + return None + if not candidate.is_file(): + return None + return candidate + + def _mount_spa(app: FastAPI) -> None: """Register static file serving and SPA fallback route. @@ -412,6 +440,8 @@ def _mount_spa(app: FastAPI) -> None: except Exception: pass # Already mounted (e.g., during --reload; ignore duplicate) + spa_root = static_dir.resolve() + # SPA fallback: non-API routes return index.html for React Router. # API paths (/api/*) that don't match a registered route return 404 — # this is critical for FIX-04: disabled modules must return 404, not 200. @@ -420,15 +450,17 @@ async def serve_spa(full_path: str): from fastapi import HTTPException # Reject unmatched /api/* paths so disabled modules return 404 (not SPA). + # MUST stay first: this is the FIX-04 disabled-module contract. if full_path.startswith("api/"): raise HTTPException(status_code=404, detail="Not found") - # Try to serve the exact file first (favicon.svg, etc.) - file_path = static_dir / full_path - if full_path and file_path.is_file(): + # Try to serve the exact file first (favicon.svg, etc.), but only when + # it is contained under the SPA root (SEC-01 / F1). + file_path = _resolve_spa_file(full_path, spa_root) + if file_path is not None: return FileResponse(file_path) # Otherwise return index.html for React Router - return FileResponse(static_dir / "index.html") + return FileResponse(spa_root / "index.html") # === CLI entry point === diff --git a/tests/integration/test_live_attack_chains.py b/tests/integration/test_live_attack_chains.py new file mode 100644 index 0000000..aded842 --- /dev/null +++ b/tests/integration/test_live_attack_chains.py @@ -0,0 +1,304 @@ +"""Live attack-chain replay — a REAL server, raw HTTP over a socket. + +This is the empirical proof Phase 10 is judged on. It boots `backend.main:app` +under uvicorn as a subprocess (temp data dir, demo mode) and speaks raw HTTP so +percent-encoded escapes arrive at the handler exactly as an attacker sends +them — no client library normalises them away first. + +Covers: + * SEC-01 / F1 — pre-auth path traversal in the SPA catch-all. + * SEC-07 / F11 — `GET /api/system/app-config/{key}` serving the session secret. + +Deliberately does NOT use the sync `client` / `client_auth` conftest fixtures: +they drive their own event loop, and this test owns a real process instead. +""" + +from __future__ import annotations + +import json +import socket +import subprocess +import sys +import time +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] +BOOT_TIMEOUT_S = 30.0 + + +def _free_port() -> int: + with socket.socket() as s: + s.bind(("127.0.0.1", 0)) + return int(s.getsockname()[1]) + + +def _raw_request(port: int, raw_target: str, headers: str = "") -> tuple[int, bytes]: + """Send a hand-built request line so the target is NOT normalised. + + Returns (status_code, body_bytes). + """ + with socket.create_connection(("127.0.0.1", port), timeout=10) as sock: + request = ( + f"GET {raw_target} HTTP/1.1\r\n" + f"Host: 127.0.0.1:{port}\r\n" + f"{headers}" + "Connection: close\r\n\r\n" + ) + sock.sendall(request.encode("latin-1")) + chunks = [] + while True: + chunk = sock.recv(65536) + if not chunk: + break + chunks.append(chunk) + payload = b"".join(chunks) + head, _, body = payload.partition(b"\r\n\r\n") + status = int(head.split(b"\r\n")[0].split(b" ")[1]) + return status, body + + +@pytest.fixture(scope="module") +def live_server(tmp_path_factory: pytest.TempPathFactory): + """Boot a real uvicorn subprocess on an ephemeral port; always tear it down.""" + data_dir = tmp_path_factory.mktemp("live-server-data") + port = _free_port() + env = { + "PATH": "/usr/bin:/bin:/usr/local/bin", + "HOME": str(data_dir), + "IPMIDECK_DEMO": "true", + "IPMIDECK_DATA_DIR": str(data_dir), + "IPMIDECK_DATA_DB_PATH": str(data_dir / "test.db"), + "IPMIDECK_LOGGING_LEVEL": "warning", + "PYTHONPATH": str(REPO_ROOT), + "NO_COLOR": "1", + "TERM": "dumb", + } + proc = subprocess.Popen( + [ + sys.executable, + "-m", + "uvicorn", + "backend.main:app", + "--host", + "127.0.0.1", + "--port", + str(port), + "--log-level", + "warning", + ], + cwd=str(REPO_ROOT), + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + try: + deadline = time.monotonic() + BOOT_TIMEOUT_S + ready = False + while time.monotonic() < deadline: + if proc.poll() is not None: + out = proc.stdout.read().decode(errors="replace") if proc.stdout else "" + pytest.fail(f"server exited during boot (rc={proc.returncode}):\n{out}") + try: + status, _ = _raw_request(port, "/api/health") + if status == 200: + ready = True + break + except OSError: + pass + time.sleep(0.25) + if not ready: + pytest.fail(f"server did not answer /api/health within {BOOT_TIMEOUT_S}s") + yield port + finally: + proc.terminate() + try: + proc.wait(timeout=10) + except subprocess.TimeoutExpired: # pragma: no cover - defensive + proc.kill() + proc.wait(timeout=10) + if proc.stdout: + proc.stdout.close() + + +@pytest.fixture(scope="module") +def index_html_size(live_server: int) -> int: + """The byte length of the SPA shell — the "contained" answer for every escape.""" + status, body = _raw_request(live_server, "/") + assert status == 200 + assert len(body) > 0 + return len(body) + + +# --- SEC-01 / F1 — pre-auth path traversal -------------------------------- + +# The first three were verified EXPLOITABLE against this handler before the fix: +# each returned 200 with the 4322-byte pyproject.toml where index.html is 647 bytes. +TRAVERSAL_TARGETS = [ + "/../../pyproject.toml", + "/%2e%2e%2f%2e%2e%2fpyproject.toml", + "/..%2f..%2fpyproject.toml", + "/..%5c..%5cpyproject.toml", + "/....//....//pyproject.toml", + "/%2e%2e/%2e%2e/pyproject.toml", + "/../../backend/main.py", + "/../../data/ipmideck.db", + "/../../data/encryption.key", + "/etc/passwd", + "/../../../../../../etc/passwd", +] + + +@pytest.mark.parametrize("target", TRAVERSAL_TARGETS) +def test_traversal_returns_spa_not_the_target_file( + live_server: int, index_html_size: int, target: str +) -> None: + """Every escaping spelling must return the SPA shell, byte-for-byte.""" + status, body = _raw_request(live_server, target) + assert status == 200, f"{target} -> HTTP {status}" + assert len(body) == index_html_size, ( + f"{target} returned {len(body)} bytes, expected the {index_html_size}-byte " + f"index.html — content leaked outside the web root" + ) + + +@pytest.mark.parametrize("target", TRAVERSAL_TARGETS) +def test_traversal_body_contains_no_out_of_root_content(live_server: int, target: str) -> None: + """Belt-and-braces: no fingerprint of a real out-of-root file in the body.""" + _, body = _raw_request(live_server, target) + lowered = body.lower() + for marker in (b"[project]", b"[tool.", b"root:x:", b"def _mount_spa", b"sqlite format"): + assert marker not in lowered, f"{target} leaked {marker!r}" + + +def test_unmatched_api_path_still_404s(live_server: int) -> None: + """FIX-04 disabled-module contract: unmatched /api/* is 404, never the SPA.""" + for target in ("/api/nonexistent-xyz", "/api/modules/disabled/foo", "/api/"): + status, _ = _raw_request(live_server, target) + assert status == 404, f"{target} -> HTTP {status}, expected 404" + + +def test_nul_byte_path_does_not_500(live_server: int, index_html_size: int) -> None: + """Path.resolve() raises ValueError on a NUL byte — it must be caught.""" + status, body = _raw_request(live_server, "/%00x") + assert status == 200 + assert len(body) == index_html_size + + +def test_over_long_path_does_not_500(live_server: int, index_html_size: int) -> None: + status, body = _raw_request(live_server, "/" + "a" * 5000) + assert status == 200 + assert len(body) == index_html_size + + +def test_root_serves_the_spa(live_server: int, index_html_size: int) -> None: + status, body = _raw_request(live_server, "/") + assert status == 200 + assert b"
" in body or b"
" in body + assert len(body) == index_html_size + + +def test_client_side_route_serves_the_spa(live_server: int, index_html_size: int) -> None: + status, body = _raw_request(live_server, "/dashboard") + assert status == 200 + assert len(body) == index_html_size + + +# --- SEC-07 / F11 — app-config read path ---------------------------------- + +SECRET_KEYS = ["session_secret", "app_secret", "auth_enabled", "encryption_key"] + + +def _post_json(port: int, path: str, payload: dict) -> tuple[int, bytes, list[str]]: + """Minimal raw POST returning (status, body, set-cookie headers).""" + encoded = json.dumps(payload).encode() + with socket.create_connection(("127.0.0.1", port), timeout=10) as sock: + request = ( + f"POST {path} HTTP/1.1\r\n" + f"Host: 127.0.0.1:{port}\r\n" + "Content-Type: application/json\r\n" + f"Content-Length: {len(encoded)}\r\n" + "Connection: close\r\n\r\n" + ).encode() + encoded + sock.sendall(request) + chunks = [] + while True: + chunk = sock.recv(65536) + if not chunk: + break + chunks.append(chunk) + payload_bytes = b"".join(chunks) + head, _, body = payload_bytes.partition(b"\r\n\r\n") + lines = head.decode("latin-1").split("\r\n") + status = int(lines[0].split(" ")[1]) + cookies = [ln.split(":", 1)[1].strip() for ln in lines[1:] if ln.lower().startswith("set-cookie:")] + return status, body, cookies + + +@pytest.fixture(scope="module") +def session_cookie(live_server: int) -> str: + """A REAL authenticated session, obtained over HTTP like any client.""" + status, _, cookies = _post_json( + live_server, + "/api/auth/setup", + {"username": "sec07admin", "password": "correct-horse-battery-staple"}, + ) + assert status == 200, f"setup failed with HTTP {status}" + assert cookies, "setup returned no session cookie" + return cookies[0].split(";")[0] + + +@pytest.mark.parametrize("key", SECRET_KEYS) +def test_app_config_refuses_secret_keys_unauthenticated(live_server: int, key: str) -> None: + """No caller without a session gets a secret out of the read path.""" + status, body = _raw_request(live_server, f"/api/system/app-config/{key}") + assert status in (401, 200) + assert b"key_not_allowed" in body or b"unauthorized" in body.lower() + parsed = json.loads(body) + assert parsed.get("value") in (None, ""), f"{key} leaked a value: {parsed}" + + +@pytest.mark.parametrize("key", SECRET_KEYS) +def test_app_config_refuses_secret_keys_authenticated( + live_server: int, session_cookie: str, key: str +) -> None: + """A fully authenticated caller must not be able to name a secret key either.""" + status, body = _raw_request( + live_server, + f"/api/system/app-config/{key}", + headers=f"Cookie: {session_cookie}\r\n", + ) + assert status == 200, f"HTTP {status}" + parsed = json.loads(body) + assert parsed.get("success") is False, f"{key} was served: {parsed}" + assert parsed.get("error") == "key_not_allowed" + assert "value" not in parsed, f"{key} leaked a value: {parsed}" + + +@pytest.mark.parametrize( + "key", + [ + "currency", + "alerting.notifications_enabled", + "data.retention_days", + "fanpilot.auto_recover_on_offline", + "fanpilot.resume_threshold_seconds", + "fanpilot.failsafe_mode", + "fanpilot.failsafe_speed", + ], +) +def test_allow_listed_keys_still_resolve( + live_server: int, session_cookie: str, key: str +) -> None: + """The keys the SPA actually reads must keep working (missing row -> value=None).""" + status, body = _raw_request( + live_server, + f"/api/system/app-config/{key}", + headers=f"Cookie: {session_cookie}\r\n", + ) + assert status == 200 + parsed = json.loads(body) + assert parsed.get("success") is True, f"{key} was refused: {parsed}" + assert parsed.get("key") == key + assert "value" in parsed diff --git a/tests/unit/test_spa_containment.py b/tests/unit/test_spa_containment.py new file mode 100644 index 0000000..b0c5d78 --- /dev/null +++ b/tests/unit/test_spa_containment.py @@ -0,0 +1,100 @@ +"""SEC-01 / F1 — containment unit tests over the pure `_resolve_spa_file()` helper. + +These are the deterministic half of the SEC-01 proof: they exercise the decision +function directly, without booting the app. The empirical half — the same +spellings replayed over a raw socket against a live uvicorn — lives in +`tests/integration/test_live_attack_chains.py`. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from backend.main import _resolve_spa_file + + +@pytest.fixture() +def spa_root(tmp_path: Path) -> Path: + """A miniature SPA web root plus a sibling file that must stay unreachable.""" + root = tmp_path / "static" + (root / "assets").mkdir(parents=True) + (root / "index.html").write_text("spa", encoding="utf-8") + (root / "favicon.svg").write_text("", encoding="utf-8") + (root / "assets" / "app.js").write_text("console.log(1)", encoding="utf-8") + # The target an attacker is after: a real file OUTSIDE the web root. + (tmp_path / "secret.toml").write_text("token = 'do-not-leak'", encoding="utf-8") + return root.resolve() + + +ESCAPING_PATHS = [ + # The three spellings verified exploitable against the real handler + # (raw socket, pre-fix): they returned the 4322-byte pyproject.toml. + "../../secret.toml", + "../secret.toml", + # %2e / %2f spellings arrive at the handler already percent-decoded by + # Starlette, so the decoded form is what the helper must contain. + "../" * 2 + "secret.toml", + # Backslash form. + "..\\..\\secret.toml", + # Absolute paths. + "/etc/passwd", + "/etc/shadow", + # Windows-shaped. + "C:/Windows/win.ini", + # Dot-segment smuggling. + "....//....//secret.toml", + # Over-long segment (length cap). + "a" * 5000, + # Embedded NUL — Path.resolve() raises ValueError on Linux. + "index.html\x00.png", + "\x00", +] + + +@pytest.mark.parametrize("path", ESCAPING_PATHS) +def test_escaping_paths_never_resolve_outside_root(path: str, spa_root: Path) -> None: + """Every escaping spelling either falls back (None) or stays under the root.""" + resolved = _resolve_spa_file(path, spa_root) + if resolved is not None: # pragma: no cover - defensive: must stay contained + assert resolved.is_relative_to(spa_root), f"{path!r} escaped to {resolved}" + + +@pytest.mark.parametrize("path", ESCAPING_PATHS) +def test_escaping_paths_never_return_the_out_of_root_target(path: str, spa_root: Path) -> None: + """The specific out-of-root file an attacker wants is never handed back.""" + resolved = _resolve_spa_file(path, spa_root) + assert resolved is None or resolved.name != "secret.toml" + + +def test_nul_byte_does_not_raise(spa_root: Path) -> None: + """A NUL byte must be refused, not raised out as an unhandled 500.""" + assert _resolve_spa_file("index.html\x00.png", spa_root) is None + + +def test_over_long_path_is_refused(spa_root: Path) -> None: + assert _resolve_spa_file("x" * 1025, spa_root) is None + + +def test_empty_path_falls_back_to_index(spa_root: Path) -> None: + """The empty path means "/" — the handler must serve index.html.""" + assert _resolve_spa_file("", spa_root) is None + + +def test_unknown_spa_route_falls_back_to_index(spa_root: Path) -> None: + """A React Router client route is not a file: fall back, do not 404.""" + assert _resolve_spa_file("dashboard/servers", spa_root) is None + + +def test_directory_is_not_served(spa_root: Path) -> None: + """A directory resolves inside the root but is not a file.""" + assert _resolve_spa_file("assets", spa_root) is None + + +@pytest.mark.parametrize("path", ["index.html", "favicon.svg", "assets/app.js"]) +def test_legitimate_files_still_resolve(path: str, spa_root: Path) -> None: + resolved = _resolve_spa_file(path, spa_root) + assert resolved is not None, f"{path!r} should still be served" + assert resolved.is_file() + assert resolved.is_relative_to(spa_root) From cb42d07541da4d220425fae8bc7516bc048dc686 Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:25:27 +0000 Subject: [PATCH 2/9] fix(sec-07): allow-list the app-config read path so the session secret never crosses the API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /api/system/app-config/{key} served any app_config row by name to any caller holding a session — real, stolen or forged — session_secret included (F11, the authenticated variant of the F1 disclosure). The PUT path has had _ALLOWED_APP_CONFIG_KEYS since 04-W1-01; only the read path lacked it. Reuse the SAME set rather than introducing a second read-specific list, and refuse with the shape the PUT path already returns ({success: false, error: key_not_allowed}) so the frontend error handling and the route-surface snapshot are both unaffected. The bool-coercion and missing-row value=None behaviour are untouched — five frontend call sites depend on them. Verified over HTTP against a live server with a REAL authenticated session: session_secret / app_secret / auth_enabled / encryption_key are all refused with key_not_allowed and no value field; all seven allow-listed keys, which are exactly the ones the SPA reads, still resolve. --- backend/api/system_routes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/api/system_routes.py b/backend/api/system_routes.py index 3cce499..9abd507 100644 --- a/backend/api/system_routes.py +++ b/backend/api/system_routes.py @@ -58,10 +58,16 @@ class AppConfigValueBody(BaseModel): async def get_app_config_value(key: str): """Read a single app_config value. Returns {success, key, value}. + SEC-07 (F11): the key must be in the SAME allow-list the PUT path enforces. + Without it the endpoint served any app_config row by name — `session_secret` + included — to any caller holding a session (real, stolen, or forged). + Bool-shaped storage convention: values stored as 'true'/'false' strings are coerced back to JSON booleans in the response so the frontend can use them directly. Missing rows return value=None (not an error). """ + if key not in _ALLOWED_APP_CONFIG_KEYS: + return {"success": False, "error": "key_not_allowed"} from backend.main import db raw = await db.get_config(key, default=None) if raw is None: From 2b5dc7bcf9ab12df3a5f50f8b7dd24e2c4e54747 Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:33:10 +0000 Subject: [PATCH 3/9] feat(sec-02): add rotate-session-secret so a stolen signing secret can be evicted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sessions are stateless HMACs, so whoever reads app_config.session_secret out of a copied database mints valid cookies for the real username forever (F2). Closing the read path in SEC-01/SEC-07 stops NEW disclosures but cannot un-disclose a secret already taken — this is the eviction move. AuthManager.rotate_session_secret() generates a fresh secrets.token_hex(32), persists it and assigns self._secret so a running process picks it up immediately. It deliberately does NOT touch self._file_key: the at-rest credential key has a different lifecycle (see the four-case migration in initialize()) and re-keying stored BMC credentials is not this operation. Exposed as an 'ipmideck rotate-session-secret' subcommand that short-circuits in cli() exactly where reset-password does. CLI-only by decision: rotation is incident response, typically run with the app stopped, and a UI control or banner would also be visible to whoever holds a forged session. Zero frontend surface is added. Verified against real servers: a cookie held across the documented stop-rotate-restart sequence is refused (401 on a protected route, authenticated:false on /api/auth/me), and the operator logs straight back in with the same password. --- backend/core/auth.py | 22 ++++++++++++++++++++++ backend/main.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/backend/core/auth.py b/backend/core/auth.py index 6e3eb33..dba8b14 100644 --- a/backend/core/auth.py +++ b/backend/core/auth.py @@ -290,6 +290,28 @@ async def _setup_session_secret(self) -> None: await self.db.set_config("session_secret", secret) self._secret = secret + async def rotate_session_secret(self) -> str: + """SEC-02 (F2): replace the session-signing secret; evict every cookie. + + Sessions are stateless HMACs, so whoever reads `app_config.session_secret` + out of a copied database can mint valid cookies for the real username + forever — patching the read path (SEC-01/SEC-07) stops NEW disclosures but + cannot un-disclose a secret already taken. This is the eviction move. + + Generates a fresh secret, persists it, and assigns it to `self._secret` so + a running process picks it up immediately. Returns the new secret so the + caller can confirm it changed (it is not printed to the operator). + + Deliberately does NOT touch `self._file_key`: the at-rest credential key + has a different lifecycle (see the four-case migration in `initialize()`) + and re-keying stored BMC credentials is not part of this operation. + """ + secret = secrets.token_hex(32) + await self.db.set_config("session_secret", secret) + self._secret = secret + logger.info("Session signing secret rotated — all existing sessions are now invalid") + return secret + async def is_auth_enabled(self) -> bool: val = await self.db.get_config("auth_enabled", "true") return val.lower() in ("true", "1", "yes") diff --git a/backend/main.py b/backend/main.py index d9bac77..a54b247 100644 --- a/backend/main.py +++ b/backend/main.py @@ -495,6 +495,11 @@ def _build_arg_parser() -> argparse.ArgumentParser: # `serve` kept as a deprecated alias of `start` so existing docs/scripts keep working. subparsers.add_parser("serve", help="Start the server (deprecated alias of `start`)") subparsers.add_parser("reset-password", help="Reset admin password") + subparsers.add_parser( + "rotate-session-secret", + help="Rotate the session signing secret — invalidates every existing " + "session cookie, including any minted from a stolen/copied database", + ) return parser @@ -637,6 +642,10 @@ def cli(): _reset_password() return + if args.command == "rotate-session-secret": + _rotate_session_secret() + return + if args.gen_cert: # 04-W4-03: generate a self-signed pair under data/certs/, persist the paths to # config.yaml's server section, then exit. The operator flips server.https=true @@ -1076,5 +1085,30 @@ async def _do_reset(): asyncio.run(_do_reset()) +def _rotate_session_secret(): + """SEC-02 (F2): rotate the session signing secret from the CLI. + + Incident-response action, deliberately CLI-only: it is typically run with + the app stopped, and a UI control or banner would also be visible to whoever + holds a forged session. + """ + async def _do_rotate(): + cfg = load_config() + _db = Database(cfg.data.db_path) + await _db.connect() + am = AuthManager(_db) + await am.initialize() + await am.rotate_session_secret() + await _db.close() + print( + "Session signing secret rotated.\n" + "Every existing session cookie is now invalid — including any minted " + "offline from a copied database.\n" + "All operators must log in again. Restart IPMIDeck if it is running." + ) + + asyncio.run(_do_rotate()) + + if __name__ == "__main__": cli() From 1f7e55d98800b93c1426397d2a00946f85ec056c Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:33:13 +0000 Subject: [PATCH 4/9] fix(f17): reset-password no longer claims success for a username that matched zero rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update_password() ignored the cursor rowcount, so a mistyped username matched no rows and the CLI still printed 'Password updated'. An operator recovering from an incident was told a password had changed when it had not — at the worst possible moment. update_password() now returns whether a row actually changed (rowcount on the UPDATE), and _reset_password() reports that outcome instead of printing success unconditionally. The signal is returned rather than raised so the callers stay simple. --- backend/core/auth.py | 14 ++++++++++++-- backend/main.py | 10 ++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/core/auth.py b/backend/core/auth.py index dba8b14..169ca58 100644 --- a/backend/core/auth.py +++ b/backend/core/auth.py @@ -340,13 +340,23 @@ async def verify_password(self, username: str, password: str) -> bool: return False return bcrypt.checkpw(password.encode(), row["password_hash"].encode()) - async def update_password(self, username: str, new_password: str) -> None: + async def update_password(self, username: str, new_password: str) -> bool: + """Set a new password. Returns True iff a row was actually updated. + + F17: the UPDATE silently matches zero rows for a username that does not + exist, and the caller used to print "Password updated" regardless — an + operator recovering from an incident was told a password had changed + when it had not. The rowcount is returned rather than raised so callers + stay simple and decide their own reporting. + """ pw_hash = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode() - await self.db.execute( + cursor = await self.db.execute( "UPDATE users SET password_hash = ? WHERE username = ?", (pw_hash, username), ) + changed = bool(getattr(cursor, "rowcount", 0)) await self.db.commit() + return changed async def replace_user(self, username: str, password: str) -> None: """Single-user create-or-replace: clear the users table and insert one row. diff --git a/backend/main.py b/backend/main.py index a54b247..e257f4c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1075,8 +1075,14 @@ async def _do_reset(): username = input("Username: ") password = getpass.getpass("New password: ") if await am.has_user(): - await am.update_password(username, password) - print(f"Password updated for {username}") + # F17: update_password reports whether a row actually changed. A + # mistyped username matches zero rows, and printing success there + # tells an operator mid-incident that a password changed when it + # did not. + if await am.update_password(username, password): + print(f"Password updated for {username}") + else: + print(f"No user named {username} exists — nothing was updated.") else: await am.create_user(username, password) print(f"User {username} created") From b4e416c64f8b620eb76d0506ee0b6b0c1b341f16 Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:33:56 +0000 Subject: [PATCH 5/9] feat(sec-03)!: bind session tokens to a credential fingerprint, fail-closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING: every operator is logged out once on upgrade. Session tokens issued before this version carry no credential-fingerprint claim and are REJECTED. A password change used to revoke nothing (F7): sessions are stateless HMACs and only a *username* change evicted anyone, via the require_auth current-user check. The advertised eviction move did not evict. Session tokens now carry a 'cfp' claim — a truncated sha256 of the stored bcrypt hash. The hash changes on every password change (per-hash salt), so the fingerprint changes with the credentials and no session store is needed. verify_session_token_async() recomputes it from the CURRENT stored hash and compares constant-time. Absence of the claim is REJECTED (D1, fail-closed, maintainer-approved). A forged token minted from a stolen signing secret omits the claim in exactly the same way a pre-upgrade token does, so treating 'absent' as acceptable would leave the forgery path open and make the whole fix cosmetic. Alternatives weighed and rejected: a grace period until natural expiry (keeps alive precisely the tokens this change exists to revoke) and a forced short expiry (more complexity, same window). create_session_token_async() is an async SIBLING; the synchronous create_session_token() is left in place and unchanged because existing tests call it directly and changing its signature is the refactor the minimal-diff directive forbids. The rule is applied at ALL FOUR verification sites, so a rejected cookie is rejected everywhere rather than only on protected routes: require_auth, /api/auth/me, the WebSocket handshake gate, and _require_session_if_active (/configure and /toggle). Verified over real HTTP: after /configure changes the password the retained cookie 401s on a protected route and reports authenticated:false on /api/auth/me, while the freshly issued cookie works; a hand-forged claim-less cookie signed with the secret read straight out of the database is refused at both sites. --- backend/api/auth_routes.py | 10 +- backend/core/auth.py | 93 ++++++- backend/main.py | 2 +- tests/integration/test_live_attack_chains.py | 276 ++++++++++++++++--- tests/unit/test_session_secret_rotation.py | 258 +++++++++++++++++ 5 files changed, 598 insertions(+), 41 deletions(-) create mode 100644 tests/unit/test_session_secret_rotation.py diff --git a/backend/api/auth_routes.py b/backend/api/auth_routes.py index d33cc0f..6ffbe81 100644 --- a/backend/api/auth_routes.py +++ b/backend/api/auth_routes.py @@ -68,7 +68,7 @@ async def _require_session_if_active(request: Request, auth) -> None: """ if await auth.is_auth_enabled() and await auth.has_user(): token = request.cookies.get("session") - if not token or not auth.verify_session_token(token): + if not token or not await auth.verify_session_token_async(token): raise HTTPException(status_code=401, detail={"error": "unauthorized"}) @@ -79,7 +79,7 @@ async def get_me(request: Request): if not await auth.is_auth_enabled(): return {"authenticated": True, "username": "local", "auth_enabled": False, "has_user": has_user} token = request.cookies.get("session") - username = auth.verify_session_token(token) if token else None + username = await auth.verify_session_token_async(token) if token else None # REVIEWS #7: mirror require_auth — a token whose subject is no longer the current # stored user (e.g. after a credential replace) is NOT authenticated. Keeps /me # consistent with protected routes so the frontend boot routing sees the same state. @@ -125,7 +125,7 @@ async def login(body: LoginRequest, request: Request, response: Response, lang: # 3. Success: clear any prior failure counter, issue session. await auth.reset_failures(body.username) - token = auth.create_session_token(body.username) + token = await auth.create_session_token_async(body.username) _set_session_cookie(response, request, token, auth.session_expiry_seconds) return {"success": True, "username": body.username} @@ -142,7 +142,7 @@ async def setup(body: SetupRequest, request: Request, response: Response, lang: if await auth.has_user(): return {"success": False, "error": t("user_already_exists", lang)} await auth.create_user(body.username, body.password) - token = auth.create_session_token(body.username) + token = await auth.create_session_token_async(body.username) _set_session_cookie(response, request, token, auth.session_expiry_seconds) return {"success": True, "username": body.username} @@ -164,7 +164,7 @@ async def configure_auth(body: ConfigureRequest, request: Request, response: Res except ValueError as e: return {"success": False, "error": str(e)} await auth.set_auth_enabled(True) - token = auth.create_session_token(body.username) + token = await auth.create_session_token_async(body.username) _set_session_cookie(response, request, token, auth.session_expiry_seconds) return {"success": True, "username": body.username} diff --git a/backend/core/auth.py b/backend/core/auth.py index 169ca58..06e80e4 100644 --- a/backend/core/auth.py +++ b/backend/core/auth.py @@ -431,6 +431,25 @@ async def reset_failures(self, username: str) -> None: async with self._fail_lock: self._fail_state.pop(username, None) + async def _credential_fingerprint(self, username: str) -> str | None: + """SEC-03 (F7): a short, stable digest of the user's stored password hash. + + The bcrypt hash changes on every password change (per-hash salt), so a + fingerprint derived from it changes with the credentials and nothing extra + needs storing. Returns None when the username has no row — the caller then + has nothing to compare against and must refuse. + + The claim is an equality check, not a secret: it travels inside a payload + that is already HMAC-signed, and a truncated digest of a bcrypt hash + reveals nothing usable without the hash itself. + """ + row = await self.db.fetchone( + "SELECT password_hash FROM users WHERE username = ?", (username,) + ) + if not row: + return None + return hashlib.sha256(row["password_hash"].encode()).hexdigest()[:16] + def create_session_token(self, username: str) -> str: payload = { "sub": username, @@ -445,8 +464,34 @@ def create_session_token(self, username: str) -> str: b64 = base64.urlsafe_b64encode(data.encode()).decode().rstrip("=") return f"{b64}.{sig}" + async def create_session_token_async(self, username: str) -> str: + """Mint a session token carrying the credential-fingerprint claim (SEC-03). + + An async SIBLING of `create_session_token()` rather than a change to it: + the fingerprint needs a DB read, and the synchronous version is called + directly by existing tests. All three cookie issuers in `auth_routes.py` + are already async handlers, so awaiting here costs nothing. + """ + payload = { + "sub": username, + "iat": int(time.time()), + "exp": int(time.time()) + self.session_expiry_seconds, + } + fingerprint = await self._credential_fingerprint(username) + if fingerprint is not None: + payload["cfp"] = fingerprint + data = json.dumps(payload, separators=(",", ":")) + sig = hmac.new(self._secret.encode(), data.encode(), hashlib.sha256).hexdigest() + b64 = base64.urlsafe_b64encode(data.encode()).decode().rstrip("=") + return f"{b64}.{sig}" + def verify_session_token(self, token: str) -> str | None: - """Returns username if valid, None otherwise.""" + """Returns username if valid, None otherwise. + + Signature + expiry only. The credential-fingerprint claim is checked by + `verify_session_token_async()`, which needs a DB read; every request path + that authenticates a cookie MUST use that one (see D1 / fail-closed). + """ try: b64_part, sig_part = token.rsplit(".", 1) # Re-pad and decode the base64url data part back to the raw JSON that was signed. @@ -465,6 +510,50 @@ def verify_session_token(self, token: str) -> str | None: except Exception: return None + async def verify_session_token_async(self, token: str) -> str | None: + """Full session verification: signature, expiry, AND credential binding. + + SEC-03 / D1 — **FAIL-CLOSED**. A token whose payload carries no `cfp` + claim is REJECTED. That is the whole point of the change: a forged token + (minted from a stolen signing secret) omits the claim in exactly the same + way a pre-upgrade token does, so treating "absent" as acceptable would + leave the forgery path open and make the fix cosmetic. The cost — every + operator is logged out once on upgrade — was accepted with eyes open and + is documented prominently in the CHANGELOG. + + Returns the username only when the claim matches a fingerprint recomputed + from the CURRENT stored hash, so a password change evicts every token + minted before it without adding a session store. + """ + try: + b64_part, sig_part = token.rsplit(".", 1) + data_part = base64.urlsafe_b64decode( + b64_part + "=" * (-len(b64_part) % 4) + ).decode() + expected_sig = hmac.new( + self._secret.encode(), data_part.encode(), hashlib.sha256 + ).hexdigest() + if not hmac.compare_digest(sig_part, expected_sig): + return None + payload = json.loads(data_part) + if payload.get("exp", 0) < time.time(): + return None + username = payload.get("sub") + if not username: + return None + except Exception: + return None + + claim = payload.get("cfp") + if not claim: + return None # D1 fail-closed: absent claim == forged or pre-upgrade + current = await self._credential_fingerprint(username) + if current is None: + return None # token subject is no longer the stored user + if not hmac.compare_digest(claim, current): + return None + return username + def get_encryption_key(self) -> bytes: """Return the at-rest BMC-credential encryption key. @@ -499,7 +588,7 @@ async def require_auth(request: Request) -> str: if not token: raise HTTPException(status_code=401, detail={"error": "unauthorized"}) - username = _auth.verify_session_token(token) + username = await _auth.verify_session_token_async(token) if not username: raise HTTPException(status_code=401, detail={"error": "unauthorized"}) diff --git a/backend/main.py b/backend/main.py index e257f4c..440da8e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -352,7 +352,7 @@ async def websocket_endpoint( # locked out. Uses the current module globals (auth, db, ws_manager) — there is # NO app-state container exists (Decision A1 — Codex HIGH fix). if await auth.is_auth_enabled(): - username = auth.verify_session_token(session) if session else None + username = await auth.verify_session_token_async(session) if session else None if not username: # Reject pre-accept with policy-violation close code (1008). await websocket.close(code=status.WS_1008_POLICY_VIOLATION) diff --git a/tests/integration/test_live_attack_chains.py b/tests/integration/test_live_attack_chains.py index aded842..d8148e3 100644 --- a/tests/integration/test_live_attack_chains.py +++ b/tests/integration/test_live_attack_chains.py @@ -59,12 +59,8 @@ def _raw_request(port: int, raw_target: str, headers: str = "") -> tuple[int, by return status, body -@pytest.fixture(scope="module") -def live_server(tmp_path_factory: pytest.TempPathFactory): - """Boot a real uvicorn subprocess on an ephemeral port; always tear it down.""" - data_dir = tmp_path_factory.mktemp("live-server-data") - port = _free_port() - env = { +def _server_env(data_dir: Path) -> dict[str, str]: + return { "PATH": "/usr/bin:/bin:/usr/local/bin", "HOME": str(data_dir), "IPMIDECK_DEMO": "true", @@ -75,7 +71,10 @@ def live_server(tmp_path_factory: pytest.TempPathFactory): "NO_COLOR": "1", "TERM": "dumb", } - proc = subprocess.Popen( + + +def _spawn_server(data_dir: Path, port: int) -> subprocess.Popen: + return subprocess.Popen( [ sys.executable, "-m", @@ -89,37 +88,51 @@ def live_server(tmp_path_factory: pytest.TempPathFactory): "warning", ], cwd=str(REPO_ROOT), - env=env, + env=_server_env(data_dir), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) + + +def _await_ready(proc: subprocess.Popen, port: int) -> None: + """Bounded readiness poll against /api/health.""" + deadline = time.monotonic() + BOOT_TIMEOUT_S + while time.monotonic() < deadline: + if proc.poll() is not None: + out = proc.stdout.read().decode(errors="replace") if proc.stdout else "" + pytest.fail(f"server exited during boot (rc={proc.returncode}):\n{out}") + try: + status, _ = _raw_request(port, "/api/health") + if status == 200: + return + except OSError: + pass + time.sleep(0.25) + pytest.fail(f"server did not answer /api/health within {BOOT_TIMEOUT_S}s") + + +def _stop_server(proc: subprocess.Popen) -> None: + proc.terminate() + try: + proc.wait(timeout=15) + except subprocess.TimeoutExpired: # pragma: no cover - defensive + proc.kill() + proc.wait(timeout=15) + if proc.stdout: + proc.stdout.close() + + +@pytest.fixture(scope="module") +def live_server(tmp_path_factory: pytest.TempPathFactory): + """Boot a real uvicorn subprocess on an ephemeral port; always tear it down.""" + data_dir = tmp_path_factory.mktemp("live-server-data") + port = _free_port() + proc = _spawn_server(data_dir, port) try: - deadline = time.monotonic() + BOOT_TIMEOUT_S - ready = False - while time.monotonic() < deadline: - if proc.poll() is not None: - out = proc.stdout.read().decode(errors="replace") if proc.stdout else "" - pytest.fail(f"server exited during boot (rc={proc.returncode}):\n{out}") - try: - status, _ = _raw_request(port, "/api/health") - if status == 200: - ready = True - break - except OSError: - pass - time.sleep(0.25) - if not ready: - pytest.fail(f"server did not answer /api/health within {BOOT_TIMEOUT_S}s") + _await_ready(proc, port) yield port finally: - proc.terminate() - try: - proc.wait(timeout=10) - except subprocess.TimeoutExpired: # pragma: no cover - defensive - proc.kill() - proc.wait(timeout=10) - if proc.stdout: - proc.stdout.close() + _stop_server(proc) @pytest.fixture(scope="module") @@ -210,7 +223,7 @@ def test_client_side_route_serves_the_spa(live_server: int, index_html_size: int SECRET_KEYS = ["session_secret", "app_secret", "auth_enabled", "encryption_key"] -def _post_json(port: int, path: str, payload: dict) -> tuple[int, bytes, list[str]]: +def _post_json(port: int, path: str, payload: dict, headers: str = "") -> tuple[int, bytes, list[str]]: """Minimal raw POST returning (status, body, set-cookie headers).""" encoded = json.dumps(payload).encode() with socket.create_connection(("127.0.0.1", port), timeout=10) as sock: @@ -219,6 +232,7 @@ def _post_json(port: int, path: str, payload: dict) -> tuple[int, bytes, list[st f"Host: 127.0.0.1:{port}\r\n" "Content-Type: application/json\r\n" f"Content-Length: {len(encoded)}\r\n" + f"{headers}" "Connection: close\r\n\r\n" ).encode() + encoded sock.sendall(request) @@ -302,3 +316,199 @@ def test_allow_listed_keys_still_resolve( assert parsed.get("success") is True, f"{key} was refused: {parsed}" assert parsed.get("key") == key assert "value" in parsed + + +# --- SEC-02 / F2 — stop-rotate-restart evicts every cookie ----------------- + + +def _rotate_secret_via_cli(data_dir: Path) -> None: + """Run the operator's actual eviction move against the same on-disk DB.""" + result = subprocess.run( + [sys.executable, "-m", "backend.main", "rotate-session-secret"], + cwd=str(REPO_ROOT), + env=_server_env(data_dir), + capture_output=True, + timeout=60, + ) + assert result.returncode == 0, ( + f"rotate-session-secret exited {result.returncode}: " + f"{result.stdout.decode(errors='replace')}{result.stderr.decode(errors='replace')}" + ) + assert b"rotated" in result.stdout.lower() + + +def test_stop_rotate_restart_refuses_the_retained_cookie( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """ROADMAP criterion 3: a token minted from a copied DB dies on rotation. + + Replayed as the operator actually performs it, because the running process + holds the secret in memory: + + 1. stop IPMIDeck + 2. ipmideck rotate-session-secret + 3. start IPMIDeck + + This is the exact sequence 10-03 documents in README's Security section. + """ + data_dir = tmp_path_factory.mktemp("rotate-chain") + port = _free_port() + + # --- boot, create an account, keep the cookie + proc = _spawn_server(data_dir, port) + try: + _await_ready(proc, port) + status, _, cookies = _post_json( + port, + "/api/auth/setup", + {"username": "rotateop", "password": "correct-horse-battery-staple"}, + ) + assert status == 200 and cookies + cookie = cookies[0].split(";")[0] + + # The cookie works before rotation. + status, body = _raw_request(port, "/api/servers", headers=f"Cookie: {cookie}\r\n") + assert status == 200, f"baseline: authenticated request failed with {status}" + status, body = _raw_request(port, "/api/auth/me", headers=f"Cookie: {cookie}\r\n") + assert json.loads(body).get("authenticated") is True + finally: + # --- step 1: stop + _stop_server(proc) + + # --- step 2: rotate against the same on-disk DB + _rotate_secret_via_cli(data_dir) + + # --- step 3: restart on the same data dir + port2 = _free_port() + proc2 = _spawn_server(data_dir, port2) + try: + _await_ready(proc2, port2) + + status, _ = _raw_request(port2, "/api/servers", headers=f"Cookie: {cookie}\r\n") + assert status == 401, ( + f"the pre-rotation cookie still authenticated after the rotation (HTTP {status})" + ) + + status, body = _raw_request(port2, "/api/auth/me", headers=f"Cookie: {cookie}\r\n") + assert status == 200 + assert json.loads(body).get("authenticated") is False + + # And the operator can log straight back in with the same password. + status, _, cookies = _post_json( + port2, + "/api/auth/login", + {"username": "rotateop", "password": "correct-horse-battery-staple"}, + ) + assert status == 200 and cookies + fresh = cookies[0].split(";")[0] + status, _ = _raw_request(port2, "/api/servers", headers=f"Cookie: {fresh}\r\n") + assert status == 200, "a freshly issued cookie must work after rotation" + finally: + _stop_server(proc2) + + +# --- SEC-03 / F7 — a password change evicts every session ----------------- + + +def test_password_change_evicts_the_retained_cookie( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """ROADMAP criterion 4, over real HTTP: the advertised eviction move evicts. + + Chain D's counter-proof. `/configure` changes the account password; the + cookie held from before the change must be refused on a protected route AND + on `/api/auth/me`, and the newly issued cookie must work. + """ + data_dir = tmp_path_factory.mktemp("pwchange-chain") + port = _free_port() + proc = _spawn_server(data_dir, port) + try: + _await_ready(proc, port) + status, _, cookies = _post_json( + port, + "/api/auth/setup", + {"username": "changeop", "password": "original-password-value"}, + ) + assert status == 200 and cookies + old_cookie = cookies[0].split(";")[0] + + status, _ = _raw_request(port, "/api/servers", headers=f"Cookie: {old_cookie}\r\n") + assert status == 200, "baseline: the cookie must work before the change" + + # Change the password through the real endpoint, carrying the session. + status, _, new_cookies = _post_json( + port, + "/api/auth/configure", + {"username": "changeop", "password": "a-brand-new-password"}, + headers=f"Cookie: {old_cookie}\r\n", + ) + assert status == 200 + + # The OLD cookie is dead everywhere. + status, _ = _raw_request(port, "/api/servers", headers=f"Cookie: {old_cookie}\r\n") + assert status == 401, f"pre-change cookie still accepted on a protected route ({status})" + + status, body = _raw_request(port, "/api/auth/me", headers=f"Cookie: {old_cookie}\r\n") + assert status == 200 + assert json.loads(body).get("authenticated") is False, "/api/auth/me still accepts it" + + # The cookie issued by /configure works. + assert new_cookies, "/configure must issue a fresh cookie" + fresh = new_cookies[0].split(";")[0] + status, _ = _raw_request(port, "/api/servers", headers=f"Cookie: {fresh}\r\n") + assert status == 200, "the freshly issued cookie must authenticate" + finally: + _stop_server(proc) + + +def test_claimless_forged_cookie_is_refused_over_http( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """D1 fail-closed at the route layer: a perfectly-signed claim-less token. + + Built the way an attacker with the stolen signing secret would: read the + secret out of the DB, mint a token with a valid signature and no `cfp` + claim — indistinguishable from a pre-upgrade cookie. It must be refused. + """ + import base64 + import hashlib + import hmac + import sqlite3 + import time as _time + + data_dir = tmp_path_factory.mktemp("forge-chain") + port = _free_port() + proc = _spawn_server(data_dir, port) + try: + _await_ready(proc, port) + status, _, cookies = _post_json( + port, + "/api/auth/setup", + {"username": "forgeop", "password": "correct-horse-battery-staple"}, + ) + assert status == 200 and cookies + + # Attacker reads the signing secret from the (copied) database. + conn = sqlite3.connect(str(data_dir / "test.db")) + secret = conn.execute( + "SELECT value FROM app_config WHERE key='session_secret'" + ).fetchone()[0] + conn.close() + assert secret + + payload = json.dumps( + {"sub": "forgeop", "iat": int(_time.time()), "exp": int(_time.time()) + 86400}, + separators=(",", ":"), + ) + sig = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest() + b64 = base64.urlsafe_b64encode(payload.encode()).decode().rstrip("=") + forged = f"session={b64}.{sig}" + + status, _ = _raw_request(port, "/api/servers", headers=f"Cookie: {forged}\r\n") + assert status == 401, f"a claim-less forged cookie was accepted (HTTP {status})" + + status, body = _raw_request(port, "/api/auth/me", headers=f"Cookie: {forged}\r\n") + assert json.loads(body).get("authenticated") is False + finally: + _stop_server(proc) + diff --git a/tests/unit/test_session_secret_rotation.py b/tests/unit/test_session_secret_rotation.py new file mode 100644 index 0000000..b16515b --- /dev/null +++ b/tests/unit/test_session_secret_rotation.py @@ -0,0 +1,258 @@ +"""SEC-02 / SEC-03 / F17 — manager-level proofs via the async `auth_manager` fixture. + +Split by subject: + * rotation of the session signing secret (SEC-02 / F2) + * binding session tokens to a credential fingerprint, fail-closed (SEC-03 / F7, D1) + * `update_password` reporting honestly when it changed nothing (F17) + +Manager level on purpose: the fail-closed rule has to be proven at the source, not +only at one route. The route-level half runs over real HTTP in +`tests/integration/test_live_attack_chains.py`. The sync `client` / `client_auth` +fixtures are NOT used here — they drive their own event loop (prior-findings trap 6). +""" + +from __future__ import annotations + +import base64 +import hashlib +import hmac +import json + +import pytest + +USERNAME = "operator" +PASSWORD = "correct-horse-battery-staple" + + +async def _make_user(am, username: str = USERNAME, password: str = PASSWORD) -> None: + await am.create_user(username, password) + + +def _decode_payload(token: str) -> dict: + b64_part, _, _sig = token.rpartition(".") + return json.loads(base64.urlsafe_b64decode(b64_part + "=" * (-len(b64_part) % 4))) + + +def _resign(am, payload: dict) -> str: + """Re-sign a hand-edited payload with the manager's CURRENT secret. + + This is exactly what an attacker holding a stolen signing secret can do, so a + token built here is indistinguishable from a forged one on the signature. + """ + data = json.dumps(payload, separators=(",", ":")) + sig = hmac.new(am._secret.encode(), data.encode(), hashlib.sha256).hexdigest() + b64 = base64.urlsafe_b64encode(data.encode()).decode().rstrip("=") + return f"{b64}.{sig}" + + +# --- SEC-02 / F2 — rotatable signing secret -------------------------------- + + +async def test_rotation_replaces_the_stored_secret(auth_manager) -> None: + am, db = auth_manager + before = await db.get_config("session_secret") + assert before + new = await am.rotate_session_secret() + after = await db.get_config("session_secret") + assert after == new + assert after != before + assert am._secret == after, "a running process must pick the new secret up immediately" + + +async def test_token_minted_before_rotation_stops_verifying(auth_manager) -> None: + """The copied-database case: a token minted under the OLD secret dies on rotation.""" + am, _db = auth_manager + await _make_user(am) + token = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(token) == USERNAME + + await am.rotate_session_secret() + + assert await am.verify_session_token_async(token) is None + assert am.verify_session_token(token) is None + + +async def test_token_minted_after_rotation_verifies(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + await am.rotate_session_secret() + token = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(token) == USERNAME + + +async def test_rotation_is_idempotent_and_always_evicts(auth_manager) -> None: + am, db = auth_manager + await _make_user(am) + first = await am.rotate_session_secret() + token = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(token) == USERNAME + + second = await am.rotate_session_secret() + assert second != first + assert await db.get_config("session_secret") == second + assert await am.verify_session_token_async(token) is None + + +async def test_rotation_does_not_touch_the_at_rest_credential_key(auth_manager) -> None: + """Rotating the session secret must not re-key stored BMC credentials.""" + am, _db = auth_manager + key_before = am.get_encryption_key() + await am.rotate_session_secret() + assert am.get_encryption_key() == key_before + + +# --- SEC-03 / F7 — credential fingerprint, fail-closed (D1) ---------------- + + +async def test_fresh_token_verifies(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + token = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(token) == USERNAME + + +async def test_token_carries_the_fingerprint_claim(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + payload = _decode_payload(await am.create_session_token_async(USERNAME)) + assert payload.get("cfp"), "the minted token must carry the cfp claim" + assert payload["sub"] == USERNAME + + +async def test_password_change_invalidates_existing_tokens(auth_manager) -> None: + """SEC-03: the operator's advertised eviction move finally evicts.""" + am, _db = auth_manager + await _make_user(am) + token = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(token) == USERNAME + + assert await am.update_password(USERNAME, "a-brand-new-password") is True + + assert await am.verify_session_token_async(token) is None + + +async def test_new_token_after_password_change_works(auth_manager) -> None: + """The operator must be able to log straight back in.""" + am, _db = auth_manager + await _make_user(am) + await am.update_password(USERNAME, "a-brand-new-password") + fresh = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(fresh) == USERNAME + + +async def test_claimless_token_is_rejected_fail_closed(auth_manager) -> None: + """D1: a correctly-signed token with no cfp claim is REFUSED. + + This is both the pre-upgrade token and the forged token — they are + byte-indistinguishable on this point, which is why absence cannot mean + acceptable. + """ + am, _db = auth_manager + await _make_user(am) + token = await am.create_session_token_async(USERNAME) + payload = _decode_payload(token) + payload.pop("cfp") + stripped = _resign(am, payload) + + # The signature itself is perfectly valid — proving the rejection is the + # claim rule and not a signature failure. + assert am.verify_session_token(stripped) == USERNAME + assert await am.verify_session_token_async(stripped) is None + + +async def test_legacy_synchronous_token_is_rejected(auth_manager) -> None: + """A token minted by the pre-change synchronous minter carries no claim.""" + am, _db = auth_manager + await _make_user(am) + legacy = am.create_session_token(USERNAME) + assert am.verify_session_token(legacy) == USERNAME # signature is fine + assert await am.verify_session_token_async(legacy) is None # but refused + + +async def test_wrong_fingerprint_claim_is_rejected(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + payload = _decode_payload(await am.create_session_token_async(USERNAME)) + payload["cfp"] = "0" * 16 + assert await am.verify_session_token_async(_resign(am, payload)) is None + + +async def test_empty_fingerprint_claim_is_rejected(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + payload = _decode_payload(await am.create_session_token_async(USERNAME)) + payload["cfp"] = "" + assert await am.verify_session_token_async(_resign(am, payload)) is None + + +async def test_token_for_a_removed_user_is_rejected(auth_manager) -> None: + """The existing username-change eviction still works.""" + am, _db = auth_manager + await _make_user(am) + token = await am.create_session_token_async(USERNAME) + await am.replace_user("someone-else", "another-password") + assert await am.verify_session_token_async(token) is None + + +async def test_expired_token_is_rejected(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + payload = _decode_payload(await am.create_session_token_async(USERNAME)) + payload["exp"] = 1 + assert await am.verify_session_token_async(_resign(am, payload)) is None + + +async def test_tampered_signature_is_rejected(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + token = await am.create_session_token_async(USERNAME) + assert await am.verify_session_token_async(token + "x") is None + + +@pytest.mark.parametrize("garbage", ["", "not-a-token", "a.b.c", "....", "x." * 50]) +async def test_malformed_tokens_are_rejected(auth_manager, garbage: str) -> None: + am, _db = auth_manager + await _make_user(am) + assert await am.verify_session_token_async(garbage) is None + + +async def test_subjectless_token_is_rejected(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + payload = _decode_payload(await am.create_session_token_async(USERNAME)) + payload["sub"] = "" + assert await am.verify_session_token_async(_resign(am, payload)) is None + + +async def test_fingerprint_is_none_for_unknown_user(auth_manager) -> None: + am, _db = auth_manager + assert await am._credential_fingerprint("nobody") is None + + +async def test_minting_for_an_unknown_user_yields_an_unusable_token(auth_manager) -> None: + """No user row means no claim to add — and the result must not authenticate.""" + am, _db = auth_manager + token = await am.create_session_token_async("ghost") + assert "cfp" not in _decode_payload(token) + assert await am.verify_session_token_async(token) is None + + +# --- F17 — honest reset-password ------------------------------------------ + + +async def test_update_password_reports_no_change_for_unknown_username(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + assert await am.update_password("nosuchuser", "irrelevant") is False + + +async def test_update_password_reports_change_for_the_real_username(auth_manager) -> None: + am, _db = auth_manager + await _make_user(am) + assert await am.update_password(USERNAME, "a-brand-new-password") is True + assert await am.verify_password(USERNAME, "a-brand-new-password") is True + + +async def test_update_password_on_an_empty_users_table_reports_no_change(auth_manager) -> None: + am, _db = auth_manager + assert await am.update_password(USERNAME, "whatever") is False From 75cd45197b897b95dfd528d5c88c1cf9bc2ff6fb Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:44:33 +0000 Subject: [PATCH 6/9] fix(sec-04): first-run setup always re-enables authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An anonymous POST /api/auth/toggle {enabled:false} is accepted on a fresh instance because the session guard short-circuits while no user exists, and /api/auth/setup then created the account WITHOUT re-enabling auth. The instance stayed open permanently, with no UI symptom. Verified live before this change: anonymous toggle-off -> setup -> anonymous GET /api/servers returned 200 with the full inventory. It now returns 401. This closes clause 2 of SEC-04 ("completing first-run setup always leaves authentication enabled") and deliberately NOT clause 1 ("an anonymous caller cannot disable authentication on a not-yet-configured instance"), which is deferred onto SEC-06: before any credential exists nothing over HTTP distinguishes the legitimate first-run operator from a LAN attacker, and separating them needs the out-of-band bootstrap secret SEC-06 provides. The residual is measured and disclosed in README's Security section rather than being papered over. toggle_auth is deliberately UNCHANGED. No connection-property gate (the README tells operators to open http://:3000 and the Dockerfile uses --network host, so normal first run is not from loopback; and behind a reverse proxy everything looks like loopback). No servers-emptiness gate (a fresh instance holds zero servers so it never fires against the attacker, while demo mode seeds six with no account so it fires against the operator). No flat "refuse when no user exists" (SetupPage.tsx:101 calls this endpoint anonymously and auth_enabled defaults to true, so refusing it is a permanent lockout — the withdrawn attempt's error). A named regression test pins that the first-run skip still succeeds. --- backend/api/auth_routes.py | 16 +++- tests/integration/test_live_attack_chains.py | 46 +++++++++++ tests/unit/test_phase10_auth_hardening.py | 83 ++++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_phase10_auth_hardening.py diff --git a/backend/api/auth_routes.py b/backend/api/auth_routes.py index 6ffbe81..0e986a8 100644 --- a/backend/api/auth_routes.py +++ b/backend/api/auth_routes.py @@ -138,10 +138,23 @@ async def logout(response: Response): @router.post("/setup") async def setup(body: SetupRequest, request: Request, response: Response, lang: str = Depends(get_lang)): + """First-run account creation. Always leaves authentication ENABLED. + + SEC-04 clause 2 (F5): an anonymous caller can disable auth on a + not-yet-configured instance (that clause is deferred onto SEC-06 — see the + README Security advisory), and `setup` used to create the account without + touching the flag. The instance therefore stayed open forever, with no UI + symptom, even after the real operator finished first run. + + Re-enabling unconditionally removes the DURABILITY and the SILENCE of that + attack: whatever the flag was beforehand, completing first run closes the + instance and the login page is enforced from then on. + """ from backend.main import auth if await auth.has_user(): return {"success": False, "error": t("user_already_exists", lang)} await auth.create_user(body.username, body.password) + await auth.set_auth_enabled(True) token = await auth.create_session_token_async(body.username) _set_session_cookie(response, request, token, auth.session_expiry_seconds) return {"success": True, "username": body.username} @@ -159,6 +172,7 @@ async def configure_auth(body: ConfigureRequest, request: Request, response: Res """ from backend.main import auth await _require_session_if_active(request, auth) + try: await auth.replace_user(body.username, body.password) except ValueError as e: @@ -209,7 +223,7 @@ async def toggle_auth(body: ToggleRequest, request: Request, lang: str = Depends "error": "Current password is required to disable authentication", } token = request.cookies.get("session") - username = auth.verify_session_token(token) if token else None + username = await auth.verify_session_token_async(token) if token else None if not username or not await auth.verify_password(username, body.current_password): return {"success": False, "error": "Incorrect password"} diff --git a/tests/integration/test_live_attack_chains.py b/tests/integration/test_live_attack_chains.py index d8148e3..f68001b 100644 --- a/tests/integration/test_live_attack_chains.py +++ b/tests/integration/test_live_attack_chains.py @@ -512,3 +512,49 @@ def test_claimless_forged_cookie_is_refused_over_http( finally: _stop_server(proc) + +# --- SEC-04 clause 2 / SEC-05 — Chain B and Chain D, live ----------------- + + +def test_chain_b_end_state_is_closed_live(tmp_path_factory: pytest.TempPathFactory) -> None: + """Chain B replayed against a real server: anonymous toggle -> setup -> 401. + + Verified live in this tree BEFORE the fix, this exact sequence ended in a + **200** with the full server inventory. It must now end in a 401. + + The assertion is on the END STATE, not on the toggle. The anonymous + pre-setup disable is STILL EXPECTED TO SUCCEED — that is SEC-04 clause 1, + deferred onto SEC-06 per D4, and refusing it would break the + SAFETY-CRITICAL first-run skip in SetupPage.tsx:101. + """ + data_dir = tmp_path_factory.mktemp("chain-b") + port = _free_port() + proc = _spawn_server(data_dir, port) + try: + _await_ready(proc, port) + + # Step 1 — anonymous disable. Accepted by design (clause 1, deferred). + status, body, _ = _post_json(port, "/api/auth/toggle", {"enabled": False}) + assert status == 200 + assert json.loads(body).get("success") is True, ( + "the first-run skip path must keep working — refusing it is a permanent lockout" + ) + + # Step 2 — the real operator completes first run. + status, _, cookies = _post_json( + port, + "/api/auth/setup", + {"username": "chainbop", "password": "correct-horse-battery-staple"}, + ) + assert status == 200 and cookies + + # Step 3 — an ANONYMOUS caller (no cookie) hits a protected route. + status, _ = _raw_request(port, "/api/servers") + assert status == 401, ( + f"Chain B still ends open: anonymous /api/servers returned {status}, expected 401" + ) + + status, body = _raw_request(port, "/api/auth/status") + assert json.loads(body).get("auth_enabled") is True + finally: + _stop_server(proc) diff --git a/tests/unit/test_phase10_auth_hardening.py b/tests/unit/test_phase10_auth_hardening.py new file mode 100644 index 0000000..12c5546 --- /dev/null +++ b/tests/unit/test_phase10_auth_hardening.py @@ -0,0 +1,83 @@ +"""SEC-04 / SEC-05 route-level hardening tests (Phase 10, Plan 03). + +Everything here goes through HTTP with the synchronous `client_auth` fixture — +never `await bm.auth...` — because those fixtures drive their own event loop +(prior-findings trap 6). + +Scope note, deliberate: there is NO test asserting that an anonymous +`POST /api/auth/toggle {enabled:false}` is refused on a fresh instance. Per D4 +that is SEC-04 **clause 1**, which is deferred onto SEC-06 — before any +credential exists nothing over HTTP separates the first-run operator from a LAN +attacker. The test below asserts the opposite (that it still SUCCEEDS), because +`SetupPage.tsx:101` depends on it and refusing it is a permanent lockout. +""" + +from __future__ import annotations + +SETUP_USER = "phase10admin" +SETUP_PASS = "correct-horse-battery-staple" + + +# --- SEC-04 clause 2 (F5) ------------------------------------------------- + + +def test_first_run_skip_on_an_empty_instance_still_succeeds(client_auth): + """SAFETY-CRITICAL regression guard for the SetupPage "No" branch. + + `SetupPage.tsx:101` calls this endpoint anonymously and `auth_enabled` + defaults to "true", so a frontend-only skip would leave auth on with no + user = permanent lockout. This passes on today's code and must keep + passing; it is the guard that catches any future attempt to bolt a refusal + onto this path. + + It also passes under the demo-seeded fixture (six servers, no account) + precisely because no servers-emptiness condition was added — such a guard + would fire against the legitimate operator, not the attacker. + """ + r = client_auth.post("/api/auth/toggle", json={"enabled": False}) + assert r.status_code == 200 + assert r.json()["success"] is True + + status = client_auth.get("/api/auth/status").json() + assert status["auth_enabled"] is False + assert status["has_user"] is False + + +def test_setup_re_enables_auth_after_an_anonymous_skip(client_auth): + """SEC-04 clause 2: completing first run always re-closes the instance.""" + client_auth.post("/api/auth/toggle", json={"enabled": False}) + assert client_auth.get("/api/auth/status").json()["auth_enabled"] is False + + r = client_auth.post( + "/api/auth/setup", json={"username": SETUP_USER, "password": SETUP_PASS} + ) + assert r.status_code == 200 and r.json()["success"] is True + + assert client_auth.get("/api/auth/status").json()["auth_enabled"] is True + + +def test_chain_b_end_state_is_closed(client_auth): + """Anonymous toggle-off -> setup -> anonymous protected request = 401. + + The middle step is still accepted (clause 1, deferred). What changed is + that it no longer SURVIVES setup: before this phase the instance stayed + open forever with no UI symptom. + """ + client_auth.post("/api/auth/toggle", json={"enabled": False}) + client_auth.post( + "/api/auth/setup", json={"username": SETUP_USER, "password": SETUP_PASS} + ) + # Drop the cookie /setup issued — we are asking what an ANONYMOUS caller sees. + client_auth.cookies.clear() + + r = client_auth.get("/api/servers") + assert r.status_code == 401, f"instance still open to anonymous callers ({r.status_code})" + + +def test_setup_leaves_auth_enabled_on_a_normal_first_run(client_auth): + """The flag was never touched beforehand — setup must still leave it on.""" + r = client_auth.post( + "/api/auth/setup", json={"username": SETUP_USER, "password": SETUP_PASS} + ) + assert r.status_code == 200 and r.json()["success"] is True + assert client_auth.get("/api/auth/status").json()["auth_enabled"] is True From 67b2505ed29dec7245c4ce504ad00884a13e8f52 Mon Sep 17 00:00:00 2001 From: dev-luigi <70869541+dev-luigi@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:45:11 +0000 Subject: [PATCH 7/9] fix(sec-05): require the current password to rewrite the account MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _require_session_if_active checked only the token signature, so a stale-but-signed cookie could rewrite the sole account through /configure — the exact action an incident responder takes to evict an attacker (F6). On an auth-disabled instance that still had an account, no cookie was needed at all (F10). /configure now requires the current password whenever an account exists, and _require_session_if_active and /toggle apply the COMPLETE validator (signature + current user + credential fingerprint) rather than the signature alone, so a cookie refused everywhere else is refused here too. The gate keys on has_user(), NOT on auth_enabled. That keying is load-bearing: keying on auth_enabled would leave the F10 window wide open, and the frontend only shows this form when auth is OFF, so an auth_enabled condition would never fire from the UI. With no account present nothing is required — that is genuine first run, and enabling auth from Settings on a fresh instance still works. On an auth-disabled instance there is no session to name the current user, so verification falls back to the single stored account row (the users table is single-user by design). UI: one new Current password input inside the EXISTING Security enable-auth form — the single UI exception approved for this phase. It renders and is enforced only when an account exists, grafted onto the already-present secCurrentPassword state and currentPassword* i18n keys, so no new component, state hook, i18n key or flow is introduced. Committed component test covers all four cases including the account-less path, where an unconditional guard would have made enabling auth impossible. backend/static rebuilt from source (it is the PyPI wheel's package-data); index.html keeps its original line endings so the diff is the one changed asset hash rather than 12 lines of CRLF noise. --- backend/api/auth_routes.py | 32 ++++ ...oard-BtJ8u-Vn.js => Dashboard-D0XMO9O-.js} | 2 +- ...ate-12OPdy7G.js => EmptyState-D9mKpvu9.js} | 2 +- ...RUPage-C090S3Sn.js => FRUPage-B2eK3zkd.js} | 2 +- ...e-DBREHJWC.js => FanPilotPage-Dq68oKy3.js} | 2 +- ...Cgo_vqvE.js => LanguageSelect-yb_avW7t.js} | 2 +- ...Page-Bq8jXMVx.js => LoginPage-htt-iwGP.js} | 2 +- ...ge-DAGhthzy.js => ModulesPage-BXE9Cdoj.js} | 2 +- ...ELPage-BWtaElar.js => SELPage-CreMDWWE.js} | 2 +- .../static/assets/SettingsPage-BS6Vz2aR.js | 1 + .../static/assets/SettingsPage-DTxmRYKO.js | 1 - ...Page-wPD1HYfu.js => SetupPage-BsG7fLuD.js} | 2 +- .../{index-l2esYWfi.js => index-DXNHFWmw.js} | 4 +- backend/static/index.html | 2 +- .../pages/settings/SecuritySection.test.tsx | 151 ++++++++++++++++ .../src/pages/settings/SecuritySection.tsx | 25 ++- tests/integration/test_live_attack_chains.py | 116 +++++++++++- tests/unit/test_phase10_auth_hardening.py | 169 ++++++++++++++++++ 18 files changed, 504 insertions(+), 15 deletions(-) rename backend/static/assets/{Dashboard-BtJ8u-Vn.js => Dashboard-D0XMO9O-.js} (99%) rename backend/static/assets/{EmptyState-12OPdy7G.js => EmptyState-D9mKpvu9.js} (99%) rename backend/static/assets/{FRUPage-C090S3Sn.js => FRUPage-B2eK3zkd.js} (98%) rename backend/static/assets/{FanPilotPage-DBREHJWC.js => FanPilotPage-Dq68oKy3.js} (99%) rename backend/static/assets/{LanguageSelect-Cgo_vqvE.js => LanguageSelect-yb_avW7t.js} (99%) rename backend/static/assets/{LoginPage-Bq8jXMVx.js => LoginPage-htt-iwGP.js} (98%) rename backend/static/assets/{ModulesPage-DAGhthzy.js => ModulesPage-BXE9Cdoj.js} (98%) rename backend/static/assets/{SELPage-BWtaElar.js => SELPage-CreMDWWE.js} (96%) create mode 100644 backend/static/assets/SettingsPage-BS6Vz2aR.js delete mode 100644 backend/static/assets/SettingsPage-DTxmRYKO.js rename backend/static/assets/{SetupPage-wPD1HYfu.js => SetupPage-BsG7fLuD.js} (98%) rename backend/static/assets/{index-l2esYWfi.js => index-DXNHFWmw.js} (99%) create mode 100644 frontend/src/pages/settings/SecuritySection.test.tsx diff --git a/backend/api/auth_routes.py b/backend/api/auth_routes.py index 0e986a8..4719ecb 100644 --- a/backend/api/auth_routes.py +++ b/backend/api/auth_routes.py @@ -47,6 +47,10 @@ class SetupRequest(BaseModel): class ConfigureRequest(BaseModel): username: str password: str + # SEC-05 (F6/F10): required whenever an account already exists — including on an + # auth-DISABLED instance, which is the F10 window. Optional only at genuine first + # run (no account), where there is no password to prove knowledge of. + current_password: str | None = None class ToggleRequest(BaseModel): @@ -169,10 +173,38 @@ async def configure_auth(body: ConfigureRequest, request: Request, response: Res endpoint is NOT an unauthenticated credential-takeover path. Issues a fresh session cookie for the new username so the operator stays logged in (and the new cookie passes the require_auth current-user check while any old-username cookie is rejected). + + SEC-05 (F6, and F10 as a side effect): a valid-looking session was the ONLY thing + standing between a caller and a rewrite of the sole account — the exact action an + incident responder takes to evict an attacker. Proving knowledge of the CURRENT + password is now required whenever an account exists. + + The gate keys on `has_user()`, NOT on `auth_enabled`. That keying is load-bearing: + keying on `auth_enabled` would leave the F10 window wide open, because an + auth-disabled instance with an existing account could be seized with no cookie at + all — and the frontend only shows this form when auth is OFF, so an `auth_enabled` + condition would never fire from the UI. + + With no account present nothing is required: that is genuine first run. """ from backend.main import auth await _require_session_if_active(request, auth) + if await auth.has_user(): + if not body.current_password: + return {"success": False, "error": "Current password is required"} + # On an auth-disabled instance there is no session to name the current user, + # so fall back to the single stored account row (the users table is single-user). + token = request.cookies.get("session") + current_username = await auth.verify_session_token_async(token) if token else None + if not current_username: + row = await auth.db.fetchone("SELECT username FROM users LIMIT 1") + current_username = row["username"] if row else None + if not current_username or not await auth.verify_password( + current_username, body.current_password + ): + return {"success": False, "error": "Incorrect password"} + try: await auth.replace_user(body.username, body.password) except ValueError as e: diff --git a/backend/static/assets/Dashboard-BtJ8u-Vn.js b/backend/static/assets/Dashboard-D0XMO9O-.js similarity index 99% rename from backend/static/assets/Dashboard-BtJ8u-Vn.js rename to backend/static/assets/Dashboard-D0XMO9O-.js index cb56875..56aaa2f 100644 --- a/backend/static/assets/Dashboard-BtJ8u-Vn.js +++ b/backend/static/assets/Dashboard-D0XMO9O-.js @@ -1,4 +1,4 @@ -import{C as e,F as t,I as n,M as r,P as i,S as a,_ as o,a as s,c,g as l,i as u,l as d,n as f,o as p,r as m,v as h,x as g}from"./auth-store-CVoL-wZN.js";import{t as _}from"./chevron-down-CfmQL6fD.js";import{t as v}from"./circle-check-ClDS3H6l.js";import{n as y,t as b}from"./pencil-B4_dOeTs.js";import{n as x,t as S}from"./octagon-alert-DjLcypqO.js";import{n as C,r as w,t as T}from"./EmptyState-12OPdy7G.js";import{n as E,t as D}from"./zap-DybMssoI.js";import{t as O}from"./refresh-cw-Cp5njxos.js";import{a as k,i as A,n as j,o as M,r as N,t as ee}from"./sensorUtils-dM-phdaF.js";import{t as te}from"./thermometer-C8NQPlaj.js";import{t as P}from"./triangle-alert-O0Mm9jLA.js";import{A as ne,B as F,D as re,F as ie,H as ae,I as oe,O as se,R as I,S as L,T as ce,U as R,V as le,i as ue,j as de,r as fe,s as pe,t as me,v as he,w as z,x as B,y as ge,z as V}from"./index-l2esYWfi.js";var _e=f(`chart-line`,[[`path`,{d:`M3 3v16a2 2 0 0 0 2 2h16`,key:`c24i48`}],[`path`,{d:`m19 9-5 5-4-4-3 3`,key:`2osh9i`}]]),ve=f(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),ye=f(`circle-question-mark`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3`,key:`1u773s`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),be=f(`layout-grid`,[[`rect`,{width:`7`,height:`7`,x:`3`,y:`3`,rx:`1`,key:`1g98yp`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`3`,rx:`1`,key:`6d4xhi`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`14`,rx:`1`,key:`nxv5o0`}],[`rect`,{width:`7`,height:`7`,x:`3`,y:`14`,rx:`1`,key:`1bb6yr`}]]);function xe(e){let{margin:t,containerPadding:n,containerWidth:r,cols:i}=e;return(r-t[0]*(i-1)-n[0]*2)/i}function Se(e,t,n){return Number.isFinite(e)?Math.round(t*e+Math.max(0,e-1)*n):e}function Ce(e,t,n,r,i,a,o){let{margin:s,containerPadding:c,rowHeight:l}=e,u=xe(e),d,f,p,m;if(o?(d=Math.round(o.width),f=Math.round(o.height)):(d=Se(r,u,s[0]),f=Se(i,l,s[1])),a?(p=Math.round(a.top),m=Math.round(a.left)):o?(p=Math.round(o.top),m=Math.round(o.left)):(p=Math.round((l+s[1])*n+c[1]),m=Math.round((u+s[0])*t+c[0])),!a&&!o){if(Number.isFinite(r)){let e=Math.round((u+s[0])*(t+r)+c[0])-m-d;e!==s[0]&&(d+=e-s[0])}if(Number.isFinite(i)){let e=Math.round((l+s[1])*(n+i)+c[1])-p-f;e!==s[1]&&(f+=e-s[1])}}return{top:p,left:m,width:d,height:f}}function we(e,t,n,r,i){let{margin:a,containerPadding:o,cols:s,rowHeight:c,maxRows:l}=e,u=xe(e),d=Math.round((n-o[0])/(u+a[0])),f=Math.round((t-o[1])/(c+a[1]));return d=De(d,0,s-r),f=De(f,0,l-i),{x:d,y:f}}function Te(e,t,n){let{margin:r,containerPadding:i,rowHeight:a}=e,o=xe(e);return{x:Math.round((n-i[0])/(o+r[0])),y:Math.round((t-i[1])/(a+r[1]))}}function Ee(e,t,n){let{margin:r,rowHeight:i}=e,a=xe(e);return{w:Math.max(1,Math.round((t+r[0])/(a+r[0]))),h:Math.max(1,Math.round((n+r[1])/(i+r[1])))}}function De(e,t,n){return Math.max(Math.min(e,n),t)}function Oe(e,t){return!(e.i===t.i||e.x+e.w<=t.x||e.x>=t.x+t.w||e.y+e.h<=t.y||e.y>=t.y+t.h)}function ke(e,t){for(let n=0;nOe(e,t))}function je(e,t){return t===`horizontal`?Ne(e):t===`vertical`||t===`wrap`?Me(e):[...e]}function Me(e){return[...e].sort((e,t)=>e.y===t.y?e.x-t.x:e.y-t.y)}function Ne(e){return[...e].sort((e,t)=>e.x===t.x?e.y-t.y:e.x-t.x)}function Pe(e){let t=0;for(let n=0;nt&&(t=e)}}return t}function Fe(e,t){for(let n=0;ne.static===!0)}function Le(e){return{i:e.i,x:e.x,y:e.y,w:e.w,h:e.h,minW:e.minW,maxW:e.maxW,minH:e.minH,maxH:e.maxH,moved:!!e.moved,static:!!e.static,isDraggable:e.isDraggable,isResizable:e.isResizable,resizeHandles:e.resizeHandles,constraints:e.constraints,isBounded:e.isBounded}}function Re(e){let t=Array(e.length);for(let n=0;nt.cols&&(i.x=t.cols-i.w),i.x<0&&(i.x=0,i.w=t.cols),!i.static)n.push(i);else for(;ke(n,i);)i.y++}return e}function He(e,t,n,r,i,a,o,s,c){if(t.static&&t.isDraggable!==!0||t.y===r&&t.x===n)return[...e];let l=t.x,u=t.y;typeof n==`number`&&(t.x=n),typeof r==`number`&&(t.y=r),t.moved=!0;let d=je(e,o);(o===`vertical`&&typeof r==`number`?u>=r:o===`horizontal`&&typeof n==`number`&&l>=n)&&(d=d.reverse());let f=Ae(d,t),p=f.length>0;if(p&&c)return Re(e);if(p&&a)return t.x=l,t.y=u,t.moved=!1,e;let m=[...e];for(let e=0;et.y,d=l!==void 0&&t.x+t.w>l.x;if(!l)return He(e,n,o?a.x:void 0,s?a.y:void 0,r,c,i);if(u&&s)return He(e,n,void 0,n.y+1,r,c,i);if(u&&i===null)return t.y=n.y,n.y+=n.h,[...e];if(d&&o)return He(e,t,n.x,void 0,r,c,i)}let l=o?n.x+1:void 0,u=s?n.y+1:void 0;return l===void 0&&u===void 0?[...e]:He(e,n,l,u,r,c,i)}function We(e,t,n){return Math.max(t,Math.min(n,e))}var Ge=[{name:`gridBounds`,constrainPosition(e,t,n,{cols:r,maxRows:i}){return{x:We(t,0,Math.max(0,r-e.w)),y:We(n,0,Math.max(0,i-e.h))}},constrainSize(e,t,n,r,{cols:i,maxRows:a}){let o=r===`w`||r===`nw`||r===`sw`?e.x+e.w:i-e.x,s=r===`n`||r===`nw`||r===`ne`?e.y+e.h:a-e.y;return{w:We(t,1,Math.max(1,o)),h:We(n,1,Math.max(1,s))}}},{name:`minMaxSize`,constrainSize(e,t,n){return{w:We(t,e.minW??1,e.maxW??1/0),h:We(n,e.minH??1,e.maxH??1/0)}}}];function Ke(e,t,n,r,i){let a={x:n,y:r};for(let n of e)n.constrainPosition&&(a=n.constrainPosition(t,a.x,a.y,i));if(t.constraints)for(let e of t.constraints)e.constrainPosition&&(a=e.constrainPosition(t,a.x,a.y,i));return a}function qe(e,t,n,r,i,a){let o={w:n,h:r};for(let n of e)n.constrainSize&&(o=n.constrainSize(t,o.w,o.h,i,a));if(t.constraints)for(let e of t.constraints)e.constrainSize&&(o=e.constrainSize(t,o.w,o.h,i,a));return o}function Je({top:e,left:t,width:n,height:r}){let i=`translate(${t}px,${e}px)`;return{transform:i,WebkitTransform:i,MozTransform:i,msTransform:i,OTransform:i,width:`${n}px`,height:`${r}px`,position:`absolute`}}function Ye({top:e,left:t,width:n,height:r}){return{top:`${e}px`,left:`${t}px`,width:`${n}px`,height:`${r}px`,position:`absolute`}}function Xe(e){return e*100+`%`}function Ze(e,t,n,r){return e+n>r?t:n}function Qe(e,t,n){return e<0?t:n}function $e(e){return Math.max(0,e)}function et(e){return Math.max(0,e)}var tt=(e,t,n)=>{let{left:r,height:i,width:a}=t,o=e.top-(i-e.height);return{left:r,width:a,height:Qe(o,e.height,i),top:et(o)}},nt=(e,t,n)=>{let{top:r,left:i,height:a,width:o}=t;return{top:r,height:a,width:Ze(e.left,e.width,o,n),left:$e(i)}},rt=(e,t,n)=>{let{top:r,height:i,width:a}=t,o=e.left+e.width-a;return o<0?{height:i,width:e.left+e.width,top:et(r),left:0}:{height:i,width:a,top:et(r),left:o}},it=(e,t,n)=>{let{top:r,left:i,height:a,width:o}=t;return{width:o,left:i,height:Qe(r,e.height,a),top:et(r)}},at={n:tt,ne:(e,t,n)=>tt(e,nt(e,t,n)),e:nt,se:(e,t,n)=>it(e,nt(e,t,n)),s:it,sw:(e,t,n)=>it(e,rt(e,t)),w:rt,nw:(e,t,n)=>tt(e,rt(e,t))};function ot(e,t,n,r){let i=at[e];return i?i(t,{...t,...n},r):n}var st={type:`transform`,scale:1,calcStyle(e){return Je(e)}},ct={cols:12,rowHeight:150,margin:[10,10],containerPadding:null,maxRows:1/0},lt={enabled:!0,bounded:!1,threshold:3},ut={enabled:!0,handles:[`se`]},dt={enabled:!1,defaultItem:{w:1,h:1}};function ft(e,t,n,r,i){let a=r===`x`?`w`:`h`;t[r]+=1;let o=e.findIndex(e=>e.i===t.i),s=i??Ie(e).length>0;for(let i=o+1;it.y+t.h)break;Oe(t,o)&&ft(e,o,n+t[a],r,s)}}t[r]=n}function pt(e,t,n,r){for(t.x=Math.max(t.x,0),t.y=Math.max(t.y,0),t.y=Math.min(r,t.y);t.y>0&&!ke(e,t);)t.y--;let i;for(;(i=ke(e,t))!==void 0;)ft(n,t,i.y+i.h,`y`);return t.y=Math.max(t.y,0),t}function mt(e,t,n,r){for(t.x=Math.max(t.x,0),t.y=Math.max(t.y,0);t.x>0&&!ke(e,t);)t.x--;let i;for(;(i=ke(e,t))!==void 0;)if(ft(r,t,i.x+i.w,`x`),t.x+t.w>n)for(t.x=n-t.w,t.y++;t.x>0&&!ke(e,t);)t.x--;return t.x=Math.max(t.x,0),t}var ht={type:`vertical`,allowOverlap:!1,compact(e,t){let n=Ie(e),r=Pe(n),i=Me(e),a=Array(e.length);for(let t=0;te[t]-e[n])}function Ct(e,t){let n=St(e),r=n[0];if(r===void 0)throw Error(`No breakpoints defined`);for(let i=1;ie[a]&&(r=a)}return r}function wt(e,t){let n=t[e];if(n===void 0)throw Error(`ResponsiveReactGridLayout: \`cols\` entry for breakpoint ${String(e)} is missing!`);return n}function Tt(e,t,n,r,i,a){let o=e[n];if(o)return Re(o);let s=e[r],c=St(t),l=c.slice(c.indexOf(n));for(let t=0;t{(function(n,r){typeof e==`object`&&t!==void 0?r(e):typeof define==`function`&&define.amd?define([`exports`],r):(n=typeof globalThis<`u`?globalThis:n||self,r(n[`fast-equals`]={}))})(e,(function(e){function t(e){return function(t,n,r,i,a,o,s){return e(t,n,s)}}function n(e){return function(t,n,r,i){if(!t||!n||typeof t!=`object`||typeof n!=`object`)return e(t,n,r,i);var a=i.get(t),o=i.get(n);if(a&&o)return a===n&&o===t;i.set(t,n),i.set(n,t);var s=e(t,n,r,i);return i.delete(t),i.delete(n),s}}function r(e,t){var n={};for(var r in e)n[r]=e[r];for(var r in t)n[r]=t[r];return n}function i(e){return e.constructor===Object||e.constructor==null}function a(e){return typeof e.then==`function`}function o(e,t){return e===t||e!==e&&t!==t}var s=`[object Arguments]`,c=`[object Boolean]`,l=`[object Date]`,u=`[object RegExp]`,d=`[object Map]`,f=`[object Number]`,p=`[object Object]`,m=`[object Set]`,h=`[object String]`,g=Object.prototype.toString;function _(e){var t=e.areArraysEqual,n=e.areDatesEqual,r=e.areMapsEqual,_=e.areObjectsEqual,v=e.areRegExpsEqual,y=e.areSetsEqual,b=e.createIsNestedEqual,x=b(S);function S(e,b,S){if(e===b)return!0;if(!e||!b||typeof e!=`object`||typeof b!=`object`)return e!==e&&b!==b;if(i(e)&&i(b))return _(e,b,x,S);var C=Array.isArray(e),w=Array.isArray(b);if(C||w)return C===w&&t(e,b,x,S);var T=g.call(e);return T===g.call(b)?T===l?n(e,b,x,S):T===u?v(e,b,x,S):T===d?r(e,b,x,S):T===m?y(e,b,x,S):T===p||T===s?a(e)||a(b)?!1:_(e,b,x,S):T===c||T===f||T===h?o(e.valueOf(),b.valueOf()):!1:!1}return S}function v(e,t,n,r){var i=e.length;if(t.length!==i)return!1;for(;i-- >0;)if(!n(e[i],t[i],i,i,e,t,r))return!1;return!0}var y=n(v);function b(e,t){return o(e.valueOf(),t.valueOf())}function x(e,t,n,r){var i=e.size===t.size;if(!i)return!1;if(!e.size)return!0;var a={},o=0;return e.forEach(function(s,c){if(i){var l=!1,u=0;t.forEach(function(i,d){!l&&!a[u]&&(l=n(c,d,o,u,e,t,r)&&n(s,i,c,d,e,t,r))&&(a[u]=!0),u++}),o++,i=l}}),i}var S=n(x),C=`_owner`,w=Object.prototype.hasOwnProperty;function T(e,t,n,r){var i=Object.keys(e),a=i.length;if(Object.keys(t).length!==a)return!1;for(var o;a-- >0;){if(o=i[a],o===C){var s=!!e.$$typeof,c=!!t.$$typeof;if((s||c)&&s!==c)return!1}if(!w.call(t,o)||!n(e[o],t[o],o,o,e,t,r))return!1}return!0}var E=n(T);function D(e,t){return e.source===t.source&&e.flags===t.flags}function O(e,t,n,r){var i=e.size===t.size;if(!i)return!1;if(!e.size)return!0;var a={};return e.forEach(function(o,s){if(i){var c=!1,l=0;t.forEach(function(i,u){!c&&!a[l]&&(c=n(o,i,s,u,e,t,r))&&(a[l]=!0),l++}),i=c}}),i}var k=n(O),A=Object.freeze({areArraysEqual:v,areDatesEqual:b,areMapsEqual:x,areObjectsEqual:T,areRegExpsEqual:D,areSetsEqual:O,createIsNestedEqual:t}),j=Object.freeze({areArraysEqual:y,areDatesEqual:b,areMapsEqual:S,areObjectsEqual:E,areRegExpsEqual:D,areSetsEqual:k,createIsNestedEqual:t}),M=_(A);function N(e,t){return M(e,t,void 0)}var ee=_(r(A,{createIsNestedEqual:function(){return o}}));function te(e,t){return ee(e,t,void 0)}var P=_(j);function ne(e,t){return P(e,t,new WeakMap)}var F=_(r(j,{createIsNestedEqual:function(){return o}}));function re(e,t){return F(e,t,new WeakMap)}function ie(e){return _(r(A,e(A)))}function ae(e){var t=_(r(j,e(j)));return(function(e,n,r){return r===void 0&&(r=new WeakMap),t(e,n,r)})}e.circularDeepEqual=ne,e.circularShallowEqual=re,e.createCustomCircularEqual=ae,e.createCustomEqual=ie,e.deepEqual=N,e.sameValueZeroEqual=o,e.shallowEqual=te,Object.defineProperty(e,`__esModule`,{value:!0})}))})),Ot=i(((e,t)=>{t.exports=`SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED`})),kt=i(((e,t)=>{var n=Ot();function r(){}function i(){}i.resetWarningCache=r,t.exports=function(){function e(e,t,r,i,a,o){if(o!==n){var s=Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name=`Invariant Violation`,s}}e.isRequired=e;function t(){return e}var a={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:r};return a.PropTypes=a,a}})),At=i(((e,t)=>{t.exports=kt()()})),jt=i(((e,t)=>{function n(e){var t,r,i=``;if(typeof e==`string`||typeof e==`number`)i+=e;else if(typeof e==`object`)if(Array.isArray(e)){var a=e.length;for(t=0;t{Object.defineProperty(e,`__esModule`,{value:!0}),e.dontSetMe=a,e.findInArray=t,e.int=i,e.isFunction=n,e.isNum=r;function t(e,t){for(let n=0,r=e.length;n{Object.defineProperty(e,`__esModule`,{value:!0}),e.browserPrefixToKey=r,e.browserPrefixToStyle=i,e.default=void 0,e.getPrefix=n;var t=[`Moz`,`Webkit`,`O`,`ms`];function n(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:`transform`;if(typeof window>`u`)return``;let n=window.document?.documentElement?.style;if(!n||e in n)return``;for(let i=0;i{Object.defineProperty(e,`__esModule`,{value:!0}),e.addClassName=S,e.addEvent=s,e.addUserSelectStyles=y,e.createCSSTransform=m,e.createSVGTransform=h,e.getTouch=_,e.getTouchIdentifier=v,e.getTranslation=g,e.innerHeight=d,e.innerWidth=f,e.matchesSelector=a,e.matchesSelectorAndParentsTo=o,e.offsetXYFromParent=p,e.outerHeight=l,e.outerWidth=u,e.removeClassName=C,e.removeEvent=c,e.scheduleRemoveUserSelectStyles=b;var t=Mt(),n=r(Nt());function r(e,t){if(typeof WeakMap==`function`)var n=new WeakMap,i=new WeakMap;return(r=function(e,t){if(!t&&e&&e.__esModule)return e;var r,a,o={__proto__:null,default:e};if(e===null||typeof e!=`object`&&typeof e!=`function`)return o;if(r=t?i:n){if(r.has(e))return r.get(e);r.set(e,o)}for(let t in e)t!==`default`&&{}.hasOwnProperty.call(e,t)&&((a=(r=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(a.get||a.set)?r(o,t,a):o[t]=e[t]);return o})(e,t)}var i=``;function a(e,n){return i||=(0,t.findInArray)([`matches`,`webkitMatchesSelector`,`mozMatchesSelector`,`msMatchesSelector`,`oMatchesSelector`],function(n){return(0,t.isFunction)(e[n])}),(0,t.isFunction)(e[i])?e[i](n):!1}function o(e,t,n){let r=e;do{if(a(r,t))return!0;if(r===n)return!1;r=r.parentNode}while(r);return!1}function s(e,t,n,r){if(!e)return;let i={capture:!0,...r};e.addEventListener?e.addEventListener(t,n,i):e.attachEvent?e.attachEvent(`on`+t,n):e[`on`+t]=n}function c(e,t,n,r){if(!e)return;let i={capture:!0,...r};e.removeEventListener?e.removeEventListener(t,n,i):e.detachEvent?e.detachEvent(`on`+t,n):e[`on`+t]=null}function l(e){let n=e.clientHeight,r=e.ownerDocument.defaultView.getComputedStyle(e);return n+=(0,t.int)(r.borderTopWidth),n+=(0,t.int)(r.borderBottomWidth),n}function u(e){let n=e.clientWidth,r=e.ownerDocument.defaultView.getComputedStyle(e);return n+=(0,t.int)(r.borderLeftWidth),n+=(0,t.int)(r.borderRightWidth),n}function d(e){let n=e.clientHeight,r=e.ownerDocument.defaultView.getComputedStyle(e);return n-=(0,t.int)(r.paddingTop),n-=(0,t.int)(r.paddingBottom),n}function f(e){let n=e.clientWidth,r=e.ownerDocument.defaultView.getComputedStyle(e);return n-=(0,t.int)(r.paddingLeft),n-=(0,t.int)(r.paddingRight),n}function p(e,t,n){let r=t===t.ownerDocument.body?{left:0,top:0}:t.getBoundingClientRect();return{x:(e.clientX+t.scrollLeft-r.left)/n,y:(e.clientY+t.scrollTop-r.top)/n}}function m(e,t){let r=g(e,t,`px`);return{[(0,n.browserPrefixToKey)(`transform`,n.default)]:r}}function h(e,t){return g(e,t,``)}function g(e,t,n){let{x:r,y:i}=e,a=`translate(${r}${n},${i}${n})`;return t&&(a=`translate(${`${typeof t.x==`string`?t.x:t.x+n}`}, ${`${typeof t.y==`string`?t.y:t.y+n}`})`+a),a}function _(e,n){return e.targetTouches&&(0,t.findInArray)(e.targetTouches,e=>n===e.identifier)||e.changedTouches&&(0,t.findInArray)(e.changedTouches,e=>n===e.identifier)}function v(e){if(e.targetTouches&&e.targetTouches[0])return e.targetTouches[0].identifier;if(e.changedTouches&&e.changedTouches[0])return e.changedTouches[0].identifier}function y(e){if(!e)return;let t=e.getElementById(`react-draggable-style-el`);t||(t=e.createElement(`style`),t.type=`text/css`,t.id=`react-draggable-style-el`,t.innerHTML=`.react-draggable-transparent-selection *::-moz-selection {all: inherit;} +import{C as e,F as t,I as n,M as r,P as i,S as a,_ as o,a as s,c,g as l,i as u,l as d,n as f,o as p,r as m,v as h,x as g}from"./auth-store-CVoL-wZN.js";import{t as _}from"./chevron-down-CfmQL6fD.js";import{t as v}from"./circle-check-ClDS3H6l.js";import{n as y,t as b}from"./pencil-B4_dOeTs.js";import{n as x,t as S}from"./octagon-alert-DjLcypqO.js";import{n as C,r as w,t as T}from"./EmptyState-D9mKpvu9.js";import{n as E,t as D}from"./zap-DybMssoI.js";import{t as O}from"./refresh-cw-Cp5njxos.js";import{a as k,i as A,n as j,o as M,r as N,t as ee}from"./sensorUtils-dM-phdaF.js";import{t as te}from"./thermometer-C8NQPlaj.js";import{t as P}from"./triangle-alert-O0Mm9jLA.js";import{A as ne,B as F,D as re,F as ie,H as ae,I as oe,O as se,R as I,S as L,T as ce,U as R,V as le,i as ue,j as de,r as fe,s as pe,t as me,v as he,w as z,x as B,y as ge,z as V}from"./index-DXNHFWmw.js";var _e=f(`chart-line`,[[`path`,{d:`M3 3v16a2 2 0 0 0 2 2h16`,key:`c24i48`}],[`path`,{d:`m19 9-5 5-4-4-3 3`,key:`2osh9i`}]]),ve=f(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),ye=f(`circle-question-mark`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3`,key:`1u773s`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),be=f(`layout-grid`,[[`rect`,{width:`7`,height:`7`,x:`3`,y:`3`,rx:`1`,key:`1g98yp`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`3`,rx:`1`,key:`6d4xhi`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`14`,rx:`1`,key:`nxv5o0`}],[`rect`,{width:`7`,height:`7`,x:`3`,y:`14`,rx:`1`,key:`1bb6yr`}]]);function xe(e){let{margin:t,containerPadding:n,containerWidth:r,cols:i}=e;return(r-t[0]*(i-1)-n[0]*2)/i}function Se(e,t,n){return Number.isFinite(e)?Math.round(t*e+Math.max(0,e-1)*n):e}function Ce(e,t,n,r,i,a,o){let{margin:s,containerPadding:c,rowHeight:l}=e,u=xe(e),d,f,p,m;if(o?(d=Math.round(o.width),f=Math.round(o.height)):(d=Se(r,u,s[0]),f=Se(i,l,s[1])),a?(p=Math.round(a.top),m=Math.round(a.left)):o?(p=Math.round(o.top),m=Math.round(o.left)):(p=Math.round((l+s[1])*n+c[1]),m=Math.round((u+s[0])*t+c[0])),!a&&!o){if(Number.isFinite(r)){let e=Math.round((u+s[0])*(t+r)+c[0])-m-d;e!==s[0]&&(d+=e-s[0])}if(Number.isFinite(i)){let e=Math.round((l+s[1])*(n+i)+c[1])-p-f;e!==s[1]&&(f+=e-s[1])}}return{top:p,left:m,width:d,height:f}}function we(e,t,n,r,i){let{margin:a,containerPadding:o,cols:s,rowHeight:c,maxRows:l}=e,u=xe(e),d=Math.round((n-o[0])/(u+a[0])),f=Math.round((t-o[1])/(c+a[1]));return d=De(d,0,s-r),f=De(f,0,l-i),{x:d,y:f}}function Te(e,t,n){let{margin:r,containerPadding:i,rowHeight:a}=e,o=xe(e);return{x:Math.round((n-i[0])/(o+r[0])),y:Math.round((t-i[1])/(a+r[1]))}}function Ee(e,t,n){let{margin:r,rowHeight:i}=e,a=xe(e);return{w:Math.max(1,Math.round((t+r[0])/(a+r[0]))),h:Math.max(1,Math.round((n+r[1])/(i+r[1])))}}function De(e,t,n){return Math.max(Math.min(e,n),t)}function Oe(e,t){return!(e.i===t.i||e.x+e.w<=t.x||e.x>=t.x+t.w||e.y+e.h<=t.y||e.y>=t.y+t.h)}function ke(e,t){for(let n=0;nOe(e,t))}function je(e,t){return t===`horizontal`?Ne(e):t===`vertical`||t===`wrap`?Me(e):[...e]}function Me(e){return[...e].sort((e,t)=>e.y===t.y?e.x-t.x:e.y-t.y)}function Ne(e){return[...e].sort((e,t)=>e.x===t.x?e.y-t.y:e.x-t.x)}function Pe(e){let t=0;for(let n=0;nt&&(t=e)}}return t}function Fe(e,t){for(let n=0;ne.static===!0)}function Le(e){return{i:e.i,x:e.x,y:e.y,w:e.w,h:e.h,minW:e.minW,maxW:e.maxW,minH:e.minH,maxH:e.maxH,moved:!!e.moved,static:!!e.static,isDraggable:e.isDraggable,isResizable:e.isResizable,resizeHandles:e.resizeHandles,constraints:e.constraints,isBounded:e.isBounded}}function Re(e){let t=Array(e.length);for(let n=0;nt.cols&&(i.x=t.cols-i.w),i.x<0&&(i.x=0,i.w=t.cols),!i.static)n.push(i);else for(;ke(n,i);)i.y++}return e}function He(e,t,n,r,i,a,o,s,c){if(t.static&&t.isDraggable!==!0||t.y===r&&t.x===n)return[...e];let l=t.x,u=t.y;typeof n==`number`&&(t.x=n),typeof r==`number`&&(t.y=r),t.moved=!0;let d=je(e,o);(o===`vertical`&&typeof r==`number`?u>=r:o===`horizontal`&&typeof n==`number`&&l>=n)&&(d=d.reverse());let f=Ae(d,t),p=f.length>0;if(p&&c)return Re(e);if(p&&a)return t.x=l,t.y=u,t.moved=!1,e;let m=[...e];for(let e=0;et.y,d=l!==void 0&&t.x+t.w>l.x;if(!l)return He(e,n,o?a.x:void 0,s?a.y:void 0,r,c,i);if(u&&s)return He(e,n,void 0,n.y+1,r,c,i);if(u&&i===null)return t.y=n.y,n.y+=n.h,[...e];if(d&&o)return He(e,t,n.x,void 0,r,c,i)}let l=o?n.x+1:void 0,u=s?n.y+1:void 0;return l===void 0&&u===void 0?[...e]:He(e,n,l,u,r,c,i)}function We(e,t,n){return Math.max(t,Math.min(n,e))}var Ge=[{name:`gridBounds`,constrainPosition(e,t,n,{cols:r,maxRows:i}){return{x:We(t,0,Math.max(0,r-e.w)),y:We(n,0,Math.max(0,i-e.h))}},constrainSize(e,t,n,r,{cols:i,maxRows:a}){let o=r===`w`||r===`nw`||r===`sw`?e.x+e.w:i-e.x,s=r===`n`||r===`nw`||r===`ne`?e.y+e.h:a-e.y;return{w:We(t,1,Math.max(1,o)),h:We(n,1,Math.max(1,s))}}},{name:`minMaxSize`,constrainSize(e,t,n){return{w:We(t,e.minW??1,e.maxW??1/0),h:We(n,e.minH??1,e.maxH??1/0)}}}];function Ke(e,t,n,r,i){let a={x:n,y:r};for(let n of e)n.constrainPosition&&(a=n.constrainPosition(t,a.x,a.y,i));if(t.constraints)for(let e of t.constraints)e.constrainPosition&&(a=e.constrainPosition(t,a.x,a.y,i));return a}function qe(e,t,n,r,i,a){let o={w:n,h:r};for(let n of e)n.constrainSize&&(o=n.constrainSize(t,o.w,o.h,i,a));if(t.constraints)for(let e of t.constraints)e.constrainSize&&(o=e.constrainSize(t,o.w,o.h,i,a));return o}function Je({top:e,left:t,width:n,height:r}){let i=`translate(${t}px,${e}px)`;return{transform:i,WebkitTransform:i,MozTransform:i,msTransform:i,OTransform:i,width:`${n}px`,height:`${r}px`,position:`absolute`}}function Ye({top:e,left:t,width:n,height:r}){return{top:`${e}px`,left:`${t}px`,width:`${n}px`,height:`${r}px`,position:`absolute`}}function Xe(e){return e*100+`%`}function Ze(e,t,n,r){return e+n>r?t:n}function Qe(e,t,n){return e<0?t:n}function $e(e){return Math.max(0,e)}function et(e){return Math.max(0,e)}var tt=(e,t,n)=>{let{left:r,height:i,width:a}=t,o=e.top-(i-e.height);return{left:r,width:a,height:Qe(o,e.height,i),top:et(o)}},nt=(e,t,n)=>{let{top:r,left:i,height:a,width:o}=t;return{top:r,height:a,width:Ze(e.left,e.width,o,n),left:$e(i)}},rt=(e,t,n)=>{let{top:r,height:i,width:a}=t,o=e.left+e.width-a;return o<0?{height:i,width:e.left+e.width,top:et(r),left:0}:{height:i,width:a,top:et(r),left:o}},it=(e,t,n)=>{let{top:r,left:i,height:a,width:o}=t;return{width:o,left:i,height:Qe(r,e.height,a),top:et(r)}},at={n:tt,ne:(e,t,n)=>tt(e,nt(e,t,n)),e:nt,se:(e,t,n)=>it(e,nt(e,t,n)),s:it,sw:(e,t,n)=>it(e,rt(e,t)),w:rt,nw:(e,t,n)=>tt(e,rt(e,t))};function ot(e,t,n,r){let i=at[e];return i?i(t,{...t,...n},r):n}var st={type:`transform`,scale:1,calcStyle(e){return Je(e)}},ct={cols:12,rowHeight:150,margin:[10,10],containerPadding:null,maxRows:1/0},lt={enabled:!0,bounded:!1,threshold:3},ut={enabled:!0,handles:[`se`]},dt={enabled:!1,defaultItem:{w:1,h:1}};function ft(e,t,n,r,i){let a=r===`x`?`w`:`h`;t[r]+=1;let o=e.findIndex(e=>e.i===t.i),s=i??Ie(e).length>0;for(let i=o+1;it.y+t.h)break;Oe(t,o)&&ft(e,o,n+t[a],r,s)}}t[r]=n}function pt(e,t,n,r){for(t.x=Math.max(t.x,0),t.y=Math.max(t.y,0),t.y=Math.min(r,t.y);t.y>0&&!ke(e,t);)t.y--;let i;for(;(i=ke(e,t))!==void 0;)ft(n,t,i.y+i.h,`y`);return t.y=Math.max(t.y,0),t}function mt(e,t,n,r){for(t.x=Math.max(t.x,0),t.y=Math.max(t.y,0);t.x>0&&!ke(e,t);)t.x--;let i;for(;(i=ke(e,t))!==void 0;)if(ft(r,t,i.x+i.w,`x`),t.x+t.w>n)for(t.x=n-t.w,t.y++;t.x>0&&!ke(e,t);)t.x--;return t.x=Math.max(t.x,0),t}var ht={type:`vertical`,allowOverlap:!1,compact(e,t){let n=Ie(e),r=Pe(n),i=Me(e),a=Array(e.length);for(let t=0;te[t]-e[n])}function Ct(e,t){let n=St(e),r=n[0];if(r===void 0)throw Error(`No breakpoints defined`);for(let i=1;ie[a]&&(r=a)}return r}function wt(e,t){let n=t[e];if(n===void 0)throw Error(`ResponsiveReactGridLayout: \`cols\` entry for breakpoint ${String(e)} is missing!`);return n}function Tt(e,t,n,r,i,a){let o=e[n];if(o)return Re(o);let s=e[r],c=St(t),l=c.slice(c.indexOf(n));for(let t=0;t{(function(n,r){typeof e==`object`&&t!==void 0?r(e):typeof define==`function`&&define.amd?define([`exports`],r):(n=typeof globalThis<`u`?globalThis:n||self,r(n[`fast-equals`]={}))})(e,(function(e){function t(e){return function(t,n,r,i,a,o,s){return e(t,n,s)}}function n(e){return function(t,n,r,i){if(!t||!n||typeof t!=`object`||typeof n!=`object`)return e(t,n,r,i);var a=i.get(t),o=i.get(n);if(a&&o)return a===n&&o===t;i.set(t,n),i.set(n,t);var s=e(t,n,r,i);return i.delete(t),i.delete(n),s}}function r(e,t){var n={};for(var r in e)n[r]=e[r];for(var r in t)n[r]=t[r];return n}function i(e){return e.constructor===Object||e.constructor==null}function a(e){return typeof e.then==`function`}function o(e,t){return e===t||e!==e&&t!==t}var s=`[object Arguments]`,c=`[object Boolean]`,l=`[object Date]`,u=`[object RegExp]`,d=`[object Map]`,f=`[object Number]`,p=`[object Object]`,m=`[object Set]`,h=`[object String]`,g=Object.prototype.toString;function _(e){var t=e.areArraysEqual,n=e.areDatesEqual,r=e.areMapsEqual,_=e.areObjectsEqual,v=e.areRegExpsEqual,y=e.areSetsEqual,b=e.createIsNestedEqual,x=b(S);function S(e,b,S){if(e===b)return!0;if(!e||!b||typeof e!=`object`||typeof b!=`object`)return e!==e&&b!==b;if(i(e)&&i(b))return _(e,b,x,S);var C=Array.isArray(e),w=Array.isArray(b);if(C||w)return C===w&&t(e,b,x,S);var T=g.call(e);return T===g.call(b)?T===l?n(e,b,x,S):T===u?v(e,b,x,S):T===d?r(e,b,x,S):T===m?y(e,b,x,S):T===p||T===s?a(e)||a(b)?!1:_(e,b,x,S):T===c||T===f||T===h?o(e.valueOf(),b.valueOf()):!1:!1}return S}function v(e,t,n,r){var i=e.length;if(t.length!==i)return!1;for(;i-- >0;)if(!n(e[i],t[i],i,i,e,t,r))return!1;return!0}var y=n(v);function b(e,t){return o(e.valueOf(),t.valueOf())}function x(e,t,n,r){var i=e.size===t.size;if(!i)return!1;if(!e.size)return!0;var a={},o=0;return e.forEach(function(s,c){if(i){var l=!1,u=0;t.forEach(function(i,d){!l&&!a[u]&&(l=n(c,d,o,u,e,t,r)&&n(s,i,c,d,e,t,r))&&(a[u]=!0),u++}),o++,i=l}}),i}var S=n(x),C=`_owner`,w=Object.prototype.hasOwnProperty;function T(e,t,n,r){var i=Object.keys(e),a=i.length;if(Object.keys(t).length!==a)return!1;for(var o;a-- >0;){if(o=i[a],o===C){var s=!!e.$$typeof,c=!!t.$$typeof;if((s||c)&&s!==c)return!1}if(!w.call(t,o)||!n(e[o],t[o],o,o,e,t,r))return!1}return!0}var E=n(T);function D(e,t){return e.source===t.source&&e.flags===t.flags}function O(e,t,n,r){var i=e.size===t.size;if(!i)return!1;if(!e.size)return!0;var a={};return e.forEach(function(o,s){if(i){var c=!1,l=0;t.forEach(function(i,u){!c&&!a[l]&&(c=n(o,i,s,u,e,t,r))&&(a[l]=!0),l++}),i=c}}),i}var k=n(O),A=Object.freeze({areArraysEqual:v,areDatesEqual:b,areMapsEqual:x,areObjectsEqual:T,areRegExpsEqual:D,areSetsEqual:O,createIsNestedEqual:t}),j=Object.freeze({areArraysEqual:y,areDatesEqual:b,areMapsEqual:S,areObjectsEqual:E,areRegExpsEqual:D,areSetsEqual:k,createIsNestedEqual:t}),M=_(A);function N(e,t){return M(e,t,void 0)}var ee=_(r(A,{createIsNestedEqual:function(){return o}}));function te(e,t){return ee(e,t,void 0)}var P=_(j);function ne(e,t){return P(e,t,new WeakMap)}var F=_(r(j,{createIsNestedEqual:function(){return o}}));function re(e,t){return F(e,t,new WeakMap)}function ie(e){return _(r(A,e(A)))}function ae(e){var t=_(r(j,e(j)));return(function(e,n,r){return r===void 0&&(r=new WeakMap),t(e,n,r)})}e.circularDeepEqual=ne,e.circularShallowEqual=re,e.createCustomCircularEqual=ae,e.createCustomEqual=ie,e.deepEqual=N,e.sameValueZeroEqual=o,e.shallowEqual=te,Object.defineProperty(e,`__esModule`,{value:!0})}))})),Ot=i(((e,t)=>{t.exports=`SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED`})),kt=i(((e,t)=>{var n=Ot();function r(){}function i(){}i.resetWarningCache=r,t.exports=function(){function e(e,t,r,i,a,o){if(o!==n){var s=Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name=`Invariant Violation`,s}}e.isRequired=e;function t(){return e}var a={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:r};return a.PropTypes=a,a}})),At=i(((e,t)=>{t.exports=kt()()})),jt=i(((e,t)=>{function n(e){var t,r,i=``;if(typeof e==`string`||typeof e==`number`)i+=e;else if(typeof e==`object`)if(Array.isArray(e)){var a=e.length;for(t=0;t{Object.defineProperty(e,`__esModule`,{value:!0}),e.dontSetMe=a,e.findInArray=t,e.int=i,e.isFunction=n,e.isNum=r;function t(e,t){for(let n=0,r=e.length;n{Object.defineProperty(e,`__esModule`,{value:!0}),e.browserPrefixToKey=r,e.browserPrefixToStyle=i,e.default=void 0,e.getPrefix=n;var t=[`Moz`,`Webkit`,`O`,`ms`];function n(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:`transform`;if(typeof window>`u`)return``;let n=window.document?.documentElement?.style;if(!n||e in n)return``;for(let i=0;i{Object.defineProperty(e,`__esModule`,{value:!0}),e.addClassName=S,e.addEvent=s,e.addUserSelectStyles=y,e.createCSSTransform=m,e.createSVGTransform=h,e.getTouch=_,e.getTouchIdentifier=v,e.getTranslation=g,e.innerHeight=d,e.innerWidth=f,e.matchesSelector=a,e.matchesSelectorAndParentsTo=o,e.offsetXYFromParent=p,e.outerHeight=l,e.outerWidth=u,e.removeClassName=C,e.removeEvent=c,e.scheduleRemoveUserSelectStyles=b;var t=Mt(),n=r(Nt());function r(e,t){if(typeof WeakMap==`function`)var n=new WeakMap,i=new WeakMap;return(r=function(e,t){if(!t&&e&&e.__esModule)return e;var r,a,o={__proto__:null,default:e};if(e===null||typeof e!=`object`&&typeof e!=`function`)return o;if(r=t?i:n){if(r.has(e))return r.get(e);r.set(e,o)}for(let t in e)t!==`default`&&{}.hasOwnProperty.call(e,t)&&((a=(r=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(a.get||a.set)?r(o,t,a):o[t]=e[t]);return o})(e,t)}var i=``;function a(e,n){return i||=(0,t.findInArray)([`matches`,`webkitMatchesSelector`,`mozMatchesSelector`,`msMatchesSelector`,`oMatchesSelector`],function(n){return(0,t.isFunction)(e[n])}),(0,t.isFunction)(e[i])?e[i](n):!1}function o(e,t,n){let r=e;do{if(a(r,t))return!0;if(r===n)return!1;r=r.parentNode}while(r);return!1}function s(e,t,n,r){if(!e)return;let i={capture:!0,...r};e.addEventListener?e.addEventListener(t,n,i):e.attachEvent?e.attachEvent(`on`+t,n):e[`on`+t]=n}function c(e,t,n,r){if(!e)return;let i={capture:!0,...r};e.removeEventListener?e.removeEventListener(t,n,i):e.detachEvent?e.detachEvent(`on`+t,n):e[`on`+t]=null}function l(e){let n=e.clientHeight,r=e.ownerDocument.defaultView.getComputedStyle(e);return n+=(0,t.int)(r.borderTopWidth),n+=(0,t.int)(r.borderBottomWidth),n}function u(e){let n=e.clientWidth,r=e.ownerDocument.defaultView.getComputedStyle(e);return n+=(0,t.int)(r.borderLeftWidth),n+=(0,t.int)(r.borderRightWidth),n}function d(e){let n=e.clientHeight,r=e.ownerDocument.defaultView.getComputedStyle(e);return n-=(0,t.int)(r.paddingTop),n-=(0,t.int)(r.paddingBottom),n}function f(e){let n=e.clientWidth,r=e.ownerDocument.defaultView.getComputedStyle(e);return n-=(0,t.int)(r.paddingLeft),n-=(0,t.int)(r.paddingRight),n}function p(e,t,n){let r=t===t.ownerDocument.body?{left:0,top:0}:t.getBoundingClientRect();return{x:(e.clientX+t.scrollLeft-r.left)/n,y:(e.clientY+t.scrollTop-r.top)/n}}function m(e,t){let r=g(e,t,`px`);return{[(0,n.browserPrefixToKey)(`transform`,n.default)]:r}}function h(e,t){return g(e,t,``)}function g(e,t,n){let{x:r,y:i}=e,a=`translate(${r}${n},${i}${n})`;return t&&(a=`translate(${`${typeof t.x==`string`?t.x:t.x+n}`}, ${`${typeof t.y==`string`?t.y:t.y+n}`})`+a),a}function _(e,n){return e.targetTouches&&(0,t.findInArray)(e.targetTouches,e=>n===e.identifier)||e.changedTouches&&(0,t.findInArray)(e.changedTouches,e=>n===e.identifier)}function v(e){if(e.targetTouches&&e.targetTouches[0])return e.targetTouches[0].identifier;if(e.changedTouches&&e.changedTouches[0])return e.changedTouches[0].identifier}function y(e){if(!e)return;let t=e.getElementById(`react-draggable-style-el`);t||(t=e.createElement(`style`),t.type=`text/css`,t.id=`react-draggable-style-el`,t.innerHTML=`.react-draggable-transparent-selection *::-moz-selection {all: inherit;} `,t.innerHTML+=`.react-draggable-transparent-selection *::selection {all: inherit;} `,e.getElementsByTagName(`head`)[0].appendChild(t)),e.body&&S(e.body,`react-draggable-transparent-selection`)}function b(e){window.requestAnimationFrame?window.requestAnimationFrame(()=>{x(e)}):x(e)}function x(e){if(e)try{if(e.body&&C(e.body,`react-draggable-transparent-selection`),e.selection)e.selection.empty();else{let t=(e.defaultView||window).getSelection();t&&t.type!==`Caret`&&t.removeAllRanges()}}catch{}}function S(e,t){e.classList?e.classList.add(t):e.className.match(RegExp(`(?:^|\\s)${t}(?!\\S)`))||(e.className+=` ${t}`)}function C(e,t){e.classList?e.classList.remove(t):e.className=e.className.replace(RegExp(`(?:^|\\s)${t}(?!\\S)`,`g`),``)}})),Ft=i((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.canDragX=a,e.canDragY=o,e.createCoreData=c,e.createDraggableData=l,e.getBoundPosition=r,e.getControlPosition=s,e.snapToGrid=i;var t=Mt(),n=Pt();function r(e,r,i){if(!e.props.bounds)return[r,i];let{bounds:a}=e.props;a=typeof a==`string`?a:u(a);let o=d(e);if(typeof a==`string`){let{ownerDocument:e}=o,r=e.defaultView,i;if(i=a===`parent`?o.parentNode:o.getRootNode().querySelector(a),!(i instanceof r.HTMLElement))throw Error(`Bounds selector "`+a+`" could not find an element.`);let s=i,c=r.getComputedStyle(o),l=r.getComputedStyle(s);a={left:-o.offsetLeft+(0,t.int)(l.paddingLeft)+(0,t.int)(c.marginLeft),top:-o.offsetTop+(0,t.int)(l.paddingTop)+(0,t.int)(c.marginTop),right:(0,n.innerWidth)(s)-(0,n.outerWidth)(o)-o.offsetLeft+(0,t.int)(l.paddingRight)-(0,t.int)(c.marginRight),bottom:(0,n.innerHeight)(s)-(0,n.outerHeight)(o)-o.offsetTop+(0,t.int)(l.paddingBottom)-(0,t.int)(c.marginBottom)}}return(0,t.isNum)(a.right)&&(r=Math.min(r,a.right)),(0,t.isNum)(a.bottom)&&(i=Math.min(i,a.bottom)),(0,t.isNum)(a.left)&&(r=Math.max(r,a.left)),(0,t.isNum)(a.top)&&(i=Math.max(i,a.top)),[r,i]}function i(e,t,n){return[Math.round(t/e[0])*e[0],Math.round(n/e[1])*e[1]]}function a(e){return e.props.axis===`both`||e.props.axis===`x`}function o(e){return e.props.axis===`both`||e.props.axis===`y`}function s(e,t,r){let i=typeof t==`number`?(0,n.getTouch)(e,t):null;if(typeof t==`number`&&!i)return null;let a=d(r),o=r.props.offsetParent||a.offsetParent||a.ownerDocument.body;return(0,n.offsetXYFromParent)(i||e,o,r.props.scale)}function c(e,n,r){let i=!(0,t.isNum)(e.lastX),a=d(e);return i?{node:a,deltaX:0,deltaY:0,lastX:n,lastY:r,x:n,y:r}:{node:a,deltaX:n-e.lastX,deltaY:r-e.lastY,lastX:e.lastX,lastY:e.lastY,x:n,y:r}}function l(e,t){let n=e.props.scale;return{node:t.node,x:e.state.x+t.deltaX/n,y:e.state.y+t.deltaY/n,deltaX:t.deltaX/n,deltaY:t.deltaY/n,lastX:e.state.x,lastY:e.state.y}}function u(e){return{left:e.left,top:e.top,right:e.right,bottom:e.bottom}}function d(e){let t=e.findDOMNode();if(!t)throw Error(`: Unmounted during event!`);return t}})),It=i((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.default=t;function t(){}})),Lt=i((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.default=void 0;var t=u(r()),n=l(At()),i=l(R()),a=Pt(),o=Ft(),s=Mt(),c=l(It());function l(e){return e&&e.__esModule?e:{default:e}}function u(e,t){if(typeof WeakMap==`function`)var n=new WeakMap,r=new WeakMap;return(u=function(e,t){if(!t&&e&&e.__esModule)return e;var i,a,o={__proto__:null,default:e};if(e===null||typeof e!=`object`&&typeof e!=`function`)return o;if(i=t?r:n){if(i.has(e))return i.get(e);i.set(e,o)}for(let t in e)t!==`default`&&{}.hasOwnProperty.call(e,t)&&((a=(i=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(a.get||a.set)?i(o,t,a):o[t]=e[t]);return o})(e,t)}function d(e,t,n){return(t=f(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function f(e){var t=p(e,`string`);return typeof t==`symbol`?t:t+``}function p(e,t){if(typeof e!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(typeof r!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}var m={touch:{start:`touchstart`,move:`touchmove`,stop:`touchend`},mouse:{start:`mousedown`,move:`mousemove`,stop:`mouseup`}},h=m.mouse,g=class extends t.Component{constructor(){super(...arguments),d(this,`dragging`,!1),d(this,`lastX`,NaN),d(this,`lastY`,NaN),d(this,`touchIdentifier`,null),d(this,`mounted`,!1),d(this,`handleDragStart`,e=>{if(this.props.onMouseDown(e),!this.props.allowAnyClick&&typeof e.button==`number`&&e.button!==0)return!1;let t=this.findDOMNode();if(!t||!t.ownerDocument||!t.ownerDocument.body)throw Error(` not mounted on DragStart!`);let{ownerDocument:n}=t;if(this.props.disabled||!(e.target instanceof n.defaultView.Node)||this.props.handle&&!(0,a.matchesSelectorAndParentsTo)(e.target,this.props.handle,t)||this.props.cancel&&(0,a.matchesSelectorAndParentsTo)(e.target,this.props.cancel,t))return;e.type===`touchstart`&&!this.props.allowMobileScroll&&e.preventDefault();let r=(0,a.getTouchIdentifier)(e);this.touchIdentifier=r;let i=(0,o.getControlPosition)(e,r,this);if(i==null)return;let{x:s,y:l}=i,u=(0,o.createCoreData)(this,s,l);(0,c.default)(`DraggableCore: handleDragStart: %j`,u),(0,c.default)(`calling`,this.props.onStart),!(this.props.onStart(e,u)===!1||this.mounted===!1)&&(this.props.enableUserSelectHack&&(0,a.addUserSelectStyles)(n),this.dragging=!0,this.lastX=s,this.lastY=l,(0,a.addEvent)(n,h.move,this.handleDrag),(0,a.addEvent)(n,h.stop,this.handleDragStop))}),d(this,`handleDrag`,e=>{let t=(0,o.getControlPosition)(e,this.touchIdentifier,this);if(t==null)return;let{x:n,y:r}=t;if(Array.isArray(this.props.grid)){let e=n-this.lastX,t=r-this.lastY;if([e,t]=(0,o.snapToGrid)(this.props.grid,e,t),!e&&!t)return;n=this.lastX+e,r=this.lastY+t}let i=(0,o.createCoreData)(this,n,r);if((0,c.default)(`DraggableCore: handleDrag: %j`,i),this.props.onDrag(e,i)===!1||this.mounted===!1){try{this.handleDragStop(new MouseEvent(`mouseup`))}catch{let e=document.createEvent(`MouseEvents`);e.initMouseEvent(`mouseup`,!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null),this.handleDragStop(e)}return}this.lastX=n,this.lastY=r}),d(this,`handleDragStop`,e=>{if(!this.dragging)return;let t=(0,o.getControlPosition)(e,this.touchIdentifier,this);if(t==null)return;let{x:n,y:r}=t;if(Array.isArray(this.props.grid)){let e=n-this.lastX||0,t=r-this.lastY||0;[e,t]=(0,o.snapToGrid)(this.props.grid,e,t),n=this.lastX+e,r=this.lastY+t}let i=(0,o.createCoreData)(this,n,r);if(this.props.onStop(e,i)===!1||this.mounted===!1)return!1;let s=this.findDOMNode();s&&this.props.enableUserSelectHack&&(0,a.scheduleRemoveUserSelectStyles)(s.ownerDocument),(0,c.default)(`DraggableCore: handleDragStop: %j`,i),this.dragging=!1,this.lastX=NaN,this.lastY=NaN,s&&((0,c.default)(`DraggableCore: Removing handlers`),(0,a.removeEvent)(s.ownerDocument,h.move,this.handleDrag),(0,a.removeEvent)(s.ownerDocument,h.stop,this.handleDragStop))}),d(this,`onMouseDown`,e=>(h=m.mouse,this.handleDragStart(e))),d(this,`onMouseUp`,e=>(h=m.mouse,this.handleDragStop(e))),d(this,`onTouchStart`,e=>(h=m.touch,this.handleDragStart(e))),d(this,`onTouchEnd`,e=>(h=m.touch,this.handleDragStop(e)))}componentDidMount(){this.mounted=!0;let e=this.findDOMNode();e&&(0,a.addEvent)(e,m.touch.start,this.onTouchStart,{passive:!1})}componentWillUnmount(){this.mounted=!1;let e=this.findDOMNode();if(e){let{ownerDocument:t}=e;(0,a.removeEvent)(t,m.mouse.move,this.handleDrag),(0,a.removeEvent)(t,m.touch.move,this.handleDrag),(0,a.removeEvent)(t,m.mouse.stop,this.handleDragStop),(0,a.removeEvent)(t,m.touch.stop,this.handleDragStop),(0,a.removeEvent)(e,m.touch.start,this.onTouchStart,{passive:!1}),this.props.enableUserSelectHack&&(0,a.scheduleRemoveUserSelectStyles)(t)}}findDOMNode(){return this.props?.nodeRef?this.props?.nodeRef?.current:i.default.findDOMNode(this)}render(){return t.cloneElement(t.Children.only(this.props.children),{onMouseDown:this.onMouseDown,onMouseUp:this.onMouseUp,onTouchEnd:this.onTouchEnd})}};e.default=g,d(g,`displayName`,`DraggableCore`),d(g,`propTypes`,{allowAnyClick:n.default.bool,allowMobileScroll:n.default.bool,children:n.default.node.isRequired,disabled:n.default.bool,enableUserSelectHack:n.default.bool,offsetParent:function(e,t){if(e[t]&&e[t].nodeType!==1)throw Error(`Draggable's offsetParent must be a DOM Node.`)},grid:n.default.arrayOf(n.default.number),handle:n.default.string,cancel:n.default.string,nodeRef:n.default.object,onStart:n.default.func,onDrag:n.default.func,onStop:n.default.func,onMouseDown:n.default.func,scale:n.default.number,className:s.dontSetMe,style:s.dontSetMe,transform:s.dontSetMe}),d(g,`defaultProps`,{allowAnyClick:!1,allowMobileScroll:!1,disabled:!1,enableUserSelectHack:!0,onStart:function(){},onDrag:function(){},onStop:function(){},onMouseDown:function(){},scale:1})})),Rt=i((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),Object.defineProperty(e,`DraggableCore`,{enumerable:!0,get:function(){return l.default}}),e.default=void 0;var t=f(r()),n=d(At()),i=d(R()),a=jt(),o=Pt(),s=Ft(),c=Mt(),l=d(Lt()),u=d(It());function d(e){return e&&e.__esModule?e:{default:e}}function f(e,t){if(typeof WeakMap==`function`)var n=new WeakMap,r=new WeakMap;return(f=function(e,t){if(!t&&e&&e.__esModule)return e;var i,a,o={__proto__:null,default:e};if(e===null||typeof e!=`object`&&typeof e!=`function`)return o;if(i=t?r:n){if(i.has(e))return i.get(e);i.set(e,o)}for(let t in e)t!==`default`&&{}.hasOwnProperty.call(e,t)&&((a=(i=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(a.get||a.set)?i(o,t,a):o[t]=e[t]);return o})(e,t)}function p(){return p=Object.assign?Object.assign.bind():function(e){for(var t=1;t{if((0,u.default)(`Draggable: onDragStart: %j`,t),this.props.onStart(e,(0,s.createDraggableData)(this,t))===!1)return!1;this.setState({dragging:!0,dragged:!0})}),m(this,`onDrag`,(e,t)=>{if(!this.state.dragging)return!1;(0,u.default)(`Draggable: onDrag: %j`,t);let n=(0,s.createDraggableData)(this,t),r={x:n.x,y:n.y,slackX:0,slackY:0};if(this.props.bounds){let{x:e,y:t}=r;r.x+=this.state.slackX,r.y+=this.state.slackY;let[i,a]=(0,s.getBoundPosition)(this,r.x,r.y);r.x=i,r.y=a,r.slackX=this.state.slackX+(e-r.x),r.slackY=this.state.slackY+(t-r.y),n.x=r.x,n.y=r.y,n.deltaX=r.x-this.state.x,n.deltaY=r.y-this.state.y}if(this.props.onDrag(e,n)===!1)return!1;this.setState(r)}),m(this,`onDragStop`,(e,t)=>{if(!this.state.dragging||this.props.onStop(e,(0,s.createDraggableData)(this,t))===!1)return!1;(0,u.default)(`Draggable: onDragStop: %j`,t);let n={dragging:!1,slackX:0,slackY:0};if(this.props.position){let{x:e,y:t}=this.props.position;n.x=e,n.y=t}this.setState(n)}),this.state={dragging:!1,dragged:!1,x:e.position?e.position.x:e.defaultPosition.x,y:e.position?e.position.y:e.defaultPosition.y,prevPropsPosition:{...e.position},slackX:0,slackY:0,isElementSVG:!1},e.position&&!(e.onDrag||e.onStop)&&console.warn("A `position` was applied to this , without drag handlers. This will make this component effectively undraggable. Please attach `onDrag` or `onStop` handlers so you can adjust the `position` of this element.")}componentDidMount(){window.SVGElement!==void 0&&this.findDOMNode()instanceof window.SVGElement&&this.setState({isElementSVG:!0})}componentWillUnmount(){this.state.dragging&&this.setState({dragging:!1})}findDOMNode(){return this.props?.nodeRef?.current??i.default.findDOMNode(this)}render(){let{axis:e,bounds:n,children:r,defaultPosition:i,defaultClassName:c,defaultClassNameDragging:u,defaultClassNameDragged:d,position:f,positionOffset:m,scale:h,...g}=this.props,_={},v=null,y=!f||this.state.dragging,b=f||i,x={x:(0,s.canDragX)(this)&&y?this.state.x:b.x,y:(0,s.canDragY)(this)&&y?this.state.y:b.y};this.state.isElementSVG?v=(0,o.createSVGTransform)(x,m):_=(0,o.createCSSTransform)(x,m);let S=(0,a.clsx)(r.props.className||``,c,{[u]:this.state.dragging,[d]:this.state.dragged});return t.createElement(l.default,p({},g,{onStart:this.onDragStart,onDrag:this.onDrag,onStop:this.onDragStop}),t.cloneElement(t.Children.only(r),{className:S,style:{...r.props.style,..._},transform:v}))}};e.default=_,m(_,`displayName`,`Draggable`),m(_,`propTypes`,{...l.default.propTypes,axis:n.default.oneOf([`both`,`x`,`y`,`none`]),bounds:n.default.oneOfType([n.default.shape({left:n.default.number,right:n.default.number,top:n.default.number,bottom:n.default.number}),n.default.string,n.default.oneOf([!1])]),defaultClassName:n.default.string,defaultClassNameDragging:n.default.string,defaultClassNameDragged:n.default.string,defaultPosition:n.default.shape({x:n.default.number,y:n.default.number}),positionOffset:n.default.shape({x:n.default.oneOfType([n.default.number,n.default.string]),y:n.default.oneOfType([n.default.number,n.default.string])}),position:n.default.shape({x:n.default.number,y:n.default.number}),className:c.dontSetMe,style:c.dontSetMe,transform:c.dontSetMe}),m(_,`defaultProps`,{...l.default.defaultProps,axis:`both`,bounds:!1,defaultClassName:`react-draggable`,defaultClassNameDragging:`react-draggable-dragging`,defaultClassNameDragged:`react-draggable-dragged`,defaultPosition:{x:0,y:0},scale:1})})),zt=i(((e,t)=>{var{default:n,DraggableCore:r}=Rt();t.exports=n,t.exports.default=n,t.exports.DraggableCore=r})),Bt=i((e=>{e.__esModule=!0,e.cloneElement=l;var t=n(r());function n(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function a(e){for(var t=1;t{e.__esModule=!0,e.resizableProps=void 0;var t=n(At());zt();function n(e){return e&&e.__esModule?e:{default:e}}e.resizableProps={axis:t.default.oneOf([`both`,`x`,`y`,`none`]),className:t.default.string,children:t.default.element.isRequired,draggableOpts:t.default.shape({allowAnyClick:t.default.bool,cancel:t.default.string,children:t.default.node,disabled:t.default.bool,enableUserSelectHack:t.default.bool,offsetParent:typeof Element<`u`?t.default.instanceOf(Element):t.default.any,grid:t.default.arrayOf(t.default.number),handle:t.default.string,nodeRef:t.default.object,onStart:t.default.func,onDrag:t.default.func,onStop:t.default.func,onMouseDown:t.default.func,scale:t.default.number}),height:function(){var e=[...arguments];let[n]=e;return n.axis===`both`||n.axis===`y`?t.default.number.isRequired(...e):t.default.number(...e)},handle:t.default.oneOfType([t.default.node,t.default.func]),handleSize:t.default.arrayOf(t.default.number),lockAspectRatio:t.default.bool,maxConstraints:t.default.arrayOf(t.default.number),minConstraints:t.default.arrayOf(t.default.number),onResizeStop:t.default.func,onResizeStart:t.default.func,onResize:t.default.func,resizeHandles:t.default.arrayOf(t.default.oneOf([`s`,`w`,`e`,`n`,`sw`,`nw`,`se`,`ne`])),transformScale:t.default.number,width:function(){var e=[...arguments];let[n]=e;return n.axis===`both`||n.axis===`x`?t.default.number.isRequired(...e):t.default.number(...e)}}})),Ht=i((e=>{e.__esModule=!0,e.default=void 0;var t=s(r()),n=zt(),i=Bt(),a=Vt(),o=[`children`,`className`,`draggableOpts`,`width`,`height`,`handle`,`handleSize`,`lockAspectRatio`,`axis`,`minConstraints`,`maxConstraints`,`onResize`,`onResizeStop`,`onResizeStart`,`resizeHandles`,`transformScale`];function s(e,t){if(typeof WeakMap==`function`)var n=new WeakMap,r=new WeakMap;return(s=function(e,t){if(!t&&e&&e.__esModule)return e;var i,a,o={__proto__:null,default:e};if(e===null||typeof e!=`object`&&typeof e!=`function`)return o;if(i=t?r:n){if(i.has(e))return i.get(e);i.set(e,o)}for(let t in e)t!==`default`&&{}.hasOwnProperty.call(e,t)&&((a=(i=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(a.get||a.set)?i(o,t,a):o[t]=e[t]);return o})(e,t)}function c(){return c=Object.assign?Object.assign.bind():function(e){for(var t=1;tMath.abs(i*n)?t=e/n:e=t*n}let[a,o]=[e,t],[s,c]=this.slack||[0,0];return e+=s,t+=c,n&&(e=Math.max(n[0],e),t=Math.max(n[1],t)),r&&(e=Math.min(r[0],e),t=Math.min(r[1],t)),this.slack=[s+(a-e),c+(o-t)],[e,t]}resizeHandler(e,t){return(n,r)=>{let{node:i,deltaX:a,deltaY:o}=r;e===`onResizeStart`&&this.resetData();let s=(this.props.axis===`both`||this.props.axis===`x`)&&t!==`n`&&t!==`s`,c=(this.props.axis===`both`||this.props.axis===`y`)&&t!==`e`&&t!==`w`;if(!s&&!c)return;let l=t[0],u=t[t.length-1],d=i.getBoundingClientRect();if(this.lastHandleRect!=null){if(u===`w`){let e=d.left-this.lastHandleRect.left;a+=e}if(l===`n`){let e=d.top-this.lastHandleRect.top;o+=e}}this.lastHandleRect=d,u===`w`&&(a=-a),l===`n`&&(o=-o);let f=this.props.width+(s?a/this.props.transformScale:0),p=this.props.height+(c?o/this.props.transformScale:0);[f,p]=this.runConstraints(f,p),e===`onResizeStop`&&this.lastSize&&({width:f,height:p}=this.lastSize);let m=f!==this.props.width||p!==this.props.height;e!==`onResizeStop`&&(this.lastSize={width:f,height:p});let h=typeof this.props[e]==`function`?this.props[e]:null;h&&!(e===`onResize`&&!m)&&(n.persist?.(),h(n,{node:i,size:{width:f,height:p},handle:t})),e===`onResizeStop`&&this.resetData()}}renderResizeHandle(e,n){let{handle:r}=this.props;if(!r)return t.createElement(`span`,{className:`react-resizable-handle react-resizable-handle-${e}`,ref:n});if(typeof r==`function`)return r(e,n);let i=typeof r.type==`string`,a=f({ref:n},i?{}:{handleAxis:e});return t.cloneElement(r,a)}render(){let e=this.props,{children:r,className:a,draggableOpts:s,width:u,height:d,handle:p,handleSize:m,lockAspectRatio:h,axis:g,minConstraints:_,maxConstraints:v,onResize:y,onResizeStop:b,onResizeStart:x,resizeHandles:S,transformScale:C}=e,w=l(e,o);return(0,i.cloneElement)(r,f(f({},w),{},{className:`${a?`${a} `:``}react-resizable`,children:[...t.Children.toArray(r.props.children),...S.map(e=>{let r=this.handleRefs[e]??(this.handleRefs[e]=t.createRef());return t.createElement(n.DraggableCore,c({},s,{nodeRef:r,key:`resizableHandle-${e}`,onStop:this.resizeHandler(`onResizeStop`,e),onStart:this.resizeHandler(`onResizeStart`,e),onDrag:this.resizeHandler(`onResize`,e)}),this.renderResizeHandle(e,r))})]}))}};e.default=g,g.propTypes=a.resizableProps,g.defaultProps={axis:`both`,handleSize:[20,20],lockAspectRatio:!1,minConstraints:[20,20],maxConstraints:[1/0,1/0],resizeHandles:[`se`],transformScale:1}})),Ut=i((e=>{e.__esModule=!0,e.default=void 0;var t=c(r()),n=s(At()),i=s(Ht()),a=Vt(),o=[`handle`,`handleSize`,`onResize`,`onResizeStart`,`onResizeStop`,`draggableOpts`,`minConstraints`,`maxConstraints`,`lockAspectRatio`,`axis`,`width`,`height`,`resizeHandles`,`style`,`transformScale`];function s(e){return e&&e.__esModule?e:{default:e}}function c(e,t){if(typeof WeakMap==`function`)var n=new WeakMap,r=new WeakMap;return(c=function(e,t){if(!t&&e&&e.__esModule)return e;var i,a,o={__proto__:null,default:e};if(e===null||typeof e!=`object`&&typeof e!=`function`)return o;if(i=t?r:n){if(i.has(e))return i.get(e);i.set(e,o)}for(let t in e)t!==`default`&&{}.hasOwnProperty.call(e,t)&&((a=(i=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(a.get||a.set)?i(o,t,a):o[t]=e[t]);return o})(e,t)}function l(){return l=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let{size:n}=t;this.props.onResize?(e.persist?.(),this.setState(n,()=>this.props.onResize&&this.props.onResize(e,t))):this.setState(n)}}static getDerivedStateFromProps(e,t){return t.propsWidth!==e.width||t.propsHeight!==e.height?{width:e.width,height:e.height,propsWidth:e.width,propsHeight:e.height}:null}render(){let e=this.props,{handle:n,handleSize:r,onResize:a,onResizeStart:s,onResizeStop:c,draggableOpts:u,minConstraints:f,maxConstraints:p,lockAspectRatio:m,axis:g,width:_,height:v,resizeHandles:y,style:b,transformScale:x}=e,S=h(e,o);return t.createElement(i.default,{axis:g,draggableOpts:u,handle:n,handleSize:r,height:this.state.height,lockAspectRatio:m,maxConstraints:p,minConstraints:f,onResizeStart:s,onResize:this.onResize,onResizeStop:c,resizeHandles:y,transformScale:x,width:this.state.width},t.createElement(`div`,l({},S,{style:d(d({},b),{},{width:this.state.width+`px`,height:this.state.height+`px`})})))}};e.default=_,_.propTypes=d(d({},a.resizableProps),{},{children:n.default.element})})),Wt=i(((e,t)=>{t.exports=function(){throw Error(`Don't instantiate Resizable directly! Use require('react-resizable').Resizable`)},t.exports.Resizable=Ht().default,t.exports.ResizableBox=Ut().default})),H=n(r(),1),Gt=zt(),Kt=Wt(),U=o(),qt=Dt();function Jt(e){let{children:t,cols:n,containerWidth:r,margin:i,containerPadding:a,rowHeight:o,maxRows:s,isDraggable:c,isResizable:l,isBounded:u,static:d,useCSSTransforms:f=!0,usePercentages:p=!1,transformScale:m=1,positionStrategy:h,dragThreshold:g=0,droppingPosition:_,className:v=``,style:y,handle:b=``,cancel:x=``,x:S,y:C,w,h:T,minW:E=1,maxW:D=1/0,minH:O=1,maxH:k=1/0,i:A,resizeHandles:j,resizeHandle:M,constraints:N=Ge,layoutItem:ee,layout:te=[],onDragStart:P,onDrag:ne,onDragStop:F,onResizeStart:re,onResize:ie,onResizeStop:ae}=e,[oe,se]=(0,H.useState)(!1),[I,L]=(0,H.useState)(!1),ce=(0,H.useRef)(null),R=(0,H.useRef)({left:0,top:0}),le=(0,H.useRef)({top:0,left:0,width:0,height:0}),ue=(0,H.useRef)(void 0),de=(0,H.useRef)(te);de.current=te;let fe=(0,H.useRef)(null),pe=(0,H.useRef)(null),me=(0,H.useRef)(!1),he=(0,H.useRef)({x:0,y:0}),z=(0,H.useRef)(!1),B=(0,H.useMemo)(()=>({cols:n,containerPadding:a,containerWidth:r,margin:i,maxRows:s,rowHeight:o}),[n,a,r,i,s,o]),ge=(0,H.useMemo)(()=>({cols:n,maxRows:s,containerWidth:r,containerHeight:0,rowHeight:o,margin:i,layout:[]}),[n,s,r,o,i]),_e=(0,H.useCallback)(()=>({...ge,layout:de.current}),[ge]),ve=(0,H.useMemo)(()=>ee??{i:A,x:S,y:C,w,h:T,minW:E,maxW:D,minH:O,maxH:k},[ee,A,S,C,w,T,E,D,O,k]),ye=(0,H.useCallback)(e=>{if(h?.calcStyle)return h.calcStyle(e);if(f)return Je(e);let t=Ye(e);return p?{...t,left:Xe(e.left/r),width:Xe(e.width/r)}:t},[h,f,p,r]),be=(0,H.useCallback)((e,{node:t})=>{if(!P)return;let{offsetParent:n}=t;if(!n)return;let r=n.getBoundingClientRect(),i=t.getBoundingClientRect(),a=i.left/m,o=r.left/m,s=i.top/m,c=r.top/m,l;if(h?.calcDragPosition){let t=e;l=h.calcDragPosition(t.clientX,t.clientY,t.clientX-i.left,t.clientY-i.top)}else l={left:a-o+n.scrollLeft,top:s-c+n.scrollTop};if(R.current=l,g>0){let t=e;he.current={x:t.clientX,y:t.clientY},me.current=!0,z.current=!1,se(!0);return}se(!0);let u=Te(B,l.top,l.left),{x:d,y:f}=Ke(N,ve,u.x,u.y,_e());P(A,d,f,{e,node:t,newPosition:l})},[P,m,B,h,g,N,ve,_e,A]),we=(0,H.useCallback)((e,{node:t,deltaX:n,deltaY:a})=>{if(!ne||!oe)return;let s=e;if(me.current&&!z.current){let n=s.clientX-he.current.x,r=s.clientY-he.current.y;if(Math.hypot(n,r){if(!F||!oe)return;let n=me.current;if(me.current=!1,z.current=!1,he.current={x:0,y:0},n){se(!1),R.current={left:0,top:0};return}let{left:r,top:i}=R.current,a={top:i,left:r};se(!1),R.current={left:0,top:0};let o=Te(B,i,r),{x:s,y:c}=Ke(N,ve,o.x,o.y,_e());F(A,s,c,{e,node:t,newPosition:a})},[F,oe,B,N,ve,_e,A]);fe.current=be,pe.current=we;let ke=(0,H.useCallback)((e,{node:t,size:n,handle:i},a,o)=>{let s=o===`onResizeStart`?re:o===`onResize`?ie:ae;if(!s)return;let c;c=t?ot(i,a,n,r):{...n,top:a.top,left:a.left},le.current=c;let l=Ee(B,c.width,c.height),{w:u,h:d}=qe(N,ve,l.w,l.h,i,_e());s(A,u,d,{e:e.nativeEvent,node:t,size:c,handle:i})},[re,ie,ae,r,B,A,N,ve,_e]),Ae=(0,H.useCallback)((e,t)=>{L(!0);let n=Ce(B,S,C,w,T);ke(e,{...t,handle:t.handle},n,`onResizeStart`)},[ke,B,S,C,w,T]),je=(0,H.useCallback)((e,t)=>{let n=Ce(B,S,C,w,T);ke(e,{...t,handle:t.handle},n,`onResize`)},[ke,B,S,C,w,T]),Me=(0,H.useCallback)((e,t)=>{L(!1),le.current={top:0,left:0,width:0,height:0};let n=Ce(B,S,C,w,T);ke(e,{...t,handle:t.handle},n,`onResizeStop`)},[ke,B,S,C,w,T]);(0,H.useEffect)(()=>{if(!_)return;let e=ce.current;if(!e)return;let t=ue.current||{left:0,top:0},n=oe&&(_.left!==t.left||_.top!==t.top);if(!oe){let t={node:e,deltaX:_.left,deltaY:_.top,lastX:0,lastY:0,x:_.left,y:_.top};fe.current?.(_.e,t)}else if(n){let t={node:e,deltaX:_.left-R.current.left,deltaY:_.top-R.current.top,lastX:R.current.left,lastY:R.current.top,x:_.left,y:_.top};pe.current?.(_.e,t)}ue.current=_},[_,oe,A]);let Ne=Ce(B,S,C,w,T,oe?R.current:null,I?le.current:null),Pe=H.Children.only(t),Fe=xe(B),Ie=[Se(E,Fe,i[0]),Se(O,o,i[1])],Le=[Se(D,Fe,i[0]),Se(k,o,i[1])],Re=Pe.props,ze=Re.className,Be=Re.style,Ve=H.cloneElement(Pe,{ref:ce,className:V(`react-grid-item`,ze,v,{static:d,resizing:I,"react-draggable":c,"react-draggable-dragging":oe,dropping:!!_,cssTransforms:f}),style:{...y,...Be,...ye(Ne)}}),He=M;return Ve=(0,U.jsx)(Kt.Resizable,{draggableOpts:{disabled:!l},className:l?void 0:`react-resizable-hide`,width:Ne.width,height:Ne.height,minConstraints:Ie,maxConstraints:Le,onResizeStart:Ae,onResize:je,onResizeStop:Me,transformScale:m,resizeHandles:j,handle:He,children:Ve}),Ve=(0,U.jsx)(Gt.DraggableCore,{disabled:!c,onStart:be,onDrag:we,onStop:Oe,handle:b,cancel:`.react-resizable-handle`+(x?`,`+x:``),scale:m,nodeRef:ce,children:Ve}),Ve}var Yt=()=>{},Xt=`react-grid-layout`,Zt=!1;try{Zt=/firefox/i.test(navigator.userAgent)}catch{}function Qt(e,t){let n=H.Children.toArray(e),r=H.Children.toArray(t);if(n.length!==r.length)return!1;for(let e=0;e{if(!H.isValidElement(t)||t.key===null)return;let n=String(t.key);a.add(n);let r=e.find(e=>e.i===n);if(r)i.push(Le(r));else{let e=t.props[`data-grid`];e?i.push({i:n,x:e.x??0,y:e.y??0,w:e.w??1,h:e.h??1,minW:e.minW,maxW:e.maxW,minH:e.minH,maxH:e.maxH,static:e.static,isDraggable:e.isDraggable,isResizable:e.isResizable,resizeHandles:e.resizeHandles,isBounded:e.isBounded}):i.push({i:n,x:0,y:Pe(i),w:1,h:1})}});let o=Ve(i,{cols:n});return r.compact(o,n)}function en(e){let{children:t,width:n,gridConfig:r,dragConfig:i,resizeConfig:a,dropConfig:o,positionStrategy:s=st,compactor:c,constraints:l=Ge,layout:u=[],droppingItem:d,autoSize:f=!0,className:p=``,style:m={},innerRef:h,onLayoutChange:g=Yt,onDragStart:_=Yt,onDrag:v=Yt,onDragStop:y=Yt,onResizeStart:b=Yt,onResize:x=Yt,onResizeStop:S=Yt,onDrop:C=Yt,onDropDragOver:w=Yt}=e,T=(0,H.useMemo)(()=>({...ct,...r}),[r]),E=(0,H.useMemo)(()=>({...lt,...i}),[i]),D=(0,H.useMemo)(()=>({...ut,...a}),[a]),O=(0,H.useMemo)(()=>({...dt,...o}),[o]),{cols:k,rowHeight:A,maxRows:j,margin:M,containerPadding:N}=T,{enabled:ee,bounded:te,handle:P,cancel:ne,threshold:F}=E,{enabled:re,handles:ie,handleComponent:ae}=D,{enabled:oe,defaultItem:se,onDragOver:I}=O,L=c??xt(`vertical`),ce=L.type,R=L.allowOverlap,le=L.preventCollision??!1,ue=(0,H.useMemo)(()=>d??{i:`__dropping-elem__`,...se},[d,se]),de=s.type===`transform`,fe=s.scale,pe=N??M,[me,he]=(0,H.useState)(!1),[z,B]=(0,H.useState)(()=>$t(u,t,k,L)),[ge,_e]=(0,H.useState)(null),[ve,ye]=(0,H.useState)(!1),[be,Ce]=(0,H.useState)(null),[Te,Ee]=(0,H.useState)(),De=(0,H.useRef)(null),Oe=(0,H.useRef)(null),ke=(0,H.useRef)(null),je=(0,H.useRef)(0),Me=(0,H.useRef)(z),Ne=(0,H.useRef)(u),Ie=(0,H.useRef)(t),Re=(0,H.useRef)(ce),ze=(0,H.useRef)(z);ze.current=z,(0,H.useEffect)(()=>{he(!0),(0,qt.deepEqual)(z,u)||g(z)},[]),(0,H.useEffect)(()=>{if(ge||be)return;let e=!(0,qt.deepEqual)(u,Ne.current),n=!Qt(t,Ie.current),r=ce!==Re.current;if(e||n||r){let n=$t(e?u:z,t,k,L);(0,qt.deepEqual)(n,z)||B(n)}Ne.current=u,Ie.current=t,Re.current=ce},[u,t,k,ce,L,ge,be,z]),(0,H.useEffect)(()=>{!ge&&!(0,qt.deepEqual)(z,Me.current)&&(Me.current=z,g(z.filter(e=>e.i!==ue.i)))},[z,ge,g,ue.i]);let Ve=(0,H.useMemo)(()=>{if(!f)return;let e=Pe(z),t=pe[1];return e*A+(e-1)*M[1]+t*2+`px`},[f,z,A,M,pe]),Ue=(0,H.useCallback)((e,t,n,r)=>{let i=ze.current,a=Fe(i,e);if(!a)return;let o={w:a.w,h:a.h,x:a.x,y:a.y,i:e};De.current=Le(a),ke.current=i,_e(o),_(i,a,a,null,r.e,r.node)},[_]),We=(0,H.useCallback)((e,t,n,r)=>{let i=ze.current,a=De.current,o=Fe(i,e);if(!o)return;let s={w:o.w,h:o.h,x:o.x,y:o.y,i:e},c=He(i,o,t,n,!0,le,ce,k,R);v(c,a,o,s,r.e,r.node),B(L.compact(c,k)),_e(s)},[le,ce,k,R,L,v]),Ke=(0,H.useCallback)((e,t,n,r)=>{if(!ge)return;let i=ze.current,a=De.current,o=Fe(i,e);if(!o)return;let s=He(i,o,t,n,!0,le,ce,k,R),c=L.compact(s,k);y(c,a,o,null,r.e,r.node);let l=ke.current;De.current=null,ke.current=null,_e(null),B(c),l&&!(0,qt.deepEqual)(l,c)&&g(c)},[ge,le,ce,k,R,L,y,g]),qe=(0,H.useCallback)((e,t,n,r)=>{let i=ze.current,a=Fe(i,e);a&&(Oe.current=Le(a),ke.current=i,ye(!0),b(i,a,a,null,r.e,r.node))},[b]),Je=(0,H.useCallback)((e,t,n,r)=>{let i=ze.current,a=Oe.current,{handle:o}=r,s=!1,c,l,[u,d]=Be(i,e,e=>(c=e.x,l=e.y,[`sw`,`w`,`nw`,`n`,`ne`].includes(o)&&([`sw`,`nw`,`w`].includes(o)&&(c=e.x+(e.w-t),t=e.x!==c&&c<0?e.w:t,c=c<0?0:c),[`ne`,`n`,`nw`].includes(o)&&(l=e.y+(e.h-n),n=e.y!==l&&l<0?e.h:n,l=l<0?0:l),s=!0),le&&!R&&Ae(i,{...e,w:t,h:n,x:c??e.x,y:l??e.y}).filter(t=>t.i!==e.i).length>0&&(l=e.y,n=e.h,c=e.x,t=e.w,s=!1),e.w=t,e.h=n,e));if(!d)return;let f=u;s&&c!==void 0&&l!==void 0&&(f=He(u,d,c,l,!0,le,ce,k,R));let p={w:d.w,h:d.h,x:d.x,y:d.y,i:e,static:!0};x(f,a,d,p,r.e,r.node),B(L.compact(f,k)),_e(p)},[le,ce,k,R,L,x]),Ye=(0,H.useCallback)((e,t,n,r)=>{let i=ze.current,a=Oe.current,o=Fe(i,e),s=L.compact(i,k);S(s,a,o??null,null,r.e,r.node);let c=ke.current;Oe.current=null,ke.current=null,_e(null),ye(!1),B(s),c&&!(0,qt.deepEqual)(c,s)&&g(s)},[k,L,S,g]),Xe=(0,H.useCallback)(()=>{let e=ze.current;if(!e.some(e=>e.i===ue.i)){Ce(null),_e(null),Ee(void 0);return}B(L.compact(e.filter(e=>e.i!==ue.i),k)),Ce(null),_e(null),Ee(void 0)},[ue.i,k,L]),Ze=(0,H.useCallback)(e=>{if(e.preventDefault(),e.stopPropagation(),Zt&&!e.nativeEvent.target?.classList.contains(Xt))return!1;let t=I?I(e.nativeEvent):w(e);if(t===!1)return be&&Xe(),!1;let{dragOffsetX:r=0,dragOffsetY:i=0,...a}=t??{},o={...ue,...a},s=e.currentTarget.getBoundingClientRect(),c={cols:k,margin:M,maxRows:j,rowHeight:A,containerWidth:n,containerPadding:pe},l=xe(c),u=Se(o.w,l,M[0]),d=Se(o.h,A,M[1]),f=u/2,p=d/2,m=e.clientX-s.left+r-f,h=e.clientY-s.top+i-p,g=Math.max(0,m),_=Math.max(0,h),v={left:g/fe,top:_/fe,e:e.nativeEvent};if(be)Te&&(Te.left!==v.left||Te.top!==v.top)&&Ee(v);else{let e=we(c,_,g,o.w,o.h);Ce((0,U.jsx)(`div`,{},o.i)),Ee(v),B([...ze.current.filter(e=>e.i!==o.i),{...o,x:e.x,y:e.y,static:!1,isDraggable:!0}])}},[be,Te,ue,I,w,Xe,fe,k,M,j,A,n,pe]),Qe=(0,H.useCallback)(e=>{e.preventDefault(),e.stopPropagation(),je.current--,je.current<0&&(je.current=0),je.current===0&&Xe()},[Xe]),$e=(0,H.useCallback)(e=>{e.preventDefault(),e.stopPropagation(),je.current++},[]),et=(0,H.useCallback)(e=>{e.preventDefault(),e.stopPropagation();let t=ze.current,n=t.find(e=>e.i===ue.i);je.current=0,Xe(),C(t,n,e.nativeEvent)},[ue.i,Xe,C]),tt=(0,H.useCallback)((e,t)=>{if(!e||!e.key)return null;let r=Fe(z,String(e.key));if(!r)return null;let i=typeof r.isDraggable==`boolean`?r.isDraggable:!r.static&&ee,a=typeof r.isResizable==`boolean`?r.isResizable:!r.static&&re,o=r.resizeHandles||[...ie],c=i&&te&&r.isBounded!==!1,u=ae;return(0,U.jsx)(Jt,{containerWidth:n,cols:k,margin:M,containerPadding:pe,maxRows:j,rowHeight:A,cancel:ne,handle:P,onDragStart:Ue,onDrag:We,onDragStop:Ke,onResizeStart:qe,onResize:Je,onResizeStop:Ye,isDraggable:i,isResizable:a,isBounded:c,useCSSTransforms:de&&me,usePercentages:!me,transformScale:fe,positionStrategy:s,dragThreshold:F,w:r.w,h:r.h,x:r.x,y:r.y,i:r.i,minH:r.minH,minW:r.minW,maxH:r.maxH,maxW:r.maxW,static:r.static,droppingPosition:t?Te:void 0,resizeHandles:o,resizeHandle:u,constraints:l,layoutItem:r,layout:z,children:e},r.i)},[z,n,k,M,pe,j,A,ne,P,Ue,We,Ke,qe,Je,Ye,ee,re,te,de,me,fe,s,F,Te,ie,ae,l]),nt=()=>ge?(0,U.jsx)(Jt,{w:ge.w,h:ge.h,x:ge.x,y:ge.y,i:ge.i,className:`react-grid-placeholder ${ve?`placeholder-resizing`:``}`,containerWidth:n,cols:k,margin:M,containerPadding:pe,maxRows:j,rowHeight:A,isDraggable:!1,isResizable:!1,isBounded:!1,useCSSTransforms:de,transformScale:fe,constraints:l,layout:z,children:(0,U.jsx)(`div`,{})}):null;return(0,U.jsxs)(`div`,{ref:h,className:V(Xt,p),style:{height:Ve,...m},onDrop:oe?et:void 0,onDragLeave:oe?Qe:void 0,onDragEnter:oe?$e:void 0,onDragOver:oe?Ze:void 0,children:[H.Children.map(t,e=>H.isValidElement(e)?tt(e):null),oe&&be&&tt(be,!0),nt()]})}var tn={lg:1200,md:996,sm:768,xs:480,xxs:0},nn={lg:12,md:10,sm:6,xs:4,xxs:2},rn=()=>{};function an(e,t,n,r){let i=[];H.Children.forEach(t,t=>{if(!H.isValidElement(t)||t.key===null)return;let n=String(t.key),r=e.find(e=>e.i===n);if(r)i.push({...r,i:n});else{let e=t.props[`data-grid`];e?i.push({i:n,x:e.x??0,y:e.y??0,w:e.w??1,h:e.h??1,minW:e.minW,maxW:e.maxW,minH:e.minH,maxH:e.maxH,static:e.static,isDraggable:e.isDraggable,isResizable:e.isResizable,resizeHandles:e.resizeHandles,isBounded:e.isBounded}):i.push({i:n,x:0,y:Pe(i),w:1,h:1})}});let a=Ve(i,{cols:n});return r.compact(a,n)}function on(e){let{children:t,width:n,breakpoint:r,breakpoints:i=tn,cols:a=nn,layouts:o={},rowHeight:s=150,maxRows:c=1/0,margin:l=[10,10],containerPadding:u=null,compactor:d,onBreakpointChange:f=rn,onLayoutChange:p=rn,onWidthChange:m=rn,...h}=e,g=d??xt(`vertical`),_=g.type,v=g.allowOverlap,y=(0,H.useMemo)(()=>r??Ct(i,n),[]),b=(0,H.useMemo)(()=>wt(y,a),[y,a]),x=(0,H.useMemo)(()=>Tt(o,i,y,y,b,_),[]),[S,C]=(0,H.useState)(y),[w,T]=(0,H.useState)(b),[E,D]=(0,H.useState)(x),[O,k]=(0,H.useState)(o),A=(0,H.useRef)(n),j=(0,H.useRef)(r),M=(0,H.useRef)(i),N=(0,H.useRef)(a),ee=(0,H.useRef)(o),te=(0,H.useRef)(_),P=(0,H.useRef)(O);(0,H.useEffect)(()=>{P.current=O},[O]);let ne=(0,H.useMemo)(()=>(0,qt.deepEqual)(o,ee.current)?null:Tt(o,i,S,S,w,g),[o,i,S,w,g]),F=ne??E;(0,H.useEffect)(()=>{ne!==null&&(D(ne),k(o),P.current=o,ee.current=o)},[ne,o]),(0,H.useEffect)(()=>{if(_!==te.current){let e=g.compact(Re(F),w),t={...P.current,[S]:e};D(e),k(t),P.current=t,p(e,t),te.current=_}},[_,g,F,w,v,S,p]),(0,H.useEffect)(()=>{let e=n!==A.current,o=r!==j.current,s=!(0,qt.deepEqual)(i,M.current),c=!(0,qt.deepEqual)(a,N.current);if(e||o||s||c){let e=r??Ct(i,n),o=wt(e,a),d=S;if(d!==e||s||c){let n={...P.current};n[d]||(n[d]=Re(E));let r=Tt(n,i,e,d,o,g);r=an(r,t,o,g),n[e]=r,C(e),T(o),D(r),k(n),P.current=n,f(e,o),p(r,n)}m(n,Et(l,e),o,u?Et(u,e):null),A.current=n,j.current=r,M.current=i,N.current=a}},[n,r,i,a,S,w,E,t,g,_,v,l,u,f,p,m]);let re=(0,H.useCallback)(e=>{let t={...P.current,[S]:e};D(e),k(t),P.current=t,p(e,t)},[S,p]),ie=(0,H.useMemo)(()=>Et(l,S),[l,S]),ae=(0,H.useMemo)(()=>u===null?null:Et(u,S),[u,S]),oe=(0,H.useMemo)(()=>({cols:w,rowHeight:s,maxRows:c,margin:ie,containerPadding:ae}),[w,s,c,ie,ae]);return(0,U.jsx)(en,{...h,width:n,gridConfig:oe,compactor:g,onLayoutChange:re,layout:F,children:t})}var sn=d(e=>({layout:[],setLayout:t=>e({layout:t}),addWidget:t=>e(e=>({layout:[...e.layout,t]})),removeWidget:t=>e(e=>({layout:e.layout.filter(e=>e.i!==t)})),updateLayout:t=>e(e=>({layout:e.layout.map(e=>{let n=t.find(t=>t.i===e.i);return n?{...e,x:n.x,y:n.y,w:n.w,h:n.h}:e})})),setWidgetServer:(t,n)=>e(e=>({layout:e.layout.map(e=>e.i===t?{...e,server_id:n}:e)})),updateWidgetConfig:(t,n)=>e(e=>({layout:e.layout.map(e=>e.i===t?{...e,config:{...e.config??{},...n}}:e)}))})),cn=d((e,t)=>({enabled:{},loadModules:async()=>{try{let t=await u(`/api/admin/modules`),n={};for(let e of t.modules)n[e.id]=e.enabled;e({enabled:n})}catch{}},isEnabled:e=>t().enabled[e]??!0})),ln=d(e=>({editMode:!1,setEditMode:t=>e({editMode:t}),toggleEditMode:()=>e(e=>({editMode:!e.editMode}))})),un=n(R(),1),dn=[],fn=[`temperature`,`power`,`fan`,`voltage`,`current`];function pn({data:e,color:t}){let n=(0,H.useId)();if(e.length<2)return(0,U.jsx)(`div`,{className:`mt-2 h-9 w-full`,"aria-hidden":`true`});let r=Math.min(...e),i=Math.max(...e)-r||1,a=e.map((t,n)=>`${n/(e.length-1)*100},${30-(t-r)/i*28}`).join(` `),o=`0,32 ${a} 100,32`;return(0,U.jsxs)(`svg`,{viewBox:`0 0 100 32`,preserveAspectRatio:`none`,className:`mt-2 h-9 w-full`,"aria-hidden":`true`,children:[(0,U.jsx)(`defs`,{children:(0,U.jsxs)(`linearGradient`,{id:n,x1:`0`,y1:`0`,x2:`0`,y2:`1`,children:[(0,U.jsx)(`stop`,{offset:`0%`,stopColor:t,stopOpacity:.28}),(0,U.jsx)(`stop`,{offset:`100%`,stopColor:t,stopOpacity:0})]})}),(0,U.jsx)(`polygon`,{points:o,fill:`url(#${n})`}),(0,U.jsx)(`polyline`,{points:a,fill:`none`,stroke:t,strokeWidth:1.75,strokeLinecap:`round`,strokeLinejoin:`round`,vectorEffect:`non-scaling-stroke`})]})}function mn({anchorRef:e,options:t,active:n,readings:r,onSelect:i,onClose:a}){let o=(0,H.useRef)(null),[s,c]=(0,H.useState)(null),[l,u]=(0,H.useState)(()=>n?Math.max(0,t.indexOf(n)):0),d=(0,H.useCallback)(()=>{let t=e.current;if(!t)return;let n=t.getBoundingClientRect(),r=Math.max(n.width,176),i=n.right-r;i<8&&(i=8),i+r>window.innerWidth-8&&(i=window.innerWidth-8-r),c({top:n.bottom+4,left:i,width:r})},[e]);return(0,H.useLayoutEffect)(()=>{d()},[d]),(0,H.useEffect)(()=>{function e(){d()}return window.addEventListener(`scroll`,e,!0),window.addEventListener(`resize`,e),()=>{window.removeEventListener(`scroll`,e,!0),window.removeEventListener(`resize`,e)}},[d]),(0,H.useEffect)(()=>{function t(t){let n=t.target;o.current?.contains(n)||e.current?.contains(n)||a()}return document.addEventListener(`pointerdown`,t,!0),()=>document.removeEventListener(`pointerdown`,t,!0)},[e,a]),(0,H.useEffect)(()=>{function e(e){if(e.key===`Escape`)e.preventDefault(),a();else if(e.key===`ArrowDown`)e.preventDefault(),u(e=>Math.min(t.length-1,e+1));else if(e.key===`ArrowUp`)e.preventDefault(),u(e=>Math.max(0,e-1));else if(e.key===`Enter`){e.preventDefault();let n=t[l];n&&i(n)}}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[t,l,i,a]),s?(0,un.createPortal)((0,U.jsx)(`div`,{ref:o,role:`listbox`,style:{position:`fixed`,top:s.top,left:s.left,width:s.width,zIndex:9999},className:`rounded-lg border border-border bg-popover text-popover-foreground shadow-lg`,onMouseDown:e=>e.stopPropagation(),children:(0,U.jsx)(`div`,{className:`max-h-60 overflow-y-auto py-1`,children:t.map((e,t)=>{let a=r?.[e],o=e===n,s=t===l;return(0,U.jsxs)(`button`,{type:`button`,role:`option`,"aria-selected":o,onMouseDown:e=>e.stopPropagation(),onMouseEnter:()=>u(t),onClick:()=>i(e),className:I(`flex w-full items-center justify-between gap-3 px-2 py-1.5 text-left text-[12px]`,s?`bg-muted`:`hover:bg-muted`,o&&`font-medium`),children:[(0,U.jsx)(`span`,{className:`truncate`,children:e}),(0,U.jsx)(`span`,{className:`shrink-0 font-mono text-[10px] text-muted-foreground`,children:a?.value==null?`—`:`${a.value}${a.unit}`})]},e)})})}),document.body):null}function hn({serverId:e,sensorName:t,label:n,onSelectSensor:r}){let{t:i}=a(),o=B(t=>t.readings[e]),s=L(),[c,l]=(0,H.useState)(!1),u=(0,H.useRef)(null),d=(0,H.useMemo)(()=>o?Object.entries(o).filter(([,e])=>e?.value!=null).sort(([e,t],[n,r])=>{let i=fn.indexOf(t?.type??``),a=fn.indexOf(r?.type??``),o=i===-1?fn.length:i,s=a===-1?fn.length:a;return o===s?j(e,n):o-s}).map(([e])=>e):[],[o]),f=t!=null&&o?.[t]!=null,p=f?t:d[0],m=(0,H.useRef)(!1);(0,H.useEffect)(()=>{if(r){if(f){m.current=!1;return}m.current||p&&(m.current=!0,r(p))}},[f,p,r]);let h=(0,H.useCallback)(e=>{r?.(e),l(!1)},[r]),g=p?o?.[p]:void 0,y=B(t=>p?t.sparklines[e]?.[p]??dn:dn);if(!e)return(0,U.jsx)(`div`,{className:`flex h-full items-center justify-center text-muted-foreground`,children:`—`});try{let e=g?.value,t=g?.unit||``,a=g?.status||`unknown`,f=n&&n!==p?n:null,m=a===`ok`?`bg-success/10 text-success`:a===`warning`?`bg-warning/10 text-warning`:a===`critical`?`bg-danger/10 text-danger`:`bg-muted text-muted-foreground`,b=a===`ok`?v:a===`warning`?P:a===`critical`?S:ye,x=i(a===`ok`?`widget.statusNormal`:a===`warning`?`widget.statusWarning`:a===`critical`?`widget.statusCritical`:`widget.statusUnknown`),C=t===`C`?`#2563eb`:t===`RPM`?`#f59e0b`:t===`W`?`#8b5cf6`:t===`V`?`#10b981`:t===`A`?`#06b6d4`:`#a1a1aa`;return(0,U.jsxs)(`div`,{className:I(`relative flex h-full flex-col`,!s&&`opacity-50 grayscale transition-[filter,opacity]`),children:[r&&d.length>0&&(0,U.jsxs)(`div`,{className:`absolute right-0 top-0 z-20`,children:[(0,U.jsxs)(`button`,{ref:u,type:`button`,"aria-label":i(`widget.selectSensor`),"aria-haspopup":`listbox`,"aria-expanded":c,onMouseDown:e=>e.stopPropagation(),onClick:()=>l(e=>!e),className:`flex max-w-[140px] items-center gap-1 rounded px-1 py-0.5 text-[10px] text-muted-foreground hover:bg-muted`,children:[(0,U.jsx)(`span`,{className:`truncate`,children:p??i(`widget.select`)}),(0,U.jsx)(_,{className:`h-3 w-3 shrink-0`})]}),c&&(0,U.jsx)(mn,{anchorRef:u,options:d,active:p,readings:o,onSelect:h,onClose:()=>l(!1)})]}),(0,U.jsxs)(`div`,{className:`flex flex-1 flex-col justify-center`,children:[(0,U.jsx)(`div`,{className:`font-mono text-3xl font-bold leading-none tracking-tight text-foreground`,children:e==null?(0,U.jsx)(`span`,{className:`text-muted-foreground`,children:`—`}):(0,U.jsxs)(U.Fragment,{children:[Number.isInteger(e)?e:e.toFixed(1),(0,U.jsx)(`span`,{className:`ml-0.5 text-sm font-normal text-muted-foreground`,children:t})]})}),(0,U.jsxs)(`div`,{className:`mt-2 flex items-center gap-2`,children:[(0,U.jsxs)(`span`,{className:I(`inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-semibold`,m),children:[(0,U.jsx)(b,{className:`h-3 w-3 shrink-0`,"aria-hidden":`true`}),x]}),f&&(0,U.jsx)(`span`,{className:`truncate text-[10px] text-muted-foreground`,children:f})]})]}),(0,U.jsx)(pn,{data:Array.isArray(y)?y:[],color:C})]})}catch{return(0,U.jsx)(`div`,{className:`flex h-full items-center justify-center text-muted-foreground`,children:`—`})}}var gn=d(e=>({range:`live`,setRange:t=>e({range:t})})),_n=`dangerouslySetInnerHTML.onCopy.onCopyCapture.onCut.onCutCapture.onPaste.onPasteCapture.onCompositionEnd.onCompositionEndCapture.onCompositionStart.onCompositionStartCapture.onCompositionUpdate.onCompositionUpdateCapture.onFocus.onFocusCapture.onBlur.onBlurCapture.onChange.onChangeCapture.onBeforeInput.onBeforeInputCapture.onInput.onInputCapture.onReset.onResetCapture.onSubmit.onSubmitCapture.onInvalid.onInvalidCapture.onLoad.onLoadCapture.onError.onErrorCapture.onKeyDown.onKeyDownCapture.onKeyPress.onKeyPressCapture.onKeyUp.onKeyUpCapture.onAbort.onAbortCapture.onCanPlay.onCanPlayCapture.onCanPlayThrough.onCanPlayThroughCapture.onDurationChange.onDurationChangeCapture.onEmptied.onEmptiedCapture.onEncrypted.onEncryptedCapture.onEnded.onEndedCapture.onLoadedData.onLoadedDataCapture.onLoadedMetadata.onLoadedMetadataCapture.onLoadStart.onLoadStartCapture.onPause.onPauseCapture.onPlay.onPlayCapture.onPlaying.onPlayingCapture.onProgress.onProgressCapture.onRateChange.onRateChangeCapture.onSeeked.onSeekedCapture.onSeeking.onSeekingCapture.onStalled.onStalledCapture.onSuspend.onSuspendCapture.onTimeUpdate.onTimeUpdateCapture.onVolumeChange.onVolumeChangeCapture.onWaiting.onWaitingCapture.onAuxClick.onAuxClickCapture.onClick.onClickCapture.onContextMenu.onContextMenuCapture.onDoubleClick.onDoubleClickCapture.onDrag.onDragCapture.onDragEnd.onDragEndCapture.onDragEnter.onDragEnterCapture.onDragExit.onDragExitCapture.onDragLeave.onDragLeaveCapture.onDragOver.onDragOverCapture.onDragStart.onDragStartCapture.onDrop.onDropCapture.onMouseDown.onMouseDownCapture.onMouseEnter.onMouseLeave.onMouseMove.onMouseMoveCapture.onMouseOut.onMouseOutCapture.onMouseOver.onMouseOverCapture.onMouseUp.onMouseUpCapture.onSelect.onSelectCapture.onTouchCancel.onTouchCancelCapture.onTouchEnd.onTouchEndCapture.onTouchMove.onTouchMoveCapture.onTouchStart.onTouchStartCapture.onPointerDown.onPointerDownCapture.onPointerMove.onPointerMoveCapture.onPointerUp.onPointerUpCapture.onPointerCancel.onPointerCancelCapture.onPointerEnter.onPointerEnterCapture.onPointerLeave.onPointerLeaveCapture.onPointerOver.onPointerOverCapture.onPointerOut.onPointerOutCapture.onGotPointerCapture.onGotPointerCaptureCapture.onLostPointerCapture.onLostPointerCaptureCapture.onScroll.onScrollCapture.onWheel.onWheelCapture.onAnimationStart.onAnimationStartCapture.onAnimationEnd.onAnimationEndCapture.onAnimationIteration.onAnimationIterationCapture.onTransitionEnd.onTransitionEndCapture`.split(`.`);function vn(e){return typeof e==`string`?_n.includes(e):!1}var yn=new Set(`aria-activedescendant.aria-atomic.aria-autocomplete.aria-busy.aria-checked.aria-colcount.aria-colindex.aria-colspan.aria-controls.aria-current.aria-describedby.aria-details.aria-disabled.aria-errormessage.aria-expanded.aria-flowto.aria-haspopup.aria-hidden.aria-invalid.aria-keyshortcuts.aria-label.aria-labelledby.aria-level.aria-live.aria-modal.aria-multiline.aria-multiselectable.aria-orientation.aria-owns.aria-placeholder.aria-posinset.aria-pressed.aria-readonly.aria-relevant.aria-required.aria-roledescription.aria-rowcount.aria-rowindex.aria-rowspan.aria-selected.aria-setsize.aria-sort.aria-valuemax.aria-valuemin.aria-valuenow.aria-valuetext.className.color.height.id.lang.max.media.method.min.name.style.target.width.role.tabIndex.accentHeight.accumulate.additive.alignmentBaseline.allowReorder.alphabetic.amplitude.arabicForm.ascent.attributeName.attributeType.autoReverse.azimuth.baseFrequency.baselineShift.baseProfile.bbox.begin.bias.by.calcMode.capHeight.clip.clipPath.clipPathUnits.clipRule.colorInterpolation.colorInterpolationFilters.colorProfile.colorRendering.contentScriptType.contentStyleType.cursor.cx.cy.d.decelerate.descent.diffuseConstant.direction.display.divisor.dominantBaseline.dur.dx.dy.edgeMode.elevation.enableBackground.end.exponent.externalResourcesRequired.fill.fillOpacity.fillRule.filter.filterRes.filterUnits.floodColor.floodOpacity.focusable.fontFamily.fontSize.fontSizeAdjust.fontStretch.fontStyle.fontVariant.fontWeight.format.from.fx.fy.g1.g2.glyphName.glyphOrientationHorizontal.glyphOrientationVertical.glyphRef.gradientTransform.gradientUnits.hanging.horizAdvX.horizOriginX.href.ideographic.imageRendering.in2.in.intercept.k1.k2.k3.k4.k.kernelMatrix.kernelUnitLength.kerning.keyPoints.keySplines.keyTimes.lengthAdjust.letterSpacing.lightingColor.limitingConeAngle.local.markerEnd.markerHeight.markerMid.markerStart.markerUnits.markerWidth.mask.maskContentUnits.maskUnits.mathematical.mode.numOctaves.offset.opacity.operator.order.orient.orientation.origin.overflow.overlinePosition.overlineThickness.paintOrder.panose1.pathLength.patternContentUnits.patternTransform.patternUnits.pointerEvents.pointsAtX.pointsAtY.pointsAtZ.preserveAlpha.preserveAspectRatio.primitiveUnits.r.radius.refX.refY.renderingIntent.repeatCount.repeatDur.requiredExtensions.requiredFeatures.restart.result.rotate.rx.ry.seed.shapeRendering.slope.spacing.specularConstant.specularExponent.speed.spreadMethod.startOffset.stdDeviation.stemh.stemv.stitchTiles.stopColor.stopOpacity.strikethroughPosition.strikethroughThickness.string.stroke.strokeDasharray.strokeDashoffset.strokeLinecap.strokeLinejoin.strokeMiterlimit.strokeOpacity.strokeWidth.surfaceScale.systemLanguage.tableValues.targetX.targetY.textAnchor.textDecoration.textLength.textRendering.to.transform.u1.u2.underlinePosition.underlineThickness.unicode.unicodeBidi.unicodeRange.unitsPerEm.vAlphabetic.values.vectorEffect.version.vertAdvY.vertOriginX.vertOriginY.vHanging.vIdeographic.viewTarget.visibility.vMathematical.widths.wordSpacing.writingMode.x1.x2.x.xChannelSelector.xHeight.xlinkActuate.xlinkArcrole.xlinkHref.xlinkRole.xlinkShow.xlinkTitle.xlinkType.xmlBase.xmlLang.xmlns.xmlnsXlink.xmlSpace.y1.y2.y.yChannelSelector.z.zoomAndPan.ref.key.angle`.split(`.`));function bn(e){return typeof e==`string`?yn.has(e):!1}function xn(e){return typeof e==`string`&&e.startsWith(`data-`)}function Sn(e){if(typeof e!=`object`||!e)return{};var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(bn(n)||xn(n))&&(t[n]=e[n]);return t}function Cn(e){if(e==null)return null;if((0,H.isValidElement)(e)&&typeof e.props==`object`&&e.props!==null){var t=e.props;return Sn(t)}return typeof e==`object`&&!Array.isArray(e)?Sn(e):null}function wn(e){var t={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(bn(n)||xn(n)||vn(n))&&(t[n]=e[n]);return t}function Tn(e){return e==null?null:(0,H.isValidElement)(e)?wn(e.props):typeof e==`object`&&!Array.isArray(e)?wn(e):null}var En=[`children`,`width`,`height`,`viewBox`,`className`,`style`,`title`,`desc`];function Dn(){return Dn=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:n,width:r,height:i,viewBox:a,className:o,style:s,title:c,desc:l}=e,u=On(e,En),d=a||{width:r,height:i,x:0,y:0},f=V(`recharts-surface`,o);return H.createElement(`svg`,Dn({},wn(u),{className:f,width:r,height:i,style:s,viewBox:`${d.x} ${d.y} ${d.width} ${d.height}`,ref:t}),H.createElement(`title`,null,c),H.createElement(`desc`,null,l),n)}),jn=[`children`,`className`];function Mn(){return Mn=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var{children:n,className:r}=e,i=Nn(e,jn),a=V(`recharts-layer`,r);return H.createElement(`g`,Mn({className:a},wn(i),{ref:t}),n)}),In=(0,H.createContext)(null),Ln=()=>(0,H.useContext)(In);function W(e){return function(){return e}}var Rn=Math.cos,zn=Math.sin,Bn=Math.sqrt,Vn=Math.PI;Vn/2;var Hn=2*Vn,Un=Math.PI,Wn=2*Un,Gn=1e-6,Kn=Wn-Gn;function qn(e){this._+=e[0];for(let t=1,n=e.length;t=0))throw Error(`invalid digits: ${e}`);if(t>15)return qn;let n=10**t;return function(e){this._+=e[0];for(let t=1,r=e.length;tGn)if(!(Math.abs(u*s-c*l)>Gn)||!i)this._append`L${this._x1=e},${this._y1=t}`;else{let f=n-a,p=r-o,m=s*s+c*c,h=f*f+p*p,g=Math.sqrt(m),_=Math.sqrt(d),v=i*Math.tan((Un-Math.acos((m+d-h)/(2*g*_)))/2),y=v/_,b=v/g;Math.abs(y-1)>Gn&&this._append`L${e+y*l},${t+y*u}`,this._append`A${i},${i},0,0,${+(u*f>l*p)},${this._x1=e+b*s},${this._y1=t+b*c}`}}arc(e,t,n,r,i,a){if(e=+e,t=+t,n=+n,a=!!a,n<0)throw Error(`negative radius: ${n}`);let o=n*Math.cos(r),s=n*Math.sin(r),c=e+o,l=t+s,u=1^a,d=a?r-i:i-r;this._x1===null?this._append`M${c},${l}`:(Math.abs(this._x1-c)>Gn||Math.abs(this._y1-l)>Gn)&&this._append`L${c},${l}`,n&&(d<0&&(d=d%Wn+Wn),d>Kn?this._append`A${n},${n},0,1,${u},${e-o},${t-s}A${n},${n},0,1,${u},${this._x1=c},${this._y1=l}`:d>Gn&&this._append`A${n},${n},0,${+(d>=Un)},${u},${this._x1=e+n*Math.cos(i)},${this._y1=t+n*Math.sin(i)}`)}rect(e,t,n,r){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+t}h${n=+n}v${+r}h${-n}Z`}toString(){return this._}};function Xn(){return new Yn}Xn.prototype=Yn.prototype;function Zn(e){let t=3;return e.digits=function(n){if(!arguments.length)return t;if(n==null)t=null;else{let e=Math.floor(n);if(!(e>=0))throw RangeError(`invalid digits: ${n}`);t=e}return e},()=>new Yn(t)}Array.prototype.slice;function Qn(e){return typeof e==`object`&&`length`in e?e:Array.from(e)}function $n(e){this._context=e}$n.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t);break}}};function er(e){return new $n(e)}function tr(e){return e[0]}function nr(e){return e[1]}function rr(e,t){var n=W(!0),r=null,i=er,a=null,o=Zn(s);e=typeof e==`function`?e:e===void 0?tr:W(e),t=typeof t==`function`?t:t===void 0?nr:W(t);function s(s){var c,l=(s=Qn(s)).length,u,d=!1,f;for(r??(a=i(f=o())),c=0;c<=l;++c)!(c=d;--f)s.point(_[f],v[f]);s.lineEnd(),s.areaEnd()}h&&(_[u]=+e(m,u,l),v[u]=+t(m,u,l),s.point(r?+r(m,u,l):_[u],n?+n(m,u,l):v[u]))}if(g)return s=null,g+``||null}function u(){return rr().defined(i).curve(o).context(a)}return l.x=function(t){return arguments.length?(e=typeof t==`function`?t:W(+t),r=null,l):e},l.x0=function(t){return arguments.length?(e=typeof t==`function`?t:W(+t),l):e},l.x1=function(e){return arguments.length?(r=e==null?null:typeof e==`function`?e:W(+e),l):r},l.y=function(e){return arguments.length?(t=typeof e==`function`?e:W(+e),n=null,l):t},l.y0=function(e){return arguments.length?(t=typeof e==`function`?e:W(+e),l):t},l.y1=function(e){return arguments.length?(n=e==null?null:typeof e==`function`?e:W(+e),l):n},l.lineX0=l.lineY0=function(){return u().x(e).y(t)},l.lineY1=function(){return u().x(e).y(n)},l.lineX1=function(){return u().x(r).y(t)},l.defined=function(e){return arguments.length?(i=typeof e==`function`?e:W(!!e),l):i},l.curve=function(e){return arguments.length?(o=e,a!=null&&(s=o(a)),l):o},l.context=function(e){return arguments.length?(e==null?a=s=null:s=o(a=e),l):a},l}var ar=class{constructor(e,t){this._context=e,this._x=t}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,t,e,t):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+t)/2,e,this._y0,e,t);break}this._x0=e,this._y0=t}};function or(e){return new ar(e,!0)}function sr(e){return new ar(e,!1)}var cr={draw(e,t){let n=Bn(t/Vn);e.moveTo(n,0),e.arc(0,0,n,0,Hn)}},lr={draw(e,t){let n=Bn(t/5)/2;e.moveTo(-3*n,-n),e.lineTo(-n,-n),e.lineTo(-n,-3*n),e.lineTo(n,-3*n),e.lineTo(n,-n),e.lineTo(3*n,-n),e.lineTo(3*n,n),e.lineTo(n,n),e.lineTo(n,3*n),e.lineTo(-n,3*n),e.lineTo(-n,n),e.lineTo(-3*n,n),e.closePath()}},ur=Bn(1/3),dr=ur*2,fr={draw(e,t){let n=Bn(t/dr),r=n*ur;e.moveTo(0,-n),e.lineTo(r,0),e.lineTo(0,n),e.lineTo(-r,0),e.closePath()}},pr={draw(e,t){let n=Bn(t),r=-n/2;e.rect(r,r,n,n)}},mr=.8908130915292852,hr=zn(Vn/10)/zn(7*Vn/10),gr=zn(Hn/10)*hr,_r=-Rn(Hn/10)*hr,vr={draw(e,t){let n=Bn(t*mr),r=gr*n,i=_r*n;e.moveTo(0,-n),e.lineTo(r,i);for(let t=1;t<5;++t){let a=Hn*t/5,o=Rn(a),s=zn(a);e.lineTo(s*n,-o*n),e.lineTo(o*r-s*i,s*r+o*i)}e.closePath()}},yr=Bn(3),br={draw(e,t){let n=-Bn(t/(yr*3));e.moveTo(0,n*2),e.lineTo(-yr*n,-n),e.lineTo(yr*n,-n),e.closePath()}},xr=-.5,Sr=Bn(3)/2,Cr=1/Bn(12),wr=(Cr/2+1)*3,Tr={draw(e,t){let n=Bn(t/wr),r=n/2,i=n*Cr,a=r,o=n*Cr+n,s=-a,c=o;e.moveTo(r,i),e.lineTo(a,o),e.lineTo(s,c),e.lineTo(xr*r-Sr*i,Sr*r+xr*i),e.lineTo(xr*a-Sr*o,Sr*a+xr*o),e.lineTo(xr*s-Sr*c,Sr*s+xr*c),e.lineTo(xr*r+Sr*i,xr*i-Sr*r),e.lineTo(xr*a+Sr*o,xr*o-Sr*a),e.lineTo(xr*s+Sr*c,xr*c-Sr*s),e.closePath()}};function Er(e,t){let n=null,r=Zn(i);e=typeof e==`function`?e:W(e||cr),t=typeof t==`function`?t:W(t===void 0?64:+t);function i(){let i;if(n||=i=r(),e.apply(this,arguments).draw(n,+t.apply(this,arguments)),i)return n=null,i+``||null}return i.type=function(t){return arguments.length?(e=typeof t==`function`?t:W(t),i):e},i.size=function(e){return arguments.length?(t=typeof e==`function`?e:W(+e),i):t},i.context=function(e){return arguments.length?(n=e??null,i):n},i}function Dr(){}function Or(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function kr(e){this._context=e}kr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Or(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Or(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Ar(e){return new kr(e)}function jr(e){this._context=e}jr.prototype={areaStart:Dr,areaEnd:Dr,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Or(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Mr(e){return new jr(e)}function Nr(e){this._context=e}Nr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Or(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function Pr(e){return new Nr(e)}function Fr(e){this._context=e}Fr.prototype={areaStart:Dr,areaEnd:Dr,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function Ir(e){return new Fr(e)}function Lr(e){return e<0?-1:1}function Rr(e,t,n){var r=e._x1-e._x0,i=t-e._x1,a=(e._y1-e._y0)/(r||i<0&&-0),o=(n-e._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(Lr(a)+Lr(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function zr(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function Br(e,t,n){var r=e._x0,i=e._y0,a=e._x1,o=e._y1,s=(a-r)/3;e._context.bezierCurveTo(r+s,i+s*t,a-s,o-s*n,a,o)}function Vr(e){this._context=e}Vr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:Br(this,this._t0,zr(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){var n=NaN;if(e=+e,t=+t,!(e===this._x1&&t===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,Br(this,zr(this,n=Rr(this,e,t)),n);break;default:Br(this,this._t0,n=Rr(this,e,t));break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t,this._t0=n}}};function Hr(e){this._context=new Ur(e)}(Hr.prototype=Object.create(Vr.prototype)).point=function(e,t){Vr.prototype.point.call(this,t,e)};function Ur(e){this._context=e}Ur.prototype={moveTo:function(e,t){this._context.moveTo(t,e)},closePath:function(){this._context.closePath()},lineTo:function(e,t){this._context.lineTo(t,e)},bezierCurveTo:function(e,t,n,r,i,a){this._context.bezierCurveTo(t,e,r,n,a,i)}};function Wr(e){return new Vr(e)}function Gr(e){return new Hr(e)}function Kr(e){this._context=e}Kr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var e=this._x,t=this._y,n=e.length;if(n)if(this._line?this._context.lineTo(e[0],t[0]):this._context.moveTo(e[0],t[0]),n===2)this._context.lineTo(e[1],t[1]);else for(var r=qr(e),i=qr(t),a=0,o=1;o=0;--t)i[t]=(o[t]-i[t+1])/a[t];for(a[n-1]=(e[n]+i[n-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}break}this._x=e,this._y=t}};function Xr(e){return new Yr(e,.5)}function Zr(e){return new Yr(e,0)}function Qr(e){return new Yr(e,1)}function $r(e,t){if((o=e.length)>1)for(var n=1,r,i,a=e[t[0]],o,s=a.length;n=0;)n[t]=t;return n}function ti(e,t){return e[t]}function ni(e){let t=[];return t.key=e,t}function ri(){var e=W([]),t=ei,n=$r,r=ti;function i(i){var a=Array.from(e.apply(this,arguments),ni),o,s=a.length,c=-1,l;for(let e of i)for(o=0,++c;o0){for(var n,r,i=0,a=e[0].length,o;i0){for(var n=0,r=e[t[0]],i,a=r.length;n0)||!((a=(i=e[t[0]]).length)>0))){for(var n=0,r=1,i,a,o;r{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return e===`__proto__`}e.isUnsafeProperty=t})),ci=i((e=>{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){switch(typeof e){case`number`:case`symbol`:return!1;case`string`:return e.includes(`.`)||e.includes(`[`)||e.includes(`]`)}}e.isDeepKey=t})),li=i((e=>{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){return typeof e==`string`||typeof e==`symbol`?e:Object.is(e?.valueOf?.(),-0)?`-0`:String(e)}e.toKey=t})),ui=i((e=>{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){if(e==null)return``;if(typeof e==`string`)return e;if(Array.isArray(e))return e.map(t).join(`,`);let n=String(e);return n===`0`&&Object.is(Number(e),-0)?`-0`:n}e.toString=t})),di=i((e=>{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=ui(),n=li();function r(e){if(Array.isArray(e))return e.map(n.toKey);if(typeof e==`symbol`)return[e];e=t.toString(e);let r=[],i=e.length;if(i===0)return r;let a=0,o=``,s=``,c=!1;for(e.charCodeAt(0)===46&&(r.push(``),a++);a{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=si(),n=ci(),r=li(),i=di();function a(e,s,c){if(e==null)return c;switch(typeof s){case`string`:{if(t.isUnsafeProperty(s))return c;let r=e[s];return r===void 0?n.isDeepKey(s)?a(e,i.toPath(s),c):c:r}case`number`:case`symbol`:{typeof s==`number`&&(s=r.toKey(s));let t=e[s];return t===void 0?c:t}default:{if(Array.isArray(s))return o(e,s,c);if(s=Object.is(s?.valueOf(),-0)?`-0`:String(s),t.isUnsafeProperty(s))return c;let n=e[s];return n===void 0?c:n}}}function o(e,n,r){if(n.length===0)return r;let i=e;for(let e=0;e{t.exports=fi().get})),mi=4;function hi(e){var t=10**(arguments.length>1&&arguments[1]!==void 0?arguments[1]:mi),n=Math.round(e*t)/t;return Object.is(n,-0)?0:n}function gi(e){var t=[...arguments].slice(1);return e.reduce((e,n,r)=>{var i=t[r-1];return typeof i==`string`?e+i+n:i===void 0?e+n:e+hi(i)+n},``)}var _i=n(pi()),vi=e=>e===0?0:e>0?1:-1,yi=e=>typeof e==`number`&&e!=+e,bi=e=>typeof e==`string`&&e.indexOf(`%`)===e.length-1,G=e=>(typeof e==`number`||e instanceof Number)&&!yi(e),xi=e=>G(e)||typeof e==`string`,Si=0,Ci=e=>{var t=++Si;return`${e||``}${t}`},wi=function(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0,r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(!G(e)&&typeof e!=`string`)return n;var i;if(bi(e)){if(t==null)return n;var a=e.indexOf(`%`);i=t*parseFloat(e.slice(0,a))/100}else i=+e;return yi(i)&&(i=n),r&&t!=null&&i>t&&(i=t),i},Ti=e=>{if(!Array.isArray(e))return!1;for(var t=e.length,n={},r=0;re&&(typeof t==`function`?t(e):(0,_i.default)(e,t))===n)}var Oi=e=>e==null,ki=e=>Oi(e)?e:`${e.charAt(0).toUpperCase()}${e.slice(1)}`;function Ai(e){return e!=null}function ji(){}var Mi=[`type`,`size`,`sizeType`];function Ni(){return Ni=Object.assign?Object.assign.bind():function(e){for(var t=1;tVi[`symbol${ki(e)}`]||cr,Wi=(e,t,n)=>{if(t===`area`)return e;switch(n){case`cross`:return 5*e*e/9;case`diamond`:return .5*e*e/Math.sqrt(3);case`square`:return e*e;case`star`:var r=18*Hi;return 1.25*e*e*(Math.tan(r)-Math.tan(r*2)*Math.tan(r)**2);case`triangle`:return Math.sqrt(3)*e*e/4;case`wye`:return(21-10*Math.sqrt(3))*e*e/8;default:return Math.PI*e*e/4}},Gi=(e,t)=>{Vi[`symbol${ki(e)}`]=t},Ki=e=>{var{type:t=`circle`,size:n=64,sizeType:r=`area`}=e,i=Fi(Fi({},zi(e,Mi)),{},{type:t,size:n,sizeType:r}),a=`circle`;typeof t==`string`&&(a=t);var o=()=>{var e=Ui(a),t=Er().type(e).size(Wi(n,r,a))();if(t!==null)return t},{className:s,cx:c,cy:l}=i,u=wn(i);return G(c)&&G(l)&&G(n)?H.createElement(`path`,Ni({},u,{className:V(`recharts-symbols`,s),transform:`translate(${c}, ${l})`,d:o()})):null};Ki.registerSymbol=Gi;var qi=e=>`radius`in e&&`startAngle`in e&&`endAngle`in e,Ji=(e,t)=>{if(!e||typeof e==`function`||typeof e==`boolean`)return null;var n=e;if((0,H.isValidElement)(e)&&(n=e.props),typeof n!=`object`&&typeof n!=`function`)return null;var r={};return Object.keys(n).forEach(e=>{vn(e)&&typeof n[e]==`function`&&(r[e]=t||(t=>n[e](n,t)))}),r},Yi=(e,t,n)=>r=>(e(t,n,r),null),Xi=(e,t,n)=>{if(e===null||typeof e!=`object`&&typeof e!=`function`)return null;var r=null;return Object.keys(e).forEach(i=>{var a=e[i];vn(i)&&typeof a==`function`&&(r||={},r[i]=Yi(a,t,n))}),r};function Zi(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function Qi(e){for(var t=1;t(e[t]===void 0&&r[t]!==void 0&&(e[t]=r[t]),e),n)}function ra(){return ra=Object.assign?Object.assign.bind():function(e){for(var t=1;t`u`)return;let t=document.head||document.getElementsByTagName(`head`)[0],n=document.createElement(`style`);n.type=`text/css`,t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}var ae=k.createContext({drawerRef:{current:null},overlayRef:{current:null},onPress:()=>{},onRelease:()=>{},onDrag:()=>{},onNestedDrag:()=>{},onNestedOpenChange:()=>{},onNestedRelease:()=>{},openProp:void 0,dismissible:!1,isOpen:!1,isDragging:!1,keyboardIsOpen:{current:!1},snapPointsOffset:null,snapPoints:null,handleOnly:!1,modal:!1,shouldFade:!1,activeSnapPoint:null,onOpenChange:()=>{},setActiveSnapPoint:()=>{},closeDrawer:()=>{},direction:`bottom`,shouldAnimate:{current:!0},shouldScaleBackground:!1,setBackgroundColorOnScale:!0,noBodyStyles:!1,container:null,autoFocus:!1}),A=()=>{let e=k.useContext(ae);if(!e)throw Error(`useDrawerContext must be used within a Drawer.Root`);return e};ie(`[data-vaul-drawer]{touch-action:none;will-change:transform;transition:transform .5s cubic-bezier(.32, .72, 0, 1);animation-duration:.5s;animation-timing-function:cubic-bezier(0.32,0.72,0,1)}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=bottom][data-state=open]{animation-name:slideFromBottom}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=bottom][data-state=closed]{animation-name:slideToBottom}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=top][data-state=open]{animation-name:slideFromTop}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=top][data-state=closed]{animation-name:slideToTop}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=left][data-state=open]{animation-name:slideFromLeft}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=left][data-state=closed]{animation-name:slideToLeft}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=right][data-state=open]{animation-name:slideFromRight}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=right][data-state=closed]{animation-name:slideToRight}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=bottom]{transform:translate3d(0,var(--initial-transform,100%),0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=top]{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=left]{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=right]{transform:translate3d(var(--initial-transform,100%),0,0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=top]{transform:translate3d(0,var(--snap-point-height,0),0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=bottom]{transform:translate3d(0,var(--snap-point-height,0),0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=left]{transform:translate3d(var(--snap-point-height,0),0,0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=right]{transform:translate3d(var(--snap-point-height,0),0,0)}[data-vaul-overlay][data-vaul-snap-points=false]{animation-duration:.5s;animation-timing-function:cubic-bezier(0.32,0.72,0,1)}[data-vaul-overlay][data-vaul-snap-points=false][data-state=open]{animation-name:fadeIn}[data-vaul-overlay][data-state=closed]{animation-name:fadeOut}[data-vaul-animate=false]{animation:none!important}[data-vaul-overlay][data-vaul-snap-points=true]{opacity:0;transition:opacity .5s cubic-bezier(.32, .72, 0, 1)}[data-vaul-overlay][data-vaul-snap-points=true]{opacity:1}[data-vaul-drawer]:not([data-vaul-custom-container=true])::after{content:'';position:absolute;background:inherit;background-color:inherit}[data-vaul-drawer][data-vaul-drawer-direction=top]::after{top:initial;bottom:100%;left:0;right:0;height:200%}[data-vaul-drawer][data-vaul-drawer-direction=bottom]::after{top:100%;bottom:initial;left:0;right:0;height:200%}[data-vaul-drawer][data-vaul-drawer-direction=left]::after{left:initial;right:100%;top:0;bottom:0;width:200%}[data-vaul-drawer][data-vaul-drawer-direction=right]::after{left:100%;right:initial;top:0;bottom:0;width:200%}[data-vaul-overlay][data-vaul-snap-points=true]:not([data-vaul-snap-points-overlay=true]):not( +import{I as e,M as t,S as n,_ as r,a as i,c as a,d as o,g as s,n as c,o as l,t as u}from"./auth-store-CVoL-wZN.js";import{C as d,D as f,F as p,I as m,M as h,N as g,P as _,R as v,T as y,_ as b,d as x,f as S,g as ee,h as C,k as w,m as T,p as E,u as te}from"./index-DXNHFWmw.js";var ne=c(`ellipsis-vertical`,[[`circle`,{cx:`12`,cy:`12`,r:`1`,key:`41hilf`}],[`circle`,{cx:`12`,cy:`5`,r:`1`,key:`gxeob9`}],[`circle`,{cx:`12`,cy:`19`,r:`1`,key:`lyex9k`}]]),D=c(`log-out`,[[`path`,{d:`m16 17 5-5-5-5`,key:`1bji2h`}],[`path`,{d:`M21 12H9`,key:`dn1m92`}],[`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`,key:`1uf3rs`}]]),re=c(`menu`,[[`path`,{d:`M4 5h16`,key:`1tepv9`}],[`path`,{d:`M4 12h16`,key:`1lakjw`}],[`path`,{d:`M4 19h16`,key:`1djgab`}]]),O=c(`wifi`,[[`path`,{d:`M12 20h.01`,key:`zekei9`}],[`path`,{d:`M2 8.82a15 15 0 0 1 20 0`,key:`dnpr2z`}],[`path`,{d:`M5 12.859a10 10 0 0 1 14 0`,key:`1x1e6c`}],[`path`,{d:`M8.5 16.429a5 5 0 0 1 7 0`,key:`1bycff`}]]),k=e(t(),1);function ie(e){if(!e||typeof document>`u`)return;let t=document.head||document.getElementsByTagName(`head`)[0],n=document.createElement(`style`);n.type=`text/css`,t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}var ae=k.createContext({drawerRef:{current:null},overlayRef:{current:null},onPress:()=>{},onRelease:()=>{},onDrag:()=>{},onNestedDrag:()=>{},onNestedOpenChange:()=>{},onNestedRelease:()=>{},openProp:void 0,dismissible:!1,isOpen:!1,isDragging:!1,keyboardIsOpen:{current:!1},snapPointsOffset:null,snapPoints:null,handleOnly:!1,modal:!1,shouldFade:!1,activeSnapPoint:null,onOpenChange:()=>{},setActiveSnapPoint:()=>{},closeDrawer:()=>{},direction:`bottom`,shouldAnimate:{current:!0},shouldScaleBackground:!1,setBackgroundColorOnScale:!0,noBodyStyles:!1,container:null,autoFocus:!1}),A=()=>{let e=k.useContext(ae);if(!e)throw Error(`useDrawerContext must be used within a Drawer.Root`);return e};ie(`[data-vaul-drawer]{touch-action:none;will-change:transform;transition:transform .5s cubic-bezier(.32, .72, 0, 1);animation-duration:.5s;animation-timing-function:cubic-bezier(0.32,0.72,0,1)}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=bottom][data-state=open]{animation-name:slideFromBottom}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=bottom][data-state=closed]{animation-name:slideToBottom}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=top][data-state=open]{animation-name:slideFromTop}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=top][data-state=closed]{animation-name:slideToTop}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=left][data-state=open]{animation-name:slideFromLeft}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=left][data-state=closed]{animation-name:slideToLeft}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=right][data-state=open]{animation-name:slideFromRight}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=right][data-state=closed]{animation-name:slideToRight}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=bottom]{transform:translate3d(0,var(--initial-transform,100%),0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=top]{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=left]{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=right]{transform:translate3d(var(--initial-transform,100%),0,0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=top]{transform:translate3d(0,var(--snap-point-height,0),0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=bottom]{transform:translate3d(0,var(--snap-point-height,0),0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=left]{transform:translate3d(var(--snap-point-height,0),0,0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=right]{transform:translate3d(var(--snap-point-height,0),0,0)}[data-vaul-overlay][data-vaul-snap-points=false]{animation-duration:.5s;animation-timing-function:cubic-bezier(0.32,0.72,0,1)}[data-vaul-overlay][data-vaul-snap-points=false][data-state=open]{animation-name:fadeIn}[data-vaul-overlay][data-state=closed]{animation-name:fadeOut}[data-vaul-animate=false]{animation:none!important}[data-vaul-overlay][data-vaul-snap-points=true]{opacity:0;transition:opacity .5s cubic-bezier(.32, .72, 0, 1)}[data-vaul-overlay][data-vaul-snap-points=true]{opacity:1}[data-vaul-drawer]:not([data-vaul-custom-container=true])::after{content:'';position:absolute;background:inherit;background-color:inherit}[data-vaul-drawer][data-vaul-drawer-direction=top]::after{top:initial;bottom:100%;left:0;right:0;height:200%}[data-vaul-drawer][data-vaul-drawer-direction=bottom]::after{top:100%;bottom:initial;left:0;right:0;height:200%}[data-vaul-drawer][data-vaul-drawer-direction=left]::after{left:initial;right:100%;top:0;bottom:0;width:200%}[data-vaul-drawer][data-vaul-drawer-direction=right]::after{left:100%;right:initial;top:0;bottom:0;width:200%}[data-vaul-overlay][data-vaul-snap-points=true]:not([data-vaul-snap-points-overlay=true]):not( [data-state=closed] ){opacity:0}[data-vaul-overlay][data-vaul-snap-points-overlay=true]{opacity:1}[data-vaul-handle]{display:block;position:relative;opacity:.7;background:#e2e2e4;margin-left:auto;margin-right:auto;height:5px;width:32px;border-radius:1rem;touch-action:pan-y}[data-vaul-handle]:active,[data-vaul-handle]:hover{opacity:1}[data-vaul-handle-hitarea]{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:max(100%,2.75rem);height:max(100%,2.75rem);touch-action:inherit}@media (hover:hover) and (pointer:fine){[data-vaul-drawer]{user-select:none}}@media (pointer:fine){[data-vaul-handle-hitarea]:{width:100%;height:100%}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fadeOut{to{opacity:0}}@keyframes slideFromBottom{from{transform:translate3d(0,var(--initial-transform,100%),0)}to{transform:translate3d(0,0,0)}}@keyframes slideToBottom{to{transform:translate3d(0,var(--initial-transform,100%),0)}}@keyframes slideFromTop{from{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}to{transform:translate3d(0,0,0)}}@keyframes slideToTop{to{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}}@keyframes slideFromLeft{from{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}to{transform:translate3d(0,0,0)}}@keyframes slideToLeft{to{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}}@keyframes slideFromRight{from{transform:translate3d(var(--initial-transform,100%),0,0)}to{transform:translate3d(0,0,0)}}@keyframes slideToRight{to{transform:translate3d(var(--initial-transform,100%),0,0)}}`);function oe(){let e=navigator.userAgent;return typeof window<`u`&&(/Firefox/.test(e)&&/Mobile/.test(e)||/FxiOS/.test(e))}function se(){return M(/^Mac/)}function ce(){return M(/^iPhone/)}function le(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function j(){return M(/^iPad/)||se()&&navigator.maxTouchPoints>1}function ue(){return ce()||j()}function M(e){return typeof window<`u`&&window.navigator!=null?e.test(window.navigator.platform):void 0}var de=24,fe=typeof window<`u`?k.useLayoutEffect:k.useEffect;function N(...e){return(...t)=>{for(let n of e)typeof n==`function`&&n(...t)}}var P=typeof document<`u`&&window.visualViewport;function F(e){let t=window.getComputedStyle(e);return/(auto|scroll)/.test(t.overflow+t.overflowX+t.overflowY)}function I(e){for(F(e)&&(e=e.parentElement);e&&!F(e);)e=e.parentElement;return e||document.scrollingElement||document.documentElement}var L=new Set([`checkbox`,`radio`,`range`,`color`,`file`,`image`,`button`,`submit`,`reset`]),R=0,z;function pe(e={}){let{isDisabled:t}=e;fe(()=>{if(!t)return R++,R===1&&ue()&&(z=B()),()=>{R--,R===0&&z?.()}},[t])}function B(){let e,t=0,n=n=>{e=I(n.target),!(e===document.documentElement&&e===document.body)&&(t=n.changedTouches[0].pageY)},r=n=>{if(!e||e===document.documentElement||e===document.body){n.preventDefault();return}let r=n.changedTouches[0].pageY,i=e.scrollTop,a=e.scrollHeight-e.clientHeight;a!==0&&((i<=0&&r>t||i>=a&&r{let t=e.target;he(t)&&t!==document.activeElement&&(e.preventDefault(),t.style.transform=`translateY(-2000px)`,t.focus(),requestAnimationFrame(()=>{t.style.transform=``}))},a=e=>{let t=e.target;he(t)&&(t.style.transform=`translateY(-2000px)`,requestAnimationFrame(()=>{t.style.transform=``,P&&(P.height{H(t)}):P.addEventListener(`resize`,()=>H(t),{once:!0}))}))},o=()=>{window.scrollTo(0,0)},s=window.pageXOffset,c=window.pageYOffset,l=N(me(document.documentElement,`paddingRight`,`${window.innerWidth-document.documentElement.clientWidth}px`));window.scrollTo(0,0);let u=N(V(document,`touchstart`,n,{passive:!1,capture:!0}),V(document,`touchmove`,r,{passive:!1,capture:!0}),V(document,`touchend`,i,{passive:!1,capture:!0}),V(document,`focus`,a,!0),V(window,`scroll`,o));return()=>{l(),u(),window.scrollTo(s,c)}}function me(e,t,n){let r=e.style[t];return e.style[t]=n,()=>{e.style[t]=r}}function V(e,t,n,r){return e.addEventListener(t,n,r),()=>{e.removeEventListener(t,n,r)}}function H(e){let t=document.scrollingElement||document.documentElement;for(;e&&e!==t;){let t=I(e);if(t!==document.documentElement&&t!==document.body&&t!==e){let n=t.getBoundingClientRect().top,r=e.getBoundingClientRect().top;e.getBoundingClientRect().bottom>t.getBoundingClientRect().bottom+de&&(t.scrollTop+=r-n)}e=t.parentElement}}function he(e){return e instanceof HTMLInputElement&&!L.has(e.type)||e instanceof HTMLTextAreaElement||e instanceof HTMLElement&&e.isContentEditable}function ge(e,t){typeof e==`function`?e(t):e!=null&&(e.current=t)}function U(...e){return t=>e.forEach(e=>ge(e,t))}function _e(...e){return k.useCallback(U(...e),e)}var ve=new WeakMap;function W(e,t,n=!1){if(!e||!(e instanceof HTMLElement))return;let r={};Object.entries(t).forEach(([t,n])=>{if(t.startsWith(`--`)){e.style.setProperty(t,n);return}r[t]=e.style[t],e.style[t]=n}),!n&&ve.set(e,r)}function ye(e,t){if(!e||!(e instanceof HTMLElement))return;let n=ve.get(e);n&&(e.style[t]=n[t])}var G=e=>{switch(e){case`top`:case`bottom`:return!0;case`left`:case`right`:return!1;default:return e}};function be(e,t){if(!e)return null;let n=window.getComputedStyle(e),r=n.transform||n.webkitTransform||n.mozTransform,i=r.match(/^matrix3d\((.+)\)$/);return i?parseFloat(i[1].split(`, `)[G(t)?13:12]):(i=r.match(/^matrix\((.+)\)$/),i?parseFloat(i[1].split(`, `)[G(t)?5:4]):null)}function xe(e){return 8*(Math.log(e+1)-2)}function K(e,t){if(!e)return()=>{};let n=e.style.cssText;return Object.assign(e.style,t),()=>{e.style.cssText=n}}var q={DURATION:.5,EASE:[.32,.72,0,1]},Se=.4,Ce=.25,we=100,Te=8,J=16,Ee=26,De=`vaul-dragging`;function Oe(e){let t=k.useRef(e);return k.useEffect(()=>{t.current=e}),k.useMemo(()=>(...e)=>t.current==null?void 0:t.current.call(t,...e),[])}function ke({defaultProp:e,onChange:t}){let n=k.useState(e),[r]=n,i=k.useRef(r),a=Oe(t);return k.useEffect(()=>{i.current!==r&&(a(r),i.current=r)},[r,i,a]),n}function Ae({prop:e,defaultProp:t,onChange:n=()=>{}}){let[r,i]=ke({defaultProp:t,onChange:n}),a=e!==void 0,o=a?e:r,s=Oe(n);return[o,k.useCallback(t=>{if(a){let n=typeof t==`function`?t(e):t;n!==e&&s(n)}else i(t)},[a,e,i,s])]}function je({activeSnapPointProp:e,setActiveSnapPointProp:t,snapPoints:n,drawerRef:r,overlayRef:i,fadeFromIndex:a,onSnapPointChange:o,direction:s=`bottom`,container:c,snapToSequentialPoint:l}){let[u,d]=Ae({prop:e,defaultProp:n?.[0],onChange:t}),[f,p]=k.useState(typeof window<`u`?{innerWidth:window.innerWidth,innerHeight:window.innerHeight}:void 0);k.useEffect(()=>{function e(){p({innerWidth:window.innerWidth,innerHeight:window.innerHeight})}return window.addEventListener(`resize`,e),()=>window.removeEventListener(`resize`,e)},[]);let m=k.useMemo(()=>u===n?.[n.length-1]||null,[n,u]),h=k.useMemo(()=>n?.findIndex(e=>e===u)??null,[n,u]),g=n&&n.length>0&&(a||a===0)&&!Number.isNaN(a)&&n[a]===u||!n,_=k.useMemo(()=>{let e=c?{width:c.getBoundingClientRect().width,height:c.getBoundingClientRect().height}:typeof window<`u`?{width:window.innerWidth,height:window.innerHeight}:{width:0,height:0};return n?.map(t=>{let n=typeof t==`string`,r=0;if(n&&(r=parseInt(t,10)),G(s)){let i=n?r:f?t*e.height:0;return f?s===`bottom`?e.height-i:-e.height+i:i}let i=n?r:f?t*e.width:0;return f?s===`right`?e.width-i:-e.width+i:i})??[]},[n,f,c]),v=k.useMemo(()=>h===null?null:_?.[h],[_,h]),y=k.useCallback(e=>{let t=_?.findIndex(t=>t===e)??null;o(t),W(r.current,{transition:`transform ${q.DURATION}s cubic-bezier(${q.EASE.join(`,`)})`,transform:G(s)?`translate3d(0, ${e}px, 0)`:`translate3d(${e}px, 0, 0)`}),_&&t!==_.length-1&&a!==void 0&&t!==a&&t{if(u||e){let t=n?.findIndex(t=>t===e||t===u)??-1;_&&t!==-1&&typeof _[t]==`number`&&y(_[t])}},[u,e,n,_,y]);function b({draggedDistance:e,closeDrawer:t,velocity:r,dismissible:o}){if(a===void 0)return;let c=s===`bottom`||s===`right`?(v??0)-e:(v??0)+e,u=h===a-1,d=h===0,f=e>0;if(u&&W(i.current,{transition:`opacity ${q.DURATION}s cubic-bezier(${q.EASE.join(`,`)})`}),!l&&r>2&&!f){o?t():y(_[0]);return}if(!l&&r>2&&f&&_&&n){y(_[n.length-1]);return}let p=_?.reduce((e,t)=>typeof e!=`number`||typeof t!=`number`?e:Math.abs(t-c)Se&&Math.abs(e)0&&m&&n){y(_[n.length-1]);return}if(d&&e<0&&o&&t(),h===null)return;y(_[h+e]);return}y(p)}function x({draggedDistance:e}){if(v===null)return;let t=s===`bottom`||s===`right`?v-e:v+e;(s===`bottom`||s===`right`)&&t<_[_.length-1]||(s===`top`||s===`left`)&&t>_[_.length-1]||W(r.current,{transform:G(s)?`translate3d(0, ${t}px, 0)`:`translate3d(${t}px, 0, 0)`})}function S(e,t){if(!n||typeof h!=`number`||!_||a===void 0)return null;let r=h===a-1;if(h>=a&&t)return 0;if(r&&!t)return 1;if(!g&&!r)return null;let i=r?h+1:h-1,o=r?_[i]-_[i-1]:_[i+1]-_[i],s=e/Math.abs(o);return r?1-s:s}return{isLastSnapPoint:m,activeSnapPoint:u,shouldFade:g,getPercentageDragged:S,setActiveSnapPoint:d,activeSnapPointIndex:h,onRelease:b,onDrag:x,snapPointsOffset:_}}function Me(){let{direction:e,isOpen:t,shouldScaleBackground:n,setBackgroundColorOnScale:r,noBodyStyles:i}=A(),a=k.useRef(null),o=(0,k.useMemo)(()=>document.body.style.backgroundColor,[]);function s(){return(window.innerWidth-Ee)/window.innerWidth}k.useEffect(()=>{if(t&&n){a.current&&clearTimeout(a.current);let t=document.querySelector(`[data-vaul-drawer-wrapper]`)||document.querySelector(`[vaul-drawer-wrapper]`);if(!t)return;r&&!i&&K(document.body,{background:`black`}),K(t,{transformOrigin:G(e)?`top`:`left`,transitionProperty:`transform, border-radius`,transitionDuration:`${q.DURATION}s`,transitionTimingFunction:`cubic-bezier(${q.EASE.join(`,`)})`});let n=K(t,{borderRadius:`${Te}px`,overflow:`hidden`,...G(e)?{transform:`scale(${s()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`}:{transform:`scale(${s()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`}});return()=>{n(),a.current=window.setTimeout(()=>{o?document.body.style.background=o:document.body.style.removeProperty(`background`)},q.DURATION*1e3)}}},[t,n,o])}var Y=null;function Ne({isOpen:e,modal:t,nested:n,hasBeenOpened:r,preventScrollRestoration:i,noBodyStyles:a}){let[o,s]=k.useState(()=>typeof window<`u`?window.location.href:``),c=k.useRef(0),l=k.useCallback(()=>{if(le()&&Y===null&&e&&!a){Y={position:document.body.style.position,top:document.body.style.top,left:document.body.style.left,height:document.body.style.height,right:`unset`};let{scrollX:e,innerHeight:t}=window;document.body.style.setProperty(`position`,`fixed`,`important`),Object.assign(document.body.style,{top:`${-c.current}px`,left:`${-e}px`,right:`0px`,height:`auto`}),window.setTimeout(()=>window.requestAnimationFrame(()=>{let e=t-window.innerHeight;e&&c.current>=t&&(document.body.style.top=`${-(c.current+e)}px`)}),300)}},[e]),u=k.useCallback(()=>{if(le()&&Y!==null&&!a){let e=-parseInt(document.body.style.top,10),t=-parseInt(document.body.style.left,10);Object.assign(document.body.style,Y),window.requestAnimationFrame(()=>{if(i&&o!==window.location.href){s(window.location.href);return}window.scrollTo(t,e)}),Y=null}},[o]);return k.useEffect(()=>{function e(){c.current=window.scrollY}return e(),window.addEventListener(`scroll`,e),()=>{window.removeEventListener(`scroll`,e)}},[]),k.useEffect(()=>{if(t)return()=>{typeof document>`u`||document.querySelector(`[data-vaul-drawer]`)||u()}},[t,u]),k.useEffect(()=>{n||!r||(e?(!window.matchMedia(`(display-mode: standalone)`).matches&&l(),t||window.setTimeout(()=>{u()},500)):u())},[e,r,o,t,n,l,u]),{restorePositionSetting:u}}function X({open:e,onOpenChange:t,children:n,onDrag:r,onRelease:i,snapPoints:a,shouldScaleBackground:o=!1,setBackgroundColorOnScale:s=!0,closeThreshold:c=Ce,scrollLockTimeout:l=we,dismissible:u=!0,handleOnly:d=!1,fadeFromIndex:f=a&&a.length-1,activeSnapPoint:p,setActiveSnapPoint:m,fixed:h,modal:g=!0,onClose:_,nested:v,noBodyStyles:y=!1,direction:b=`bottom`,defaultOpen:x=!1,disablePreventScroll:S=!0,snapToSequentialPoint:ee=!1,preventScrollRestoration:w=!1,repositionInputs:T=!0,onAnimationEnd:E,container:te,autoFocus:ne=!1}){let[D=!1,re]=Ae({defaultProp:x,prop:e,onChange:e=>{t?.(e),!e&&!v&&Y(),setTimeout(()=>{E?.(e)},q.DURATION*1e3),e&&!g&&typeof window<`u`&&window.requestAnimationFrame(()=>{document.body.style.pointerEvents=`auto`}),e||(document.body.style.pointerEvents=`auto`)}}),[O,ie]=k.useState(!1),[A,se]=k.useState(!1),[ce,le]=k.useState(!1),j=k.useRef(null),M=k.useRef(null),de=k.useRef(null),fe=k.useRef(null),N=k.useRef(null),P=k.useRef(!1),F=k.useRef(null),I=k.useRef(0),L=k.useRef(!1),R=k.useRef(!x),z=k.useRef(0),B=k.useRef(null),me=k.useRef(B.current?.getBoundingClientRect().height||0),V=k.useRef(B.current?.getBoundingClientRect().width||0),H=k.useRef(0),{activeSnapPoint:ge,activeSnapPointIndex:U,setActiveSnapPoint:_e,onRelease:ve,snapPointsOffset:K,onDrag:Oe,shouldFade:ke,getPercentageDragged:Me}=je({snapPoints:a,activeSnapPointProp:p,setActiveSnapPointProp:m,drawerRef:B,fadeFromIndex:f,overlayRef:j,onSnapPointChange:k.useCallback(e=>{a&&e===K.length-1&&(M.current=new Date)},[]),direction:b,container:te,snapToSequentialPoint:ee});pe({isDisabled:!D||A||!g||ce||!O||!T||!S});let{restorePositionSetting:Y}=Ne({isOpen:D,modal:g,nested:v??!1,hasBeenOpened:O,preventScrollRestoration:w,noBodyStyles:y});function X(){return(window.innerWidth-Ee)/window.innerWidth}function Pe(e){!u&&!a||B.current&&!B.current.contains(e.target)||(me.current=B.current?.getBoundingClientRect().height||0,V.current=B.current?.getBoundingClientRect().width||0,se(!0),de.current=new Date,ue()&&window.addEventListener(`touchend`,()=>P.current=!1,{once:!0}),e.target.setPointerCapture(e.pointerId),I.current=G(b)?e.pageY:e.pageX)}function Fe(e,t){let n=e,r=window.getSelection()?.toString(),i=B.current?be(B.current,b):null,a=new Date;if(n.tagName===`SELECT`||n.hasAttribute(`data-vaul-no-drag`)||n.closest(`[data-vaul-no-drag]`))return!1;if(b===`right`||b===`left`)return!0;if(M.current&&a.getTime()-M.current.getTime()<500)return!1;if(i!==null&&(b===`bottom`?i>0:i<0))return!0;if(r&&r.length>0)return!1;if(N.current&&a.getTime()-N.current.getTime()n.clientHeight){if(n.scrollTop!==0)return N.current=new Date,!1;if(n.getAttribute(`role`)===`dialog`)return!0}n=n.parentNode}return!0}function Ie(e){if(B.current&&A){let t=b===`bottom`||b===`right`?1:-1,n=(I.current-(G(b)?e.pageY:e.pageX))*t,i=n>0,s=a&&!u&&!i;if(s&&U===0)return;let c=Math.abs(n),l=document.querySelector(`[data-vaul-drawer-wrapper]`),d=c/(b===`bottom`||b===`top`?me.current:V.current),p=Me(c,i);if(p!==null&&(d=p),s&&d>=1||!P.current&&!Fe(e.target,i))return;if(B.current.classList.add(De),P.current=!0,W(B.current,{transition:`none`}),W(j.current,{transition:`none`}),a&&Oe({draggedDistance:n}),i&&!a){let e=xe(n),r=Math.min(e*-1,0)*t;W(B.current,{transform:G(b)?`translate3d(0, ${r}px, 0)`:`translate3d(${r}px, 0, 0)`});return}let m=1-d;if((ke||f&&U===f-1)&&(r?.(e,d),W(j.current,{opacity:`${m}`,transition:`none`},!0)),l&&j.current&&o){let e=Math.min(X()+d*(1-X()),1),t=8-d*8,n=Math.max(0,14-d*14);W(l,{borderRadius:`${t}px`,transform:G(b)?`scale(${e}) translate3d(0, ${n}px, 0)`:`scale(${e}) translate3d(${n}px, 0, 0)`,transition:`none`},!0)}if(!a){let e=c*t;W(B.current,{transform:G(b)?`translate3d(0, ${e}px, 0)`:`translate3d(${e}px, 0, 0)`})}}}k.useEffect(()=>{window.requestAnimationFrame(()=>{R.current=!0})},[]),k.useEffect(()=>{var e;function t(){if(!B.current||!T)return;let e=document.activeElement;if(he(e)||L.current){let e=window.visualViewport?.height||0,t=window.innerHeight,n=t-e,r=B.current.getBoundingClientRect().height||0,i=r>t*.8;H.current||=r;let o=B.current.getBoundingClientRect().top;if(Math.abs(z.current-n)>60&&(L.current=!L.current),a&&a.length>0&&K&&U){let e=K[U]||0;n+=e}if(z.current=n,r>e||L.current){let t=B.current.getBoundingClientRect().height,r=t;t>e&&(r=e-(i?o:Ee)),h?B.current.style.height=`${t-Math.max(n,0)}px`:B.current.style.height=`${Math.max(r,e-o)}px`}else oe()||(B.current.style.height=`${H.current}px`);a&&a.length>0&&!L.current?B.current.style.bottom=`0px`:B.current.style.bottom=`${Math.max(n,0)}px`}}return(e=window.visualViewport)==null||e.addEventListener(`resize`,t),()=>window.visualViewport?.removeEventListener(`resize`,t)},[U,a,K]);function Z(e){Re(),_?.(),e||re(!1),setTimeout(()=>{a&&_e(a[0])},q.DURATION*1e3)}function Le(){if(!B.current)return;let e=document.querySelector(`[data-vaul-drawer-wrapper]`),t=be(B.current,b);W(B.current,{transform:`translate3d(0, 0, 0)`,transition:`transform ${q.DURATION}s cubic-bezier(${q.EASE.join(`,`)})`}),W(j.current,{transition:`opacity ${q.DURATION}s cubic-bezier(${q.EASE.join(`,`)})`,opacity:`1`}),o&&t&&t>0&&D&&W(e,{borderRadius:`${Te}px`,overflow:`hidden`,...G(b)?{transform:`scale(${X()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`,transformOrigin:`top`}:{transform:`scale(${X()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`,transformOrigin:`left`},transitionProperty:`transform, border-radius`,transitionDuration:`${q.DURATION}s`,transitionTimingFunction:`cubic-bezier(${q.EASE.join(`,`)})`},!0)}function Re(){!A||!B.current||(B.current.classList.remove(De),P.current=!1,se(!1),fe.current=new Date)}function ze(e){if(!A||!B.current)return;B.current.classList.remove(De),P.current=!1,se(!1),fe.current=new Date;let t=be(B.current,b);if(!e||!Fe(e.target,!1)||!t||Number.isNaN(t)||de.current===null)return;let n=fe.current.getTime()-de.current.getTime(),r=I.current-(G(b)?e.pageY:e.pageX),o=Math.abs(r)/n;if(o>.05&&(le(!0),setTimeout(()=>{le(!1)},200)),a){ve({draggedDistance:r*(b===`bottom`||b===`right`?1:-1),closeDrawer:Z,velocity:o,dismissible:u}),i?.(e,!0);return}if(b===`bottom`||b===`right`?r>0:r<0){Le(),i?.(e,!0);return}if(o>Se){Z(),i?.(e,!1);return}let s=Math.min(B.current.getBoundingClientRect().height??0,window.innerHeight),l=Math.min(B.current.getBoundingClientRect().width??0,window.innerWidth);if(Math.abs(t)>=(b===`left`||b===`right`?l:s)*c){Z(),i?.(e,!1);return}i?.(e,!0),Le()}k.useEffect(()=>(D&&(W(document.documentElement,{scrollBehavior:`auto`}),M.current=new Date),()=>{ye(document.documentElement,`scrollBehavior`)}),[D]);function Q(e){let t=e?(window.innerWidth-J)/window.innerWidth:1,n=e?-J:0;F.current&&window.clearTimeout(F.current),W(B.current,{transition:`transform ${q.DURATION}s cubic-bezier(${q.EASE.join(`,`)})`,transform:G(b)?`scale(${t}) translate3d(0, ${n}px, 0)`:`scale(${t}) translate3d(${n}px, 0, 0)`}),!e&&B.current&&(F.current=setTimeout(()=>{let e=be(B.current,b);W(B.current,{transition:`none`,transform:G(b)?`translate3d(0, ${e}px, 0)`:`translate3d(${e}px, 0, 0)`})},500))}function $(e,t){if(t<0)return;let n=(window.innerWidth-J)/window.innerWidth,r=n+t*(1-n),i=-J+t*J;W(B.current,{transform:G(b)?`scale(${r}) translate3d(0, ${i}px, 0)`:`scale(${r}) translate3d(${i}px, 0, 0)`,transition:`none`})}function Be(e,t){let n=G(b)?window.innerHeight:window.innerWidth,r=t?(n-J)/n:1,i=t?-J:0;t&&W(B.current,{transition:`transform ${q.DURATION}s cubic-bezier(${q.EASE.join(`,`)})`,transform:G(b)?`scale(${r}) translate3d(0, ${i}px, 0)`:`scale(${r}) translate3d(${i}px, 0, 0)`})}return k.useEffect(()=>{g||window.requestAnimationFrame(()=>{document.body.style.pointerEvents=`auto`})},[g]),k.createElement(C,{defaultOpen:x,onOpenChange:e=>{!u&&!e||(e?ie(!0):Z(!0),re(e))},open:D},k.createElement(ae.Provider,{value:{activeSnapPoint:ge,snapPoints:a,setActiveSnapPoint:_e,drawerRef:B,overlayRef:j,onOpenChange:t,onPress:Pe,onRelease:ze,onDrag:Ie,dismissible:u,shouldAnimate:R,handleOnly:d,isOpen:D,isDragging:A,shouldFade:ke,closeDrawer:Z,onNestedDrag:$,onNestedOpenChange:Q,onNestedRelease:Be,keyboardIsOpen:L,modal:g,snapPointsOffset:K,activeSnapPointIndex:U,direction:b,shouldScaleBackground:o,setBackgroundColorOnScale:s,noBodyStyles:y,container:te,autoFocus:ne}},n))}var Pe=k.forwardRef(function({...e},t){let{overlayRef:n,snapPoints:r,onRelease:i,shouldFade:a,isOpen:o,modal:s,shouldAnimate:c}=A(),l=_e(t,n),u=r&&r.length>0;if(!s)return null;let d=k.useCallback(e=>i(e),[i]);return k.createElement(E,{onMouseUp:d,ref:l,"data-vaul-overlay":``,"data-vaul-snap-points":o&&u?`true`:`false`,"data-vaul-snap-points-overlay":o&&a?`true`:`false`,"data-vaul-animate":c?.current?`true`:`false`,...e})});Pe.displayName=`Drawer.Overlay`;var Fe=k.forwardRef(function({onPointerDownOutside:e,style:t,onOpenAutoFocus:n,...r},i){let{drawerRef:a,onPress:o,onRelease:s,onDrag:c,keyboardIsOpen:l,snapPointsOffset:u,activeSnapPointIndex:d,modal:f,isOpen:p,direction:m,snapPoints:h,container:g,handleOnly:_,shouldAnimate:v,autoFocus:y}=A(),[b,S]=k.useState(!1),ee=_e(i,a),C=k.useRef(null),w=k.useRef(null),T=k.useRef(!1),E=h&&h.length>0;Me();let te=(e,t,n=0)=>{if(T.current)return!0;let r=Math.abs(e.y),i=Math.abs(e.x),a=i>r,o=[`bottom`,`right`].includes(t)?1:-1;if(t===`left`||t===`right`){if(!(e.x*o<0)&&i>=0&&i<=n)return a}else if(!(e.y*o<0)&&r>=0&&r<=n)return!a;return T.current=!0,!0};k.useEffect(()=>{E&&window.requestAnimationFrame(()=>{S(!0)})},[]);function ne(e){C.current=null,T.current=!1,s(e)}return k.createElement(x,{"data-vaul-drawer-direction":m,"data-vaul-drawer":``,"data-vaul-delayed-snap-points":b?`true`:`false`,"data-vaul-snap-points":p&&E?`true`:`false`,"data-vaul-custom-container":g?`true`:`false`,"data-vaul-animate":v?.current?`true`:`false`,...r,ref:ee,style:u&&u.length>0?{"--snap-point-height":`${u[d??0]}px`,...t}:t,onPointerDown:e=>{_||(r.onPointerDown==null||r.onPointerDown.call(r,e),C.current={x:e.pageX,y:e.pageY},o(e))},onOpenAutoFocus:e=>{n?.(e),y||e.preventDefault()},onPointerDownOutside:t=>{if(e?.(t),!f||t.defaultPrevented){t.preventDefault();return}l.current&&=!1},onFocusOutside:e=>{if(!f){e.preventDefault();return}},onPointerMove:e=>{if(w.current=e,_||(r.onPointerMove==null||r.onPointerMove.call(r,e),!C.current))return;let t=e.pageY-C.current.y,n=e.pageX-C.current.x,i=e.pointerType===`touch`?10:2;te({x:n,y:t},m,i)?c(e):(Math.abs(n)>i||Math.abs(t)>i)&&(C.current=null)},onPointerUp:e=>{r.onPointerUp==null||r.onPointerUp.call(r,e),C.current=null,T.current=!1,s(e)},onPointerOut:e=>{r.onPointerOut==null||r.onPointerOut.call(r,e),ne(w.current)},onContextMenu:e=>{r.onContextMenu==null||r.onContextMenu.call(r,e),w.current&&ne(w.current)}})});Fe.displayName=`Drawer.Content`;var Ie=250,Z=120,Le=k.forwardRef(function({preventCycle:e=!1,children:t,...n},r){let{closeDrawer:i,isDragging:a,snapPoints:o,activeSnapPoint:s,setActiveSnapPoint:c,dismissible:l,handleOnly:u,isOpen:d,onPress:f,onDrag:p}=A(),m=k.useRef(null),h=k.useRef(!1);function g(){if(h.current){y();return}window.setTimeout(()=>{_()},Z)}function _(){if(a||e||h.current){y();return}if(y(),!o||o.length===0){l||i();return}if(s===o[o.length-1]&&l){i();return}let t=o.findIndex(e=>e===s);if(t===-1)return;let n=o[t+1];c(n)}function v(){m.current=window.setTimeout(()=>{h.current=!0},Ie)}function y(){m.current&&window.clearTimeout(m.current),h.current=!1}return k.createElement(`div`,{onClick:g,onPointerCancel:y,onPointerDown:e=>{u&&f(e),v()},onPointerMove:e=>{u&&p(e)},ref:r,"data-vaul-drawer-visible":d?`true`:`false`,"data-vaul-handle":``,"aria-hidden":`true`,...n},k.createElement(`span`,{"data-vaul-handle-hitarea":``,"aria-hidden":`true`},t))});Le.displayName=`Drawer.Handle`;function Re({onDrag:e,onOpenChange:t,open:n,...r}){let{onNestedDrag:i,onNestedOpenChange:a,onNestedRelease:o}=A();if(!i)throw Error(`Drawer.NestedRoot must be placed in another drawer`);return k.createElement(X,{nested:!0,open:n,onClose:()=>{a(!1)},onDrag:(t,n)=>{i(t,n),e?.(t,n)},onOpenChange:e=>{e&&a(e),t?.(e)},onRelease:o,...r})}function ze(e){let t=A(),{container:n=t.container,...r}=e;return k.createElement(T,{container:n,...r})}var Q={Root:X,NestedRoot:Re,Content:Fe,Overlay:Pe,Trigger:b,Portal:ze,Handle:Le,Close:te,Title:ee,Description:S},$=r(),Be=[{labelKey:`nav.dashboard`,path:`/`,icon:_},{labelKey:`nav.fanpilot`,path:`/fanpilot`,icon:p},{labelKey:`nav.sel`,path:`/sel`,icon:g},{labelKey:`nav.fru`,path:`/fru`,icon:m}],Ve=[{labelKey:`nav.modules`,path:`/modules`,icon:h},{labelKey:`nav.settings`,path:`/settings`,icon:f}];function He({open:e,onClose:t}){let{t:r}=n(),{servers:i,contextServerId:c,setContextServer:u}=a(),d=s();async function p(e){u(e);try{await l(`/api/dashboard/context`,{server_id:e})}catch{}}return(0,$.jsx)(Q.Root,{open:e,onOpenChange:e=>!e&&t(),direction:`left`,children:(0,$.jsxs)(Q.Portal,{children:[(0,$.jsx)(Q.Overlay,{className:`fixed inset-0 z-40 bg-black/40`}),(0,$.jsxs)(Q.Content,{id:`mobile-nav-drawer`,className:`fixed bottom-0 left-0 top-0 z-50 flex w-[280px] flex-col border-r border-border bg-sidebar outline-none`,children:[(0,$.jsx)(Q.Title,{className:`sr-only`,children:r(`nav.openMenu`)}),(0,$.jsx)(`div`,{className:`border-b border-border px-4 py-3`,children:(0,$.jsxs)(`div`,{className:`flex items-center gap-2.5`,children:[(0,$.jsx)(`div`,{className:`flex h-8 w-8 items-center justify-center rounded-lg bg-foreground text-sm font-bold text-background`,children:`ID`}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`text-sm font-semibold`,children:`IPMIDeck`}),(0,$.jsx)(`span`,{className:`ml-1 text-xs text-muted-foreground`,children:`v2`})]})]})}),(0,$.jsxs)(`div`,{className:`border-b border-border p-3`,children:[(0,$.jsx)(`p`,{className:`px-1 pb-2 text-[11px] font-medium uppercase tracking-wider text-muted-foreground`,children:r(`nav.selectServer`)}),(0,$.jsxs)(`div`,{className:`space-y-1`,children:[i.length===0&&(0,$.jsx)(`p`,{className:`px-1 py-1 text-xs text-muted-foreground`,children:r(`sidebar.noServersConfigured`)}),i.map(e=>(0,$.jsxs)(`button`,{onClick:()=>p(e.id),className:v(`flex min-h-11 w-full items-center gap-2.5 rounded-md px-3 py-2 text-left transition-colors hover:bg-muted`,e.id===c&&`bg-muted`),children:[(0,$.jsx)(`div`,{className:`h-2 w-2 shrink-0 rounded-full`,style:{backgroundColor:e.is_online?`var(--color-success)`:`var(--color-danger)`}}),(0,$.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,$.jsx)(`p`,{className:`truncate text-xs font-medium`,children:e.name}),(0,$.jsx)(`p`,{className:`font-mono text-[11px] text-muted-foreground`,children:e.host})]})]},e.id))]})]}),(0,$.jsxs)(`nav`,{className:`flex-1 overflow-y-auto p-2`,children:[(0,$.jsx)(`p`,{className:`px-2 pb-2 pt-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground`,children:r(`sidebar.platform`)}),(0,$.jsx)(`div`,{className:`flex flex-col gap-0.5`,children:Be.map(e=>(0,$.jsxs)(o,{to:e.path,end:e.path===`/`,onClick:t,className:({isActive:e})=>v(`flex min-h-11 items-center gap-2.5 rounded-md px-3 py-2 text-[13px] font-medium text-muted-foreground transition-colors`,e?`bg-muted text-foreground`:`hover:bg-muted hover:text-foreground`),children:[(0,$.jsx)(e.icon,{className:`h-4 w-4`}),r(e.labelKey)]},e.path))}),(0,$.jsx)(`p`,{className:`px-2 pb-2 pt-4 text-[11px] font-medium uppercase tracking-wider text-muted-foreground`,children:r(`sidebar.system`)}),(0,$.jsx)(`div`,{className:`flex flex-col gap-0.5`,children:Ve.map(e=>(0,$.jsxs)(o,{to:e.path,onClick:t,className:({isActive:e})=>v(`flex min-h-11 items-center gap-2.5 rounded-md px-3 py-2 text-[13px] font-medium text-muted-foreground transition-colors`,e?`bg-muted text-foreground`:`hover:bg-muted hover:text-foreground`),children:[(0,$.jsx)(e.icon,{className:`h-4 w-4`}),r(e.labelKey)]},e.path))})]}),(0,$.jsx)(`div`,{className:`border-t border-border p-2`,children:(0,$.jsxs)(`button`,{onClick:()=>{t(),d(`/settings`)},className:`flex min-h-11 w-full items-center gap-2 rounded-md px-3 py-2 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground`,children:[(0,$.jsx)(f,{className:`h-3.5 w-3.5`}),r(`sidebar.manageServers`)]})})]})]})})}function Ue({status:e}){let{t}=n(),r=e===`connected`?O:e===`connecting`?w:y,i=e===`connected`?`bg-success/10 text-success`:e===`connecting`?`bg-warning/10 text-warning`:`bg-danger/10 text-danger`,a=t(e===`connected`?`header.live`:e===`connecting`?`header.connecting`:`header.offline`);return(0,$.jsxs)(`div`,{className:v(`flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-medium`,i),children:[(0,$.jsx)(r,{className:v(`h-3 w-3 shrink-0`,e===`connecting`&&`animate-spin`),"aria-hidden":`true`}),(0,$.jsx)(`span`,{children:a})]})}function We({title:e,children:t}){let{t:r}=n(),o=d(e=>e.wsStatus),c=a(e=>e.servers.find(t=>t.id===e.contextServerId)),[l,f]=(0,k.useState)(!1),[p,m]=(0,k.useState)(!1),h=(0,k.useRef)(null);(0,k.useEffect)(()=>{if(!p)return;function e(e){h.current&&!h.current.contains(e.target)&&m(!1)}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[p]);let g=s(),_=u(e=>e.authEnabled),v=u(e=>e.authenticated);async function y(){try{await i(`/api/auth/logout`)}catch{}u.setState({authenticated:!1}),g(`/login`,{replace:!0})}return(0,$.jsxs)(`header`,{className:`flex h-[52px] items-center justify-between border-b border-border bg-card px-4 sm:px-6 shrink-0`,children:[(0,$.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2 text-[13px]`,children:[(0,$.jsx)(`button`,{onClick:()=>f(!0),"aria-label":r(`nav.openMenu`),"aria-expanded":l,"aria-controls":`mobile-nav-drawer`,className:`md:hidden -ml-1 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-muted`,children:(0,$.jsx)(re,{className:`h-5 w-5`,"aria-hidden":`true`})}),c&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`span`,{className:`hidden truncate text-muted-foreground sm:inline`,children:c.name}),(0,$.jsx)(`span`,{className:`hidden text-muted-foreground sm:inline`,children:`/`})]}),(0,$.jsx)(`span`,{className:`truncate font-medium`,children:e}),(0,$.jsx)(Ue,{status:o})]}),(0,$.jsx)(He,{open:l,onClose:()=>f(!1)}),(0,$.jsxs)(`div`,{className:`flex items-center gap-2`,children:[t&&(0,$.jsx)(`div`,{className:`hidden items-center gap-2 sm:flex`,children:t}),t&&(0,$.jsxs)(`div`,{className:`relative sm:hidden`,ref:h,children:[(0,$.jsx)(`button`,{onClick:()=>m(e=>!e),"aria-label":r(`nav.moreActions`),"aria-expanded":p,className:`inline-flex min-h-11 min-w-11 items-center justify-center rounded-md hover:bg-muted`,children:(0,$.jsx)(ne,{className:`h-5 w-5`,"aria-hidden":`true`})}),p&&(0,$.jsx)(`div`,{onClick:()=>m(!1),className:`absolute right-0 z-50 mt-1 flex w-56 flex-col gap-2 rounded-md border border-border bg-popover p-3 text-popover-foreground shadow-lg`,children:t})]}),_&&v&&(0,$.jsxs)(`button`,{onClick:y,"aria-label":r(`header.logoutAria`),className:`inline-flex min-h-11 min-w-11 items-center justify-center gap-1 rounded-md border border-border px-2.5 text-xs font-medium hover:bg-muted sm:min-h-9 sm:min-w-0 sm:py-1`,children:[(0,$.jsx)(D,{className:`h-3.5 w-3.5`}),(0,$.jsx)(`span`,{className:`hidden sm:inline`,children:r(`header.logout`)})]})]})]})}function Ge({icon:e,title:t,description:n,action:r,className:i}){let a=e;return(0,$.jsxs)(`div`,{className:v(`flex flex-col items-center justify-center py-24 text-center`,i),children:[(0,$.jsx)(`div`,{className:`mb-4 flex h-16 w-16 items-center justify-center rounded-xl bg-muted`,children:(0,$.jsx)(a,{className:`h-6 w-6 text-muted-foreground`})}),(0,$.jsx)(`h2`,{className:`text-lg font-semibold`,children:t}),n&&(0,$.jsx)(`p`,{className:`mt-1 max-w-sm text-sm text-muted-foreground`,children:n}),r&&(0,$.jsx)(`button`,{onClick:r.onClick,className:`mt-4 inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90 transition-colors`,children:r.label})]})}export{O as i,We as n,Q as r,Ge as t}; \ No newline at end of file diff --git a/backend/static/assets/FRUPage-C090S3Sn.js b/backend/static/assets/FRUPage-B2eK3zkd.js similarity index 98% rename from backend/static/assets/FRUPage-C090S3Sn.js rename to backend/static/assets/FRUPage-B2eK3zkd.js index 238bd80..14fe61a 100644 --- a/backend/static/assets/FRUPage-C090S3Sn.js +++ b/backend/static/assets/FRUPage-B2eK3zkd.js @@ -1 +1 @@ -import{I as e,M as t,S as n,_ as r,a as i,c as a,i as o,n as s,x as c}from"./auth-store-CVoL-wZN.js";import{n as l,t as u}from"./EmptyState-12OPdy7G.js";import{t as d}from"./info-CT6rLGjT.js";import{t as f}from"./refresh-cw-Cp5njxos.js";import{t as p}from"./server-off-DVPGiws7.js";import{B as m,I as h,O as g,S as _,x as v}from"./index-l2esYWfi.js";var y=s(`circuit-board`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M11 9h4a2 2 0 0 0 2-2V3`,key:`1ve2rv`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}],[`path`,{d:`M7 21v-4a2 2 0 0 1 2-2h4`,key:`1fwkro`}],[`circle`,{cx:`15`,cy:`15`,r:`2`,key:`3i40o0`}]]),b=s(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),x=e(t(),1),S=r();function C(e,t){let n=e.replace(/^FRU Device Description\s*:?\s*/i,``).trim();if(/builtin fru device/i.test(n))return t(`fru.section.systemBoard`);let r=n.match(/^PS(\d+)/i);if(r)return t(`fru.section.powerSupply`,{n:r[1]});let i=n.match(/^BP(\d+)/i);return i?t(`fru.section.backplane`,{n:i[1]}):/^PERC/i.test(n)||/storage cntlr/i.test(n)?t(`fru.section.storageController`):/^NDC/i.test(n)?t(`fru.section.networkDaughterCard`):n||e}function w(e,t){for(let n of t){let t=e.find(e=>e.field.toLowerCase()===n.toLowerCase());if(t?.value)return t.value}}function T(){let{t:e,i18n:t}=n(),r=a(e=>e.contextServerId),s=v(e=>r?e.readings[r]:void 0),[T,E]=(0,x.useState)(null),[D,O]=(0,x.useState)(!1),k=_(),A=async()=>{if(r)try{E(await o(`/api/modules/fru/${r}`))}catch{}},j=async()=>{if(r){O(!0);try{await i(`/api/modules/fru/${r}/refresh`),await A(),m.success(e(`fru.refreshed`))}catch{m.error(e(`fru.refreshFailed`))}finally{O(!1)}}};(0,x.useEffect)(()=>{A()},[r]);let M=t=>{navigator.clipboard.writeText(t),m.success(e(`fru.copied`))},N=T?.sections||{},P=Object.keys(N).length>0,F=(0,x.useMemo)(()=>s?Object.entries(s).filter(([e,t])=>t?.type===`temperature`&&/^cpu\b/i.test(e)).length:0,[s]),I=(0,x.useMemo)(()=>{let e=[];for(let[t,n]of Object.entries(N))/\bPS\d|power\s*sup|^bp\d|backplane|perc|storage cntlr|\bndc\b|\bdrive\b|network/i.test(t)||e.push(...n);if(e.length===0)return null;let t=w(e,[`Product Name`,`Board Product`]),n=w(e,[`Product Manufacturer`,`Board Mfg`]),r=w(e,[`Product Serial`,`Chassis Serial`,`Board Serial`]),i=w(e,[`Product Asset Tag`,`Chassis Asset Tag`]);return!t&&!n&&!r?null:{model:t,vendor:n,serviceTag:r,assetTag:i}},[N]);return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(l,{title:e(`nav.fru`),children:(0,S.jsxs)(`button`,{onClick:j,disabled:D||!k,title:k?void 0:e(`header.backendDisconnected`),className:`flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,children:[(0,S.jsx)(f,{className:`h-3 w-3 ${D?`animate-spin`:``}`}),` `,e(`fru.refresh`)]})}),(0,S.jsx)(`div`,{className:`flex-1 overflow-auto p-6`,children:P?(0,S.jsxs)(`div`,{className:`mx-auto max-w-6xl space-y-6`,children:[I&&(I.model||I.vendor||I.serviceTag)&&(0,S.jsx)(`div`,{className:`rounded-lg border border-border bg-card p-5 shadow-sm`,children:(0,S.jsxs)(`div`,{className:`flex items-start gap-4`,children:[(0,S.jsx)(`div`,{className:`flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary/10`,children:(0,S.jsx)(g,{className:`h-6 w-6 text-primary`})}),(0,S.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,S.jsx)(`h2`,{className:`text-xl font-bold leading-tight text-foreground`,children:I.model||e(`fru.unknownSystem`)}),(0,S.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:I.vendor}),(0,S.jsxs)(`div`,{className:`mt-4 flex flex-wrap gap-x-8 gap-y-3 text-xs`,children:[I.serviceTag&&(0,S.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,S.jsx)(`span`,{className:`uppercase tracking-wide text-[10px] font-medium text-muted-foreground`,children:e(`fru.serviceTag`)}),(0,S.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,S.jsx)(`span`,{className:`font-mono text-sm font-semibold text-foreground`,children:I.serviceTag}),(0,S.jsx)(`button`,{onClick:()=>M(I.serviceTag),"aria-label":e(`fru.copyField`,{field:e(`fru.serviceTag`)}),title:e(`fru.copyField`,{field:e(`fru.serviceTag`)}),className:`inline-flex min-h-11 min-w-11 items-center justify-center rounded hover:bg-muted`,children:(0,S.jsx)(b,{className:`h-3 w-3 text-muted-foreground`,"aria-hidden":`true`})})]})]}),I.assetTag&&(0,S.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,S.jsx)(`span`,{className:`uppercase tracking-wide text-[10px] font-medium text-muted-foreground`,children:e(`fru.assetTag`)}),(0,S.jsx)(`span`,{className:`font-mono text-sm font-semibold text-foreground`,children:I.assetTag})]}),(0,S.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,S.jsxs)(`span`,{className:`flex items-center gap-1 uppercase tracking-wide text-[10px] font-medium text-muted-foreground`,children:[(0,S.jsx)(h,{className:`h-3 w-3`}),e(`fru.cpusDetected`)]}),(0,S.jsx)(`span`,{className:`text-sm font-semibold text-foreground`,children:k&&F>0?F:`—`})]})]})]})]})}),(0,S.jsxs)(`div`,{className:`flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground`,children:[(0,S.jsx)(d,{className:`mt-0.5 h-3.5 w-3.5 shrink-0`}),(0,S.jsx)(`p`,{children:e(`fru.capabilityNote`)})]}),(0,S.jsx)(`div`,{className:`grid gap-4 md:grid-cols-2 lg:grid-cols-3`,children:Object.entries(N).map(([t,n])=>(0,S.jsxs)(`div`,{className:`overflow-hidden rounded-lg border border-border bg-card shadow-sm transition-shadow hover:shadow-md`,children:[(0,S.jsx)(`div`,{className:`border-b border-border bg-muted px-4 py-2.5`,children:(0,S.jsx)(`h3`,{className:`text-xs font-semibold uppercase tracking-wider text-foreground`,children:C(t,e)})}),(0,S.jsx)(`div`,{className:`divide-y divide-border`,children:n.map((t,n)=>(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-4 px-4 py-2`,children:[(0,S.jsx)(`span`,{className:`text-[11px] text-muted-foreground truncate`,children:t.field}),(0,S.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,S.jsx)(`span`,{className:`font-mono text-xs font-semibold text-foreground truncate max-w-[180px]`,children:t.value}),(t.field.toLowerCase().includes(`serial`)||t.field.toLowerCase().includes(`part`))&&(0,S.jsx)(`button`,{onClick:()=>M(t.value),"aria-label":e(`fru.copyField`,{field:t.field}),title:e(`fru.copyField`,{field:t.field}),className:`inline-flex min-h-11 min-w-11 items-center justify-center rounded hover:bg-muted`,children:(0,S.jsx)(b,{className:`h-3 w-3 text-muted-foreground`,"aria-hidden":`true`})})]})]},n))})]},t))}),T?.fetched_at&&(0,S.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:e(`fru.lastUpdated`,{value:new Date(T.fetched_at).toLocaleString(c(t.resolvedLanguage))})})]}):r?(0,S.jsx)(u,{icon:y,title:e(`fru.noDataTitle`),description:e(`fru.noDataDescription`)}):(0,S.jsx)(u,{icon:p,title:e(`fru.noServerTitle`),description:e(`fru.noServerDescription`)})})]})}export{T as default}; \ No newline at end of file +import{I as e,M as t,S as n,_ as r,a as i,c as a,i as o,n as s,x as c}from"./auth-store-CVoL-wZN.js";import{n as l,t as u}from"./EmptyState-D9mKpvu9.js";import{t as d}from"./info-CT6rLGjT.js";import{t as f}from"./refresh-cw-Cp5njxos.js";import{t as p}from"./server-off-DVPGiws7.js";import{B as m,I as h,O as g,S as _,x as v}from"./index-DXNHFWmw.js";var y=s(`circuit-board`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M11 9h4a2 2 0 0 0 2-2V3`,key:`1ve2rv`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}],[`path`,{d:`M7 21v-4a2 2 0 0 1 2-2h4`,key:`1fwkro`}],[`circle`,{cx:`15`,cy:`15`,r:`2`,key:`3i40o0`}]]),b=s(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),x=e(t(),1),S=r();function C(e,t){let n=e.replace(/^FRU Device Description\s*:?\s*/i,``).trim();if(/builtin fru device/i.test(n))return t(`fru.section.systemBoard`);let r=n.match(/^PS(\d+)/i);if(r)return t(`fru.section.powerSupply`,{n:r[1]});let i=n.match(/^BP(\d+)/i);return i?t(`fru.section.backplane`,{n:i[1]}):/^PERC/i.test(n)||/storage cntlr/i.test(n)?t(`fru.section.storageController`):/^NDC/i.test(n)?t(`fru.section.networkDaughterCard`):n||e}function w(e,t){for(let n of t){let t=e.find(e=>e.field.toLowerCase()===n.toLowerCase());if(t?.value)return t.value}}function T(){let{t:e,i18n:t}=n(),r=a(e=>e.contextServerId),s=v(e=>r?e.readings[r]:void 0),[T,E]=(0,x.useState)(null),[D,O]=(0,x.useState)(!1),k=_(),A=async()=>{if(r)try{E(await o(`/api/modules/fru/${r}`))}catch{}},j=async()=>{if(r){O(!0);try{await i(`/api/modules/fru/${r}/refresh`),await A(),m.success(e(`fru.refreshed`))}catch{m.error(e(`fru.refreshFailed`))}finally{O(!1)}}};(0,x.useEffect)(()=>{A()},[r]);let M=t=>{navigator.clipboard.writeText(t),m.success(e(`fru.copied`))},N=T?.sections||{},P=Object.keys(N).length>0,F=(0,x.useMemo)(()=>s?Object.entries(s).filter(([e,t])=>t?.type===`temperature`&&/^cpu\b/i.test(e)).length:0,[s]),I=(0,x.useMemo)(()=>{let e=[];for(let[t,n]of Object.entries(N))/\bPS\d|power\s*sup|^bp\d|backplane|perc|storage cntlr|\bndc\b|\bdrive\b|network/i.test(t)||e.push(...n);if(e.length===0)return null;let t=w(e,[`Product Name`,`Board Product`]),n=w(e,[`Product Manufacturer`,`Board Mfg`]),r=w(e,[`Product Serial`,`Chassis Serial`,`Board Serial`]),i=w(e,[`Product Asset Tag`,`Chassis Asset Tag`]);return!t&&!n&&!r?null:{model:t,vendor:n,serviceTag:r,assetTag:i}},[N]);return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(l,{title:e(`nav.fru`),children:(0,S.jsxs)(`button`,{onClick:j,disabled:D||!k,title:k?void 0:e(`header.backendDisconnected`),className:`flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,children:[(0,S.jsx)(f,{className:`h-3 w-3 ${D?`animate-spin`:``}`}),` `,e(`fru.refresh`)]})}),(0,S.jsx)(`div`,{className:`flex-1 overflow-auto p-6`,children:P?(0,S.jsxs)(`div`,{className:`mx-auto max-w-6xl space-y-6`,children:[I&&(I.model||I.vendor||I.serviceTag)&&(0,S.jsx)(`div`,{className:`rounded-lg border border-border bg-card p-5 shadow-sm`,children:(0,S.jsxs)(`div`,{className:`flex items-start gap-4`,children:[(0,S.jsx)(`div`,{className:`flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary/10`,children:(0,S.jsx)(g,{className:`h-6 w-6 text-primary`})}),(0,S.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,S.jsx)(`h2`,{className:`text-xl font-bold leading-tight text-foreground`,children:I.model||e(`fru.unknownSystem`)}),(0,S.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:I.vendor}),(0,S.jsxs)(`div`,{className:`mt-4 flex flex-wrap gap-x-8 gap-y-3 text-xs`,children:[I.serviceTag&&(0,S.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,S.jsx)(`span`,{className:`uppercase tracking-wide text-[10px] font-medium text-muted-foreground`,children:e(`fru.serviceTag`)}),(0,S.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,S.jsx)(`span`,{className:`font-mono text-sm font-semibold text-foreground`,children:I.serviceTag}),(0,S.jsx)(`button`,{onClick:()=>M(I.serviceTag),"aria-label":e(`fru.copyField`,{field:e(`fru.serviceTag`)}),title:e(`fru.copyField`,{field:e(`fru.serviceTag`)}),className:`inline-flex min-h-11 min-w-11 items-center justify-center rounded hover:bg-muted`,children:(0,S.jsx)(b,{className:`h-3 w-3 text-muted-foreground`,"aria-hidden":`true`})})]})]}),I.assetTag&&(0,S.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,S.jsx)(`span`,{className:`uppercase tracking-wide text-[10px] font-medium text-muted-foreground`,children:e(`fru.assetTag`)}),(0,S.jsx)(`span`,{className:`font-mono text-sm font-semibold text-foreground`,children:I.assetTag})]}),(0,S.jsxs)(`div`,{className:`flex flex-col gap-0.5`,children:[(0,S.jsxs)(`span`,{className:`flex items-center gap-1 uppercase tracking-wide text-[10px] font-medium text-muted-foreground`,children:[(0,S.jsx)(h,{className:`h-3 w-3`}),e(`fru.cpusDetected`)]}),(0,S.jsx)(`span`,{className:`text-sm font-semibold text-foreground`,children:k&&F>0?F:`—`})]})]})]})]})}),(0,S.jsxs)(`div`,{className:`flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3 text-xs text-muted-foreground`,children:[(0,S.jsx)(d,{className:`mt-0.5 h-3.5 w-3.5 shrink-0`}),(0,S.jsx)(`p`,{children:e(`fru.capabilityNote`)})]}),(0,S.jsx)(`div`,{className:`grid gap-4 md:grid-cols-2 lg:grid-cols-3`,children:Object.entries(N).map(([t,n])=>(0,S.jsxs)(`div`,{className:`overflow-hidden rounded-lg border border-border bg-card shadow-sm transition-shadow hover:shadow-md`,children:[(0,S.jsx)(`div`,{className:`border-b border-border bg-muted px-4 py-2.5`,children:(0,S.jsx)(`h3`,{className:`text-xs font-semibold uppercase tracking-wider text-foreground`,children:C(t,e)})}),(0,S.jsx)(`div`,{className:`divide-y divide-border`,children:n.map((t,n)=>(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-4 px-4 py-2`,children:[(0,S.jsx)(`span`,{className:`text-[11px] text-muted-foreground truncate`,children:t.field}),(0,S.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,S.jsx)(`span`,{className:`font-mono text-xs font-semibold text-foreground truncate max-w-[180px]`,children:t.value}),(t.field.toLowerCase().includes(`serial`)||t.field.toLowerCase().includes(`part`))&&(0,S.jsx)(`button`,{onClick:()=>M(t.value),"aria-label":e(`fru.copyField`,{field:t.field}),title:e(`fru.copyField`,{field:t.field}),className:`inline-flex min-h-11 min-w-11 items-center justify-center rounded hover:bg-muted`,children:(0,S.jsx)(b,{className:`h-3 w-3 text-muted-foreground`,"aria-hidden":`true`})})]})]},n))})]},t))}),T?.fetched_at&&(0,S.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:e(`fru.lastUpdated`,{value:new Date(T.fetched_at).toLocaleString(c(t.resolvedLanguage))})})]}):r?(0,S.jsx)(u,{icon:y,title:e(`fru.noDataTitle`),description:e(`fru.noDataDescription`)}):(0,S.jsx)(u,{icon:p,title:e(`fru.noServerTitle`),description:e(`fru.noServerDescription`)})})]})}export{T as default}; \ No newline at end of file diff --git a/backend/static/assets/FanPilotPage-DBREHJWC.js b/backend/static/assets/FanPilotPage-Dq68oKy3.js similarity index 99% rename from backend/static/assets/FanPilotPage-DBREHJWC.js rename to backend/static/assets/FanPilotPage-Dq68oKy3.js index 48e3b0a..d66b897 100644 --- a/backend/static/assets/FanPilotPage-DBREHJWC.js +++ b/backend/static/assets/FanPilotPage-Dq68oKy3.js @@ -1 +1 @@ -import{A as e,D as t,E as n,I as r,M as i,N as a,O as o,P as s,S as c,T as l,_ as u,a as d,c as f,i as p,j as m,k as h,n as g,o as _,r as v,w as y}from"./auth-store-CVoL-wZN.js";import{n as b,t as x}from"./EmptyState-12OPdy7G.js";import{n as S,t as C}from"./zap-DybMssoI.js";import{a as w,i as ee,o as T}from"./sensorUtils-dM-phdaF.js";import{t as te}from"./thermometer-C8NQPlaj.js";import{t as ne}from"./trash-2-3Xn7YcNf.js";import{t as E}from"./triangle-alert-O0Mm9jLA.js";import{B as D,E as O,F as k,I as re,L as ie,R as A,S as ae,x as oe}from"./index-l2esYWfi.js";var se=r(s(((e,t)=>{t.exports={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}}))()),j=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function ce(e){var t={type:`tag`,name:``,voidElement:!1,attrs:{},children:[]},n=e.match(/<\/?([^\s]+?)[/\s>]/);if(n&&(t.name=n[1],(se.default[n[1]]||e.charAt(e.length-2)===`/`)&&(t.voidElement=!0),t.name.startsWith(`!--`))){var r=e.indexOf(`-->`);return{type:`comment`,comment:r===-1?``:e.slice(4,r)}}for(var i=new RegExp(j),a=null;(a=i.exec(e))!==null;)if(a[0].trim())if(a[1]){var o=a[1].trim(),s=[o,``];o.indexOf(`=`)>-1&&(s=o.split(`=`)),t.attrs[s[0]]=s[1],i.lastIndex--}else a[2]&&(t.attrs[a[2]]=a[3].trim().substring(1,a[3].length-1));return t}var le=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,ue=/^\s*$/,M=Object.create(null);function de(e,t){switch(t.type){case`text`:return e+t.content;case`tag`:return e+=`<`+t.name+(t.attrs?function(e){var t=[];for(var n in e)t.push(n+`="`+e[n]+`"`);return t.length?` `+t.join(` `):``}(t.attrs):``)+(t.voidElement?`/>`:`>`),t.voidElement?e:e+t.children.reduce(de,``)+``;case`comment`:return e+``}}var fe={parse:function(e,t){t||={},t.components||=M;var n,r=[],i=[],a=-1,o=!1;if(e.indexOf(`<`)!==0){var s=e.indexOf(`<`);r.push({type:`text`,content:s===-1?e:e.substring(0,s)})}return e.replace(le,function(s,c){if(o){if(s!==``)return;o=!1}var l,u=s.charAt(1)!==`/`,d=s.startsWith(``);return{type:`comment`,comment:r===-1?``:e.slice(4,r)}}for(var i=new RegExp(j),a=null;(a=i.exec(e))!==null;)if(a[0].trim())if(a[1]){var o=a[1].trim(),s=[o,``];o.indexOf(`=`)>-1&&(s=o.split(`=`)),t.attrs[s[0]]=s[1],i.lastIndex--}else a[2]&&(t.attrs[a[2]]=a[3].trim().substring(1,a[3].length-1));return t}var le=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,ue=/^\s*$/,M=Object.create(null);function de(e,t){switch(t.type){case`text`:return e+t.content;case`tag`:return e+=`<`+t.name+(t.attrs?function(e){var t=[];for(var n in e)t.push(n+`="`+e[n]+`"`);return t.length?` `+t.join(` `):``}(t.attrs):``)+(t.voidElement?`/>`:`>`),t.voidElement?e:e+t.children.reduce(de,``)+``;case`comment`:return e+``}}var fe={parse:function(e,t){t||={},t.components||=M;var n,r=[],i=[],a=-1,o=!1;if(e.indexOf(`<`)!==0){var s=e.indexOf(`<`);r.push({type:`text`,content:s===-1?e:e.substring(0,s)})}return e.replace(le,function(s,c){if(o){if(s!==``)return;o=!1}var l,u=s.charAt(1)!==`/`,d=s.startsWith(`