Files
nas-tools/dashboard/backend/routers/cc_connect.py
T
Gan, Jimmy 1ca019fa48
Deploy Dashboard (Dev) / deploy-dev (push) Has been cancelled
Run Tests / Backend Tests (push) Has been cancelled
Run Tests / Frontend Tests (push) Has been cancelled
Run Tests / Test Summary (push) Has been cancelled
feat: add OPC (One Person Company) management system with Kanban board
Phase 1 MVP implementation:
- PostgreSQL database schema for tasks, agents, executions, time tracking
- Backend API: task CRUD, agent management, automatic time tracking
- Frontend: Kanban board with drag-and-drop (Parking Lot → In Progress → Done)
- Task modal for creating/editing tasks with tags, priority, assignee
- 3 core agents seeded: PM, CTO, COO
- Agent approval workflow: CxO level requires approval, others auto-execute
- Automatic time tracking: starts on in_progress, stops on done
- RBAC integration: OPC page accessible to admin/member roles

Features:
- Drag-and-drop tasks between columns
- Assign tasks to humans or AI agents
- Priority badges (low, medium, high, urgent)
- Tags and due dates
- Task history tracking
- Time entry tracking with automatic timer
- Agent execution records with approval workflow

Next steps:
- Initialize OPC database on NAS
- Implement agent executor service
- Add WebSocket for real-time updates
- Add email notifications
- Add PDF invoice generation
2026-03-31 14:31:31 +08:00

198 lines
5.8 KiB
Python

import time
import docker
import httpx
from fastapi import APIRouter
from config import CC_CONNECT_URL, DOCKER_HOST
router = APIRouter()
_client = None
def get_docker_client():
"""Get or create Docker client (lazy initialization)."""
global _client
if _client is None:
_client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
return _client
def _get_cc_connect_container():
client = get_docker_client()
matches = client.containers.list(all=True, filters={"name": "cc-connect"})
return matches[0] if matches else None
@router.post("/start")
def start_cc_connect():
try:
container = _get_cc_connect_container()
except Exception as exc:
return {
"ok": False,
"status": "down",
"container_status": None,
"error": f"docker check failed: {exc}",
}
if not container:
return {
"ok": False,
"status": "down",
"container_status": "not_found",
"error": "cc-connect container not found",
}
try:
container.start()
container.reload()
container_status = container.status
return {
"ok": container_status == "running",
"status": "up" if container_status == "running" else "down",
"container_status": container_status,
"error": None if container_status == "running" else "cc-connect failed to start",
}
except Exception as exc:
return {
"ok": False,
"status": "down",
"container_status": container.status,
"error": f"docker start failed: {exc}",
}
@router.post("/stop")
def stop_cc_connect():
try:
container = _get_cc_connect_container()
except Exception as exc:
return {
"ok": False,
"status": "down",
"container_status": None,
"error": f"docker check failed: {exc}",
}
if not container:
return {
"ok": False,
"status": "down",
"container_status": "not_found",
"error": "cc-connect container not found",
}
try:
container.stop()
container.reload()
container_status = container.status
return {
"ok": container_status != "running",
"status": "down",
"container_status": container_status,
"error": None if container_status != "running" else "cc-connect failed to stop",
}
except Exception as exc:
return {
"ok": False,
"status": "down",
"container_status": container.status,
"error": f"docker stop failed: {exc}",
}
@router.get("/health")
async def cc_connect_health():
url = (CC_CONNECT_URL or "").strip()
container = None
try:
container = _get_cc_connect_container()
except Exception as exc:
return {
"ok": False,
"status": "down",
"source": "docker",
"container_name": None,
"container_status": None,
"endpoint": None,
"http_status": None,
"latency_ms": None,
"error": f"docker check failed: {exc}",
}
container_name = container.name if container else None
container_status = container.status if container else "not_found"
container_up = container_status == "running"
if not url:
return {
"ok": container_up,
"status": "up" if container_up else "down",
"source": "docker",
"container_name": container_name,
"container_status": container_status,
"endpoint": None,
"http_status": None,
"latency_ms": None,
"error": None if container_up else "cc-connect container is not running",
}
timeout = httpx.Timeout(2.0)
started = time.perf_counter()
try:
async with httpx.AsyncClient(timeout=timeout) as http_client:
response = await http_client.get(url)
latency_ms = round((time.perf_counter() - started) * 1000, 1)
if response.is_success and container_up:
return {
"ok": True,
"status": "up",
"source": "docker+http",
"container_name": container_name,
"container_status": container_status,
"endpoint": url,
"http_status": response.status_code,
"latency_ms": latency_ms,
"error": None,
}
if response.is_success and not container_up:
return {
"ok": False,
"status": "down",
"source": "docker+http",
"container_name": container_name,
"container_status": container_status,
"endpoint": url,
"http_status": response.status_code,
"latency_ms": latency_ms,
"error": "HTTP probe succeeded but cc-connect container is not running",
}
return {
"ok": False,
"status": "down",
"source": "docker+http",
"container_name": container_name,
"container_status": container_status,
"endpoint": url,
"http_status": response.status_code,
"latency_ms": latency_ms,
"error": f"HTTP probe returned {response.status_code}",
}
except Exception as exc:
return {
"ok": False,
"status": "down",
"source": "docker+http",
"container_name": container_name,
"container_status": container_status,
"endpoint": url,
"http_status": None,
"latency_ms": None,
"error": str(exc),
}