feat: add configurable flow retention settings with super admin controls and pagination for workload flows

Add RuntimeSettingsRead/RuntimeSettingsUpdate schemas with flow_retention_hours field (1-8760 hours), implement runtime_setting helper to initialize/fetch runtime system setting with 24h default, add require_super_admin guard to validate * permission, update agent_heartbeat to use configurable retention_cutoff from runtime settings instead of hardcoded 24h, replace /settings GET endpoint to return RuntimeSettingsRead with flow_retention_hours,
This commit is contained in:
2026-07-09 22:59:31 +02:00
parent 8c59ab32d5
commit 1531b7ea47
6 changed files with 172 additions and 7 deletions
+47 -4
View File
@@ -57,6 +57,8 @@ from app.schemas.domain import (
ProjectRead,
RoleCreate,
RoleRead,
RuntimeSettingsRead,
RuntimeSettingsUpdate,
SecurityRuleCreate,
SecurityRuleRead,
ServiceCatalogCreate,
@@ -102,6 +104,27 @@ def setup_setting(db: Session) -> SystemSetting:
return setting
def runtime_setting(db: Session) -> SystemSetting:
setting = db.get(SystemSetting, "runtime")
if not setting:
setting = SystemSetting(key="runtime", value={"flow_retention_hours": 24})
db.add(setting)
db.commit()
db.refresh(setting)
return setting
def runtime_settings_payload(db: Session) -> RuntimeSettingsRead:
value = runtime_setting(db).value or {}
return RuntimeSettingsRead(flow_retention_hours=int(value.get("flow_retention_hours") or 24))
def require_super_admin(user: User) -> None:
permissions = {permission for role in user.roles for permission in role.permissions}
if "*" not in permissions:
raise HTTPException(status_code=403, detail="Super Admin permission required")
def ensure_discovered_network(db: Session, cluster_id: str) -> Network:
network = db.scalar(select(Network).where(Network.cluster_id == cluster_id, Network.name == "discovered-ipam"))
if network:
@@ -1409,7 +1432,8 @@ def agent_heartbeat(payload: AgentHeartbeat, authorization: str | None = Header(
agent.version = payload.version
agent.last_seen_at = datetime.utcnow()
agent.last_payload = payload.model_dump(mode="json")
retention_cutoff = datetime.utcnow() - timedelta(hours=24)
retention_hours = runtime_settings_payload(db).flow_retention_hours
retention_cutoff = datetime.utcnow() - timedelta(hours=retention_hours)
for old_flow in db.scalars(select(TrafficFlow).where(TrafficFlow.node_id == node.id, TrafficFlow.updated_at < retention_cutoff)).all():
db.delete(old_flow)
existing_flows = {
@@ -2034,6 +2058,25 @@ def audit(_: CurrentUser, db: Session = Depends(get_db)) -> list[AuditLog]:
return db.scalars(select(AuditLog).order_by(AuditLog.created_at.desc()).limit(200)).all()
@api_router.get("/settings")
def settings(_: CurrentUser) -> dict:
return {"product": "NexaFabric", "firewall_apply_requires_preview": True, "agent_optional": True}
@api_router.get("/settings", response_model=RuntimeSettingsRead)
def settings(_: CurrentUser, db: Session = Depends(get_db)) -> RuntimeSettingsRead:
return runtime_settings_payload(db)
@api_router.patch("/settings", response_model=RuntimeSettingsRead)
def update_settings(payload: RuntimeSettingsUpdate, user: CurrentUser, db: Session = Depends(get_db)) -> RuntimeSettingsRead:
require_super_admin(user)
setting = runtime_setting(db)
old_values = dict(setting.value or {})
setting.value = {**old_values, "flow_retention_hours": payload.flow_retention_hours}
commit_or_400(db)
write_audit(
db,
action="settings.updated",
object_type="system",
object_id="runtime",
user_id=user.id,
old_values=old_values,
new_values=setting.value,
)
return runtime_settings_payload(db)