221 lines
7.1 KiB
Python
221 lines
7.1 KiB
Python
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, EmailStr, Field
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.security import get_current_user
|
|
from app.db.session import get_db
|
|
from app.models.datasource import DataSource
|
|
from app.models.system_setting import SystemSetting
|
|
from app.models.user import User
|
|
from app.services.scheduler import sync_datasource_job
|
|
|
|
router = APIRouter()
|
|
|
|
DEFAULT_SETTINGS = {
|
|
"system": {
|
|
"system_name": "智能星球",
|
|
"refresh_interval": 60,
|
|
"auto_refresh": True,
|
|
"data_retention_days": 30,
|
|
"max_concurrent_tasks": 5,
|
|
},
|
|
"notifications": {
|
|
"email_enabled": False,
|
|
"email_address": "",
|
|
"critical_alerts": True,
|
|
"warning_alerts": True,
|
|
"daily_summary": False,
|
|
},
|
|
"security": {
|
|
"session_timeout": 60,
|
|
"max_login_attempts": 5,
|
|
"password_policy": "medium",
|
|
},
|
|
}
|
|
|
|
|
|
class SystemSettingsUpdate(BaseModel):
|
|
system_name: str = "智能星球"
|
|
refresh_interval: int = Field(default=60, ge=10, le=3600)
|
|
auto_refresh: bool = True
|
|
data_retention_days: int = Field(default=30, ge=1, le=3650)
|
|
max_concurrent_tasks: int = Field(default=5, ge=1, le=50)
|
|
|
|
|
|
class NotificationSettingsUpdate(BaseModel):
|
|
email_enabled: bool = False
|
|
email_address: Optional[EmailStr] = None
|
|
critical_alerts: bool = True
|
|
warning_alerts: bool = True
|
|
daily_summary: bool = False
|
|
|
|
|
|
class SecuritySettingsUpdate(BaseModel):
|
|
session_timeout: int = Field(default=60, ge=5, le=1440)
|
|
max_login_attempts: int = Field(default=5, ge=1, le=20)
|
|
password_policy: str = Field(default="medium")
|
|
|
|
|
|
class CollectorSettingsUpdate(BaseModel):
|
|
is_active: bool
|
|
priority: str = Field(default="P1")
|
|
frequency_minutes: int = Field(default=60, ge=1, le=10080)
|
|
|
|
|
|
def merge_with_defaults(category: str, payload: Optional[dict]) -> dict:
|
|
merged = DEFAULT_SETTINGS[category].copy()
|
|
if payload:
|
|
merged.update(payload)
|
|
return merged
|
|
|
|
|
|
async def get_setting_record(db: AsyncSession, category: str) -> Optional[SystemSetting]:
|
|
result = await db.execute(select(SystemSetting).where(SystemSetting.category == category))
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def get_setting_payload(db: AsyncSession, category: str) -> dict:
|
|
record = await get_setting_record(db, category)
|
|
return merge_with_defaults(category, record.payload if record else None)
|
|
|
|
|
|
async def save_setting_payload(db: AsyncSession, category: str, payload: dict) -> dict:
|
|
record = await get_setting_record(db, category)
|
|
if record is None:
|
|
record = SystemSetting(category=category, payload=payload)
|
|
db.add(record)
|
|
else:
|
|
record.payload = payload
|
|
|
|
await db.commit()
|
|
await db.refresh(record)
|
|
return merge_with_defaults(category, record.payload)
|
|
|
|
|
|
def format_frequency_label(minutes: int) -> str:
|
|
if minutes % 1440 == 0:
|
|
return f"{minutes // 1440}d"
|
|
if minutes % 60 == 0:
|
|
return f"{minutes // 60}h"
|
|
return f"{minutes}m"
|
|
|
|
|
|
def serialize_collector(datasource: DataSource) -> dict:
|
|
return {
|
|
"id": datasource.id,
|
|
"name": datasource.name,
|
|
"source": datasource.source,
|
|
"module": datasource.module,
|
|
"priority": datasource.priority,
|
|
"frequency_minutes": datasource.frequency_minutes,
|
|
"frequency": format_frequency_label(datasource.frequency_minutes),
|
|
"is_active": datasource.is_active,
|
|
"last_run_at": datasource.last_run_at.isoformat() if datasource.last_run_at else None,
|
|
"last_status": datasource.last_status,
|
|
"next_run_at": datasource.next_run_at.isoformat() if datasource.next_run_at else None,
|
|
}
|
|
|
|
|
|
@router.get("/system")
|
|
async def get_system_settings(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
return {"system": await get_setting_payload(db, "system")}
|
|
|
|
|
|
@router.put("/system")
|
|
async def update_system_settings(
|
|
settings: SystemSettingsUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
payload = await save_setting_payload(db, "system", settings.model_dump())
|
|
return {"status": "updated", "system": payload}
|
|
|
|
|
|
@router.get("/notifications")
|
|
async def get_notification_settings(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
return {"notifications": await get_setting_payload(db, "notifications")}
|
|
|
|
|
|
@router.put("/notifications")
|
|
async def update_notification_settings(
|
|
settings: NotificationSettingsUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
payload = await save_setting_payload(db, "notifications", settings.model_dump())
|
|
return {"status": "updated", "notifications": payload}
|
|
|
|
|
|
@router.get("/security")
|
|
async def get_security_settings(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
return {"security": await get_setting_payload(db, "security")}
|
|
|
|
|
|
@router.put("/security")
|
|
async def update_security_settings(
|
|
settings: SecuritySettingsUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
payload = await save_setting_payload(db, "security", settings.model_dump())
|
|
return {"status": "updated", "security": payload}
|
|
|
|
|
|
@router.get("/collectors")
|
|
async def get_collector_settings(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(select(DataSource).order_by(DataSource.module, DataSource.id))
|
|
datasources = result.scalars().all()
|
|
return {"collectors": [serialize_collector(datasource) for datasource in datasources]}
|
|
|
|
|
|
@router.put("/collectors/{datasource_id}")
|
|
async def update_collector_settings(
|
|
datasource_id: int,
|
|
settings: CollectorSettingsUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
datasource = await db.get(DataSource, datasource_id)
|
|
if not datasource:
|
|
raise HTTPException(status_code=404, detail="Data source not found")
|
|
|
|
datasource.is_active = settings.is_active
|
|
datasource.priority = settings.priority
|
|
datasource.frequency_minutes = settings.frequency_minutes
|
|
await db.commit()
|
|
await db.refresh(datasource)
|
|
await sync_datasource_job(datasource.id)
|
|
return {"status": "updated", "collector": serialize_collector(datasource)}
|
|
|
|
|
|
@router.get("")
|
|
async def get_all_settings(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(select(DataSource).order_by(DataSource.module, DataSource.id))
|
|
datasources = result.scalars().all()
|
|
return {
|
|
"system": await get_setting_payload(db, "system"),
|
|
"notifications": await get_setting_payload(db, "notifications"),
|
|
"security": await get_setting_payload(db, "security"),
|
|
"collectors": [serialize_collector(datasource) for datasource in datasources],
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
}
|