Multi-interface management, topology view, performance tuning, precompiled CSS

- Interface model with per-interface subnet/port/keys; import/adopt existing wg-quick configs (key-less imported peers, optional key rotation), cascade delete
- Split wireguard.py into a package (keys via cryptography X25519, status, addressing, conf parse/render, sync, host tuning)
- ECharts horizontal topology view (interface -> peers -> site subnets)
- Advanced options: MTU, MSS clamping, FwMark/Table, custom PostUp/PostDown, per-peer keepalive override
- Runtime settings (sample interval/retention, online threshold, UI refresh) with traffic sample pruning; host tuning (UDP buffers, backlog, GRO forwarding)
- Precompiled Tailwind CSS replacing Play CDN runtime (fixes FOUC); stable table layout and diffed polling renders
- Host network mode in compose; NAT/isolation iptables moved into app sync

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-07-05 14:01:44 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent 192960ad3e
commit 1b227c2470
31 changed files with 2361 additions and 583 deletions
+90 -4
View File
@@ -35,13 +35,99 @@ PEER_MIGRATIONS = {
"last_tx": "ALTER TABLE peers ADD COLUMN last_tx INTEGER NOT NULL DEFAULT 0",
}
PEER_COLUMNS = (
"id, interface_id, name, public_key, private_key_enc, preshared_key_enc, "
"address, enabled, expires_at, created_at, note, dns, extra_allowed_ips, "
"client_allowed_ips, quota_bytes, cum_rx, cum_tx, last_rx, last_tx"
)
def _columns(conn, table: str) -> set[str]:
return {row[1] for row in conn.execute(text(f"PRAGMA table_info({table})"))}
def _rebuild_peers_with_interface(conn) -> None:
# SQLite cannot alter unique constraints in place; rebuild the table so
# name/address become unique per interface instead of globally.
conn.execute(text("ALTER TABLE peers RENAME TO peers_old"))
conn.execute(
text(
"""
CREATE TABLE peers (
id INTEGER NOT NULL PRIMARY KEY,
interface_id INTEGER NOT NULL DEFAULT 1 REFERENCES interfaces (id) ON DELETE CASCADE,
name VARCHAR(64) NOT NULL,
public_key VARCHAR(64) NOT NULL UNIQUE,
private_key_enc VARCHAR(256) NOT NULL DEFAULT '',
preshared_key_enc VARCHAR(256) NOT NULL DEFAULT '',
address VARCHAR(64) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
expires_at DATETIME,
created_at DATETIME NOT NULL,
note VARCHAR(256) NOT NULL DEFAULT '',
dns VARCHAR(128) NOT NULL DEFAULT '',
extra_allowed_ips VARCHAR(512) NOT NULL DEFAULT '',
client_allowed_ips VARCHAR(512) NOT NULL DEFAULT '',
quota_bytes INTEGER NOT NULL DEFAULT 0,
cum_rx INTEGER NOT NULL DEFAULT 0,
cum_tx INTEGER NOT NULL DEFAULT 0,
last_rx INTEGER NOT NULL DEFAULT 0,
last_tx INTEGER NOT NULL DEFAULT 0,
CONSTRAINT uq_peer_iface_name UNIQUE (interface_id, name),
CONSTRAINT uq_peer_iface_address UNIQUE (interface_id, address)
)
"""
)
)
old_columns = _columns(conn, "peers_old")
select_columns = ", ".join(
column.strip() if column.strip() in old_columns else f"1 AS {column.strip()}"
for column in PEER_COLUMNS.split(",")
)
conn.execute(
text(
f"INSERT INTO peers ({PEER_COLUMNS}) SELECT {select_columns} FROM peers_old"
)
)
conn.execute(text("DROP TABLE peers_old"))
conn.execute(
text("CREATE INDEX ix_peers_interface_id ON peers (interface_id)")
)
INTERFACE_MIGRATIONS = {
"mtu": "ALTER TABLE interfaces ADD COLUMN mtu INTEGER NOT NULL DEFAULT 0",
"mss_clamp": "ALTER TABLE interfaces ADD COLUMN mss_clamp BOOLEAN NOT NULL DEFAULT 0",
"fwmark": "ALTER TABLE interfaces ADD COLUMN fwmark VARCHAR(32) NOT NULL DEFAULT ''",
"route_table": "ALTER TABLE interfaces ADD COLUMN route_table VARCHAR(32) NOT NULL DEFAULT ''",
"post_up": "ALTER TABLE interfaces ADD COLUMN post_up VARCHAR(2048) NOT NULL DEFAULT ''",
"post_down": "ALTER TABLE interfaces ADD COLUMN post_down VARCHAR(2048) NOT NULL DEFAULT ''",
}
def run_migrations() -> None:
with engine.connect() as conn:
existing = {
row[1] for row in conn.execute(text("PRAGMA table_info(peers)"))
}
peer_columns = _columns(conn, "peers")
for column, ddl in PEER_MIGRATIONS.items():
if existing and column not in existing:
if peer_columns and column not in peer_columns:
conn.execute(text(ddl))
if peer_columns and "interface_id" not in peer_columns:
_rebuild_peers_with_interface(conn)
peer_columns = _columns(conn, "peers")
if peer_columns and "persistent_keepalive" not in peer_columns:
conn.execute(
text("ALTER TABLE peers ADD COLUMN persistent_keepalive INTEGER")
)
iface_columns = _columns(conn, "interfaces")
for column, ddl in INTERFACE_MIGRATIONS.items():
if iface_columns and column not in iface_columns:
conn.execute(text(ddl))
sample_columns = _columns(conn, "traffic_samples")
if sample_columns and "interface_name" not in sample_columns:
conn.execute(
text(
"ALTER TABLE traffic_samples ADD COLUMN "
"interface_name VARCHAR(15) NOT NULL DEFAULT ''"
)
)
conn.commit()