new
This commit is contained in:
Binary file not shown.
43
main.py
43
main.py
@@ -1234,6 +1234,49 @@ def add_user_api(user_data: AddUserRequest):
|
|||||||
release_db_connection(conn)
|
release_db_connection(conn)
|
||||||
return JSONResponse(content={"success": False, "message": f"添加失败: {str(e)}"}, status_code=500)
|
return JSONResponse(content={"success": False, "message": f"添加失败: {str(e)}"}, status_code=500)
|
||||||
|
|
||||||
|
@app.post("/api/admin/login")
|
||||||
|
async def admin_login(request: Request):
|
||||||
|
"""
|
||||||
|
管理后台登录验证接口。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
password = body.get('password', '')
|
||||||
|
|
||||||
|
secret = os.getenv("ADD_USER_SECRET", "123quant-speed")
|
||||||
|
|
||||||
|
if password == secret:
|
||||||
|
response = JSONResponse(content={"success": True, "message": "登录成功"})
|
||||||
|
response.set_cookie(
|
||||||
|
key="admin_auth",
|
||||||
|
value="true",
|
||||||
|
httponly=True,
|
||||||
|
max_age=60*60*24*7,
|
||||||
|
samesite="lax"
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return JSONResponse(content={"success": False, "message": "密码错误"}, status_code=401)
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(content={"success": False, "message": str(e)}, status_code=500)
|
||||||
|
|
||||||
|
@app.get("/api/admin/check-auth")
|
||||||
|
async def check_admin_auth(request: Request):
|
||||||
|
"""
|
||||||
|
检查管理员是否已登录。
|
||||||
|
"""
|
||||||
|
auth_cookie = request.cookies.get("admin_auth")
|
||||||
|
return {"success": auth_cookie == "true", "authenticated": auth_cookie == "true"}
|
||||||
|
|
||||||
|
@app.post("/api/admin/logout")
|
||||||
|
async def admin_logout():
|
||||||
|
"""
|
||||||
|
管理员退出登录。
|
||||||
|
"""
|
||||||
|
response = JSONResponse(content={"success": True, "message": "已退出登录"})
|
||||||
|
response.delete_cookie("admin_auth")
|
||||||
|
return response
|
||||||
|
|
||||||
@app.get("/admin", response_class=HTMLResponse)
|
@app.get("/admin", response_class=HTMLResponse)
|
||||||
async def admin_page(request: Request):
|
async def admin_page(request: Request):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -297,28 +297,62 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Use secret from backend
|
document.addEventListener('DOMContentLoaded', checkAuthStatus);
|
||||||
const ACCESS_SECRET = "{{ secret }}";
|
|
||||||
|
|
||||||
// Password Check Logic
|
async function checkAuthStatus() {
|
||||||
function checkSecret() {
|
try {
|
||||||
const input = document.getElementById('secret-input');
|
const res = await fetch('/api/admin/check-auth');
|
||||||
const error = document.getElementById('secret-error');
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.authenticated) {
|
||||||
|
hideOverlay();
|
||||||
|
} else {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOverlay() {
|
||||||
|
document.getElementById('password-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOverlay() {
|
||||||
const overlay = document.getElementById('password-overlay');
|
const overlay = document.getElementById('password-overlay');
|
||||||
|
|
||||||
if (input.value === ACCESS_SECRET) {
|
|
||||||
overlay.style.opacity = '0';
|
overlay.style.opacity = '0';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
overlay.style.display = 'none';
|
overlay.style.display = 'none';
|
||||||
}, 500);
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSecret() {
|
||||||
|
const input = document.getElementById('secret-input');
|
||||||
|
const error = document.getElementById('secret-error');
|
||||||
|
const password = input.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({password: password})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
error.classList.add('hidden');
|
||||||
|
hideOverlay();
|
||||||
} else {
|
} else {
|
||||||
error.classList.remove('hidden');
|
error.classList.remove('hidden');
|
||||||
input.value = '';
|
input.value = '';
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.textContent = '网络错误';
|
||||||
|
error.classList.remove('hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow pressing Enter to submit password
|
|
||||||
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
checkSecret();
|
checkSecret();
|
||||||
|
|||||||
@@ -330,6 +330,27 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="padding: 20px;">
|
||||||
|
<h2 style="margin-bottom: 15px;">快捷导航</h2>
|
||||||
|
<div style="display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;">
|
||||||
|
<a href="/add-user" style="text-decoration: none;">
|
||||||
|
<button style="background: linear-gradient(90deg, #28a745 0%, #20c997 100%);">
|
||||||
|
➕ 添加嘉宾
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a href="/edit" style="text-decoration: none;">
|
||||||
|
<button style="background: linear-gradient(90deg, #ffc107 0%, #fd7e14 100%);">
|
||||||
|
✏️ 编辑信息
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a href="/data-base" style="text-decoration: none;">
|
||||||
|
<button style="background: linear-gradient(90deg, #17a2b8 0%, #138496 100%);">
|
||||||
|
🗄️ 数据库管理
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>基本信息设置</h2>
|
<h2>基本信息设置</h2>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -603,27 +624,62 @@
|
|||||||
header.parentElement.classList.toggle('collapsed');
|
header.parentElement.classList.toggle('collapsed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
checkAuthStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use secret from backend
|
async function checkAuthStatus() {
|
||||||
const ACCESS_SECRET = "{{ secret }}";
|
try {
|
||||||
|
const res = await fetch('/api/admin/check-auth');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
// Password Check Logic
|
if (data.authenticated) {
|
||||||
function checkSecret() {
|
hideOverlay();
|
||||||
const input = document.getElementById('secret-input');
|
} else {
|
||||||
const error = document.getElementById('secret-error');
|
showOverlay();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOverlay() {
|
||||||
|
document.getElementById('password-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOverlay() {
|
||||||
const overlay = document.getElementById('password-overlay');
|
const overlay = document.getElementById('password-overlay');
|
||||||
|
|
||||||
if (input.value === ACCESS_SECRET) {
|
|
||||||
overlay.style.opacity = '0';
|
overlay.style.opacity = '0';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
overlay.style.display = 'none';
|
overlay.style.display = 'none';
|
||||||
}, 500);
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSecret() {
|
||||||
|
const input = document.getElementById('secret-input');
|
||||||
|
const error = document.getElementById('secret-error');
|
||||||
|
const password = input.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({password: password})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
error.style.display = 'none';
|
||||||
|
hideOverlay();
|
||||||
} else {
|
} else {
|
||||||
error.style.display = 'block';
|
error.style.display = 'block';
|
||||||
input.value = '';
|
input.value = '';
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.textContent = '网络错误';
|
||||||
|
error.style.display = 'block';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
||||||
|
|||||||
@@ -360,6 +360,18 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Password Overlay -->
|
||||||
|
<div id="password-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--bg-color); z-index: 9999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(10px);">
|
||||||
|
<div class="card" style="text-align: center; width: 90%; max-width: 400px;">
|
||||||
|
<h2 style="margin-bottom: 20px; color: var(--primary-color);">请输入访问密钥</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="password" id="secret-input" placeholder="输入密钥" style="text-align: center;">
|
||||||
|
</div>
|
||||||
|
<button onclick="checkSecret()">确认</button>
|
||||||
|
<p id="secret-error" style="color: #ff6b6b; margin-top: 15px; display: none;">密钥错误</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>数据库管理</h1>
|
<h1>数据库管理</h1>
|
||||||
|
|
||||||
@@ -410,6 +422,68 @@
|
|||||||
<div id="message"></div>
|
<div id="message"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', checkAuthStatus);
|
||||||
|
|
||||||
|
async function checkAuthStatus() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/check-auth');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.authenticated) {
|
||||||
|
hideOverlay();
|
||||||
|
} else {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOverlay() {
|
||||||
|
document.getElementById('password-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOverlay() {
|
||||||
|
const overlay = document.getElementById('password-overlay');
|
||||||
|
overlay.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSecret() {
|
||||||
|
const input = document.getElementById('secret-input');
|
||||||
|
const error = document.getElementById('secret-error');
|
||||||
|
const password = input.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({password: password})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
error.style.display = 'none';
|
||||||
|
hideOverlay();
|
||||||
|
} else {
|
||||||
|
error.style.display = 'block';
|
||||||
|
input.value = '';
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.textContent = '网络错误';
|
||||||
|
error.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
checkSecret();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let currentTable = '';
|
let currentTable = '';
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
let pageSize = 50;
|
let pageSize = 50;
|
||||||
|
|||||||
@@ -273,6 +273,18 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Password Overlay -->
|
||||||
|
<div id="password-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--bg-color); z-index: 9999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(10px);">
|
||||||
|
<div class="card" style="text-align: center; width: 90%; max-width: 400px;">
|
||||||
|
<h2 style="margin-bottom: 20px;">请输入访问密钥</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" id="secret-input" placeholder="输入密钥" style="text-align: center;">
|
||||||
|
</div>
|
||||||
|
<button onclick="checkSecret()">确认</button>
|
||||||
|
<p id="secret-error" class="error-msg hidden">密钥错误</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<header>
|
||||||
<img src="{{ config.header_image }}" alt="{{ config.event_title }}" class="header-img" onerror="this.style.display='none'">
|
<img src="{{ config.header_image }}" alt="{{ config.event_title }}" class="header-img" onerror="this.style.display='none'">
|
||||||
@@ -384,6 +396,68 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', checkAuthStatus);
|
||||||
|
|
||||||
|
async function checkAuthStatus() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/check-auth');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.authenticated) {
|
||||||
|
hideOverlay();
|
||||||
|
} else {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOverlay() {
|
||||||
|
document.getElementById('password-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOverlay() {
|
||||||
|
const overlay = document.getElementById('password-overlay');
|
||||||
|
overlay.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSecret() {
|
||||||
|
const input = document.getElementById('secret-input');
|
||||||
|
const error = document.getElementById('secret-error');
|
||||||
|
const password = input.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({password: password})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
error.classList.add('hidden');
|
||||||
|
hideOverlay();
|
||||||
|
} else {
|
||||||
|
error.classList.remove('hidden');
|
||||||
|
input.value = '';
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.textContent = '网络错误';
|
||||||
|
error.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
checkSecret();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function searchUser() {
|
async function searchUser() {
|
||||||
const query = document.getElementById('search-input').value.trim();
|
const query = document.getElementById('search-input').value.trim();
|
||||||
const btn = document.getElementById('search-btn');
|
const btn = document.getElementById('search-btn');
|
||||||
|
|||||||
Reference in New Issue
Block a user