- Add workers API for pool management - Add jobs API for queue operations - Add scan-rules API for CRUD operations - Add scanner API for control and status - Add settings API for configuration management - Add system API for resource monitoring - Add filesystem API for path browsing - Add setup wizard API endpoint
324 lines
9.1 KiB
Python
324 lines
9.1 KiB
Python
"""Settings management API routes."""
|
|
import logging
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, HTTPException, Query, status
|
|
from pydantic import BaseModel, Field
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
|
|
|
|
|
# === REQUEST/RESPONSE MODELS ===
|
|
|
|
class SettingResponse(BaseModel):
|
|
"""Setting response model."""
|
|
id: int
|
|
key: str
|
|
value: Optional[str]
|
|
description: Optional[str]
|
|
category: Optional[str]
|
|
value_type: Optional[str]
|
|
created_at: Optional[str]
|
|
updated_at: Optional[str]
|
|
|
|
|
|
class SettingUpdateRequest(BaseModel):
|
|
"""Setting update request."""
|
|
value: str = Field(..., description="New value (as string)")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"value": "true"
|
|
}
|
|
}
|
|
|
|
|
|
class SettingCreateRequest(BaseModel):
|
|
"""Setting create request."""
|
|
key: str = Field(..., description="Setting key")
|
|
value: Optional[str] = Field(None, description="Setting value")
|
|
description: Optional[str] = Field(None, description="Description")
|
|
category: Optional[str] = Field(None, description="Category")
|
|
value_type: Optional[str] = Field("string", description="Value type")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"key": "custom_setting",
|
|
"value": "value",
|
|
"description": "Custom setting description",
|
|
"category": "general",
|
|
"value_type": "string"
|
|
}
|
|
}
|
|
|
|
|
|
class BulkUpdateRequest(BaseModel):
|
|
"""Bulk update request."""
|
|
settings: dict = Field(..., description="Dictionary of key-value pairs")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"settings": {
|
|
"worker_cpu_count": "2",
|
|
"worker_gpu_count": "1",
|
|
"scanner_enabled": "true"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class MessageResponse(BaseModel):
|
|
"""Generic message response."""
|
|
message: str
|
|
|
|
|
|
# === ROUTES ===
|
|
|
|
@router.get("/", response_model=List[SettingResponse])
|
|
async def get_all_settings(category: Optional[str] = Query(None, description="Filter by category")):
|
|
"""
|
|
Get all settings or filter by category.
|
|
|
|
Args:
|
|
category: Optional category filter (general, workers, transcription, scanner, bazarr)
|
|
|
|
Returns:
|
|
List of settings
|
|
"""
|
|
from backend.core.settings_service import settings_service
|
|
|
|
if category:
|
|
settings = settings_service.get_by_category(category)
|
|
else:
|
|
settings = settings_service.get_all()
|
|
|
|
return [SettingResponse(**s.to_dict()) for s in settings]
|
|
|
|
|
|
@router.get("/{key}", response_model=SettingResponse)
|
|
async def get_setting(key: str):
|
|
"""
|
|
Get a specific setting by key.
|
|
|
|
Args:
|
|
key: Setting key
|
|
|
|
Returns:
|
|
Setting object
|
|
|
|
Raises:
|
|
404: Setting not found
|
|
"""
|
|
from backend.core.database import database
|
|
from backend.core.settings_model import SystemSettings
|
|
|
|
with database.get_session() as session:
|
|
setting = session.query(SystemSettings).filter(SystemSettings.key == key).first()
|
|
|
|
if not setting:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Setting '{key}' not found"
|
|
)
|
|
|
|
return SettingResponse(**setting.to_dict())
|
|
|
|
|
|
@router.put("/{key}", response_model=SettingResponse)
|
|
async def update_setting(key: str, request: SettingUpdateRequest):
|
|
"""
|
|
Update a setting value.
|
|
|
|
Args:
|
|
key: Setting key
|
|
request: Update request with new value
|
|
|
|
Returns:
|
|
Updated setting object
|
|
|
|
Raises:
|
|
404: Setting not found
|
|
400: Invalid value (e.g., GPU workers without GPU)
|
|
"""
|
|
from backend.core.settings_service import settings_service
|
|
from backend.core.database import database
|
|
from backend.core.settings_model import SystemSettings
|
|
from backend.core.system_monitor import system_monitor
|
|
|
|
value = request.value
|
|
|
|
# Validate GPU worker count - force to 0 if no GPU available
|
|
if key == 'worker_gpu_count':
|
|
gpu_count = int(value) if value else 0
|
|
if gpu_count > 0 and system_monitor.gpu_count == 0:
|
|
logger.warning(
|
|
f"Attempted to set worker_gpu_count={gpu_count} but no GPU detected. "
|
|
"Forcing to 0."
|
|
)
|
|
value = '0'
|
|
|
|
success = settings_service.set(key, value)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to update setting '{key}'"
|
|
)
|
|
|
|
# Return updated setting
|
|
with database.get_session() as session:
|
|
setting = session.query(SystemSettings).filter(SystemSettings.key == key).first()
|
|
|
|
if not setting:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Setting '{key}' not found"
|
|
)
|
|
|
|
return SettingResponse(**setting.to_dict())
|
|
|
|
|
|
@router.post("/bulk-update", response_model=MessageResponse)
|
|
async def bulk_update_settings(request: BulkUpdateRequest):
|
|
"""
|
|
Update multiple settings at once.
|
|
|
|
Args:
|
|
request: Bulk update request with settings dictionary
|
|
|
|
Returns:
|
|
Success message
|
|
"""
|
|
from backend.core.settings_service import settings_service
|
|
from backend.core.system_monitor import system_monitor
|
|
|
|
# Validate GPU worker count - force to 0 if no GPU available
|
|
settings_to_update = request.settings.copy()
|
|
if 'worker_gpu_count' in settings_to_update:
|
|
gpu_count = int(settings_to_update.get('worker_gpu_count', 0))
|
|
if gpu_count > 0 and system_monitor.gpu_count == 0:
|
|
logger.warning(
|
|
f"Attempted to set worker_gpu_count={gpu_count} but no GPU detected. "
|
|
"Forcing to 0."
|
|
)
|
|
settings_to_update['worker_gpu_count'] = '0'
|
|
|
|
success = settings_service.bulk_update(settings_to_update)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to update settings"
|
|
)
|
|
|
|
logger.info(f"Bulk updated {len(request.settings)} settings")
|
|
|
|
return MessageResponse(message=f"Updated {len(request.settings)} settings successfully")
|
|
|
|
|
|
@router.post("/", response_model=SettingResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_setting(request: SettingCreateRequest):
|
|
"""
|
|
Create a new setting.
|
|
|
|
Args:
|
|
request: Create request with setting details
|
|
|
|
Returns:
|
|
Created setting object
|
|
|
|
Raises:
|
|
409: Setting already exists
|
|
"""
|
|
from backend.core.settings_service import settings_service
|
|
from backend.core.database import database
|
|
from backend.core.settings_model import SystemSettings
|
|
|
|
# Check if exists
|
|
with database.get_session() as session:
|
|
existing = session.query(SystemSettings).filter(SystemSettings.key == request.key).first()
|
|
|
|
if existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail=f"Setting '{request.key}' already exists"
|
|
)
|
|
|
|
# Create
|
|
success = settings_service.set(
|
|
key=request.key,
|
|
value=request.value,
|
|
description=request.description,
|
|
category=request.category,
|
|
value_type=request.value_type
|
|
)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to create setting"
|
|
)
|
|
|
|
# Return created setting
|
|
with database.get_session() as session:
|
|
setting = session.query(SystemSettings).filter(SystemSettings.key == request.key).first()
|
|
return SettingResponse(**setting.to_dict())
|
|
|
|
|
|
@router.delete("/{key}", response_model=MessageResponse)
|
|
async def delete_setting(key: str):
|
|
"""
|
|
Delete a setting.
|
|
|
|
Args:
|
|
key: Setting key
|
|
|
|
Returns:
|
|
Success message
|
|
|
|
Raises:
|
|
404: Setting not found
|
|
"""
|
|
from backend.core.settings_service import settings_service
|
|
|
|
success = settings_service.delete(key)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Setting '{key}' not found"
|
|
)
|
|
|
|
logger.info(f"Setting deleted: {key}")
|
|
|
|
return MessageResponse(message=f"Setting '{key}' deleted successfully")
|
|
|
|
|
|
@router.post("/init-defaults", response_model=MessageResponse)
|
|
async def init_default_settings():
|
|
"""
|
|
Initialize default settings.
|
|
|
|
Creates all default settings if they don't exist.
|
|
Safe to call multiple times (won't overwrite existing).
|
|
|
|
Returns:
|
|
Success message
|
|
"""
|
|
from backend.core.settings_service import settings_service
|
|
|
|
try:
|
|
settings_service.init_default_settings()
|
|
return MessageResponse(message="Default settings initialized successfully")
|
|
except Exception as e:
|
|
logger.error(f"Failed to initialize default settings: {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to initialize default settings: {str(e)}"
|
|
)
|
|
|