Files
orbit/internal/transport/httpserver/console/login.html

77 lines
4.1 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>登录 Orbit · 星轨控制台</title>
<link rel="stylesheet" href="/console/styles.css">
</head>
<body class="login-body">
<main class="login-page">
<section class="login-brand" aria-label="Orbit 产品介绍">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<span>
</span>
<span>
</span>
<span>
</span>
</div>
<strong>Orbit<span class="brand-separator"> / </span>
<small>星轨控制台</small>
</strong>
</div>
<div class="login-intro">
<span class="page-eyebrow">YOUR OPERATIONS, IN ORBIT</span>
<h1>运行有序,<br>一目了然。</h1>
<p>任务、调度、流程与结果交付,<br>在一个工作台里管理。</p>
<div class="login-flow" aria-hidden="true">
<span>任务</span>
<i>→</i>
<span>执行</span>
<i>→</i>
<span>结果</span>
</div>
</div>
<small>Pulse · Weave</small>
</section>
<section class="login-panel" aria-label="账号登录">
<div class="login-card">
<h2>登录控制台</h2>
<p class="login-copy">欢迎回来,使用管理员账号登录。</p>
<form class="login-form" id="login-form" autocomplete="on">
<label class="login-field">
<span>用户名</span>
<input id="username" name="username" autocomplete="username" maxlength="128" required autofocus placeholder="请输入用户名">
</label>
<label class="login-field">
<span>密码</span>
<input id="password" name="password" type="password" autocomplete="current-password" maxlength="1024" required placeholder="请输入密码">
</label>
<label class="password-toggle">
<input type="checkbox" id="show-password">显示密码</label>
<p class="login-error" id="login-error" role="alert">
</p>
<button class="primary-button login-submit" id="login-submit" type="submit">登录 Orbit →</button>
</form>
<p class="login-help">忘记密码?请联系部署管理员重置。</p>
</div>
<small class="login-footer">Orbit Console · 统一运行管理</small>
</section>
</main>
<script>
(() => {
const form = document.getElementById('login-form'); const username = document.getElementById('username'); const password = document.getElementById('password'); const submit = document.getElementById('login-submit'); const error = document.getElementById('login-error');
document.getElementById('show-password').addEventListener('change', event => { password.type = event.target.checked ? 'text' : 'password'; });
const params = new URLSearchParams(location.search); let returnTo = params.get('return') || '/console/'; if (!returnTo.startsWith('/console') || returnTo.startsWith('//') || returnTo.startsWith('/console/login')) returnTo = '/console/';
// 初次访问时 URL 片段不会发给服务端,浏览器会把它保留在跳转后的登录地址上。
if (!returnTo.includes('#') && /^#\/(overview|tasks|schedules|workflows|subscriptions|retention|archives|applications)$/.test(location.hash)) returnTo += location.hash;
fetch('/console/auth/session', { headers: { Accept: 'application/json' }, credentials: 'same-origin', cache: 'no-store' }).then(response => response.json()).then(body => { if (body.authenticated) location.replace(returnTo); }).catch(() => {});
form.addEventListener('submit', async event => { event.preventDefault(); error.textContent = ''; submit.disabled = true; submit.textContent = '正在登录…'; try { const response = await fetch('/console/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ username: username.value.trim(), password: password.value }) }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new Error(body?.error?.message || '登录失败,请检查用户名和密码'); location.replace(returnTo); } catch (loginError) { error.textContent = loginError.message || '登录失败,请稍后重试'; password.select(); } finally { submit.disabled = false; submit.textContent = '登录 Orbit →'; } });
})();
</script>
</body>
</html>