Improve machines page: online status, formatted time, delete unbound placeholders.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -493,6 +493,24 @@ def delete_account(conn: sqlite3.Connection, account_id: int) -> None:
|
|||||||
conn.execute("DELETE FROM accounts WHERE id=?", (account_id,))
|
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(
|
def create_user(
|
||||||
conn: sqlite3.Connection,
|
conn: sqlite3.Connection,
|
||||||
username: str,
|
username: str,
|
||||||
|
|||||||
+16
-15
@@ -321,22 +321,12 @@ def register_routes(app: Flask) -> None:
|
|||||||
@app.get("/machines")
|
@app.get("/machines")
|
||||||
@login_required
|
@login_required
|
||||||
def machines_page():
|
def machines_page():
|
||||||
# 机器列表:管理员全看;普通用户只看与自己账户激活相关的机器
|
|
||||||
u = current_user()
|
u = current_user()
|
||||||
with database.get_db() as conn:
|
with database.get_db() as conn:
|
||||||
if u["role"] == "admin":
|
items = database.list_machines_for_user(conn, u)
|
||||||
items = database.list_machines(conn)
|
for m in items:
|
||||||
else:
|
m["last_seen_fmt"] = _fmt_ts(m.get("last_seen_at"))
|
||||||
cur = conn.execute(
|
m["bound"] = bool(m.get("device_fingerprint"))
|
||||||
"""
|
|
||||||
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()]
|
|
||||||
return render_template("machines.html", items=items, is_admin=(u["role"] == "admin"))
|
return render_template("machines.html", items=items, is_admin=(u["role"] == "admin"))
|
||||||
|
|
||||||
@app.post("/machines")
|
@app.post("/machines")
|
||||||
@@ -349,7 +339,7 @@ def register_routes(app: Flask) -> None:
|
|||||||
VALUES(?,?,?,?,?)""",
|
VALUES(?,?,?,?,?)""",
|
||||||
(name, "", (request.form.get("note") or "").strip(), 1, database.now()),
|
(name, "", (request.form.get("note") or "").strip(), 1, database.now()),
|
||||||
)
|
)
|
||||||
flash("机器已添加", "ok")
|
flash("机器已添加(预登记,激活后绑定指纹)", "ok")
|
||||||
return redirect(url_for("machines_page"))
|
return redirect(url_for("machines_page"))
|
||||||
|
|
||||||
@app.post("/machines/<int:mid>/note")
|
@app.post("/machines/<int:mid>/note")
|
||||||
@@ -365,6 +355,17 @@ def register_routes(app: Flask) -> None:
|
|||||||
flash("机器标注已保存", "ok")
|
flash("机器标注已保存", "ok")
|
||||||
return redirect(url_for("machines_page"))
|
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")
|
@app.get("/download")
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
+44
-11
@@ -1,12 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}机器 · {{ app_name }}{% endblock %}
|
{% block title %}机器 · {{ app_name }}{% endblock %}
|
||||||
{% block content %}
|
{% 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">
|
<div class="grid cols-2">
|
||||||
{% if is_admin %}
|
{% if is_admin %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>预登记机器</h3>
|
<h3>预登记机器(可选)</h3>
|
||||||
<p class="muted">也可不预登记:激活时自动创建机器并绑定指纹。</p>
|
<p class="muted">一般可跳过。仅当需要先占名、再发激活码预绑定时使用。</p>
|
||||||
<form method="post" action="{{ url_for('machines_create') }}">
|
<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><input name="name" placeholder="如:书房台式机"></div>
|
||||||
<div class="form-row"><label>标注</label><textarea name="note" rows="2"></textarea></div>
|
<div class="form-row"><label>标注</label><textarea name="note" rows="2"></textarea></div>
|
||||||
@@ -14,30 +18,59 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card">
|
<div class="card" {% if not is_admin %}style="grid-column:1/-1"{% endif %}>
|
||||||
<h3>已有机器</h3>
|
<h3>已有机器</h3>
|
||||||
<table>
|
<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>
|
<tbody>
|
||||||
{% for m in items %}
|
{% for m in items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if is_admin %}
|
{% if is_admin %}
|
||||||
<form method="post" action="{{ url_for('machines_note', mid=m.id) }}">
|
<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">
|
<input name="name" value="{{ m.name }}" style="margin-bottom:6px">
|
||||||
<div class="muted">{{ m.device_fingerprint or '未绑定' }}</div>
|
<div class="muted">{{ m.device_fingerprint or '尚未绑定指纹' }}</div>
|
||||||
<textarea name="note" rows="2" style="margin-top:6px">{{ m.note or '' }}</textarea>
|
{% 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>
|
<button class="btn secondary" style="margin-top:6px" type="submit">保存</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div>{{ m.name }}</div>
|
<div>{{ m.name }} <span class="muted">#{{ m.id }}</span></div>
|
||||||
<div class="muted">{{ m.device_fingerprint or '未绑定' }}</div>
|
<div class="muted">{{ m.device_fingerprint or '尚未绑定指纹' }}</div>
|
||||||
{% if m.note %}<div class="muted" style="margin-top:6px">{{ m.note }}</div>{% endif %}
|
{% if m.note %}<div class="muted" style="margin-top:6px">{{ m.note }}</div>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="muted">{{ m.last_seen_at or '—' }}</td>
|
<td>
|
||||||
<td>#{{ m.id }}</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>
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr><td colspan="{% if is_admin %}5{% else %}4{% endif %}" class="muted">暂无机器。客户端首次激活后会出现在此。</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user