Implement session management with login functionality and add admin credentials to configuration
This commit is contained in:
parent
ac7be6ee02
commit
657188dc27
4
app/.env
4
app/.env
@ -20,3 +20,7 @@ LOG_LEVEL=INFO
|
||||
PORT_NUM=10
|
||||
|
||||
MAX_ONLINE=10000
|
||||
|
||||
ADMIN_USERNAME=heping
|
||||
ADMIN_PASSWORD=He_Ping551
|
||||
SESSION_SECRET=sinehiunsdkfi
|
||||
@ -17,6 +17,10 @@ class Settings(BaseModel):
|
||||
log_level: str = os.getenv("LOG_LEVEL", "INFO")
|
||||
port_num: int = int(os.getenv("PORT_NUM", 3))
|
||||
max_online: int = int(os.getenv("MAX_ONLINE", 3))
|
||||
# 前台登录配置
|
||||
admin_username: str = os.getenv("ADMIN_USERNAME", "admin")
|
||||
admin_password: str = os.getenv("ADMIN_PASSWORD", "admin")
|
||||
session_secret: str = os.getenv("SESSION_SECRET", "change-me-please")
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
30
app/main.py
30
app/main.py
@ -1,15 +1,19 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi import FastAPI, Request, Form
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from .config import settings
|
||||
from .routers.proxy import router as proxy_router
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
app = FastAPI(title="EIP Rotation Service")
|
||||
|
||||
# 设置模板目录
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
# 会话中间件(用于简单登录态)
|
||||
app.add_middleware(SessionMiddleware, secret_key=settings.session_secret)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
@ -19,6 +23,8 @@ def health_check():
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, id: str = None):
|
||||
"""主页 - IP轮换控制面板"""
|
||||
if not request.session.get("authenticated"):
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
return templates.TemplateResponse("index.html", {
|
||||
"request": request,
|
||||
"client_id": id or "1" # 默认ID为1
|
||||
@ -28,3 +34,23 @@ async def index(request: Request, id: str = None):
|
||||
app.include_router(proxy_router, prefix="/proxy", tags=["proxy"])
|
||||
|
||||
|
||||
@app.get("/login", response_class=HTMLResponse)
|
||||
async def login_page(request: Request):
|
||||
return templates.TemplateResponse("login.html", {"request": request, "error": None})
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
async def login_submit(request: Request, username: str = Form(...), password: str = Form(...)):
|
||||
if username == settings.admin_username and password == settings.admin_password:
|
||||
request.session["authenticated"] = True
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
# 登录失败,回到登录页并提示
|
||||
return templates.TemplateResponse("login.html", {"request": request, "error": "用户名或密码错误"}, status_code=401)
|
||||
|
||||
|
||||
@app.get("/logout")
|
||||
async def logout(request: Request):
|
||||
request.session.clear()
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
|
||||
|
||||
47
app/templates/login.html
Normal file
47
app/templates/login.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - EIP 控制面板</title>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; margin: 0; }
|
||||
.card { width: 360px; background: #fff; padding: 32px; border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.12); }
|
||||
h1 { margin: 0 0 8px; font-weight: 400; text-align: center; }
|
||||
p { margin: 0 0 24px; color: #666; text-align: center; }
|
||||
label { display: block; font-weight: 600; margin: 12px 0 8px; color: #333; }
|
||||
input[type="text"], input[type="password"] { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; }
|
||||
.btn { width: 100%; margin-top: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; border: none; padding: 10px 14px; border-radius: 10px; cursor: pointer; font-size: 15px; }
|
||||
.error { margin-top: 12px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; padding: 10px; border-radius: 8px; display: {{ 'block' if error else 'none' }}; }
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const u = document.getElementById('username');
|
||||
if (u) u.focus();
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
if (window.top !== window.self) {
|
||||
window.top.location = window.location;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>登录</h1>
|
||||
<p>进入 EIP IP 轮换控制面板</p>
|
||||
<form method="post" action="/login">
|
||||
<label for="username">用户名</label>
|
||||
<input id="username" name="username" type="text" autocomplete="username" required>
|
||||
<label for="password">密码</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required>
|
||||
<button class="btn" type="submit">登录</button>
|
||||
</form>
|
||||
<div class="error">{{ error }}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user