feat: 退出按钮改用SVG图标,密码配置外部化到auth.ini,设置页添加修改密码功能
This commit is contained in:
@@ -44,6 +44,9 @@ logs/
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# 认证配置(含密码)
|
||||
auth.ini
|
||||
|
||||
# 数据库
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
+50
-5
@@ -24,8 +24,37 @@ import hmac
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
import configparser
|
||||
|
||||
SECRET_KEY = "CapaReportSecretKey2026"
|
||||
ADMIN_PASSWORD = "admin" # 默认管理密码
|
||||
AUTH_INI_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "auth.ini")
|
||||
|
||||
def _ensure_auth_ini():
|
||||
"""确保 auth.ini 存在,不存在则创建默认配置"""
|
||||
if not os.path.exists(AUTH_INI_PATH):
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg["auth"] = {"username": "root", "password": "admin"}
|
||||
with open(AUTH_INI_PATH, "w", encoding="utf-8") as f:
|
||||
cfg.write(f)
|
||||
|
||||
def get_auth_config():
|
||||
"""从 auth.ini 读取认证配置"""
|
||||
_ensure_auth_ini()
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(AUTH_INI_PATH, encoding="utf-8")
|
||||
return {
|
||||
"username": cfg.get("auth", "username", fallback="root"),
|
||||
"password": cfg.get("auth", "password", fallback="admin")
|
||||
}
|
||||
|
||||
def save_auth_password(new_password: str):
|
||||
"""更新 auth.ini 中的密码"""
|
||||
_ensure_auth_ini()
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(AUTH_INI_PATH, encoding="utf-8")
|
||||
cfg.set("auth", "password", new_password)
|
||||
with open(AUTH_INI_PATH, "w", encoding="utf-8") as f:
|
||||
cfg.write(f)
|
||||
|
||||
def create_jwt_token(data: dict, expires_in: int = 86400 * 30) -> str:
|
||||
header = base64.urlsafe_b64encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()).decode().rstrip("=")
|
||||
@@ -111,12 +140,28 @@ async def jwt_middleware(request: Request, call_next):
|
||||
|
||||
@app.post("/api/login")
|
||||
async def login(username: str = Body(..., embed=True), password: str = Body(..., embed=True)):
|
||||
if username != "root" or password != ADMIN_PASSWORD:
|
||||
auth = get_auth_config()
|
||||
if username != auth["username"] or password != auth["password"]:
|
||||
return JSONResponse(status_code=401, content={"detail": "账号或密码错误"})
|
||||
token = create_jwt_token({"user": username})
|
||||
return {"success": True, "token": token}
|
||||
|
||||
|
||||
@app.post("/api/change-password")
|
||||
async def change_password(
|
||||
current_password: str = Body(..., embed=True),
|
||||
new_password: str = Body(..., embed=True)
|
||||
):
|
||||
"""修改登录密码"""
|
||||
auth = get_auth_config()
|
||||
if current_password != auth["password"]:
|
||||
return JSONResponse(status_code=400, content={"detail": "当前密码错误"})
|
||||
if len(new_password) < 4:
|
||||
return JSONResponse(status_code=400, content={"detail": "新密码长度不能少于4位"})
|
||||
save_auth_password(new_password)
|
||||
return {"success": True, "message": "密码修改成功"}
|
||||
|
||||
|
||||
# ==================== 健康检查 ====================
|
||||
|
||||
@app.get("/health")
|
||||
@@ -159,7 +204,7 @@ async def health_check():
|
||||
return {
|
||||
"status": "healthy" if is_healthy else "unhealthy",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"uptime_pid": os.getpid(),
|
||||
"checks": checks
|
||||
}
|
||||
@@ -1193,7 +1238,7 @@ async def get_service_status():
|
||||
"""获取服务运行状态"""
|
||||
return {
|
||||
"status": "running",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"platform": platform.system(),
|
||||
"supervisor": is_supervisor_running(),
|
||||
"pid": os.getpid(),
|
||||
@@ -1336,6 +1381,6 @@ async def save_script_content(content: str = Body(..., embed=True)):
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
print(f"CapacityReport v2.0.1")
|
||||
print(f"CapacityReport v2.0.2")
|
||||
print(f"配置更新时间: {config.update}")
|
||||
uvicorn.run("app.main:app", host="0.0.0.0", port=9081, reload=False)
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
- **API 401 处理**: `showLoginModal()` 函数保留,内部清除 token 后 `window.location.href = '/'` 跳转到登录页。
|
||||
- **涉及文件**: `static/login.html`(新增)、`app/main.py`、`static/index.html`、`static/js/app.js`、`static/css/style.css`
|
||||
|
||||
### 2026-04-23: 退出 SVG 图标 + INI 密码配置 + 修改密码功能
|
||||
- **退出按钮图标**: Unicode `⏻` 在浏览器中不显示,替换为明确的 SVG 退出图标(门+箭头样式)。
|
||||
- **密码外部配置**: 账号密码从 `main.py` 硬编码移到 `auth.ini` 文件。使用 `configparser` 读写,每次登录/改密码都实时读取。`auth.ini` 已加入 `.gitignore`。首次运行不存在时自动创建默认配置 `root/admin`。
|
||||
- **修改密码功能**:
|
||||
- 后端 `/api/change-password` 接口,验证当前密码后写入新密码到 `auth.ini`。
|
||||
- 前端设置页左侧列新增"修改登录密码"卡片(当前密码 + 新密码 + 确认新密码),修改成功后自动退出重新登录。
|
||||
- **涉及文件**: `auth.ini`(新增)、`app/main.py`、`static/index.html`、`static/js/app.js`、`.gitignore`
|
||||
|
||||
### 2026-04-22: 增加 JWT 鉴权和接口安全控制
|
||||
- **问题背景**: 网管部门通报安全问题,扫描到项目存在暴露的 API 文档(/docs, /redoc, /openapi.json),并且 API 接口没有使用授权控制,要求快速增加鉴权。
|
||||
- **架构变更**:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
title CapacityReport v2.0.1 [自动重启模式]
|
||||
title CapacityReport v2.0.2 [自动重启模式]
|
||||
|
||||
echo ========================================
|
||||
echo CapacityReport - 容量报表处理程序
|
||||
|
||||
+61
-7
@@ -41,9 +41,9 @@
|
||||
<span>系统设置</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="sidebar-footer" data-version="v2.0.1">
|
||||
<div class="sidebar-footer" data-version="v2.0.2">
|
||||
<div class="sidebar-footer-info">
|
||||
<span class="version">版本:v2.0.1</span>
|
||||
<span class="version">版本:v2.0.2</span>
|
||||
<span class="version">Power by:NIXEVOL</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +73,13 @@
|
||||
<line x1="8" y1="4" x2="8" y2="6.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">⏻</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -184,7 +190,13 @@
|
||||
<line x1="8" y1="4" x2="8" y2="6.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">⏻</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-body">
|
||||
@@ -219,7 +231,13 @@
|
||||
<line x1="8" y1="4" x2="8" y2="6.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">⏻</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-body" style="display: flex; flex-direction: column; height: calc(100% - 64px);">
|
||||
@@ -319,7 +337,13 @@
|
||||
<line x1="8" y1="4" x2="8" y2="6.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">⏻</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-body script-page-body">
|
||||
@@ -385,7 +409,13 @@
|
||||
<line x1="8" y1="4" x2="8" y2="6.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">⏻</button>
|
||||
<button class="logout-btn" id="logoutBtn" title="退出登录">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-body settings-page-body">
|
||||
@@ -453,6 +483,30 @@
|
||||
<button class="btn btn-primary" id="saveSheetFilter">保存规则</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<div class="card settings-card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">修改登录密码</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>当前密码</label>
|
||||
<input type="password" id="currentPassword" class="form-input" placeholder="输入当前密码">
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label>新密码</label>
|
||||
<input type="password" id="newPassword" class="form-input" placeholder="输入新密码">
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label>确认新密码</label>
|
||||
<input type="password" id="confirmPassword" class="form-input" placeholder="再次输入新密码">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" id="changePassword">修改密码</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧列 - 字段映射 -->
|
||||
|
||||
+32
-1
@@ -2225,7 +2225,7 @@ function initApp() {
|
||||
// 恢复上次访问的页面
|
||||
navigation.restorePage();
|
||||
|
||||
console.log('CapacityReport v2.0.1 已加载');
|
||||
console.log('CapacityReport v2.0.2 已加载');
|
||||
|
||||
// 退出登录按钮事件(事件委托)
|
||||
document.addEventListener('click', (e) => {
|
||||
@@ -2235,6 +2235,37 @@ function initApp() {
|
||||
logout();
|
||||
}
|
||||
});
|
||||
|
||||
// 修改密码按钮事件
|
||||
const changePwdBtn = $('#changePassword');
|
||||
if (changePwdBtn) {
|
||||
changePwdBtn.addEventListener('click', async () => {
|
||||
const currentPwd = $('#currentPassword')?.value;
|
||||
const newPwd = $('#newPassword')?.value;
|
||||
const confirmPwd = $('#confirmPassword')?.value;
|
||||
|
||||
if (!currentPwd) { showToast('请输入当前密码', 'warning'); return; }
|
||||
if (!newPwd) { showToast('请输入新密码', 'warning'); return; }
|
||||
if (newPwd.length < 4) { showToast('新密码长度不能少于4位', 'warning'); return; }
|
||||
if (newPwd !== confirmPwd) { showToast('两次输入的新密码不一致', 'warning'); return; }
|
||||
|
||||
try {
|
||||
const res = await api('/change-password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ current_password: currentPwd, new_password: newPwd })
|
||||
});
|
||||
if (res.success) {
|
||||
showToast('密码修改成功,请重新登录', 'success');
|
||||
$('#currentPassword').value = '';
|
||||
$('#newPassword').value = '';
|
||||
$('#confirmPassword').value = '';
|
||||
setTimeout(() => logout(), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(err.message || '密码修改失败', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 重启服务按钮事件(使用事件委托,支持所有页面的重启按钮)
|
||||
document.addEventListener('click', async (e) => {
|
||||
|
||||
Reference in New Issue
Block a user