feat(proxy-host): enhance certificate handling and update form integration
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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?: {
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user