| <!DOCTYPE html>
|
| <html lang="zh-CN">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>管理员登录 - Media Gateway</title>
|
| <link rel="preconnect" href="https://fonts.googleapis.com">
|
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| <style>
|
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
|
|
|
| * {
|
| margin: 0;
|
| padding: 0;
|
| box-sizing: border-box;
|
| }
|
|
|
| body {
|
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| background-size: 200% 200%;
|
| animation: gradientShift 15s ease infinite;
|
| min-height: 100vh;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| padding: 20px;
|
| }
|
|
|
| @keyframes gradientShift {
|
| 0% { background-position: 0% 50%; }
|
| 50% { background-position: 100% 50%; }
|
| 100% { background-position: 0% 50%; }
|
| }
|
|
|
| @keyframes slideIn {
|
| from {
|
| opacity: 0;
|
| transform: translateY(-30px);
|
| }
|
| to {
|
| opacity: 1;
|
| transform: translateY(0);
|
| }
|
| }
|
|
|
| @keyframes shake {
|
| 0%, 100% { transform: translateX(0); }
|
| 25% { transform: translateX(-10px); }
|
| 75% { transform: translateX(10px); }
|
| }
|
|
|
| .login-container {
|
| background: rgba(255, 255, 255, 0.95);
|
| backdrop-filter: blur(20px);
|
| border-radius: 30px;
|
| box-shadow: 0 30px 80px rgba(0, 0, 0, 0.3);
|
| overflow: hidden;
|
| max-width: 480px;
|
| width: 100%;
|
| animation: slideIn 0.6s ease;
|
| }
|
|
|
| .login-header {
|
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| color: white;
|
| padding: 50px 40px;
|
| text-align: center;
|
| }
|
|
|
| .login-header h1 {
|
| font-size: 2.5em;
|
| margin-bottom: 12px;
|
| font-weight: 800;
|
| }
|
|
|
| .login-header p {
|
| font-size: 1.1em;
|
| opacity: 0.95;
|
| }
|
|
|
| .login-body {
|
| padding: 40px;
|
| }
|
|
|
| .form-group {
|
| margin-bottom: 25px;
|
| }
|
|
|
| .form-group label {
|
| display: block;
|
| margin-bottom: 10px;
|
| font-weight: 600;
|
| color: #1e293b;
|
| font-size: 0.95em;
|
| }
|
|
|
| .input-wrapper {
|
| position: relative;
|
| }
|
|
|
| .form-group input {
|
| width: 100%;
|
| padding: 16px 20px;
|
| border: 2px solid #e2e8f0;
|
| border-radius: 12px;
|
| font-size: 1em;
|
| transition: all 0.3s ease;
|
| background: white;
|
| font-family: inherit;
|
| }
|
|
|
| .form-group input:focus {
|
| outline: none;
|
| border-color: #6366f1;
|
| box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
|
| }
|
|
|
| .password-toggle {
|
| position: absolute;
|
| right: 16px;
|
| top: 50%;
|
| transform: translateY(-50%);
|
| background: none;
|
| border: none;
|
| cursor: pointer;
|
| color: #64748b;
|
| font-size: 1.3em;
|
| padding: 6px;
|
| }
|
|
|
| .password-toggle:hover {
|
| color: #6366f1;
|
| }
|
|
|
| .btn-login {
|
| width: 100%;
|
| padding: 18px;
|
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| color: white;
|
| border: none;
|
| border-radius: 12px;
|
| font-size: 1.1em;
|
| font-weight: 700;
|
| cursor: pointer;
|
| transition: all 0.3s ease;
|
| }
|
|
|
| .btn-login:hover:not(:disabled) {
|
| transform: translateY(-3px);
|
| box-shadow: 0 12px 35px rgba(99, 102, 241, 0.4);
|
| }
|
|
|
| .btn-login:disabled {
|
| opacity: 0.7;
|
| cursor: not-allowed;
|
| }
|
|
|
| .error-message {
|
| background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
| color: #991b1b;
|
| padding: 16px;
|
| border-radius: 12px;
|
| margin-bottom: 20px;
|
| display: none;
|
| animation: shake 0.5s;
|
| border: 2px solid #fca5a5;
|
| }
|
|
|
| .error-message.show {
|
| display: block;
|
| }
|
|
|
| .success-message {
|
| background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
|
| color: #065f46;
|
| padding: 16px;
|
| border-radius: 12px;
|
| margin-bottom: 20px;
|
| display: none;
|
| border: 2px solid #6ee7b7;
|
| }
|
|
|
| .success-message.show {
|
| display: block;
|
| }
|
|
|
| .info-box {
|
| background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
| color: #1e40af;
|
| padding: 16px;
|
| border-radius: 12px;
|
| margin-bottom: 25px;
|
| font-size: 0.9em;
|
| line-height: 1.6;
|
| border: 1px solid #93c5fd;
|
| }
|
|
|
| .info-box strong {
|
| display: block;
|
| margin-bottom: 8px;
|
| font-size: 1.05em;
|
| }
|
|
|
| .login-footer {
|
| padding: 25px 40px;
|
| background: rgba(248, 250, 252, 0.8);
|
| text-align: center;
|
| color: #64748b;
|
| font-size: 0.9em;
|
| border-top: 1px solid #e2e8f0;
|
| }
|
|
|
| .login-footer a {
|
| color: #6366f1;
|
| text-decoration: none;
|
| font-weight: 600;
|
| }
|
|
|
| .login-footer a:hover {
|
| text-decoration: underline;
|
| }
|
|
|
| @media (max-width: 480px) {
|
| .login-container {
|
| margin: 10px;
|
| }
|
|
|
| .login-header {
|
| padding: 40px 30px;
|
| }
|
|
|
| .login-header h1 {
|
| font-size: 2em;
|
| }
|
|
|
| .login-body {
|
| padding: 30px 25px;
|
| }
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <div class="login-container">
|
| <div class="login-header">
|
| <h1>🔐 管理员登录</h1>
|
| <p>Media Gateway Admin Panel</p>
|
| </div>
|
|
|
| <div class="login-body">
|
| <div id="errorMessage" class="error-message"></div>
|
| <div id="successMessage" class="success-message"></div>
|
|
|
| <div class="info-box">
|
| <strong>💡 管理员功能</strong>
|
| 登录后可创建和管理用户账号,配置系统设置
|
| </div>
|
|
|
| <form id="loginForm">
|
| <div class="form-group">
|
| <label for="username">👤 管理员账号</label>
|
| <input
|
| type="text"
|
| id="username"
|
| placeholder="请输入管理员账号"
|
| required
|
| autocomplete="username"
|
| >
|
| </div>
|
|
|
| <div class="form-group">
|
| <label for="password">🔒 管理员密码</label>
|
| <div class="input-wrapper">
|
| <input
|
| type="password"
|
| id="password"
|
| placeholder="请输入管理员密码"
|
| required
|
| autocomplete="current-password"
|
| >
|
| <button type="button" class="password-toggle" id="togglePassword">
|
| 👁️
|
| </button>
|
| </div>
|
| </div>
|
|
|
| <button type="submit" class="btn-login" id="loginBtn">
|
| 🚀 登录管理后台
|
| </button>
|
| </form>
|
| </div>
|
|
|
| <div class="login-footer">
|
| <a href="/">← 返回首页</a>
|
| </div>
|
| </div>
|
|
|
| <script>
|
| (function() {
|
| 'use strict';
|
|
|
| const API = window.location.origin;
|
|
|
| function togglePassword() {
|
| const passwordInput = document.getElementById('password');
|
| const toggleBtn = document.getElementById('togglePassword');
|
|
|
| if (!passwordInput || !toggleBtn) return;
|
|
|
| if (passwordInput.type === 'password') {
|
| passwordInput.type = 'text';
|
| toggleBtn.textContent = '🙈';
|
| } else {
|
| passwordInput.type = 'password';
|
| toggleBtn.textContent = '👁️';
|
| }
|
| }
|
|
|
| function showError(message) {
|
| const errorEl = document.getElementById('errorMessage');
|
| const successEl = document.getElementById('successMessage');
|
|
|
| if (!errorEl) return;
|
|
|
| successEl.classList.remove('show');
|
| errorEl.textContent = '❌ ' + message;
|
| errorEl.classList.add('show');
|
|
|
| setTimeout(() => {
|
| errorEl.classList.remove('show');
|
| }, 5000);
|
| }
|
|
|
| function showSuccess(message) {
|
| const successEl = document.getElementById('successMessage');
|
| const errorEl = document.getElementById('errorMessage');
|
|
|
| if (!successEl) return;
|
|
|
| errorEl.classList.remove('show');
|
| successEl.textContent = '✅ ' + message;
|
| successEl.classList.add('show');
|
| }
|
|
|
| async function sha256(message) {
|
| try {
|
| const msgBuffer = new TextEncoder().encode(message);
|
| const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
| const hashArray = Array.from(new Uint8Array(hashBuffer));
|
| return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
| } catch (error) {
|
| throw error;
|
| }
|
| }
|
|
|
| function clearAuth() {
|
| localStorage.removeItem('admin_token');
|
| localStorage.removeItem('admin_token_expiry');
|
| localStorage.removeItem('admin_username');
|
| }
|
|
|
| function saveAuth(token, username) {
|
| const expiry = Date.now() + (24 * 60 * 60 * 1000);
|
|
|
| localStorage.setItem('admin_token', token);
|
| localStorage.setItem('admin_token_expiry', expiry.toString());
|
| localStorage.setItem('admin_username', username);
|
|
|
| }
|
|
|
| async function login(username, password) {
|
| try {
|
| const passwordHash = await sha256(password);
|
|
|
| const response = await fetch(`${API}/api/admin/login`, {
|
| method: 'POST',
|
| headers: {
|
| 'Content-Type': 'application/json'
|
| },
|
| body: JSON.stringify({
|
| username: username,
|
| password_hash: passwordHash
|
| })
|
| });
|
|
|
|
|
| if (!response.ok) {
|
| const error = await response.json();
|
| throw new Error(error.message || '登录失败');
|
| }
|
|
|
| const data = await response.json();
|
|
|
| if (data.success && data.token) {
|
| saveAuth(data.token, username);
|
| return true;
|
| } else {
|
| throw new Error('登录失败:未返回有效 token');
|
| }
|
| } catch (error) {
|
| throw error;
|
| }
|
| }
|
|
|
| async function checkAuthStatus() {
|
| const token = localStorage.getItem('admin_token');
|
| const expiry = localStorage.getItem('admin_token_expiry');
|
|
|
| if (!token || !expiry) {
|
| return false;
|
| }
|
|
|
| if (Date.now() > parseInt(expiry)) {
|
| clearAuth();
|
| return false;
|
| }
|
|
|
| try {
|
| const response = await fetch(`${API}/api/admin/check`, {
|
| headers: {
|
| 'Authorization': `Bearer ${token}`
|
| }
|
| });
|
|
|
| if (response.ok) {
|
| const data = await response.json();
|
| if (data.authenticated) {
|
| return true;
|
| }
|
| }
|
|
|
| clearAuth();
|
| return false;
|
| } catch (error) {
|
| clearAuth();
|
| return false;
|
| }
|
| }
|
|
|
| function initialize() {
|
|
|
| const togglePasswordBtn = document.getElementById('togglePassword');
|
| if (togglePasswordBtn) {
|
| togglePasswordBtn.addEventListener('click', togglePassword);
|
| }
|
|
|
| const loginForm = document.getElementById('loginForm');
|
| if (loginForm) {
|
| loginForm.addEventListener('submit', async (e) => {
|
| e.preventDefault();
|
|
|
| const usernameInput = document.getElementById('username');
|
| const passwordInput = document.getElementById('password');
|
| const loginBtn = document.getElementById('loginBtn');
|
|
|
| if (!usernameInput || !passwordInput || !loginBtn) return;
|
|
|
| const username = usernameInput.value.trim();
|
| const password = passwordInput.value;
|
|
|
| if (!username || !password) {
|
| showError('请输入用户名和密码');
|
| return;
|
| }
|
|
|
| loginBtn.disabled = true;
|
| loginBtn.textContent = '验证中...';
|
|
|
| try {
|
| const success = await login(username, password);
|
|
|
| if (success) {
|
| showSuccess('登录成功!正在跳转...');
|
|
|
| setTimeout(() => {
|
| window.location.href = '/admin';
|
| }, 1500);
|
| } else {
|
| showError('登录失败,请重试');
|
| loginBtn.disabled = false;
|
| loginBtn.textContent = '🚀 登录管理后台';
|
| passwordInput.value = '';
|
| passwordInput.focus();
|
| }
|
| } catch (error) {
|
| showError(error.message || '登录失败,请重试');
|
| loginBtn.disabled = false;
|
| loginBtn.textContent = '🚀 登录管理后台';
|
| passwordInput.value = '';
|
| passwordInput.focus();
|
| }
|
| });
|
| }
|
|
|
| checkAuthStatus().then(isValid => {
|
| if (isValid) {
|
| window.location.href = '/admin';
|
| } else {
|
| }
|
| });
|
|
|
| setTimeout(() => {
|
| const usernameInput = document.getElementById('username');
|
| if (usernameInput) {
|
| usernameInput.focus();
|
| }
|
| }, 300);
|
| }
|
|
|
| if (document.readyState === 'loading') {
|
| document.addEventListener('DOMContentLoaded', initialize);
|
| } else {
|
| initialize();
|
| }
|
|
|
| })();
|
| </script>
|
| </body>
|
| </html> |