diff --git a/app/src/components/ServiceCard.tsx b/app/src/components/ServiceCard.tsx index 8a45214..7754cd3 100644 --- a/app/src/components/ServiceCard.tsx +++ b/app/src/components/ServiceCard.tsx @@ -2,7 +2,7 @@ import { ExternalLink, Play, RefreshCw, Server, Square, Terminal } from "lucide- import { Link } from "react-router-dom" import type { ServiceSummary, HealthStatus } from "@/types" import { useServiceAction } from "@/services/api/hooks" -import { runnerLabel } from "@/lib/labels" +import { runnerLabel, subdomainUrl } from "@/lib/labels" import { HealthBadge } from "./HealthBadge" import { StackBadge } from "./StackBadge" @@ -58,13 +58,13 @@ export function ServiceCard({ service, health }: ServiceCardProps) { {runnerLabel(service.runner)} )} - {service.proxy_path && ( + {service.subdomain && ( - {service.proxy_path} + {service.subdomain} )} {service.port && ( diff --git a/app/src/components/detail/CreateDeploymentForm.tsx b/app/src/components/detail/CreateDeploymentForm.tsx index 5a4f192..c72a4af 100644 --- a/app/src/components/detail/CreateDeploymentForm.tsx +++ b/app/src/components/detail/CreateDeploymentForm.tsx @@ -39,8 +39,7 @@ export function CreateDeploymentForm({ const [runTarget, setRunTarget] = useState(prefill?.runTarget ?? prefill?.name ?? "") const [port, setPort] = useState("") const [health, setHealth] = useState("/health") - const [path, setPath] = useState("") - const [host, setHost] = useState("") + const [expose, setExpose] = useState(true) const [schedule, setSchedule] = useState("0 2 * * *") const [busy, setBusy] = useState(null) const [error, setError] = useState("") @@ -75,14 +74,7 @@ export function CreateDeploymentForm({ }, } } - if (path || host) { - base.proxy = { - caddy: { - ...(path ? { path_prefix: path.startsWith("/") ? path : `/${path}` } : {}), - ...(host ? { host } : {}), - }, - } - } + if (port && expose) base.proxy = { caddy: {} } return base } @@ -160,8 +152,14 @@ export function CreateDeploymentForm({ <> - - + + + ) : ( diff --git a/app/src/components/detail/ServiceFields.tsx b/app/src/components/detail/ServiceFields.tsx index a67de74..c60318a 100644 --- a/app/src/components/detail/ServiceFields.tsx +++ b/app/src/components/detail/ServiceFields.tsx @@ -18,7 +18,7 @@ export function ServiceFields({ service, onSave, onDelete }: Props) { const run = obj(m.run) const internal = obj(obj(obj(m.expose).http).internal) const httpExpose = obj(obj(m.expose).http) - const caddy = obj(obj(m.proxy).caddy) + const caddyRaw = obj(m.proxy).caddy const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) @@ -29,8 +29,8 @@ export function ServiceFields({ service, onSave, onDelete }: Props) { ) const [port, setPort] = useState(internal.port != null ? String(internal.port) : "") const [health, setHealth] = useState((httpExpose.health_path as string) ?? "") - const [proxyPath, setProxyPath] = useState((caddy.path_prefix as string) ?? "") - const [proxyHost, setProxyHost] = useState((caddy.host as string) ?? "") + // Exposed at . when proxy.caddy is present + enabled. + const [expose, setExpose] = useState(caddyRaw !== undefined && obj(caddyRaw).enable !== false) const { element: envEditor, merged } = useEnvSecrets(obj(obj(m.defaults).env) as Record) @@ -62,16 +62,8 @@ export function ServiceFields({ service, onSave, onDelete }: Props) { delete config.expose } - if (proxyPath || proxyHost) { - config.proxy = { - caddy: { - ...(proxyPath ? { path_prefix: proxyPath } : {}), - ...(proxyHost ? { host: proxyHost } : {}), - }, - } - } else { - delete config.proxy - } + if (expose) config.proxy = { caddy: {} } + else delete config.proxy const env = merged() if (Object.keys(env).length > 0) config.defaults = { ...obj(config.defaults), env } @@ -117,23 +109,21 @@ export function ServiceFields({ service, onSave, onDelete }: Props) { placeholder="/health" hint="HTTP path castle polls to report up/down." /> - - + + + {envEditor} .. The domain is + * derived from the dashboard's own host (it is served at castle-app.), so + * this returns null when the dashboard is on a bare host (off mode, no subdomains). + */ +export function subdomainUrl(subdomain: string): string | null { + if (typeof window === "undefined") return null + const { protocol, hostname } = window.location + const labels = hostname.split(".") + if (labels.length <= 2) return null + return `${protocol}//${subdomain}.${labels.slice(1).join(".")}` +} diff --git a/app/src/pages/ServiceDetail.tsx b/app/src/pages/ServiceDetail.tsx index e4bb1c1..9a467bc 100644 --- a/app/src/pages/ServiceDetail.tsx +++ b/app/src/pages/ServiceDetail.tsx @@ -1,7 +1,7 @@ import { useParams, Link } from "react-router-dom" import { Server, ExternalLink, Terminal } from "lucide-react" import { useService, useStatus, useEventStream, useCaddyfile } from "@/services/api/hooks" -import { runnerLabel } from "@/lib/labels" +import { runnerLabel, subdomainUrl } from "@/lib/labels" import { HealthBadge } from "@/components/HealthBadge" import { LogViewer } from "@/components/LogViewer" import { DetailHeader } from "@/components/detail/DetailHeader" @@ -68,23 +68,17 @@ export function ServiceDetailPage() { {deployment.health_path} )} - {deployment.proxy_path && ( + {deployment.subdomain && ( <> - Proxy + Subdomain - {deployment.proxy_path} + {deployment.subdomain} )} - {deployment.proxy_host && ( - <> - Host - {deployment.proxy_host} - - )} {deployment.runner && ( <> Runs diff --git a/app/src/types/index.ts b/app/src/types/index.ts index 19bebf1..4938b1b 100644 --- a/app/src/types/index.ts +++ b/app/src/types/index.ts @@ -12,8 +12,7 @@ export interface ServiceSummary { run_target: string | null port: number | null health_path: string | null - proxy_path: string | null - proxy_host: string | null + subdomain: string | null // exposed at ., else null managed: boolean systemd: SystemdInfo | null program: string | null @@ -80,7 +79,7 @@ export interface DeploymentSummary { runner: string | null port: number | null health_path: string | null - proxy_path: string | null + subdomain: string | null // exposed at ., else null managed: boolean systemd: SystemdInfo | null version: string | null