Improve machines page: online status, formatted time, delete unbound placeholders.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-17 20:59:52 +08:00
parent 679b55f1db
commit ba74a9cce8
3 changed files with 78 additions and 26 deletions
+18
View File
@@ -493,6 +493,24 @@ def delete_account(conn: sqlite3.Connection, account_id: int) -> None:
conn.execute("DELETE FROM accounts WHERE id=?", (account_id,))
def delete_machine(conn: sqlite3.Connection, machine_id: int) -> None:
"""删除机器。在线中的不允许删;相关激活/成交仅解除 machine 引用。"""
m = conn.execute("SELECT * FROM machines WHERE id=?", (machine_id,)).fetchone()
if not m:
raise ValueError("机器不存在")
last = m["last_seen_at"]
online = False
try:
online = bool(last and (now() - float(last)) <= ONLINE_SECONDS)
except (TypeError, ValueError):
online = False
if online:
raise ValueError("机器当前在线,请先关闭客户端后再删")
conn.execute("UPDATE activations SET machine_id=NULL WHERE machine_id=?", (machine_id,))
conn.execute("UPDATE trades SET machine_id=NULL WHERE machine_id=?", (machine_id,))
conn.execute("DELETE FROM machines WHERE id=?", (machine_id,))
def create_user(
conn: sqlite3.Connection,
username: str,
+16 -15
View File
@@ -321,22 +321,12 @@ def register_routes(app: Flask) -> None:
@app.get("/machines")
@login_required
def machines_page():
# 机器列表:管理员全看;普通用户只看与自己账户激活相关的机器
u = current_user()
with database.get_db() as conn:
if u["role"] == "admin":
items = database.list_machines(conn)
else:
cur = conn.execute(
"""
SELECT DISTINCT m.* FROM machines m
JOIN activations act ON act.machine_id = m.id
WHERE act.account_id=?
ORDER BY m.id DESC
""",
(u.get("account_id"),),
)
items = [database.row_to_dict(r) for r in cur.fetchall()]
items = database.list_machines_for_user(conn, u)
for m in items:
m["last_seen_fmt"] = _fmt_ts(m.get("last_seen_at"))
m["bound"] = bool(m.get("device_fingerprint"))
return render_template("machines.html", items=items, is_admin=(u["role"] == "admin"))
@app.post("/machines")
@@ -349,7 +339,7 @@ def register_routes(app: Flask) -> None:
VALUES(?,?,?,?,?)""",
(name, "", (request.form.get("note") or "").strip(), 1, database.now()),
)
flash("机器已添加", "ok")
flash("机器已添加(预登记,激活后绑定指纹)", "ok")
return redirect(url_for("machines_page"))
@app.post("/machines/<int:mid>/note")
@@ -365,6 +355,17 @@ def register_routes(app: Flask) -> None:
flash("机器标注已保存", "ok")
return redirect(url_for("machines_page"))
@app.post("/machines/<int:mid>/delete")
@admin_required
def machines_delete(mid: int):
try:
with database.get_db() as conn:
database.delete_machine(conn, mid)
flash("机器已删除", "ok")
except ValueError as exc:
flash(str(exc), "error")
return redirect(url_for("machines_page"))
# ---------- 下载 / 激活(管理员与用户都可发码;用户仅自己账户)----------
@app.get("/download")
@login_required
+44 -11
View File
@@ -1,12 +1,16 @@
{% extends "base.html" %}
{% block title %}机器 · {{ app_name }}{% endblock %}
{% block content %}
<h2 style="margin:0 0 14px">机器{% if is_admin %}(管理员可预登记){% else %}(与本账户相关){% endif %}</h2>
<h2 style="margin:0 0 6px">机器{% if is_admin %}(管理员可预登记){% else %}(与本账户相关){% endif %}</h2>
<p class="muted" style="margin:0 0 14px">
正常用法:不必预登记,客户端激活时会自动创建机器并绑定指纹。
「未绑定 / 无指纹」多为早期占位或预登记未使用,管理员可删除。
</p>
<div class="grid cols-2">
{% if is_admin %}
<div class="card">
<h3>预登记机器</h3>
<p class="muted">也可不预登记:激活时自动创建机器并绑定指纹。</p>
<h3>预登记机器(可选)</h3>
<p class="muted">一般可跳过。仅当需要先占名、再发激活码预绑定时使用。</p>
<form method="post" action="{{ url_for('machines_create') }}">
<div class="form-row"><label>名称</label><input name="name" placeholder="如:书房台式机"></div>
<div class="form-row"><label>标注</label><textarea name="note" rows="2"></textarea></div>
@@ -14,30 +18,59 @@
</form>
</div>
{% endif %}
<div class="card">
<div class="card" {% if not is_admin %}style="grid-column:1/-1"{% endif %}>
<h3>已有机器</h3>
<table>
<thead><tr><th>名称 / 指纹</th><th>最近在线</th><th>标注</th></tr></thead>
<thead>
<tr>
<th>名称 / 指纹</th>
<th>状态</th>
<th>最近心跳</th>
<th>标注</th>
{% if is_admin %}<th></th>{% endif %}
</tr>
</thead>
<tbody>
{% for m in items %}
<tr>
<td>
{% if is_admin %}
<form method="post" action="{{ url_for('machines_note', mid=m.id) }}">
<div class="muted" style="margin-bottom:4px">#{{ m.id }}</div>
<input name="name" value="{{ m.name }}" style="margin-bottom:6px">
<div class="muted">{{ m.device_fingerprint or '未绑定' }}</div>
<textarea name="note" rows="2" style="margin-top:6px">{{ m.note or '' }}</textarea>
<div class="muted">{{ m.device_fingerprint or '尚未绑定指纹' }}</div>
{% if not m.bound %}<div class="muted" style="margin-top:4px">预登记 / 占位,激活后才会出现指纹</div>{% endif %}
<textarea name="note" rows="2" style="margin-top:6px" placeholder="备注">{{ m.note or '' }}</textarea>
<button class="btn secondary" style="margin-top:6px" type="submit">保存</button>
</form>
{% else %}
<div>{{ m.name }}</div>
<div class="muted">{{ m.device_fingerprint or '未绑定' }}</div>
<div>{{ m.name }} <span class="muted">#{{ m.id }}</span></div>
<div class="muted">{{ m.device_fingerprint or '尚未绑定指纹' }}</div>
{% if m.note %}<div class="muted" style="margin-top:6px">{{ m.note }}</div>{% endif %}
{% endif %}
</td>
<td class="muted">{{ m.last_seen_at or '—' }}</td>
<td>#{{ m.id }}</td>
<td>
{% if m.online %}
<span class="tag green">在线</span>
{% elif m.bound %}
<span class="tag gray">离线</span>
{% else %}
<span class="tag gray">未绑定</span>
{% endif %}
</td>
<td class="muted">{{ m.last_seen_fmt }}</td>
<td class="muted">{{ m.note or '—' }}</td>
{% if is_admin %}
<td>
<form method="post" action="{{ url_for('machines_delete', mid=m.id) }}"
onsubmit="return confirm('确认删除机器 #{{ m.id }} {{ m.name }}?');">
<button class="btn secondary" type="submit" {% if m.online %}disabled title="在线中不可删"{% endif %}>删除</button>
</form>
</td>
{% endif %}
</tr>
{% else %}
<tr><td colspan="{% if is_admin %}5{% else %}4{% endif %}" class="muted">暂无机器。客户端首次激活后会出现在此。</td></tr>
{% endfor %}
</tbody>
</table>