All checks were successful
Deploy / deploy (push) Successful in 33s
Allows promoting any replica to master with zero document re-downloads.
The sync_map rebuild uses existing DB data only — pure in-memory join.
Changes:
- app/sync/promote.py: preflight() checks (doc count, sync lock, ack
warnings) and promote() transaction (pause scheduler, rebuild all
sync_maps, create old-master replica, swap settings, resume scheduler)
- app/api/master.py: GET /api/master/promote/{id}/preflight (dry run)
and POST /api/master/promote/{id} (execute)
- app/models.py: add promoted_from_master bool field to Replica
- app/database.py: idempotent ALTER TABLE migration for new column
- app/main.py: register master router
- app/templates/replica_detail.html: "Promote to Master" button +
dialog with pre-flight summary, 3-card stats, ack checkboxes, spinner
- app/ui/routes.py: flash query param on dashboard route
- app/templates/dashboard.html: blue info banner for post-promotion flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import UniqueConstraint
|
|
from sqlmodel import Field, SQLModel
|
|
|
|
|
|
class Replica(SQLModel, table=True):
|
|
__tablename__ = "replicas" # type: ignore[assignment]
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str
|
|
url: str
|
|
api_token: str # Fernet-encrypted
|
|
enabled: bool = True
|
|
sync_interval_seconds: Optional[int] = None
|
|
last_sync_ts: Optional[datetime] = None
|
|
consecutive_failures: int = 0
|
|
suspended_at: Optional[datetime] = None
|
|
last_alert_at: Optional[datetime] = None
|
|
promoted_from_master: Optional[bool] = Field(default=False)
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class SyncMap(SQLModel, table=True):
|
|
__tablename__ = "sync_map" # type: ignore[assignment]
|
|
__table_args__ = (UniqueConstraint("replica_id", "master_doc_id"),) # type: ignore[assignment]
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
replica_id: int = Field(foreign_key="replicas.id")
|
|
master_doc_id: int
|
|
replica_doc_id: Optional[int] = None
|
|
task_id: Optional[str] = None
|
|
last_synced: Optional[datetime] = None
|
|
file_checksum: Optional[str] = None
|
|
status: str = "pending"
|
|
error_msg: Optional[str] = None
|
|
retry_count: int = 0
|
|
|
|
|
|
class SyncRun(SQLModel, table=True):
|
|
__tablename__ = "sync_runs" # type: ignore[assignment]
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
replica_id: Optional[int] = Field(default=None, foreign_key="replicas.id")
|
|
started_at: Optional[datetime] = None
|
|
finished_at: Optional[datetime] = None
|
|
triggered_by: Optional[str] = None
|
|
docs_synced: int = 0
|
|
docs_failed: int = 0
|
|
timed_out: bool = False
|
|
|
|
|
|
class Log(SQLModel, table=True):
|
|
__tablename__ = "logs" # type: ignore[assignment]
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
run_id: Optional[int] = Field(default=None, foreign_key="sync_runs.id")
|
|
replica_id: Optional[int] = Field(default=None, foreign_key="replicas.id")
|
|
level: Optional[str] = None
|
|
message: Optional[str] = None
|
|
doc_id: Optional[int] = None
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class Setting(SQLModel, table=True):
|
|
__tablename__ = "settings" # type: ignore[assignment]
|
|
|
|
key: str = Field(primary_key=True)
|
|
value: Optional[str] = None
|