52 lines
11 KiB
HTML
52 lines
11 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="dark">
|
|
<title>登录 Orbit · 星轨控制台</title>
|
|
<link rel="stylesheet" href="/console/styles.css">
|
|
<style>
|
|
:root{--login-bg:#050d19;--login-panel:#0d1c30;--login-panel-deep:#091527;--login-line:rgba(153,185,221,.16);--login-text:#e6f2ff;--login-muted:#8196ad;--login-dim:#5e7591;--login-cyan:#62e6d4;--login-blue:#72a6ff;--login-purple:#b29aff}
|
|
*{box-sizing:border-box}html,body{min-height:100%;margin:0}body{min-height:100vh;overflow:hidden;background:var(--login-bg);color:var(--login-text);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.login-page{position:relative;display:grid;place-items:center;min-height:100vh;padding:36px;background:radial-gradient(800px 520px at 76% 42%,rgba(34,104,132,.18),transparent 68%),radial-gradient(600px 480px at 16% 90%,rgba(56,43,130,.13),transparent 72%),#050d19}.login-page:before{position:absolute;inset:0;content:"";pointer-events:none;opacity:.18;background-image:linear-gradient(rgba(150,187,226,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(150,187,226,.08) 1px,transparent 1px);background-size:56px 56px;mask-image:linear-gradient(to bottom,transparent,#000 24%,#000 80%,transparent)}.login-page:after{position:absolute;inset:0;content:"";pointer-events:none;background:linear-gradient(115deg,transparent 30%,rgba(98,230,212,.035) 50%,transparent 70%)}.login-stage{position:relative;z-index:1;display:grid;grid-template-columns:minmax(0,1.12fr) minmax(370px,.82fr);width:min(1080px,100%);min-height:620px;overflow:hidden;border:1px solid rgba(153,185,221,.14);border-radius:30px;background:linear-gradient(135deg,rgba(12,29,49,.72),rgba(5,16,30,.82));box-shadow:0 35px 100px rgba(0,0,0,.36),inset 0 1px rgba(255,255,255,.035)}.login-landing{position:relative;display:flex;flex-direction:column;justify-content:space-between;min-width:0;padding:44px 50px 40px;border-right:1px solid rgba(153,185,221,.1);overflow:hidden}.login-landing:before{position:absolute;right:-130px;bottom:-170px;width:470px;height:470px;border:1px solid rgba(98,230,212,.09);border-radius:50%;content:"";box-shadow:0 0 0 34px rgba(98,230,212,.018),0 0 0 68px rgba(98,230,212,.014),0 0 0 102px rgba(98,230,212,.01)}.login-landing:after{position:absolute;right:58px;bottom:64px;width:6px;height:6px;border-radius:50%;background:var(--login-cyan);content:"";box-shadow:0 0 0 7px rgba(98,230,212,.12),0 0 24px 5px rgba(98,230,212,.6)}.login-header{display:flex;align-items:center;gap:13px}.brand-mark{display:grid;grid-template-columns:repeat(3,5px);align-items:end;gap:3px;width:37px;height:37px;padding:9px;border:1px solid rgba(98,230,212,.5);border-radius:13px;background:rgba(98,230,212,.08);box-shadow:0 0 24px rgba(98,230,212,.1)}.brand-mark i{display:block;border-radius:4px;background:var(--login-cyan)}.brand-mark i:nth-child(1){height:11px}.brand-mark i:nth-child(2){height:20px}.brand-mark i:nth-child(3){height:15px}.login-header strong{display:block;font-size:20px;letter-spacing:.02em}.login-header small{display:block;margin-top:3px;color:#718aa4;font-size:10px}.landing-copy{position:relative;z-index:1;margin-top:auto;margin-bottom:65px}.landing-kicker{display:flex;align-items:center;gap:10px;color:var(--login-cyan);font-size:10px;letter-spacing:.24em;font-weight:700}.landing-kicker:before{width:28px;height:1px;background:var(--login-cyan);content:""}.landing-title{max-width:520px;margin:19px 0 17px;color:#eef7ff;font-size:clamp(36px,4.2vw,58px);font-weight:680;letter-spacing:-.07em;line-height:1.07}.landing-title span{color:var(--login-cyan)}.landing-description{max-width:435px;margin:0;color:#8197af;font-size:13px;line-height:1.8}.landing-points{display:flex;flex-wrap:wrap;gap:10px;margin-top:32px}.landing-point{display:flex;align-items:center;gap:8px;padding:9px 11px;border:1px solid rgba(153,185,221,.12);border-radius:10px;background:rgba(4,14,27,.42);color:#9bb0c6;font-size:10px}.landing-point i{display:block;width:6px;height:6px;border-radius:50%;background:var(--login-cyan);box-shadow:0 0 10px rgba(98,230,212,.7)}.landing-point:nth-child(2) i{background:var(--login-blue);box-shadow:0 0 10px rgba(114,166,255,.7)}.landing-point:nth-child(3) i{background:var(--login-purple);box-shadow:0 0 10px rgba(178,154,255,.7)}.landing-footer{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;color:#5d7590;font-size:10px}.landing-footer span:last-child{color:#7189a3}.login-panel{display:flex;align-items:center;justify-content:center;padding:42px 44px;background:linear-gradient(180deg,rgba(13,31,52,.62),rgba(7,19,34,.32))}.login-card{width:min(100%,360px)}.login-card-heading{margin-bottom:31px}.login-kicker{color:var(--login-cyan);font-size:10px;letter-spacing:.2em;font-weight:700}.login-title{margin:11px 0 9px;color:#edf7ff;font-size:29px;letter-spacing:-.05em;line-height:1.1}.login-copy{margin:0;color:var(--login-muted);font-size:12px;line-height:1.7}.login-form{display:grid;gap:17px}.login-field span{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;color:#9bb0c6;font-size:11px}.login-field input{display:block;width:100%;height:47px;padding:0 14px;border:1px solid rgba(153,185,221,.17);border-radius:11px;outline:0;background:rgba(3,12,24,.64);color:#e5f2ff;font:inherit;font-size:13px;transition:border-color .2s,box-shadow .2s,background .2s}.login-field input:hover{background:rgba(4,15,28,.8)}.login-field input:focus{border-color:rgba(98,230,212,.72);background:rgba(4,17,29,.86);box-shadow:0 0 0 3px rgba(98,230,212,.09),0 0 22px rgba(98,230,212,.06)}.login-submit{display:flex;align-items:center;justify-content:center;gap:9px;height:47px;margin-top:4px;border:0;border-radius:11px;background:linear-gradient(100deg,#62e6d4,#76e9dc);color:#05262a;font:700 13px inherit;cursor:pointer;box-shadow:0 14px 28px rgba(98,230,212,.16);transition:transform .2s,box-shadow .2s,filter .2s}.login-submit:hover{filter:brightness(1.06);transform:translateY(-1px);box-shadow:0 17px 32px rgba(98,230,212,.22)}.login-submit:disabled{opacity:.62;cursor:wait;transform:none}.login-error{min-height:16px;margin:-2px 0 -3px;color:#ff9baa;font-size:11px}.login-security{display:flex;gap:9px;margin:26px 0 0;padding-top:17px;border-top:1px solid rgba(153,185,221,.1);color:#637b96;font-size:10px;line-height:1.65}.login-security b{color:var(--login-cyan);font-size:12px;font-weight:500}.login-security span{display:block}.login-version{margin-top:25px;color:#506984;font-size:9px;letter-spacing:.08em}@media(max-width:820px){body{overflow:auto}.login-page{padding:20px}.login-stage{grid-template-columns:1fr;min-height:0;max-width:500px}.login-landing{min-height:320px;padding:29px 29px 25px;border-right:0;border-bottom:1px solid rgba(153,185,221,.1)}.landing-copy{margin:49px 0 0}.landing-title{font-size:39px}.landing-description{font-size:12px}.landing-points{margin-top:22px}.landing-footer{margin-top:40px}.login-panel{padding:35px 29px 39px}.login-card{max-width:390px}}@media(max-width:480px){.login-page{padding:0}.login-stage{width:100%;border:0;border-radius:0;min-height:100vh}.login-landing{min-height:300px;border-radius:0}.landing-title{font-size:34px}.landing-points{gap:7px}.landing-point{padding:8px 9px;font-size:9px}.login-panel{padding:32px 25px 35px}.login-title{font-size:26px}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="login-page">
|
|
<div class="login-stage">
|
|
<section class="login-landing" aria-label="Orbit 产品介绍">
|
|
<div class="login-header"><div class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></div><div><strong>Orbit</strong><small>星轨控制台</small></div></div>
|
|
<div class="landing-copy">
|
|
<div class="landing-kicker">CONTROL PLANE</div>
|
|
<h1 class="landing-title">让每一次执行<br><span>都清晰可见。</span></h1>
|
|
<p class="landing-description">统一观测任务、定时计划、流程实例与结果交付,让复杂的异步系统保持有序运行。</p>
|
|
<div class="landing-points"><span class="landing-point"><i></i>任务执行</span><span class="landing-point"><i></i>定时调度</span><span class="landing-point"><i></i>流程编排</span></div>
|
|
</div>
|
|
<div class="landing-footer"><span>Orbit Console</span><span>Pulse · Weave · v1</span></div>
|
|
</section>
|
|
<section class="login-panel" aria-label="账号登录">
|
|
<div class="login-card">
|
|
<div class="login-card-heading"><div class="login-kicker">WELCOME BACK</div><h2 class="login-title">登录控制台</h2><p class="login-copy">使用控制台账号进入你的运行空间。</p></div>
|
|
<form class="login-form" id="login-form" autocomplete="on">
|
|
<label class="login-field"><span>用户名</span><input id="username" name="username" type="text" autocomplete="username" maxlength="128" required autofocus></label>
|
|
<label class="login-field"><span>密码</span><input id="password" name="password" type="password" autocomplete="current-password" maxlength="1024" required></label>
|
|
<p class="login-error" id="login-error" role="alert"></p>
|
|
<button class="login-submit" id="login-submit" type="submit">登录 Orbit <span>→</span></button>
|
|
</form>
|
|
<div class="login-security"><b>◈</b><span>会话由服务端保存和校验,浏览器不会读取业务凭据。</span></div>
|
|
<div class="login-version">SECURE CONSOLE ACCESS · ORBIT</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</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');
|
|
const params = new URLSearchParams(location.search); let returnTo = params.get('return') || '/console/'; if (!returnTo.startsWith('/console') || returnTo.startsWith('//') || returnTo.startsWith('/console/login')) returnTo = '/console/';
|
|
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>
|