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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user