Files
crypto_monitor/manual_trading_hub/static/login.html
T

168 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="utf-8" />
<script src="/assets/theme.js?v=20260604-hub-theme4"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#0b0e18" />
<meta name="apple-mobile-web-app-title" content="中控" />
<link rel="icon" href="/assets/icons/favicon.ico" sizes="32x32" />
<link rel="icon" href="/assets/icons/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png" />
<link rel="manifest" href="/assets/icons/manifest.webmanifest" />
<title>登录 · 复盘系统中控</title>
<link rel="stylesheet" href="/assets/app.css?v=20260604-hub-theme4" />
</head>
<body class="login-page">
<div class="login-bg" aria-hidden="true"></div>
<div class="login-theme-bar">
<div class="theme-toggle" role="group" aria-label="界面主题">
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/>
</svg>
</button>
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4"/>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
</svg>
</button>
</div>
</div>
<div class="login-panel">
<div class="login-brand">
<span class="brand-mark"></span>
<div>
<div class="login-title">复盘系统中控</div>
<div class="login-sub">CRYPTO MONITOR · COMMAND</div>
</div>
</div>
<form id="login-form" class="login-form" autocomplete="on">
<label class="field">
<span>用户名</span>
<input type="text" name="username" id="login-username" required autocomplete="username" />
</label>
<label class="field">
<span>密码</span>
<input type="password" name="password" id="login-password" required autocomplete="current-password" />
</label>
<button type="submit" class="primary login-submit" id="login-submit">进入系统</button>
<p id="login-err" class="login-err" hidden></p>
<p id="login-hint" class="login-foot" hidden></p>
</form>
<p class="login-foot">账号在云端 hub 的 <code>.env</code><code>HUB_USERNAME</code> / <code>HUB_PASSWORD</code></p>
</div>
<script>
(function () {
const form = document.getElementById("login-form");
const err = document.getElementById("login-err");
const hint = document.getElementById("login-hint");
const submitBtn = document.getElementById("login-submit");
const userInput = document.getElementById("login-username");
const params = new URLSearchParams(location.search);
const next = params.get("next") || "/monitor";
const inFrame = window.self !== window.top;
const isHttps = location.protocol === "https:";
if (inFrame) {
hint.hidden = false;
hint.textContent = isHttps
? "嵌入模式:登录成功后将自动写入会话。"
: "嵌入模式需 HTTPS 中控;HTTP 时请用本地导航工具栏「中控登录」。";
}
function showErr(msg) {
err.textContent = msg;
err.hidden = false;
}
function gotoAfterLogin(token, dest) {
const target = dest.startsWith("/") ? dest : "/monitor";
if (inFrame) {
if (!token) {
showErr("登录响应缺少 session_token,请升级云端 hub 或使用本地导航「中控登录」。");
return;
}
if (!isHttps) {
showErr("跨站 iframe 登录需要 HTTPS 中控;请改用本地导航「中控登录」按钮。");
return;
}
const q = new URLSearchParams({ token, next: target });
const embedUrl = "/embed-auth?" + q.toString();
try {
window.parent.postMessage(
{
type: "hub:login-ok",
session_token: token,
next: target,
embed_auth_url: location.origin + embedUrl,
},
"*"
);
} catch (_) {}
submitBtn.textContent = "跳转中…";
submitBtn.disabled = true;
location.replace(embedUrl);
return;
}
location.href = target;
}
fetch("/api/auth/status")
.then((r) => r.json())
.then((s) => {
if (!s.required || s.logged_in) gotoAfterLogin(null, next);
if (s.username_hint && !userInput.value) userInput.value = s.username_hint;
})
.catch(() => {});
form.addEventListener("submit", async (e) => {
e.preventDefault();
err.hidden = true;
submitBtn.disabled = true;
const oldLabel = submitBtn.textContent;
submitBtn.textContent = "登录中…";
const username = userInput.value.trim();
const password = document.getElementById("login-password").value;
const headers = { "Content-Type": "application/json", Accept: "application/json" };
if (inFrame) headers["X-Hub-Embed"] = "1";
try {
const r = await fetch("/api/auth/login", {
method: "POST",
headers,
body: JSON.stringify({ username, password }),
});
let j = {};
try {
j = await r.json();
} catch (_) {
j = {};
}
if (r.ok && j.ok) {
gotoAfterLogin(j.session_token || null, next);
if (!inFrame) {
submitBtn.disabled = false;
submitBtn.textContent = oldLabel;
}
return;
}
if (r.status === 403) {
showErr("访问被拒绝(403):云端 hub 需设置 HUB_ALLOW_PUBLIC=true");
} else {
showErr(j.detail || j.msg || "用户名或密码错误 (" + r.status + ")");
}
} catch (ex) {
showErr("网络错误:" + ex);
}
submitBtn.disabled = false;
submitBtn.textContent = oldLabel;
});
})();
if (window.HubTheme && typeof HubTheme.initToggleUI === "function") {
HubTheme.initToggleUI();
}
</script>
</body>
</html>