Rename Castle -> Wild PC across the repo
Repo-side rename only (Phases 1-3 of the migration plan); the live box (~/.castle, systemd units, /data/castle, domains) is a separate cutover. - Slug `castle` -> `wildpc`: CLI command, module names (wildpc_core/cli/api), dist names, entry point `wildpc = wildpc_cli.main:main`. - Identifiers: CastleConfig/NATSClient/DirError/MDNS -> Wildpc*. - Env/constants: CASTLE_* -> WILDPC_*; ~/.castle -> ~/.wildpc, castle.yaml -> wildpc.yaml, /data/castle -> /data/wildpc. - Systemd UNIT_PREFIX castle- -> wildpc-; own programs castle-api/gateway/etc. - Display prose "Castle" -> "Wild PC" in docs, agent-guide files, README, frontend. - Package dirs and bootstrap yaml renamed via git mv; lockfiles regenerated; redundant nested uv.lock files dropped (workspace root lock is authoritative). Tests: core 273, cli 47, wildpc-api 120 all pass. Frontend type-checks + builds. Fixed a stale test fixture (secret_env_path kind arg) broken pre-rename.
This commit is contained in:
258
wildpc-api/src/wildpc_api/models.py
Normal file
258
wildpc-api/src/wildpc_api/models.py
Normal file
@@ -0,0 +1,258 @@
|
||||
"""Response models for the dashboard API."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class SystemdInfo(BaseModel):
|
||||
"""Systemd unit information for a managed component."""
|
||||
|
||||
unit_name: str
|
||||
unit_path: str
|
||||
timer: bool = False
|
||||
|
||||
|
||||
class DeploymentSummary(BaseModel):
|
||||
"""Summary of a single component."""
|
||||
|
||||
id: str
|
||||
category: str | None = None # "program", "service", or "job"
|
||||
description: str | None = None
|
||||
kind: str | None = None # derived: service|job|tool|static|reference
|
||||
stack: str | None = None
|
||||
manager: str | None = None # systemd|caddy|path|none
|
||||
launcher: str | None = None # python|command|container|compose|node (systemd only)
|
||||
port: int | None = None
|
||||
health_path: str | None = None
|
||||
subdomain: str | None = None # exposed at <subdomain>.<gateway.domain>, else None
|
||||
managed: bool = False
|
||||
systemd: SystemdInfo | None = None
|
||||
version: str | None = None
|
||||
source: str | None = None
|
||||
repo: str | None = None
|
||||
ref: str | None = None
|
||||
commands: dict[str, list[list[str]]] | None = None
|
||||
system_dependencies: list[str] = []
|
||||
schedule: str | None = None
|
||||
installed: bool | None = None
|
||||
active: bool | None = None # uniform lifecycle state (on PATH / running / served)
|
||||
enabled: bool = True # declared desired state; `apply` converges to it
|
||||
node: str | None = None
|
||||
|
||||
|
||||
class DeploymentDetail(DeploymentSummary):
|
||||
"""Full detail for a single component, including raw manifest."""
|
||||
|
||||
manifest: dict
|
||||
|
||||
|
||||
class ServiceSummary(BaseModel):
|
||||
"""Summary of a service — a systemd daemon OR a caddy-served static site.
|
||||
|
||||
Both are "services" (exposed, URL-reachable things); `kind`/`manager`
|
||||
distinguish them (service+systemd vs static+caddy).
|
||||
"""
|
||||
|
||||
id: str
|
||||
description: str | None = None
|
||||
stack: str | None = None
|
||||
kind: str | None = None # service | static
|
||||
manager: str | None = None # systemd | caddy
|
||||
launcher: str | None = None # python|command|container|compose|node (systemd only)
|
||||
run_target: str | None = None # what it runs: program name, argv, image, …
|
||||
port: int | None = None
|
||||
health_path: str | None = None
|
||||
subdomain: str | None = None # exposed at <subdomain>.<gateway.domain>, else None
|
||||
managed: bool = False
|
||||
systemd: SystemdInfo | None = None
|
||||
program: str | None = None # the program this deployment references, if any
|
||||
source: str | None = None
|
||||
enabled: bool = True # declared desired state; `apply` converges to it
|
||||
node: str | None = None
|
||||
|
||||
|
||||
class ServiceDetail(ServiceSummary):
|
||||
"""Full detail for a service, including raw manifest."""
|
||||
|
||||
manifest: dict
|
||||
|
||||
|
||||
class JobSummary(BaseModel):
|
||||
"""Summary of a job (scheduled task)."""
|
||||
|
||||
id: str
|
||||
description: str | None = None
|
||||
stack: str | None = None
|
||||
launcher: str | None = None # python|command|container|compose|node
|
||||
run_target: str | None = None # what it runs: program name, argv, …
|
||||
schedule: str | None = None
|
||||
managed: bool = False
|
||||
systemd: SystemdInfo | None = None
|
||||
program: str | None = None # the program this deployment references, if any
|
||||
source: str | None = None
|
||||
enabled: bool = True # declared desired state; `apply` converges to it
|
||||
node: str | None = None
|
||||
|
||||
|
||||
class JobDetail(JobSummary):
|
||||
"""Full detail for a job, including raw manifest."""
|
||||
|
||||
manifest: dict
|
||||
|
||||
|
||||
class DeploymentRef(BaseModel):
|
||||
"""A reference to one of a program's deployments (name + its derived kind)."""
|
||||
|
||||
name: str
|
||||
kind: str # service | job | tool | static | reference
|
||||
|
||||
|
||||
class ProgramSummary(BaseModel):
|
||||
"""Summary of a program (software catalog entry).
|
||||
|
||||
A program has NO kind of its own — it *has deployments*, each with a kind
|
||||
(a program can be a tool AND a job). `deployments` is that list.
|
||||
"""
|
||||
|
||||
id: str
|
||||
description: str | None = None
|
||||
stack: str | None = None
|
||||
version: str | None = None
|
||||
source: str | None = None
|
||||
repo: str | None = None
|
||||
ref: str | None = None
|
||||
commands: dict[str, list[list[str]]] | None = None
|
||||
system_dependencies: list[str] = []
|
||||
installed: bool | None = None
|
||||
active: bool | None = None # uniform lifecycle state (on PATH / running / served)
|
||||
actions: list[str] = []
|
||||
deployments: list[DeploymentRef] = [] # this program's deployments (name + kind)
|
||||
node: str | None = None
|
||||
|
||||
|
||||
class ProgramDetail(ProgramSummary):
|
||||
"""Full detail for a program, including raw manifest."""
|
||||
|
||||
manifest: dict
|
||||
|
||||
|
||||
class ToolStatusModel(BaseModel):
|
||||
"""One host toolchain a stack needs, and whether it's present where used."""
|
||||
|
||||
command: str
|
||||
purpose: str
|
||||
phase: str # "run" | "build" | "both"
|
||||
present: bool
|
||||
install_hint: str
|
||||
version: str | None = None
|
||||
|
||||
|
||||
class StackStatusModel(BaseModel):
|
||||
"""A stack's dependency health — its tools + who uses it. Powers the Stacks page."""
|
||||
|
||||
name: str
|
||||
tools: list[ToolStatusModel]
|
||||
programs: list[str]
|
||||
deployments: list[str]
|
||||
verbs: list[str]
|
||||
has_enabled_deployment: bool
|
||||
in_use: bool
|
||||
ok: bool
|
||||
|
||||
|
||||
class HealthStatus(BaseModel):
|
||||
"""Health status of a single component."""
|
||||
|
||||
id: str
|
||||
status: str # "up", "down", "unknown"
|
||||
latency_ms: int | None = None
|
||||
|
||||
|
||||
class StatusResponse(BaseModel):
|
||||
"""Aggregated health status for all exposed components."""
|
||||
|
||||
statuses: list[HealthStatus]
|
||||
|
||||
|
||||
class GatewayRoute(BaseModel):
|
||||
"""One gateway route: a public address mapped to a target.
|
||||
|
||||
kind is `static` (Caddy serves a built dir), `proxy` (reverse-proxy a local
|
||||
service), or `remote` (reverse-proxy another node). address is a path prefix
|
||||
(`/foo`) or a host (`foo.lan`); target is the serve dir or `host:port`.
|
||||
"""
|
||||
|
||||
address: str
|
||||
kind: str
|
||||
target: str
|
||||
name: str | None = None
|
||||
node: str
|
||||
# Public exposure via the tunnel: the public URL, or None if this route is
|
||||
# LAN-only. Set when the backing service has `public: true`.
|
||||
public_url: str | None = None
|
||||
|
||||
|
||||
class GatewayInfo(BaseModel):
|
||||
"""Gateway configuration summary."""
|
||||
|
||||
port: int
|
||||
hostname: str
|
||||
deployment_count: int
|
||||
service_count: int
|
||||
managed_count: int
|
||||
routes: list[GatewayRoute] = []
|
||||
# TLS mode: None/"off" → HTTP-only; "acme" → Let's Encrypt wildcard (publicly
|
||||
# trusted, no client CA setup) for host routes.
|
||||
tls: str | None = None
|
||||
# Routing/exposure config (editable from the dashboard).
|
||||
domain: str | None = None # acme zone → <service>.<domain>
|
||||
public_domain: str | None = None # tunnel zone → <service>.<public_domain>
|
||||
tunnel_id: str | None = None
|
||||
tunnel_connected: bool = False # cloudflared service active
|
||||
|
||||
|
||||
class GatewayConfigRequest(BaseModel):
|
||||
"""Editable gateway settings (saved to wildpc.yaml; deploy to apply)."""
|
||||
|
||||
tls: str | None = None
|
||||
domain: str | None = None
|
||||
public_domain: str | None = None
|
||||
tunnel_id: str | None = None
|
||||
|
||||
|
||||
class NodeSummary(BaseModel):
|
||||
"""Summary of a discovered node in the mesh."""
|
||||
|
||||
hostname: str
|
||||
gateway_port: int
|
||||
gateway_domain: str | None = None # acme domain → dashboard at wildpc.<domain>
|
||||
deployed_count: int
|
||||
service_count: int
|
||||
is_local: bool = False
|
||||
online: bool = True
|
||||
is_stale: bool = False
|
||||
last_seen: float | None = None
|
||||
|
||||
|
||||
class NodeDetail(NodeSummary):
|
||||
"""Full detail for a node, including its deployed components."""
|
||||
|
||||
deployed: list[DeploymentSummary] = []
|
||||
|
||||
|
||||
class MeshStatus(BaseModel):
|
||||
"""Current state of the mesh coordination layer."""
|
||||
|
||||
enabled: bool = False
|
||||
connected: bool = False
|
||||
nats_url: str | None = None
|
||||
mdns_enabled: bool = False
|
||||
peer_count: int = 0
|
||||
peers: list[str] = []
|
||||
|
||||
|
||||
class ServiceActionResponse(BaseModel):
|
||||
"""Response from a service management action."""
|
||||
|
||||
program: str
|
||||
action: str
|
||||
status: str
|
||||
Reference in New Issue
Block a user