ipmi-fan-control/backend/schemas.py

284 lines
7.1 KiB
Python

"""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
@validator('curve_data', pre=True)
def parse_curve_data(cls, v):
"""Parse curve_data from JSON string if needed."""
if isinstance(v, str):
import json
return json.loads(v)
return v
# IPMI Settings schema
class IPMISettings(BaseModel):
ipmi_host: str = Field(..., description="IPMI IP address or hostname")
ipmi_port: int = Field(623, description="IPMI port (default 623)")
ipmi_username: str = Field(..., description="IPMI username")
ipmi_password: str = Field(..., description="IPMI password")
# SSH Settings schema
class SSHSettings(BaseModel):
use_ssh: bool = Field(False, description="Enable SSH for sensor data collection")
ssh_host: Optional[str] = Field(None, description="SSH host (leave empty to use IPMI host)")
ssh_port: int = Field(22, description="SSH port (default 22)")
ssh_username: Optional[str] = Field(None, description="SSH username")
ssh_password: Optional[str] = Field(None, description="SSH password (or use key)")
ssh_key_file: Optional[str] = Field(None, description="Path to SSH private key file")
# Server schemas
class ServerBase(BaseModel):
name: str = Field(..., description="Server name/display name")
vendor: str = Field("dell", description="Server vendor (dell, hpe, supermicro, other)")
@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):
ipmi: IPMISettings
ssh: SSHSettings = Field(default_factory=lambda: SSHSettings(use_ssh=False))
class ServerUpdate(BaseModel):
name: Optional[str] = None
ipmi_host: Optional[str] = None
ipmi_port: Optional[int] = None
ipmi_username: Optional[str] = None
ipmi_password: Optional[str] = None
ssh_host: Optional[str] = None
ssh_port: Optional[int] = None
ssh_username: Optional[str] = None
ssh_password: Optional[str] = None
ssh_key_file: Optional[str] = None
use_ssh: Optional[bool] = 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
ipmi_host: str
ipmi_port: int
ipmi_username: str
ssh_host: Optional[str]
ssh_port: int
ssh_username: Optional[str]
use_ssh: bool
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]]