feat: add node agent system with heartbeat collection, installer generation, and traffic flow telemetry
CI / backend (push) Failing after 3s
CI / frontend (push) Failing after 28s

Add NodeAgent and TrafficFlow models to track agent status and network flows, implement /agents/heartbeat endpoint to receive interface counters, conntrack flows, firewall status, and nftables ruleset hash from agents, add nexafabric-agent.py Python script to collect host telemetry including VM/LXC interface hints via tap/fwbr regex matching, conntrack flow parsing with protocol/state/byte counters, and pve-firewall status checks,
This commit is contained in:
2026-07-09 14:13:47 +02:00
parent 88badf1f22
commit e67174a4ae
8 changed files with 677 additions and 7 deletions
+30 -1
View File
@@ -2,7 +2,7 @@ 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 import JSON, BigInteger, Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.session import Base
@@ -129,6 +129,18 @@ class Node(Base, TimestampMixin):
cluster: Mapped[Cluster] = relationship()
class NodeAgent(Base, TimestampMixin):
__tablename__ = "node_agents"
node_id: Mapped[str] = mapped_column(ForeignKey("nodes.id"), primary_key=True)
status: Mapped[str] = mapped_column(String(100), default="not_installed")
version: Mapped[str | None] = mapped_column(String(50))
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime)
last_payload: Mapped[dict | None] = mapped_column(JSON)
install_count: Mapped[int] = mapped_column(Integer, default=0)
node: Mapped[Node] = relationship()
class Workload(Base, TimestampMixin):
__tablename__ = "workloads"
@@ -183,6 +195,23 @@ class IpAddress(Base, TimestampMixin):
note: Mapped[str | None] = mapped_column(Text)
class TrafficFlow(Base, TimestampMixin):
__tablename__ = "traffic_flows"
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
node_id: Mapped[str] = mapped_column(ForeignKey("nodes.id"), index=True)
source_ip: Mapped[str] = mapped_column(String(100), index=True)
destination_ip: Mapped[str] = mapped_column(String(100), index=True)
protocol: Mapped[str] = mapped_column(String(20), default="unknown")
source_port: Mapped[int | None] = mapped_column(Integer)
destination_port: Mapped[int | None] = mapped_column(Integer)
bytes: Mapped[int] = mapped_column(BigInteger, default=0)
packets: Mapped[int] = mapped_column(BigInteger, default=0)
state: Mapped[str | None] = mapped_column(String(100))
observed_at: Mapped[datetime | None] = mapped_column(DateTime)
raw: Mapped[dict | None] = mapped_column(JSON)
class SecurityGroup(Base, TimestampMixin):
__tablename__ = "security_groups"