diff --git a/api/certificate/dns_credential.go b/api/certificate/dns_credential.go index 91da5991..c4246371 100644 --- a/api/certificate/dns_credential.go +++ b/api/certificate/dns_credential.go @@ -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)) +} diff --git a/app/package.json b/app/package.json index 876f4f54..740e0e9f 100644 --- a/app/package.json +++ b/app/package.json @@ -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", diff --git a/app/src/api/auto_cert.ts b/app/src/api/auto_cert.ts index 3e612e00..cceae919 100644 --- a/app/src/api/auto_cert.ts +++ b/app/src/api/auto_cert.ts @@ -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 diff --git a/app/src/api/dns.ts b/app/src/api/dns.ts index a94ae475..26a429a9 100644 --- a/app/src/api/dns.ts +++ b/app/src/api/dns.ts @@ -10,6 +10,7 @@ export interface DNSDomain extends ModelBase { id: number name: string provider: string + provider_code?: string } } diff --git a/app/src/api/dns_credential.ts b/app/src/api/dns_credential.ts index af87c02f..024983f8 100644 --- a/app/src/api/dns_credential.ts +++ b/app/src/api/dns_credential.ts @@ -6,6 +6,7 @@ export interface DnsCredential extends ModelBase { name: string config?: DNSProvider provider: string + provider_code?: string code: string configuration: DNSProvider['configuration'] } diff --git a/app/src/components/AutoCertForm/DNSChallenge.vue b/app/src/components/AutoCertForm/DNSChallenge.vue index 2e0db26c..c5a204a7 100644 --- a/app/src/components/AutoCertForm/DNSChallenge.vue +++ b/app/src/components/AutoCertForm/DNSChallenge.vue @@ -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(() => { }) 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) } diff --git a/app/src/version.json b/app/src/version.json index 49064c39..ba62ff7d 100644 --- a/app/src/version.json +++ b/app/src/version.json @@ -1 +1 @@ -{"version":"2.3.1","build_id":1,"total_build":511} \ No newline at end of file +{"version":"2.3.2","build_id":1,"total_build":512} \ No newline at end of file diff --git a/app/src/views/certificate/ACMEUser.vue b/app/src/views/certificate/ACMEUser.vue index c907395e..bd923db3 100644 --- a/app/src/views/certificate/ACMEUser.vue +++ b/app/src/views/certificate/ACMEUser.vue @@ -8,7 +8,7 @@ import acme_user from '@/api/acme_user' const { message } = App.useApp() -const columns: StdTableColumn[] = [ +const columns: ComputedRef = 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 => { diff --git a/app/src/views/dns/DNSCredential.vue b/app/src/views/dns/DNSCredential.vue index fc0129f9..455fc08a 100644 --- a/app/src/views/dns/DNSCredential.vue +++ b/app/src/views/dns/DNSCredential.vue @@ -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 { diff --git a/app/src/views/dns/DNSDomainList.vue b/app/src/views/dns/DNSDomainList.vue index 9ed1d75b..b4ed4fcd 100644 --- a/app/src/views/dns/DNSDomainList.vue +++ b/app/src/views/dns/DNSDomainList.vue @@ -31,11 +31,12 @@ const credentialLoadingMap = reactive>({}) const providerOptions = computed(() => { 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 } diff --git a/app/src/views/dns/components/DNSChallenge.vue b/app/src/views/dns/components/DNSChallenge.vue index e095dc0d..04e8a63e 100644 --- a/app/src/views/dns/components/DNSChallenge.vue +++ b/app/src/views/dns/components/DNSChallenge.vue @@ -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(() => { }) 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) } diff --git a/internal/dns/service.go b/internal/dns/service.go index 6a3098d9..1d3f5690 100644 --- a/internal/dns/service.go +++ b/internal/dns/service.go @@ -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 diff --git a/internal/license/licenses.xz b/internal/license/licenses.xz index ce24f1ed..d94f3ab7 100644 Binary files a/internal/license/licenses.xz and b/internal/license/licenses.xz differ diff --git a/internal/migrate/7.add_provider_code_to_dns_credentials.go b/internal/migrate/7.add_provider_code_to_dns_credentials.go new file mode 100644 index 00000000..25c4d28a --- /dev/null +++ b/internal/migrate/7.add_provider_code_to_dns_credentials.go @@ -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)) +} diff --git a/internal/migrate/migrate.go b/internal/migrate/migrate.go index 413b2a1d..726241fc 100644 --- a/internal/migrate/migrate.go +++ b/internal/migrate/migrate.go @@ -10,6 +10,7 @@ var Migrations = []*gormigrate.Migration{ UpdateCertDomains, RenameEnvGroupsToNamespaces, RenameEnvironmentsToNodes, + AddProviderCodeToDnsCredentials, } var BeforeAutoMigrate = []*gormigrate.Migration{ diff --git a/model/dns_credential.go b/model/dns_credential.go index f5a634c1..4206625d 100644 --- a/model/dns_credential.go +++ b/model/dns_credential.go @@ -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"` } diff --git a/query/dns_credentials.gen.go b/query/dns_credentials.gen.go index e9f361b6..5a397aac 100644 --- a/query/dns_credentials.gen.go +++ b/query/dns_credentials.gen.go @@ -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 {