修复
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from flask import Flask, flash, redirect, render_template, request, url_for
|
||||
from flask import Flask, flash, jsonify, redirect, render_template, request, url_for
|
||||
from flask_login import LoginManager, current_user, login_required, login_user, logout_user
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
@@ -159,7 +163,79 @@ def create_app() -> Flask:
|
||||
.all()
|
||||
)
|
||||
grouped.append((g, svcs))
|
||||
return render_template("index.html", grouped=grouped)
|
||||
return render_template(
|
||||
"index.html",
|
||||
grouped=grouped,
|
||||
hub_auto_login=os.environ.get("NAV_HUB_AUTO_LOGIN", "").strip() == "1",
|
||||
)
|
||||
|
||||
@app.route("/api/embed/hub-login", methods=["POST"])
|
||||
@csrf.exempt
|
||||
@login_required
|
||||
def api_embed_hub_login():
|
||||
"""
|
||||
本地导航代登录云端中控:服务端请求 hub /api/auth/login,返回 embed-auth URL。
|
||||
避免浏览器在跨站 iframe 里丢弃 Set-Cookie。
|
||||
"""
|
||||
body = request.get_json(silent=True) or {}
|
||||
sid = body.get("service_id")
|
||||
base = (body.get("base_url") or "").strip().rstrip("/")
|
||||
next_path = (body.get("next") or "/monitor").strip() or "/monitor"
|
||||
if not next_path.startswith("/"):
|
||||
next_path = "/" + next_path
|
||||
username = (body.get("username") or os.environ.get("NAV_HUB_USERNAME") or "").strip()
|
||||
password = body.get("password")
|
||||
if password is None:
|
||||
password = os.environ.get("NAV_HUB_PASSWORD") or ""
|
||||
password = str(password)
|
||||
|
||||
if sid:
|
||||
svc = db.session.get(Service, int(sid))
|
||||
if not svc or not svc.is_hub_embed():
|
||||
return jsonify({"ok": False, "detail": "服务不存在或未标记为中控"}), 400
|
||||
base = svc.build_origin()
|
||||
if not next_path or next_path == "/monitor":
|
||||
p = (svc.path or "/monitor").strip() or "/monitor"
|
||||
next_path = p if p.startswith("/") else "/" + p
|
||||
|
||||
if not base:
|
||||
return jsonify({"ok": False, "detail": "缺少 base_url"}), 400
|
||||
if not username or not password:
|
||||
return jsonify({"ok": False, "detail": "缺少中控用户名或密码(可配置 NAV_HUB_USERNAME / NAV_HUB_PASSWORD)"}), 400
|
||||
|
||||
payload = json.dumps({"username": username, "password": password}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
f"{base}/api/auth/login",
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
raw = resp.read().decode("utf-8", errors="replace")
|
||||
except urllib.error.HTTPError as e:
|
||||
try:
|
||||
err_body = e.read().decode("utf-8", errors="replace")
|
||||
detail = json.loads(err_body).get("detail", err_body)
|
||||
except Exception:
|
||||
detail = str(e)
|
||||
return jsonify({"ok": False, "detail": detail or "中控登录失败"}), 401
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "detail": f"无法连接中控: {e}"}), 502
|
||||
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return jsonify({"ok": False, "detail": "中控返回非 JSON"}), 502
|
||||
if not data.get("ok"):
|
||||
return jsonify({"ok": False, "detail": data.get("detail") or "登录失败"}), 401
|
||||
token = data.get("session_token")
|
||||
if not token:
|
||||
return jsonify({"ok": False, "detail": "中控未返回 session_token,请升级云端 hub 后重试"}), 502
|
||||
|
||||
q = urlencode({"token": token, "next": next_path})
|
||||
embed_url = f"{base}/embed-auth?{q}"
|
||||
return jsonify({"ok": True, "embed_auth_url": embed_url, "next": next_path})
|
||||
|
||||
# ---------- 分组管理 ----------
|
||||
@app.route("/admin/groups")
|
||||
@@ -261,6 +337,7 @@ def create_app() -> Flask:
|
||||
path=path,
|
||||
sort_order=form.sort_order.data or 0,
|
||||
group_id=form.group_id.data,
|
||||
embed_kind=(form.embed_kind.data or "").strip(),
|
||||
)
|
||||
db.session.add(s)
|
||||
db.session.commit()
|
||||
@@ -296,6 +373,7 @@ def create_app() -> Flask:
|
||||
s.path = (form.path.data or "").strip() or "/"
|
||||
s.sort_order = form.sort_order.data or 0
|
||||
s.group_id = form.group_id.data
|
||||
s.embed_kind = (form.embed_kind.data or "").strip()
|
||||
db.session.commit()
|
||||
flash("服务已更新", "success")
|
||||
return redirect(url_for("admin_services"))
|
||||
@@ -352,6 +430,16 @@ def _migrate_schema() -> None:
|
||||
)
|
||||
)
|
||||
print("[nav] 已为 services 表添加 scheme 列(默认 http)。", flush=True)
|
||||
cols = {c["name"] for c in insp.get_columns("services")}
|
||||
if "embed_kind" not in cols:
|
||||
with db.engine.begin() as conn:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE services ADD COLUMN embed_kind VARCHAR(16) "
|
||||
"NOT NULL DEFAULT ''"
|
||||
)
|
||||
)
|
||||
print("[nav] 已为 services 表添加 embed_kind 列。", flush=True)
|
||||
except Exception as exc:
|
||||
print(f"[nav] 数据库结构迁移跳过或失败: {exc}", flush=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user