- PostgreSQL schema: 14 tables, JSONB attributes, audit triggers, soft delete - FastAPI backend: CRUD, search/filter, relationship graph, bulk import, JWT RBAC - React frontend: CI table, detail card, force-graph, dashboard - Seed data: homelab scenario (Proxmox, Mikrotik, VMs, services) - Docker Compose + Kubernetes manifests - 20 backend tests (pytest + httpx)
315 lines
8.6 KiB
Python
315 lines
8.6 KiB
Python
from __future__ import annotations
|
|
from datetime import date, datetime
|
|
from uuid import UUID
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
from typing import Optional
|
|
|
|
|
|
# ── Auth ────────────────────────────────────────────────
|
|
class UserCreate(BaseModel):
|
|
username: str
|
|
email: str
|
|
full_name: Optional[str] = None
|
|
role: str = "viewer"
|
|
team: Optional[str] = None
|
|
password: str
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
username: str
|
|
email: str
|
|
full_name: Optional[str]
|
|
role: str
|
|
team: Optional[str]
|
|
is_active: bool
|
|
created_at: datetime
|
|
|
|
|
|
class Token(BaseModel):
|
|
access_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
|
|
# ── Locations ───────────────────────────────────────────
|
|
class LocationCreate(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
parent_id: Optional[UUID] = None
|
|
metadata: dict = {}
|
|
|
|
|
|
class LocationResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
name: str
|
|
description: Optional[str]
|
|
parent_id: Optional[UUID]
|
|
metadata: dict
|
|
created_at: datetime
|
|
|
|
|
|
# ── CI Classes / Types ──────────────────────────────────
|
|
class CIClassCreate(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
parent_id: Optional[UUID] = None
|
|
icon: Optional[str] = None
|
|
|
|
|
|
class CIClassResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
name: str
|
|
description: Optional[str]
|
|
parent_id: Optional[UUID]
|
|
icon: Optional[str]
|
|
|
|
|
|
class CITypeCreate(BaseModel):
|
|
class_id: UUID
|
|
name: str
|
|
description: Optional[str] = None
|
|
|
|
|
|
class CITypeResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
class_id: UUID
|
|
name: str
|
|
description: Optional[str]
|
|
|
|
|
|
# ── Configuration Item ──────────────────────────────────
|
|
class CICreate(BaseModel):
|
|
ci_type_id: UUID
|
|
name: str
|
|
description: Optional[str] = None
|
|
status: str = "active"
|
|
location_id: Optional[UUID] = None
|
|
serial_number: Optional[str] = None
|
|
asset_tag: Optional[str] = None
|
|
purchase_date: Optional[date] = None
|
|
warranty_expiry: Optional[date] = None
|
|
attributes: dict = {}
|
|
tags: list[str] = []
|
|
|
|
|
|
class CIUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
status: Optional[str] = None
|
|
location_id: Optional[UUID] = None
|
|
serial_number: Optional[str] = None
|
|
asset_tag: Optional[str] = None
|
|
purchase_date: Optional[date] = None
|
|
warranty_expiry: Optional[date] = None
|
|
attributes: Optional[dict] = None
|
|
tags: Optional[list[str]] = None
|
|
|
|
|
|
class CIResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
ci_type_id: UUID
|
|
ci_type: Optional[CITypeResponse] = None
|
|
name: str
|
|
description: Optional[str]
|
|
status: str
|
|
location_id: Optional[UUID]
|
|
location: Optional[LocationResponse] = None
|
|
serial_number: Optional[str]
|
|
asset_tag: Optional[str]
|
|
purchase_date: Optional[date]
|
|
warranty_expiry: Optional[date]
|
|
attributes: dict
|
|
tags: list[str]
|
|
version: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class CIDetailResponse(CIResponse):
|
|
ip_addresses: list[IPAddressResponse] = []
|
|
network_interfaces: list[NetworkInterfaceResponse] = []
|
|
hardware_detail: Optional[HardwareDetailResponse] = None
|
|
software_instances: list[SoftwareInstanceResponse] = []
|
|
relationships_out: list[RelationshipResponse] = []
|
|
relationships_in: list[RelationshipResponse] = []
|
|
owners: list[OwnerResponse] = []
|
|
|
|
|
|
# ── IP Address ──────────────────────────────────────────
|
|
class IPAddressCreate(BaseModel):
|
|
ip_address: str
|
|
subnet_mask: Optional[str] = None
|
|
gateway: Optional[str] = None
|
|
is_primary: bool = False
|
|
vlan_id: Optional[int] = None
|
|
|
|
|
|
class IPAddressResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
ci_id: UUID
|
|
ip_address: str
|
|
subnet_mask: Optional[str]
|
|
gateway: Optional[str]
|
|
is_primary: bool
|
|
vlan_id: Optional[int]
|
|
|
|
|
|
# ── Network Interface ───────────────────────────────────
|
|
class NetworkInterfaceCreate(BaseModel):
|
|
name: str
|
|
mac_address: Optional[str] = None
|
|
speed_mbps: Optional[int] = None
|
|
interface_type: str = "ethernet"
|
|
is_up: bool = True
|
|
ip_address_id: Optional[UUID] = None
|
|
|
|
|
|
class NetworkInterfaceResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
ci_id: UUID
|
|
name: str
|
|
mac_address: Optional[str]
|
|
speed_mbps: Optional[int]
|
|
interface_type: str
|
|
is_up: bool
|
|
ip_address_id: Optional[UUID]
|
|
|
|
|
|
# ── Hardware Detail ─────────────────────────────────────
|
|
class HardwareDetailCreate(BaseModel):
|
|
manufacturer: Optional[str] = None
|
|
model: Optional[str] = None
|
|
cpu_model: Optional[str] = None
|
|
cpu_cores: Optional[int] = None
|
|
ram_gb: Optional[float] = None
|
|
storage_gb: Optional[float] = None
|
|
storage_type: Optional[str] = None
|
|
form_factor: Optional[str] = None
|
|
specs: dict = {}
|
|
|
|
|
|
class HardwareDetailResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
ci_id: UUID
|
|
manufacturer: Optional[str]
|
|
model: Optional[str]
|
|
cpu_model: Optional[str]
|
|
cpu_cores: Optional[int]
|
|
ram_gb: Optional[float]
|
|
storage_gb: Optional[float]
|
|
storage_type: Optional[str]
|
|
form_factor: Optional[str]
|
|
specs: dict
|
|
|
|
|
|
# ── Software Instance ───────────────────────────────────
|
|
class SoftwareInstanceCreate(BaseModel):
|
|
name: str
|
|
vendor: Optional[str] = None
|
|
version: Optional[str] = None
|
|
port: Optional[int] = None
|
|
protocol: Optional[str] = None
|
|
config: dict = {}
|
|
|
|
|
|
class SoftwareInstanceResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
ci_id: UUID
|
|
name: str
|
|
vendor: Optional[str]
|
|
version: Optional[str]
|
|
port: Optional[int]
|
|
protocol: Optional[str]
|
|
config: dict
|
|
|
|
|
|
# ── Relationships ───────────────────────────────────────
|
|
class RelationshipCreate(BaseModel):
|
|
source_ci_id: UUID
|
|
target_ci_id: UUID
|
|
relationship: str
|
|
description: Optional[str] = None
|
|
metadata: dict = {}
|
|
|
|
|
|
class RelationshipResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
source_ci_id: UUID
|
|
target_ci_id: UUID
|
|
relationship: str
|
|
description: Optional[str]
|
|
metadata: dict
|
|
created_at: datetime
|
|
|
|
|
|
# ── Owner ───────────────────────────────────────────────
|
|
class OwnerCreate(BaseModel):
|
|
user_id: UUID
|
|
role: str = "owner"
|
|
|
|
|
|
class OwnerResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
ci_id: UUID
|
|
user_id: UUID
|
|
role: str
|
|
|
|
|
|
# ── Pagination ──────────────────────────────────────────
|
|
class PaginatedResponse(BaseModel):
|
|
items: list
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
pages: int
|
|
|
|
|
|
# ── Graph ───────────────────────────────────────────────
|
|
class GraphNode(BaseModel):
|
|
id: str
|
|
label: str
|
|
group: str
|
|
status: str
|
|
|
|
|
|
class GraphEdge(BaseModel):
|
|
source: str
|
|
target: str
|
|
label: str
|
|
|
|
|
|
class GraphData(BaseModel):
|
|
nodes: list[GraphNode]
|
|
edges: list[GraphEdge]
|
|
|
|
|
|
# ── Bulk Import ─────────────────────────────────────────
|
|
class BulkImportItem(BaseModel):
|
|
name: str
|
|
ci_type_name: str
|
|
description: Optional[str] = None
|
|
status: str = "active"
|
|
location_name: Optional[str] = None
|
|
attributes: dict = {}
|
|
tags: list[str] = []
|
|
|
|
|
|
class BulkImportRequest(BaseModel):
|
|
items: list[BulkImportItem]
|