Add Fleet control plane and split manage.sh deploy menu.
Strategy nodes gain fleet token APIs; control/ app for local ops; manage.sh offers strategy vs control one-click deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""中控 SQLite。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Any
|
||||
|
||||
from .config import get_control_settings
|
||||
|
||||
_SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
base_url TEXT NOT NULL UNIQUE,
|
||||
token_sealed TEXT NOT NULL DEFAULT '',
|
||||
created_at_ms INTEGER NOT NULL,
|
||||
updated_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS meta (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
_db: "ControlDB | None" = None
|
||||
_lock = Lock()
|
||||
|
||||
|
||||
def set_control_db(db: "ControlDB | None") -> None:
|
||||
global _db
|
||||
_db = db
|
||||
|
||||
|
||||
def get_control_db() -> "ControlDB":
|
||||
if _db is None:
|
||||
raise RuntimeError("control db not initialized")
|
||||
return _db
|
||||
|
||||
|
||||
class ControlDB:
|
||||
def __init__(self, path: Path | None = None) -> None:
|
||||
import sqlite3
|
||||
import time
|
||||
|
||||
settings = get_control_settings()
|
||||
self.path = path or settings.db_path
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._conn = sqlite3.connect(str(self.path), check_same_thread=False)
|
||||
self._conn.row_factory = sqlite3.Row
|
||||
self._conn.executescript(_SCHEMA)
|
||||
self._conn.commit()
|
||||
self._lock = Lock()
|
||||
# touch
|
||||
_ = time.time()
|
||||
|
||||
def close(self) -> None:
|
||||
self._conn.close()
|
||||
|
||||
def list_nodes(self) -> list[dict[str, Any]]:
|
||||
with self._lock:
|
||||
rows = self._conn.execute(
|
||||
"SELECT id, name, base_url, token_sealed, created_at_ms, updated_at_ms FROM nodes ORDER BY id"
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_node(self, node_id: int) -> dict[str, Any] | None:
|
||||
with self._lock:
|
||||
row = self._conn.execute(
|
||||
"SELECT id, name, base_url, token_sealed, created_at_ms, updated_at_ms FROM nodes WHERE id=?",
|
||||
(node_id,),
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def create_node(self, name: str, base_url: str) -> dict[str, Any]:
|
||||
import time
|
||||
|
||||
now = int(time.time() * 1000)
|
||||
name = name.strip()
|
||||
base_url = base_url.strip().rstrip("/")
|
||||
with self._lock:
|
||||
cur = self._conn.execute(
|
||||
"INSERT INTO nodes(name, base_url, token_sealed, created_at_ms, updated_at_ms) VALUES (?,?,?,?,?)",
|
||||
(name, base_url, "", now, now),
|
||||
)
|
||||
self._conn.commit()
|
||||
nid = int(cur.lastrowid)
|
||||
return self.get_node(nid) # type: ignore[return-value]
|
||||
|
||||
def update_node(
|
||||
self,
|
||||
node_id: int,
|
||||
*,
|
||||
name: str | None = None,
|
||||
base_url: str | None = None,
|
||||
token_sealed: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
import time
|
||||
|
||||
node = self.get_node(node_id)
|
||||
if not node:
|
||||
return None
|
||||
now = int(time.time() * 1000)
|
||||
new_name = name.strip() if name is not None else node["name"]
|
||||
new_url = base_url.strip().rstrip("/") if base_url is not None else node["base_url"]
|
||||
new_tok = token_sealed if token_sealed is not None else node["token_sealed"]
|
||||
with self._lock:
|
||||
self._conn.execute(
|
||||
"UPDATE nodes SET name=?, base_url=?, token_sealed=?, updated_at_ms=? WHERE id=?",
|
||||
(new_name, new_url, new_tok, now, node_id),
|
||||
)
|
||||
self._conn.commit()
|
||||
return self.get_node(node_id)
|
||||
|
||||
def delete_node(self, node_id: int) -> bool:
|
||||
with self._lock:
|
||||
cur = self._conn.execute("DELETE FROM nodes WHERE id=?", (node_id,))
|
||||
self._conn.commit()
|
||||
return cur.rowcount > 0
|
||||
Reference in New Issue
Block a user