da5eb4c18c
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>
120 lines
3.6 KiB
Python
120 lines
3.6 KiB
Python
"""中控 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
|