chore: initial project setup with backend, frontend, CI/CD, and documentation
CI / backend (push) Failing after 15s
CI / frontend (push) Failing after 39s

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
This commit is contained in:
2026-07-09 12:10:35 +02:00
commit 14e7710120
83 changed files with 2887 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
from sqlalchemy.orm import Session
from app.models.domain import AuditLog
def write_audit(
db: Session,
*,
action: str,
object_type: str,
object_id: str | None = None,
user_id: str | None = None,
old_values: dict | None = None,
new_values: dict | None = None,
result: str = "success",
error_text: str | None = None,
) -> AuditLog:
audit = AuditLog(
action=action,
object_type=object_type,
object_id=object_id,
user_id=user_id,
old_values=old_values,
new_values=new_values,
result=result,
error_text=error_text,
)
db.add(audit)
db.commit()
db.refresh(audit)
return audit
@@ -0,0 +1,29 @@
from app.models.domain import Cluster, Policy
from app.schemas.domain import FirewallPreview
from app.services.policy_engine import PolicyEngine
from app.services.providers.base import ProviderConnection
from app.services.providers.registry import get_provider
class FirewallOrchestrator:
def __init__(self) -> None:
self.policy_engine = PolicyEngine()
async def preview(self, cluster: Cluster, policy: Policy) -> FirewallPreview:
compiled = self.policy_engine.compile(policy)
provider = get_provider(cluster.provider)
connection = ProviderConnection(
api_url=cluster.api_url,
token=cluster.token_ref or "",
verify_tls=cluster.verify_tls,
read_only=cluster.mode == "read_only",
)
provider_preview = await provider.preview_rules(connection, compiled["rules"])
return FirewallPreview(
policy_id=policy.id,
dry_run=True,
generated_rules=provider_preview["generated"],
warnings=[*compiled["warnings"], *provider_preview.get("warnings", [])],
conflicts=compiled["conflicts"],
)
+35
View File
@@ -0,0 +1,35 @@
from typing import Any
from app.models.domain import Policy
class PolicyEngine:
def compile(self, policy: Policy) -> dict[str, Any]:
definition = policy.definition or {}
source = definition.get("source", "any")
destination = definition.get("destination", "any")
service = definition.get("service", {"protocol": "any", "ports": "any"})
action = definition.get("action", "allow")
direction = definition.get("direction", "ingress")
generated_rule = {
"policy_id": policy.id,
"policy_version": policy.version,
"source": source,
"destination": destination,
"protocol": service.get("protocol", "any"),
"ports": service.get("ports", "any"),
"direction": direction,
"action": action,
"logging": bool(definition.get("logging", False)),
"description": definition.get("description", policy.name),
}
warnings = []
if source == "any" and destination == "any":
warnings.append("Policy targets all sources and destinations.")
if action == "allow" and service.get("ports") == "any":
warnings.append("Broad allow policy uses all ports.")
return {"rules": [generated_rule], "warnings": warnings, "conflicts": []}
+50
View File
@@ -0,0 +1,50 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class ProviderConnection:
api_url: str
token: str
verify_tls: bool = True
read_only: bool = True
class HypervisorProvider(ABC):
@abstractmethod
async def test_connection(self, connection: ProviderConnection) -> dict[str, Any]:
raise NotImplementedError
class InventoryProvider(ABC):
@abstractmethod
async def sync_inventory(self, connection: ProviderConnection) -> dict[str, list[dict[str, Any]]]:
raise NotImplementedError
class NetworkProvider(ABC):
@abstractmethod
async def list_networks(self, connection: ProviderConnection) -> list[dict[str, Any]]:
raise NotImplementedError
class FirewallProvider(ABC):
@abstractmethod
async def preview_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
raise NotImplementedError
@abstractmethod
async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
raise NotImplementedError
class Provider(
HypervisorProvider,
InventoryProvider,
NetworkProvider,
FirewallProvider,
ABC,
):
name: str
+35
View File
@@ -0,0 +1,35 @@
from typing import Any
from app.services.providers.base import Provider, ProviderConnection
class DemoProvider(Provider):
name = "demo"
async def test_connection(self, connection: ProviderConnection) -> dict[str, Any]:
return {"version": "demo", "api_url": connection.api_url, "mode": "offline"}
async def sync_inventory(self, connection: ProviderConnection) -> dict[str, list[dict[str, Any]]]:
return {
"nodes": [
{"node": "demo-pve-01", "status": "online", "maxcpu": 16, "maxmem": 68719476736},
{"node": "demo-pve-02", "status": "online", "maxcpu": 16, "maxmem": 68719476736},
],
"workloads": [
{"vmid": 201, "name": "demo-web-01", "type": "qemu", "status": "running", "node": "demo-pve-01"},
{"vmid": 202, "name": "demo-db-01", "type": "qemu", "status": "running", "node": "demo-pve-02"},
],
"networks": [
{"name": "vmbr0", "type": "bridge", "vlan": 10},
{"name": "vnet-prod", "type": "vnet", "vlan": 120},
],
}
async def list_networks(self, connection: ProviderConnection) -> list[dict[str, Any]]:
return (await self.sync_inventory(connection))["networks"]
async def preview_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
return {"provider": self.name, "generated": rules, "warnings": ["Demo provider preview."]}
async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
return {"applied": False, "reason": "Demo provider never applies firewall changes.", "rules": rules}
+56
View File
@@ -0,0 +1,56 @@
from typing import Any
import httpx
from app.services.providers.base import Provider, ProviderConnection
class ProxmoxProvider(Provider):
name = "proxmox"
async def test_connection(self, connection: ProviderConnection) -> dict[str, Any]:
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=10) as client:
response = await client.get(
f"{connection.api_url.rstrip('/')}/api2/json/version",
headers={"Authorization": connection.token},
)
response.raise_for_status()
return response.json().get("data", {})
async def sync_inventory(self, connection: ProviderConnection) -> dict[str, list[dict[str, Any]]]:
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=20) as client:
resources = await client.get(
f"{connection.api_url.rstrip('/')}/api2/json/cluster/resources",
headers={"Authorization": connection.token},
)
resources.raise_for_status()
data = resources.json().get("data", [])
nodes = [item for item in data if item.get("type") == "node"]
workloads = [item for item in data if item.get("type") in {"qemu", "lxc"}]
networks = await self.list_networks(connection)
return {"nodes": nodes, "workloads": workloads, "networks": networks}
async def list_networks(self, connection: ProviderConnection) -> list[dict[str, Any]]:
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=20) as client:
resources = await client.get(
f"{connection.api_url.rstrip('/')}/api2/json/cluster/resources",
headers={"Authorization": connection.token},
)
resources.raise_for_status()
data = resources.json().get("data", [])
return [item for item in data if item.get("type") in {"network", "sdn"}]
async def preview_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
return {
"provider": self.name,
"read_only": connection.read_only,
"generated": rules,
"warnings": ["Preview only. No Proxmox firewall changes were sent."],
}
async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
if connection.read_only:
return {"applied": False, "reason": "Cluster is read-only", "rules": rules}
return {"applied": False, "reason": "Apply adapter intentionally requires explicit implementation", "rules": rules}
@@ -0,0 +1,10 @@
from app.services.providers.base import Provider
from app.services.providers.demo import DemoProvider
from app.services.providers.proxmox import ProxmoxProvider
_providers: dict[str, Provider] = {"proxmox": ProxmoxProvider(), "demo": DemoProvider()}
def get_provider(name: str) -> Provider:
return _providers[name]