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