release: nexus btpanel session fix and app-v2

This commit is contained in:
Codex Release Bot
2026-07-08 22:31:31 +08:00
commit 1933f0af6e
2457 changed files with 350105 additions and 0 deletions
+665
View File
@@ -0,0 +1,665 @@
"""
Nexus Agent — Runs on each managed server
Provides: health endpoint, heartbeat, command execution, config reload
"""
import sys
import json
import time
import asyncio
import logging
import platform
import subprocess
import secrets
import shlex
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
import ipaddress
import socket
import httpx
from heartbeat_policy import heartbeat_should_stop
from cpu_metrics import reset_watch_cpu_bootstrap, sample_cpu_percent, sample_watch_cpu_bundle
AGENT_VERSION = "2.1.1"
import psutil
from fastapi import FastAPI, HTTPException, Header, Request, Depends
from fastapi.responses import JSONResponse
# --- Configuration ---
CONFIG_PATH = Path(__file__).parent / "config.json"
def load_config():
if CONFIG_PATH.exists():
with open(CONFIG_PATH, encoding="utf-8") as f:
return json.load(f)
example = Path(__file__).parent / "config.example.json"
if example.exists():
with open(example, encoding="utf-8") as f:
return json.load(f)
return {}
config = load_config()
API_KEY = (config.get("api_key") or "").strip()
if not API_KEY:
print("FATAL: api_key must be set in config.json", file=sys.stderr)
sys.exit(1)
SERVER_ID = config.get("server_id", 0)
CENTRAL_URL = config.get("central", {}).get("url", "")
CENTRAL_API_KEY = config.get("central", {}).get("api_key", "")
HEARTBEAT_INTERVAL = config.get("heartbeat_interval", 60)
HEARTBEAT_KEEPALIVE_SEC = int(config.get("heartbeat_keepalive_sec", 30))
WATCH_INTERVAL_SEC = int(config.get("watch_interval_sec", 5))
# --- IP allowlist (only accept requests from trusted IPs) ---
# In config.json: "allowed_ips": ["1.2.3.4", "10.0.0.0/8"]
# Empty list = allow all (default for backward compat)
_ALLOWED_IPS: list[str] = config.get("allowed_ips") or []
# --- Logging ---
log_file = config.get("log_file", "/var/log/nexus-agent.log")
log_level = config.get("log_level", "INFO")
logging.basicConfig(
level=getattr(logging, log_level, logging.INFO),
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler(log_file, mode="a") if log_file != "stdout" else logging.StreamHandler(sys.stdout),
],
)
logger = logging.getLogger("nexus-agent")
_heartbeat_task: Optional[asyncio.Task] = None
# --- FastAPI App ---
from contextlib import asynccontextmanager
def _start_heartbeat_task() -> asyncio.Task:
"""Start heartbeat loop (idempotent if previous task still running)."""
global _heartbeat_task
if _heartbeat_task is not None and not _heartbeat_task.done():
return _heartbeat_task
_heartbeat_task = asyncio.create_task(send_heartbeat())
return _heartbeat_task
@asynccontextmanager
async def agent_lifespan(app):
"""Application lifecycle: start heartbeat task on startup, clean up on shutdown."""
global _heartbeat_task
logger.info(f"Nexus Agent starting (server_id={SERVER_ID})")
_start_heartbeat_task()
host = config.get("server", {}).get("host", "127.0.0.1")
port = config.get("server", {}).get("port", 8601)
logger.info(f"Agent listening on {host}:{port}")
yield
if _heartbeat_task is not None:
_heartbeat_task.cancel()
_heartbeat_task = None
logger.info("Nexus Agent stopped")
app = FastAPI(title="Nexus Agent", version=AGENT_VERSION, lifespan=agent_lifespan)
def _ip_allowed(client_ip: str) -> bool:
"""Check client IP against allowed_ips list (supports CIDR and exact match)."""
if not _ALLOWED_IPS:
return True # Empty = allow all
import ipaddress
try:
addr = ipaddress.ip_address(client_ip)
for entry in _ALLOWED_IPS:
entry = entry.strip()
try:
if "/" in entry:
if addr in ipaddress.ip_network(entry, strict=False):
return True
elif addr == ipaddress.ip_address(entry):
return True
except ValueError:
if client_ip == entry:
return True
except ValueError:
return client_ip in _ALLOWED_IPS
return False
async def verify_api_key(request: Request, x_api_key: str = Header(default="")):
"""Dependency: IP allowlist + API key verification."""
client_ip = request.client.host if request.client else ""
if not _ip_allowed(client_ip):
logger.warning("Blocked request from %s (not in allowed_ips)", client_ip)
raise HTTPException(status_code=403, detail="IP not allowed")
if not secrets.compare_digest(x_api_key or "", API_KEY):
raise HTTPException(status_code=401, detail="Invalid API key")
# --- Health Endpoint ---
@app.get("/health")
async def health_check(request: Request):
"""Health check endpoint — called by Nexus central server"""
client_ip = request.client.host if request.client else ""
if not _ip_allowed(client_ip):
raise HTTPException(status_code=403, detail="IP not allowed")
try:
cpu_percent = psutil.cpu_percent(interval=0.5)
memory = psutil.virtual_memory()
disk = psutil.disk_usage("/")
system_info = {
"hostname": platform.node(),
"platform": platform.platform(),
"python_version": platform.python_version(),
"cpu_usage": cpu_percent,
"mem_usage": memory.percent,
"disk_usage": round(disk.percent, 1),
"agent_time": datetime.now(timezone.utc).isoformat(),
**_hardware_specs_dict(),
}
return {"status": "healthy", "system_info": system_info}
except Exception as e:
return {"status": "degraded", "error": str(e)}
# --- Command Execution ---
_running_procs: dict[int, asyncio.subprocess.Process] = {}
def _proc_result(proc: asyncio.subprocess.Process, stdout: bytes, stderr: bytes) -> dict:
exit_code = proc.returncode if proc.returncode is not None else -1
return {
"status": "success" if exit_code == 0 else "failed",
"stdout": stdout.decode(errors="replace")[:10000],
"stderr": stderr.decode(errors="replace")[:10000],
"exit_code": exit_code,
"pid": proc.pid,
}
@app.post("/exec")
async def exec_command(payload: dict, _auth=Depends(verify_api_key)):
"""Execute a shell command — wait=true (default) blocks; wait=false returns pid for /exec/wait."""
command = payload.get("command", "")
timeout = payload.get("timeout", 30)
sudo = payload.get("sudo", False)
wait = payload.get("wait", True)
if not command:
raise HTTPException(status_code=400, detail="command is required")
logger.warning(
"Shell exec via subprocess_shell (server_id=%s, wait=%s, len=%s)",
SERVER_ID,
wait,
len(command),
)
if sudo:
command = f"sudo -n sh -c {shlex.quote(command)}"
proc = None
try:
proc = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
start_new_session=True,
)
_running_procs[proc.pid] = proc
if not wait:
return {
"status": "started",
"pid": proc.pid,
"stdout": "",
"stderr": "",
"exit_code": None,
}
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
_running_procs.pop(proc.pid, None)
return _proc_result(proc, stdout, stderr)
except asyncio.TimeoutError:
if proc:
proc.kill()
_running_procs.pop(proc.pid, None)
return {
"status": "timeout",
"stdout": "",
"stderr": f"Command timed out after {timeout}s",
"exit_code": -1,
"pid": proc.pid if proc else None,
}
except Exception as e:
return {"status": "error", "stdout": "", "stderr": str(e), "exit_code": -1}
@app.post("/exec/kill")
async def exec_kill(payload: dict, _auth=Depends(verify_api_key)):
"""Kill a process started with wait=false."""
pid = int(payload.get("pid", 0))
if pid <= 0:
raise HTTPException(status_code=400, detail="pid required")
proc = _running_procs.pop(pid, None)
if proc and proc.returncode is None:
proc.kill()
try:
await proc.wait()
except Exception:
pass
return {"status": "killed", "pid": pid}
raise HTTPException(
status_code=404,
detail="process not found or not started by this agent",
)
@app.post("/exec/wait")
async def exec_wait(payload: dict, _auth=Depends(verify_api_key)):
"""Wait for a process started with wait=false."""
pid = int(payload.get("pid", 0))
timeout = int(payload.get("timeout", 60))
if pid <= 0:
raise HTTPException(status_code=400, detail="pid required")
proc = _running_procs.get(pid)
if not proc:
raise HTTPException(status_code=404, detail="unknown pid (finished or invalid)")
try:
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
_running_procs.pop(pid, None)
return _proc_result(proc, stdout, stderr)
except asyncio.TimeoutError:
_running_procs.pop(pid, None)
if proc.returncode is None:
proc.kill()
try:
await proc.wait()
except Exception:
pass
return {
"status": "timeout",
"stdout": "",
"stderr": f"Command timed out after {timeout}s",
"exit_code": -1,
"pid": pid,
}
# --- Config Reload ---
@app.post("/config/reload")
async def reload_config(data: dict, _auth=Depends(verify_api_key)):
"""Nexus pushes new config — update config.json and reload"""
try:
now = datetime.now(timezone.utc).isoformat()
data["updated_at"] = now
data["updated_by"] = "nexus"
with open(CONFIG_PATH, "w", encoding="utf-8", newline="\n") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
global SERVER_ID, CENTRAL_URL, CENTRAL_API_KEY, config
config = data
SERVER_ID = config.get("server_id", SERVER_ID)
CENTRAL_URL = config.get("central", {}).get("url", CENTRAL_URL)
CENTRAL_API_KEY = config.get("central", {}).get("api_key", CENTRAL_API_KEY)
if CENTRAL_URL and SERVER_ID and CENTRAL_API_KEY:
_start_heartbeat_task()
logger.info("Heartbeat task (re)started after config reload")
return {"status": "ok", "message": "Config updated", "updated_at": now}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# --- Heartbeat to Central Server ---
_last_metrics = {}
_last_full_sync = 0
def _cpu_model() -> str:
proc = (platform.processor() or "").strip()
if proc:
return proc[:120]
try:
with open("/proc/cpuinfo", encoding="utf-8", errors="ignore") as f:
for line in f:
if line.lower().startswith("model name"):
return line.split(":", 1)[1].strip()[:120]
except Exception:
pass
return ""
def _hardware_specs_dict() -> dict:
"""Static-ish capacity fields for heartbeat / health."""
vm = psutil.virtual_memory()
du = psutil.disk_usage("/")
swap = psutil.swap_memory()
logical = psutil.cpu_count(logical=True) or 1
physical = psutil.cpu_count(logical=False) or logical
return {
"cpu_cores": logical,
"cpu_cores_physical": physical,
"cpu_model": _cpu_model(),
"memory_total_gb": round(vm.total / (1024**3), 1),
"memory_used_gb": round(vm.used / (1024**3), 1),
"memory_available_gb": round(vm.available / (1024**3), 1),
"disk_total_gb": round(du.total / (1024**3), 1),
"disk_used_gb": round(du.used / (1024**3), 1),
"disk_free_gb": round(du.free / (1024**3), 1),
"swap_total_gb": round(swap.total / (1024**3), 1),
"swap_used_gb": round(swap.used / (1024**3), 1),
"swap_pct": round(swap.percent, 1),
"uptime_seconds": int(time.time() - psutil.boot_time()),
"os_release": _os_release(),
}
def _os_release() -> str:
"""Read PRETTY_NAME from /etc/os-release (e.g. 'Ubuntu 24.04 LTS')."""
try:
path = Path("/etc/os-release")
if path.exists():
for line in path.read_text(encoding="utf-8").splitlines():
if line.startswith("PRETTY_NAME="):
return line.split("=", 1)[1].strip().strip('"')
except Exception:
pass
return ""
def _alert_thresholds() -> dict:
"""Align with Nexus center defaults (config.py / settings table). Override in config.json if needed."""
t = config.get("alert_thresholds") or {}
return {
"cpu": float(t.get("cpu", 80)),
"mem": float(t.get("mem", 80)),
"disk": float(t.get("disk", 80)),
}
def _metrics_over_threshold(cpu: float, mem: float, disk: float) -> bool:
"""Same rule as center _detect_alerts: strictly greater than threshold."""
th = _alert_thresholds()
return cpu > th["cpu"] or mem > th["mem"] or disk > th["disk"]
def _local_private_ip() -> Optional[str]:
"""Default-route local IPv4 (for Telegram 内网 display)."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.connect(("8.8.8.8", 80))
candidate = sock.getsockname()[0]
addr = ipaddress.ip_address(candidate)
if addr.is_private or addr.is_loopback:
return candidate
except OSError:
pass
return None
async def _fetch_public_ip() -> Optional[str]:
"""Outbound probe for real WAN IP (Telegram 外网 display)."""
endpoints = (
"https://api.ipify.org",
"https://ifconfig.me/ip",
)
try:
async with httpx.AsyncClient(timeout=4.0) as client:
for url in endpoints:
try:
resp = await client.get(url)
if resp.status_code != 200:
continue
candidate = (resp.text or "").strip()
addr = ipaddress.ip_address(candidate)
if not (addr.is_private or addr.is_loopback or addr.is_reserved):
return candidate
except (ValueError, httpx.HTTPError):
continue
except httpx.HTTPError:
pass
return None
_cached_public_ip: Optional[str] = None
_cached_public_ip: Optional[str] = None
_watch_active = False
_watch_interval_sec = WATCH_INTERVAL_SEC
_last_send_ts = 0.0
def _apply_watch_mode_from_response(body: Optional[dict]) -> None:
"""Switch between 60s idle heartbeat and 5s watch mode per central pin state."""
global _watch_active, _watch_interval_sec
prev_active = _watch_active
if not isinstance(body, dict):
return
active = body.get("watch_active")
if active is not None:
_watch_active = bool(active)
interval = body.get("watch_interval_sec")
if isinstance(interval, (int, float)) and int(interval) > 0:
_watch_interval_sec = int(interval)
if _watch_active and not prev_active:
reset_watch_cpu_bootstrap()
def _collect_load_avg() -> list[float]:
try:
return [float(x) for x in psutil.getloadavg()]
except (AttributeError, OSError, NotImplementedError):
return [0.0, 0.0, 0.0]
def _build_system_info(*, watch_mode: bool) -> dict:
if watch_mode:
cpu, per_cpu = sample_watch_cpu_bundle()
else:
cpu = sample_cpu_percent(watch_mode=False)
per_cpu = None
vm = psutil.virtual_memory()
du = psutil.disk_usage("/")
system_info = {
"cpu_usage": cpu,
"mem_usage": vm.percent,
"disk_usage": round(du.percent, 1),
"hostname": platform.node(),
"platform": platform.platform(),
"agent_time": datetime.now(timezone.utc).isoformat(),
**_hardware_specs_dict(),
}
if watch_mode:
system_info["watch_mode"] = True
system_info["load_avg"] = _collect_load_avg()
system_info["per_cpu_pct"] = per_cpu
try:
net = psutil.net_io_counters()
disk_io = psutil.disk_io_counters()
if net:
system_info["net_io"] = {
"bytes_sent": int(net.bytes_sent),
"bytes_recv": int(net.bytes_recv),
}
if disk_io:
system_info["disk_io"] = {
"read_bytes": int(disk_io.read_bytes),
"write_bytes": int(disk_io.write_bytes),
}
except Exception:
logger.debug("Failed to collect net/disk IO for heartbeat", exc_info=True)
private_ip = _local_private_ip()
if private_ip:
system_info["private_ip"] = private_ip
return system_info
async def _enrich_system_info_network(system_info: dict, *, force_sync: bool) -> None:
global _cached_public_ip
if force_sync:
fetched = await _fetch_public_ip()
if fetched:
_cached_public_ip = fetched
if _cached_public_ip:
system_info["public_ip"] = _cached_public_ip
async def _post_heartbeat(system_info: dict) -> tuple[int, Optional[dict]]:
payload = {
"server_id": SERVER_ID,
"is_online": True,
"agent_version": AGENT_VERSION,
"system_info": system_info,
}
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(
f"{CENTRAL_URL}/api/agent/heartbeat",
json=payload,
headers={"X-API-Key": CENTRAL_API_KEY},
)
body = resp.json() if resp.status_code == 200 else None
return resp.status_code, body
async def send_heartbeat():
"""Send heartbeat to Nexus central — idle: smart 60s; watch: 5s full metrics when pinned."""
global _last_metrics, _last_full_sync, _cached_public_ip, _last_send_ts
_consecutive_failures = 0
while True:
sleep_sec = _watch_interval_sec if _watch_active else HEARTBEAT_INTERVAL
try:
if not CENTRAL_URL or not SERVER_ID:
await asyncio.sleep(HEARTBEAT_INTERVAL)
continue
if _watch_active:
system_info = _build_system_info(watch_mode=True)
await _enrich_system_info_network(system_info, force_sync=False)
status_code, body = await _post_heartbeat(system_info)
if heartbeat_should_stop(status_code, body):
if status_code == 401:
logger.error(
"Heartbeat rejected by central (401): invalid API key or "
"server_id=%s not configured with per-server agent_api_key. "
"Stopping heartbeat — reconfigure agent key via admin UI.",
SERVER_ID,
)
else:
reason = (body or {}).get("reason", "unknown")
logger.error(
"Heartbeat discarded by central: %s. "
"This agent may be misconfigured (wrong server_id=%s). "
"Stopping heartbeat to prevent noise.",
reason,
SERVER_ID,
)
return
if status_code == 200:
_consecutive_failures = 0
_last_send_ts = time.time()
_apply_watch_mode_from_response(body)
logger.debug("Watch-mode heartbeat sent")
else:
_consecutive_failures += 1
logger.warning("Watch-mode heartbeat failed: %s", status_code)
sleep_sec = _watch_interval_sec if _watch_active else HEARTBEAT_INTERVAL
else:
cpu = sample_cpu_percent(watch_mode=False)
vm = psutil.virtual_memory()
du = psutil.disk_usage("/")
mem = vm.percent
disk = du.percent
now_ts = time.time()
changed = (
abs(cpu - _last_metrics.get("cpu", 0)) >= 10 or
abs(mem - _last_metrics.get("memory", 0)) >= 10 or
abs(disk - _last_metrics.get("disk", 0)) >= 10
)
force_sync = (now_ts - _last_full_sync) > 600
over_threshold = _metrics_over_threshold(cpu, mem, disk)
keepalive_due = (now_ts - _last_send_ts) >= HEARTBEAT_KEEPALIVE_SEC
if changed or force_sync or over_threshold or keepalive_due:
_last_metrics = {"cpu": cpu, "memory": mem, "disk": disk}
if force_sync:
_last_full_sync = now_ts
system_info = _build_system_info(watch_mode=False)
await _enrich_system_info_network(system_info, force_sync=force_sync)
status_code, body = await _post_heartbeat(system_info)
if heartbeat_should_stop(status_code, body):
if status_code == 401:
logger.error(
"Heartbeat rejected by central (401): invalid API key or "
"server_id=%s not configured with per-server agent_api_key. "
"Stopping heartbeat — reconfigure agent key via admin UI.",
SERVER_ID,
)
else:
reason = (body or {}).get("reason", "unknown")
logger.error(
"Heartbeat discarded by central: %s. "
"This agent may be misconfigured (wrong server_id=%s). "
"Stopping heartbeat to prevent noise.",
reason,
SERVER_ID,
)
return
if status_code == 200:
_consecutive_failures = 0
_last_send_ts = now_ts
_apply_watch_mode_from_response(body)
if _watch_active:
logger.info("Central enabled watch mode — switching to %ss interval", _watch_interval_sec)
else:
logger.debug("Heartbeat sent")
else:
_consecutive_failures += 1
logger.warning("Heartbeat failed: %s", status_code)
else:
logger.debug("Metrics unchanged, skip heartbeat")
sleep_sec = _watch_interval_sec if _watch_active else HEARTBEAT_INTERVAL
except Exception as e:
_consecutive_failures += 1
logger.warning("Heartbeat error: %s", e)
if _consecutive_failures > 0:
backoff = min(HEARTBEAT_INTERVAL * (2 ** min(_consecutive_failures, 5)), 300)
logger.info("Backing off %ss after %s consecutive failures", backoff, _consecutive_failures)
await asyncio.sleep(backoff)
else:
await asyncio.sleep(sleep_sec)
if __name__ == "__main__":
host = config.get("server", {}).get("host", "127.0.0.1")
port = config.get("server", {}).get("port", 8601)
import uvicorn
uvicorn.run(app, host=host, port=port)
+98
View File
@@ -0,0 +1,98 @@
#!/bin/bash
# ================================================================
# Nexus Shell Agent — 心跳 + 健康接口
# 依赖: bash, curl
# ================================================================
HEARTBEAT_INTERVAL="${HEARTBEAT_INTERVAL:-60}"
LOG_FILE="${LOG_FILE:-/var/log/nexus-agent.log}"
CENTRAL_URL="${CENTRAL_URL:-}"
CENTRAL_API_KEY="${CENTRAL_API_KEY:-}"
SERVER_ID="${SERVER_ID:-0}"
AGENT_PORT="${AGENT_PORT:-8601}"
log() {
local ts=$(date '+%Y-%m-%d %H:%M:%S')
echo "${ts} [$1] $2" >> "$LOG_FILE" 2>/dev/null
echo "${ts} [$1] $2"
}
check_deps() {
if ! command -v curl >/dev/null 2>&1; then
log "ERROR" "缺少 curl,请安装: apt install curl"
exit 1
fi
}
get_system_info() {
local hn=$(hostname 2>/dev/null)
local load=$(awk '{print $1}' /proc/loadavg 2>/dev/null)
local mem_total=$(awk '/Mem:/{print $2}' /proc/meminfo 2>/dev/null)
local mem_avail=$(awk '/MemAvailable:/{print $2}' /proc/meminfo 2>/dev/null)
local disk_pct=$(df / 2>/dev/null | awk 'NR==2{print $5}' | tr -d '%')
echo "{\"hostname\":\"${hn}\",\"load\":\"${load}\",\"mem_kb\":${mem_total:-0},\"mem_avail_kb\":${mem_avail:-0},\"disk_pct\":${disk_pct:-0}}"
}
heartbeat_loop() {
while true; do
local info=$(get_system_info)
curl -s -o /dev/null -X POST -H "Content-Type: application/json" \
-H "X-API-Key: ${CENTRAL_API_KEY}" \
-d "{\"server_id\":${SERVER_ID},\"is_online\":true,\"system_info\":${info}}" \
"${CENTRAL_URL}/api/agent/heartbeat" 2>/dev/null
if [ $? -eq 0 ]; then
log "INFO" "心跳 OK"
else
log "WARN" "心跳失败: ${CENTRAL_URL}"
fi
sleep "$HEARTBEAT_INTERVAL"
done
}
health_server() {
while true; do
if command -v nc >/dev/null 2>&1; then
printf "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{\"status\":\"healthy\",\"agent\":\"nexus-shell\"}" \
| nc -l -p "${AGENT_PORT}" -q 1 2>/dev/null
else
exec 3<>/dev/tcp/0.0.0.0/${AGENT_PORT} 2>/dev/null && {
printf "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{\"status\":\"healthy\"}" >&3
exec 3>&-
} || sleep 1
fi
done
}
cleanup() {
log "INFO" "正在停止..."
for pid in "${PIDS[@]}"; do kill "$pid" 2>/dev/null; done
}
trap cleanup SIGINT SIGTERM
main() {
echo "=========================================="
echo " Nexus Shell Agent — 心跳上报"
echo "=========================================="
check_deps
log "INFO" "中央服务器: ${CENTRAL_URL}"
log "INFO" "服务器ID: ${SERVER_ID}"
log "INFO" "心跳间隔: ${HEARTBEAT_INTERVAL}"
heartbeat_loop &
PIDS+=($!)
log "INFO" "心跳服务已启动"
health_server &
PIDS+=($!)
log "INFO" "健康接口: http://0.0.0.0:${AGENT_PORT}/health"
log "INFO" "Agent 已启动,按 Ctrl+C 停止"
wait
}
case "${1:-}" in
stop) systemctl stop nexus-agent; echo "已停止" ;;
restart) systemctl restart nexus-agent; echo "已重启" ;;
status) systemctl status nexus-agent --no-pager 2>/dev/null || echo "未运行" ;;
log|logs) journalctl -u nexus-agent -f --no-pager ;;
*) main ;;
esac
+22
View File
@@ -0,0 +1,22 @@
{
"server_id": 0,
"central": {
"url": "https://YOUR_NEXUS_DOMAIN",
"api_key": "YOUR_API_KEY"
},
"api_key": "YOUR_API_KEY",
"heartbeat_interval": 60,
"heartbeat_keepalive_sec": 30,
"watch_interval_sec": 5,
"alert_thresholds": {
"cpu": 80,
"mem": 80,
"disk": 80
},
"log_file": "/var/log/nexus-agent.log",
"log_level": "INFO",
"server": {
"host": "127.0.0.1",
"port": 8601
}
}
+47
View File
@@ -0,0 +1,47 @@
"""CPU sampling helpers for Nexus Agent heartbeats."""
from __future__ import annotations
import psutil
_primed = False
_watch_bootstrapped = False
def reset_watch_cpu_bootstrap() -> None:
"""Call when entering watch mode so first sample uses a 1s blocking window."""
global _watch_bootstrapped
_watch_bootstrapped = False
def _prime_nonblocking() -> None:
global _primed
if not _primed:
psutil.cpu_percent(interval=None)
_primed = True
def sample_cpu_percent(*, watch_mode: bool) -> float:
"""Idle heartbeats: CPU since last sample (up to ~60s). Watch: ~5s window after bootstrap."""
_prime_nonblocking()
if watch_mode:
global _watch_bootstrapped
if not _watch_bootstrapped:
_watch_bootstrapped = True
return round(float(psutil.cpu_percent(interval=1.0)), 1)
return round(float(psutil.cpu_percent(interval=None)), 1)
return round(float(psutil.cpu_percent(interval=None)), 1)
def sample_watch_cpu_bundle() -> tuple[float, list[float]]:
"""Watch-mode overall + per-core; interval=None measures since prior heartbeat (~5s)."""
_prime_nonblocking()
global _watch_bootstrapped
if not _watch_bootstrapped:
_watch_bootstrapped = True
per = psutil.cpu_percent(interval=1.0, percpu=True)
else:
per = psutil.cpu_percent(interval=None, percpu=True)
per_list = [round(float(x), 1) for x in per]
overall = round(sum(per_list) / len(per_list), 1) if per_list else 0.0
return overall, per_list
+18
View File
@@ -0,0 +1,18 @@
"""Heartbeat loop stop conditions — shared by agent.py and unit tests."""
from __future__ import annotations
from typing import Any, Mapping, Optional
def heartbeat_should_stop(
status_code: int,
body: Optional[Mapping[str, Any]] = None,
) -> bool:
"""True when central rejected heartbeat permanently (do not retry)."""
if status_code == 401:
return True
if status_code == 200 and isinstance(body, Mapping):
if body.get("status") == "discarded":
return True
return False
+339
View File
@@ -0,0 +1,339 @@
#!/bin/bash
# ================================================================
# Nexus Agent Install Script
# Usage: curl -fsSL https://YOUR_NEXUS_DOMAIN/agent/install.sh | bash -s -- --url https://YOUR_NEXUS_DOMAIN --key API_KEY --id SERVER_ID
#
# Network model:
# - Heartbeat: child → central HTTPS (443 outbound), no inbound 8601 required
# - Scripts / push: central → child SSH (22)
# - Agent HTTP listens on 127.0.0.1 only (local); not exposed on public 8601
# ================================================================
set -e
CENTRAL_URL=""
WEB_URL=""
API_KEY=""
SERVER_ID=""
AGENT_PORT="8601"
INSTALL_DIR="/opt/nexus-agent"
# --- Python dependency versions (single source of truth) ---
FASTAPI_VER="0.115.6"
UVICORN_VER="0.34.0"
HTTPX_VER="0.28.1"
PSUTIL_VER="" # latest
MULTIPART_VER="0.0.19"
# --- Privilege detection ---
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
if command -v sudo >/dev/null 2>&1; then
# Test passwordless sudo (-n = non-interactive)
if sudo -n true 2>/dev/null; then
SUDO="sudo"
echo " Non-root user detected — using sudo for privileged operations."
else
echo "ERROR: sudo requires a password. Configure passwordless sudo first:"
echo " echo \"$(whoami) ALL=(ALL) NOPASSWD:ALL\" | sudo tee /etc/sudoers.d/nexus-agent"
exit 1
fi
else
echo "ERROR: Running as non-root and sudo is not available."
echo " Either run as root or install sudo."
exit 1
fi
fi
while [[ $# -gt 0 ]]; do
case $1 in
--url) CENTRAL_URL="$2"; shift 2 ;;
--web-url) WEB_URL="$2"; shift 2 ;;
--key) API_KEY="$2"; shift 2 ;;
--id) SERVER_ID="$2"; shift 2 ;;
--port) AGENT_PORT="$2"; shift 2 ;;
*) shift ;;
esac
done
[ -z "$CENTRAL_URL" ] && echo "ERROR: --url is required" && exit 1
[ -z "$SERVER_ID" ] && echo "ERROR: --id is required (server ID from Nexus dashboard)" && exit 1
[ -z "$API_KEY" ] && echo "ERROR: --key is required (API key from Nexus settings)" && exit 1
echo ""
echo "=========================================="
echo " Nexus Agent Install"
echo "=========================================="
echo " Server: $CENTRAL_URL"
echo " ID: $SERVER_ID"
echo " Port: ${AGENT_PORT} (localhost only)"
echo "=========================================="
echo ""
# 0. Stop existing service + kill stale processes on the port
echo "[0/7] Clean up old installation..."
$SUDO systemctl stop nexus-agent 2>/dev/null || true
# Kill any process occupying the agent port (stale process from manual start, etc.)
if command -v fuser >/dev/null 2>&1; then
$SUDO fuser -k "${AGENT_PORT}"/tcp 2>/dev/null || true
elif command -v ss >/dev/null 2>&1; then
PID=$(ss -tlnp 2>/dev/null | grep ":${AGENT_PORT}" | grep -oP 'pid=\K[0-9]+' | head -1)
[ -n "$PID" ] && kill "$PID" 2>/dev/null || true
elif command -v netstat >/dev/null 2>&1; then
PID=$(netstat -tlnp 2>/dev/null | grep ":${AGENT_PORT}" | awk '{print $7}' | cut -d'/' -f1 | head -1)
[ -n "$PID" ] && kill "$PID" 2>/dev/null || true
fi
echo " Old service stopped, port cleared"
# 1. Resolve Python 3.10+ (system → BT only if executable → auto-install system 3.12)
echo "[1/7] Resolve Python 3.10+..."
PYTHON=""
_python_version_ok() {
local ver="$1" major minor
[ -n "$ver" ] || return 1
major=${ver%%.*}
minor=${ver#*.}
[ "$major" -eq 3 ] && [ "${minor:-0}" -ge 10 ] 2>/dev/null
}
_try_python_candidate() {
local candidate="$1" ver
[ -n "$candidate" ] || return 1
if [ "${candidate#/}" != "$candidate" ]; then
[ -x "$candidate" ] || return 1
else
command -v "$candidate" >/dev/null 2>&1 || return 1
fi
ver=$("$candidate" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true)
if _python_version_ok "$ver"; then
PYTHON="$candidate"
return 0
fi
return 1
}
_bt_executable_python_candidates() {
local base p
for base in /www/server/panel/pyenv/bin; do
[ -d "$base" ] || continue
for p in "$base"/python3.12 "$base"/python3.11 "$base"/python3.10; do
[ -x "$p" ] && echo "$p"
done
if command -v find >/dev/null 2>&1; then
find "$base" -maxdepth 1 -type f -name 'python3.*' -executable 2>/dev/null | sort -Vr
else
for p in "$base"/python3.*; do
[ -x "$p" ] && echo "$p"
done
fi
done
for p in /www/server/python-manager/versions/*/bin/python3.* \
/www/server/python_manager/versions/*/bin/python3.*; do
[ -x "$p" ] && echo "$p"
done
}
_resolve_python_from() {
local candidate
for candidate in $1; do
if _try_python_candidate "$candidate"; then
return 0
fi
done
return 1
}
_install_system_python312() {
echo " No executable Python 3.10+ — installing system python3.12..."
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update -qq
if ! $SUDO apt-get install -y -qq python3.12 python3.12-venv python3.12-dev >/dev/null 2>&1; then
echo " Trying deadsnakes PPA (Ubuntu)..."
$SUDO apt-get install -y -qq software-properties-common >/dev/null 2>&1 || true
$SUDO add-apt-repository -y ppa:deadsnakes/ppa >/dev/null 2>&1 || true
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq python3.12 python3.12-venv python3.12-dev >/dev/null 2>&1 \
|| return 1
fi
return 0
fi
if command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y -q python3.12 python3.12-devel >/dev/null 2>&1 \
|| $SUDO dnf install -y -q python3.11 python3.11-devel >/dev/null 2>&1 \
|| $SUDO dnf install -y -q python3 python3-devel >/dev/null 2>&1 \
|| return 1
return 0
fi
if command -v yum >/dev/null 2>&1; then
$SUDO yum install -y -q python3.12 python3.12-devel >/dev/null 2>&1 \
|| $SUDO yum install -y -q python3 python3-devel >/dev/null 2>&1 \
|| return 1
return 0
fi
if command -v apk >/dev/null 2>&1; then
$SUDO apk add --quiet python3 py3-pip >/dev/null 2>&1 || return 1
return 0
fi
return 1
}
SYSTEM_CANDIDATES="python3.12 python3.11 python3.10 /usr/bin/python3.12 /usr/bin/python3.11 /usr/bin/python3.10"
BT_CANDIDATES=""
while IFS= read -r p; do
[ -n "$p" ] && BT_CANDIDATES="$BT_CANDIDATES $p"
done < <(_bt_executable_python_candidates | awk '!seen[$0]++')
if ! _resolve_python_from "$SYSTEM_CANDIDATES $BT_CANDIDATES"; then
if _install_system_python312; then
POST_INSTALL="python3.12 python3.11 python3.10 /usr/bin/python3.12 /usr/bin/python3.11 /usr/bin/python3.10 python3"
_resolve_python_from "$POST_INSTALL" || true
fi
fi
if [ -z "$PYTHON" ]; then
echo "ERROR: Python 3.10+ required. System install failed — install python3.12 manually and re-run."
exit 1
fi
echo " Using: $PYTHON ($("$PYTHON" --version 2>&1))"
# 2. Ensure rsync (required for push/sync)
echo "[2/7] Check rsync..."
if ! command -v rsync >/dev/null 2>&1; then
echo " rsync not found — installing..."
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update -qq && $SUDO apt-get install -y -qq rsync
elif command -v yum >/dev/null 2>&1; then
$SUDO yum install -y -q rsync
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y -q rsync
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --quiet rsync
else
echo "ERROR: Cannot detect package manager. Please install rsync manually."
exit 1
fi
echo " rsync installed OK"
else
echo " rsync already available"
fi
# 3. Create dir + venv
echo "[3/7] Create venv..."
VENV_DIR="${INSTALL_DIR}/.venv"
$SUDO mkdir -p "$INSTALL_DIR"
# If using sudo, give ownership to current user so venv/pip work without sudo
if [ -n "$SUDO" ]; then
$SUDO chown -R "$(id -u):$(id -g)" "$INSTALL_DIR"
fi
if [ ! -f "$VENV_DIR/bin/activate" ]; then
# Allow venv creation to fail (set -e won't abort) so we can install python3-venv
"$PYTHON" -m venv "$VENV_DIR" 2>/dev/null || true
# venv creation fails when python3-venv is missing — check if activate exists
if [ ! -f "$VENV_DIR/bin/activate" ]; then
echo " venv incomplete — installing python3-venv..."
$SUDO apt-get update -qq && $SUDO apt-get install -y -qq "python$( "$PYTHON" -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}")' )-venv" 2>/dev/null \
|| $SUDO apt-get install -y -qq python3-venv 2>/dev/null \
|| { echo "ERROR: Cannot install python3-venv. Run: $SUDO apt install python3-venv"; exit 1; }
rm -rf "$VENV_DIR"
"$PYTHON" -m venv "$VENV_DIR"
if [ ! -f "$VENV_DIR/bin/activate" ]; then
echo "ERROR: venv creation failed even after installing python3-venv"
exit 1
fi
fi
fi
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
pip install -q --upgrade pip
PIP_DEPS="fastapi==${FASTAPI_VER} uvicorn==${UVICORN_VER} httpx==${HTTPX_VER} python-multipart==${MULTIPART_VER}"
[ -n "$PSUTIL_VER" ] && PIP_DEPS="${PIP_DEPS} psutil==${PSUTIL_VER}" || PIP_DEPS="${PIP_DEPS} psutil"
pip install -q $PIP_DEPS
# 4. Download agent.py + cpu_metrics.py + heartbeat_policy.py
echo "[4/7] Download agent files..."
[ -z "$WEB_URL" ] && WEB_URL="${CENTRAL_URL}"
curl -fsSL "${WEB_URL}/agent/agent.py" -o "$INSTALL_DIR/agent.py" || {
echo "ERROR: Cannot download from $WEB_URL/agent/agent.py"
exit 1
}
curl -fsSL "${WEB_URL}/agent/cpu_metrics.py" -o "$INSTALL_DIR/cpu_metrics.py" || {
echo "ERROR: Cannot download from $WEB_URL/agent/cpu_metrics.py"
exit 1
}
curl -fsSL "${WEB_URL}/agent/heartbeat_policy.py" -o "$INSTALL_DIR/heartbeat_policy.py" || {
echo "ERROR: Cannot download from $WEB_URL/agent/heartbeat_policy.py"
exit 1
}
# 5. Config
echo "[5/7] Write config..."
CENTRAL_HOST=$(echo "${CENTRAL_URL}" | sed 's~https\?://~~' | cut -d'/' -f1 | cut -d':' -f1)
cat > "$INSTALL_DIR/config.json" << EOF
{
"server_id": ${SERVER_ID},
"central": { "url": "${CENTRAL_URL}", "api_key": "${API_KEY}" },
"api_key": "${API_KEY}",
"heartbeat_interval": 60,
"allowed_ips": ["${CENTRAL_HOST}"],
"log_file": "/var/log/nexus-agent.log",
"log_level": "INFO",
"server": { "host": "127.0.0.1", "port": ${AGENT_PORT} }
}
EOF
echo " IP allowlist (Agent HTTP): ${CENTRAL_HOST}"
echo " Note: inbound TCP ${AGENT_PORT} is NOT required (heartbeat uses HTTPS outbound)."
# 6. systemd + start (bind localhost only)
echo "[6/7] Setup systemd + start..."
$SUDO tee /etc/systemd/system/nexus-agent.service > /dev/null << EOF
[Unit]
Description=Nexus Agent
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
Environment=PATH=${VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=${VENV_DIR}/bin/python -m uvicorn agent:app --host 127.0.0.1 --port ${AGENT_PORT} --log-level info
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
$SUDO systemctl daemon-reload
$SUDO systemctl enable nexus-agent
$SUDO systemctl start nexus-agent
sleep 2
STATUS="running"
$SUDO systemctl is-active --quiet nexus-agent || STATUS="FAILED"
# 7. Verify
echo "[7/7] Verify..."
HOSTNAME=$(hostname 2>/dev/null || echo "unknown")
IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "unknown")
echo ""
echo "=========================================="
echo " Done! Status: ${STATUS}"
echo "=========================================="
echo ""
echo " Heartbeat: ${CENTRAL_URL}/api/agent/heartbeat (HTTPS outbound)"
echo " Agent HTTP: 127.0.0.1:${AGENT_PORT} (local only, no public firewall rule needed)"
echo " Scripts: via SSH port 22 from Nexus center"
echo " Server ID: ${SERVER_ID}"
echo ""
echo " Commands:"
echo " systemctl status nexus-agent"
echo " journalctl -u nexus-agent -f"
echo " systemctl restart nexus-agent"
echo ""
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
# ================================================================
# Nexus Agent Uninstall Script
# Usage: curl -fsSL https://YOUR_NEXUS_DOMAIN/agent/uninstall.sh | bash
#
# Stops and removes the nexus-agent systemd service and /opt/nexus-agent directory.
# Does NOT remove rsync (may be used by other tools).
# ================================================================
set -e
INSTALL_DIR="/opt/nexus-agent"
SERVICE_NAME="nexus-agent"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
echo ""
echo "=========================================="
echo " Nexus Agent Uninstall"
echo "=========================================="
echo ""
# --- Privilege detection ---
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
SUDO="sudo"
else
echo "ERROR: Must run as root or have passwordless sudo."
exit 1
fi
fi
# 1. Stop service if running
echo "[1/5] Stop service..."
$SUDO systemctl stop "$SERVICE_NAME" 2>/dev/null || true
# Kill any stale agent processes (new: uvicorn agent:app; legacy: python.*agent\.py or nexus_agent)
pkill -f "uvicorn agent:app" 2>/dev/null || true
pkill -f "python.*agent\.py" 2>/dev/null || true
pkill -f "nexus_agent" 2>/dev/null || true
# Kill any process on the agent port
if command -v fuser >/dev/null 2>&1; then
$SUDO fuser -k 8601/tcp 2>/dev/null || true
fi
echo " Service stopped, stale processes killed"
# 2. Disable and remove systemd unit
echo "[2/5] Remove systemd unit..."
if [ -f "$SERVICE_FILE" ]; then
$SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true
$SUDO rm -f "$SERVICE_FILE"
$SUDO systemctl daemon-reload
echo " Unit removed"
else
echo " No systemd unit found"
fi
# 3. Remove install directory (standard + legacy paths)
echo "[3/6] Remove install directory..."
for dir in "$INSTALL_DIR" "/opt/multisync-agent"; do
if [ -d "$dir" ]; then
$SUDO rm -rf "$dir"
echo " ${dir} removed"
fi
done
if [ ! -d "$INSTALL_DIR" ] && [ ! -d "/opt/multisync-agent" ]; then
echo " No install directory found"
fi
# 4. Remove log file
echo "[4/6] Remove log file..."
removed_log=0
for logfile in "/var/log/nexus-agent.log" "/var/log/multisync-agent.log"; do
if [ -f "$logfile" ]; then
$SUDO rm -f "$logfile"
echo " ${logfile} removed"
removed_log=1
fi
done
if [ "$removed_log" -eq 0 ]; then
echo " No log file found"
fi
# 5. Clean up crontab entries (legacy agents may use cron for auto-restart)
echo "[5/6] Clean up crontab..."
crontab_cleaned=0
for user_crondir in /var/spool/cron/crontabs /var/spool/cron; do
if [ -d "$user_crondir" ]; then
for crontab_file in "$user_crondir"/*; do
if [ -f "$crontab_file" ] && grep -q "nexus.agent\|multisync.agent\|nexus-agent\|multisync-agent" "$crontab_file" 2>/dev/null; then
$SUDO sed -i '/nexus.agent\|multisync.agent\|nexus-agent\|multisync-agent/d' "$crontab_file"
echo " Cleaned crontab: $crontab_file"
crontab_cleaned=1
fi
done
fi
done
if [ "$crontab_cleaned" -eq 0 ]; then
echo " No crontab entries found"
fi
# 6. Clean up sudoers residual (left by install/upgrade _sudo_wrap)
echo "[6/6] Clean up sudoers..."
if [ -f "/etc/sudoers.d/nexus-agent" ]; then
$SUDO rm -f /etc/sudoers.d/nexus-agent
echo " /etc/sudoers.d/nexus-agent removed"
else
echo " No sudoers residual found"
fi
echo ""
echo "=========================================="
echo " Uninstall complete!"
echo "=========================================="
echo ""
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));export{c as n,o as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#070b16" />
<title>Nexus App V2</title>
<script type="module" crossorigin src="/app-v2/assets/index-8f2_PJZM.js"></script>
<link rel="modulepreload" crossorigin href="/app-v2/assets/rolldown-runtime-Bh1tDfsg.js">
<link rel="modulepreload" crossorigin href="/app-v2/assets/query-B5fiFlYp.js">
<link rel="modulepreload" crossorigin href="/app-v2/assets/react-DGbf-iXy.js">
<link rel="stylesheet" crossorigin href="/app-v2/assets/index-saKc1OPG.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+214
View File
@@ -0,0 +1,214 @@
// Nexus — Shared API auth module (JWT Bearer + auto-refresh via HttpOnly cookie)
// Include before page-specific scripts: <script src="/app/api.js"></script>
const API = window.location.origin + '/api';
function _getTokens() {
return {
access: localStorage.getItem('access_token') || '',
// Refresh token is in HttpOnly cookie — not accessible from JS
expires: parseInt(localStorage.getItem('token_expires') || '0'),
};
}
function _isTokenExpiring() {
// Refresh if token expires within 2 minutes
return Date.now() > _getTokens().expires - 120000;
}
async function _refreshTokens() {
try {
// Refresh token is auto-sent as HttpOnly cookie by browser (same-origin)
const res = await fetch(API + '/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
if (!res.ok) return false;
const data = await res.json();
if (!data.success) return false;
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('token_expires', String(Date.now() + (data.expires_in || 1800) * 1000));
return true;
} catch {
return false;
}
}
function apiHeaders() {
const { access } = _getTokens();
return { 'Authorization': 'Bearer ' + access };
}
function apiHeadersJSON() {
return { ...apiHeaders(), 'Content-Type': 'application/json' };
}
async function apiFetch(url, options = {}) {
// Auto-refresh if token is about to expire
if (_isTokenExpiring()) {
const refreshed = await _refreshTokens();
if (!refreshed) {
_logout();
return null;
}
}
// Add auth headers
options.headers = { ...apiHeaders(), ...(options.headers || {}) };
const res = await fetch(url, options);
// Record session activity on successful API calls
if (res.status !== 401) {
_recordActivity();
}
// If 401, try refresh once
if (res.status === 401) {
const refreshed = await _refreshTokens();
if (refreshed) {
options.headers = { ...apiHeaders(), ...(options.headers || {}) };
return fetch(url, options);
}
_logout();
return null;
}
return res;
}
function _logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('token_expires');
localStorage.removeItem('admin');
localStorage.removeItem('last_activity');
window.location.href = '/app/login.html';
}
function doLogout() {
// Notify backend (best-effort, don't block)
// Refresh token is in HttpOnly cookie — auto-sent by browser
const { access } = _getTokens();
if (access) {
try {
fetch(API + '/auth/logout', {
method: 'POST',
keepalive: true,
credentials: 'same-origin',
});
} catch {}
}
_logout();
}
// ── Session Inactivity Timeout ──
// Auto-logout after 8 hours of no API activity
const SESSION_MAX_AGE_MS = 8 * 60 * 60 * 1000; // 8 hours
function _checkSessionAge() {
const lastActivity = parseInt(localStorage.getItem('last_activity') || '0');
if (lastActivity && Date.now() - lastActivity > SESSION_MAX_AGE_MS) {
_logout();
return false;
}
return true;
}
function _recordActivity() {
localStorage.setItem('last_activity', String(Date.now()));
}
// Backward-compatible alias — try refresh cookie if no access token
const token = localStorage.getItem('access_token') || '';
if (!token) {
// No access token — try refresh via HttpOnly cookie (auto-sent by browser)
_refreshTokens().then(ok => {
if (!ok) _logout();
else _recordActivity();
});
} else if (!_checkSessionAge()) {
// Session expired — will redirect to login via _checkSessionAge → _logout
} else {
_recordActivity();
}
// ── Monaco Editor 预加载(后台,不阻塞页面) ──
(function preloadMonaco(){
if(window._monacoLoading||window._monacoReady)return;
window._monacoLoading=true;
const s=document.createElement('script');
const monacoVs='/app/vendor/monaco/vs';
s.src=monacoVs+'/loader.js';
s.onload=function(){
require.config({paths:{vs:monacoVs}});
require(['vs/editor/editor.main'],function(){
window.monaco=monaco;
window._monacoReady=true;
window._monacoLoading=false;
});
};
s.onerror=function(){
window._monacoLoading=false;
console.warn('Monaco vendor preload failed:', monacoVs);
};
document.head.appendChild(s);
})();
// ── Global Toast Notification System ──
// Call toast('success', '操作成功') or toast('error', '操作失败') from any page.
let _toastContainer = null;
let _toastId = 0;
function _ensureToastContainer() {
if (_toastContainer) return;
_toastContainer = document.createElement('div');
_toastContainer.id = 'toastContainer';
_toastContainer.className = 'fixed top-4 right-4 z-[9999] flex flex-col gap-2 pointer-events-none';
_toastContainer.style.maxWidth = '380px';
document.body.appendChild(_toastContainer);
}
function toast(type, message, duration) {
_ensureToastContainer();
duration = duration || 3000;
const id = ++_toastId;
const colors = {
success: 'bg-green-900/90 border-green-700/50 text-green-200',
error: 'bg-red-900/90 border-red-700/50 text-red-200',
warning: 'bg-yellow-900/90 border-yellow-700/50 text-yellow-200',
info: 'bg-blue-900/90 border-blue-700/50 text-blue-200',
};
const icons = { success: '✓', error: '✕', warning: '⚠', info: '' };
const el = document.createElement('div');
el.id = 'toast-' + id;
el.className = `pointer-events-auto flex items-center gap-2 px-4 py-3 rounded-xl border shadow-lg text-sm backdrop-blur-sm transition-all duration-300 opacity-0 translate-x-4 ${colors[type] || colors.info}`;
el.innerHTML = `<span class="text-base">${icons[type] || ''}</span><span class="flex-1">${_toastEsc(message)}</span><button onclick="dismissToast(${id})" class="text-current opacity-50 hover:opacity-100 text-xs ml-2">✕</button>`;
_toastContainer.appendChild(el);
// Animate in
requestAnimationFrame(() => {
el.classList.remove('opacity-0', 'translate-x-4');
el.classList.add('opacity-100', 'translate-x-0');
});
// Auto-dismiss
if (duration > 0) {
setTimeout(() => dismissToast(id), duration);
}
return id;
}
function dismissToast(id) {
const el = document.getElementById('toast-' + id);
if (!el) return;
el.classList.add('opacity-0', 'translate-x-4');
el.classList.remove('opacity-100', 'translate-x-0');
setTimeout(() => el.remove(), 300);
}
function _toastEsc(s) { if (!s) return ''; const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
+1
View File
@@ -0,0 +1 @@
import{_ as e,t}from"./VSelect-BvR-yyLS.js";import{n,t as r}from"./VRow-rmSa_q-C.js";import{$ as i,Ar as a,Er as o,Nr as s,Or as c,Tr as l,Xt as u,Yr as d,_i as f,b as p,g as m,h,in as g,jr as _,qr as v,ri as y,u as b,ui as x,v as S,xi as C,xr as w}from"./index-CDcgx9V8.js";import{t as T}from"./usePageActivateRefresh-CqHSpagO.js";import{t as E}from"./useSnackbar-u_XK4ZNm.js";import{n as D,t as O}from"./dataTable-esDY4cOk.js";import{n as k}from"./paginatedFetch-VYTwXwno.js";import{i as A,t as j}from"./VCard-pmpQR9O7.js";import{t as M}from"./VDataTableServer-BIMNqMvk.js";var N={class:`font-weight-medium`},P={class:`text-body-2`},F={class:`text-caption text-medium-emphasis`},I=s({name:`AlertsPage`,__name:`AlertsPage`,setup(s){let I=E(),L=x(!1),R=x([]),z=x(0),B=x(1),V=x(20),H=O,U=x(null),W=x(null),G=x([{label:`今日告警`,value:`0`,color:`blue`,icon:`mdi-alert`},{label:`活跃告警`,value:`0`,color:`orange`,icon:`mdi-bell-ring`},{label:`已恢复`,value:`0`,color:`green`,icon:`mdi-check-circle`},{label:`Top 服务器`,value:``,color:`red`,icon:`mdi-server`}]),K=[{label:`CPU`,value:`cpu`},{label:`内存`,value:`memory`},{label:`磁盘`,value:`disk`},{label:`连接`,value:`connection`}],q=[{label:`活跃`,value:`active`},{label:`已恢复`,value:`recovered`}],J=D([{title:`类型`,key:`alert_type`,width:100},{title:`服务器`,key:`server_name`},{title:`详情`,key:`value`},{title:`状态`,key:`is_recovery`,width:100},{title:`时间`,key:`created_at`,width:160}]);async function Y(){try{let e=await g.get(`/alert-history/stats`);G.value[0].value=String(e.today||0),G.value[1].value=String(e.active||0),G.value[2].value=String(e.recovered||0),G.value[3].value=e.top_server||``}catch{I(`加载统计失败`,`error`)}}function X(e){B.value=e,Q()}function Z(e){V.value=e,B.value=1,Q()}async function Q(e=!1){e||(L.value=!0);try{let e=await k(`/alert-history/`,{type:U.value||void 0,status:W.value||void 0},B.value,V.value);R.value=e.items,z.value=e.total,V.value===-1&&(B.value=1)}catch{R.value=[],z.value=0,I(`加载告警列表失败`,`error`)}finally{e||(L.value=!1)}}function $(e){return e===`cpu`||e===`CPU`?`error`:e===`memory`||e===`内存`?`warning`:e===`disk`||e===`磁盘`?`info`:`primary`}function ee(e){return e===`cpu`||e===`CPU`?`mdi-chip`:e===`memory`||e===`内存`?`mdi-memory`:e===`disk`||e===`磁盘`?`mdi-harddisk`:`mdi-lan-disconnect`}async function te(e=!1){await Promise.all([Y(),Q(e)])}return T(e=>te(e)),(s,g)=>(v(),o(e,{fluid:``,class:`pa-6`},{default:y(()=>[_(r,{class:`mb-4`},{default:y(()=>[(v(!0),c(w,null,d(G.value,e=>(v(),o(n,{cols:`12`,sm:`6`,lg:`3`,key:e.label},{default:y(()=>[_(b,{elevation:`0`,lines:`two`,rounded:`lg`,border:``},{default:y(()=>[_(h,null,{append:y(()=>[_(i,{color:e.color,size:`30`},{default:y(()=>[a(C(e.icon),1)]),_:2},1032,[`color`])]),default:y(()=>[_(m,{class:`text-body-small`},{default:y(()=>[a(C(e.label),1)]),_:2},1024),_(m,null,{default:y(()=>[a(C(e.value),1)]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:2},1024))),128))]),_:1}),_(j,{elevation:`0`,border:``,rounded:`lg`},{default:y(()=>[_(A,{class:`d-flex align-center`},{default:y(()=>[g[4]||=a(` 告警历史 `,-1),_(S),_(t,{modelValue:U.value,"onUpdate:modelValue":[g[0]||=e=>U.value=e,g[1]||=e=>{B.value=1,Q()}],items:K,"item-title":`label`,"item-value":`value`,label:`类型`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`140px`},clearable:``},null,8,[`modelValue`]),_(t,{modelValue:W.value,"onUpdate:modelValue":[g[2]||=e=>W.value=e,g[3]||=e=>{B.value=1,Q()}],items:q,"item-title":`label`,"item-value":`value`,label:`状态`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`140px`},class:`ml-3`,clearable:``},null,8,[`modelValue`])]),_:1}),_(M,{items:R.value,headers:f(J),"items-length":z.value,loading:L.value,page:B.value,"items-per-page":V.value,"items-per-page-options":f(H),hover:``,density:`comfortable`,"onUpdate:page":X,"onUpdate:itemsPerPage":Z},{"item.alert_type":y(({item:e})=>[_(p,{color:$(e.alert_type),size:`small`,variant:`tonal`,label:``},{prepend:y(()=>[_(i,{size:`12`},{default:y(()=>[a(C(ee(e.alert_type)),1)]),_:2},1024)]),default:y(()=>[a(` `+C(e.alert_type),1)]),_:2},1032,[`color`])]),"item.server_name":y(({item:e})=>[l(`span`,N,C(e.server_name),1)]),"item.value":y(({item:e})=>[l(`span`,P,C(e.value),1)]),"item.is_recovery":y(({item:e})=>[e.is_recovery?(v(),o(p,{key:0,color:`success`,size:`small`,variant:`tonal`,label:``},{default:y(()=>[...g[5]||=[a(`已恢复`,-1)]]),_:1})):(v(),o(p,{key:1,color:`error`,size:`small`,variant:`tonal`,label:``},{default:y(()=>[...g[6]||=[a(`告警中`,-1)]]),_:1}))]),"item.created_at":y(({item:e})=>[l(`span`,F,C(f(u)(e.created_at)),1)]),"no-data":y(()=>[...g[7]||=[l(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无数据`,-1)]]),_:1},8,[`items`,`headers`,`items-length`,`loading`,`page`,`items-per-page`,`items-per-page-options`])]),_:1})]),_:1}))}});export{I as default};
+1
View File
@@ -0,0 +1 @@
import{_ as e,t}from"./VSelect-3h-EI23Y.js";import{n,t as r}from"./VRow-qjxp2VJ5.js";import{$ as i,Ar as a,Er as o,Nr as s,Or as c,Tr as l,Xt as u,Yr as d,_i as f,b as p,g as m,h,in as g,jr as _,qr as v,ri as y,u as b,ui as x,v as S,xi as C,xr as w}from"./index-a0fi5f-0.js";import{t as T}from"./usePageActivateRefresh-BntaOABK.js";import{t as E}from"./useSnackbar-u_XK4ZNm.js";import{n as D,t as O}from"./dataTable-esDY4cOk.js";import{n as k}from"./paginatedFetch-CijkqaDG.js";import{i as A,t as j}from"./VCard-BwwttBcf.js";import{t as M}from"./VDataTableServer-BysXbZ8p.js";var N={class:`font-weight-medium`},P={class:`text-body-2`},F={class:`text-caption text-medium-emphasis`},I=s({name:`AlertsPage`,__name:`AlertsPage`,setup(s){let I=E(),L=x(!1),R=x([]),z=x(0),B=x(1),V=x(20),H=O,U=x(null),W=x(null),G=x([{label:`今日告警`,value:`0`,color:`blue`,icon:`mdi-alert`},{label:`活跃告警`,value:`0`,color:`orange`,icon:`mdi-bell-ring`},{label:`已恢复`,value:`0`,color:`green`,icon:`mdi-check-circle`},{label:`Top 服务器`,value:``,color:`red`,icon:`mdi-server`}]),K=[{label:`CPU`,value:`cpu`},{label:`内存`,value:`memory`},{label:`磁盘`,value:`disk`},{label:`连接`,value:`connection`}],q=[{label:`活跃`,value:`active`},{label:`已恢复`,value:`recovered`}],J=D([{title:`类型`,key:`alert_type`,width:100},{title:`服务器`,key:`server_name`},{title:`详情`,key:`value`},{title:`状态`,key:`is_recovery`,width:100},{title:`时间`,key:`created_at`,width:160}]);async function Y(){try{let e=await g.get(`/alert-history/stats`);G.value[0].value=String(e.today||0),G.value[1].value=String(e.active||0),G.value[2].value=String(e.recovered||0),G.value[3].value=e.top_server||``}catch{I(`加载统计失败`,`error`)}}function X(e){B.value=e,Q()}function Z(e){V.value=e,B.value=1,Q()}async function Q(e=!1){e||(L.value=!0);try{let e=await k(`/alert-history/`,{type:U.value||void 0,status:W.value||void 0},B.value,V.value);R.value=e.items,z.value=e.total,V.value===-1&&(B.value=1)}catch{R.value=[],z.value=0,I(`加载告警列表失败`,`error`)}finally{e||(L.value=!1)}}function $(e){return e===`cpu`||e===`CPU`?`error`:e===`memory`||e===`内存`?`warning`:e===`disk`||e===`磁盘`?`info`:`primary`}function ee(e){return e===`cpu`||e===`CPU`?`mdi-chip`:e===`memory`||e===`内存`?`mdi-memory`:e===`disk`||e===`磁盘`?`mdi-harddisk`:`mdi-lan-disconnect`}async function te(e=!1){await Promise.all([Y(),Q(e)])}return T(e=>te(e)),(s,g)=>(v(),o(e,{fluid:``,class:`pa-6`},{default:y(()=>[_(r,{class:`mb-4`},{default:y(()=>[(v(!0),c(w,null,d(G.value,e=>(v(),o(n,{cols:`12`,sm:`6`,lg:`3`,key:e.label},{default:y(()=>[_(b,{elevation:`0`,lines:`two`,rounded:`lg`,border:``},{default:y(()=>[_(h,null,{append:y(()=>[_(i,{color:e.color,size:`30`},{default:y(()=>[a(C(e.icon),1)]),_:2},1032,[`color`])]),default:y(()=>[_(m,{class:`text-body-small`},{default:y(()=>[a(C(e.label),1)]),_:2},1024),_(m,null,{default:y(()=>[a(C(e.value),1)]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:2},1024))),128))]),_:1}),_(j,{elevation:`0`,border:``,rounded:`lg`},{default:y(()=>[_(A,{class:`d-flex align-center`},{default:y(()=>[g[4]||=a(` 告警历史 `,-1),_(S),_(t,{modelValue:U.value,"onUpdate:modelValue":[g[0]||=e=>U.value=e,g[1]||=e=>{B.value=1,Q()}],items:K,"item-title":`label`,"item-value":`value`,label:`类型`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`140px`},clearable:``},null,8,[`modelValue`]),_(t,{modelValue:W.value,"onUpdate:modelValue":[g[2]||=e=>W.value=e,g[3]||=e=>{B.value=1,Q()}],items:q,"item-title":`label`,"item-value":`value`,label:`状态`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`140px`},class:`ml-3`,clearable:``},null,8,[`modelValue`])]),_:1}),_(M,{items:R.value,headers:f(J),"items-length":z.value,loading:L.value,page:B.value,"items-per-page":V.value,"items-per-page-options":f(H),hover:``,density:`comfortable`,"onUpdate:page":X,"onUpdate:itemsPerPage":Z},{"item.alert_type":y(({item:e})=>[_(p,{color:$(e.alert_type),size:`small`,variant:`tonal`,label:``},{prepend:y(()=>[_(i,{size:`12`},{default:y(()=>[a(C(ee(e.alert_type)),1)]),_:2},1024)]),default:y(()=>[a(` `+C(e.alert_type),1)]),_:2},1032,[`color`])]),"item.server_name":y(({item:e})=>[l(`span`,N,C(e.server_name),1)]),"item.value":y(({item:e})=>[l(`span`,P,C(e.value),1)]),"item.is_recovery":y(({item:e})=>[e.is_recovery?(v(),o(p,{key:0,color:`success`,size:`small`,variant:`tonal`,label:``},{default:y(()=>[...g[5]||=[a(`已恢复`,-1)]]),_:1})):(v(),o(p,{key:1,color:`error`,size:`small`,variant:`tonal`,label:``},{default:y(()=>[...g[6]||=[a(`告警中`,-1)]]),_:1}))]),"item.created_at":y(({item:e})=>[l(`span`,F,C(f(u)(e.created_at)),1)]),"no-data":y(()=>[...g[7]||=[l(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无数据`,-1)]]),_:1},8,[`items`,`headers`,`items-length`,`loading`,`page`,`items-per-page`,`items-per-page-options`])]),_:1})]),_:1}))}});export{I as default};
+1
View File
@@ -0,0 +1 @@
import{_ as e,t}from"./VSelect-3h-EI23Y.js";import{Ar as n,At as r,Dr as i,Er as a,L as o,Nr as s,Tr as c,Xt as l,_i as u,b as d,br as f,in as p,jr as m,qr as h,ri as g,ui as _,v,xi as y}from"./index-a0fi5f-0.js";import{t as b}from"./usePageActivateRefresh-BntaOABK.js";import{t as x}from"./useSnackbar-u_XK4ZNm.js";import{n as S}from"./dataTable-esDY4cOk.js";import{i as C,t as w}from"./VCard-BwwttBcf.js";import{t as T}from"./VTextField-GUQr1tUN.js";import{t as E}from"./VDataTableServer-BysXbZ8p.js";import{a as D,n as O,r as k,t as A}from"./auditNormalize-xrm2MK_4.js";var j=new Set([`batch_install_agent`,`batch_upgrade_agent`,`batch_uninstall_agent`,`batch_detect_path`,`batch_health_check`,`batch_update_category`,`batch_server_op`]);function M(e){return j.has(e)}function N(e){let t=(e||``).trim();if(!t.startsWith(`{`))return null;try{let e=JSON.parse(t);return typeof e.job_id!=`number`||!e.summary?null:e}catch{return null}}function P(e){let t=[e.summary],n=e.failures||[];if(n.length){let e=n.slice(0,3).map(e=>e.server_name).join(``);t.push(`失败: ${e}${n.length>3?``:``}`)}return t.join(` · `)}var F={class:`font-weight-medium`},I={class:`text-body-2`},L={class:`d-flex align-center ga-2 flex-wrap`},R={class:`text-body-2 text-medium-emphasis`,style:{"max-width":`360px`}},z={class:`text-caption text-medium-emphasis`},B={class:`text-caption text-medium-emphasis`},V=s({name:`AuditPage`,__name:`AuditPage`,setup(s){let j=x(),V=_(!1),H=_([]),U=_(0),W=_(1),G=_(null),K=_(``),q=_(``),J=O,Y=S([{title:`操作`,key:`action`,width:100},{title:`用户`,key:`admin_username`,width:100},{title:`目标`,key:`target`,width:140},{title:`详情`,key:`detail`},{title:`IP`,key:`ip_address`,width:120},{title:`时间`,key:`created_at`,width:160}]);async function X(e=!1){e||(V.value=!0);try{let e=(W.value-1)*50,t=await p.get(`/audit/`,{limit:50,offset:e,action:G.value||void 0,admin_username:K.value||void 0,date_from:q.value||void 0});H.value=A(t.items||[]),U.value=t.total||0}catch{H.value=[],e||j(`加载审计日志失败`,`error`)}finally{e||(V.value=!1)}}function Z(e){return e.startsWith(`create`)||e===`login`?`success`:e.startsWith(`update`)?`info`:e.startsWith(`delete`)||e===`logout`?`error`:e.includes(`execute`)||e.includes(`push`)||e.includes(`retry`)?`warning`:`primary`}function Q(e){return M(e.action)?N(e.detail):null}function $(e){let t=Q(e);return t?P(t):(e.detail||``).trim()||``}function ee(e){r(e.job_id,e.summary)}return b(e=>X(e)),(r,s)=>(h(),a(e,{fluid:``,class:`pa-6`},{default:g(()=>[m(w,{elevation:`0`,border:``,rounded:`lg`},{default:g(()=>[m(C,{class:`d-flex align-center`},{default:g(()=>[s[7]||=n(` 审计日志 `,-1),m(v),m(t,{modelValue:G.value,"onUpdate:modelValue":[s[0]||=e=>G.value=e,s[1]||=e=>{W.value=1,X()}],items:u(J),"item-title":`label`,"item-value":`value`,label:`操作类型`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`200px`},clearable:``},null,8,[`modelValue`,`items`]),m(T,{modelValue:K.value,"onUpdate:modelValue":[s[2]||=e=>K.value=e,s[3]||=e=>{W.value=1,X()}],"prepend-inner-icon":`mdi-account`,label:`用户`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`140px`},class:`ml-3`,clearable:``},null,8,[`modelValue`]),m(T,{modelValue:q.value,"onUpdate:modelValue":[s[4]||=e=>q.value=e,s[5]||=e=>{W.value=1,X()}],"prepend-inner-icon":`mdi-calendar`,label:`日期`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`160px`},class:`ml-3`,type:`date`,clearable:``},null,8,[`modelValue`])]),_:1}),m(E,{items:H.value,headers:u(Y),"items-length":U.value,loading:V.value,page:W.value,"items-per-page":50,hover:``,density:`compact`,"onUpdate:page":s[6]||=e=>{W.value=e,X()}},{"item.action":g(({item:e})=>[m(d,{color:Z(e.action),size:`small`,variant:`tonal`,label:``},{default:g(()=>[n(y(u(k)(e.action)),1)]),_:2},1032,[`color`])]),"item.admin_username":g(({item:e})=>[c(`span`,F,y(e.admin_username),1)]),"item.target":g(({item:e})=>[c(`span`,I,y(u(D)(e)),1)]),"item.detail":g(({item:e})=>[c(`div`,L,[c(`span`,R,y($(e)),1),Q(e)?(h(),a(o,{key:0,size:`x-small`,variant:`tonal`,color:`primary`,onClick:f(t=>ee(Q(e)),[`stop`])},{default:g(()=>[...s[8]||=[n(` 查看结果 `,-1)]]),_:1},8,[`onClick`])):i(``,!0)])]),"item.ip_address":g(({item:e})=>[c(`span`,z,y(e.ip_address||``),1)]),"item.created_at":g(({item:e})=>[c(`span`,B,y(u(l)(e.created_at)),1)]),"no-data":g(()=>[...s[9]||=[c(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无数据`,-1)]]),_:1},8,[`items`,`headers`,`items-length`,`loading`,`page`])]),_:1})]),_:1}))}});export{V as default};
+1
View File
@@ -0,0 +1 @@
import{_ as e,t}from"./VSelect-BvR-yyLS.js";import{Ar as n,At as r,Dr as i,Er as a,L as o,Nr as s,Tr as c,Xt as l,_i as u,b as d,br as f,in as p,jr as m,qr as h,ri as g,ui as _,v,xi as y}from"./index-CDcgx9V8.js";import{t as b}from"./usePageActivateRefresh-CqHSpagO.js";import{t as x}from"./useSnackbar-u_XK4ZNm.js";import{n as S}from"./dataTable-esDY4cOk.js";import{i as C,t as w}from"./VCard-pmpQR9O7.js";import{t as T}from"./VTextField-B-BnrJAQ.js";import{t as E}from"./VDataTableServer-BIMNqMvk.js";import{a as D,n as O,r as k,t as A}from"./auditNormalize-xrm2MK_4.js";var j=new Set([`batch_install_agent`,`batch_upgrade_agent`,`batch_uninstall_agent`,`batch_detect_path`,`batch_health_check`,`batch_update_category`,`batch_server_op`]);function M(e){return j.has(e)}function N(e){let t=(e||``).trim();if(!t.startsWith(`{`))return null;try{let e=JSON.parse(t);return typeof e.job_id!=`number`||!e.summary?null:e}catch{return null}}function P(e){let t=[e.summary],n=e.failures||[];if(n.length){let e=n.slice(0,3).map(e=>e.server_name).join(``);t.push(`失败: ${e}${n.length>3?``:``}`)}return t.join(` · `)}var F={class:`font-weight-medium`},I={class:`text-body-2`},L={class:`d-flex align-center ga-2 flex-wrap`},R={class:`text-body-2 text-medium-emphasis`,style:{"max-width":`360px`}},z={class:`text-caption text-medium-emphasis`},B={class:`text-caption text-medium-emphasis`},V=s({name:`AuditPage`,__name:`AuditPage`,setup(s){let j=x(),V=_(!1),H=_([]),U=_(0),W=_(1),G=_(null),K=_(``),q=_(``),J=O,Y=S([{title:`操作`,key:`action`,width:100},{title:`用户`,key:`admin_username`,width:100},{title:`目标`,key:`target`,width:140},{title:`详情`,key:`detail`},{title:`IP`,key:`ip_address`,width:120},{title:`时间`,key:`created_at`,width:160}]);async function X(e=!1){e||(V.value=!0);try{let e=(W.value-1)*50,t=await p.get(`/audit/`,{limit:50,offset:e,action:G.value||void 0,admin_username:K.value||void 0,date_from:q.value||void 0});H.value=A(t.items||[]),U.value=t.total||0}catch{H.value=[],e||j(`加载审计日志失败`,`error`)}finally{e||(V.value=!1)}}function Z(e){return e.startsWith(`create`)||e===`login`?`success`:e.startsWith(`update`)?`info`:e.startsWith(`delete`)||e===`logout`?`error`:e.includes(`execute`)||e.includes(`push`)||e.includes(`retry`)?`warning`:`primary`}function Q(e){return M(e.action)?N(e.detail):null}function $(e){let t=Q(e);return t?P(t):(e.detail||``).trim()||``}function ee(e){r(e.job_id,e.summary)}return b(e=>X(e)),(r,s)=>(h(),a(e,{fluid:``,class:`pa-6`},{default:g(()=>[m(w,{elevation:`0`,border:``,rounded:`lg`},{default:g(()=>[m(C,{class:`d-flex align-center`},{default:g(()=>[s[7]||=n(` 审计日志 `,-1),m(v),m(t,{modelValue:G.value,"onUpdate:modelValue":[s[0]||=e=>G.value=e,s[1]||=e=>{W.value=1,X()}],items:u(J),"item-title":`label`,"item-value":`value`,label:`操作类型`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`200px`},clearable:``},null,8,[`modelValue`,`items`]),m(T,{modelValue:K.value,"onUpdate:modelValue":[s[2]||=e=>K.value=e,s[3]||=e=>{W.value=1,X()}],"prepend-inner-icon":`mdi-account`,label:`用户`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`140px`},class:`ml-3`,clearable:``},null,8,[`modelValue`]),m(T,{modelValue:q.value,"onUpdate:modelValue":[s[4]||=e=>q.value=e,s[5]||=e=>{W.value=1,X()}],"prepend-inner-icon":`mdi-calendar`,label:`日期`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`160px`},class:`ml-3`,type:`date`,clearable:``},null,8,[`modelValue`])]),_:1}),m(E,{items:H.value,headers:u(Y),"items-length":U.value,loading:V.value,page:W.value,"items-per-page":50,hover:``,density:`compact`,"onUpdate:page":s[6]||=e=>{W.value=e,X()}},{"item.action":g(({item:e})=>[m(d,{color:Z(e.action),size:`small`,variant:`tonal`,label:``},{default:g(()=>[n(y(u(k)(e.action)),1)]),_:2},1032,[`color`])]),"item.admin_username":g(({item:e})=>[c(`span`,F,y(e.admin_username),1)]),"item.target":g(({item:e})=>[c(`span`,I,y(u(D)(e)),1)]),"item.detail":g(({item:e})=>[c(`div`,L,[c(`span`,R,y($(e)),1),Q(e)?(h(),a(o,{key:0,size:`x-small`,variant:`tonal`,color:`primary`,onClick:f(t=>ee(Q(e)),[`stop`])},{default:g(()=>[...s[8]||=[n(` 查看结果 `,-1)]]),_:1},8,[`onClick`])):i(``,!0)])]),"item.ip_address":g(({item:e})=>[c(`span`,z,y(e.ip_address||``),1)]),"item.created_at":g(({item:e})=>[c(`span`,B,y(u(l)(e.created_at)),1)]),"no-data":g(()=>[...s[9]||=[c(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无数据`,-1)]]),_:1},8,[`items`,`headers`,`items-length`,`loading`,`page`])]),_:1})]),_:1}))}});export{V as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-ClfE0-sD.js";import{Er as t,Nr as n,Tr as r,jr as i,qr as a,ri as o,ui as s}from"./index-CDcgx9V8.js";import{s as c}from"./btpanel-C_41A_fE.js";import{n as l,t as u}from"./useBtPanelPageLoad-CD81wbTP.js";var d=n({name:`BtPanelCrontabPage`,__name:`BtPanelCrontabPage`,setup(n){let d=s(!1),f=s([]),p=[{title:`ID`,key:`id`,width:70},{title:`名称`,key:`name`},{title:`周期`,key:`type`},{title:`状态`,key:`status`,width:90},{title:`备注`,key:`save`}];return u(async e=>{d.value=!0;try{let t=await c(e);Array.isArray(t)?f.value=t:f.value=t.data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{d.value=!1}}),(n,s)=>(a(),t(l,{title:`宝塔 · 计划任务`,subtitle:`只读查看子机宝塔 Crontab(F1)`},{default:o(()=>[i(e,{headers:p,items:f.value,loading:d.value,density:`compact`},null,8,[`items`,`loading`]),s[0]||=r(`p`,{class:`text-caption text-medium-emphasis mt-3`},` 此处仅展示宝塔面板内计划任务,与 Nexus「调度」页无关。 `,-1)]),_:1}))}});export{d as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-pRRodit6.js";import{Er as t,Nr as n,Tr as r,jr as i,qr as a,ri as o,ui as s}from"./index-a0fi5f-0.js";import{s as c}from"./btpanel-BsL25ffd.js";import{n as l,t as u}from"./useBtPanelPageLoad-CV_unS2-.js";var d=n({name:`BtPanelCrontabPage`,__name:`BtPanelCrontabPage`,setup(n){let d=s(!1),f=s([]),p=[{title:`ID`,key:`id`,width:70},{title:`名称`,key:`name`},{title:`周期`,key:`type`},{title:`状态`,key:`status`,width:90},{title:`备注`,key:`save`}];return u(async e=>{d.value=!0;try{let t=await c(e);Array.isArray(t)?f.value=t:f.value=t.data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{d.value=!1}}),(n,s)=>(a(),t(l,{title:`宝塔 · 计划任务`,subtitle:`只读查看子机宝塔 Crontab(F1)`},{default:o(()=>[i(e,{headers:p,items:f.value,loading:d.value,density:`compact`},null,8,[`items`,`loading`]),s[0]||=r(`p`,{class:`text-caption text-medium-emphasis mt-3`},` 此处仅展示宝塔面板内计划任务,与 Nexus「调度」页无关。 `,-1)]),_:1}))}});export{d as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-pRRodit6.js";import{Ar as t,Er as n,L as r,Nr as i,jr as a,li as o,qr as s,ri as c,ui as l}from"./index-a0fi5f-0.js";import{t as u}from"./VTextField-GUQr1tUN.js";import{i as d,n as f,t as p}from"./VExpansionPanels-DUlWPWNJ.js";import{a as m,c as h,d as g,l as _}from"./btpanel-BsL25ffd.js";import{n as v,t as y}from"./useBtPanelPageLoad-CV_unS2-.js";var b=i({name:`BtPanelDatabasesPage`,__name:`BtPanelDatabasesPage`,setup(i){let b=l(!1),x=l([]),S=o({name:``,password:``}),C=o({id:0,name:``,password:``}),w=[{title:`ID`,key:`id`,width:70},{title:`库名`,key:`name`},{title:`用户`,key:`username`},{title:`备注`,key:`ps`},{title:`操作`,key:`actions`,sortable:!1,width:80}],{serverId:T,run:E}=y(async e=>{b.value=!0;try{x.value=(await g(e)).data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{b.value=!1}});async function D(){let e=T.value;if(!(!e||!S.name)){b.value=!0;try{await m(e,{name:S.name,password:S.password,db_user:S.name,codeing:`utf8mb4`}),window.$snackbar?.(`已创建`,`success`),await E()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`创建失败`,`error`)}finally{b.value=!1}}}async function O(){let e=T.value;if(!(!e||!C.id)){b.value=!0;try{await _(e,{...C}),window.$snackbar?.(`密码已更新`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`失败`,`error`)}finally{b.value=!1}}}async function k(e){let t=T.value;if(t){b.value=!0;try{await h(t,e.id),window.$snackbar?.(`备份任务已提交`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`备份失败`,`error`)}finally{b.value=!1}}}return(i,o)=>(s(),n(v,{title:`宝塔 · 数据库`,subtitle:`列表 / 创建 / 改密 / 备份(D1–D4)`},{default:c(()=>[a(e,{headers:w,items:x.value,loading:b.value,density:`compact`},{"item.actions":c(({item:e})=>[a(r,{size:`x-small`,variant:`text`,onClick:t=>k(e)},{default:c(()=>[...o[5]||=[t(`备份`,-1)]]),_:1},8,[`onClick`])]),_:1},8,[`items`,`loading`]),a(p,{class:`mt-4`,variant:`accordion`},{default:c(()=>[a(f,{title:`创建数据库`},{default:c(()=>[a(d,null,{default:c(()=>[a(u,{modelValue:S.name,"onUpdate:modelValue":o[0]||=e=>S.name=e,label:`库名`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(u,{modelValue:S.password,"onUpdate:modelValue":o[1]||=e=>S.password=e,label:`密码`,type:`password`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(r,{color:`primary`,loading:b.value,onClick:D},{default:c(()=>[...o[6]||=[t(`创建`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1}),a(f,{title:`修改密码`},{default:c(()=>[a(d,null,{default:c(()=>[a(u,{modelValue:C.id,"onUpdate:modelValue":o[2]||=e=>C.id=e,modelModifiers:{number:!0},label:`ID`,type:`number`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(u,{modelValue:C.name,"onUpdate:modelValue":o[3]||=e=>C.name=e,label:`库名`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(u,{modelValue:C.password,"onUpdate:modelValue":o[4]||=e=>C.password=e,label:`新密码`,type:`password`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(r,{variant:`tonal`,loading:b.value,onClick:O},{default:c(()=>[...o[7]||=[t(`修改`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1})]),_:1}))}});export{b as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-ClfE0-sD.js";import{Ar as t,Er as n,L as r,Nr as i,jr as a,li as o,qr as s,ri as c,ui as l}from"./index-CDcgx9V8.js";import{t as u}from"./VTextField-B-BnrJAQ.js";import{i as d,n as f,t as p}from"./VExpansionPanels-ByOIBgKv.js";import{a as m,c as h,d as g,l as _}from"./btpanel-C_41A_fE.js";import{n as v,t as y}from"./useBtPanelPageLoad-CD81wbTP.js";var b=i({name:`BtPanelDatabasesPage`,__name:`BtPanelDatabasesPage`,setup(i){let b=l(!1),x=l([]),S=o({name:``,password:``}),C=o({id:0,name:``,password:``}),w=[{title:`ID`,key:`id`,width:70},{title:`库名`,key:`name`},{title:`用户`,key:`username`},{title:`备注`,key:`ps`},{title:`操作`,key:`actions`,sortable:!1,width:80}],{serverId:T,run:E}=y(async e=>{b.value=!0;try{x.value=(await g(e)).data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{b.value=!1}});async function D(){let e=T.value;if(!(!e||!S.name)){b.value=!0;try{await m(e,{name:S.name,password:S.password,db_user:S.name,codeing:`utf8mb4`}),window.$snackbar?.(`已创建`,`success`),await E()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`创建失败`,`error`)}finally{b.value=!1}}}async function O(){let e=T.value;if(!(!e||!C.id)){b.value=!0;try{await _(e,{...C}),window.$snackbar?.(`密码已更新`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`失败`,`error`)}finally{b.value=!1}}}async function k(e){let t=T.value;if(t){b.value=!0;try{await h(t,e.id),window.$snackbar?.(`备份任务已提交`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`备份失败`,`error`)}finally{b.value=!1}}}return(i,o)=>(s(),n(v,{title:`宝塔 · 数据库`,subtitle:`列表 / 创建 / 改密 / 备份(D1–D4)`},{default:c(()=>[a(e,{headers:w,items:x.value,loading:b.value,density:`compact`},{"item.actions":c(({item:e})=>[a(r,{size:`x-small`,variant:`text`,onClick:t=>k(e)},{default:c(()=>[...o[5]||=[t(`备份`,-1)]]),_:1},8,[`onClick`])]),_:1},8,[`items`,`loading`]),a(p,{class:`mt-4`,variant:`accordion`},{default:c(()=>[a(f,{title:`创建数据库`},{default:c(()=>[a(d,null,{default:c(()=>[a(u,{modelValue:S.name,"onUpdate:modelValue":o[0]||=e=>S.name=e,label:`库名`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(u,{modelValue:S.password,"onUpdate:modelValue":o[1]||=e=>S.password=e,label:`密码`,type:`password`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(r,{color:`primary`,loading:b.value,onClick:D},{default:c(()=>[...o[6]||=[t(`创建`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1}),a(f,{title:`修改密码`},{default:c(()=>[a(d,null,{default:c(()=>[a(u,{modelValue:C.id,"onUpdate:modelValue":o[2]||=e=>C.id=e,modelModifiers:{number:!0},label:`ID`,type:`number`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(u,{modelValue:C.name,"onUpdate:modelValue":o[3]||=e=>C.name=e,label:`库名`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(u,{modelValue:C.password,"onUpdate:modelValue":o[4]||=e=>C.password=e,label:`新密码`,type:`password`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),a(r,{variant:`tonal`,loading:b.value,onClick:O},{default:c(()=>[...o[7]||=[t(`修改`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1})]),_:1}))}});export{b as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-pRRodit6.js";import{n as t,t as n}from"./VRow-qjxp2VJ5.js";import{Ar as r,Er as i,L as a,Nr as o,Tr as s,jr as c,qr as l,ri as u,ui as d,y as f}from"./index-a0fi5f-0.js";import{t as p}from"./VTextField-GUQr1tUN.js";import{f as m,r as h,u as g}from"./btpanel-BsL25ffd.js";import{n as _,t as v}from"./useBtPanelPageLoad-CV_unS2-.js";var y={class:`d-flex flex-wrap ga-2 align-center`},b=o({name:`BtPanelDomainsPage`,__name:`BtPanelDomainsPage`,setup(o){let b=d(!1),x=d(null),S=d(``),C=d(``),w=d(`80`),T=d([]),E=[{title:`ID`,key:`id`,width:70},{title:`域名`,key:`name`},{title:`端口`,key:`port`,width:80},{title:`操作`,key:`actions`,sortable:!1,width:80}],{serverId:D}=v(()=>{});async function O(){let e=D.value;if(!(!e||!x.value)){b.value=!0;try{let t=await m(e,x.value);T.value=t.data??t??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{b.value=!1}}}async function k(){let e=D.value;if(!(!e||!x.value||!S.value||!C.value)){b.value=!0;try{await h(e,{id:x.value,webname:S.value,domain:C.value,port:w.value}),window.$snackbar?.(`已添加`,`success`),C.value=``,await O()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`添加失败`,`error`)}finally{b.value=!1}}}async function A(e){let t=D.value;if(!(!t||!x.value||!S.value)){b.value=!0;try{await g(t,{id:x.value,webname:S.value,domain:e.name,port:String(e.port)}),await O()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`删除失败`,`error`)}finally{b.value=!1}}}return(o,d)=>(l(),i(_,{title:`宝塔 · 域名管理`,subtitle:`绑定域名增删(C10`},{default:u(()=>[c(n,{class:`mb-4`,dense:``},{default:u(()=>[c(t,{cols:`12`,md:`3`},{default:u(()=>[c(p,{modelValue:x.value,"onUpdate:modelValue":d[0]||=e=>x.value=e,modelModifiers:{number:!0},label:`网站 ID`,type:`number`,density:`compact`},null,8,[`modelValue`])]),_:1}),c(t,{cols:`12`,md:`3`},{default:u(()=>[c(p,{modelValue:S.value,"onUpdate:modelValue":d[1]||=e=>S.value=e,label:`网站名`,density:`compact`},null,8,[`modelValue`])]),_:1}),c(t,{cols:`12`,md:`3`},{default:u(()=>[c(a,{variant:`tonal`,loading:b.value,onClick:O},{default:u(()=>[...d[4]||=[r(`加载域名`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1}),c(e,{headers:E,items:T.value,loading:b.value,density:`compact`},{"item.actions":u(({item:e})=>[c(a,{size:`x-small`,variant:`text`,color:`error`,onClick:t=>A(e)},{default:u(()=>[...d[5]||=[r(`删除`,-1)]]),_:1},8,[`onClick`])]),_:1},8,[`items`,`loading`]),c(f,{class:`my-4`}),s(`div`,y,[c(p,{modelValue:C.value,"onUpdate:modelValue":d[2]||=e=>C.value=e,label:`新域名`,density:`compact`,"hide-details":``,style:{"max-width":`280px`}},null,8,[`modelValue`]),c(p,{modelValue:w.value,"onUpdate:modelValue":d[3]||=e=>w.value=e,label:`端口`,density:`compact`,"hide-details":``,style:{"max-width":`100px`}},null,8,[`modelValue`]),c(a,{color:`primary`,loading:b.value,onClick:k},{default:u(()=>[...d[6]||=[r(`添加域名`,-1)]]),_:1},8,[`loading`])])]),_:1}))}});export{b as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-ClfE0-sD.js";import{n as t,t as n}from"./VRow-rmSa_q-C.js";import{Ar as r,Er as i,L as a,Nr as o,Tr as s,jr as c,qr as l,ri as u,ui as d,y as f}from"./index-CDcgx9V8.js";import{t as p}from"./VTextField-B-BnrJAQ.js";import{f as m,r as h,u as g}from"./btpanel-C_41A_fE.js";import{n as _,t as v}from"./useBtPanelPageLoad-CD81wbTP.js";var y={class:`d-flex flex-wrap ga-2 align-center`},b=o({name:`BtPanelDomainsPage`,__name:`BtPanelDomainsPage`,setup(o){let b=d(!1),x=d(null),S=d(``),C=d(``),w=d(`80`),T=d([]),E=[{title:`ID`,key:`id`,width:70},{title:`域名`,key:`name`},{title:`端口`,key:`port`,width:80},{title:`操作`,key:`actions`,sortable:!1,width:80}],{serverId:D}=v(()=>{});async function O(){let e=D.value;if(!(!e||!x.value)){b.value=!0;try{let t=await m(e,x.value);T.value=t.data??t??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{b.value=!1}}}async function k(){let e=D.value;if(!(!e||!x.value||!S.value||!C.value)){b.value=!0;try{await h(e,{id:x.value,webname:S.value,domain:C.value,port:w.value}),window.$snackbar?.(`已添加`,`success`),C.value=``,await O()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`添加失败`,`error`)}finally{b.value=!1}}}async function A(e){let t=D.value;if(!(!t||!x.value||!S.value)){b.value=!0;try{await g(t,{id:x.value,webname:S.value,domain:e.name,port:String(e.port)}),await O()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`删除失败`,`error`)}finally{b.value=!1}}}return(o,d)=>(l(),i(_,{title:`宝塔 · 域名管理`,subtitle:`绑定域名增删(C10`},{default:u(()=>[c(n,{class:`mb-4`,dense:``},{default:u(()=>[c(t,{cols:`12`,md:`3`},{default:u(()=>[c(p,{modelValue:x.value,"onUpdate:modelValue":d[0]||=e=>x.value=e,modelModifiers:{number:!0},label:`网站 ID`,type:`number`,density:`compact`},null,8,[`modelValue`])]),_:1}),c(t,{cols:`12`,md:`3`},{default:u(()=>[c(p,{modelValue:S.value,"onUpdate:modelValue":d[1]||=e=>S.value=e,label:`网站名`,density:`compact`},null,8,[`modelValue`])]),_:1}),c(t,{cols:`12`,md:`3`},{default:u(()=>[c(a,{variant:`tonal`,loading:b.value,onClick:O},{default:u(()=>[...d[4]||=[r(`加载域名`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1}),c(e,{headers:E,items:T.value,loading:b.value,density:`compact`},{"item.actions":u(({item:e})=>[c(a,{size:`x-small`,variant:`text`,color:`error`,onClick:t=>A(e)},{default:u(()=>[...d[5]||=[r(`删除`,-1)]]),_:1},8,[`onClick`])]),_:1},8,[`items`,`loading`]),c(f,{class:`my-4`}),s(`div`,y,[c(p,{modelValue:C.value,"onUpdate:modelValue":d[2]||=e=>C.value=e,label:`新域名`,density:`compact`,"hide-details":``,style:{"max-width":`280px`}},null,8,[`modelValue`]),c(p,{modelValue:w.value,"onUpdate:modelValue":d[3]||=e=>w.value=e,label:`端口`,density:`compact`,"hide-details":``,style:{"max-width":`100px`}},null,8,[`modelValue`]),c(a,{color:`primary`,loading:b.value,onClick:k},{default:u(()=>[...d[6]||=[r(`添加域名`,-1)]]),_:1},8,[`loading`])])]),_:1}))}});export{b as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-ClfE0-sD.js";import{_ as t}from"./VSelect-BvR-yyLS.js";import{Ar as n,Er as r,L as i,Nr as a,Or as o,Tr as s,Wr as c,_i as l,b as u,jr as d,qr as f,ri as p,ui as m,v as h,wr as g,xi as _}from"./index-CDcgx9V8.js";import{t as v}from"./VTextField-B-BnrJAQ.js";import{n as y,r as b}from"./status-BrGQYcd-.js";import{t as x}from"./useBtPanelLogin-DCNB-U_J.js";import{T as S}from"./btpanel-C_41A_fE.js";var C={class:`d-flex flex-wrap align-center ga-3 mb-4`},w={key:1,class:`text-medium-emphasis text-caption`},T={class:`text-caption text-truncate d-inline-block`,style:{"max-width":`220px`}},E=a({name:`BtPanelLoginPage`,__name:`BtPanelLoginPage`,setup(a){let E=m(!1),D=m(``),O=m([]),k=m([]),{loadingId:A,openBtLogin:j}=x(),M=[{title:`名称`,key:`name`,minWidth:120},{title:`地址`,key:`domain`,minWidth:120},{title:`状态`,key:`status`,width:88,sortable:!0},{title:`分类`,key:`category`,minWidth:80},{title:`API`,key:`configured`,width:110,sortable:!1},{title:`引导`,key:`bootstrap_state`,width:100,sortable:!1},{title:`面板地址`,key:`base_url`,minWidth:160,sortable:!1},{title:`操作`,key:`actions`,width:200,sortable:!1,align:`end`}],N={pending:`等待获取`,installing:`安装中`,ready:`已完成`,failed:`SSH 失败`,disabled:`已关闭`};function P(e){return N[e]??e}function F(e){return e.configured||e.bootstrap_state===`ready`?`success`:e.bootstrap_state===`failed`?`error`:e.bootstrap_state===`installing`?`warning`:`default`}let I=g(()=>{let e=D.value.trim().toLowerCase();return e?O.value.filter(t=>[t.name,t.domain,t.category??``,t.base_url??``,b(t.status)].join(` `).toLowerCase().includes(e)):O.value});async function L(){E.value=!0;try{O.value=await S(),k.value=k.value.filter(e=>O.value.some(t=>t.id===e))}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载服务器列表失败`,`error`)}finally{E.value=!1}}async function R(e){let t=!e.configured;await j(e.id,{needsBootstrap:t}),t&&await L()}return c(()=>{L()}),(a,c)=>(f(),r(t,{fluid:``,class:`pa-6`},{default:p(()=>[s(`div`,C,[c[2]||=s(`div`,null,[s(`h1`,{class:`text-h6 font-weight-bold`},`宝塔 · 服务器列表`),s(`p`,{class:`text-caption text-medium-emphasis mb-0`},` 批量获取 API 请在「服务器」页勾选后使用批量栏;配置完成后可一键登录面板 `)],-1),d(h),d(v,{modelValue:D.value,"onUpdate:modelValue":c[0]||=e=>D.value=e,density:`compact`,"hide-details":``,clearable:``,"prepend-inner-icon":`mdi-magnify`,label:`搜索名称 / IP / 分类`,style:{"max-width":`320px`,"min-width":`200px`}},null,8,[`modelValue`])]),d(e,{modelValue:k.value,"onUpdate:modelValue":c[1]||=e=>k.value=e,headers:M,items:I.value,loading:E.value,density:`compact`,"item-value":`id`,"show-select":``,hover:``},{"item.status":p(({item:e})=>[d(u,{color:l(y)(e.status),size:`x-small`,variant:`tonal`,label:``},{default:p(()=>[n(_(l(b)(e.status)),1)]),_:2},1032,[`color`])]),"item.configured":p(({item:e})=>[d(u,{size:`x-small`,color:e.configured?`success`:`warning`,variant:`tonal`},{default:p(()=>[n(_(e.configured?`API 已配置`:`未配置`),1)]),_:2},1032,[`color`])]),"item.bootstrap_state":p(({item:e})=>[e.bootstrap_state?(f(),r(u,{key:0,size:`x-small`,color:F(e),variant:`tonal`},{default:p(()=>[n(_(P(e.bootstrap_state)),1)]),_:2},1032,[`color`])):(f(),o(`span`,w,``))]),"item.base_url":p(({item:e})=>[s(`span`,T,_(e.base_url||``),1)]),"item.actions":p(({item:e})=>[d(i,{size:`small`,variant:`tonal`,color:`primary`,"prepend-icon":`mdi-open-in-new`,loading:l(A)===e.id,title:e.configured?`打开宝塔临时登录`:`将自动获取 API 后打开面板`,onClick:t=>R(e)},{default:p(()=>[...c[3]||=[n(` 一键登录 `,-1)]]),_:1},8,[`loading`,`title`,`onClick`]),d(i,{size:`small`,variant:`text`,class:`ml-1`,to:`/btpanel/settings?server_id=${e.id}`},{default:p(()=>[...c[4]||=[n(` 连接配置 `,-1)]]),_:1},8,[`to`])]),_:1},8,[`modelValue`,`items`,`loading`])]),_:1}))}});export{E as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-pRRodit6.js";import{_ as t}from"./VSelect-3h-EI23Y.js";import{Ar as n,Er as r,L as i,Nr as a,Or as o,Tr as s,Wr as c,_i as l,b as u,jr as d,qr as f,ri as p,ui as m,v as h,wr as g,xi as _}from"./index-a0fi5f-0.js";import{t as v}from"./VTextField-GUQr1tUN.js";import{n as y,r as b}from"./status-XDgjPQrL.js";import{t as x}from"./useBtPanelLogin-DDcleXPj.js";import{T as S}from"./btpanel-BsL25ffd.js";var C={class:`d-flex flex-wrap align-center ga-3 mb-4`},w={key:1,class:`text-medium-emphasis text-caption`},T={class:`text-caption text-truncate d-inline-block`,style:{"max-width":`220px`}},E=a({name:`BtPanelLoginPage`,__name:`BtPanelLoginPage`,setup(a){let E=m(!1),D=m(``),O=m([]),k=m([]),{loadingId:A,openBtLogin:j}=x(),M=[{title:`名称`,key:`name`,minWidth:120},{title:`地址`,key:`domain`,minWidth:120},{title:`状态`,key:`status`,width:88,sortable:!0},{title:`分类`,key:`category`,minWidth:80},{title:`API`,key:`configured`,width:110,sortable:!1},{title:`引导`,key:`bootstrap_state`,width:100,sortable:!1},{title:`面板地址`,key:`base_url`,minWidth:160,sortable:!1},{title:`操作`,key:`actions`,width:200,sortable:!1,align:`end`}],N={pending:`等待获取`,installing:`安装中`,ready:`已完成`,failed:`SSH 失败`,disabled:`已关闭`};function P(e){return N[e]??e}function F(e){return e.configured||e.bootstrap_state===`ready`?`success`:e.bootstrap_state===`failed`?`error`:e.bootstrap_state===`installing`?`warning`:`default`}let I=g(()=>{let e=D.value.trim().toLowerCase();return e?O.value.filter(t=>[t.name,t.domain,t.category??``,t.base_url??``,b(t.status)].join(` `).toLowerCase().includes(e)):O.value});async function L(){E.value=!0;try{O.value=await S(),k.value=k.value.filter(e=>O.value.some(t=>t.id===e))}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载服务器列表失败`,`error`)}finally{E.value=!1}}async function R(e){let t=!e.configured;await j(e.id,{needsBootstrap:t}),t&&await L()}return c(()=>{L()}),(a,c)=>(f(),r(t,{fluid:``,class:`pa-6`},{default:p(()=>[s(`div`,C,[c[2]||=s(`div`,null,[s(`h1`,{class:`text-h6 font-weight-bold`},`宝塔 · 服务器列表`),s(`p`,{class:`text-caption text-medium-emphasis mb-0`},` 批量获取 API 请在「服务器」页勾选后使用批量栏;配置完成后可一键登录面板 `)],-1),d(h),d(v,{modelValue:D.value,"onUpdate:modelValue":c[0]||=e=>D.value=e,density:`compact`,"hide-details":``,clearable:``,"prepend-inner-icon":`mdi-magnify`,label:`搜索名称 / IP / 分类`,style:{"max-width":`320px`,"min-width":`200px`}},null,8,[`modelValue`])]),d(e,{modelValue:k.value,"onUpdate:modelValue":c[1]||=e=>k.value=e,headers:M,items:I.value,loading:E.value,density:`compact`,"item-value":`id`,"show-select":``,hover:``},{"item.status":p(({item:e})=>[d(u,{color:l(y)(e.status),size:`x-small`,variant:`tonal`,label:``},{default:p(()=>[n(_(l(b)(e.status)),1)]),_:2},1032,[`color`])]),"item.configured":p(({item:e})=>[d(u,{size:`x-small`,color:e.configured?`success`:`warning`,variant:`tonal`},{default:p(()=>[n(_(e.configured?`API 已配置`:`未配置`),1)]),_:2},1032,[`color`])]),"item.bootstrap_state":p(({item:e})=>[e.bootstrap_state?(f(),r(u,{key:0,size:`x-small`,color:F(e),variant:`tonal`},{default:p(()=>[n(_(P(e.bootstrap_state)),1)]),_:2},1032,[`color`])):(f(),o(`span`,w,``))]),"item.base_url":p(({item:e})=>[s(`span`,T,_(e.base_url||``),1)]),"item.actions":p(({item:e})=>[d(i,{size:`small`,variant:`tonal`,color:`primary`,"prepend-icon":`mdi-open-in-new`,loading:l(A)===e.id,title:e.configured?`打开宝塔临时登录`:`将自动获取 API 后打开面板`,onClick:t=>R(e)},{default:p(()=>[...c[3]||=[n(` 一键登录 `,-1)]]),_:1},8,[`loading`,`title`,`onClick`]),d(i,{size:`small`,variant:`text`,class:`ml-1`,to:`/btpanel/settings?server_id=${e.id}`},{default:p(()=>[...c[4]||=[n(` 连接配置 `,-1)]]),_:1},8,[`to`])]),_:1},8,[`modelValue`,`items`,`loading`])]),_:1}))}});export{E as default};
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-qjxp2VJ5.js";import{Ar as n,Dr as r,Er as i,L as a,Nr as o,Or as s,_i as c,jr as l,qr as u,ri as d,ui as f,xi as p}from"./index-a0fi5f-0.js";import{t as m}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{i as h,n as g,t as _}from"./VCard-BwwttBcf.js";import{S as v,b as y,x as b}from"./btpanel-BsL25ffd.js";import{n as x,t as S}from"./useBtPanelPageLoad-CV_unS2-.js";var C={key:0,class:`bt-json`},w={key:1,class:`text-medium-emphasis`},T={key:0,class:`bt-json`},E={key:0,class:`bt-json`},D=m(o({name:`BtPanelMonitorPage`,__name:`BtPanelMonitorPage`,setup(o){let m=f(!1),D=f(null),O=f(null),k=f(null),{run:A}=S(async e=>{m.value=!0;try{let[t,n,r]=await Promise.all([v(e),y(e),b(e)]);D.value=t,O.value=n,k.value=r}catch(e){D.value=null,O.value=null,k.value=null,window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{m.value=!1}});return(o,f)=>(u(),i(x,{title:`宝塔 · 系统监控`,subtitle:`GetSystemTotal / GetDiskInfo / GetNetWorkB1B3`},{default:d(()=>[l(t,null,{default:d(()=>[l(e,{cols:`12`,md:`6`},{default:d(()=>[l(_,{variant:`outlined`,rounded:`lg`,loading:m.value},{default:d(()=>[l(h,{class:`text-subtitle-1`},{default:d(()=>[...f[0]||=[n(`系统概览`,-1)]]),_:1}),l(g,null,{default:d(()=>[D.value?(u(),s(`pre`,C,p(JSON.stringify(D.value,null,2)),1)):(u(),s(`span`,w,`暂无数据`))]),_:1})]),_:1},8,[`loading`])]),_:1}),l(e,{cols:`12`,md:`6`},{default:d(()=>[l(_,{variant:`outlined`,rounded:`lg`,loading:m.value},{default:d(()=>[l(h,{class:`text-subtitle-1`},{default:d(()=>[...f[1]||=[n(`实时负载 / 网络`,-1)]]),_:1}),l(g,null,{default:d(()=>[k.value?(u(),s(`pre`,T,p(JSON.stringify(k.value,null,2)),1)):r(``,!0)]),_:1})]),_:1},8,[`loading`])]),_:1}),l(e,{cols:`12`},{default:d(()=>[l(_,{variant:`outlined`,rounded:`lg`,loading:m.value},{default:d(()=>[l(h,{class:`text-subtitle-1`},{default:d(()=>[...f[2]||=[n(`磁盘分区`,-1)]]),_:1}),l(g,null,{default:d(()=>[O.value?(u(),s(`pre`,E,p(JSON.stringify(O.value,null,2)),1)):r(``,!0)]),_:1})]),_:1},8,[`loading`])]),_:1})]),_:1}),l(a,{size:`small`,variant:`tonal`,class:`mt-3`,loading:m.value,onClick:c(A)},{default:d(()=>[...f[3]||=[n(`刷新`,-1)]]),_:1},8,[`loading`,`onClick`])]),_:1}))}}),[[`__scopeId`,`data-v-f30462f1`]]);export{D as default};
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-rmSa_q-C.js";import{Ar as n,Dr as r,Er as i,L as a,Nr as o,Or as s,_i as c,jr as l,qr as u,ri as d,ui as f,xi as p}from"./index-CDcgx9V8.js";import{t as m}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{i as h,n as g,t as _}from"./VCard-pmpQR9O7.js";import{S as v,b as y,x as b}from"./btpanel-C_41A_fE.js";import{n as x,t as S}from"./useBtPanelPageLoad-CD81wbTP.js";var C={key:0,class:`bt-json`},w={key:1,class:`text-medium-emphasis`},T={key:0,class:`bt-json`},E={key:0,class:`bt-json`},D=m(o({name:`BtPanelMonitorPage`,__name:`BtPanelMonitorPage`,setup(o){let m=f(!1),D=f(null),O=f(null),k=f(null),{run:A}=S(async e=>{m.value=!0;try{let[t,n,r]=await Promise.all([v(e),y(e),b(e)]);D.value=t,O.value=n,k.value=r}catch(e){D.value=null,O.value=null,k.value=null,window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{m.value=!1}});return(o,f)=>(u(),i(x,{title:`宝塔 · 系统监控`,subtitle:`GetSystemTotal / GetDiskInfo / GetNetWorkB1B3`},{default:d(()=>[l(t,null,{default:d(()=>[l(e,{cols:`12`,md:`6`},{default:d(()=>[l(_,{variant:`outlined`,rounded:`lg`,loading:m.value},{default:d(()=>[l(h,{class:`text-subtitle-1`},{default:d(()=>[...f[0]||=[n(`系统概览`,-1)]]),_:1}),l(g,null,{default:d(()=>[D.value?(u(),s(`pre`,C,p(JSON.stringify(D.value,null,2)),1)):(u(),s(`span`,w,`暂无数据`))]),_:1})]),_:1},8,[`loading`])]),_:1}),l(e,{cols:`12`,md:`6`},{default:d(()=>[l(_,{variant:`outlined`,rounded:`lg`,loading:m.value},{default:d(()=>[l(h,{class:`text-subtitle-1`},{default:d(()=>[...f[1]||=[n(`实时负载 / 网络`,-1)]]),_:1}),l(g,null,{default:d(()=>[k.value?(u(),s(`pre`,T,p(JSON.stringify(k.value,null,2)),1)):r(``,!0)]),_:1})]),_:1},8,[`loading`])]),_:1}),l(e,{cols:`12`},{default:d(()=>[l(_,{variant:`outlined`,rounded:`lg`,loading:m.value},{default:d(()=>[l(h,{class:`text-subtitle-1`},{default:d(()=>[...f[2]||=[n(`磁盘分区`,-1)]]),_:1}),l(g,null,{default:d(()=>[O.value?(u(),s(`pre`,E,p(JSON.stringify(O.value,null,2)),1)):r(``,!0)]),_:1})]),_:1},8,[`loading`])]),_:1})]),_:1}),l(a,{size:`small`,variant:`tonal`,class:`mt-3`,loading:m.value,onClick:c(A)},{default:d(()=>[...f[3]||=[n(`刷新`,-1)]]),_:1},8,[`loading`,`onClick`])]),_:1}))}}),[[`__scopeId`,`data-v-f30462f1`]]);export{D as default};
@@ -0,0 +1 @@
.bt-json[data-v-f30462f1]{white-space:pre-wrap;word-break:break-all;max-height:360px;font-size:12px;overflow:auto}
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-rmSa_q-C.js";import{Ar as n,Er as r,L as i,Nr as a,Or as o,Yr as s,jr as c,qr as l,ri as u,ui as d,xi as f,xr as p}from"./index-CDcgx9V8.js";import{i as m,o as h,t as g}from"./VCard-pmpQR9O7.js";import{h as _}from"./btpanel-C_41A_fE.js";import{n as v,t as y}from"./useBtPanelPageLoad-CD81wbTP.js";var b=a({name:`BtPanelServicesPage`,__name:`BtPanelServicesPage`,setup(a){let b=[`nginx`,`mysql`,`redis`,`php-fpm`],x=d(!1),{serverId:S}=y(()=>{});async function C(e,t){let n=S.value;if(n&&window.confirm(`确认对 ${e} 执行 ${t}`)){x.value=!0;try{await _(n,e,t),window.$snackbar?.(`${e} ${t} 已提交`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`操作失败`,`error`)}finally{x.value=!1}}}return(a,d)=>(l(),r(v,{title:`宝塔 · 服务管理`,subtitle:`Nginx / MySQL / Redis / PHP 启停重启(H1`},{default:u(()=>[c(t,null,{default:u(()=>[(l(),o(p,null,s(b,t=>c(e,{key:t,cols:`12`,sm:`6`,md:`4`},{default:u(()=>[c(g,{variant:`outlined`,rounded:`lg`},{default:u(()=>[c(m,{class:`text-subtitle-2`},{default:u(()=>[n(f(t),1)]),_:2},1024),c(h,null,{default:u(()=>[c(i,{size:`small`,variant:`tonal`,color:`success`,loading:x.value,onClick:e=>C(t,`start`)},{default:u(()=>[...d[0]||=[n(`启动`,-1)]]),_:1},8,[`loading`,`onClick`]),c(i,{size:`small`,variant:`tonal`,color:`warning`,loading:x.value,onClick:e=>C(t,`stop`)},{default:u(()=>[...d[1]||=[n(`停止`,-1)]]),_:1},8,[`loading`,`onClick`]),c(i,{size:`small`,variant:`tonal`,color:`primary`,loading:x.value,onClick:e=>C(t,`restart`)},{default:u(()=>[...d[2]||=[n(`重启`,-1)]]),_:1},8,[`loading`,`onClick`])]),_:2},1024)]),_:2},1024)]),_:2},1024)),64))]),_:1})]),_:1}))}});export{b as default};
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-qjxp2VJ5.js";import{Ar as n,Er as r,L as i,Nr as a,Or as o,Yr as s,jr as c,qr as l,ri as u,ui as d,xi as f,xr as p}from"./index-a0fi5f-0.js";import{i as m,o as h,t as g}from"./VCard-BwwttBcf.js";import{h as _}from"./btpanel-BsL25ffd.js";import{n as v,t as y}from"./useBtPanelPageLoad-CV_unS2-.js";var b=a({name:`BtPanelServicesPage`,__name:`BtPanelServicesPage`,setup(a){let b=[`nginx`,`mysql`,`redis`,`php-fpm`],x=d(!1),{serverId:S}=y(()=>{});async function C(e,t){let n=S.value;if(n&&window.confirm(`确认对 ${e} 执行 ${t}`)){x.value=!0;try{await _(n,e,t),window.$snackbar?.(`${e} ${t} 已提交`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`操作失败`,`error`)}finally{x.value=!1}}}return(a,d)=>(l(),r(v,{title:`宝塔 · 服务管理`,subtitle:`Nginx / MySQL / Redis / PHP 启停重启(H1`},{default:u(()=>[c(t,null,{default:u(()=>[(l(),o(p,null,s(b,t=>c(e,{key:t,cols:`12`,sm:`6`,md:`4`},{default:u(()=>[c(g,{variant:`outlined`,rounded:`lg`},{default:u(()=>[c(m,{class:`text-subtitle-2`},{default:u(()=>[n(f(t),1)]),_:2},1024),c(h,null,{default:u(()=>[c(i,{size:`small`,variant:`tonal`,color:`success`,loading:x.value,onClick:e=>C(t,`start`)},{default:u(()=>[...d[0]||=[n(`启动`,-1)]]),_:1},8,[`loading`,`onClick`]),c(i,{size:`small`,variant:`tonal`,color:`warning`,loading:x.value,onClick:e=>C(t,`stop`)},{default:u(()=>[...d[1]||=[n(`停止`,-1)]]),_:1},8,[`loading`,`onClick`]),c(i,{size:`small`,variant:`tonal`,color:`primary`,loading:x.value,onClick:e=>C(t,`restart`)},{default:u(()=>[...d[2]||=[n(`重启`,-1)]]),_:1},8,[`loading`,`onClick`])]),_:2},1024)]),_:2},1024)]),_:2},1024)),64))]),_:1})]),_:1}))}});export{b as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{t as e}from"./VSelect-3h-EI23Y.js";import{Ar as t,Er as n,L as r,Nr as i,br as a,jr as o,li as s,qr as c,ri as l,ui as u}from"./index-a0fi5f-0.js";import{n as d,t as f}from"./VCard-BwwttBcf.js";import{t as p}from"./VTextField-GUQr1tUN.js";import{t as m}from"./VForm-CAvEgM-c.js";import{m as h,o as g}from"./btpanel-BsL25ffd.js";import{n as _,t as v}from"./useBtPanelPageLoad-CV_unS2-.js";var y=i({name:`BtPanelSiteCreatePage`,__name:`BtPanelSiteCreatePage`,setup(i){let y=u(!1),b=u([`74`,`80`,`82`]),x=s({webname:``,path:``,version:`74`,port:`80`,ps:``,type_id:0,type:`PHP`,ftp:!1,sql:!1}),{serverId:S}=v(async e=>{try{let t=await h(e);Array.isArray(t)&&(b.value=t.map(e=>e?.version).filter(e=>!!e&&e!==`00`))}catch{}});async function C(){let e=S.value;if(!(!e||!x.webname||!x.path)){y.value=!0;try{await g(e,{...x}),window.$snackbar?.(`创建请求已提交`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`创建失败`,`error`)}finally{y.value=!1}}}return(i,s)=>(c(),n(_,{title:`宝塔 · 创建网站`,subtitle:`AddSiteC4`},{default:l(()=>[o(f,{"max-width":`640`,variant:`outlined`,rounded:`lg`},{default:l(()=>[o(d,null,{default:l(()=>[o(m,{onSubmit:a(C,[`prevent`])},{default:l(()=>[o(p,{modelValue:x.webname,"onUpdate:modelValue":s[0]||=e=>x.webname=e,label:`主域名`,required:``},null,8,[`modelValue`]),o(p,{modelValue:x.path,"onUpdate:modelValue":s[1]||=e=>x.path=e,label:`根目录`,hint:`如 /www/wwwroot/example.com`,"persistent-hint":``},null,8,[`modelValue`]),o(e,{modelValue:x.version,"onUpdate:modelValue":s[2]||=e=>x.version=e,items:b.value,label:`PHP 版本`},null,8,[`modelValue`,`items`]),o(p,{modelValue:x.port,"onUpdate:modelValue":s[3]||=e=>x.port=e,label:`端口`},null,8,[`modelValue`]),o(p,{modelValue:x.ps,"onUpdate:modelValue":s[4]||=e=>x.ps=e,label:`备注`},null,8,[`modelValue`]),o(r,{type:`submit`,color:`primary`,loading:y.value},{default:l(()=>[...s[5]||=[t(`创建`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1})]),_:1}))}});export{y as default};
@@ -0,0 +1 @@
import{t as e}from"./VSelect-BvR-yyLS.js";import{Ar as t,Er as n,L as r,Nr as i,br as a,jr as o,li as s,qr as c,ri as l,ui as u}from"./index-CDcgx9V8.js";import{n as d,t as f}from"./VCard-pmpQR9O7.js";import{t as p}from"./VTextField-B-BnrJAQ.js";import{t as m}from"./VForm-uEqKaHHF.js";import{m as h,o as g}from"./btpanel-C_41A_fE.js";import{n as _,t as v}from"./useBtPanelPageLoad-CD81wbTP.js";var y=i({name:`BtPanelSiteCreatePage`,__name:`BtPanelSiteCreatePage`,setup(i){let y=u(!1),b=u([`74`,`80`,`82`]),x=s({webname:``,path:``,version:`74`,port:`80`,ps:``,type_id:0,type:`PHP`,ftp:!1,sql:!1}),{serverId:S}=v(async e=>{try{let t=await h(e);Array.isArray(t)&&(b.value=t.map(e=>{if(!e||typeof e!=`object`||!(`version`in e))return``;let t=e.version;return typeof t==`string`?t:String(t||``)}).filter(e=>!!e&&e!==`00`))}catch{}});async function C(){let e=S.value;if(!(!e||!x.webname||!x.path)){y.value=!0;try{await g(e,{...x}),window.$snackbar?.(`创建请求已提交`,`success`)}catch(e){window.$snackbar?.(e instanceof Error?e.message:`创建失败`,`error`)}finally{y.value=!1}}}return(i,s)=>(c(),n(_,{title:`宝塔 · 创建网站`,subtitle:`AddSiteC4`},{default:l(()=>[o(f,{"max-width":`640`,variant:`outlined`,rounded:`lg`},{default:l(()=>[o(d,null,{default:l(()=>[o(m,{onSubmit:a(C,[`prevent`])},{default:l(()=>[o(p,{modelValue:x.webname,"onUpdate:modelValue":s[0]||=e=>x.webname=e,label:`主域名`,required:``},null,8,[`modelValue`]),o(p,{modelValue:x.path,"onUpdate:modelValue":s[1]||=e=>x.path=e,label:`根目录`,hint:`如 /www/wwwroot/example.com`,"persistent-hint":``},null,8,[`modelValue`]),o(e,{modelValue:x.version,"onUpdate:modelValue":s[2]||=e=>x.version=e,items:b.value,label:`PHP 版本`},null,8,[`modelValue`,`items`]),o(p,{modelValue:x.port,"onUpdate:modelValue":s[3]||=e=>x.port=e,label:`端口`},null,8,[`modelValue`]),o(p,{modelValue:x.ps,"onUpdate:modelValue":s[4]||=e=>x.ps=e,label:`备注`},null,8,[`modelValue`]),o(r,{type:`submit`,color:`primary`,loading:y.value},{default:l(()=>[...s[5]||=[t(`创建`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1})]),_:1}))}});export{y as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-ClfE0-sD.js";import{Ar as t,Er as n,L as r,Nr as i,b as a,jr as o,qr as s,ri as c,ui as l,xi as u}from"./index-CDcgx9V8.js";import{_ as d,p as f,v as p}from"./btpanel-C_41A_fE.js";import{n as m,t as h}from"./useBtPanelPageLoad-CD81wbTP.js";var g=i({name:`BtPanelSitesPage`,__name:`BtPanelSitesPage`,setup(i){let g=l(!1),_=l([]),v=[{title:`ID`,key:`id`,width:70},{title:`域名`,key:`name`},{title:`路径`,key:`path`},{title:`备注`,key:`ps`},{title:`状态`,key:`status`,width:90},{title:`操作`,key:`actions`,sortable:!1,width:100}],{run:y,serverId:b}=h(async e=>{g.value=!0;try{_.value=(await f(e)).data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{g.value=!1}});async function x(e,t){let n=b.value;if(n){g.value=!0;try{t?await d(n,e.id,e.name):await p(n,e.id,e.name),window.$snackbar?.(t?`已启用`:`已停用`,`success`),await y()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`操作失败`,`error`)}finally{g.value=!1}}}return(i,l)=>(s(),n(m,{title:`宝塔 · 网站列表`,subtitle:`站点列表与启停(C1 / C6`},{default:c(()=>[o(e,{headers:v,items:_.value,loading:g.value,density:`compact`,"item-value":`id`},{"item.status":c(({item:e})=>[o(a,{size:`x-small`,color:e.status===`1`?`success`:`warning`,variant:`tonal`},{default:c(()=>[t(u(e.status===`1`?`运行`:`停用`),1)]),_:2},1032,[`color`])]),"item.actions":c(({item:e})=>[e.status===`1`?(s(),n(r,{key:1,size:`x-small`,variant:`text`,color:`warning`,onClick:t=>x(e,!1)},{default:c(()=>[...l[1]||=[t(`停用`,-1)]]),_:1},8,[`onClick`])):(s(),n(r,{key:0,size:`x-small`,variant:`text`,color:`success`,onClick:t=>x(e,!0)},{default:c(()=>[...l[0]||=[t(`启用`,-1)]]),_:1},8,[`onClick`]))]),_:1},8,[`items`,`loading`])]),_:1}))}});export{g as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-pRRodit6.js";import{Ar as t,Er as n,L as r,Nr as i,b as a,jr as o,qr as s,ri as c,ui as l,xi as u}from"./index-a0fi5f-0.js";import{_ as d,p as f,v as p}from"./btpanel-BsL25ffd.js";import{n as m,t as h}from"./useBtPanelPageLoad-CV_unS2-.js";var g=i({name:`BtPanelSitesPage`,__name:`BtPanelSitesPage`,setup(i){let g=l(!1),_=l([]),v=[{title:`ID`,key:`id`,width:70},{title:`域名`,key:`name`},{title:`路径`,key:`path`},{title:`备注`,key:`ps`},{title:`状态`,key:`status`,width:90},{title:`操作`,key:`actions`,sortable:!1,width:100}],{run:y,serverId:b}=h(async e=>{g.value=!0;try{_.value=(await f(e)).data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{g.value=!1}});async function x(e,t){let n=b.value;if(n){g.value=!0;try{t?await d(n,e.id,e.name):await p(n,e.id,e.name),window.$snackbar?.(t?`已启用`:`已停用`,`success`),await y()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`操作失败`,`error`)}finally{g.value=!1}}}return(i,l)=>(s(),n(m,{title:`宝塔 · 网站列表`,subtitle:`站点列表与启停(C1 / C6`},{default:c(()=>[o(e,{headers:v,items:_.value,loading:g.value,density:`compact`,"item-value":`id`},{"item.status":c(({item:e})=>[o(a,{size:`x-small`,color:e.status===`1`?`success`:`warning`,variant:`tonal`},{default:c(()=>[t(u(e.status===`1`?`运行`:`停用`),1)]),_:2},1032,[`color`])]),"item.actions":c(({item:e})=>[e.status===`1`?(s(),n(r,{key:1,size:`x-small`,variant:`text`,color:`warning`,onClick:t=>x(e,!1)},{default:c(()=>[...l[1]||=[t(`停用`,-1)]]),_:1},8,[`onClick`])):(s(),n(r,{key:0,size:`x-small`,variant:`text`,color:`success`,onClick:t=>x(e,!0)},{default:c(()=>[...l[0]||=[t(`启用`,-1)]]),_:1},8,[`onClick`]))]),_:1},8,[`items`,`loading`])]),_:1}))}});export{g as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-pRRodit6.js";import{Ar as t,Er as n,L as r,Nr as i,Tr as a,jr as o,li as s,qr as c,ri as l,ui as u,xi as d}from"./index-a0fi5f-0.js";import{t as f}from"./VTextField-GUQr1tUN.js";import{t as p}from"./VTextarea-BMTWx02W.js";import{i as m,n as h,t as g}from"./VExpansionPanels-DUlWPWNJ.js";import{g as _,i as v,y}from"./btpanel-BsL25ffd.js";import{n as b,t as x}from"./useBtPanelPageLoad-CV_unS2-.js";var S=i({name:`BtPanelSslPage`,__name:`BtPanelSslPage`,setup(i){let S=u(!1),C=u([]),w=s({siteName:``,key:``,csr:``}),T=[{title:`ID`,key:`id`,width:70},{title:`网站`,key:`name`},{title:`SSL`,key:`ssl`},{title:`操作`,key:`actions`,sortable:!1,width:100}],{serverId:E,run:D}=x(async e=>{S.value=!0;try{C.value=(await y(e)).data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{S.value=!1}});function O(e){return!e.ssl||e.ssl===-1?``:typeof e.ssl==`object`?e.ssl.subject||e.ssl.notAfter||`已配置`:String(e.ssl)}async function k(e){let t=E.value;if(t){S.value=!0;try{await v(t,{domains:e.name,id:e.id,auth_type:`http`}),window.$snackbar?.(`已提交申请`,`success`),await D()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`申请失败`,`error`)}finally{S.value=!1}}}async function A(){let e=E.value;if(!(!e||!w.siteName)){S.value=!0;try{await _(e,{siteName:w.siteName,key:w.key,csr:w.csr}),window.$snackbar?.(`证书已上传`,`success`),await D()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`上传失败`,`error`)}finally{S.value=!1}}}return(i,s)=>(c(),n(b,{title:`宝塔 · SSL 证书`,subtitle:`站点证书查看 / 上传 / Let's EncryptC15`},{default:l(()=>[o(e,{headers:T,items:C.value,loading:S.value,density:`compact`},{"item.ssl":l(({item:e})=>[a(`span`,null,d(O(e)),1)]),"item.actions":l(({item:e})=>[o(r,{size:`x-small`,variant:`text`,onClick:t=>k(e)},{default:l(()=>[...s[3]||=[t(`申请 LE`,-1)]]),_:1},8,[`onClick`])]),_:1},8,[`items`,`loading`]),o(g,{class:`mt-4`,variant:`accordion`},{default:l(()=>[o(h,{title:`上传证书(SetSSL`},{default:l(()=>[o(m,null,{default:l(()=>[o(f,{modelValue:w.siteName,"onUpdate:modelValue":s[0]||=e=>w.siteName=e,label:`网站名`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),o(p,{modelValue:w.csr,"onUpdate:modelValue":s[1]||=e=>w.csr=e,label:`证书 PEM`,rows:`4`,class:`mb-2`},null,8,[`modelValue`]),o(p,{modelValue:w.key,"onUpdate:modelValue":s[2]||=e=>w.key=e,label:`私钥 PEM`,rows:`4`,class:`mb-2`},null,8,[`modelValue`]),o(r,{color:`primary`,loading:S.value,onClick:A},{default:l(()=>[...s[4]||=[t(`上传`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1})]),_:1}))}});export{S as default};
@@ -0,0 +1 @@
import{t as e}from"./VDataTable-ClfE0-sD.js";import{Ar as t,Er as n,L as r,Nr as i,Tr as a,jr as o,li as s,qr as c,ri as l,ui as u,xi as d}from"./index-CDcgx9V8.js";import{t as f}from"./VTextField-B-BnrJAQ.js";import{t as p}from"./VTextarea-PzNEhaMZ.js";import{i as m,n as h,t as g}from"./VExpansionPanels-ByOIBgKv.js";import{g as _,i as v,y}from"./btpanel-C_41A_fE.js";import{n as b,t as x}from"./useBtPanelPageLoad-CD81wbTP.js";var S=i({name:`BtPanelSslPage`,__name:`BtPanelSslPage`,setup(i){let S=u(!1),C=u([]),w=s({siteName:``,key:``,csr:``}),T=[{title:`ID`,key:`id`,width:70},{title:`网站`,key:`name`},{title:`SSL`,key:`ssl`},{title:`操作`,key:`actions`,sortable:!1,width:100}],{serverId:E,run:D}=x(async e=>{S.value=!0;try{C.value=(await y(e)).data??[]}catch(e){window.$snackbar?.(e instanceof Error?e.message:`加载失败`,`error`)}finally{S.value=!1}});function O(e){return!e.ssl||e.ssl===-1?``:typeof e.ssl==`object`?e.ssl.subject||e.ssl.notAfter||`已配置`:String(e.ssl)}async function k(e){let t=E.value;if(t){S.value=!0;try{await v(t,{domains:e.name,id:e.id,auth_type:`http`}),window.$snackbar?.(`已提交申请`,`success`),await D()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`申请失败`,`error`)}finally{S.value=!1}}}async function A(){let e=E.value;if(!(!e||!w.siteName)){S.value=!0;try{await _(e,{siteName:w.siteName,key:w.key,csr:w.csr}),window.$snackbar?.(`证书已上传`,`success`),await D()}catch(e){window.$snackbar?.(e instanceof Error?e.message:`上传失败`,`error`)}finally{S.value=!1}}}return(i,s)=>(c(),n(b,{title:`宝塔 · SSL 证书`,subtitle:`站点证书查看 / 上传 / Let's EncryptC15`},{default:l(()=>[o(e,{headers:T,items:C.value,loading:S.value,density:`compact`},{"item.ssl":l(({item:e})=>[a(`span`,null,d(O(e)),1)]),"item.actions":l(({item:e})=>[o(r,{size:`x-small`,variant:`text`,onClick:t=>k(e)},{default:l(()=>[...s[3]||=[t(`申请 LE`,-1)]]),_:1},8,[`onClick`])]),_:1},8,[`items`,`loading`]),o(g,{class:`mt-4`,variant:`accordion`},{default:l(()=>[o(h,{title:`上传证书(SetSSL`},{default:l(()=>[o(m,null,{default:l(()=>[o(f,{modelValue:w.siteName,"onUpdate:modelValue":s[0]||=e=>w.siteName=e,label:`网站名`,density:`compact`,class:`mb-2`},null,8,[`modelValue`]),o(p,{modelValue:w.csr,"onUpdate:modelValue":s[1]||=e=>w.csr=e,label:`证书 PEM`,rows:`4`,class:`mb-2`},null,8,[`modelValue`]),o(p,{modelValue:w.key,"onUpdate:modelValue":s[2]||=e=>w.key=e,label:`私钥 PEM`,rows:`4`,class:`mb-2`},null,8,[`modelValue`]),o(r,{color:`primary`,loading:S.value,onClick:A},{default:l(()=>[...s[4]||=[t(`上传`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1})]),_:1}))}});export{S as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{Nr as e,Or as t,Wr as n,ln as r,qr as i}from"./index-CDcgx9V8.js";var a=e({name:`CredentialsPage`,__name:`CredentialsPage`,setup(e){let a=r();return n(()=>{a.replace({path:`/servers`,query:{credentials:`1`}})}),(e,n)=>(i(),t(`div`))}});export{a as default};
@@ -0,0 +1 @@
import{Nr as e,Or as t,Wr as n,ln as r,qr as i}from"./index-a0fi5f-0.js";var a=e({name:`CredentialsPage`,__name:`CredentialsPage`,setup(e){let a=r();return n(()=>{a.replace({path:`/servers`,query:{credentials:`1`}})}),(e,n)=>(i(),t(`div`))}});export{a as default};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.fleet-kpi-card[data-v-abbdd903]{border-left-style:solid!important;border-left-width:3px!important}.fleet-kpi-card--cpu[data-v-abbdd903]{border-left-color:rgb(var(--v-theme-primary))!important}.fleet-kpi-card--mem[data-v-abbdd903]{border-left-color:rgb(var(--v-theme-warning))!important}.fleet-kpi-card--disk[data-v-abbdd903]{border-left-color:#9c27b0!important}.fleet-kpi-card--online[data-v-abbdd903]{border-left-color:rgb(var(--v-theme-success))!important}.fleet-trends-card[data-v-3da01bdd] .v-card-item{padding-bottom:8px}.fleet-trend-table[data-v-3da01bdd] th{white-space:nowrap}.dashboard-root[data-v-d7edfa9b]{min-width:0;max-width:100%}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.fleet-echart[data-v-a04be566]{width:100%}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/DashboardPage-DOc8f-2E.js","assets/_plugin-vue_export-helper-BDNMzG2s.js","assets/index-a0fi5f-0.js","assets/chunk-DK3Fl9T5.js","assets/index-BNJe_ogF.css","assets/VCard-BwwttBcf.js","assets/VCard-BCYbpv-w.css","assets/VSelect-3h-EI23Y.js","assets/VTextField-GUQr1tUN.js","assets/VTextField-Bl91kSVs.css","assets/VSelect-BmvHN1-i.css","assets/VDataTable-pRRodit6.js","assets/VTable-w1sxrmvd.js","assets/VTable-Dq_7MYoa.css","assets/VDataTable-D61zAwkI.css","assets/VDialog-BFQGuuXw.js","assets/VDialog-DP-kc9Gx.css","assets/VRow-qjxp2VJ5.js","assets/VRow-DIr3-SqU.css","assets/VSkeletonLoader-BW_kZu-c.js","assets/VSkeletonLoader-CkAIsbz1.css","assets/StatCardsRow-CWt_wcFU.js","assets/StatCardsRow-snFLCRnD.css","assets/usePageActivateRefresh-BntaOABK.js","assets/useSnackbar-u_XK4ZNm.js","assets/auditNormalize-xrm2MK_4.js","assets/status-XDgjPQrL.js","assets/DashboardPage-DmG38yGr.css"])))=>i.map(i=>d[i]);
import{$t as e,Ar as t,Dr as n,Er as r,Gr as i,I as a,L as o,Nr as s,Or as c,Qt as ee,Tr as l,Wr as u,b as d,bi as f,br as p,cn as m,en as h,in as g,jr as _,ln as v,qr as y,ri as b,ti as x,tn as S,ui as C,wr as w,xi as T}from"./index-a0fi5f-0.js";import{t as E}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as D}from"./VCard-BwwttBcf.js";import{t as O}from"./VTextField-GUQr1tUN.js";import{t as k}from"./VForm-CAvEgM-c.js";var A={class:`login-page`},j={key:0,class:`login-center`},te={key:2,class:`text-center mt-4`},M=E(s({name:`LoginPage`,__name:`LoginPage`,setup(s){let E=m(),M=v(),N=e(),P=C(`checking`),F=C(``),I=C(``),L=C(``),R=C(!1),z=C(!1),B=C(``),V=C(!1),H=C(0),U=C(null),W=C([]),G=C(0),K=null,q=w(()=>P.value===`checking`?{backgroundColor:`#0a0a12`}:W.value.length===0?{backgroundImage:`linear-gradient(135deg, #1a237e 0%, #0d47a1 40%, #1565c0 100%)`}:{backgroundImage:`url(${W.value[G.value]})`});async function J(){try{let e=await g.get(`/settings/bing-wallpapers`);e?.urls?.length&&(W.value=e.urls,G.value=Math.floor(Math.random()*e.urls.length))}catch{}}function Y(){K&&clearInterval(K),!(W.value.length<=1)&&(K=setInterval(()=>{G.value=(G.value+1)%W.value.length},36e5))}async function X(){P.value=`checking`;try{return await g.get(`/auth/login-access`),P.value=`form`,!0}catch{return P.value=`blocked`,!1}}function Z(){P.value===`form`&&ee(()=>import(`./DashboardPage-DOc8f-2E.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]))}x(P,e=>{e===`form`&&Z()}),u(async()=>{await X(),await J(),Y()}),i(()=>{K&&clearInterval(K),$&&clearInterval($)});let Q=C(Date.now()),$=null,ne=w(()=>{if(!U.value)return 0;let e=new Date(U.value).getTime()-Q.value;return Math.max(0,Math.ceil(e/6e4))});x(U,e=>{$&&=(clearInterval($),null),e&&($=setInterval(()=>{Q.value=Date.now()},3e4))});async function re(){if(!F.value||!I.value){B.value=`请输入用户名和密码`;return}z.value=!0,B.value=``;try{await N.login(F.value,I.value,V.value?L.value:void 0);let e=E.query.redirect||`/`,t=e.startsWith(`/`)&&!e.startsWith(`//`)?e:`/`;await M.replace(t)}catch(e){if(e instanceof S)V.value=!0,B.value=e.message;else if(e instanceof h){if(e.status===403){P.value=`blocked`;return}e.status===429?(U.value=new Date(Date.now()+900*1e3).toISOString(),B.value=`登录尝试过多,账户已锁定 15 分钟`):(H.value++,B.value=e.message)}else B.value=`网络错误,请重试`}finally{z.value=!1}}return(e,i)=>(y(),c(`div`,A,[l(`div`,{class:`wallpaper-bg`,style:f(q.value)},null,4),i[6]||=l(`div`,{class:`wallpaper-overlay`},null,-1),P.value===`form`?(y(),c(`div`,j,[_(D,{elevation:`0`,rounded:`xl`,class:`login-card pa-10`,border:``},{default:b(()=>[B.value?(y(),r(a,{key:0,type:`error`,density:`compact`,class:`mb-4`,closable:``,"onClick:close":i[0]||=e=>B.value=``,rounded:`lg`,variant:`tonal`},{default:b(()=>[t(T(B.value),1)]),_:1})):n(``,!0),U.value?(y(),r(a,{key:1,type:`warning`,density:`compact`,class:`mb-4`,rounded:`lg`,variant:`tonal`},{default:b(()=>[t(` 账户已锁定,请 `+T(ne.value)+` 分钟后重试 `,1)]),_:1})):n(``,!0),_(k,{onSubmit:p(re,[`prevent`])},{default:b(()=>[_(O,{modelValue:F.value,"onUpdate:modelValue":i[1]||=e=>F.value=e,label:`用户名`,variant:`outlined`,density:`comfortable`,"prepend-inner-icon":`mdi-account-outline`,disabled:z.value,class:`mb-4`,autocomplete:`username`,rounded:`lg`,"hide-details":`auto`},null,8,[`modelValue`,`disabled`]),_(O,{modelValue:I.value,"onUpdate:modelValue":i[2]||=e=>I.value=e,label:`密码`,variant:`outlined`,density:`comfortable`,"prepend-inner-icon":`mdi-lock-outline`,"append-inner-icon":R.value?`mdi-eye`:`mdi-eye-off`,type:R.value?`text`:`password`,"onClick:appendInner":i[3]||=e=>R.value=!R.value,disabled:z.value,class:`mb-4`,autocomplete:`current-password`,rounded:`lg`,"hide-details":`auto`},null,8,[`modelValue`,`append-inner-icon`,`type`,`disabled`]),V.value?(y(),r(O,{key:0,modelValue:L.value,"onUpdate:modelValue":i[4]||=e=>L.value=e,label:`TOTP 验证码`,variant:`outlined`,density:`comfortable`,"prepend-inner-icon":`mdi-shield-key-outline`,disabled:z.value,class:`mb-4`,autocomplete:`one-time-code`,rounded:`lg`,"hide-details":`auto`},null,8,[`modelValue`,`disabled`])):n(``,!0),_(o,{type:`submit`,color:`primary`,variant:`flat`,block:``,size:`large`,loading:z.value,disabled:!!U.value,rounded:`lg`,class:`mt-2`,height:`48`},{default:b(()=>[...i[5]||=[t(` 登录 `,-1)]]),_:1},8,[`loading`,`disabled`])]),_:1}),H.value>0&&!U.value?(y(),c(`div`,te,[_(d,{size:`small`,variant:`tonal`,color:`warning`,label:``},{default:b(()=>[t(` 已失败 `+T(H.value)+``,1)]),_:1})])):n(``,!0)]),_:1})])):n(``,!0)]))}}),[[`__scopeId`,`data-v-5c1d22f2`]]);export{M as default};
+2
View File
@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/DashboardPage-yElaw2BV.js","assets/_plugin-vue_export-helper-BDNMzG2s.js","assets/index-CDcgx9V8.js","assets/chunk-DK3Fl9T5.js","assets/index-BNJe_ogF.css","assets/VCard-pmpQR9O7.js","assets/VCard-BCYbpv-w.css","assets/VSelect-BvR-yyLS.js","assets/VTextField-B-BnrJAQ.js","assets/VTextField-Bl91kSVs.css","assets/VSelect-BmvHN1-i.css","assets/VDataTable-ClfE0-sD.js","assets/VTable-BTftprIQ.js","assets/VTable-Dq_7MYoa.css","assets/VDataTable-D61zAwkI.css","assets/VDialog-nKJcjfNL.js","assets/VDialog-DP-kc9Gx.css","assets/VRow-rmSa_q-C.js","assets/VRow-DIr3-SqU.css","assets/VSkeletonLoader-gL4kF19X.js","assets/VSkeletonLoader-CkAIsbz1.css","assets/StatCardsRow-CdfBB9tI.js","assets/StatCardsRow-snFLCRnD.css","assets/usePageActivateRefresh-CqHSpagO.js","assets/useSnackbar-u_XK4ZNm.js","assets/auditNormalize-xrm2MK_4.js","assets/status-BrGQYcd-.js","assets/DashboardPage-DmG38yGr.css"])))=>i.map(i=>d[i]);
import{$t as e,Ar as t,Dr as n,Er as r,Gr as i,I as a,L as o,Nr as s,Or as c,Qt as ee,Tr as l,Wr as u,b as d,bi as f,br as p,cn as m,en as h,in as g,jr as _,ln as v,qr as y,ri as b,ti as x,tn as S,ui as C,wr as w,xi as T}from"./index-CDcgx9V8.js";import{t as E}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as D}from"./VCard-pmpQR9O7.js";import{t as O}from"./VTextField-B-BnrJAQ.js";import{t as k}from"./VForm-uEqKaHHF.js";var A={class:`login-page`},j={key:0,class:`login-center`},te={key:2,class:`text-center mt-4`},M=E(s({name:`LoginPage`,__name:`LoginPage`,setup(s){let E=m(),M=v(),N=e(),P=C(`checking`),F=C(``),I=C(``),L=C(``),R=C(!1),z=C(!1),B=C(``),V=C(!1),H=C(0),U=C(null),W=C([]),G=C(0),K=null,q=w(()=>P.value===`checking`?{backgroundColor:`#0a0a12`}:W.value.length===0?{backgroundImage:`linear-gradient(135deg, #1a237e 0%, #0d47a1 40%, #1565c0 100%)`}:{backgroundImage:`url(${W.value[G.value]})`});async function J(){try{let e=await g.get(`/settings/bing-wallpapers`);e?.urls?.length&&(W.value=e.urls,G.value=Math.floor(Math.random()*e.urls.length))}catch{}}function Y(){K&&clearInterval(K),!(W.value.length<=1)&&(K=setInterval(()=>{G.value=(G.value+1)%W.value.length},36e5))}async function X(){P.value=`checking`;try{return await g.get(`/auth/login-access`),P.value=`form`,!0}catch{return P.value=`blocked`,!1}}function Z(){P.value===`form`&&ee(()=>import(`./DashboardPage-yElaw2BV.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]))}x(P,e=>{e===`form`&&Z()}),u(async()=>{await X(),await J(),Y()}),i(()=>{K&&clearInterval(K),$&&clearInterval($)});let Q=C(Date.now()),$=null,ne=w(()=>{if(!U.value)return 0;let e=new Date(U.value).getTime()-Q.value;return Math.max(0,Math.ceil(e/6e4))});x(U,e=>{$&&=(clearInterval($),null),e&&($=setInterval(()=>{Q.value=Date.now()},3e4))});async function re(){if(!F.value||!I.value){B.value=`请输入用户名和密码`;return}z.value=!0,B.value=``;try{await N.login(F.value,I.value,V.value?L.value:void 0);let e=E.query.redirect||`/`,t=e.startsWith(`/`)&&!e.startsWith(`//`)?e:`/`;await M.replace(t)}catch(e){if(e instanceof S)V.value=!0,B.value=e.message;else if(e instanceof h){if(e.status===403){P.value=`blocked`;return}e.status===429?(U.value=new Date(Date.now()+900*1e3).toISOString(),B.value=`登录尝试过多,账户已锁定 15 分钟`):(H.value++,B.value=e.message)}else B.value=`网络错误,请重试`}finally{z.value=!1}}return(e,i)=>(y(),c(`div`,A,[l(`div`,{class:`wallpaper-bg`,style:f(q.value)},null,4),i[6]||=l(`div`,{class:`wallpaper-overlay`},null,-1),P.value===`form`?(y(),c(`div`,j,[_(D,{elevation:`0`,rounded:`xl`,class:`login-card pa-10`,border:``},{default:b(()=>[B.value?(y(),r(a,{key:0,type:`error`,density:`compact`,class:`mb-4`,closable:``,"onClick:close":i[0]||=e=>B.value=``,rounded:`lg`,variant:`tonal`},{default:b(()=>[t(T(B.value),1)]),_:1})):n(``,!0),U.value?(y(),r(a,{key:1,type:`warning`,density:`compact`,class:`mb-4`,rounded:`lg`,variant:`tonal`},{default:b(()=>[t(` 账户已锁定,请 `+T(ne.value)+` 分钟后重试 `,1)]),_:1})):n(``,!0),_(k,{onSubmit:p(re,[`prevent`])},{default:b(()=>[_(O,{modelValue:F.value,"onUpdate:modelValue":i[1]||=e=>F.value=e,label:`用户名`,variant:`outlined`,density:`comfortable`,"prepend-inner-icon":`mdi-account-outline`,disabled:z.value,class:`mb-4`,autocomplete:`username`,rounded:`lg`,"hide-details":`auto`},null,8,[`modelValue`,`disabled`]),_(O,{modelValue:I.value,"onUpdate:modelValue":i[2]||=e=>I.value=e,label:`密码`,variant:`outlined`,density:`comfortable`,"prepend-inner-icon":`mdi-lock-outline`,"append-inner-icon":R.value?`mdi-eye`:`mdi-eye-off`,type:R.value?`text`:`password`,"onClick:appendInner":i[3]||=e=>R.value=!R.value,disabled:z.value,class:`mb-4`,autocomplete:`current-password`,rounded:`lg`,"hide-details":`auto`},null,8,[`modelValue`,`append-inner-icon`,`type`,`disabled`]),V.value?(y(),r(O,{key:0,modelValue:L.value,"onUpdate:modelValue":i[4]||=e=>L.value=e,label:`TOTP 验证码`,variant:`outlined`,density:`comfortable`,"prepend-inner-icon":`mdi-shield-key-outline`,disabled:z.value,class:`mb-4`,autocomplete:`one-time-code`,rounded:`lg`,"hide-details":`auto`},null,8,[`modelValue`,`disabled`])):n(``,!0),_(o,{type:`submit`,color:`primary`,variant:`flat`,block:``,size:`large`,loading:z.value,disabled:!!U.value,rounded:`lg`,class:`mt-2`,height:`48`},{default:b(()=>[...i[5]||=[t(` 登录 `,-1)]]),_:1},8,[`loading`,`disabled`])]),_:1}),H.value>0&&!U.value?(y(),c(`div`,te,[_(d,{size:`small`,variant:`tonal`,color:`warning`,label:``},{default:b(()=>[t(` 已失败 `+T(H.value)+``,1)]),_:1})])):n(``,!0)]),_:1})])):n(``,!0)]))}}),[[`__scopeId`,`data-v-5c1d22f2`]]);export{M as default};
+1
View File
@@ -0,0 +1 @@
.login-page[data-v-5c1d22f2]{width:100%;min-height:100vh;position:relative;overflow:hidden}.wallpaper-bg[data-v-5c1d22f2]{z-index:0;background-position:50%;background-repeat:no-repeat;background-size:cover;transition:background-image 1.5s ease-in-out;position:fixed;inset:0}.wallpaper-overlay[data-v-5c1d22f2]{z-index:1;background:linear-gradient(135deg,#00000073 0%,#0003 100%);position:fixed;inset:0}.login-center[data-v-5c1d22f2]{z-index:2;justify-content:center;align-items:center;min-height:100vh;padding:24px;display:flex;position:relative}.login-card[data-v-5c1d22f2]{-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);width:100%;max-width:420px;background:rgba(var(--v-theme-surface), .92)!important}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.push-history-server-name[data-v-0faa7c2b]{text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;max-width:200px;display:inline-block;overflow:hidden}.push-preview-server-name[data-v-659fdff5]{text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;max-width:220px;display:inline-block;overflow:hidden}.push-progress-name[data-v-644b2e4c]{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.push-server-card__name[data-v-41a27b92]{-webkit-line-clamp:2;word-break:break-word;-webkit-box-orient:vertical;flex:auto;min-width:0;line-height:1.25;display:-webkit-box;overflow:hidden}.push-server-card__line[data-v-41a27b92]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.push-server-list__col-check[data-v-41a27b92]{width:44px}.push-server-list__col-online[data-v-41a27b92]{width:28px}.push-server-list__col-status[data-v-41a27b92]{width:96px}.push-server-list__row--selected[data-v-41a27b92]{background:rgba(var(--v-theme-primary), .08)}.push-server-list__name-cell[data-v-41a27b92]{word-break:break-word;width:32%;max-width:0}.push-server-list__ellipsis[data-v-41a27b92]{text-overflow:ellipsis;white-space:nowrap;max-width:0;overflow:hidden}.push-server-list[data-v-41a27b92] th:nth-child(4),.push-server-list[data-v-41a27b92] td:nth-child(4){width:24%}.push-server-list[data-v-41a27b92] th:nth-child(5),.push-server-list[data-v-41a27b92] td:nth-child(5){width:28%}@layer vuetify-components{.v-radio-group>.v-input__control{flex-direction:column}.v-radio-group>.v-input__control>.v-label{margin-inline-start:16px}.v-radio-group>.v-input__control>.v-label+.v-selection-control-group{margin-top:8px;padding-inline-start:6px}}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{_ as e,t}from"./VSelect-BvR-yyLS.js";import{Ar as n,Dr as r,Er as i,L as a,Nr as o,Tr as s,Xt as c,_i as l,b as u,in as d,jr as f,qr as p,ri as m,ui as h,v as g,xi as _}from"./index-CDcgx9V8.js";import{t as v}from"./usePageActivateRefresh-CqHSpagO.js";import{t as y}from"./useSnackbar-u_XK4ZNm.js";import{n as b}from"./dataTable-esDY4cOk.js";import{i as x,n as S,o as C,t as w}from"./VCard-pmpQR9O7.js";import{t as T}from"./VDataTableServer-BIMNqMvk.js";import{t as E}from"./VDialog-nKJcjfNL.js";import{t as D}from"./usePageAutoRefresh-DvG6Fd0_.js";var O={class:`font-weight-medium text-truncate d-inline-block`,style:{"max-width":`240px`}},k={class:`text-medium-emphasis`},A={class:`text-medium-emphasis`},j={class:`text-caption text-medium-emphasis`},M={class:`text-caption text-medium-emphasis`},N=o({name:`RetriesPage`,__name:`RetriesPage`,setup(o){let N=y(),P=h(!1),F=h([]),I=h(0),L=h(1),R=h(null),z=h(!1),B=h(null),V=[{label:`待重试`,value:`pending`},{label:`重试中`,value:`retrying`},{label:`成功`,value:`success`},{label:`失败`,value:`failed`}],H=b([{title:`状态`,key:`status`,width:100},{title:`任务`,key:`operation`},{title:`服务器`,key:`server_name`},{title:`次数`,key:`retry_count`,width:100},{title:`下次重试`,key:`next_retry_at`,width:140},{title:`创建时间`,key:`created_at`,width:160},{title:``,key:`actions`,width:120,align:`end`}]);async function U(e=!1){e||(P.value=!0);try{let e=(await d.getList(`/retries/`,{limit:200})).items||[];R.value&&(e=e.filter(e=>e.status===R.value)),I.value=e.length;let t=(L.value-1)*20;F.value=e.slice(t,t+20)}catch(t){e||(F.value=[],I.value=0,N(t instanceof Error?t.message:`加载重试队列失败`,`error`))}finally{e||(P.value=!1)}}async function W(e){try{await d.post(`/retries/${e.id}/retry`),U(),N(`重试已触发`)}catch(e){N(e.message||`操作失败`,`error`)}}function G(e){B.value=e.id,z.value=!0}async function K(){if(B.value)try{await d.delete(`/retries/${B.value}`),z.value=!1,U(),N(`已删除`)}catch(e){N(e.message||`删除失败`,`error`)}}function q(e){return e===`success`?`success`:e===`failed`?`error`:e===`retrying`?`warning`:`info`}function J(e){return e===`pending`?`待重试`:e===`retrying`?`重试中`:e===`success`?`成功`:e===`failed`?`失败`:e}function Y(e){return e.source_path&&e.target_path?`${e.source_path}${e.target_path}`:e.source_path||e.target_path||`文件推送`}return D(()=>U(!0),15e3),v(e=>U(e)),(o,d)=>(p(),i(e,{fluid:``,class:`pa-6`},{default:m(()=>[f(w,{elevation:`0`,border:``,rounded:`lg`},{default:m(()=>[f(x,{class:`d-flex align-center text-h6`},{default:m(()=>[d[5]||=n(` 重试队列 `,-1),f(g),f(t,{modelValue:R.value,"onUpdate:modelValue":[d[0]||=e=>R.value=e,d[1]||=e=>{L.value=1,U()}],items:V,"item-title":`label`,"item-value":`value`,label:`状态筛选`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`160px`},clearable:``},null,8,[`modelValue`])]),_:1}),f(T,{items:F.value,headers:l(H),"items-length":I.value,loading:P.value,page:L.value,"items-per-page":20,hover:``,density:`comfortable`,"onUpdate:page":d[2]||=e=>{L.value=e,U()}},{"item.status":m(({item:e})=>[f(u,{color:q(e.status),size:`x-small`,variant:`tonal`,label:``,border:`sm`},{default:m(()=>[n(_(J(e.status)),1)]),_:2},1032,[`color`])]),"item.operation":m(({item:e})=>[s(`span`,O,_(Y(e)),1)]),"item.server_name":m(({item:e})=>[s(`span`,k,_(e.server_name||``),1)]),"item.retry_count":m(({item:e})=>[s(`span`,A,_(e.retry_count||0)+` / `+_(e.max_retries||3),1)]),"item.next_retry_at":m(({item:e})=>[s(`span`,j,_(l(c)(e.next_retry_at)),1)]),"item.created_at":m(({item:e})=>[s(`span`,M,_(l(c)(e.created_at)),1)]),"item.actions":m(({item:e})=>[e.status===`pending`||e.status===`failed`?(p(),i(a,{key:0,variant:`text`,size:`x-small`,color:`primary`,density:`compact`,onClick:t=>W(e)},{default:m(()=>[...d[6]||=[n(` 重试 `,-1)]]),_:1},8,[`onClick`])):r(``,!0),f(a,{variant:`text`,size:`x-small`,color:`error`,density:`compact`,onClick:t=>G(e)},{default:m(()=>[...d[7]||=[n(` 删除 `,-1)]]),_:1},8,[`onClick`])]),"no-data":m(()=>[...d[8]||=[s(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无数据`,-1)]]),_:1},8,[`items`,`headers`,`items-length`,`loading`,`page`])]),_:1}),f(E,{modelValue:z.value,"onUpdate:modelValue":d[4]||=e=>z.value=e,"max-width":`400`},{default:m(()=>[f(w,{border:``},{default:m(()=>[f(x,null,{default:m(()=>[...d[9]||=[n(`确认删除`,-1)]]),_:1}),f(S,null,{default:m(()=>[...d[10]||=[n(`确定要删除此重试任务吗?`,-1)]]),_:1}),f(C,null,{default:m(()=>[f(g),f(a,{variant:`text`,onClick:d[3]||=e=>z.value=!1},{default:m(()=>[...d[11]||=[n(`取消`,-1)]]),_:1}),f(a,{color:`error`,variant:`flat`,onClick:K},{default:m(()=>[...d[12]||=[n(`删除`,-1)]]),_:1})]),_:1})]),_:1})]),_:1},8,[`modelValue`])]),_:1}))}});export{N as default};
+1
View File
@@ -0,0 +1 @@
import{_ as e,t}from"./VSelect-3h-EI23Y.js";import{Ar as n,Dr as r,Er as i,L as a,Nr as o,Tr as s,Xt as c,_i as l,b as u,in as d,jr as f,qr as p,ri as m,ui as h,v as g,xi as _}from"./index-a0fi5f-0.js";import{t as v}from"./usePageActivateRefresh-BntaOABK.js";import{t as y}from"./useSnackbar-u_XK4ZNm.js";import{n as b}from"./dataTable-esDY4cOk.js";import{i as x,n as S,o as C,t as w}from"./VCard-BwwttBcf.js";import{t as T}from"./VDataTableServer-BysXbZ8p.js";import{t as E}from"./VDialog-BFQGuuXw.js";import{t as D}from"./usePageAutoRefresh-DW8DyVQQ.js";var O={class:`font-weight-medium text-truncate d-inline-block`,style:{"max-width":`240px`}},k={class:`text-medium-emphasis`},A={class:`text-medium-emphasis`},j={class:`text-caption text-medium-emphasis`},M={class:`text-caption text-medium-emphasis`},N=o({name:`RetriesPage`,__name:`RetriesPage`,setup(o){let N=y(),P=h(!1),F=h([]),I=h(0),L=h(1),R=h(null),z=h(!1),B=h(null),V=[{label:`待重试`,value:`pending`},{label:`重试中`,value:`retrying`},{label:`成功`,value:`success`},{label:`失败`,value:`failed`}],H=b([{title:`状态`,key:`status`,width:100},{title:`任务`,key:`operation`},{title:`服务器`,key:`server_name`},{title:`次数`,key:`retry_count`,width:100},{title:`下次重试`,key:`next_retry_at`,width:140},{title:`创建时间`,key:`created_at`,width:160},{title:``,key:`actions`,width:120,align:`end`}]);async function U(e=!1){e||(P.value=!0);try{let e=(await d.getList(`/retries/`,{limit:200})).items||[];R.value&&(e=e.filter(e=>e.status===R.value)),I.value=e.length;let t=(L.value-1)*20;F.value=e.slice(t,t+20)}catch(t){e||(F.value=[],I.value=0,N(t instanceof Error?t.message:`加载重试队列失败`,`error`))}finally{e||(P.value=!1)}}async function W(e){try{await d.post(`/retries/${e.id}/retry`),U(),N(`重试已触发`)}catch(e){N(e.message||`操作失败`,`error`)}}function G(e){B.value=e.id,z.value=!0}async function K(){if(B.value)try{await d.delete(`/retries/${B.value}`),z.value=!1,U(),N(`已删除`)}catch(e){N(e.message||`删除失败`,`error`)}}function q(e){return e===`success`?`success`:e===`failed`?`error`:e===`retrying`?`warning`:`info`}function J(e){return e===`pending`?`待重试`:e===`retrying`?`重试中`:e===`success`?`成功`:e===`failed`?`失败`:e}function Y(e){return e.source_path&&e.target_path?`${e.source_path}${e.target_path}`:e.source_path||e.target_path||`文件推送`}return D(()=>U(!0),15e3),v(e=>U(e)),(o,d)=>(p(),i(e,{fluid:``,class:`pa-6`},{default:m(()=>[f(w,{elevation:`0`,border:``,rounded:`lg`},{default:m(()=>[f(x,{class:`d-flex align-center text-h6`},{default:m(()=>[d[5]||=n(` 重试队列 `,-1),f(g),f(t,{modelValue:R.value,"onUpdate:modelValue":[d[0]||=e=>R.value=e,d[1]||=e=>{L.value=1,U()}],items:V,"item-title":`label`,"item-value":`value`,label:`状态筛选`,variant:`outlined`,density:`compact`,"hide-details":``,style:{"max-width":`160px`},clearable:``},null,8,[`modelValue`])]),_:1}),f(T,{items:F.value,headers:l(H),"items-length":I.value,loading:P.value,page:L.value,"items-per-page":20,hover:``,density:`comfortable`,"onUpdate:page":d[2]||=e=>{L.value=e,U()}},{"item.status":m(({item:e})=>[f(u,{color:q(e.status),size:`x-small`,variant:`tonal`,label:``,border:`sm`},{default:m(()=>[n(_(J(e.status)),1)]),_:2},1032,[`color`])]),"item.operation":m(({item:e})=>[s(`span`,O,_(Y(e)),1)]),"item.server_name":m(({item:e})=>[s(`span`,k,_(e.server_name||``),1)]),"item.retry_count":m(({item:e})=>[s(`span`,A,_(e.retry_count||0)+` / `+_(e.max_retries||3),1)]),"item.next_retry_at":m(({item:e})=>[s(`span`,j,_(l(c)(e.next_retry_at)),1)]),"item.created_at":m(({item:e})=>[s(`span`,M,_(l(c)(e.created_at)),1)]),"item.actions":m(({item:e})=>[e.status===`pending`||e.status===`failed`?(p(),i(a,{key:0,variant:`text`,size:`x-small`,color:`primary`,density:`compact`,onClick:t=>W(e)},{default:m(()=>[...d[6]||=[n(` 重试 `,-1)]]),_:1},8,[`onClick`])):r(``,!0),f(a,{variant:`text`,size:`x-small`,color:`error`,density:`compact`,onClick:t=>G(e)},{default:m(()=>[...d[7]||=[n(` 删除 `,-1)]]),_:1},8,[`onClick`])]),"no-data":m(()=>[...d[8]||=[s(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无数据`,-1)]]),_:1},8,[`items`,`headers`,`items-length`,`loading`,`page`])]),_:1}),f(E,{modelValue:z.value,"onUpdate:modelValue":d[4]||=e=>z.value=e,"max-width":`400`},{default:m(()=>[f(w,{border:``},{default:m(()=>[f(x,null,{default:m(()=>[...d[9]||=[n(`确认删除`,-1)]]),_:1}),f(S,null,{default:m(()=>[...d[10]||=[n(`确定要删除此重试任务吗?`,-1)]]),_:1}),f(C,null,{default:m(()=>[f(g),f(a,{variant:`text`,onClick:d[3]||=e=>z.value=!1},{default:m(()=>[...d[11]||=[n(`取消`,-1)]]),_:1}),f(a,{color:`error`,variant:`flat`,onClick:K},{default:m(()=>[...d[12]||=[n(`删除`,-1)]]),_:1})]),_:1})]),_:1})]),_:1},8,[`modelValue`])]),_:1}))}});export{N as default};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.cycle-type-col[data-v-88b715a3]{flex:none}.cycle-type-select[data-v-88b715a3]{width:7.5rem;max-width:7.5rem}.cycle-type-fixed[data-v-88b715a3]{white-space:nowrap;align-items:center;height:40px;padding:0 4px;font-weight:500;display:inline-flex}.time-col[data-v-88b715a3]{flex:none;width:4.5rem;max-width:4.5rem}.time-col-wide[data-v-88b715a3]{flex:none;width:5.5rem;max-width:5.5rem}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.exec-detail-row--failed[data-v-aa0343c7]{background:rgba(var(--v-theme-error), .06)}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.script-content-editor[data-v-8c9e5994]{border:1px solid rgba(var(--v-border-color), var(--v-border-opacity));border-radius:8px;height:360px;overflow:hidden}.exec-detail-row--failed[data-v-990c5229]{background:rgba(var(--v-theme-error), .08)}.exec-detail-row--active[data-v-990c5229]{background:rgba(var(--v-theme-primary), .06)}.exec-detail-output[data-v-990c5229]{white-space:pre-wrap;word-break:break-word;max-height:240px}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-qjxp2VJ5.js";import{$ as n,$r as r,Ar as i,Dr as a,Er as o,Ir as s,L as c,Nr as l,Or as u,Q as d,Tr as f,Yr as p,_i as m,b as h,bi as g,jr as _,qr as v,ri as y,ui as b,wr as x,xi as S,xr as C}from"./index-a0fi5f-0.js";import{t as w}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as T}from"./VCard-BwwttBcf.js";import{t as E}from"./VCheckbox-RnLQ4WBr.js";import{t as D}from"./VTextField-GUQr1tUN.js";import{o as O}from"./labels-C-A2d08W.js";import{i as k}from"./useServerCategories-BcvoZF0H.js";var A={class:`server-category-picker`},j={class:`d-flex align-center flex-wrap ga-2 mb-2`},M={key:0,class:`text-center text-medium-emphasis py-4`},N={class:`d-flex align-center px-1 mb-1`},P={class:`text-caption text-medium-emphasis ml-1`},F={class:`d-flex align-center ga-2`},I={class:`text-body-2 font-weight-medium text-truncate`},L={class:`text-caption text-medium-emphasis text-truncate`},R=w(l({__name:`ServerCategoryPicker`,props:s({servers:{},loading:{type:Boolean,default:!1},maxHeight:{default:`280px`}},{modelValue:{default:()=>[]},modelModifiers:{}}),emits:[`update:modelValue`],setup(s){let l=r(s,`modelValue`),w=s,R=b(``),z=x(()=>new Set(l.value)),B=x(()=>{let e=R.value.trim().toLowerCase();return e?w.servers.filter(t=>t.name.toLowerCase().includes(e)||(t.domain||``).toLowerCase().includes(e)||(t.category||``).toLowerCase().includes(e)):w.servers}),V=x(()=>k(B.value).map(e=>({...e,servers:[...e.servers].sort((e,t)=>e.name.localeCompare(t.name,`zh`))}))),H=x(()=>{let e=B.value;return e.length>0&&e.every(e=>z.value.has(e.id))});function U(e){return e.servers.filter(e=>z.value.has(e.id)).length}function W(e){let t=V.value.find(t=>t.category===e);return!t||t.servers.length===0?!1:t.servers.every(e=>z.value.has(e.id))}function G(e){l.value=[...e]}function K(e){let t=new Set(l.value);t.has(e)?t.delete(e):t.add(e),G(t)}function q(e){let t=V.value.find(t=>t.category===e);if(!t)return;let n=new Set(l.value),r=t.servers.every(e=>n.has(e.id));for(let e of t.servers)r?n.delete(e.id):n.add(e.id);G(n)}function J(){let e=B.value,t=new Set(l.value),n=e.length>0&&e.every(e=>t.has(e.id));for(let r of e)n?t.delete(r.id):t.add(r.id);G(t)}return(r,b)=>(v(),u(`div`,A,[f(`div`,j,[_(E,{"model-value":H.value,label:`全选`,density:`compact`,"hide-details":``,"onUpdate:modelValue":J},null,8,[`model-value`]),_(D,{modelValue:R.value,"onUpdate:modelValue":b[0]||=e=>R.value=e,"prepend-inner-icon":`mdi-magnify`,label:`搜索服务器`,density:`compact`,"hide-details":``,variant:`outlined`,rounded:``,style:{"max-width":`220px`},clearable:``},null,8,[`modelValue`]),l.value.length>0?(v(),o(h,{key:0,size:`small`,color:`primary`,variant:`tonal`,label:``},{default:y(()=>[i(` 已选 `+S(l.value.length)+``,1)]),_:1})):a(``,!0),s.loading?(v(),o(d,{key:1,indeterminate:``,size:`18`,width:`2`})):a(``,!0)]),f(`div`,{class:`picker-scroll`,style:g({maxHeight:s.maxHeight})},[!s.loading&&V.value.length===0?(v(),u(`div`,M,` 暂无服务器 `)):a(``,!0),(v(!0),u(C,null,p(V.value,r=>(v(),u(`div`,{key:r.category,class:`mb-3`},[f(`div`,N,[_(c,{size:`x-small`,variant:`text`,color:W(r.category)?`primary`:`default`,onClick:e=>q(r.category)},{default:y(()=>[_(n,{size:`16`,class:`mr-1`},{default:y(()=>[i(S(W(r.category)?`mdi-checkbox-marked`:`mdi-checkbox-blank-outline`),1)]),_:2},1024),i(` `+S(r.label),1)]),_:2},1032,[`color`,`onClick`]),f(`span`,P,` (`+S(U(r))+`/`+S(r.servers.length)+`) `,1)]),_(t,{dense:``},{default:y(()=>[(v(!0),u(C,null,p(r.servers,t=>(v(),o(e,{key:t.id,cols:`12`,sm:`6`},{default:y(()=>[_(T,{color:z.value.has(t.id)?`primary`:void 0,variant:z.value.has(t.id)?`tonal`:`outlined`,rounded:`lg`,class:`pa-2 cursor-pointer`,onClick:e=>K(t.id)},{default:y(()=>[f(`div`,F,[_(n,{color:m(O)(t.is_online),size:`10`},{default:y(()=>[...b[1]||=[i(`mdi-circle`,-1)]]),_:1},8,[`color`]),f(`span`,I,S(t.name),1)]),f(`div`,L,S(t.domain),1)]),_:2},1032,[`color`,`variant`,`onClick`])]),_:2},1024))),128))]),_:2},1024)]))),128))],4)]))}}),[[`__scopeId`,`data-v-55970572`]]);export{R as t};
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-rmSa_q-C.js";import{$ as n,$r as r,Ar as i,Dr as a,Er as o,Ir as s,L as c,Nr as l,Or as u,Q as d,Tr as f,Yr as p,_i as m,b as h,bi as g,jr as _,qr as v,ri as y,ui as b,wr as x,xi as S,xr as C}from"./index-CDcgx9V8.js";import{t as w}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as T}from"./VCard-pmpQR9O7.js";import{t as E}from"./VCheckbox-BO-mWtTk.js";import{t as D}from"./VTextField-B-BnrJAQ.js";import{o as O}from"./labels-C-A2d08W.js";import{i as k}from"./useServerCategories-V7luFEnW.js";var A={class:`server-category-picker`},j={class:`d-flex align-center flex-wrap ga-2 mb-2`},M={key:0,class:`text-center text-medium-emphasis py-4`},N={class:`d-flex align-center px-1 mb-1`},P={class:`text-caption text-medium-emphasis ml-1`},F={class:`d-flex align-center ga-2`},I={class:`text-body-2 font-weight-medium text-truncate`},L={class:`text-caption text-medium-emphasis text-truncate`},R=w(l({__name:`ServerCategoryPicker`,props:s({servers:{},loading:{type:Boolean,default:!1},maxHeight:{default:`280px`}},{modelValue:{default:()=>[]},modelModifiers:{}}),emits:[`update:modelValue`],setup(s){let l=r(s,`modelValue`),w=s,R=b(``),z=x(()=>new Set(l.value)),B=x(()=>{let e=R.value.trim().toLowerCase();return e?w.servers.filter(t=>t.name.toLowerCase().includes(e)||(t.domain||``).toLowerCase().includes(e)||(t.category||``).toLowerCase().includes(e)):w.servers}),V=x(()=>k(B.value).map(e=>({...e,servers:[...e.servers].sort((e,t)=>e.name.localeCompare(t.name,`zh`))}))),H=x(()=>{let e=B.value;return e.length>0&&e.every(e=>z.value.has(e.id))});function U(e){return e.servers.filter(e=>z.value.has(e.id)).length}function W(e){let t=V.value.find(t=>t.category===e);return!t||t.servers.length===0?!1:t.servers.every(e=>z.value.has(e.id))}function G(e){l.value=[...e]}function K(e){let t=new Set(l.value);t.has(e)?t.delete(e):t.add(e),G(t)}function q(e){let t=V.value.find(t=>t.category===e);if(!t)return;let n=new Set(l.value),r=t.servers.every(e=>n.has(e.id));for(let e of t.servers)r?n.delete(e.id):n.add(e.id);G(n)}function J(){let e=B.value,t=new Set(l.value),n=e.length>0&&e.every(e=>t.has(e.id));for(let r of e)n?t.delete(r.id):t.add(r.id);G(t)}return(r,b)=>(v(),u(`div`,A,[f(`div`,j,[_(E,{"model-value":H.value,label:`全选`,density:`compact`,"hide-details":``,"onUpdate:modelValue":J},null,8,[`model-value`]),_(D,{modelValue:R.value,"onUpdate:modelValue":b[0]||=e=>R.value=e,"prepend-inner-icon":`mdi-magnify`,label:`搜索服务器`,density:`compact`,"hide-details":``,variant:`outlined`,rounded:``,style:{"max-width":`220px`},clearable:``},null,8,[`modelValue`]),l.value.length>0?(v(),o(h,{key:0,size:`small`,color:`primary`,variant:`tonal`,label:``},{default:y(()=>[i(` 已选 `+S(l.value.length)+``,1)]),_:1})):a(``,!0),s.loading?(v(),o(d,{key:1,indeterminate:``,size:`18`,width:`2`})):a(``,!0)]),f(`div`,{class:`picker-scroll`,style:g({maxHeight:s.maxHeight})},[!s.loading&&V.value.length===0?(v(),u(`div`,M,` 暂无服务器 `)):a(``,!0),(v(!0),u(C,null,p(V.value,r=>(v(),u(`div`,{key:r.category,class:`mb-3`},[f(`div`,N,[_(c,{size:`x-small`,variant:`text`,color:W(r.category)?`primary`:`default`,onClick:e=>q(r.category)},{default:y(()=>[_(n,{size:`16`,class:`mr-1`},{default:y(()=>[i(S(W(r.category)?`mdi-checkbox-marked`:`mdi-checkbox-blank-outline`),1)]),_:2},1024),i(` `+S(r.label),1)]),_:2},1032,[`color`,`onClick`]),f(`span`,P,` (`+S(U(r))+`/`+S(r.servers.length)+`) `,1)]),_(t,{dense:``},{default:y(()=>[(v(!0),u(C,null,p(r.servers,t=>(v(),o(e,{key:t.id,cols:`12`,sm:`6`},{default:y(()=>[_(T,{color:z.value.has(t.id)?`primary`:void 0,variant:z.value.has(t.id)?`tonal`:`outlined`,rounded:`lg`,class:`pa-2 cursor-pointer`,onClick:e=>K(t.id)},{default:y(()=>[f(`div`,F,[_(n,{color:m(O)(t.is_online),size:`10`},{default:y(()=>[...b[1]||=[i(`mdi-circle`,-1)]]),_:1},8,[`color`]),f(`span`,I,S(t.name),1)]),f(`div`,L,S(t.domain),1)]),_:2},1032,[`color`,`variant`,`onClick`])]),_:2},1024))),128))]),_:2},1024)]))),128))],4)]))}}),[[`__scopeId`,`data-v-55970572`]]);export{R as t};
@@ -0,0 +1 @@
.picker-scroll[data-v-55970572]{padding-right:4px;overflow-y:auto}
@@ -0,0 +1 @@
import{Ar as e,Er as t,L as n,Lt as r,Nr as i,Or as a,Tr as o,Yr as s,_ as c,_i as l,ai as u,g as d,h as f,in as p,jr as m,qr as h,ri as g,u as _,ui as v,v as y,wt as b,xi as x,xr as S}from"./index-a0fi5f-0.js";import{t as C}from"./useSnackbar-u_XK4ZNm.js";import{i as w,n as T,o as E,t as D}from"./VCard-BwwttBcf.js";import{t as O}from"./VTextField-GUQr1tUN.js";import{t as k}from"./apiError-BUh3zCmq.js";import{t as A}from"./VDialog-BFQGuuXw.js";function j(e={}){let t=C(),n=v(!1),i=v(!1),a=v({domain:``,port:22,name:``}),o=v(!1),s=v([]),c=v(``);function l(){a.value={domain:``,port:22,name:``},n.value=!0}function u(e,t){s.value=e,c.value=t||``,o.value=!0}function d(e){e.onboarding_job_id&&(r(e.onboarding_job_id,e.onboarding_label||`新服务器初始化`,1,`onboard`,`0/1`,`running`),b(`新服务器初始化已在后台执行(sudo + 路径探测)`))}async function f(){let r=a.value.domain.trim();if(!r){t(`请填写 IP 或域名`,`error`);return}i.value=!0;try{let i={domain:r,port:a.value.port||22},o=a.value.name.trim();o&&(i.name=o);let s=await p.post(`/servers/add-by-ip`,i);s.success&&s.server?(n.value=!1,d(s),t(`添加成功:${s.matched_preset} (${s.matched_username})`),await e.onSuccess?.(s)):(n.value=!1,u(s.errors||[],s.message),await e.onPendingFailure?.())}catch(e){t(k(e,`添加失败`),`error`)}finally{i.value=!1}}return{showQuickAdd:n,quickAddLoading:i,quickAddForm:a,showPollFailure:o,pollFailureErrors:s,pollFailureMessage:c,openQuickAdd:l,submitQuickAdd:f,showFailureDialog:u}}var M={key:1,class:`text-medium-emphasis`},N=i({__name:`ServerQuickAddDialogs`,props:{onSuccess:{type:Function},onPendingFailure:{type:Function}},setup(r,{expose:i}){let p=r,{showQuickAdd:v,quickAddLoading:b,quickAddForm:C,showPollFailure:k,pollFailureErrors:N,pollFailureMessage:P,openQuickAdd:F,submitQuickAdd:I,showFailureDialog:L}=j({onSuccess:e=>p.onSuccess?.(e),onPendingFailure:()=>p.onPendingFailure?.()});return i({open:F,showFailure:L}),(r,i)=>(h(),a(S,null,[m(A,{modelValue:l(v),"onUpdate:modelValue":i[4]||=e=>u(v)?v.value=e:null,"max-width":`480`,persistent:``},{default:g(()=>[m(D,{border:``},{default:g(()=>[m(w,null,{default:g(()=>[...i[7]||=[e(`快速添加服务器(仅 IP`,-1)]]),_:1}),m(T,null,{default:g(()=>[i[8]||=o(`p`,{class:`text-body-2 text-medium-emphasis mb-3`},` 将按顺序轮询凭据管理中的全部密码预设与 SSH 密钥预设尝试登录,首个成功即加入服务器列表。 `,-1),m(O,{modelValue:l(C).domain,"onUpdate:modelValue":i[0]||=e=>l(C).domain=e,label:`IP 或域名`,variant:`outlined`,density:`compact`,class:`mb-2`,placeholder:`192.168.1.10`,rules:[e=>!!e?.trim()||`必填`]},null,8,[`modelValue`,`rules`]),m(O,{modelValue:l(C).port,"onUpdate:modelValue":i[1]||=e=>l(C).port=e,modelModifiers:{number:!0},label:`SSH 端口`,variant:`outlined`,density:`compact`,type:`number`,class:`mb-2`},null,8,[`modelValue`]),m(O,{modelValue:l(C).name,"onUpdate:modelValue":i[2]||=e=>l(C).name=e,label:`显示名称(可选)`,variant:`outlined`,density:`compact`,hint:`留空则使用 IP`,"persistent-hint":``},null,8,[`modelValue`])]),_:1}),m(E,null,{default:g(()=>[m(y),m(n,{variant:`text`,disabled:l(b),onClick:i[3]||=e=>v.value=!1},{default:g(()=>[...i[9]||=[e(`取消`,-1)]]),_:1},8,[`disabled`]),m(n,{color:`primary`,variant:`flat`,loading:l(b),onClick:l(I)},{default:g(()=>[...i[10]||=[e(`开始轮询`,-1)]]),_:1},8,[`loading`,`onClick`])]),_:1})]),_:1})]),_:1},8,[`modelValue`]),m(A,{modelValue:l(k),"onUpdate:modelValue":i[6]||=e=>u(k)?k.value=e:null,"max-width":`560`},{default:g(()=>[m(D,{border:``},{default:g(()=>[m(w,{class:`text-error`},{default:g(()=>[...i[11]||=[e(`SSH 连接失败`,-1)]]),_:1}),m(T,null,{default:g(()=>[i[12]||=o(`p`,{class:`text-body-2 mb-3`},`已尝试全部凭据预设,均未登录成功。该服务器已加入「连接失败列表」,可稍后重试。`,-1),l(N).length?(h(),t(_,{key:0,density:`compact`,class:`bg-grey-lighten-4 rounded`},{default:g(()=>[(h(!0),a(S,null,s(l(N),(n,r)=>(h(),t(f,{key:r},{default:g(()=>[m(d,{class:`text-body-2`},{default:g(()=>[e(` [`+x(n.preset_type===`password`?`密码`:n.preset_type===`system`?`系统`:`密钥`)+`] `+x(n.preset_name)+` (`+x(n.username)+`) `,1)]),_:2},1024),m(c,{class:`text-error`},{default:g(()=>[e(x(n.error),1)]),_:2},1024)]),_:2},1024))),128))]),_:1})):(h(),a(`p`,M,x(l(P)),1))]),_:1}),m(E,null,{default:g(()=>[m(y),m(n,{color:`primary`,variant:`flat`,onClick:i[5]||=e=>k.value=!1},{default:g(()=>[...i[13]||=[e(`知道了`,-1)]]),_:1})]),_:1})]),_:1})]),_:1},8,[`modelValue`])],64))}});export{N as t};
@@ -0,0 +1 @@
import{Ar as e,Er as t,L as n,Lt as r,Nr as i,Or as a,Tr as o,Yr as s,_ as c,_i as l,ai as u,g as d,h as f,in as p,jr as m,qr as h,ri as g,u as _,ui as v,v as y,wt as b,xi as x,xr as S}from"./index-CDcgx9V8.js";import{t as C}from"./useSnackbar-u_XK4ZNm.js";import{i as w,n as T,o as E,t as D}from"./VCard-pmpQR9O7.js";import{t as O}from"./VTextField-B-BnrJAQ.js";import{t as k}from"./apiError-CpgZRt4T.js";import{t as A}from"./VDialog-nKJcjfNL.js";function j(e={}){let t=C(),n=v(!1),i=v(!1),a=v({domain:``,port:22,name:``}),o=v(!1),s=v([]),c=v(``);function l(){a.value={domain:``,port:22,name:``},n.value=!0}function u(e,t){s.value=e,c.value=t||``,o.value=!0}function d(e){e.onboarding_job_id&&(r(e.onboarding_job_id,e.onboarding_label||`新服务器初始化`,1,`onboard`,`0/1`,`running`),b(`新服务器初始化已在后台执行(sudo + 路径探测)`))}async function f(){let r=a.value.domain.trim();if(!r){t(`请填写 IP 或域名`,`error`);return}i.value=!0;try{let i={domain:r,port:a.value.port||22},o=a.value.name.trim();o&&(i.name=o);let s=await p.post(`/servers/add-by-ip`,i);s.success&&s.server?(n.value=!1,d(s),t(`添加成功:${s.matched_preset} (${s.matched_username})`),await e.onSuccess?.(s)):(n.value=!1,u(s.errors||[],s.message),await e.onPendingFailure?.())}catch(e){t(k(e,`添加失败`),`error`)}finally{i.value=!1}}return{showQuickAdd:n,quickAddLoading:i,quickAddForm:a,showPollFailure:o,pollFailureErrors:s,pollFailureMessage:c,openQuickAdd:l,submitQuickAdd:f,showFailureDialog:u}}var M={key:1,class:`text-medium-emphasis`},N=i({__name:`ServerQuickAddDialogs`,props:{onSuccess:{type:Function},onPendingFailure:{type:Function}},setup(r,{expose:i}){let p=r,{showQuickAdd:v,quickAddLoading:b,quickAddForm:C,showPollFailure:k,pollFailureErrors:N,pollFailureMessage:P,openQuickAdd:F,submitQuickAdd:I,showFailureDialog:L}=j({onSuccess:e=>p.onSuccess?.(e),onPendingFailure:()=>p.onPendingFailure?.()});return i({open:F,showFailure:L}),(r,i)=>(h(),a(S,null,[m(A,{modelValue:l(v),"onUpdate:modelValue":i[4]||=e=>u(v)?v.value=e:null,"max-width":`480`,persistent:``},{default:g(()=>[m(D,{border:``},{default:g(()=>[m(w,null,{default:g(()=>[...i[7]||=[e(`快速添加服务器(仅 IP`,-1)]]),_:1}),m(T,null,{default:g(()=>[i[8]||=o(`p`,{class:`text-body-2 text-medium-emphasis mb-3`},` 将按顺序轮询凭据管理中的全部密码预设与 SSH 密钥预设尝试登录,首个成功即加入服务器列表。 `,-1),m(O,{modelValue:l(C).domain,"onUpdate:modelValue":i[0]||=e=>l(C).domain=e,label:`IP 或域名`,variant:`outlined`,density:`compact`,class:`mb-2`,placeholder:`192.168.1.10`,rules:[e=>!!e?.trim()||`必填`]},null,8,[`modelValue`,`rules`]),m(O,{modelValue:l(C).port,"onUpdate:modelValue":i[1]||=e=>l(C).port=e,modelModifiers:{number:!0},label:`SSH 端口`,variant:`outlined`,density:`compact`,type:`number`,class:`mb-2`},null,8,[`modelValue`]),m(O,{modelValue:l(C).name,"onUpdate:modelValue":i[2]||=e=>l(C).name=e,label:`显示名称(可选)`,variant:`outlined`,density:`compact`,hint:`留空则使用 IP`,"persistent-hint":``},null,8,[`modelValue`])]),_:1}),m(E,null,{default:g(()=>[m(y),m(n,{variant:`text`,disabled:l(b),onClick:i[3]||=e=>v.value=!1},{default:g(()=>[...i[9]||=[e(`取消`,-1)]]),_:1},8,[`disabled`]),m(n,{color:`primary`,variant:`flat`,loading:l(b),onClick:l(I)},{default:g(()=>[...i[10]||=[e(`开始轮询`,-1)]]),_:1},8,[`loading`,`onClick`])]),_:1})]),_:1})]),_:1},8,[`modelValue`]),m(A,{modelValue:l(k),"onUpdate:modelValue":i[6]||=e=>u(k)?k.value=e:null,"max-width":`560`},{default:g(()=>[m(D,{border:``},{default:g(()=>[m(w,{class:`text-error`},{default:g(()=>[...i[11]||=[e(`SSH 连接失败`,-1)]]),_:1}),m(T,null,{default:g(()=>[i[12]||=o(`p`,{class:`text-body-2 mb-3`},`已尝试全部凭据预设,均未登录成功。该服务器已加入「连接失败列表」,可稍后重试。`,-1),l(N).length?(h(),t(_,{key:0,density:`compact`,class:`bg-grey-lighten-4 rounded`},{default:g(()=>[(h(!0),a(S,null,s(l(N),(n,r)=>(h(),t(f,{key:r},{default:g(()=>[m(d,{class:`text-body-2`},{default:g(()=>[e(` [`+x(n.preset_type===`password`?`密码`:n.preset_type===`system`?`系统`:`密钥`)+`] `+x(n.preset_name)+` (`+x(n.username)+`) `,1)]),_:2},1024),m(c,{class:`text-error`},{default:g(()=>[e(x(n.error),1)]),_:2},1024)]),_:2},1024))),128))]),_:1})):(h(),a(`p`,M,x(l(P)),1))]),_:1}),m(E,null,{default:g(()=>[m(y),m(n,{color:`primary`,variant:`flat`,onClick:i[5]||=e=>k.value=!1},{default:g(()=>[...i[13]||=[e(`知道了`,-1)]]),_:1})]),_:1})]),_:1})]),_:1},8,[`modelValue`])],64))}});export{N as t};
@@ -0,0 +1 @@
.transfer-file-table[data-v-6d8b506b]{max-height:420px;overflow:auto}.transfer-file-table--dest[data-v-6d8b506b]{max-height:280px}.transfer-result-json[data-v-6d8b506b]{white-space:pre-wrap;word-break:break-all;max-height:200px;margin:0;overflow:auto}.cursor-pointer[data-v-6d8b506b]{cursor:pointer}.transfer-site-link[data-v-6d8b506b]:hover{text-decoration:underline}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-qjxp2VJ5.js";import{$ as n,C as r,Ct as i,Dr as a,Er as o,L as s,Nr as c,Or as l,St as u,Tr as d,Xt as f,Yr as p,Zt as m,br as h,in as g,jr as _,qr as v,ri as y,ui as b,xi as x,xr as S,xt as C,yi as w,yr as T}from"./index-a0fi5f-0.js";import{t as E}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{n as D,t as O}from"./VCard-BwwttBcf.js";import{t as k}from"./VSkeletonLoader-BW_kZu-c.js";var A={cpu:`cpu_avg`,mem:`mem_avg`,disk:`disk_avg`,online:null};function j(e,t){if(t===`online`)return e.map(e=>e.online_count);let n=A[t];return e.map(e=>e[n])}function M(e){let t=e.filter(e=>e!=null),n=e.length?e[e.length-1]:null,r=e.length>1?e[e.length-2]:null,i=n!=null&&r!=null?Math.round((n-r)*10)/10:null;if(!t.length)return{current:n,previous:r,delta:i,min:null,max:null,avg:null};let a=t.reduce((e,t)=>e+t,0);return{current:n,previous:r,delta:i,min:Math.min(...t),max:Math.max(...t),avg:Math.round(a/t.length)}}function N(e,t,n){let r=e.slice(t,n+1);return{cpu:M(j(r,`cpu`)),mem:M(j(r,`mem`)),disk:M(j(r,`disk`)),online:M(j(r,`online`))}}function P(e){let t=new Map(e.map(e=>[e.key,e.value])),n=(e,n)=>{let r=Number(t.get(e));return Number.isFinite(r)&&r>0?r:n};return{cpu:n(`cpu_alert_threshold`,80),mem:n(`mem_alert_threshold`,80),disk:n(`disk_alert_threshold`,80)}}function F(e){let t=m(e);return t?f(t.toISOString()):e}function I(e,t=`%`){return e==null?``:e===0?`0${t}`:`${e>0?`+`:``}${e}${t}`}function L(e){let t=m(e);return t?t.getTime():null}function R(e,t){if(!e.length)return-1;let n=0,r=1/0;return e.forEach((e,i)=>{let a=L(e.recorded_at);if(a==null)return;let o=Math.abs(a-t);o<r&&(r=o,n=i)}),r<=301e3?n:-1}function z(e,t,n){return e<=0?{startIdx:0,endIdx:0}:{startIdx:Math.max(0,Math.floor(t/100*(e-1))),endIdx:Math.min(e-1,Math.ceil(n/100*(e-1)))}}var B=[`total`,`online`,`offline`,`unset-path-offline`,`unset-path`,`alerts`];function V(){return[{subtitle:`服务器总数`,title:`0`,icon:`mdi-server-network`,color:`blue`},{subtitle:`在线`,title:`0`,icon:`mdi-check-circle-outline`,color:`green`},{subtitle:`离线`,title:`0`,icon:`mdi-alert-circle-outline`,color:`orange`},{subtitle:`未设路径·离线`,title:`0`,icon:`mdi-folder-alert-outline`,color:`deep-orange`},{subtitle:`未设路径`,title:`0`,icon:`mdi-folder-off-outline`,color:`amber`},{subtitle:`告警中`,title:`0`,icon:`mdi-bell-alert-outline`,color:`red`}]}function H(e,t){switch(e){case`total`:return String(t.total);case`online`:return String(t.online);case`offline`:return String(t.offline_list??t.offline);case`unset-path-offline`:return String(t.unset_path_offline??0);case`unset-path`:return String(t.unset_path??0);case`alerts`:return String(t.alerts??0);default:return`0`}}function U(e,t){e.value.forEach((e,n)=>{let r=B[n];r&&(e.title=H(r,t))})}async function W(){let{data:e}=await u(C.serverStats,()=>g.get(`/servers/stats`));return e}async function G(e){i(C.serverStats);let t=await W();return U(e,t),t}function K(){return{statItems:b(V())}}var q={class:`stat-cards-row`},J={key:0,class:`d-flex justify-end align-center mb-2`},Y={class:`text-caption text-medium-emphasis mb-1`,style:{"font-size":`14px`,"letter-spacing":`0.5px`,"text-transform":`uppercase`}},X=E(c({__name:`StatCardsRow`,props:{items:{},loading:{type:Boolean,default:!1},refreshing:{type:Boolean,default:!1},showRefresh:{type:Boolean,default:!0}},emits:[`click`,`refresh`],setup(i,{emit:c}){let u=c;function f(e){e.clickable===!1||!e.key||u(`click`,e.key)}return(c,m)=>(v(),l(`div`,q,[i.showRefresh?(v(),l(`div`,J,[_(s,{icon:`mdi-refresh`,size:`small`,variant:`text`,loading:i.refreshing,disabled:i.loading,title:`刷新统计`,"aria-label":`刷新统计`,onClick:m[0]||=e=>u(`refresh`)},null,8,[`loading`,`disabled`])])):a(``,!0),i.loading?(v(),o(k,{key:1,type:`heading`,class:`mb-1`})):(v(),o(t,{key:2},{default:y(()=>[(v(!0),l(S,null,p(i.items,(t,a)=>(v(),o(e,{key:t.key??a,cols:`6`,sm:i.items.length>4?4:3,lg:i.items.length>4?2:3},{default:y(()=>[_(O,{elevation:`0`,rounded:`xl`,border:``,class:w([`stat-card`,{"stat-card--clickable":t.clickable!==!1&&t.key,"stat-card--active":t.active,"stat-card--refreshing":i.refreshing}]),role:t.clickable!==!1&&t.key?`button`:void 0,tabindex:t.clickable!==!1&&t.key?0:void 0,onClick:e=>f(t),onKeydown:T(h(e=>f(t),[`prevent`]),[`enter`])},{default:y(()=>[_(D,{class:`pa-4 d-flex align-center justify-space-between`},{default:y(()=>[d(`div`,null,[d(`div`,Y,x(t.subtitle),1),d(`div`,{class:w([`font-weight-bold`,`text-`+t.color]),style:{"font-size":`36px`,"line-height":`1.1`}},x(t.title),3)]),_(r,{color:t.color,size:`48`,rounded:`lg`,class:`stat-icon`},{default:y(()=>[_(n,{icon:t.icon,size:`24`,color:`white`},null,8,[`icon`])]),_:2},1032,[`color`])]),_:2},1024)]),_:2},1032,[`class`,`role`,`tabindex`,`onClick`,`onKeydown`])]),_:2},1032,[`sm`,`lg`]))),128))]),_:1}))]))}}),[[`__scopeId`,`data-v-c6013623`]]);export{G as a,R as c,z as d,P as f,W as i,I as l,B as n,K as o,L as p,U as r,N as s,X as t,F as u};
+1
View File
@@ -0,0 +1 @@
import{n as e,t}from"./VRow-rmSa_q-C.js";import{$ as n,C as r,Ct as i,Dr as a,Er as o,L as s,Nr as c,Or as l,St as u,Tr as d,Xt as f,Yr as p,Zt as m,br as h,in as g,jr as _,qr as v,ri as y,ui as b,xi as x,xr as S,xt as C,yi as w,yr as T}from"./index-CDcgx9V8.js";import{t as E}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{n as D,t as O}from"./VCard-pmpQR9O7.js";import{t as k}from"./VSkeletonLoader-gL4kF19X.js";var A={cpu:`cpu_avg`,mem:`mem_avg`,disk:`disk_avg`,online:null};function j(e,t){if(t===`online`)return e.map(e=>e.online_count);let n=A[t];return e.map(e=>e[n])}function M(e){let t=e.filter(e=>e!=null),n=e.length?e[e.length-1]:null,r=e.length>1?e[e.length-2]:null,i=n!=null&&r!=null?Math.round((n-r)*10)/10:null;if(!t.length)return{current:n,previous:r,delta:i,min:null,max:null,avg:null};let a=t.reduce((e,t)=>e+t,0);return{current:n,previous:r,delta:i,min:Math.min(...t),max:Math.max(...t),avg:Math.round(a/t.length)}}function N(e,t,n){let r=e.slice(t,n+1);return{cpu:M(j(r,`cpu`)),mem:M(j(r,`mem`)),disk:M(j(r,`disk`)),online:M(j(r,`online`))}}function P(e){let t=new Map(e.map(e=>[e.key,e.value])),n=(e,n)=>{let r=Number(t.get(e));return Number.isFinite(r)&&r>0?r:n};return{cpu:n(`cpu_alert_threshold`,80),mem:n(`mem_alert_threshold`,80),disk:n(`disk_alert_threshold`,80)}}function F(e){let t=m(e);return t?f(t.toISOString()):e}function I(e,t=`%`){return e==null?``:e===0?`0${t}`:`${e>0?`+`:``}${e}${t}`}function L(e){let t=m(e);return t?t.getTime():null}function R(e,t){if(!e.length)return-1;let n=0,r=1/0;return e.forEach((e,i)=>{let a=L(e.recorded_at);if(a==null)return;let o=Math.abs(a-t);o<r&&(r=o,n=i)}),r<=301e3?n:-1}function z(e,t,n){return e<=0?{startIdx:0,endIdx:0}:{startIdx:Math.max(0,Math.floor(t/100*(e-1))),endIdx:Math.min(e-1,Math.ceil(n/100*(e-1)))}}var B=[`total`,`online`,`offline`,`unset-path-offline`,`unset-path`,`alerts`];function V(){return[{subtitle:`服务器总数`,title:`0`,icon:`mdi-server-network`,color:`blue`},{subtitle:`在线`,title:`0`,icon:`mdi-check-circle-outline`,color:`green`},{subtitle:`离线`,title:`0`,icon:`mdi-alert-circle-outline`,color:`orange`},{subtitle:`未设路径·离线`,title:`0`,icon:`mdi-folder-alert-outline`,color:`deep-orange`},{subtitle:`未设路径`,title:`0`,icon:`mdi-folder-off-outline`,color:`amber`},{subtitle:`告警中`,title:`0`,icon:`mdi-bell-alert-outline`,color:`red`}]}function H(e,t){switch(e){case`total`:return String(t.total);case`online`:return String(t.online);case`offline`:return String(t.offline_list??t.offline);case`unset-path-offline`:return String(t.unset_path_offline??0);case`unset-path`:return String(t.unset_path??0);case`alerts`:return String(t.alerts??0);default:return`0`}}function U(e,t){e.value.forEach((e,n)=>{let r=B[n];r&&(e.title=H(r,t))})}async function W(){let{data:e}=await u(C.serverStats,()=>g.get(`/servers/stats`));return e}async function G(e){i(C.serverStats);let t=await W();return U(e,t),t}function K(){return{statItems:b(V())}}var q={class:`stat-cards-row`},J={key:0,class:`d-flex justify-end align-center mb-2`},Y={class:`text-caption text-medium-emphasis mb-1`,style:{"font-size":`14px`,"letter-spacing":`0.5px`,"text-transform":`uppercase`}},X=E(c({__name:`StatCardsRow`,props:{items:{},loading:{type:Boolean,default:!1},refreshing:{type:Boolean,default:!1},showRefresh:{type:Boolean,default:!0}},emits:[`click`,`refresh`],setup(i,{emit:c}){let u=c;function f(e){e.clickable===!1||!e.key||u(`click`,e.key)}return(c,m)=>(v(),l(`div`,q,[i.showRefresh?(v(),l(`div`,J,[_(s,{icon:`mdi-refresh`,size:`small`,variant:`text`,loading:i.refreshing,disabled:i.loading,title:`刷新统计`,"aria-label":`刷新统计`,onClick:m[0]||=e=>u(`refresh`)},null,8,[`loading`,`disabled`])])):a(``,!0),i.loading?(v(),o(k,{key:1,type:`heading`,class:`mb-1`})):(v(),o(t,{key:2},{default:y(()=>[(v(!0),l(S,null,p(i.items,(t,a)=>(v(),o(e,{key:t.key??a,cols:`6`,sm:i.items.length>4?4:3,lg:i.items.length>4?2:3},{default:y(()=>[_(O,{elevation:`0`,rounded:`xl`,border:``,class:w([`stat-card`,{"stat-card--clickable":t.clickable!==!1&&t.key,"stat-card--active":t.active,"stat-card--refreshing":i.refreshing}]),role:t.clickable!==!1&&t.key?`button`:void 0,tabindex:t.clickable!==!1&&t.key?0:void 0,onClick:e=>f(t),onKeydown:T(h(e=>f(t),[`prevent`]),[`enter`])},{default:y(()=>[_(D,{class:`pa-4 d-flex align-center justify-space-between`},{default:y(()=>[d(`div`,null,[d(`div`,Y,x(t.subtitle),1),d(`div`,{class:w([`font-weight-bold`,`text-`+t.color]),style:{"font-size":`36px`,"line-height":`1.1`}},x(t.title),3)]),_(r,{color:t.color,size:`48`,rounded:`lg`,class:`stat-icon`},{default:y(()=>[_(n,{icon:t.icon,size:`24`,color:`white`},null,8,[`icon`])]),_:2},1032,[`color`])]),_:2},1024)]),_:2},1032,[`class`,`role`,`tabindex`,`onClick`,`onKeydown`])]),_:2},1032,[`sm`,`lg`]))),128))]),_:1}))]))}}),[[`__scopeId`,`data-v-c6013623`]]);export{G as a,R as c,z as d,P as f,W as i,I as l,B as n,K as o,L as p,U as r,N as s,X as t,F as u};
+1
View File
@@ -0,0 +1 @@
.stat-card[data-v-c6013623]{transition:transform .2s,box-shadow .2s,border-color .2s,opacity .2s}.stat-card--refreshing[data-v-c6013623]{opacity:.88}.stat-card--clickable[data-v-c6013623]{cursor:pointer}.stat-card--clickable[data-v-c6013623]:focus-visible{outline:2px solid rgb(var(--v-theme-primary));outline-offset:2px}.stat-card--active[data-v-c6013623]{border-color:rgb(var(--v-theme-primary))!important;box-shadow:0 0 0 1px rgba(var(--v-theme-primary), .35)!important}.stat-card--clickable[data-v-c6013623]:hover{transform:translateY(-2px);box-shadow:0 4px 24px rgba(var(--v-theme-primary), .08)!important}.stat-icon[data-v-c6013623]{transition:transform .3s}.stat-card:hover .stat-icon[data-v-c6013623]{transform:scale(1.1)}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
@layer vuetify-components{.v-autocomplete--chips.v-input--density-compact .v-field--variant-solo .v-label.v-field-label--floating,.v-autocomplete--chips.v-input--density-compact .v-field--variant-solo-inverted .v-label.v-field-label--floating,.v-autocomplete--chips.v-input--density-compact .v-field--variant-filled .v-label.v-field-label--floating,.v-autocomplete--chips.v-input--density-compact .v-field--variant-solo-filled .v-label.v-field-label--floating{top:0}.v-autocomplete .v-field .v-text-field__prefix,.v-autocomplete .v-field .v-text-field__suffix,.v-autocomplete .v-field .v-field__input,.v-autocomplete .v-field .v-field__input>input,.v-autocomplete .v-field.v-field{cursor:text}.v-autocomplete .v-field .v-field__input>input{flex:1}.v-autocomplete .v-field input{min-width:64px}.v-autocomplete .v-field:not(.v-field--focused) input{min-width:0}.v-autocomplete .v-field--dirty .v-autocomplete__selection{margin-inline-end:2px}.v-autocomplete .v-autocomplete__selection-text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.v-autocomplete__content{box-shadow:0px 1px 2px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity,.3)), 0px 2px 6px 2px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity,.15));--v-elevation-overlay:color-mix(in srgb, var(--v-elevation-overlay-color) 4%, transparent);overflow:hidden}.v-menu>.v-overlay__content.v-autocomplete__content{border-radius:4px}.v-autocomplete__content>.v-sheet{flex-direction:column;display:flex}.v-autocomplete__mask{background:rgb(var(--v-theme-surface-light))}.v-autocomplete__selection{height:1.5rem;letter-spacing:inherit;line-height:inherit;align-items:center;max-width:calc(100% - 4px);display:inline-flex}.v-autocomplete__selection:first-child{margin-inline-start:0}.v-autocomplete--selecting-index .v-autocomplete__selection{opacity:var(--v-medium-emphasis-opacity)}.v-autocomplete--selecting-index .v-autocomplete__selection--selected{opacity:1}.v-autocomplete--selecting-index .v-field__input>input{caret-color:#0000}.v-autocomplete--single:not(.v-autocomplete--selection-slot).v-text-field input{width:100%;padding-inline:inherit;flex:1;position:absolute;left:0;right:0}.v-autocomplete--single:not(.v-autocomplete--selection-slot) .v-field--active input{transition:none}.v-autocomplete--single:not(.v-autocomplete--selection-slot) .v-field--dirty:not(.v-field--focused) input,.v-autocomplete--single:not(.v-autocomplete--selection-slot) .v-field--focused .v-autocomplete__selection{opacity:0}.v-autocomplete__menu-icon{margin-inline-start:4px;transition:all .2s cubic-bezier(.4,0,.2,1)}.v-autocomplete--active-menu .v-autocomplete__menu-icon{transform:rotate(180deg)}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{d as e,f as t}from"./VSelect-BvR-yyLS.js";import{Lr as n,Nn as r,On as i,Qr as a,Sn as o,i as s,jr as c,qn as l,sr as u,ui as d,yn as f}from"./index-CDcgx9V8.js";import{l as p,m,u as h}from"./VTextField-B-BnrJAQ.js";var g=r({...u(h(),[`direction`]),...u(t(),[`inline`])},`VCheckbox`),_=i()({name:`VCheckbox`,inheritAttrs:!1,props:g(),emits:{"update:modelValue":e=>!0,"update:focused":e=>!0},setup(t,{attrs:r,slots:i}){let u=f(t,`modelValue`),{isFocused:h,focus:g,blur:_}=m(t),v=d(),y=a();return o(()=>{let[a,o]=l(r),s=p.filterProps(t),d=e.filterProps(t);return c(p,n({ref:v,class:[`v-checkbox`,t.class]},a,s,{modelValue:u.value,"onUpdate:modelValue":e=>u.value=e,id:t.id||`checkbox-${y}`,focused:h.value,style:t.style}),{...i,default:({id:t,messagesId:r,isDisabled:a,isReadonly:s,isValid:l})=>c(e,n(d,{id:t.value,"aria-describedby":r.value,disabled:a.value,readonly:s.value},o,{error:l.value===!1,modelValue:u.value,"onUpdate:modelValue":e=>u.value=e,onFocus:g,onBlur:_}),i)})}),s({},v)}});export{_ as t};
+1
View File
@@ -0,0 +1 @@
@layer vuetify-components{.v-checkbox.v-input{flex:0 auto}.v-checkbox .v-selection-control{min-height:var(--v-input-control-height)}}

Some files were not shown because too many files have changed in this diff Show More