refactor: improve provider selection logic in DNSChallenge component and update column definition in ACMEUser view

This commit is contained in:
0xJacky
2025-12-09 09:20:15 +00:00
parent f725462e84
commit cb1fb691af
17 changed files with 172 additions and 49 deletions

View File

@@ -2,6 +2,7 @@ package certificate
import (
"net/http"
"strings"
"github.com/0xJacky/Nginx-UI/internal/cert/dns"
"github.com/0xJacky/Nginx-UI/model"
@@ -23,28 +24,32 @@ func GetDnsCredential(c *gin.Context) {
}
type apiDnsCredential struct {
model.Model
Name string `json:"name"`
Provider string `json:"provider"`
Name string `json:"name"`
Provider string `json:"provider"`
ProviderCode string `json:"provider_code"`
dns.Config
}
c.JSON(http.StatusOK, apiDnsCredential{
Model: dnsCredential.Model,
Name: dnsCredential.Name,
Provider: dnsCredential.Provider,
Config: *dnsCredential.Config,
Model: dnsCredential.Model,
Name: dnsCredential.Name,
Provider: dnsCredential.Provider,
ProviderCode: dnsCredential.ProviderCode,
Config: *dnsCredential.Config,
})
}
func GetDnsCredentialList(c *gin.Context) {
cosy.Core[model.DnsCredential](c).
SetEqual("provider_code").
SetEqual("provider").
SetFussy("name").
PagingList()
}
type DnsCredentialManageJson struct {
Name string `json:"name" binding:"required"`
Provider string `json:"provider"`
Name string `json:"name" binding:"required"`
Provider string `json:"provider"`
ProviderCode string `json:"provider_code"`
dns.Config
}
@@ -54,11 +59,14 @@ func AddDnsCredential(c *gin.Context) {
return
}
providerCode := resolveProviderCode(json)
json.Config.Code = providerCode
json.Config.Name = json.Provider
dnsCredential := model.DnsCredential{
Name: json.Name,
Config: &json.Config,
Provider: json.Provider,
Name: json.Name,
Config: &json.Config,
Provider: json.Provider,
ProviderCode: providerCode,
}
d := query.DnsCredential
@@ -89,10 +97,12 @@ func EditDnsCredential(c *gin.Context) {
}
json.Config.Name = json.Provider
json.Config.Code = resolveProviderCode(json)
_, err = d.Where(d.ID.Eq(dnsCredential.ID)).Updates(&model.DnsCredential{
Name: json.Name,
Config: &json.Config,
Provider: json.Provider,
Name: json.Name,
Config: &json.Config,
Provider: json.Provider,
ProviderCode: resolveProviderCode(json),
})
if err != nil {
@@ -106,3 +116,17 @@ func EditDnsCredential(c *gin.Context) {
func DeleteDnsCredential(c *gin.Context) {
cosy.Core[model.DnsCredential](c).Destroy()
}
func resolveProviderCode(payload DnsCredentialManageJson) string {
if trimmed := normalizeProviderCode(payload.ProviderCode); trimmed != "" {
return trimmed
}
if trimmed := normalizeProviderCode(payload.Code); trimmed != "" {
return trimmed
}
return normalizeProviderCode(payload.Provider)
}
func normalizeProviderCode(value string) string {
return strings.TrimSpace(strings.ToLower(value))
}

View File

@@ -1,7 +1,7 @@
{
"name": "nginx-ui-app-next",
"type": "module",
"version": "2.3.1",
"version": "2.3.2",
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
"scripts": {
"dev": "vite --host",

View File

@@ -30,6 +30,7 @@ export interface AutoCertOptions {
key_type: string
acme_user_id?: number
provider?: string
provider_code?: string
must_staple?: boolean
lego_disable_cname_support?: boolean
revoke_old?: boolean

View File

@@ -10,6 +10,7 @@ export interface DNSDomain extends ModelBase {
id: number
name: string
provider: string
provider_code?: string
}
}

View File

@@ -6,6 +6,7 @@ export interface DnsCredential extends ModelBase {
name: string
config?: DNSProvider
provider: string
provider_code?: string
code: string
configuration: DNSProvider['configuration']
}

View File

@@ -48,18 +48,20 @@ watch(current, () => {
if (mounted.value) {
data.value.code = undefined
data.value.provider = undefined
data.value.provider_code = undefined
data.value.dns_credential_id = undefined
}
return
}
credentials.value = []
data.value.code = current.value.code
// Keep provider consistent with credential records (prefer provider/code over display name).
data.value.provider = current.value.provider || current.value.code || current.value.name
// Use display name for credential query; fall back to provider/code if missing.
data.value.provider = current.value.name || current.value.provider || current.value.code
data.value.provider_code = current.value.code
if (mounted.value)
data.value.dns_credential_id = undefined
dns_credential.getList({ provider: data.value.provider }).then(r => {
dns_credential.getList({ provider_code: data.value.provider_code || current.value.code }).then(r => {
r.data.forEach(v => {
credentials.value?.push({
value: v.id,
@@ -82,12 +84,14 @@ onMounted(async () => {
if (idx > -1) {
data.value.code = r.code
data.value.provider = r.provider
data.value.provider_code = r.provider_code || r.code
providerIdx.value = idx
}
else {
// provider not supported anymore; clear existing selection to keep form consistent
data.value.code = undefined
data.value.provider = undefined
data.value.provider_code = undefined
data.value.dns_credential_id = undefined
providerIdx.value = undefined
}
@@ -112,7 +116,10 @@ const options = computed<SelectProps['options']>(() => {
})
function filterOption(input: string, option?: DefaultOptionType) {
return option?.label.toLowerCase().includes(input.toLowerCase())
const needle = input.toLowerCase()
const label = option?.label?.toString().toLowerCase() ?? ''
const value = option?.value?.toString().toLowerCase() ?? ''
return label.includes(needle) || value.includes(needle)
}
</script>

View File

@@ -1 +1 @@
{"version":"2.3.1","build_id":1,"total_build":511}
{"version":"2.3.2","build_id":1,"total_build":512}

View File

@@ -8,7 +8,7 @@ import acme_user from '@/api/acme_user'
const { message } = App.useApp()
const columns: StdTableColumn[] = [
const columns: ComputedRef<StdTableColumn[]> = computed(() => [
{
title: () => $gettext('Name'),
dataIndex: 'name',
@@ -42,7 +42,7 @@ const columns: StdTableColumn[] = [
edit: {
type: 'autoComplete',
autoComplete: {
placeholder: () => $gettext('Select or enter a CA directory URL'),
placeholder: $gettext('Select or enter a CA directory URL'),
allowClear: true,
options: [
{
@@ -132,7 +132,7 @@ const columns: StdTableColumn[] = [
dataIndex: 'actions',
fixed: 'right',
},
]
])
function register(id: number, data: AcmeUser) {
acme_user.register(id).then(r => {

View File

@@ -24,7 +24,7 @@ const columns: StdTableColumn[] = [{
search: true,
}, {
title: () => $gettext('Provider'),
dataIndex: 'provider',
dataIndex: 'provider_code',
customRender: ({ record }: CustomRenderArgs) => {
return record.provider
},
@@ -34,7 +34,7 @@ const columns: StdTableColumn[] = [{
type: 'select',
select: {
remote: {
valueKey: 'name',
valueKey: 'code',
labelKey: 'name',
api: async () => {
return {

View File

@@ -31,11 +31,12 @@ const credentialLoadingMap = reactive<Record<string, boolean>>({})
const providerOptions = computed<SelectOptionList>(() => {
const list: SelectOptionList = []
dnsProviders.value.forEach(provider => {
const label = provider.name ?? provider.provider ?? ''
if (label) {
const code = provider.code ?? provider.provider ?? ''
const label = provider.name ?? provider.provider ?? code
if (code) {
list.push({
label,
value: label,
value: code,
})
}
})
@@ -57,20 +58,20 @@ function clearCredentialSelection(formData: DomainForm) {
formData.dns_credential_id = undefined
}
async function ensureCredentialOptions(provider: string) {
if (!provider || credentialOptions[provider])
async function ensureCredentialOptions(providerCode: string) {
if (!providerCode || credentialOptions[providerCode])
return
credentialLoadingMap[provider] = true
credentialLoadingMap[providerCode] = true
try {
const response = await dns_credential.getList({ provider })
const response = await dns_credential.getList({ provider_code: providerCode })
const list = response?.data ?? []
credentialOptions[provider] = list.map(item => ({
credentialOptions[providerCode] = list.map(item => ({
label: item.name,
value: item.id,
}))
}
finally {
credentialLoadingMap[provider] = false
credentialLoadingMap[providerCode] = false
}
}
@@ -90,6 +91,11 @@ function resolveProviderName(record: DomainForm) {
?? ''
}
function resolveProviderCode(record: DomainForm) {
return record.dns_credential?.provider_code
?? ''
}
function resolveCredentialName(record: DomainForm) {
return record.dns_credential?.name ?? '-'
}
@@ -116,7 +122,7 @@ const columns: StdTableColumn[] = [{
const formData = context.formData
if (!formData.provider_initialized) {
formData.selected_provider = context.mode === 'edit'
? resolveProviderName(formData)
? (resolveProviderCode(formData) || resolveProviderName(formData))
: ''
formData.provider_initialized = true
}

View File

@@ -35,6 +35,7 @@ watch(current, () => {
if (current.value) {
data.value.code = current.value.code!
data.value.provider = current.value.name!
data.value.provider_code = current.value.code
auto_cert.get_dns_provider(data.value.code).then(r => {
Object.assign(current.value!, r)
@@ -50,7 +51,10 @@ const options = computed<SelectProps['options']>(() => {
})
function filterOption(input: string, option?: DefaultOptionType) {
return option?.label.toLowerCase().includes(input.toLowerCase())
const needle = input.toLowerCase()
const label = option?.label?.toString().toLowerCase() ?? ''
const value = option?.value?.toString().toLowerCase() ?? ''
return label.includes(needle) || value.includes(needle)
}
</script>

View File

@@ -315,11 +315,16 @@ func toProviderCredential(credential *model.DnsCredential) (*Credential, error)
}
}
code := credential.Config.Code
if code == "" {
code = credential.ProviderCode
}
return &Credential{
ID: credential.ID,
Name: credential.Name,
Provider: credential.Provider,
Code: credential.Config.Code,
Code: code,
Values: values,
Additional: additional,
}, nil

Binary file not shown.

View File

@@ -0,0 +1,68 @@
package migrate
import (
"encoding/json"
"strings"
"github.com/0xJacky/Nginx-UI/internal/cert/dns"
"github.com/0xJacky/Nginx-UI/model"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/datatypes"
"gorm.io/gorm"
)
var AddProviderCodeToDnsCredentials = &gormigrate.Migration{
ID: "20251209000002",
Migrate: func(tx *gorm.DB) error {
if !tx.Migrator().HasColumn(&model.DnsCredential{}, "ProviderCode") {
if err := tx.Migrator().AddColumn(&model.DnsCredential{}, "ProviderCode"); err != nil {
return err
}
}
if !tx.Migrator().HasIndex(&model.DnsCredential{}, "ProviderCode") {
if err := tx.Migrator().CreateIndex(&model.DnsCredential{}, "ProviderCode"); err != nil {
return err
}
}
// Backfill provider_code from config.code (preferred) or provider/name fallback.
type credentialRow struct {
ID uint64 `gorm:"column:id"`
Config datatypes.JSON `gorm:"column:config"`
Provider string `gorm:"column:provider"`
Name string `gorm:"column:name"`
}
var rows []credentialRow
if err := tx.Table("dns_credentials").Select("id, config, provider, name").Find(&rows).Error; err != nil {
return err
}
for _, row := range rows {
providerCode := normalizeProviderCode(row.Provider)
if len(row.Config) > 0 {
var cfg dns.Config
if err := json.Unmarshal(row.Config, &cfg); err == nil && strings.TrimSpace(cfg.Code) != "" {
providerCode = normalizeProviderCode(cfg.Code)
}
}
if providerCode == "" {
providerCode = normalizeProviderCode(row.Name)
}
if err := tx.Table("dns_credentials").
Where("id = ?", row.ID).
Update("provider_code", providerCode).Error; err != nil {
return err
}
}
return nil
},
}
func normalizeProviderCode(value string) string {
return strings.TrimSpace(strings.ToLower(value))
}

View File

@@ -10,6 +10,7 @@ var Migrations = []*gormigrate.Migration{
UpdateCertDomains,
RenameEnvGroupsToNamespaces,
RenameEnvironmentsToNodes,
AddProviderCodeToDnsCredentials,
}
var BeforeAutoMigrate = []*gormigrate.Migration{

View File

@@ -6,7 +6,8 @@ import (
type DnsCredential struct {
Model
Name string `json:"name"`
Config *dns.Config `json:"config,omitempty" gorm:"serializer:json"`
Provider string `json:"provider"`
Name string `json:"name"`
Config *dns.Config `json:"config,omitempty" gorm:"serializer:json"`
Provider string `json:"provider"`
ProviderCode string `json:"provider_code" gorm:"index"`
}

View File

@@ -35,6 +35,7 @@ func newDnsCredential(db *gorm.DB, opts ...gen.DOOption) dnsCredential {
_dnsCredential.Name = field.NewString(tableName, "name")
_dnsCredential.Config = field.NewField(tableName, "config")
_dnsCredential.Provider = field.NewString(tableName, "provider")
_dnsCredential.ProviderCode = field.NewString(tableName, "provider_code")
_dnsCredential.fillFieldMap()
@@ -44,14 +45,15 @@ func newDnsCredential(db *gorm.DB, opts ...gen.DOOption) dnsCredential {
type dnsCredential struct {
dnsCredentialDo
ALL field.Asterisk
ID field.Uint64
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Name field.String
Config field.Field
Provider field.String
ALL field.Asterisk
ID field.Uint64
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Name field.String
Config field.Field
Provider field.String
ProviderCode field.String
fieldMap map[string]field.Expr
}
@@ -75,6 +77,7 @@ func (d *dnsCredential) updateTableName(table string) *dnsCredential {
d.Name = field.NewString(table, "name")
d.Config = field.NewField(table, "config")
d.Provider = field.NewString(table, "provider")
d.ProviderCode = field.NewString(table, "provider_code")
d.fillFieldMap()
@@ -91,7 +94,7 @@ func (d *dnsCredential) GetFieldByName(fieldName string) (field.OrderExpr, bool)
}
func (d *dnsCredential) fillFieldMap() {
d.fieldMap = make(map[string]field.Expr, 7)
d.fieldMap = make(map[string]field.Expr, 8)
d.fieldMap["id"] = d.ID
d.fieldMap["created_at"] = d.CreatedAt
d.fieldMap["updated_at"] = d.UpdatedAt
@@ -99,6 +102,7 @@ func (d *dnsCredential) fillFieldMap() {
d.fieldMap["name"] = d.Name
d.fieldMap["config"] = d.Config
d.fieldMap["provider"] = d.Provider
d.fieldMap["provider_code"] = d.ProviderCode
}
func (d dnsCredential) clone(db *gorm.DB) dnsCredential {