Harden config sync defaults, crash resume, and network nav collapse.

Disable auto-sync by default, enforce single-flight cycles with crash requeue, delay new scheduled runs after restart, and make the network sidebar collapsible.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-07-31 10:43:01 +08:00
parent 0eead0e662
commit f5b4a14c0f
14 changed files with 387 additions and 99 deletions

View file

@ -47,7 +47,7 @@ def _utcnow() -> datetime:
def ensure_policy(db: Session) -> ConfigSyncPolicy:
row = db.get(ConfigSyncPolicy, POLICY_ID)
if row is None:
row = ConfigSyncPolicy(id=POLICY_ID)
row = ConfigSyncPolicy(id=POLICY_ID, enabled=False)
db.add(row)
db.commit()
db.refresh(row)
@ -202,15 +202,21 @@ def expand_targets(db: Session, policy: ConfigSyncPolicy) -> list[dict[str, str]
return out
def has_running_cycle(db: Session) -> ConfigSyncCycle | None:
def has_active_cycle(db: Session) -> ConfigSyncCycle | None:
"""Any non-terminal cycle occupies the single-flight slot (incl. paused)."""
return (
db.query(ConfigSyncCycle)
.filter(ConfigSyncCycle.status.in_(("running", "pending")))
.filter(ConfigSyncCycle.status.in_(("running", "pending", "paused")))
.order_by(ConfigSyncCycle.created_at.desc())
.first()
)
def has_running_cycle(db: Session) -> ConfigSyncCycle | None:
"""Backward-compatible alias: treat paused as active so a new cycle cannot start."""
return has_active_cycle(db)
def last_finished_cycle(db: Session) -> ConfigSyncCycle | None:
return (
db.query(ConfigSyncCycle)
@ -224,6 +230,8 @@ def next_due_at(db: Session, policy: ConfigSyncPolicy | None = None) -> datetime
pol = policy or ensure_policy(db)
if not pol.enabled:
return None
from .config_sync_scheduler import startup_grace_until
last = (
db.query(ConfigSyncCycle)
.filter(ConfigSyncCycle.status == "success", ConfigSyncCycle.ended_at.isnot(None))
@ -232,8 +240,14 @@ def next_due_at(db: Session, policy: ConfigSyncPolicy | None = None) -> datetime
)
days = max(1, int(pol.interval_days or 3))
if last and last.ended_at:
return last.ended_at + timedelta(days=days)
return _utcnow()
due = last.ended_at + timedelta(days=days)
else:
# Never synced successfully: do not fire immediately on enable / first boot.
due = _utcnow() + timedelta(days=days)
grace_until = startup_grace_until()
if grace_until is not None and due < grace_until:
return grace_until
return due
def create_cycle(db: Session, body: ConfigSyncCycleCreate) -> ConfigSyncCycleOut: