修复
This commit is contained in:
@@ -58,3 +58,7 @@
|
|||||||
# NAV_GATE_EXECUTOR_HOST=exec.你的域名
|
# NAV_GATE_EXECUTOR_HOST=exec.你的域名
|
||||||
# NAV_GATE_SCOUT_PORT=443
|
# NAV_GATE_SCOUT_PORT=443
|
||||||
# NAV_GATE_EXECUTOR_PORT=443
|
# NAV_GATE_EXECUTOR_PORT=443
|
||||||
|
# iframe 内自动代登录(须与云端 gate_scout PM2 的 NAV_EMBED_SESSION=1 配合)
|
||||||
|
# NAV_GATE_SCOUT_USERNAME=admin
|
||||||
|
# NAV_GATE_SCOUT_PASSWORD=你的扫单密码
|
||||||
|
# NAV_GATE_SCOUT_AUTO_LOGIN=1
|
||||||
|
|||||||
@@ -190,6 +190,85 @@ def _resolve_hub_embed_service(body: dict) -> tuple[str | None, str | None, str
|
|||||||
return base, next_path, None
|
return base, next_path, None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_gate_scout_embed_service(
|
||||||
|
body: dict,
|
||||||
|
) -> tuple[str | None, str | None, str | None, str | None]:
|
||||||
|
"""返回 (base_origin, default_next_path, embed_kind, error_detail)。"""
|
||||||
|
sid = body.get("service_id")
|
||||||
|
base = (body.get("base_url") or "").strip().rstrip("/")
|
||||||
|
next_path = (body.get("next") or "/dashboard").strip() or "/dashboard"
|
||||||
|
embed_kind = (body.get("embed_kind") or "").strip().lower()
|
||||||
|
if not next_path.startswith("/"):
|
||||||
|
next_path = "/" + next_path
|
||||||
|
|
||||||
|
if sid:
|
||||||
|
svc = db.session.get(Service, int(sid))
|
||||||
|
if not svc or not svc.is_gate_scout_embed():
|
||||||
|
return None, None, None, "服务不存在或未标记为 Gate 扫单嵌入"
|
||||||
|
base = svc.build_origin()
|
||||||
|
embed_kind = (svc.embed_kind or "").strip().lower()
|
||||||
|
if not body.get("next") or next_path == "/dashboard":
|
||||||
|
p = (svc.path or "/dashboard").strip() or "/dashboard"
|
||||||
|
next_path = p if p.startswith("/") else "/" + p
|
||||||
|
|
||||||
|
if not base:
|
||||||
|
return None, None, None, "缺少 base_url"
|
||||||
|
if embed_kind in ("gate_exec", "exec"):
|
||||||
|
login_path = "/login"
|
||||||
|
else:
|
||||||
|
login_path = "/api/auth/login"
|
||||||
|
return base, next_path, login_path, None
|
||||||
|
|
||||||
|
|
||||||
|
def _gate_scout_api_login(
|
||||||
|
base: str, username: str, password: str, *, login_path: str, next_path: str
|
||||||
|
) -> tuple[str | None, str | None]:
|
||||||
|
"""服务端登录 Gate 扫单/执行器,返回 (完整 embed_auth_url, error_detail)。"""
|
||||||
|
payload = json.dumps(
|
||||||
|
{"username": username, "password": password, "embed": "1", "next": next_path}
|
||||||
|
).encode("utf-8")
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{base.rstrip('/')}{login_path}",
|
||||||
|
data=payload,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"X-Nav-Embed": "1",
|
||||||
|
},
|
||||||
|
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 None, detail or "Gate 扫单登录失败"
|
||||||
|
except Exception as e:
|
||||||
|
return None, f"无法连接 Gate 服务: {e}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(raw)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return None, "Gate 服务返回非 JSON"
|
||||||
|
if not data.get("ok"):
|
||||||
|
return None, data.get("detail") or "登录失败"
|
||||||
|
embed_url = (data.get("embed_auth_url") or "").strip()
|
||||||
|
if embed_url.startswith("/"):
|
||||||
|
embed_url = base.rstrip("/") + embed_url
|
||||||
|
if not embed_url:
|
||||||
|
token = (data.get("session_token") or "").strip()
|
||||||
|
if token:
|
||||||
|
q = urlencode({"token": token, "next": next_path, "embed": "1"})
|
||||||
|
embed_url = f"{base.rstrip('/')}/embed-auth?{q}"
|
||||||
|
if not embed_url:
|
||||||
|
return None, "未返回 embed_auth_url,请确认云端已设置 NAV_EMBED_SESSION=1 并重启 PM2"
|
||||||
|
return embed_url, None
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> Flask:
|
def create_app() -> Flask:
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config["SECRET_KEY"] = os.environ.get("NAV_SECRET_KEY") or secrets.token_hex(32)
|
app.config["SECRET_KEY"] = os.environ.get("NAV_SECRET_KEY") or secrets.token_hex(32)
|
||||||
@@ -265,6 +344,8 @@ def create_app() -> Flask:
|
|||||||
"index.html",
|
"index.html",
|
||||||
grouped=grouped,
|
grouped=grouped,
|
||||||
hub_auto_login=os.environ.get("NAV_HUB_AUTO_LOGIN", "").strip() == "1",
|
hub_auto_login=os.environ.get("NAV_HUB_AUTO_LOGIN", "").strip() == "1",
|
||||||
|
gate_scout_auto_login=os.environ.get("NAV_GATE_SCOUT_AUTO_LOGIN", "").strip()
|
||||||
|
== "1",
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/api/embed/hub-login", methods=["POST"])
|
@app.route("/api/embed/hub-login", methods=["POST"])
|
||||||
@@ -297,6 +378,47 @@ def create_app() -> Flask:
|
|||||||
embed_url = f"{base}/embed-auth?{q}"
|
embed_url = f"{base}/embed-auth?{q}"
|
||||||
return jsonify({"ok": True, "embed_auth_url": embed_url, "next": next_path})
|
return jsonify({"ok": True, "embed_auth_url": embed_url, "next": next_path})
|
||||||
|
|
||||||
|
@app.route("/api/embed/gate-scout-login", methods=["POST"])
|
||||||
|
@csrf.exempt
|
||||||
|
@login_required
|
||||||
|
def api_embed_gate_scout_login():
|
||||||
|
"""
|
||||||
|
本地导航代登录 Gate 扫描端/执行器:服务端请求 /api/auth/login 或 /login,
|
||||||
|
再让 iframe 打开 /embed-auth 写入 SameSite=None Cookie。
|
||||||
|
"""
|
||||||
|
body = request.get_json(silent=True) or {}
|
||||||
|
username = (
|
||||||
|
body.get("username") or os.environ.get("NAV_GATE_SCOUT_USERNAME") or ""
|
||||||
|
).strip()
|
||||||
|
password = body.get("password")
|
||||||
|
if password is None:
|
||||||
|
password = os.environ.get("NAV_GATE_SCOUT_PASSWORD") or ""
|
||||||
|
password = str(password)
|
||||||
|
|
||||||
|
base, next_path, login_path, err = _resolve_gate_scout_embed_service(body)
|
||||||
|
if err:
|
||||||
|
return jsonify({"ok": False, "detail": err}), 400
|
||||||
|
if not username or not password:
|
||||||
|
return jsonify(
|
||||||
|
{
|
||||||
|
"ok": False,
|
||||||
|
"detail": "缺少 Gate 扫单用户名或密码(可配置 NAV_GATE_SCOUT_USERNAME / NAV_GATE_SCOUT_PASSWORD)",
|
||||||
|
}
|
||||||
|
), 400
|
||||||
|
|
||||||
|
embed_url, err = _gate_scout_api_login(
|
||||||
|
base,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
login_path=login_path,
|
||||||
|
next_path=next_path,
|
||||||
|
)
|
||||||
|
if err:
|
||||||
|
status = 401 if "登录" in err or "401" in err or "密码" in err else 502
|
||||||
|
return jsonify({"ok": False, "detail": err}), status
|
||||||
|
|
||||||
|
return jsonify({"ok": True, "embed_auth_url": embed_url, "next": next_path})
|
||||||
|
|
||||||
@app.route("/api/embed/hub-instance-url", methods=["POST"])
|
@app.route("/api/embed/hub-instance-url", methods=["POST"])
|
||||||
@csrf.exempt
|
@csrf.exempt
|
||||||
@login_required
|
@login_required
|
||||||
@@ -620,12 +742,12 @@ def _ensure_gate_scout_services() -> None:
|
|||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
defs = (
|
defs = (
|
||||||
("Gate 扫描端", scout_host, scout_port, scout_path, 0),
|
("Gate 扫描端", scout_host, scout_port, scout_path, 0, "gate_scout"),
|
||||||
("Gate 下单执行器", exec_host, exec_port, exec_path, 10),
|
("Gate 下单执行器", exec_host, exec_port, exec_path, 10, "gate_exec"),
|
||||||
)
|
)
|
||||||
added = 0
|
added = 0
|
||||||
updated = 0
|
updated = 0
|
||||||
for name, h, port, path, order in defs:
|
for name, h, port, path, order, embed_k in defs:
|
||||||
existing = Service.query.filter_by(group_id=g.id, name=name).first()
|
existing = Service.query.filter_by(group_id=g.id, name=name).first()
|
||||||
if existing:
|
if existing:
|
||||||
if update_existing and (
|
if update_existing and (
|
||||||
@@ -633,11 +755,16 @@ def _ensure_gate_scout_services() -> None:
|
|||||||
or existing.host != h
|
or existing.host != h
|
||||||
or existing.port != port
|
or existing.port != port
|
||||||
or existing.path != path
|
or existing.path != path
|
||||||
|
or (existing.embed_kind or "") != embed_k
|
||||||
):
|
):
|
||||||
existing.scheme = scheme
|
existing.scheme = scheme
|
||||||
existing.host = h
|
existing.host = h
|
||||||
existing.port = port
|
existing.port = port
|
||||||
existing.path = path
|
existing.path = path
|
||||||
|
existing.embed_kind = embed_k
|
||||||
|
updated += 1
|
||||||
|
elif not (existing.embed_kind or "").strip():
|
||||||
|
existing.embed_kind = embed_k
|
||||||
updated += 1
|
updated += 1
|
||||||
continue
|
continue
|
||||||
db.session.add(
|
db.session.add(
|
||||||
@@ -649,7 +776,7 @@ def _ensure_gate_scout_services() -> None:
|
|||||||
path=path,
|
path=path,
|
||||||
sort_order=order,
|
sort_order=order,
|
||||||
group_id=g.id,
|
group_id=g.id,
|
||||||
embed_kind="",
|
embed_kind=embed_k,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
added += 1
|
added += 1
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ class ServiceForm(FlaskForm):
|
|||||||
choices=[
|
choices=[
|
||||||
("", "普通(直接打开路径)"),
|
("", "普通(直接打开路径)"),
|
||||||
("hub", "复盘中控(云端 hub,需 embed-auth 登录)"),
|
("hub", "复盘中控(云端 hub,需 embed-auth 登录)"),
|
||||||
|
("gate_scout", "Gate 扫描端(iframe 代登录)"),
|
||||||
|
("gate_exec", "Gate 执行器(iframe 代登录)"),
|
||||||
],
|
],
|
||||||
default="",
|
default="",
|
||||||
validators=[Optional()],
|
validators=[Optional()],
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ class Service(db.Model):
|
|||||||
return f"{self.build_origin()}{p}"
|
return f"{self.build_origin()}{p}"
|
||||||
|
|
||||||
def build_open_url(self) -> str:
|
def build_open_url(self) -> str:
|
||||||
"""导航 iframe 首次打开的地址(中控走 login?embed=1 以便写入会话)。"""
|
"""导航 iframe 首次打开的地址(中控 / Gate 扫单走 login?embed=1 以便写入会话)。"""
|
||||||
kind = (self.embed_kind or "").strip().lower()
|
kind = (self.embed_kind or "").strip().lower()
|
||||||
next_path = (self.path or "/monitor").strip() or "/monitor"
|
next_path = (self.path or "/monitor").strip() or "/monitor"
|
||||||
if not next_path.startswith("/"):
|
if not next_path.startswith("/"):
|
||||||
next_path = "/" + next_path
|
next_path = "/" + next_path
|
||||||
if kind == "hub":
|
if kind == "hub" or kind in ("gate_scout", "gate_exec", "scout", "exec"):
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
q = urlencode({"embed": "1", "next": next_path})
|
q = urlencode({"embed": "1", "next": next_path})
|
||||||
@@ -80,3 +80,18 @@ class Service(db.Model):
|
|||||||
|
|
||||||
def is_hub_embed(self) -> bool:
|
def is_hub_embed(self) -> bool:
|
||||||
return (self.embed_kind or "").strip().lower() == "hub"
|
return (self.embed_kind or "").strip().lower() == "hub"
|
||||||
|
|
||||||
|
def is_gate_scout_embed(self) -> bool:
|
||||||
|
return (self.embed_kind or "").strip().lower() in (
|
||||||
|
"gate_scout",
|
||||||
|
"gate_exec",
|
||||||
|
"scout",
|
||||||
|
"exec",
|
||||||
|
)
|
||||||
|
|
||||||
|
def gate_scout_api_login_path(self) -> str:
|
||||||
|
"""服务端代登录使用的 API 路径。"""
|
||||||
|
kind = (self.embed_kind or "").strip().lower()
|
||||||
|
if kind in ("gate_exec", "exec"):
|
||||||
|
return "/login"
|
||||||
|
return "/api/auth/login"
|
||||||
|
|||||||
+86
-2
@@ -118,6 +118,15 @@
|
|||||||
<span class="frame-title" id="current-service-name"></span>
|
<span class="frame-title" id="current-service-name"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame-toolbar-actions">
|
<div class="frame-toolbar-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary btn-toolbar-refresh"
|
||||||
|
id="frame-gate-login"
|
||||||
|
title="通过本地导航代登录 Gate 扫单(需配置 NAV_GATE_SCOUT_USERNAME / NAV_GATE_SCOUT_PASSWORD)"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
Gate 登录
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-secondary btn-toolbar-refresh"
|
class="btn btn-secondary btn-toolbar-refresh"
|
||||||
@@ -159,6 +168,7 @@
|
|||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
var hubAutoLogin = {{ 'true' if hub_auto_login else 'false' }};
|
var hubAutoLogin = {{ 'true' if hub_auto_login else 'false' }};
|
||||||
|
var gateScoutAutoLogin = {{ 'true' if gate_scout_auto_login else 'false' }};
|
||||||
var layoutMain = document.getElementById("layout-main");
|
var layoutMain = document.getElementById("layout-main");
|
||||||
var btnSidebarCollapse = document.getElementById("sidebar-collapse");
|
var btnSidebarCollapse = document.getElementById("sidebar-collapse");
|
||||||
var btnSidebarExpand = document.getElementById("sidebar-expand");
|
var btnSidebarExpand = document.getElementById("sidebar-expand");
|
||||||
@@ -201,6 +211,7 @@
|
|||||||
var btnBack = document.getElementById("frame-back-overview");
|
var btnBack = document.getElementById("frame-back-overview");
|
||||||
var btnBackHub = document.getElementById("frame-back-hub");
|
var btnBackHub = document.getElementById("frame-back-hub");
|
||||||
var btnHubLogin = document.getElementById("frame-hub-login");
|
var btnHubLogin = document.getElementById("frame-hub-login");
|
||||||
|
var btnGateLogin = document.getElementById("frame-gate-login");
|
||||||
var btnInstanceSso = document.getElementById("frame-instance-sso");
|
var btnInstanceSso = document.getElementById("frame-instance-sso");
|
||||||
var currentBaseUrl = "";
|
var currentBaseUrl = "";
|
||||||
var currentOpenUrl = "";
|
var currentOpenUrl = "";
|
||||||
@@ -240,6 +251,11 @@
|
|||||||
return (kind || "").toLowerCase() === "hub";
|
return (kind || "").toLowerCase() === "hub";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGateScoutEmbed(kind) {
|
||||||
|
var k = (kind || "").toLowerCase();
|
||||||
|
return k === "gate_scout" || k === "gate_exec" || k === "scout" || k === "exec";
|
||||||
|
}
|
||||||
|
|
||||||
function toggleInstanceBackBtn(show) {
|
function toggleInstanceBackBtn(show) {
|
||||||
if (btnBackHub) btnBackHub.hidden = !show;
|
if (btnBackHub) btnBackHub.hidden = !show;
|
||||||
}
|
}
|
||||||
@@ -252,6 +268,10 @@
|
|||||||
if (btnHubLogin) btnHubLogin.hidden = !show;
|
if (btnHubLogin) btnHubLogin.hidden = !show;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleGateLoginBtn(show) {
|
||||||
|
if (btnGateLogin) btnGateLogin.hidden = !show;
|
||||||
|
}
|
||||||
|
|
||||||
function applyIframeUrl(url) {
|
function applyIframeUrl(url) {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
frame.src = url;
|
frame.src = url;
|
||||||
@@ -329,6 +349,36 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gateScoutLoginViaProxy(done) {
|
||||||
|
if (!currentServiceId && !currentOrigin) {
|
||||||
|
if (done) done(false, "未选择 Gate 服务");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var body = { service_id: parseInt(currentServiceId, 10) || undefined, next: currentNextPath };
|
||||||
|
fetch("/api/embed/gate-scout-login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json", "Accept": "application/json" },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
.then(function (r) {
|
||||||
|
return r.json().then(function (j) {
|
||||||
|
return { ok: r.ok, j: j };
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
if (res.ok && res.j.ok && res.j.embed_auth_url) {
|
||||||
|
applyIframeUrl(res.j.embed_auth_url);
|
||||||
|
toggleGateLoginBtn(false);
|
||||||
|
if (done) done(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (done) done(false, (res.j && res.j.detail) || "Gate 登录失败");
|
||||||
|
})
|
||||||
|
.catch(function (e) {
|
||||||
|
if (done) done(false, String(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("message", function (ev) {
|
window.addEventListener("message", function (ev) {
|
||||||
var data = ev.data;
|
var data = ev.data;
|
||||||
if (!data || !data.type) return;
|
if (!data || !data.type) return;
|
||||||
@@ -480,19 +530,36 @@
|
|||||||
instanceNavCtx = null;
|
instanceNavCtx = null;
|
||||||
hubReturnState = null;
|
hubReturnState = null;
|
||||||
toggleInstanceBackBtn(false);
|
toggleInstanceBackBtn(false);
|
||||||
|
toggleGateLoginBtn(false);
|
||||||
nameEl.textContent = name || "";
|
nameEl.textContent = name || "";
|
||||||
dashboard.hidden = true;
|
dashboard.hidden = true;
|
||||||
frameStack.hidden = false;
|
frameStack.hidden = false;
|
||||||
frame.hidden = false;
|
frame.hidden = false;
|
||||||
toggleHubLoginBtn(isHubEmbed(currentEmbedKind));
|
toggleHubLoginBtn(isHubEmbed(currentEmbedKind) && !hubAutoLogin);
|
||||||
|
toggleGateLoginBtn(isGateScoutEmbed(currentEmbedKind) && !gateScoutAutoLogin);
|
||||||
if (isHubEmbed(currentEmbedKind) && hubAutoLogin) {
|
if (isHubEmbed(currentEmbedKind) && hubAutoLogin) {
|
||||||
hubLoginViaProxy(function (ok, err) {
|
hubLoginViaProxy(function (ok, err) {
|
||||||
if (!ok) applyIframeUrl(url);
|
if (!ok) {
|
||||||
|
applyIframeUrl(url);
|
||||||
|
toggleHubLoginBtn(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var nav = preferredNav || findNavLink(url);
|
var nav = preferredNav || findNavLink(url);
|
||||||
setActive(nav);
|
setActive(nav);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isGateScoutEmbed(currentEmbedKind) && gateScoutAutoLogin) {
|
||||||
|
gateScoutLoginViaProxy(function (ok, err) {
|
||||||
|
if (!ok) {
|
||||||
|
applyIframeUrl(url);
|
||||||
|
toggleGateLoginBtn(true);
|
||||||
|
if (err) console.warn("[LocalNav] Gate 代登录失败:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var navGate = preferredNav || findNavLink(url);
|
||||||
|
setActive(navGate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
applyIframeUrl(url);
|
applyIframeUrl(url);
|
||||||
var nav = preferredNav || findNavLink(url);
|
var nav = preferredNav || findNavLink(url);
|
||||||
setActive(nav);
|
setActive(nav);
|
||||||
@@ -573,6 +640,7 @@
|
|||||||
frameStack.hidden = true;
|
frameStack.hidden = true;
|
||||||
dashboard.hidden = false;
|
dashboard.hidden = false;
|
||||||
toggleHubLoginBtn(false);
|
toggleHubLoginBtn(false);
|
||||||
|
toggleGateLoginBtn(false);
|
||||||
toggleInstanceSsoBtn(false);
|
toggleInstanceSsoBtn(false);
|
||||||
setActive(null);
|
setActive(null);
|
||||||
}
|
}
|
||||||
@@ -604,6 +672,22 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (btnGateLogin) {
|
||||||
|
btnGateLogin.addEventListener("click", function () {
|
||||||
|
btnGateLogin.disabled = true;
|
||||||
|
gateScoutLoginViaProxy(function (ok, err) {
|
||||||
|
btnGateLogin.disabled = false;
|
||||||
|
if (!ok && err) {
|
||||||
|
window.alert(
|
||||||
|
"Gate 登录失败:\n" +
|
||||||
|
err +
|
||||||
|
"\n\n请检查 LocalNav .env 的 NAV_GATE_SCOUT_USERNAME / NAV_GATE_SCOUT_PASSWORD,以及云端 PM2 的 NAV_EMBED_SESSION / NAV_EMBED_ORIGINS。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (btnHubLogin) {
|
if (btnHubLogin) {
|
||||||
btnHubLogin.addEventListener("click", function () {
|
btnHubLogin.addEventListener("click", function () {
|
||||||
btnHubLogin.disabled = true;
|
btnHubLogin.disabled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user