"""Pydantic schemas for API requests and responses.""" from datetime import datetime from typing import List, Optional, Dict, Any, Union from pydantic import BaseModel, Field, validator # User schemas class UserBase(BaseModel): username: str class UserCreate(UserBase): password: str = Field(..., min_length=8) class UserLogin(UserBase): password: str class UserResponse(UserBase): id: int is_active: bool created_at: datetime last_login: Optional[datetime] class Config: from_attributes = True # Token schemas class Token(BaseModel): access_token: str token_type: str = "bearer" class TokenData(BaseModel): username: Optional[str] = None # Fan curve schemas class FanCurvePoint(BaseModel): temp: float = Field(..., ge=0, le=150, description="Temperature in Celsius") speed: int = Field(..., ge=0, le=100, description="Fan speed percentage") class FanCurveBase(BaseModel): name: str = "Default" curve_data: List[FanCurvePoint] sensor_source: str = "cpu" is_active: bool = True class FanCurveCreate(FanCurveBase): pass class FanCurveResponse(FanCurveBase): id: int server_id: int created_at: datetime updated_at: datetime class Config: from_attributes = True # Server schemas class ServerBase(BaseModel): name: str host: str port: int = 623 username: str vendor: str = "dell" @validator('vendor') def validate_vendor(cls, v): allowed = ['dell', 'hpe', 'supermicro', 'other'] if v.lower() not in allowed: raise ValueError(f'Vendor must be one of: {allowed}') return v.lower() class ServerCreate(ServerBase): password: str @validator('port') def validate_port(cls, v): if not 1 <= v <= 65535: raise ValueError('Port must be between 1 and 65535') return v class ServerUpdate(BaseModel): name: Optional[str] = None host: Optional[str] = None port: Optional[int] = None username: Optional[str] = None password: Optional[str] = None vendor: Optional[str] = None manual_control_enabled: Optional[bool] = None third_party_pcie_response: Optional[bool] = None auto_control_enabled: Optional[bool] = None panic_mode_enabled: Optional[bool] = None panic_timeout_seconds: Optional[int] = None class ServerResponse(ServerBase): id: int manual_control_enabled: bool third_party_pcie_response: bool auto_control_enabled: bool panic_mode_enabled: bool panic_timeout_seconds: int created_at: datetime updated_at: datetime last_seen: Optional[datetime] is_active: bool class Config: from_attributes = True class ServerDetailResponse(ServerResponse): fan_curves: List[FanCurveResponse] = [] class ServerStatusResponse(BaseModel): server: ServerResponse is_connected: bool controller_status: Dict[str, Any] # Sensor schemas class SensorReading(BaseModel): name: str sensor_type: str value: float unit: str status: str class TemperatureReading(BaseModel): name: str location: str value: float status: str class FanReading(BaseModel): fan_id: str fan_number: int speed_rpm: Optional[int] speed_percent: Optional[int] class SensorDataResponse(BaseModel): id: int sensor_name: str sensor_type: str value: float unit: Optional[str] timestamp: datetime class Config: from_attributes = True class FanDataResponse(BaseModel): id: int fan_number: int fan_id: str speed_rpm: Optional[int] speed_percent: Optional[int] is_manual: bool timestamp: datetime class Config: from_attributes = True class ServerSensorsResponse(BaseModel): server_id: int temperatures: List[TemperatureReading] fans: List[FanReading] all_sensors: List[SensorReading] timestamp: datetime # Fan control schemas class FanControlCommand(BaseModel): fan_id: str = Field(default="0xff", description="Fan ID (0xff for all, 0x00-0x07 for specific)") speed_percent: int = Field(..., ge=0, le=100, description="Fan speed percentage") class FanCurveApply(BaseModel): curve_id: Optional[int] = None curve_data: Optional[List[FanCurvePoint]] = None sensor_source: Optional[str] = "cpu" class AutoControlSettings(BaseModel): enabled: bool curve_id: Optional[int] = None # System log schemas class SystemLogResponse(BaseModel): id: int server_id: Optional[int] event_type: str message: str details: Optional[str] timestamp: datetime class Config: from_attributes = True # Setup wizard schemas class SetupStatus(BaseModel): setup_complete: bool class SetupComplete(BaseModel): username: str = Field(..., min_length=3, max_length=50) password: str = Field(..., min_length=8) confirm_password: str = Field(..., min_length=8) @validator('confirm_password') def passwords_match(cls, v, values): if 'password' in values and v != values['password']: raise ValueError('Passwords do not match') return v # Dashboard schemas class DashboardStats(BaseModel): total_servers: int active_servers: int manual_control_servers: int auto_control_servers: int panic_mode_servers: int recent_logs: List[SystemLogResponse] class ServerDashboardData(BaseModel): server: ServerResponse current_temperatures: List[TemperatureReading] current_fans: List[FanReading] recent_sensor_data: List[SensorDataResponse] recent_fan_data: List[FanDataResponse] power_consumption: Optional[Dict[str, str]]