feat: settings password change, migrate desktop client with hub activation/report
This commit is contained in:
@@ -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
@@ -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():
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user