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 os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import qrcode
|
import qrcode
|
||||||
@@ -38,9 +39,13 @@ def ensure_schema():
|
|||||||
public_key TEXT UNIQUE NOT NULL,
|
public_key TEXT UNIQUE NOT NULL,
|
||||||
client_address TEXT,
|
client_address TEXT,
|
||||||
advertised_routes TEXT,
|
advertised_routes TEXT,
|
||||||
|
client_conf TEXT,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
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()
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
@@ -83,15 +88,18 @@ def wg_dump():
|
|||||||
parts = line.split("\t")
|
parts = line.split("\t")
|
||||||
if len(parts) < 8:
|
if len(parts) < 8:
|
||||||
continue
|
continue
|
||||||
|
latest_ts = 0
|
||||||
latest = "never"
|
latest = "never"
|
||||||
if parts[5].isdigit() and int(parts[5]) > 0:
|
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(
|
peers.append(
|
||||||
{
|
{
|
||||||
"public_key": parts[0],
|
"public_key": parts[0],
|
||||||
"endpoint": parts[2] or "-",
|
"endpoint": parts[2] or "-",
|
||||||
"allowed_ips": parts[3],
|
"allowed_ips": parts[3],
|
||||||
"latest_handshake": latest,
|
"latest_handshake": latest,
|
||||||
|
"latest_handshake_ts": latest_ts,
|
||||||
"rx_bytes": int(parts[6] or 0),
|
"rx_bytes": int(parts[6] or 0),
|
||||||
"tx_bytes": int(parts[7] or 0),
|
"tx_bytes": int(parts[7] or 0),
|
||||||
}
|
}
|
||||||
@@ -160,11 +168,15 @@ def index():
|
|||||||
|
|
||||||
items = []
|
items = []
|
||||||
seen = set()
|
seen = set()
|
||||||
|
now = int(time.time())
|
||||||
for row in db_peers:
|
for row in db_peers:
|
||||||
rt = runtime.get(row["public_key"], {})
|
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"])
|
seen.add(row["public_key"])
|
||||||
items.append(
|
items.append(
|
||||||
{
|
{
|
||||||
|
"id": row["id"],
|
||||||
"name": row["name"],
|
"name": row["name"],
|
||||||
"public_key": row["public_key"],
|
"public_key": row["public_key"],
|
||||||
"client_address": row.get("client_address") or "-",
|
"client_address": row.get("client_address") or "-",
|
||||||
@@ -174,15 +186,18 @@ def index():
|
|||||||
"latest_handshake": rt.get("latest_handshake", "offline"),
|
"latest_handshake": rt.get("latest_handshake", "offline"),
|
||||||
"rx": bytes_h(rt.get("rx_bytes", 0)),
|
"rx": bytes_h(rt.get("rx_bytes", 0)),
|
||||||
"tx": bytes_h(rt.get("tx_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():
|
for pk, rt in runtime.items():
|
||||||
if pk in seen:
|
if pk in seen:
|
||||||
continue
|
continue
|
||||||
|
ts = int(rt.get("latest_handshake_ts", 0) or 0)
|
||||||
|
is_online = ts > 0 and (now - ts) <= 180
|
||||||
items.append(
|
items.append(
|
||||||
{
|
{
|
||||||
|
"id": None,
|
||||||
"name": "(external)",
|
"name": "(external)",
|
||||||
"public_key": pk,
|
"public_key": pk,
|
||||||
"client_address": rt.get("allowed_ips", "-").split(",", 1)[0],
|
"client_address": rt.get("allowed_ips", "-").split(",", 1)[0],
|
||||||
@@ -192,7 +207,7 @@ def index():
|
|||||||
"latest_handshake": rt.get("latest_handshake", "offline"),
|
"latest_handshake": rt.get("latest_handshake", "offline"),
|
||||||
"rx": bytes_h(rt.get("rx_bytes", 0)),
|
"rx": bytes_h(rt.get("rx_bytes", 0)),
|
||||||
"tx": bytes_h(rt.get("tx_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:
|
with db_conn() as conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"UPDATE peers SET name=?, client_address=?, advertised_routes=? WHERE public_key=?",
|
"UPDATE peers SET name=?, client_address=?, advertised_routes=?, client_conf=? WHERE public_key=?",
|
||||||
(name, client_addr, routes, client_pub),
|
(name, client_addr, routes, client_conf, client_pub),
|
||||||
)
|
)
|
||||||
if cur.rowcount == 0:
|
if cur.rowcount == 0:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO peers(name, public_key, client_address, advertised_routes) VALUES (?,?,?,?)",
|
"INSERT INTO peers(name, public_key, client_address, advertised_routes, client_conf) VALUES (?,?,?,?,?)",
|
||||||
(name, client_pub, client_addr, routes),
|
(name, client_pub, client_addr, routes, client_conf),
|
||||||
)
|
)
|
||||||
conn.commit()
|
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")
|
@app.route("/scripts")
|
||||||
def scripts():
|
def scripts():
|
||||||
commands = {
|
commands = {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -21,6 +21,13 @@
|
|||||||
<td>{{ p.rx }}</td>
|
<td>{{ p.rx }}</td>
|
||||||
<td>{{ p.tx }}</td>
|
<td>{{ p.tx }}</td>
|
||||||
<td class="mono">{{ p.public_key }}</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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user