Add SubnetUpdate schema with optional fields for PATCH operations, implement update_subnet endpoint with validation and audit logging, add subnet_label_for_ip helper to match IPs against known subnets using longest prefix matching, update flow_endpoint_label to show "internal (CIDR)" for traffic within known subnets instead of "external", add DNS servers and DHCP toggle to subnet form UI, implement edit
363 lines
7.1 KiB
Python
363 lines
7.1 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class OrmModel(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class TokenPair(BaseModel):
|
|
access_token: str
|
|
refresh_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: str
|
|
password: str
|
|
|
|
|
|
class SetupStatus(BaseModel):
|
|
complete: bool
|
|
has_users: bool
|
|
has_clusters: bool
|
|
|
|
|
|
class SetupCompleteRequest(BaseModel):
|
|
admin_email: str
|
|
admin_name: str
|
|
admin_password: str = Field(min_length=12)
|
|
cluster_name: str | None = None
|
|
cluster_api_url: str | None = None
|
|
cluster_api_token: str | None = None
|
|
cluster_provider: str = "proxmox"
|
|
cluster_mode: str = "read_only"
|
|
verify_tls: bool = True
|
|
|
|
|
|
class UserRead(OrmModel):
|
|
id: str
|
|
email: str
|
|
display_name: str
|
|
is_active: bool
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
email: str
|
|
display_name: str
|
|
password: str = Field(min_length=12)
|
|
role_ids: list[str] = []
|
|
|
|
|
|
class RoleCreate(BaseModel):
|
|
name: str
|
|
permissions: list[str] = []
|
|
|
|
|
|
class RoleRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
permissions: list[str]
|
|
|
|
|
|
class ClusterCreate(BaseModel):
|
|
name: str
|
|
api_url: str
|
|
api_token: str = Field(min_length=8)
|
|
provider: str = "proxmox"
|
|
mode: str = "read_only"
|
|
verify_tls: bool = True
|
|
|
|
|
|
class ClusterUpdate(BaseModel):
|
|
name: str
|
|
api_url: str
|
|
api_token: str | None = Field(default=None, min_length=8)
|
|
provider: str = "proxmox"
|
|
mode: str = "read_only"
|
|
verify_tls: bool = True
|
|
|
|
|
|
class TenantCreate(BaseModel):
|
|
name: str
|
|
description: str | None = None
|
|
|
|
|
|
class ProjectCreate(BaseModel):
|
|
tenant_id: str
|
|
name: str
|
|
description: str | None = None
|
|
|
|
|
|
class NetworkCreate(BaseModel):
|
|
cluster_id: str
|
|
project_id: str | None = None
|
|
name: str
|
|
kind: str = "bridge"
|
|
vlan_id: int | None = None
|
|
mtu: int = 1500
|
|
gateway: str | None = None
|
|
dns: list[str] = []
|
|
dhcp_enabled: bool = False
|
|
tags: list[str] = []
|
|
description: str | None = None
|
|
|
|
|
|
class SubnetCreate(BaseModel):
|
|
network_id: str
|
|
cidr: str
|
|
gateway: str | None = None
|
|
dns: list[str] = []
|
|
dhcp_enabled: bool = False
|
|
|
|
|
|
class SubnetUpdate(BaseModel):
|
|
network_id: str | None = None
|
|
cidr: str | None = None
|
|
gateway: str | None = None
|
|
dns: list[str] | None = None
|
|
dhcp_enabled: bool | None = None
|
|
|
|
|
|
class SecurityGroupCreate(BaseModel):
|
|
project_id: str | None = None
|
|
name: str
|
|
description: str | None = None
|
|
|
|
|
|
class SecurityRuleCreate(BaseModel):
|
|
security_group_id: str
|
|
direction: str = "ingress"
|
|
action: str = "allow"
|
|
protocol: str = "tcp"
|
|
source: str = "any"
|
|
destination: str = "any"
|
|
port: str | None = None
|
|
priority: int = 1000
|
|
logging: bool = False
|
|
description: str | None = None
|
|
|
|
|
|
class PolicyCreate(BaseModel):
|
|
project_id: str | None = None
|
|
name: str
|
|
enabled: bool = True
|
|
definition: dict[str, Any]
|
|
|
|
|
|
class IpReservationCreate(BaseModel):
|
|
subnet_id: str
|
|
address: str
|
|
status: str = "reserved"
|
|
note: str | None = None
|
|
|
|
|
|
class ServiceCatalogCreate(BaseModel):
|
|
name: str
|
|
protocol: str
|
|
ports: str
|
|
editable: bool = True
|
|
|
|
|
|
class FirewallApplyRequest(BaseModel):
|
|
policy_id: str
|
|
cluster_id: str | None = None
|
|
confirm: bool = False
|
|
dry_run: bool = True
|
|
|
|
|
|
class ClusterRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
api_url: str
|
|
provider: str
|
|
mode: str
|
|
verify_tls: bool
|
|
last_sync_at: datetime | None
|
|
last_sync_status: str | None
|
|
last_sync_error: str | None
|
|
|
|
|
|
class NodeRead(OrmModel):
|
|
id: str
|
|
cluster_id: str
|
|
name: str
|
|
status: str
|
|
cpu_count: int
|
|
memory_mb: int
|
|
|
|
|
|
class NodeAgentRead(BaseModel):
|
|
node_id: str
|
|
status: str
|
|
version: str | None = None
|
|
last_seen_at: datetime | None = None
|
|
install_count: int = 0
|
|
last_payload: dict[str, Any] | None = None
|
|
|
|
|
|
class NodeWithAgentRead(NodeRead):
|
|
agent: NodeAgentRead | None = None
|
|
|
|
|
|
class AgentHeartbeat(BaseModel):
|
|
version: str
|
|
node_name: str | None = None
|
|
collected_at: datetime | None = None
|
|
hostname: str | None = None
|
|
kernel: str | None = None
|
|
uptime_seconds: float | None = None
|
|
loadavg: list[float] = []
|
|
interfaces: list[dict[str, Any]] = []
|
|
interface_traffic: list[dict[str, Any]] = []
|
|
flows: list[dict[str, Any]] = []
|
|
conntrack: dict[str, Any] = {}
|
|
firewall: dict[str, Any] = {}
|
|
extra: dict[str, Any] = {}
|
|
|
|
|
|
class WorkloadRead(OrmModel):
|
|
id: str
|
|
cluster_id: str
|
|
node_id: str
|
|
project_id: str | None
|
|
external_id: str
|
|
name: str
|
|
kind: str
|
|
status: str
|
|
tags: list[str]
|
|
|
|
|
|
class NetworkRead(OrmModel):
|
|
id: str
|
|
cluster_id: str
|
|
project_id: str | None
|
|
name: str
|
|
kind: str
|
|
vlan_id: int | None
|
|
mtu: int
|
|
gateway: str | None
|
|
dns: list[str]
|
|
dhcp_enabled: bool
|
|
tags: list[str]
|
|
description: str | None
|
|
|
|
|
|
class SubnetRead(OrmModel):
|
|
id: str
|
|
network_id: str
|
|
cidr: str
|
|
gateway: str | None
|
|
dns: list[str]
|
|
dhcp_enabled: bool
|
|
|
|
|
|
class IpAddressRead(OrmModel):
|
|
id: str
|
|
subnet_id: str
|
|
subnet_cidr: str | None = None
|
|
address: str
|
|
status: str
|
|
workload_id: str | None
|
|
workload_name: str | None = None
|
|
workload_external_id: str | None = None
|
|
note: str | None
|
|
|
|
|
|
class TenantRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
description: str | None
|
|
|
|
|
|
class ProjectRead(OrmModel):
|
|
id: str
|
|
tenant_id: str
|
|
name: str
|
|
description: str | None
|
|
|
|
|
|
class SecurityGroupRead(OrmModel):
|
|
id: str
|
|
project_id: str | None
|
|
name: str
|
|
description: str | None
|
|
|
|
|
|
class SecurityRuleRead(OrmModel):
|
|
id: str
|
|
security_group_id: str
|
|
direction: str
|
|
action: str
|
|
protocol: str
|
|
source: str
|
|
destination: str
|
|
port: str | None
|
|
priority: int
|
|
logging: bool
|
|
description: str | None
|
|
|
|
|
|
class PolicyRead(OrmModel):
|
|
id: str
|
|
project_id: str | None
|
|
name: str
|
|
version: int
|
|
enabled: bool
|
|
enforcement_mode: str
|
|
definition: dict[str, Any]
|
|
last_compiled: dict[str, Any] | None
|
|
|
|
|
|
class WorkloadInsight(BaseModel):
|
|
workload: WorkloadRead
|
|
assigned_ips: list[IpAddressRead]
|
|
traffic: list[dict[str, Any]]
|
|
matching_policies: list[PolicyRead]
|
|
effective_decision: str
|
|
audit_mode_notes: list[str]
|
|
|
|
|
|
class ServiceCatalogRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
protocol: str
|
|
ports: str
|
|
editable: bool
|
|
|
|
|
|
class FirewallPreview(BaseModel):
|
|
policy_id: str
|
|
dry_run: bool = True
|
|
generated_rules: list[dict[str, Any]]
|
|
warnings: list[str]
|
|
conflicts: list[str]
|
|
|
|
|
|
class JobRead(OrmModel):
|
|
id: str
|
|
kind: str
|
|
status: str
|
|
progress: int
|
|
started_at: datetime | None
|
|
finished_at: datetime | None
|
|
logs: list[str]
|
|
error: str | None
|
|
|
|
|
|
class AuditLogRead(OrmModel):
|
|
id: str
|
|
created_at: datetime
|
|
user_id: str | None
|
|
action: str
|
|
object_type: str
|
|
object_id: str | None
|
|
old_values: dict[str, Any] | None
|
|
new_values: dict[str, Any] | None
|
|
ip_address: str | None
|
|
user_agent: str | None
|
|
result: str
|
|
error_text: str | None
|