feat: settings password change, migrate desktop client with hub activation/report

This commit is contained in:
dekun
2026-10-17 17:50:42 +08:00
parent fb35968a1f
commit 1d4d5b06df
14 changed files with 1933 additions and 26 deletions
+62
View File
@@ -101,6 +101,11 @@ def init_db() -> None:
open REAL, high REAL, low REAL, close REAL, volume REAL,
UNIQUE(symbol, timeframe, ts)
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
"""
)
@@ -259,7 +264,23 @@ def get_bars(conn: sqlite3.Connection, symbol: str, timeframe: str, start_ts: fl
return [row_to_dict(r) for r in cur.fetchall()]
def get_setting(conn: sqlite3.Connection, key: str, default: str = "") -> str:
row = conn.execute("SELECT value FROM settings WHERE key=?", (key,)).fetchone()
return row["value"] if row else default
def set_setting(conn: sqlite3.Connection, key: str, value: str) -> None:
conn.execute(
"INSERT INTO settings(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value",
(key, value),
)
def seed_if_empty() -> None:
from werkzeug.security import generate_password_hash
from app.config import Config
with get_db() as conn:
n = conn.execute("SELECT COUNT(*) AS c FROM accounts").fetchone()["c"]
if n == 0:
@@ -273,3 +294,44 @@ def seed_if_empty() -> None:
"INSERT INTO machines(name, device_fingerprint, note, enabled, created_at) VALUES(?,?,?,?,?)",
("未绑定机器", "", "下载通用包后用激活码绑定", 1, now()),
)
if not get_setting(conn, "admin_user"):
set_setting(conn, "admin_user", Config.ADMIN_USER)
if not get_setting(conn, "admin_password_hash"):
set_setting(
conn,
"admin_password_hash",
generate_password_hash(Config.ADMIN_PASSWORD),
)
def verify_admin(username: str, password: str) -> bool:
from werkzeug.security import check_password_hash
with get_db() as conn:
user = get_setting(conn, "admin_user", "admin")
pwd_hash = get_setting(conn, "admin_password_hash")
if username != user:
return False
if not pwd_hash:
from app.config import Config
return password == Config.ADMIN_PASSWORD
return check_password_hash(pwd_hash, password)
def change_admin_password(old_password: str, new_password: str) -> None:
from werkzeug.security import check_password_hash, generate_password_hash
if len(new_password) < 6:
raise ValueError("新密码至少 6 位")
with get_db() as conn:
pwd_hash = get_setting(conn, "admin_password_hash")
if pwd_hash:
if not check_password_hash(pwd_hash, old_password):
raise ValueError("当前密码不正确")
else:
from app.config import Config
if old_password != Config.ADMIN_PASSWORD:
raise ValueError("当前密码不正确")
set_setting(conn, "admin_password_hash", generate_password_hash(new_password))
+22 -1
View File
@@ -50,7 +50,7 @@ def register_routes(app: Flask) -> None:
def login_post():
user = (request.form.get("username") or "").strip()
pwd = request.form.get("password") or ""
if user == Config.ADMIN_USER and pwd == Config.ADMIN_PASSWORD:
if database.verify_admin(user, pwd):
session["user"] = user
return redirect(request.args.get("next") or url_for("dashboard"))
flash("用户名或密码错误", "error")
@@ -61,6 +61,27 @@ def register_routes(app: Flask) -> None:
session.clear()
return redirect(url_for("login"))
@app.get("/settings")
@login_required
def settings_page():
return render_template("settings.html")
@app.post("/settings/password")
@login_required
def settings_password():
old_pwd = request.form.get("old_password") or ""
new_pwd = request.form.get("new_password") or ""
new_pwd2 = request.form.get("new_password2") or ""
if new_pwd != new_pwd2:
flash("两次输入的新密码不一致", "error")
return redirect(url_for("settings_page"))
try:
database.change_admin_password(old_pwd, new_pwd)
flash("密码已更新,请牢记新密码", "ok")
except ValueError as exc:
flash(str(exc), "error")
return redirect(url_for("settings_page"))
@app.get("/")
@login_required
def dashboard():
+1
View File
@@ -16,6 +16,7 @@
<a href="{{ url_for('download_page') }}" class="{% if request.endpoint and 'download' in request.endpoint %}active{% endif %}">下载中心</a>
<a href="{{ url_for('accounts_page') }}" class="{% if request.endpoint and 'account' in request.endpoint %}active{% endif %}">账户</a>
<a href="{{ url_for('machines_page') }}" class="{% if request.endpoint and 'machine' in request.endpoint %}active{% endif %}">机器</a>
<a href="{{ url_for('settings_page') }}" class="{% if request.endpoint and 'settings' in request.endpoint %}active{% endif %}">系统设置</a>
</nav>
<div class="right">{{ session.get('user') }} · <a href="{{ url_for('logout') }}">退出</a></div>
</header>
+33
View File
@@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block title %}系统设置 · {{ app_name }}{% endblock %}
{% block content %}
<h2 style="margin:0 0 14px">系统设置</h2>
<div class="grid cols-2">
<div class="card">
<h3>修改登录密码</h3>
<p class="muted">修改后立即生效,无需重启服务。请使用足够强度的密码。</p>
<form method="post" action="{{ url_for('settings_password') }}" style="margin-top:12px">
<div class="form-row">
<label>当前密码</label>
<input name="old_password" type="password" autocomplete="current-password" required>
</div>
<div class="form-row">
<label>新密码(至少 6 位)</label>
<input name="new_password" type="password" autocomplete="new-password" required minlength="6">
</div>
<div class="form-row">
<label>确认新密码</label>
<input name="new_password2" type="password" autocomplete="new-password" required minlength="6">
</div>
<button class="btn" type="submit">保存密码</button>
</form>
</div>
<div class="card">
<h3>服务信息</h3>
<p class="muted">对外地址:{{ public_base }}</p>
<p class="muted">端口:5800</p>
<p class="muted">当前登录用户:{{ session.get('user') }}</p>
<p class="muted" style="margin-top:12px">通用 exe 放置目录:<code>releases/黄金市价辅助.exe</code></p>
</div>
</div>
{% endblock %}