feat: add ComponentGrid and ComponentTable components for displaying components in grid and table formats
feat: implement HealthBadge and RoleBadge components for displaying health status and roles feat: create LogViewer component for streaming logs of services feat: develop SecretsEditor for managing secrets with CRUD operations feat: introduce ToolCard component for displaying tool information style: add global CSS variables and styles for consistent theming feat: set up API client for handling requests to the backend feat: implement hooks for fetching components, statuses, and tools feat: create routes for dashboard, component details, and tools feat: add service management endpoints for starting, stopping, and restarting services feat: implement event bus for handling real-time updates via SSE feat: create health check and logs endpoints for monitoring services feat: add tests for health and tools endpoints to ensure functionality chore: update project configuration files and dependencies for better development experience
This commit is contained in:
0
dashboard/.gitignore → app/.gitignore
vendored
0
dashboard/.gitignore → app/.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "dashboard",
|
"name": "app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
31
castle-api/CLAUDE.md
Normal file
31
castle-api/CLAUDE.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working
|
||||||
|
with code in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
castle-api is a FastAPI service. Castle API.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync # Install dependencies
|
||||||
|
uv run castle-api # Run service (port 9020)
|
||||||
|
uv run pytest tests/ -v # Run tests
|
||||||
|
uv run ruff check . # Lint
|
||||||
|
uv run ruff format . # Format
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- `src/castle_api/config.py` — Settings via pydantic-settings, env prefix `CASTLE_API_`
|
||||||
|
- `src/castle_api/main.py` — FastAPI app, lifespan, health endpoint
|
||||||
|
- `tests/` — pytest with TestClient fixtures
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Environment variables with `CASTLE_API_` prefix:
|
||||||
|
- `CASTLE_API_DATA_DIR` — Data directory (default: ./data)
|
||||||
|
- `CASTLE_API_HOST` — Bind host (default: 0.0.0.0)
|
||||||
|
- `CASTLE_API_PORT` — Port (default: 9020)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "dashboard-api"
|
name = "castle-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Castle dashboard API"
|
description = "Castle API"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi>=0.115.0",
|
"fastapi>=0.115.0",
|
||||||
@@ -15,14 +15,14 @@ dependencies = [
|
|||||||
castle-cli = { path = "../cli", editable = true }
|
castle-cli = { path = "../cli", editable = true }
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
dashboard-api = "dashboard_api.main:run"
|
castle-api = "castle_api.main:run"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/dashboard_api"]
|
packages = ["src/castle_api"]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -33,4 +33,4 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.isort]
|
[tool.ruff.lint.isort]
|
||||||
known-first-party = ["dashboard_api"]
|
known-first-party = ["castle_api"]
|
||||||
3
castle-api/src/castle_api/__init__.py
Normal file
3
castle-api/src/castle_api/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""Castle API."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Configuration for dashboard-api."""
|
"""Configuration for castle-api."""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ class Settings(BaseSettings):
|
|||||||
port: int = 9020
|
port: int = 9020
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"env_prefix": "DASHBOARD_API_",
|
"env_prefix": "CASTLE_API_",
|
||||||
"env_file": ".env",
|
"env_file": ".env",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@ from pydantic import BaseModel
|
|||||||
from castle_cli.config import load_config, save_config
|
from castle_cli.config import load_config, save_config
|
||||||
from castle_cli.manifest import ComponentManifest
|
from castle_cli.manifest import ComponentManifest
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.stream import broadcast
|
from castle_api.stream import broadcast
|
||||||
|
|
||||||
router = APIRouter(prefix="/config", tags=["config"])
|
router = APIRouter(prefix="/config", tags=["config"])
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from dashboard_api.bus import bus
|
from castle_api.bus import bus
|
||||||
|
|
||||||
router = APIRouter(prefix="/events", tags=["events"])
|
router = APIRouter(prefix="/events", tags=["events"])
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import httpx
|
|||||||
|
|
||||||
from castle_cli.config import CastleConfig
|
from castle_cli.config import CastleConfig
|
||||||
|
|
||||||
from dashboard_api.models import HealthStatus
|
from castle_api.models import HealthStatus
|
||||||
|
|
||||||
|
|
||||||
async def check_all_health(config: CastleConfig) -> list[HealthStatus]:
|
async def check_all_health(config: CastleConfig) -> list[HealthStatus]:
|
||||||
@@ -10,7 +10,7 @@ from starlette.responses import StreamingResponse
|
|||||||
|
|
||||||
from castle_cli.config import load_config
|
from castle_cli.config import load_config
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
|
|
||||||
router = APIRouter(prefix="/logs", tags=["logs"])
|
router = APIRouter(prefix="/logs", tags=["logs"])
|
||||||
|
|
||||||
@@ -11,16 +11,16 @@ from fastapi import FastAPI
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from starlette.responses import StreamingResponse
|
from starlette.responses import StreamingResponse
|
||||||
|
|
||||||
from dashboard_api.bus import bus
|
from castle_api.bus import bus
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.config_editor import router as config_router
|
from castle_api.config_editor import router as config_router
|
||||||
from dashboard_api.events import router as events_router
|
from castle_api.events import router as events_router
|
||||||
from dashboard_api.logs import router as logs_router
|
from castle_api.logs import router as logs_router
|
||||||
from dashboard_api.routes import router as dashboard_router
|
from castle_api.routes import router as dashboard_router
|
||||||
from dashboard_api.secrets import router as secrets_router
|
from castle_api.secrets import router as secrets_router
|
||||||
from dashboard_api.services import router as services_router
|
from castle_api.services import router as services_router
|
||||||
from dashboard_api.stream import close_all_subscribers, health_poll_loop, subscribe, unsubscribe
|
from castle_api.stream import close_all_subscribers, health_poll_loop, subscribe, unsubscribe
|
||||||
from dashboard_api.tools import router as tools_router
|
from castle_api.tools import router as tools_router
|
||||||
|
|
||||||
# Set by _watch_shutdown when uvicorn begins its shutdown sequence.
|
# Set by _watch_shutdown when uvicorn begins its shutdown sequence.
|
||||||
_shutting_down = False
|
_shutting_down = False
|
||||||
@@ -53,8 +53,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Castle API",
|
title="castle-api",
|
||||||
description="Castle dashboard API, event bus, and service management",
|
description="Castle API, event bus, and service management",
|
||||||
version="0.1.0",
|
version="0.1.0",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
@@ -109,7 +109,7 @@ async def sse_stream() -> StreamingResponse:
|
|||||||
def run() -> None:
|
def run() -> None:
|
||||||
"""Run the application with uvicorn."""
|
"""Run the application with uvicorn."""
|
||||||
config = uvicorn.Config(
|
config = uvicorn.Config(
|
||||||
"dashboard_api.main:app",
|
"castle_api.main:app",
|
||||||
host=settings.host,
|
host=settings.host,
|
||||||
port=settings.port,
|
port=settings.port,
|
||||||
reload=False,
|
reload=False,
|
||||||
@@ -9,9 +9,9 @@ from fastapi import APIRouter, HTTPException, status
|
|||||||
|
|
||||||
from castle_cli.config import load_config
|
from castle_cli.config import load_config
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.health import check_all_health
|
from castle_api.health import check_all_health
|
||||||
from dashboard_api.models import (
|
from castle_api.models import (
|
||||||
ComponentDetail,
|
ComponentDetail,
|
||||||
ComponentSummary,
|
ComponentSummary,
|
||||||
GatewayInfo,
|
GatewayInfo,
|
||||||
@@ -10,15 +10,15 @@ from starlette.responses import JSONResponse
|
|||||||
|
|
||||||
from castle_cli.config import load_config
|
from castle_cli.config import load_config
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.health import check_all_health
|
from castle_api.health import check_all_health
|
||||||
from dashboard_api.models import HealthStatus
|
from castle_api.models import HealthStatus
|
||||||
from dashboard_api.stream import broadcast
|
from castle_api.stream import broadcast
|
||||||
|
|
||||||
router = APIRouter(prefix="/services", tags=["services"])
|
router = APIRouter(prefix="/services", tags=["services"])
|
||||||
|
|
||||||
UNIT_PREFIX = "castle-"
|
UNIT_PREFIX = "castle-"
|
||||||
SELF_NAME = "dashboard-api"
|
SELF_NAME = "castle-api"
|
||||||
|
|
||||||
|
|
||||||
async def _systemctl(action: str, unit: str) -> tuple[bool, str]:
|
async def _systemctl(action: str, unit: str) -> tuple[bool, str]:
|
||||||
@@ -9,8 +9,8 @@ import time
|
|||||||
|
|
||||||
from castle_cli.config import load_config
|
from castle_cli.config import load_config
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.health import check_all_health
|
from castle_api.health import check_all_health
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -10,8 +10,8 @@ from fastapi import APIRouter, HTTPException, status
|
|||||||
from castle_cli.config import load_config
|
from castle_cli.config import load_config
|
||||||
from castle_cli.manifest import ComponentManifest
|
from castle_cli.manifest import ComponentManifest
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.models import ToolCategory, ToolDetail, ToolSummary
|
from castle_api.models import ToolCategory, ToolDetail, ToolSummary
|
||||||
|
|
||||||
router = APIRouter(tags=["tools"])
|
router = APIRouter(tags=["tools"])
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Test fixtures for dashboard-api."""
|
"""Test fixtures for castle-api."""
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -7,8 +7,8 @@ import pytest
|
|||||||
import yaml
|
import yaml
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from dashboard_api.config import settings
|
from castle_api.config import settings
|
||||||
from dashboard_api.main import app
|
from castle_api.main import app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for dashboard-api health endpoint."""
|
"""Tests for castle-api health endpoint."""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
74
dashboard-api/uv.lock → castle-api/uv.lock
generated
74
dashboard-api/uv.lock → castle-api/uv.lock
generated
@@ -32,6 +32,43 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castle-api"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "castle-cli" },
|
||||||
|
{ name = "fastapi" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "uvicorn" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-asyncio" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "castle-cli", editable = "../cli" },
|
||||||
|
{ name = "fastapi", specifier = ">=0.115.0" },
|
||||||
|
{ name = "httpx", specifier = ">=0.27.0" },
|
||||||
|
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
||||||
|
{ name = "uvicorn", specifier = ">=0.34.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "httpx", specifier = ">=0.27.0" },
|
||||||
|
{ name = "pytest", specifier = ">=7.0.0" },
|
||||||
|
{ name = "pytest-asyncio", specifier = ">=0.23.0" },
|
||||||
|
{ name = "pyyaml", specifier = ">=6.0.0" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "castle-cli"
|
name = "castle-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -86,43 +123,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dashboard-api"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = { editable = "." }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "castle-cli" },
|
|
||||||
{ name = "fastapi" },
|
|
||||||
{ name = "httpx" },
|
|
||||||
{ name = "pydantic-settings" },
|
|
||||||
{ name = "uvicorn" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "httpx" },
|
|
||||||
{ name = "pytest" },
|
|
||||||
{ name = "pytest-asyncio" },
|
|
||||||
{ name = "pyyaml" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
requires-dist = [
|
|
||||||
{ name = "castle-cli", editable = "../cli" },
|
|
||||||
{ name = "fastapi", specifier = ">=0.115.0" },
|
|
||||||
{ name = "httpx", specifier = ">=0.27.0" },
|
|
||||||
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
|
||||||
{ name = "uvicorn", specifier = ">=0.34.0" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
|
||||||
dev = [
|
|
||||||
{ name = "httpx", specifier = ">=0.27.0" },
|
|
||||||
{ name = "pytest", specifier = ">=7.0.0" },
|
|
||||||
{ name = "pytest-asyncio", specifier = ">=0.23.0" },
|
|
||||||
{ name = "pyyaml", specifier = ">=6.0.0" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.129.0"
|
version = "0.129.0"
|
||||||
32
castle.yaml
32
castle.yaml
@@ -1,8 +1,7 @@
|
|||||||
gateway:
|
gateway:
|
||||||
port: 9000
|
port: 9000
|
||||||
components:
|
components:
|
||||||
# ── Infrastructure ──────────────────────────────────────
|
castle-gateway:
|
||||||
gateway:
|
|
||||||
description: Caddy reverse proxy gateway
|
description: Caddy reverse proxy gateway
|
||||||
run:
|
run:
|
||||||
runner: command
|
runner: command
|
||||||
@@ -14,16 +13,15 @@ components:
|
|||||||
- /home/payne/.castle/generated/Caddyfile
|
- /home/payne/.castle/generated/Caddyfile
|
||||||
- --adapter
|
- --adapter
|
||||||
- caddyfile
|
- caddyfile
|
||||||
|
manage:
|
||||||
|
systemd:
|
||||||
|
exec_reload: caddy reload --config /home/payne/.castle/generated/Caddyfile
|
||||||
|
--adapter caddyfile
|
||||||
expose:
|
expose:
|
||||||
http:
|
http:
|
||||||
internal:
|
internal:
|
||||||
port: 9000
|
port: 9000
|
||||||
health_path: /
|
health_path: /
|
||||||
manage:
|
|
||||||
systemd:
|
|
||||||
exec_reload: caddy reload --config /home/payne/.castle/generated/Caddyfile --adapter caddyfile
|
|
||||||
|
|
||||||
# ── Services ──────────────────────────────────────────────
|
|
||||||
central-context:
|
central-context:
|
||||||
description: Content storage API
|
description: Content storage API
|
||||||
run:
|
run:
|
||||||
@@ -44,7 +42,9 @@ components:
|
|||||||
caddy:
|
caddy:
|
||||||
path_prefix: /central-context
|
path_prefix: /central-context
|
||||||
notification-bridge:
|
notification-bridge:
|
||||||
description: Desktop notification forwarder
|
description: Desktop notification forwarder. This taps into your notifications
|
||||||
|
system on the desktop and will forward all notifications the the central context
|
||||||
|
server.
|
||||||
run:
|
run:
|
||||||
runner: python_uv_tool
|
runner: python_uv_tool
|
||||||
cwd: notification-bridge
|
cwd: notification-bridge
|
||||||
@@ -63,14 +63,14 @@ components:
|
|||||||
proxy:
|
proxy:
|
||||||
caddy:
|
caddy:
|
||||||
path_prefix: /notifications
|
path_prefix: /notifications
|
||||||
dashboard-api:
|
castle-api:
|
||||||
description: Castle dashboard API
|
description: Castle API
|
||||||
run:
|
run:
|
||||||
runner: python_uv_tool
|
runner: python_uv_tool
|
||||||
cwd: dashboard-api
|
cwd: castle-api
|
||||||
env:
|
env:
|
||||||
DASHBOARD_API_CASTLE_ROOT: /data/repos/castle
|
CASTLE_API_CASTLE_ROOT: /data/repos/castle
|
||||||
tool: dashboard-api
|
tool: castle-api
|
||||||
manage:
|
manage:
|
||||||
systemd: {}
|
systemd: {}
|
||||||
expose:
|
expose:
|
||||||
@@ -137,10 +137,10 @@ components:
|
|||||||
manage:
|
manage:
|
||||||
systemd: {}
|
systemd: {}
|
||||||
tool: {}
|
tool: {}
|
||||||
dashboard:
|
castle-app:
|
||||||
description: Castle web dashboard
|
description: Castle web app
|
||||||
build:
|
build:
|
||||||
working_dir: dashboard
|
working_dir: app
|
||||||
commands:
|
commands:
|
||||||
- - pnpm
|
- - pnpm
|
||||||
- build
|
- build
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import subprocess
|
|||||||
from castle_cli.config import GENERATED_DIR, CastleConfig, ensure_dirs, load_config
|
from castle_cli.config import GENERATED_DIR, CastleConfig, ensure_dirs, load_config
|
||||||
|
|
||||||
|
|
||||||
GATEWAY_COMPONENT = "gateway"
|
GATEWAY_COMPONENT = "castle-gateway"
|
||||||
GATEWAY_UNIT = "castle-gateway.service"
|
GATEWAY_UNIT = "castle-gateway.service"
|
||||||
|
|
||||||
|
|
||||||
def _find_dashboard_dist(config: CastleConfig) -> str | None:
|
def _find_app_dist(config: CastleConfig) -> str | None:
|
||||||
"""Find the dashboard dist/ directory if it exists."""
|
"""Find the app dist/ directory if it exists."""
|
||||||
dist = config.root / "dashboard" / "dist"
|
dist = config.root / "app" / "dist"
|
||||||
if dist.exists() and (dist / "index.html").exists():
|
if dist.exists() and (dist / "index.html").exists():
|
||||||
return str(dist)
|
return str(dist)
|
||||||
return None
|
return None
|
||||||
@@ -42,17 +42,17 @@ def _generate_caddyfile(config: CastleConfig) -> str:
|
|||||||
lines.append(" }")
|
lines.append(" }")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Dashboard SPA at root (must come after more-specific handle_path rules)
|
# App SPA at root (must come after more-specific handle_path rules)
|
||||||
dashboard_dist = _find_dashboard_dist(config)
|
app_dist = _find_app_dist(config)
|
||||||
if dashboard_dist:
|
if app_dist:
|
||||||
lines.append(" handle {")
|
lines.append(" handle {")
|
||||||
lines.append(f" root * {dashboard_dist}")
|
lines.append(f" root * {app_dist}")
|
||||||
lines.append(" try_files {path} /index.html")
|
lines.append(" try_files {path} /index.html")
|
||||||
lines.append(" file_server")
|
lines.append(" file_server")
|
||||||
lines.append(" }")
|
lines.append(" }")
|
||||||
else:
|
else:
|
||||||
# Fallback: serve from generated directory
|
# Fallback: serve from generated directory
|
||||||
fallback = GENERATED_DIR / "dashboard"
|
fallback = GENERATED_DIR / "app"
|
||||||
lines.append(" handle / {")
|
lines.append(" handle / {")
|
||||||
lines.append(f" root * {fallback}")
|
lines.append(f" root * {fallback}")
|
||||||
lines.append(" file_server")
|
lines.append(" file_server")
|
||||||
@@ -70,11 +70,11 @@ def _write_generated_files(config: CastleConfig) -> None:
|
|||||||
caddyfile_path.write_text(_generate_caddyfile(config))
|
caddyfile_path.write_text(_generate_caddyfile(config))
|
||||||
print(f" Generated {caddyfile_path}")
|
print(f" Generated {caddyfile_path}")
|
||||||
|
|
||||||
dashboard_dist = _find_dashboard_dist(config)
|
app_dist = _find_app_dist(config)
|
||||||
if dashboard_dist:
|
if app_dist:
|
||||||
print(f" Dashboard: {dashboard_dist}")
|
print(f" App: {app_dist}")
|
||||||
else:
|
else:
|
||||||
print(" Dashboard: dist/ not found, using fallback")
|
print(" App: dist/ not found, using fallback")
|
||||||
|
|
||||||
|
|
||||||
def run_gateway(args: argparse.Namespace) -> int:
|
def run_gateway(args: argparse.Namespace) -> int:
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working
|
|
||||||
with code in this repository.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
dashboard-api is a FastAPI service. Castle dashboard API.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv sync # Install dependencies
|
|
||||||
uv run dashboard-api # Run service (port 9020)
|
|
||||||
uv run pytest tests/ -v # Run tests
|
|
||||||
uv run ruff check . # Lint
|
|
||||||
uv run ruff format . # Format
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
- `src/dashboard_api/config.py` — Settings via pydantic-settings, env prefix `DASHBOARD_API_`
|
|
||||||
- `src/dashboard_api/main.py` — FastAPI app, lifespan, health endpoint
|
|
||||||
- `tests/` — pytest with TestClient fixtures
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Environment variables with `DASHBOARD_API_` prefix:
|
|
||||||
- `DASHBOARD_API_DATA_DIR` — Data directory (default: ./data)
|
|
||||||
- `DASHBOARD_API_HOST` — Bind host (default: 0.0.0.0)
|
|
||||||
- `DASHBOARD_API_PORT` — Port (default: 9020)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""Castle dashboard API."""
|
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
|
||||||
@@ -1,23 +1,20 @@
|
|||||||
# Web Frontends in Castle
|
# Web Frontends in Castle
|
||||||
|
|
||||||
How to build, serve, and manage web frontends as castle components. Based on
|
How to build, serve, and manage web frontends as castle components.
|
||||||
the stack used in [wild-cloud/web](https://github.com/civilsociety-dev/wild-cloud).
|
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
| Layer | Choice |
|
- Build: Vite 6
|
||||||
|-------|--------|
|
- Language: TypeScript 5.8 (strict)
|
||||||
| **Build** | Vite 6 |
|
- Framework: React 19
|
||||||
| **Language** | TypeScript 5.8 (strict) |
|
- Routing: React Router 7
|
||||||
| **Framework** | React 19 |
|
- Styling: Tailwind CSS 4 (`@tailwindcss/vite` plugin)
|
||||||
| **Routing** | React Router 7 |
|
- Components: shadcn/ui (new-york style) + Radix UI primitives
|
||||||
| **Styling** | Tailwind CSS 4 (`@tailwindcss/vite` plugin) |
|
- Icons: Lucide React
|
||||||
| **Components** | shadcn/ui (new-york style) + Radix UI primitives |
|
- Server state: TanStack React Query 5
|
||||||
| **Icons** | Lucide React |
|
- Forms: React Hook Form + Zod validation
|
||||||
| **Server state** | TanStack React Query 5 |
|
- Testing: Vitest + Testing Library
|
||||||
| **Forms** | React Hook Form + Zod validation |
|
- Package manager: pnpm
|
||||||
| **Testing** | Vitest + Testing Library |
|
|
||||||
| **Package manager** | pnpm |
|
|
||||||
|
|
||||||
## Scaffolding a new frontend
|
## Scaffolding a new frontend
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user