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