GUI: add persistent QR/config view and handshake-based online status
This commit is contained in:
55
gui/app.py
55
gui/app.py
@@ -4,6 +4,7 @@ import io
|
||||
import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import qrcode
|
||||
@@ -38,9 +39,13 @@ def ensure_schema():
|
||||
public_key TEXT UNIQUE NOT NULL,
|
||||
client_address TEXT,
|
||||
advertised_routes TEXT,
|
||||
client_conf TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
""")
|
||||
cols = {row[1] for row in cur.execute("PRAGMA table_info(peers)").fetchall()}
|
||||
if "client_conf" not in cols:
|
||||
cur.execute("ALTER TABLE peers ADD COLUMN client_conf TEXT")
|
||||
conn.commit()
|
||||
|
||||
|
||||
@@ -83,15 +88,18 @@ def wg_dump():
|
||||
parts = line.split("\t")
|
||||
if len(parts) < 8:
|
||||
continue
|
||||
latest_ts = 0
|
||||
latest = "never"
|
||||
if parts[5].isdigit() and int(parts[5]) > 0:
|
||||
latest = datetime.utcfromtimestamp(int(parts[5])).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
latest_ts = int(parts[5])
|
||||
latest = datetime.utcfromtimestamp(latest_ts).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
peers.append(
|
||||
{
|
||||
"public_key": parts[0],
|
||||
"endpoint": parts[2] or "-",
|
||||
"allowed_ips": parts[3],
|
||||
"latest_handshake": latest,
|
||||
"latest_handshake_ts": latest_ts,
|
||||
"rx_bytes": int(parts[6] or 0),
|
||||
"tx_bytes": int(parts[7] or 0),
|
||||
}
|
||||
@@ -160,11 +168,15 @@ def index():
|
||||
|
||||
items = []
|
||||
seen = set()
|
||||
now = int(time.time())
|
||||
for row in db_peers:
|
||||
rt = runtime.get(row["public_key"], {})
|
||||
ts = int(rt.get("latest_handshake_ts", 0) or 0)
|
||||
is_online = ts > 0 and (now - ts) <= 180
|
||||
seen.add(row["public_key"])
|
||||
items.append(
|
||||
{
|
||||
"id": row["id"],
|
||||
"name": row["name"],
|
||||
"public_key": row["public_key"],
|
||||
"client_address": row.get("client_address") or "-",
|
||||
@@ -174,15 +186,18 @@ def index():
|
||||
"latest_handshake": rt.get("latest_handshake", "offline"),
|
||||
"rx": bytes_h(rt.get("rx_bytes", 0)),
|
||||
"tx": bytes_h(rt.get("tx_bytes", 0)),
|
||||
"status": "online" if rt else "offline",
|
||||
"status": "online" if is_online else "offline",
|
||||
}
|
||||
)
|
||||
|
||||
for pk, rt in runtime.items():
|
||||
if pk in seen:
|
||||
continue
|
||||
ts = int(rt.get("latest_handshake_ts", 0) or 0)
|
||||
is_online = ts > 0 and (now - ts) <= 180
|
||||
items.append(
|
||||
{
|
||||
"id": None,
|
||||
"name": "(external)",
|
||||
"public_key": pk,
|
||||
"client_address": rt.get("allowed_ips", "-").split(",", 1)[0],
|
||||
@@ -192,7 +207,7 @@ def index():
|
||||
"latest_handshake": rt.get("latest_handshake", "offline"),
|
||||
"rx": bytes_h(rt.get("rx_bytes", 0)),
|
||||
"tx": bytes_h(rt.get("tx_bytes", 0)),
|
||||
"status": "online",
|
||||
"status": "online" if is_online else "offline",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -267,13 +282,13 @@ def new_peer():
|
||||
with db_conn() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"UPDATE peers SET name=?, client_address=?, advertised_routes=? WHERE public_key=?",
|
||||
(name, client_addr, routes, client_pub),
|
||||
"UPDATE peers SET name=?, client_address=?, advertised_routes=?, client_conf=? WHERE public_key=?",
|
||||
(name, client_addr, routes, client_conf, client_pub),
|
||||
)
|
||||
if cur.rowcount == 0:
|
||||
cur.execute(
|
||||
"INSERT INTO peers(name, public_key, client_address, advertised_routes) VALUES (?,?,?,?)",
|
||||
(name, client_pub, client_addr, routes),
|
||||
"INSERT INTO peers(name, public_key, client_address, advertised_routes, client_conf) VALUES (?,?,?,?,?)",
|
||||
(name, client_pub, client_addr, routes, client_conf),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
@@ -286,6 +301,32 @@ def new_peer():
|
||||
)
|
||||
|
||||
|
||||
@app.route("/peers/<int:peer_id>")
|
||||
def peer_view(peer_id: int):
|
||||
with db_conn() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM peers WHERE id = ?", (peer_id,))
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
flash("Клиент не найден", "error")
|
||||
return redirect(url_for("index"))
|
||||
item = dict(row)
|
||||
|
||||
conf = item.get("client_conf") or ""
|
||||
if not conf:
|
||||
flash("Для этого клиента не найден сохраненный конфиг", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
qr_b64 = to_png_b64(conf)
|
||||
return render_template(
|
||||
"peer_created.html",
|
||||
name=item.get("name", "peer"),
|
||||
client_conf=conf,
|
||||
qr_b64=qr_b64,
|
||||
public_key=item.get("public_key", ""),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/scripts")
|
||||
def scripts():
|
||||
commands = {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th><th>Статус</th><th>IP</th><th>Роуты</th><th>AllowedIPs</th><th>Endpoint</th><th>Handshake</th><th>RX</th><th>TX</th><th>PubKey</th>
|
||||
<th>Имя</th><th>Статус</th><th>IP</th><th>Роуты</th><th>AllowedIPs</th><th>Endpoint</th><th>Handshake</th><th>RX</th><th>TX</th><th>PubKey</th><th>Действие</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -21,6 +21,13 @@
|
||||
<td>{{ p.rx }}</td>
|
||||
<td>{{ p.tx }}</td>
|
||||
<td class="mono">{{ p.public_key }}</td>
|
||||
<td>
|
||||
{% if p.id %}
|
||||
<a href="{{ url_for('peer_view', peer_id=p.id) }}">QR/Config</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user