Files
NexaFabric/backend/app/models/domain.py
T
nessi 14e7710120
CI / backend (push) Failing after 15s
CI / frontend (push) Failing after 39s
chore: initial project setup with backend, frontend, CI/CD, and documentation
Add complete NexaFabric project structure including:
- FastAPI backend with SQLAlchemy models, JWT auth, RBAC, audit logging, and provider interfaces
- React + TypeScript frontend with Vite, Tailwind CSS, TanStack Query, and Zustand
- Docker Compose configuration for PostgreSQL, Redis, API, worker, frontend, and nginx
- GitHub Actions and GitLab CI workflows for testing, linting, building, and security scanning
- Environment
2026-07-09 12:10:35 +02:00

255 lines
10 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 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)
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)