Files
NexaFabric/backend/app/models/domain.py
T
nessi 3cd2c0a2f1
CI / backend (push) Failing after 3s
CI / frontend (push) Failing after 29s
feat: add initial setup wizard, workload insights, and policy audit mode
Add setup wizard with status tracking via SystemSetting model, implement /setup/status and /setup/complete endpoints to create initial admin user and optional cluster configuration, add workload insights endpoint with traffic analysis and policy matching including audit mode detection, implement enforcement_mode property on Policy model with audit/enforced states, add Modal component for dialogs, create SetupWizard page with multi
2026-07-09 12:47:08 +02:00

265 lines
11 KiB
Python

from datetime import datetime
from enum import StrEnum
from uuid import uuid4
from sqlalchemy import JSON, Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.session import Base
def new_id() -> str:
return str(uuid4())
class ClusterMode(StrEnum):
read_only = "read_only"
write_enabled = "write_enabled"
class JobStatus(StrEnum):
queued = "queued"
running = "running"
success = "success"
failed = "failed"
cancelled = "cancelled"
class IpStatus(StrEnum):
free = "free"
reserved = "reserved"
assigned = "assigned"
deprecated = "deprecated"
conflict = "conflict"
class RuleAction(StrEnum):
allow = "allow"
deny = "deny"
reject = "reject"
class Direction(StrEnum):
ingress = "ingress"
egress = "egress"
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class SystemSetting(Base, TimestampMixin):
__tablename__ = "system_settings"
key: Mapped[str] = mapped_column(String(100), primary_key=True)
value: Mapped[dict] = mapped_column(JSON, default=dict)
class User(Base, TimestampMixin):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
display_name: Mapped[str] = mapped_column(String(255))
password_hash: Mapped[str] = mapped_column(String(512))
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
failed_login_attempts: Mapped[int] = mapped_column(Integer, default=0)
roles: Mapped[list["Role"]] = relationship(secondary="user_roles", back_populates="users")
class Role(Base, TimestampMixin):
__tablename__ = "roles"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
name: Mapped[str] = mapped_column(String(100), unique=True)
permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
users: Mapped[list[User]] = relationship(secondary="user_roles", back_populates="roles")
class UserRole(Base):
__tablename__ = "user_roles"
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), primary_key=True)
role_id: Mapped[str] = mapped_column(ForeignKey("roles.id"), primary_key=True)
class Tenant(Base, TimestampMixin):
__tablename__ = "tenants"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
name: Mapped[str] = mapped_column(String(255), unique=True)
description: Mapped[str | None] = mapped_column(Text)
class Project(Base, TimestampMixin):
__tablename__ = "projects"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
tenant_id: Mapped[str] = mapped_column(ForeignKey("tenants.id"), index=True)
name: Mapped[str] = mapped_column(String(255))
description: Mapped[str | None] = mapped_column(Text)
tenant: Mapped[Tenant] = relationship()
class Cluster(Base, TimestampMixin):
__tablename__ = "clusters"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
name: Mapped[str] = mapped_column(String(255), unique=True)
api_url: Mapped[str] = mapped_column(String(512))
provider: Mapped[str] = mapped_column(String(100), default="proxmox")
mode: Mapped[ClusterMode] = mapped_column(Enum(ClusterMode), default=ClusterMode.read_only)
token_ref: Mapped[str | None] = mapped_column(String(512))
verify_tls: Mapped[bool] = mapped_column(Boolean, default=True)
last_sync_at: Mapped[datetime | None] = mapped_column(DateTime)
last_sync_status: Mapped[str | None] = mapped_column(String(100))
last_sync_error: Mapped[str | None] = mapped_column(Text)
class Node(Base, TimestampMixin):
__tablename__ = "nodes"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
cluster_id: Mapped[str] = mapped_column(ForeignKey("clusters.id"), index=True)
name: Mapped[str] = mapped_column(String(255))
status: Mapped[str] = mapped_column(String(100), default="unknown")
cpu_count: Mapped[int] = mapped_column(Integer, default=0)
memory_mb: Mapped[int] = mapped_column(Integer, default=0)
cluster: Mapped[Cluster] = relationship()
class Workload(Base, TimestampMixin):
__tablename__ = "workloads"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
cluster_id: Mapped[str] = mapped_column(ForeignKey("clusters.id"), index=True)
node_id: Mapped[str] = mapped_column(ForeignKey("nodes.id"), index=True)
project_id: Mapped[str | None] = mapped_column(ForeignKey("projects.id"), index=True)
external_id: Mapped[str] = mapped_column(String(100))
name: Mapped[str] = mapped_column(String(255))
kind: Mapped[str] = mapped_column(String(50))
status: Mapped[str] = mapped_column(String(100), default="unknown")
tags: Mapped[list[str]] = mapped_column(JSON, default=list)
class Network(Base, TimestampMixin):
__tablename__ = "networks"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
cluster_id: Mapped[str] = mapped_column(ForeignKey("clusters.id"), index=True)
project_id: Mapped[str | None] = mapped_column(ForeignKey("projects.id"), index=True)
name: Mapped[str] = mapped_column(String(255))
kind: Mapped[str] = mapped_column(String(50))
vlan_id: Mapped[int | None] = mapped_column(Integer)
mtu: Mapped[int] = mapped_column(Integer, default=1500)
gateway: Mapped[str | None] = mapped_column(String(100))
dns: Mapped[list[str]] = mapped_column(JSON, default=list)
dhcp_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
tags: Mapped[list[str]] = mapped_column(JSON, default=list)
description: Mapped[str | None] = mapped_column(Text)
class Subnet(Base, TimestampMixin):
__tablename__ = "subnets"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
network_id: Mapped[str] = mapped_column(ForeignKey("networks.id"), index=True)
cidr: Mapped[str] = mapped_column(String(100))
gateway: Mapped[str | None] = mapped_column(String(100))
dns: Mapped[list[str]] = mapped_column(JSON, default=list)
dhcp_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
class IpAddress(Base, TimestampMixin):
__tablename__ = "ip_addresses"
__table_args__ = (UniqueConstraint("subnet_id", "address"),)
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
subnet_id: Mapped[str] = mapped_column(ForeignKey("subnets.id"), index=True)
address: Mapped[str] = mapped_column(String(100))
status: Mapped[IpStatus] = mapped_column(Enum(IpStatus), default=IpStatus.free)
workload_id: Mapped[str | None] = mapped_column(ForeignKey("workloads.id"), index=True)
note: Mapped[str | None] = mapped_column(Text)
class SecurityGroup(Base, TimestampMixin):
__tablename__ = "security_groups"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
project_id: Mapped[str | None] = mapped_column(ForeignKey("projects.id"), index=True)
name: Mapped[str] = mapped_column(String(255))
description: Mapped[str | None] = mapped_column(Text)
class SecurityRule(Base, TimestampMixin):
__tablename__ = "security_rules"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
security_group_id: Mapped[str] = mapped_column(ForeignKey("security_groups.id"), index=True)
direction: Mapped[Direction] = mapped_column(Enum(Direction))
action: Mapped[RuleAction] = mapped_column(Enum(RuleAction))
protocol: Mapped[str] = mapped_column(String(20), default="any")
source: Mapped[str] = mapped_column(String(255), default="any")
destination: Mapped[str] = mapped_column(String(255), default="any")
port: Mapped[str | None] = mapped_column(String(100))
priority: Mapped[int] = mapped_column(Integer, default=1000)
logging: Mapped[bool] = mapped_column(Boolean, default=False)
description: Mapped[str | None] = mapped_column(Text)
class Policy(Base, TimestampMixin):
__tablename__ = "policies"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
project_id: Mapped[str | None] = mapped_column(ForeignKey("projects.id"), index=True)
name: Mapped[str] = mapped_column(String(255))
version: Mapped[int] = mapped_column(Integer, default=1)
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
definition: Mapped[dict] = mapped_column(JSON, default=dict)
last_compiled: Mapped[dict | None] = mapped_column(JSON)
@property
def enforcement_mode(self) -> str:
return (self.definition or {}).get("enforcement_mode", "enforced")
class ServiceCatalogItem(Base, TimestampMixin):
__tablename__ = "service_catalog"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
name: Mapped[str] = mapped_column(String(255), unique=True)
protocol: Mapped[str] = mapped_column(String(20))
ports: Mapped[str] = mapped_column(String(100))
editable: Mapped[bool] = mapped_column(Boolean, default=True)
class Job(Base, TimestampMixin):
__tablename__ = "jobs"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
kind: Mapped[str] = mapped_column(String(100))
status: Mapped[JobStatus] = mapped_column(Enum(JobStatus), default=JobStatus.queued)
progress: Mapped[int] = mapped_column(Integer, default=0)
started_at: Mapped[datetime | None] = mapped_column(DateTime)
finished_at: Mapped[datetime | None] = mapped_column(DateTime)
logs: Mapped[list[str]] = mapped_column(JSON, default=list)
error: Mapped[str | None] = mapped_column(Text)
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), index=True)
action: Mapped[str] = mapped_column(String(100), index=True)
object_type: Mapped[str] = mapped_column(String(100), index=True)
object_id: Mapped[str | None] = mapped_column(String(100), index=True)
old_values: Mapped[dict | None] = mapped_column(JSON)
new_values: Mapped[dict | None] = mapped_column(JSON)
ip_address: Mapped[str | None] = mapped_column(String(100))
user_agent: Mapped[str | None] = mapped_column(String(512))
result: Mapped[str] = mapped_column(String(100), default="success")
error_text: Mapped[str | None] = mapped_column(Text)