Fix auto certificate not showing in GUI when editing proxy host

When editing a proxy host with certificate_id set to null (auto), the
dropdown would not show "Managed by Caddy (Auto)" as selected and it
would revert to another certificate. There were two issues:

1. Form submission: Empty string from dropdown was treated as falsy,
   returning undefined instead of null (means "don't change")

2. Database update: The ?? operator treated null as falsy and fell back
   to existing value instead of saving null

Changes:
- app/(dashboard)/proxy-hosts/actions.ts: Check formData.has() and
  explicitly convert empty string to null for auto mode
- src/lib/models/proxy-hosts.ts: Use !== undefined instead of ?? to
  allow null values to be saved
- app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx: Add Certificate
  column to table showing "Managed by Caddy (Auto)" for auto certs

Applied same fixes to access_list_id for consistency.

Now when users select "Managed by Caddy (Auto)", it correctly sets
certificate_id to null, displays properly on subsequent edits, and
shows in the table view.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
fuomag9
2025-11-07 11:14:46 +01:00
parent f079860007
commit 102bd04d4c
3 changed files with 90 additions and 73 deletions
@@ -113,6 +113,7 @@ export default function ProxyHostsClient({ hosts, certificates, accessLists }: P
<TableCell sx={{ fontWeight: 600, color: "rgba(255, 255, 255, 0.7)" }}>Name</TableCell>
<TableCell sx={{ fontWeight: 600, color: "rgba(255, 255, 255, 0.7)" }}>Domains</TableCell>
<TableCell sx={{ fontWeight: 600, color: "rgba(255, 255, 255, 0.7)" }}>Upstreams</TableCell>
<TableCell sx={{ fontWeight: 600, color: "rgba(255, 255, 255, 0.7)" }}>Certificate</TableCell>
<TableCell sx={{ fontWeight: 600, color: "rgba(255, 255, 255, 0.7)" }}>Status</TableCell>
<TableCell align="right" sx={{ fontWeight: 600, color: "rgba(255, 255, 255, 0.7)" }}>Actions</TableCell>
</TableRow>
@@ -120,79 +121,91 @@ export default function ProxyHostsClient({ hosts, certificates, accessLists }: P
<TableBody>
{hosts.length === 0 ? (
<TableRow>
<TableCell colSpan={5} align="center" sx={{ py: 6, color: "text.secondary" }}>
<TableCell colSpan={6} align="center" sx={{ py: 6, color: "text.secondary" }}>
No proxy hosts configured. Click "Create Host" to add one.
</TableCell>
</TableRow>
) : (
hosts.map((host) => (
<TableRow
key={host.id}
sx={{
"&:hover": { bgcolor: "rgba(255, 255, 255, 0.02)" }
}}
>
<TableCell>
<Typography variant="body2" sx={{ fontWeight: 500, color: "rgba(255, 255, 255, 0.9)" }}>
{host.name}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" sx={{ color: "rgba(255, 255, 255, 0.7)", fontSize: "0.8125rem" }}>
{host.domains.slice(0, 2).join(", ")}
{host.domains.length > 2 && ` +${host.domains.length - 2} more`}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" sx={{ color: "rgba(255, 255, 255, 0.7)", fontSize: "0.8125rem" }}>
{host.upstreams.slice(0, 2).join(", ")}
{host.upstreams.length > 2 && ` +${host.upstreams.length - 2} more`}
</Typography>
</TableCell>
<TableCell>
<Chip
label={host.enabled ? "Enabled" : "Disabled"}
size="small"
sx={{
bgcolor: host.enabled ? "rgba(34, 197, 94, 0.15)" : "rgba(148, 163, 184, 0.15)",
color: host.enabled ? "rgba(34, 197, 94, 1)" : "rgba(148, 163, 184, 0.8)",
border: "1px solid",
borderColor: host.enabled ? "rgba(34, 197, 94, 0.3)" : "rgba(148, 163, 184, 0.3)",
fontWeight: 500,
fontSize: "0.75rem"
}}
/>
</TableCell>
<TableCell align="right">
<Stack direction="row" spacing={0.5} justifyContent="flex-end">
<Tooltip title="Edit">
<IconButton
size="small"
onClick={() => setEditHost(host)}
sx={{
color: "rgba(99, 102, 241, 0.8)",
"&:hover": { bgcolor: "rgba(99, 102, 241, 0.1)" }
}}
>
<EditIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton
size="small"
onClick={() => setDeleteHost(host)}
sx={{
color: "rgba(239, 68, 68, 0.8)",
"&:hover": { bgcolor: "rgba(239, 68, 68, 0.1)" }
}}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</Stack>
</TableCell>
</TableRow>
))
hosts.map((host) => {
const certificate = host.certificate_id
? certificates.find(c => c.id === host.certificate_id)
: null;
const certName = certificate?.name ?? "Managed by Caddy (Auto)";
return (
<TableRow
key={host.id}
sx={{
"&:hover": { bgcolor: "rgba(255, 255, 255, 0.02)" }
}}
>
<TableCell>
<Typography variant="body2" sx={{ fontWeight: 500, color: "rgba(255, 255, 255, 0.9)" }}>
{host.name}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" sx={{ color: "rgba(255, 255, 255, 0.7)", fontSize: "0.8125rem" }}>
{host.domains.slice(0, 2).join(", ")}
{host.domains.length > 2 && ` +${host.domains.length - 2} more`}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" sx={{ color: "rgba(255, 255, 255, 0.7)", fontSize: "0.8125rem" }}>
{host.upstreams.slice(0, 2).join(", ")}
{host.upstreams.length > 2 && ` +${host.upstreams.length - 2} more`}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" sx={{ color: "rgba(255, 255, 255, 0.7)", fontSize: "0.8125rem" }}>
{certName}
</Typography>
</TableCell>
<TableCell>
<Chip
label={host.enabled ? "Enabled" : "Disabled"}
size="small"
sx={{
bgcolor: host.enabled ? "rgba(34, 197, 94, 0.15)" : "rgba(148, 163, 184, 0.15)",
color: host.enabled ? "rgba(34, 197, 94, 1)" : "rgba(148, 163, 184, 0.8)",
border: "1px solid",
borderColor: host.enabled ? "rgba(34, 197, 94, 0.3)" : "rgba(148, 163, 184, 0.3)",
fontWeight: 500,
fontSize: "0.75rem"
}}
/>
</TableCell>
<TableCell align="right">
<Stack direction="row" spacing={0.5} justifyContent="flex-end">
<Tooltip title="Edit">
<IconButton
size="small"
onClick={() => setEditHost(host)}
sx={{
color: "rgba(99, 102, 241, 0.8)",
"&:hover": { bgcolor: "rgba(99, 102, 241, 0.1)" }
}}
>
<EditIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton
size="small"
onClick={() => setDeleteHost(host)}
sx={{
color: "rgba(239, 68, 68, 0.8)",
"&:hover": { bgcolor: "rgba(239, 68, 68, 0.1)" }
}}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</Stack>
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>