From 6dc65eea2f89b8c45bb081752f68980c3a11b199 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 3 Jun 2025 16:51:33 +0800 Subject: [PATCH] feat: new acme dns-01 provider: ucloud udnr --- internal/applicant/providers.go | 17 ++ internal/domain/provider.go | 1 + .../aliyun-esa/internal/lego.go | 11 +- .../baiducloud/internal/lego.go | 2 +- .../lego-providers/dnsla/internal/lego.go | 2 +- .../lego-providers/dynv6/internal/lego.go | 2 +- .../lego-providers/gname/internal/lego.go | 2 +- .../lego-providers/jdcloud/internal/lego.go | 2 +- .../tencentcloud-eo/internal/lego.go | 7 +- .../ucloud-udnr/internal/lego.go | 165 ++++++++++++++++++ .../lego-providers/ucloud-udnr/ucloud_udnr.go | 40 +++++ internal/pkg/sdk3rd/ucloud/udnr/apis.go | 115 ++++++++++++ internal/pkg/sdk3rd/ucloud/udnr/client.go | 18 ++ internal/pkg/sdk3rd/ucloud/udnr/models.go | 9 + ui/src/domain/provider.ts | 4 +- ui/src/i18n/locales/en/nls.provider.json | 9 +- ui/src/i18n/locales/zh/nls.provider.json | 1 + 17 files changed, 387 insertions(+), 20 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/internal/lego.go create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/ucloud_udnr.go create mode 100644 internal/pkg/sdk3rd/ucloud/udnr/apis.go create mode 100644 internal/pkg/sdk3rd/ucloud/udnr/client.go create mode 100644 internal/pkg/sdk3rd/ucloud/udnr/models.go diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index ba4fadef..fbf24742 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -39,6 +39,7 @@ import ( pRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun" pTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud" pTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud-eo" + pUCloudUDNR "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr" pVercel "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/vercel" pVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine" pWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn" @@ -596,6 +597,22 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi } } + case domain.ACMEDns01ProviderTypeUCloudUDNR: + { + access := domain.AccessConfigForUCloud{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pUCloudUDNR.NewChallengeProvider(&pUCloudUDNR.ChallengeProviderConfig{ + PrivateKey: access.PrivateKey, + PublicKey: access.PublicKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ACMEDns01ProviderTypeVercel: { access := domain.AccessConfigForVercel{} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index dd9663f2..1a30bdad 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -158,6 +158,7 @@ const ( ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS] ACMEDns01ProviderTypeTencentCloudDNS = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-dns") ACMEDns01ProviderTypeTencentCloudEO = ACMEDns01ProviderType(AccessProviderTypeTencentCloud + "-eo") + ACMEDns01ProviderTypeUCloudUDNR = ACMEDns01ProviderType(AccessProviderTypeUCloud + "-udnr") ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel) ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS] ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns") diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go index 5a576af1..43c488f5 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun-esa/internal/lego.go @@ -1,9 +1,8 @@ -package lego_aliyunesa +package internal import ( "errors" "fmt" - "strings" "sync" "time" @@ -102,13 +101,13 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("alicloud-esa: could not find zone for domain %q: %w", domain, err) } - siteName := strings.TrimRight(authZone, ".") + siteName := dns01.UnFqdn(authZone) siteId, err := d.getSiteId(siteName) if err != nil { return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err) } - if err := d.addOrUpdateDNSRecord(siteId, strings.TrimRight(info.EffectiveFQDN, "."), info.Value); err != nil { + if err := d.addOrUpdateDNSRecord(siteId, dns01.UnFqdn(info.EffectiveFQDN), info.Value); err != nil { return fmt.Errorf("alicloud-esa: %w", err) } @@ -123,13 +122,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("alicloud-esa: could not find zone for domain %q: %w", domain, err) } - siteName := strings.TrimRight(authZone, ".") + siteName := dns01.UnFqdn(authZone) siteId, err := d.getSiteId(siteName) if err != nil { return fmt.Errorf("alicloud-esa: could not find site for zone %q: %w", siteName, err) } - if err := d.removeDNSRecord(siteId, strings.TrimRight(info.EffectiveFQDN, ".")); err != nil { + if err := d.removeDNSRecord(siteId, dns01.UnFqdn(info.EffectiveFQDN)); err != nil { return fmt.Errorf("alicloud-esa: %w", err) } diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go index f67662b5..4c66f088 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal/lego.go @@ -1,4 +1,4 @@ -package lego_baiducloud +package internal import ( "errors" diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go index 87cb6cd9..1063ac5f 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal/lego.go @@ -1,4 +1,4 @@ -package lego_dnsla +package internal import ( "errors" diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go index 8b33cf9e..36a06ffa 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/dynv6/internal/lego.go @@ -1,4 +1,4 @@ -package lego_dynv6 +package internal import ( "context" diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go index 7f1f5670..6bfda830 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal/lego.go @@ -1,4 +1,4 @@ -package lego_gname +package internal import ( "errors" diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go index a1851a11..0361c7cb 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal/lego.go @@ -1,4 +1,4 @@ -package lego_jdcloud +package internal import ( "errors" diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud-eo/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud-eo/internal/lego.go index 692c42d3..69ad8a80 100644 --- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud-eo/internal/lego.go +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud-eo/internal/lego.go @@ -1,10 +1,9 @@ -package lego_tencentcloudeo +package internal import ( "errors" "fmt" "math" - "strings" "time" "github.com/go-acme/lego/v4/challenge" @@ -91,7 +90,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - if err := d.addOrUpdateDNSRecord(strings.TrimRight(info.EffectiveFQDN, "."), info.Value); err != nil { + if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value); err != nil { return fmt.Errorf("tencentcloud-eo: %w", err) } @@ -101,7 +100,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - if err := d.removeDNSRecord(strings.TrimRight(info.EffectiveFQDN, ".")); err != nil { + if err := d.removeDNSRecord(dns01.UnFqdn(info.EffectiveFQDN)); err != nil { return fmt.Errorf("tencentcloud-eo: %w", err) } diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/internal/lego.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/internal/lego.go new file mode 100644 index 00000000..e1be56a4 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/internal/lego.go @@ -0,0 +1,165 @@ +package internal + +import ( + "errors" + "fmt" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/auth" + + "github.com/usual2970/certimate/internal/pkg/sdk3rd/ucloud/udnr" +) + +const ( + envNamespace = "UCLOUDUDNR_" + + EnvPublicKey = envNamespace + "PUBLIC_KEY" + EnvPrivateKey = envNamespace + "PRIVATE_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +type Config struct { + PrivateKey string + PublicKey string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int32 + HTTPTimeout time.Duration +} + +type DNSProvider struct { + client *udnr.UDNRClient + config *Config +} + +func NewDefaultConfig() *Config { + return &Config{ + TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvPrivateKey, EnvPublicKey) + if err != nil { + return nil, fmt.Errorf("ucloud-udnr: %w", err) + } + + config := NewDefaultConfig() + config.PrivateKey = values[EnvPrivateKey] + config.PublicKey = values[EnvPublicKey] + + return NewDNSProviderConfig(config) +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("ucloud-udnr: the configuration of the DNS provider is nil") + } + + cfg := ucloud.NewConfig() + credential := auth.NewCredential() + credential.PrivateKey = config.PrivateKey + credential.PublicKey = config.PublicKey + client := udnr.NewClient(&cfg, &credential) + + return &DNSProvider{ + client: client, + config: config, + }, nil +} + +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("ucloud-udnr: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("ucloud-udnr: %w", err) + } + + udnrDomainDNSQueryReq := d.client.NewQueryDomainDNSRequest() + udnrDomainDNSQueryReq.Dn = ucloud.String(authZone) + if udnrDomainDNSQueryResp, err := d.client.QueryDomainDNS(udnrDomainDNSQueryReq); err != nil { + return fmt.Errorf("ucloud-udnr: %w", err) + } else { + for _, record := range udnrDomainDNSQueryResp.Data { + if record.DnsType == "TXT" && record.RecordName == subDomain { + udnrDomainDNSDeleteReq := d.client.NewDeleteDomainDNSRequest() + udnrDomainDNSDeleteReq.Dn = ucloud.String(authZone) + udnrDomainDNSDeleteReq.DnsType = ucloud.String(record.DnsType) + udnrDomainDNSDeleteReq.RecordName = ucloud.String(record.RecordName) + udnrDomainDNSDeleteReq.Content = ucloud.String(record.Content) + d.client.DeleteDomainDNS(udnrDomainDNSDeleteReq) + break + } + } + } + + udnrDomainDNSAddReq := d.client.NewAddDomainDNSRequest() + udnrDomainDNSAddReq.Dn = ucloud.String(authZone) + udnrDomainDNSAddReq.DnsType = ucloud.String("TXT") + udnrDomainDNSAddReq.RecordName = ucloud.String(subDomain) + udnrDomainDNSAddReq.Content = ucloud.String(info.Value) + udnrDomainDNSAddReq.TTL = ucloud.Int(int(d.config.TTL)) + if _, err := d.client.AddDomainDNS(udnrDomainDNSAddReq); err != nil { + return fmt.Errorf("ucloud-udnr: %w", err) + } + + return nil +} + +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("ucloud-udnr: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("ucloud-udnr: %w", err) + } + + udnrDomainDNSQueryReq := d.client.NewQueryDomainDNSRequest() + udnrDomainDNSQueryReq.Dn = ucloud.String(authZone) + if udnrDomainDNSQueryResp, err := d.client.QueryDomainDNS(udnrDomainDNSQueryReq); err != nil { + return fmt.Errorf("ucloud-udnr: %w", err) + } else { + for _, record := range udnrDomainDNSQueryResp.Data { + if record.DnsType == "TXT" && record.RecordName == subDomain { + udnrDomainDNSDeleteReq := d.client.NewDeleteDomainDNSRequest() + udnrDomainDNSDeleteReq.Dn = ucloud.String(authZone) + udnrDomainDNSDeleteReq.DnsType = ucloud.String(record.DnsType) + udnrDomainDNSDeleteReq.RecordName = ucloud.String(record.RecordName) + udnrDomainDNSDeleteReq.Content = ucloud.String(record.Content) + d.client.DeleteDomainDNS(udnrDomainDNSDeleteReq) + break + } + } + } + + return nil +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/ucloud_udnr.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/ucloud_udnr.go new file mode 100644 index 00000000..d1902747 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/ucloud_udnr.go @@ -0,0 +1,40 @@ +package ucloududnr + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/challenge" + + "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ucloud-udnr/internal" +) + +type ChallengeProviderConfig struct { + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + providerConfig := internal.NewDefaultConfig() + providerConfig.PrivateKey = config.PrivateKey + providerConfig.PublicKey = config.PublicKey + if config.DnsTTL != 0 { + providerConfig.TTL = config.DnsTTL + } + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + + provider, err := internal.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/internal/pkg/sdk3rd/ucloud/udnr/apis.go b/internal/pkg/sdk3rd/ucloud/udnr/apis.go new file mode 100644 index 00000000..af878e5b --- /dev/null +++ b/internal/pkg/sdk3rd/ucloud/udnr/apis.go @@ -0,0 +1,115 @@ +package udnr + +import ( + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + "github.com/ucloud/ucloud-sdk-go/ucloud/response" +) + +type QueryDomainDNSRequest struct { + request.CommonBase + + Dn *string `required:"true"` +} + +type QueryDomainDNSResponse struct { + response.CommonBase + + Data []DomainDNSRecord +} + +func (c *UDNRClient) NewQueryDomainDNSRequest() *QueryDomainDNSRequest { + req := &QueryDomainDNSRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *UDNRClient) QueryDomainDNS(req *QueryDomainDNSRequest) (*QueryDomainDNSResponse, error) { + var err error + var res QueryDomainDNSResponse + + reqCopier := *req + + err = c.Client.InvokeAction("UdnrDomainDNSQuery", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} + +type AddDomainDNSRequest struct { + request.CommonBase + + Dn *string `required:"true"` + DnsType *string `required:"true"` + RecordName *string `required:"true"` + Content *string `required:"true"` + TTL *int `required:"true"` + Prio *int `required:"false"` +} + +type AddDomainDNSResponse struct { + response.CommonBase +} + +func (c *UDNRClient) NewAddDomainDNSRequest() *AddDomainDNSRequest { + req := &AddDomainDNSRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *UDNRClient) AddDomainDNS(req *AddDomainDNSRequest) (*AddDomainDNSResponse, error) { + var err error + var res AddDomainDNSResponse + + reqCopier := *req + + err = c.Client.InvokeAction("UdnrDomainDNSAdd", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} + +type DeleteDomainDNSRequest struct { + request.CommonBase + + Dn *string `required:"true"` + DnsType *string `required:"true"` + RecordName *string `required:"true"` + Content *string `required:"true"` +} + +type DeleteDomainDNSResponse struct { + response.CommonBase +} + +func (c *UDNRClient) NewDeleteDomainDNSRequest() *DeleteDomainDNSRequest { + req := &DeleteDomainDNSRequest{} + + c.Client.SetupRequest(req) + + req.SetRetryable(false) + return req +} + +func (c *UDNRClient) DeleteDomainDNS(req *DeleteDomainDNSRequest) (*DeleteDomainDNSResponse, error) { + var err error + var res DeleteDomainDNSResponse + + reqCopier := *req + + err = c.Client.InvokeAction("UdnrDeleteDnsRecord", &reqCopier, &res) + if err != nil { + return &res, err + } + + return &res, nil +} diff --git a/internal/pkg/sdk3rd/ucloud/udnr/client.go b/internal/pkg/sdk3rd/ucloud/udnr/client.go new file mode 100644 index 00000000..5e23f227 --- /dev/null +++ b/internal/pkg/sdk3rd/ucloud/udnr/client.go @@ -0,0 +1,18 @@ +package udnr + +import ( + "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/auth" +) + +type UDNRClient struct { + *ucloud.Client +} + +func NewClient(config *ucloud.Config, credential *auth.Credential) *UDNRClient { + meta := ucloud.ClientMeta{Product: "UDNR"} + client := ucloud.NewClientWithMeta(config, credential, meta) + return &UDNRClient{ + client, + } +} diff --git a/internal/pkg/sdk3rd/ucloud/udnr/models.go b/internal/pkg/sdk3rd/ucloud/udnr/models.go new file mode 100644 index 00000000..4d2081f5 --- /dev/null +++ b/internal/pkg/sdk3rd/ucloud/udnr/models.go @@ -0,0 +1,9 @@ +package udnr + +type DomainDNSRecord struct { + DnsType string + RecordName string + Content string + TTL int + Prio int +} diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 5bd1439b..64a52723 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -121,6 +121,7 @@ export const accessProvidersMap: Map