feat(proxy-host): enhance certificate handling and update form integration

This commit is contained in:
GitHub Actions
2026-04-14 20:35:11 +00:00
parent 0e0d42c9fd
commit 8090c12556
3 changed files with 66 additions and 16 deletions

View File

@@ -248,6 +248,38 @@ func (h *ProxyHostHandler) resolveSecurityHeaderProfileReference(value any) (*ui
return &id, nil
}
func (h *ProxyHostHandler) resolveCertificateReference(value any) (*uint, error) {
if value == nil {
return nil, nil
}
parsedID, _, parseErr := parseNullableUintField(value, "certificate_id")
if parseErr == nil {
return parsedID, nil
}
uuidValue, isString := value.(string)
if !isString {
return nil, parseErr
}
trimmed := strings.TrimSpace(uuidValue)
if trimmed == "" {
return nil, nil
}
var cert models.SSLCertificate
if err := h.db.Select("id").Where("uuid = ?", trimmed).First(&cert).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("certificate not found")
}
return nil, fmt.Errorf("failed to resolve certificate")
}
id := cert.ID
return &id, nil
}
func parseForwardPortField(value any) (int, error) {
switch v := value.(type) {
case float64:
@@ -342,6 +374,15 @@ func (h *ProxyHostHandler) Create(c *gin.Context) {
payload["security_header_profile_id"] = resolvedSecurityHeaderID
}
if rawCertRef, ok := payload["certificate_id"]; ok {
resolvedCertID, resolveErr := h.resolveCertificateReference(rawCertRef)
if resolveErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": resolveErr.Error()})
return
}
payload["certificate_id"] = resolvedCertID
}
payloadBytes, marshalErr := json.Marshal(payload)
if marshalErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"})
@@ -523,12 +564,12 @@ func (h *ProxyHostHandler) Update(c *gin.Context) {
// Nullable foreign keys
if v, ok := payload["certificate_id"]; ok {
parsedID, _, parseErr := parseNullableUintField(v, "certificate_id")
if parseErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": parseErr.Error()})
resolvedCertID, resolveErr := h.resolveCertificateReference(v)
if resolveErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": resolveErr.Error()})
return
}
host.CertificateID = parsedID
host.CertificateID = resolvedCertID
}
if v, ok := payload["access_list_id"]; ok {
resolvedAccessListID, resolveErr := h.resolveAccessListReference(v)

View File

@@ -9,7 +9,7 @@ export interface Location {
}
export interface Certificate {
id: number;
id?: number;
uuid: string;
name: string;
provider: string;
@@ -40,7 +40,7 @@ export interface ProxyHost {
advanced_config?: string;
advanced_config_backup?: string;
enabled: boolean;
certificate_id?: number | null;
certificate_id?: number | string | null;
certificate?: Certificate | null;
access_list_id?: number | string | null;
access_list?: {

View File

@@ -123,7 +123,7 @@ function buildInitialFormData(host?: ProxyHost): Partial<ProxyHost> & {
application: (host?.application || 'none') as ApplicationPreset,
advanced_config: host?.advanced_config || '',
enabled: host?.enabled ?? true,
certificate_id: host?.certificate_id,
certificate_id: host?.certificate?.uuid ?? host?.certificate_id,
access_list_id: host?.access_list?.uuid ?? host?.access_list_id,
security_header_profile_id: host?.security_header_profile?.uuid ?? host?.security_header_profile_id,
dns_provider_id: host?.dns_provider_id || null,
@@ -249,9 +249,10 @@ function getEntityToken(entity: { id?: number; uuid?: string }): string | null {
}
export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFormProps) {
type ProxyHostFormState = Omit<Partial<ProxyHost>, 'access_list_id' | 'security_header_profile_id'> & {
type ProxyHostFormState = Omit<Partial<ProxyHost>, 'access_list_id' | 'security_header_profile_id' | 'certificate_id'> & {
access_list_id?: number | string | null
security_header_profile_id?: number | string | null
certificate_id?: number | string | null
addUptime?: boolean
uptimeInterval?: number
uptimeMaxRetries?: number
@@ -562,6 +563,7 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
...payloadWithoutUptime,
access_list_id: normalizeAccessListReference(payloadWithoutUptime.access_list_id),
security_header_profile_id: normalizeSecurityHeaderReference(payloadWithoutUptime.security_header_profile_id),
certificate_id: normalizeAccessListReference(payloadWithoutUptime.certificate_id),
}
const res = await onSubmit(submitPayload)
@@ -910,18 +912,25 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
<label className="block text-sm font-medium text-gray-300 mb-2">
SSL Certificate
</label>
<Select value={String(formData.certificate_id || 0)} onValueChange={e => setFormData(prev => ({ ...prev, certificate_id: parseInt(e) || null }))}>
<Select
value={resolveSelectToken(formData.certificate_id as number | string | null | undefined)}
onValueChange={token => setFormData(prev => ({ ...prev, certificate_id: resolveTokenToFormValue(token) }))}
>
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="SSL Certificate">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">Auto-manage with Let's Encrypt (recommended)</SelectItem>
{certificates.map(cert => (
<SelectItem key={cert.id || cert.domains} value={String(cert.id ?? 0)}>
{(cert.name || cert.domains)}
{cert.provider ? ` (${cert.provider})` : ''}
</SelectItem>
))}
<SelectItem value="none">Auto-manage with Let's Encrypt (recommended)</SelectItem>
{certificates.map(cert => {
const token = getEntityToken(cert)
if (!token) return null
return (
<SelectItem key={cert.uuid} value={token}>
{cert.name || cert.domains}
{cert.provider ? ` (${cert.provider})` : ''}
</SelectItem>
)
})}
</SelectContent>
</Select>
<p className="text-xs text-gray-500 mt-1">