49 lines
6.1 KiB
HTML
49 lines
6.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Linux Login</title>
|
|
<style>
|
|
:root{--bg:#f4f8fc;--panel:#fff;--line:#d8e2ee;--text:#102033;--muted:#5d7187;--brand:#0f6db6;--soft:#e9f3ff}
|
|
[data-theme="dark"]{--bg:#08111d;--panel:#0c1826;--line:#1d3245;--text:#edf4fb;--muted:#9bb0c3;--brand:#67baff;--soft:rgba(103,186,255,.1)}
|
|
*{box-sizing:border-box} body{margin:0;min-height:100vh;display:grid;place-items:center;padding:20px;background:var(--bg);color:var(--text);font-family:"Segoe UI","Microsoft YaHei",sans-serif}
|
|
.card{width:min(100%,460px);background:var(--panel);border:1px solid var(--line);border-radius:20px;padding:28px} .toolbar,.tips{display:flex;flex-wrap:wrap;gap:10px}.toolbar{justify-content:flex-end;margin-bottom:16px}
|
|
button,input{font:inherit}.btn,.ghost,.chip{border-radius:999px;padding:10px 14px;font-weight:700}.btn{border:0;background:linear-gradient(135deg,var(--brand),#37a1ff);color:#fff;cursor:pointer;width:100%}.ghost{border:1px solid var(--line);background:transparent;color:var(--text);cursor:pointer}.chip{display:inline-flex;background:var(--soft);color:var(--brand)}
|
|
form{display:grid;gap:12px;margin-top:16px} input{padding:12px;border:1px solid var(--line);border-radius:12px;background:transparent;color:var(--text)} .eyebrow{font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--brand)} h1,p{margin:0} p{color:var(--muted);line-height:1.8}.msg{margin-top:14px;padding:12px;border:1px solid var(--line);border-radius:12px;color:var(--muted);white-space:pre-wrap}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<section class="card">
|
|
<div class="toolbar">
|
|
<button class="ghost" id="themeBtn"></button>
|
|
<button class="ghost" id="langBtn"></button>
|
|
</div>
|
|
<div class="eyebrow" id="eyebrow"></div>
|
|
<h1 id="title" style="margin:10px 0 8px;"></h1>
|
|
<p id="desc"></p>
|
|
<form id="loginForm">
|
|
<input id="user" type="text" autocomplete="username" />
|
|
<input id="pass" type="password" autocomplete="current-password" />
|
|
<button class="btn" type="submit" id="submitBtn"></button>
|
|
</form>
|
|
<div class="tips" style="margin-top:14px;">
|
|
<span class="chip" id="accountTip"></span>
|
|
<span class="chip" id="passwordTip"></span>
|
|
</div>
|
|
<div class="msg" id="msg"></div>
|
|
</section>
|
|
<script>
|
|
const D=v=>{const e=document.createElement('textarea');e.innerHTML=v;return e.value};const $=id=>document.getElementById(id);
|
|
const LANG={zh:{eyebrow:'安全入口',title:'Linux 学习实验室登录',desc:'登录后才能访问课程总览、练习终端和学习面板。',user:'用户名',pass:'密码',submit:'登录并进入学习台',themeDark:'深色模式',themeLight:'浅色模式',lang:'EN',account:'账号:按部署配置',password:'密码:按部署配置',ready:'请输入部署时配置的账号密码进入 Linux 学习项目。',loading:'正在登录...',failed:'登录失败,请检查账号与密码。'},en:{eyebrow:'Secure entry',title:'Linux Learning Lab Login',desc:'Sign in before opening the course overview, practice terminal, and learning dashboard.',user:'Username',pass:'Password',submit:'Sign in and enter lab',themeDark:'Dark mode',themeLight:'Light mode',lang:'涓枃',account:'Account: deployment configured',password:'Password: deployment configured',ready:'Enter the deployment-configured credentials to access the Linux learning project.',loading:'Signing in...',failed:'Login failed. Check the username and password.'}};
|
|
const state={language:localStorage.getItem('linux_lab_language')||'zh',theme:localStorage.getItem('linux_lab_theme')||'light'};const t=()=>LANG[state.language];const tx=k=>D(t()[k]||'');
|
|
function setTheme(theme,persist=true){state.theme=theme==='dark'?'dark':'light';document.documentElement.setAttribute('data-theme',state.theme);if(persist)localStorage.setItem('linux_lab_theme',state.theme);$('themeBtn').textContent=state.theme==='light'?tx('themeDark'):tx('themeLight')}
|
|
function render(){document.title=tx('title');document.documentElement.lang=state.language==='zh'?'zh-CN':'en';$('eyebrow').textContent=tx('eyebrow');$('title').textContent=tx('title');$('desc').textContent=tx('desc');$('user').placeholder=tx('user');$('pass').placeholder=tx('pass');$('submitBtn').textContent=tx('submit');$('langBtn').textContent=state.language==='zh'?'EN':'\u4E2D\u6587';$('accountTip').textContent=tx('account');$('passwordTip').textContent=tx('password');setTheme(state.theme,false);const n=localStorage.getItem('linux_lab_login_notice');$('msg').textContent=n||tx('ready');localStorage.removeItem('linux_lab_login_notice')}
|
|
async function checkSession(){const r=await fetch('/api/session',{credentials:'same-origin'});const p=await r.json();if(p.authenticated)location.href='/app'}
|
|
async function login(e){e.preventDefault();$('msg').textContent=tx('loading');const r=await fetch('/api/login',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:$('user').value.trim(),password:$('pass').value.trim()})});const p=await r.json();if(r.ok&&p.success){location.href='/app';return}$('msg').textContent=p.error||tx('failed')}
|
|
document.addEventListener('DOMContentLoaded',async()=>{render();await checkSession();$('themeBtn').addEventListener('click',()=>setTheme(state.theme==='light'?'dark':'light'));$('langBtn').addEventListener('click',()=>{state.language=state.language==='zh'?'en':'zh';localStorage.setItem('linux_lab_language',state.language);render()});$('loginForm').addEventListener('submit',login)})
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|