<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SOAR MCP Server - 管理员登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body, html {
height: 100%;
font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow-x: hidden;
}
/* 动态背景 */
.background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: linear-gradient(135deg,
#3b82f6 0%,
#2563eb 15%,
#1d4ed8 30%,
#1e40af 45%,
#0ea5e9 60%,
#06b6d4 75%,
#14b8a6 90%,
#38bdf8 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
z-index: -2;
}
.background::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 20% 80%, rgba(59, 130, 246, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(56, 189, 248, 0.3) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(20, 184, 166, 0.3) 0%, transparent 50%);
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* 浮动装饰元素 */
.floating-elements {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: -1;
}
.floating-shape {
position: absolute;
opacity: 0.1;
animation: float 20s infinite ease-in-out;
z-index: -10;
}
.shape-1 {
width: 100px;
height: 100px;
background: linear-gradient(45deg, #3b82f6, #14b8a6);
border-radius: 50%;
top: 10%;
left: 10%;
animation-delay: 0s;
}
.shape-2 {
width: 150px;
height: 150px;
background: linear-gradient(45deg, #38bdf8, #2563eb);
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
top: 60%;
right: 10%;
animation-delay: -5s;
}
.shape-3 {
width: 80px;
height: 80px;
background: linear-gradient(45deg, #0ea5e9, #06b6d4);
border-radius: 20px;
top: 30%;
right: 30%;
animation-delay: -10s;
transform: rotate(45deg);
}
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg) scale(1); }
25% { transform: translateY(-20px) rotate(90deg) scale(1.1); }
50% { transform: translateY(-40px) rotate(180deg) scale(0.9); }
75% { transform: translateY(-20px) rotate(270deg) scale(1.1); }
}
/* 主容器 */
.container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
position: relative;
z-index: 1;
}
.login-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(30px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
width: 100%;
max-width: 450px;
text-align: center;
padding: 3rem 2.5rem;
position: relative;
animation: cardFloat 6s ease-in-out infinite;
}
@keyframes cardFloat {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
}
.login-container::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #3b82f6, #14b8a6, #0ea5e9, #06b6d4, #38bdf8);
border-radius: 26px;
z-index: -1;
animation: borderGlow 3s ease-in-out infinite alternate;
opacity: 0.3;
}
@keyframes borderGlow {
0% { opacity: 0.2; }
100% { opacity: 0.5; }
}
.login-header {
margin-bottom: 2rem;
}
.login-header h1 {
color: white;
margin-bottom: 0.5rem;
font-size: 2.2rem;
font-weight: 700;
letter-spacing: -0.02em;
}
.login-header p {
color: rgba(255, 255, 255, 0.9);
font-size: 1rem;
font-weight: 400;
}
.form-group {
margin-bottom: 1.5rem;
text-align: left;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: white;
font-weight: 600;
font-size: 0.95rem;
}
.form-group input {
width: 100%;
padding: 14px 16px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
font-size: 1rem;
color: white;
transition: all 0.3s ease;
}
.form-group input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.form-group input:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.25);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.login-btn {
width: 100%;
padding: 14px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
color: white;
font-size: 1.05rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.login-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
background: rgba(255, 255, 255, 0.3);
}
.login-btn:hover::before {
left: 100%;
}
.login-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.error-message {
background: #fee;
color: #c33;
padding: 10px;
border-radius: 5px;
margin-bottom: 1rem;
border-left: 4px solid #c33;
display: none;
}
.success-message {
background: #efe;
color: #3c3;
padding: 10px;
border-radius: 5px;
margin-bottom: 1rem;
border-left: 4px solid #3c3;
display: none;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.token-hint {
background: #f0f8ff;
color: #4a90e2;
padding: 10px;
border-radius: 5px;
margin-bottom: 1rem;
border-left: 4px solid #4a90e2;
text-align: left;
font-size: 0.9rem;
}
/* 底部信息样式 */
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 1rem 0;
text-align: center;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(56, 189, 248, 0.2);
z-index: 1000;
}
.footer-content {
color: #ffffff;
font-size: 0.85rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
position: relative;
z-index: 1001;
}
.footer-content a {
color: #38bdf8;
text-decoration: none;
font-weight: 600;
margin: 0 0.5rem;
transition: all 0.3s ease;
position: relative;
z-index: 1002;
cursor: pointer;
}
.footer-content a:hover {
color: #14b8a6;
text-shadow: 0 0 8px rgba(56, 189, 248, 0.6);
}
</style>
</head>
<body>
<!-- 动态背景 -->
<div class="background"></div>
<!-- 浮动装饰元素 -->
<div class="floating-elements">
<div class="floating-shape shape-1"></div>
<div class="floating-shape shape-2"></div>
<div class="floating-shape shape-3"></div>
</div>
<div class="container">
<div class="login-container">
<div class="login-header">
<h1 style="display: flex; align-items: center; gap: 12px; justify-content: center;">
<img src="/static/logo.webp"
alt="SOAR MCP Server Logo"
style="width: 40px; height: 40px; border-radius: 8px; object-fit: cover;">
SOAR MCP Server
</h1>
<p>管理员登录</p>
</div>
<div class="token-hint">
💡 请从服务器启动日志中获取管理员密码进行登录
</div>
<div class="error-message" id="errorMessage"></div>
<div class="success-message" id="successMessage"></div>
<form id="loginForm">
<div class="form-group">
<label for="adminPassword">管理员密码</label>
<input type="password" id="adminPassword" name="adminPassword" required
placeholder="输入管理员密码..." maxlength="100">
</div>
<button type="submit" class="login-btn" id="loginBtn">
登录
</button>
</form>
</div>
</div>
<script>
// 添加鼠标跟踪效果
document.addEventListener('mousemove', (e) => {
const shapes = document.querySelectorAll('.floating-shape');
const x = e.clientX / window.innerWidth;
const y = e.clientY / window.innerHeight;
shapes.forEach((shape, index) => {
const speed = (index + 1) * 0.3;
const xOffset = (x - 0.5) * speed * 30;
const yOffset = (y - 0.5) * speed * 30;
shape.style.transform += ` translate(${xOffset}px, ${yOffset}px)`;
});
});
// 添加登录容器倾斜效果
const loginContainer = document.querySelector('.login-container');
loginContainer.addEventListener('mousemove', (e) => {
const rect = loginContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 30;
const rotateY = (centerX - x) / 30;
loginContainer.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateY(-5px)`;
});
loginContainer.addEventListener('mouseleave', () => {
loginContainer.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) translateY(0)';
});
// 原有的登录逻辑
const loginForm = document.getElementById('loginForm');
const loginBtn = document.getElementById('loginBtn');
const errorMessage = document.getElementById('errorMessage');
const successMessage = document.getElementById('successMessage');
const adminPasswordInput = document.getElementById('adminPassword');
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
successMessage.style.display = 'none';
}
function showSuccess(message) {
successMessage.textContent = message;
successMessage.style.display = 'block';
errorMessage.style.display = 'none';
}
function hideMessages() {
errorMessage.style.display = 'none';
successMessage.style.display = 'none';
}
function setLoading(loading) {
if (loading) {
loginBtn.disabled = true;
loginBtn.innerHTML = '<span class="loading"></span>登录中...';
} else {
loginBtn.disabled = false;
loginBtn.innerHTML = '登录';
}
}
loginForm.addEventListener('submit', async function(e) {
e.preventDefault();
const adminPassword = adminPasswordInput.value.trim();
if (!adminPassword) {
showError('请输入管理员密码');
return;
}
hideMessages();
setLoading(true);
try {
const response = await fetch('/api/admin/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ adminPassword })
});
const data = await response.json();
if (response.ok && data.success) {
// 存储JWT token
localStorage.setItem('jwt_token', data.jwt);
showSuccess('登录成功,即将跳转...');
// 2秒后跳转到管理页面
setTimeout(() => {
window.location.href = '/admin';
}, 2000);
} else {
showError(data.error || '登录失败,请检查密码是否正确');
}
} catch (error) {
console.error('Login error:', error);
showError('登录过程中发生错误,请重试');
} finally {
setLoading(false);
}
});
// 页面加载时检查是否已经登录
window.addEventListener('load', function() {
const jwt = localStorage.getItem('jwt_token');
if (jwt) {
// 验证token是否有效
fetch('/api/admin/verify', {
headers: {
'Authorization': `Bearer ${jwt}`
}
}).then(response => {
if (response.ok) {
// Token有效,直接跳转
window.location.href = '/admin';
} else {
// Token无效,清除
localStorage.removeItem('jwt_token');
}
}).catch(() => {
// 验证失败,清除token
localStorage.removeItem('jwt_token');
});
}
});
// 输入框聚焦时隐藏错误信息
adminPasswordInput.addEventListener('focus', hideMessages);
</script>
<!-- 底部信息 -->
<footer class="footer">
<div class="footer-content">
雾帜智能@2025 <a href="https://flagify.com" target="_blank">最牛的SOAR</a> <a href="https://github.com/flagify-com/OctoMation/wiki" target="_blank">OctoMation</a> | <a href="https://github.com/flagify-com/soar-mcp" target="_blank">📦 Github项目</a>
</div>
</footer>
</body>
</html>