diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46369e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.pyc +.pytest_cache/ +*.egg-info/ +venv/ +.env diff --git a/agent/__init__.py b/agent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agent/models.py b/agent/models.py new file mode 100644 index 0000000..abf79a4 --- /dev/null +++ b/agent/models.py @@ -0,0 +1,28 @@ +from pydantic import BaseModel + + +class ContainerInfo(BaseModel): + id: str + name: str + status: str + image: str + created: str + uptime: str | None = None + is_swarm: bool = False + swarm_service: str | None = None + + +class ContainerAction(BaseModel): + success: bool + message: str + + +class LogsResponse(BaseModel): + container: str + logs: str + + +class HealthResponse(BaseModel): + status: str + hostname: str + containers_total: int diff --git a/agent/requirements.txt b/agent/requirements.txt new file mode 100644 index 0000000..ce4f482 --- /dev/null +++ b/agent/requirements.txt @@ -0,0 +1,3 @@ +fastapi>=0.115.0 +uvicorn>=0.34.0 +docker>=7.0.0 diff --git a/config.json b/config.json new file mode 100644 index 0000000..9dbbfa9 --- /dev/null +++ b/config.json @@ -0,0 +1,7 @@ +{ + "nodes": [ + {"name": "hf-pdocker-01", "host": "192.168.86.192", "agent_port": 8889}, + {"name": "hf-pdocker-02", "host": "192.168.86.100", "agent_port": 8889}, + {"name": "bart", "host": "192.168.86.167", "agent_port": 8889} + ] +} diff --git a/groups.json b/groups.json new file mode 100644 index 0000000..49317a9 --- /dev/null +++ b/groups.json @@ -0,0 +1,103 @@ +{ + "groups": [ + { + "id": "ai-agents", + "name": "AI Agents", + "services": [ + {"node": "hf-pdocker-01", "container": "ai-agents_margaret"}, + {"node": "hf-pdocker-01", "container": "ai-agents_spirit"}, + {"node": "hf-pdocker-01", "container": "ai-agents_bob"}, + {"node": "hf-pdocker-01", "container": "ai-agents_clippy"} + ] + }, + { + "id": "smart-home", + "name": "Smart Home", + "services": [ + {"node": "hf-pdocker-01", "container": "smarthome_homeassistant"}, + {"node": "hf-pdocker-01", "container": "smarthome_esphome"}, + {"node": "hf-pdocker-01", "container": "smarthome_mosquitto"} + ] + }, + { + "id": "monitoring", + "name": "Monitoring", + "services": [ + {"node": "hf-pdocker-01", "container": "monitoring_grafana"}, + {"node": "hf-pdocker-01", "container": "monitoring_prometheus"}, + {"node": "hf-pdocker-01", "container": "monitoring_alertmanager"}, + {"node": "hf-pdocker-01", "container": "monitoring_influxdb"}, + {"node": "hf-pdocker-01", "container": "monitoring_blackbox-exporter"} + ] + }, + { + "id": "media", + "name": "Media", + "services": [ + {"node": "bart", "container": "jellyfin"}, + {"node": "bart", "container": "sonarr"}, + {"node": "bart", "container": "radarr"}, + {"node": "bart", "container": "sabnzbd"}, + {"node": "bart", "container": "nzbhydra2"}, + {"node": "bart", "container": "jellyseerr"} + ] + }, + { + "id": "matrix", + "name": "Matrix", + "services": [ + {"node": "hf-pdocker-01", "container": "matrix_synapse"}, + {"node": "hf-pdocker-01", "container": "matrix_synapse-db"}, + {"node": "hf-pdocker-01", "container": "matrix_wellknown"} + ] + }, + { + "id": "guacamole", + "name": "Guacamole", + "services": [ + {"node": "hf-pdocker-01", "container": "guacamole_guacamole"}, + {"node": "hf-pdocker-01", "container": "guacamole_guacd"}, + {"node": "hf-pdocker-01", "container": "guacamole_guacamole-db"} + ] + }, + { + "id": "dev-tools", + "name": "Dev Tools", + "services": [ + {"node": "hf-pdocker-02", "container": "gitea"}, + {"node": "hf-pdocker-02", "container": "gitea-db"}, + {"node": "hf-pdocker-02", "container": "semaphore"}, + {"node": "hf-pdocker-02", "container": "semaphore-db"}, + {"node": "hf-pdocker-02", "container": "wikijs"}, + {"node": "hf-pdocker-02", "container": "wikijs-db"} + ] + }, + { + "id": "security", + "name": "Security", + "services": [ + {"node": "hf-pdocker-02", "container": "vaultwarden"}, + {"node": "hf-pdocker-02", "container": "trivy"}, + {"node": "bart", "container": "openvas"} + ] + }, + { + "id": "network", + "name": "Network", + "services": [ + {"node": "hf-pdocker-01", "container": "traefik_traefik"}, + {"node": "hf-pdocker-01", "container": "adguard_adguardhome"}, + {"node": "hf-pdocker-02", "container": "unifi"}, + {"node": "hf-pdocker-02", "container": "unifi-db"} + ] + }, + { + "id": "frigate", + "name": "Frigate", + "services": [ + {"node": "hf-pdocker-01", "container": "frigate"}, + {"node": "hf-pdocker-01", "container": "frigate-notify"} + ] + } + ] +} diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/models.py b/server/models.py new file mode 100644 index 0000000..08b4e5f --- /dev/null +++ b/server/models.py @@ -0,0 +1,42 @@ +from pydantic import BaseModel + + +class NodeConfig(BaseModel): + name: str + host: str + agent_port: int = 8889 + + +class NodeStatus(BaseModel): + name: str + host: str + healthy: bool + containers_total: int = 0 + error: str | None = None + + +class ServiceRef(BaseModel): + node: str + container: str + + +class Group(BaseModel): + id: str + name: str + services: list[ServiceRef] = [] + + +class GroupsData(BaseModel): + groups: list[Group] = [] + + +class ServiceInfo(BaseModel): + id: str + name: str + node: str + status: str + image: str + created: str + uptime: str | None = None + is_swarm: bool = False + swarm_service: str | None = None diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..4610b8a --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,4 @@ +fastapi>=0.115.0 +uvicorn>=0.34.0 +httpx>=0.28.0 +aiofiles>=24.1.0 diff --git a/server/routes/__init__.py b/server/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c98ea6d --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,26 @@ +import pytest + + +@pytest.fixture +def sample_container(): + return { + "id": "abc123", + "name": "test-container", + "status": "running", + "image": "nginx:latest", + "created": "2026-03-05T00:00:00Z", + "uptime": "2 hours", + "is_swarm": False, + "swarm_service": None, + } + + +@pytest.fixture +def sample_group(): + return { + "id": "test-group", + "name": "Test Group", + "services": [ + {"node": "hf-pdocker-01", "container": "test-container"}, + ], + } diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..6d2e5a3 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest>=8.0.0 +pytest-asyncio>=0.24.0 +httpx>=0.28.0