Initial Awawawa
This commit is contained in:
123
web/patchbay_web/api.py
Normal file
123
web/patchbay_web/api.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""JSON API used by the patch editor, services and stats pages."""
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from . import daemon, db, validate
|
||||
from .auth import login_required
|
||||
|
||||
bp = Blueprint("api", __name__, url_prefix="/api")
|
||||
|
||||
RANGES = { # range -> (seconds, tier, bucket seconds)
|
||||
"1h": (3600, "m", 60),
|
||||
"24h": (86400, "m", 60),
|
||||
"7d": (7 * 86400, "h", 3600),
|
||||
"30d": (30 * 86400, "h", 3600),
|
||||
"1y": (365 * 86400, "d", 86400),
|
||||
"all": (None, "d", 86400),
|
||||
}
|
||||
|
||||
|
||||
def _clients():
|
||||
rows = db.get().execute("SELECT id, name, hostname, last_seen, last_addr FROM clients ORDER BY name")
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
@bp.get("/graph")
|
||||
@login_required
|
||||
def graph_get():
|
||||
conn = db.get()
|
||||
nodes = [dict(r) for r in conn.execute(
|
||||
"SELECT id, type, client_id, host, port, proto, label, iface, x, y FROM nodes ORDER BY id")]
|
||||
links = [{"from": r["from_node"], "to": r["to_node"]} for r in conn.execute(
|
||||
"SELECT from_node, to_node FROM links")]
|
||||
return jsonify(nodes=nodes, links=links, clients=_clients(), interfaces=_interfaces())
|
||||
|
||||
|
||||
def _interfaces():
|
||||
"""{host id: [{name, addr}]}, host id 0 = target."""
|
||||
out = {}
|
||||
for r in db.get().execute("SELECT client_id, name, addr FROM interfaces ORDER BY client_id, name"):
|
||||
out.setdefault(str(r["client_id"]), []).append({"name": r["name"], "addr": r["addr"]})
|
||||
return out
|
||||
|
||||
|
||||
@bp.put("/graph")
|
||||
@login_required
|
||||
def graph_put():
|
||||
conn = db.get()
|
||||
client_ids = {r["id"] for r in conn.execute("SELECT id FROM clients")}
|
||||
try:
|
||||
nodes, links = validate.graph(request.get_json(silent=True), client_ids)
|
||||
except (validate.Invalid, TypeError, ValueError) as e:
|
||||
return jsonify(ok=False, error=str(e)), 400
|
||||
|
||||
idmap = {}
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
existing = {r["id"] for r in conn.execute("SELECT id FROM nodes")}
|
||||
keep = {n["id"] for n in nodes if n["id"] > 0}
|
||||
for nid in existing - keep:
|
||||
conn.execute("DELETE FROM nodes WHERE id = ?", (nid,))
|
||||
cols = ("type", "client_id", "host", "port", "proto", "label", "iface", "x", "y")
|
||||
for n in nodes:
|
||||
vals = [n[c] for c in cols]
|
||||
if n["id"] > 0 and n["id"] in existing:
|
||||
conn.execute(f"UPDATE nodes SET {', '.join(c + ' = ?' for c in cols)} WHERE id = ?",
|
||||
vals + [n["id"]])
|
||||
idmap[n["id"]] = n["id"]
|
||||
else:
|
||||
cur = conn.execute(f"INSERT INTO nodes ({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))})",
|
||||
vals)
|
||||
idmap[n["id"]] = cur.lastrowid
|
||||
conn.execute("DELETE FROM links")
|
||||
for a, b in links:
|
||||
conn.execute("INSERT INTO links (from_node, to_node) VALUES (?, ?)", (idmap[a], idmap[b]))
|
||||
conn.execute("COMMIT")
|
||||
except Exception:
|
||||
conn.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
res = daemon.reload()
|
||||
return jsonify(ok=True, ids={str(k): v for k, v in idmap.items()}, daemon=res.get("ok", False),
|
||||
daemon_error=res.get("error"))
|
||||
|
||||
|
||||
@bp.get("/status")
|
||||
@login_required
|
||||
def status():
|
||||
st = daemon.request("STATUS")
|
||||
return jsonify(daemon=st.get("ok", False), error=st.get("error"), clients=st.get("clients", {}),
|
||||
sinks=st.get("sinks", {}))
|
||||
|
||||
|
||||
@bp.get("/live")
|
||||
@login_required
|
||||
def live():
|
||||
st = daemon.request("LIVE")
|
||||
return jsonify(daemon=st.get("ok", False), sinks=st.get("sinks", {}))
|
||||
|
||||
|
||||
@bp.post("/services/refresh")
|
||||
@login_required
|
||||
def services_refresh():
|
||||
res = daemon.request("SERVICES")
|
||||
return jsonify(ok=res.get("ok", False), error=res.get("error"))
|
||||
|
||||
|
||||
@bp.get("/stats")
|
||||
@login_required
|
||||
def stats():
|
||||
rng = request.args.get("range", "24h")
|
||||
if rng not in RANGES:
|
||||
return jsonify(error="bad range"), 400
|
||||
span, tier, step = RANGES[rng]
|
||||
try:
|
||||
sink = int(request.args.get("sink", "0"))
|
||||
except ValueError:
|
||||
return jsonify(error="bad sink"), 400
|
||||
since = db.now() - span if span else 0
|
||||
rows = db.get().execute(
|
||||
"SELECT ts, bytes_in, bytes_out, conns FROM stats WHERE sink_id = ? AND tier = ? AND ts >= ? ORDER BY ts",
|
||||
(sink, tier, since)).fetchall()
|
||||
return jsonify(range=rng, step=step, since=since, now=db.now(),
|
||||
points=[[r["ts"], r["bytes_in"], r["bytes_out"], r["conns"]] for r in rows])
|
||||
Reference in New Issue
Block a user