mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
commit
2ed94bf509
@ -3,6 +3,7 @@ package applicant
|
|||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||||
@ -25,6 +26,7 @@ func (a *aliyun) Apply() (*Certificate, error) {
|
|||||||
|
|
||||||
os.Setenv("ALICLOUD_ACCESS_KEY", access.AccessKeyId)
|
os.Setenv("ALICLOUD_ACCESS_KEY", access.AccessKeyId)
|
||||||
os.Setenv("ALICLOUD_SECRET_KEY", access.AccessKeySecret)
|
os.Setenv("ALICLOUD_SECRET_KEY", access.AccessKeySecret)
|
||||||
|
os.Setenv("ALICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||||
dnsProvider, err := alidns.NewDNSProvider()
|
dnsProvider, err := alidns.NewDNSProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package applicant
|
package applicant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"certimate/internal/domain"
|
||||||
"certimate/internal/utils/app"
|
"certimate/internal/utils/app"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
@ -46,6 +47,8 @@ var sslProviderUrls = map[string]string{
|
|||||||
|
|
||||||
const defaultEmail = "536464346@qq.com"
|
const defaultEmail = "536464346@qq.com"
|
||||||
|
|
||||||
|
const defaultTimeout = 60
|
||||||
|
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
CertUrl string `json:"certUrl"`
|
CertUrl string `json:"certUrl"`
|
||||||
CertStableUrl string `json:"certStableUrl"`
|
CertStableUrl string `json:"certStableUrl"`
|
||||||
@ -60,6 +63,7 @@ type ApplyOption struct {
|
|||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
Nameservers string `json:"nameservers"`
|
Nameservers string `json:"nameservers"`
|
||||||
|
Timeout int64 `json:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MyUser struct {
|
type MyUser struct {
|
||||||
@ -83,8 +87,22 @@ type Applicant interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Get(record *models.Record) (Applicant, error) {
|
func Get(record *models.Record) (Applicant, error) {
|
||||||
access := record.ExpandedOne("access")
|
|
||||||
email := record.GetString("email")
|
if record.GetString("applyConfig") == "" {
|
||||||
|
return nil, errors.New("apply config is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
applyConfig := &domain.ApplyConfig{}
|
||||||
|
|
||||||
|
record.UnmarshalJSONField("applyConfig", applyConfig)
|
||||||
|
|
||||||
|
access, err := app.GetApp().Dao().FindRecordById("access", applyConfig.Access)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("access record not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
email := applyConfig.Email
|
||||||
if email == "" {
|
if email == "" {
|
||||||
email = defaultEmail
|
email = defaultEmail
|
||||||
}
|
}
|
||||||
@ -92,7 +110,8 @@ func Get(record *models.Record) (Applicant, error) {
|
|||||||
Email: email,
|
Email: email,
|
||||||
Domain: record.GetString("domain"),
|
Domain: record.GetString("domain"),
|
||||||
Access: access.GetString("config"),
|
Access: access.GetString("config"),
|
||||||
Nameservers: record.GetString("nameservers"),
|
Nameservers: applyConfig.Nameservers,
|
||||||
|
Timeout: applyConfig.Timeout,
|
||||||
}
|
}
|
||||||
switch access.GetString("configType") {
|
switch access.GetString("configType") {
|
||||||
case configTypeAliyun:
|
case configTypeAliyun:
|
||||||
|
@ -3,6 +3,7 @@ package applicant
|
|||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
cf "github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
cf "github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||||
@ -23,6 +24,7 @@ func (c *cloudflare) Apply() (*Certificate, error) {
|
|||||||
json.Unmarshal([]byte(c.option.Access), access)
|
json.Unmarshal([]byte(c.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("CLOUDFLARE_DNS_API_TOKEN", access.DnsApiToken)
|
os.Setenv("CLOUDFLARE_DNS_API_TOKEN", access.DnsApiToken)
|
||||||
|
os.Setenv("CLOUDFLARE_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", c.option.Timeout))
|
||||||
|
|
||||||
provider, err := cf.NewDNSProvider()
|
provider, err := cf.NewDNSProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,6 +3,7 @@ package applicant
|
|||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
|
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||||
@ -25,6 +26,7 @@ func (a *godaddy) Apply() (*Certificate, error) {
|
|||||||
|
|
||||||
os.Setenv("GODADDY_API_KEY", access.ApiKey)
|
os.Setenv("GODADDY_API_KEY", access.ApiKey)
|
||||||
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
|
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
|
||||||
|
os.Setenv("GODADDY_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||||
|
|
||||||
dnsProvider, err := godaddyProvider.NewDNSProvider()
|
dnsProvider, err := godaddyProvider.NewDNSProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,6 +3,7 @@ package applicant
|
|||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||||
@ -26,6 +27,8 @@ func (t *huaweicloud) Apply() (*Certificate, error) {
|
|||||||
os.Setenv("HUAWEICLOUD_REGION", access.Region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
os.Setenv("HUAWEICLOUD_REGION", access.Region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
||||||
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
|
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
|
||||||
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
||||||
|
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
||||||
|
|
||||||
dnsProvider, err := huaweicloudProvider.NewDNSProvider()
|
dnsProvider, err := huaweicloudProvider.NewDNSProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -3,6 +3,7 @@ package applicant
|
|||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
namesiloProvider "github.com/go-acme/lego/v4/providers/dns/namesilo"
|
namesiloProvider "github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||||
@ -24,6 +25,7 @@ func (a *namesilo) Apply() (*Certificate, error) {
|
|||||||
json.Unmarshal([]byte(a.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("NAMESILO_API_KEY", access.ApiKey)
|
os.Setenv("NAMESILO_API_KEY", access.ApiKey)
|
||||||
|
os.Setenv("NAMESILO_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||||
|
|
||||||
dnsProvider, err := namesiloProvider.NewDNSProvider()
|
dnsProvider, err := namesiloProvider.NewDNSProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,6 +3,7 @@ package applicant
|
|||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||||
@ -25,6 +26,8 @@ func (t *tencent) Apply() (*Certificate, error) {
|
|||||||
|
|
||||||
os.Setenv("TENCENTCLOUD_SECRET_ID", access.SecretId)
|
os.Setenv("TENCENTCLOUD_SECRET_ID", access.SecretId)
|
||||||
os.Setenv("TENCENTCLOUD_SECRET_KEY", access.SecretKey)
|
os.Setenv("TENCENTCLOUD_SECRET_KEY", access.SecretKey)
|
||||||
|
os.Setenv("TENCENTCLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
||||||
|
|
||||||
dnsProvider, err := tencentcloud.NewDNSProvider()
|
dnsProvider, err := tencentcloud.NewDNSProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -190,7 +190,7 @@ func (a *aliyun) resource() (*cas20200407.ListCloudResourcesResponseBodyData, er
|
|||||||
|
|
||||||
listCloudResourcesRequest := &cas20200407.ListCloudResourcesRequest{
|
listCloudResourcesRequest := &cas20200407.ListCloudResourcesRequest{
|
||||||
CloudProduct: tea.String(a.option.Product),
|
CloudProduct: tea.String(a.option.Product),
|
||||||
Keyword: tea.String(a.option.Domain),
|
Keyword: tea.String(getDeployString(a.option.DeployConfig, "domain")),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := a.client.ListCloudResources(listCloudResourcesRequest)
|
resp, err := a.client.ListCloudResources(listCloudResourcesRequest)
|
||||||
|
@ -2,6 +2,7 @@ package deployer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
|
"certimate/internal/utils/rand"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -46,9 +47,9 @@ func (a *AliyunCdn) GetInfo() []string {
|
|||||||
|
|
||||||
func (a *AliyunCdn) Deploy(ctx context.Context) error {
|
func (a *AliyunCdn) Deploy(ctx context.Context) error {
|
||||||
|
|
||||||
certName := fmt.Sprintf("%s-%s", a.option.Domain, a.option.DomainId)
|
certName := fmt.Sprintf("%s-%s-%s", a.option.Domain, a.option.DomainId, rand.RandStr(6))
|
||||||
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
|
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
|
||||||
DomainName: tea.String(a.option.Domain),
|
DomainName: tea.String(getDeployString(a.option.DeployConfig, "domain")),
|
||||||
CertName: tea.String(certName),
|
CertName: tea.String(certName),
|
||||||
CertType: tea.String("upload"),
|
CertType: tea.String("upload"),
|
||||||
SSLProtocol: tea.String("on"),
|
SSLProtocol: tea.String("on"),
|
||||||
|
@ -7,6 +7,7 @@ package deployer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"certimate/internal/domain"
|
"certimate/internal/domain"
|
||||||
|
"certimate/internal/utils/rand"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -51,9 +52,9 @@ func (a *AliyunEsa) GetInfo() []string {
|
|||||||
|
|
||||||
func (a *AliyunEsa) Deploy(ctx context.Context) error {
|
func (a *AliyunEsa) Deploy(ctx context.Context) error {
|
||||||
|
|
||||||
certName := fmt.Sprintf("%s-%s", a.option.Domain, a.option.DomainId)
|
certName := fmt.Sprintf("%s-%s-%s", a.option.Domain, a.option.DomainId, rand.RandStr(6))
|
||||||
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
||||||
DomainName: tea.String(a.option.Domain),
|
DomainName: tea.String(getDeployString(a.option.DeployConfig, "domain")),
|
||||||
CertName: tea.String(certName),
|
CertName: tea.String(certName),
|
||||||
CertType: tea.String("upload"),
|
CertType: tea.String("upload"),
|
||||||
SSLProtocol: tea.String("on"),
|
SSLProtocol: tea.String("on"),
|
||||||
|
@ -2,8 +2,8 @@ package deployer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"certimate/internal/applicant"
|
"certimate/internal/applicant"
|
||||||
|
"certimate/internal/domain"
|
||||||
"certimate/internal/utils/app"
|
"certimate/internal/utils/app"
|
||||||
"certimate/internal/utils/variables"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -30,6 +30,7 @@ type DeployerOption struct {
|
|||||||
Product string `json:"product"`
|
Product string `json:"product"`
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
AceessRecord *models.Record `json:"-"`
|
AceessRecord *models.Record `json:"-"`
|
||||||
|
DeployConfig domain.DeployConfig `json:"deployConfig"`
|
||||||
Certificate applicant.Certificate `json:"certificate"`
|
Certificate applicant.Certificate `json:"certificate"`
|
||||||
Variables map[string]string `json:"variables"`
|
Variables map[string]string `json:"variables"`
|
||||||
}
|
}
|
||||||
@ -42,52 +43,29 @@ type Deployer interface {
|
|||||||
|
|
||||||
func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
|
func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
|
||||||
rs := make([]Deployer, 0)
|
rs := make([]Deployer, 0)
|
||||||
|
if record.GetString("deployConfig") == "" {
|
||||||
if record.GetString("targetAccess") != "" {
|
|
||||||
singleDeployer, err := Get(record, cert)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rs = append(rs, singleDeployer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.GetString("group") != "" {
|
|
||||||
group := record.ExpandedOne("group")
|
|
||||||
|
|
||||||
if errs := app.GetApp().Dao().ExpandRecord(group, []string{"access"}, nil); len(errs) > 0 {
|
|
||||||
|
|
||||||
errList := make([]error, 0)
|
|
||||||
for name, err := range errs {
|
|
||||||
errList = append(errList, fmt.Errorf("展开记录失败,%s: %w", name, err))
|
|
||||||
}
|
|
||||||
err := errors.Join(errList...)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
records := group.ExpandedAll("access")
|
|
||||||
|
|
||||||
deployers, err := getByGroup(record, cert, records...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rs = append(rs, deployers...)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs, nil
|
return rs, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getByGroup(record *models.Record, cert *applicant.Certificate, accesses ...*models.Record) ([]Deployer, error) {
|
deployConfigs := make([]domain.DeployConfig, 0)
|
||||||
|
|
||||||
rs := make([]Deployer, 0)
|
err := record.UnmarshalJSONField("deployConfig", &deployConfigs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析部署配置失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deployConfigs) == 0 {
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, deployConfig := range deployConfigs {
|
||||||
|
|
||||||
|
deployer, err := getWithDeployConfig(record, cert, deployConfig)
|
||||||
|
|
||||||
for _, access := range accesses {
|
|
||||||
deployer, err := getWithAccess(record, cert, access)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rs = append(rs, deployer)
|
rs = append(rs, deployer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,15 +73,21 @@ func getByGroup(record *models.Record, cert *applicant.Certificate, accesses ...
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWithAccess(record *models.Record, cert *applicant.Certificate, access *models.Record) (Deployer, error) {
|
func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, deployConfig domain.DeployConfig) (Deployer, error) {
|
||||||
|
|
||||||
|
access, err := app.GetApp().Dao().FindRecordById("access", deployConfig.Access)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("access record not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
option := &DeployerOption{
|
option := &DeployerOption{
|
||||||
DomainId: record.Id,
|
DomainId: record.Id,
|
||||||
Domain: record.GetString("domain"),
|
Domain: record.GetString("domain"),
|
||||||
Product: getProduct(record),
|
Product: getProduct(deployConfig.Type),
|
||||||
Access: access.GetString("config"),
|
Access: access.GetString("config"),
|
||||||
AceessRecord: access,
|
AceessRecord: access,
|
||||||
Variables: variables.Parse2Map(record.GetString("variables")),
|
DeployConfig: deployConfig,
|
||||||
}
|
}
|
||||||
if cert != nil {
|
if cert != nil {
|
||||||
option.Certificate = *cert
|
option.Certificate = *cert
|
||||||
@ -114,7 +98,7 @@ func getWithAccess(record *models.Record, cert *applicant.Certificate, access *m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch record.GetString("targetType") {
|
switch deployConfig.Type {
|
||||||
case targetAliyunOss:
|
case targetAliyunOss:
|
||||||
return NewAliyun(option)
|
return NewAliyun(option)
|
||||||
case targetAliyunCdn:
|
case targetAliyunCdn:
|
||||||
@ -136,16 +120,8 @@ func getWithAccess(record *models.Record, cert *applicant.Certificate, access *m
|
|||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
func getProduct(t string) string {
|
||||||
|
rs := strings.Split(t, "-")
|
||||||
access := record.ExpandedOne("targetAccess")
|
|
||||||
|
|
||||||
return getWithAccess(record, cert, access)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProduct(record *models.Record) string {
|
|
||||||
targetType := record.GetString("targetType")
|
|
||||||
rs := strings.Split(targetType, "-")
|
|
||||||
if len(rs) < 2 {
|
if len(rs) < 2 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -159,3 +135,39 @@ func toStr(tag string, data any) string {
|
|||||||
byts, _ := json.Marshal(data)
|
byts, _ := json.Marshal(data)
|
||||||
return tag + ":" + string(byts)
|
return tag + ":" + string(byts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDeployString(conf domain.DeployConfig, key string) string {
|
||||||
|
if _, ok := conf.Config[key]; !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := conf.Config[key].(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeployVariables(conf domain.DeployConfig) map[string]string {
|
||||||
|
rs := make(map[string]string)
|
||||||
|
data, ok := conf.Config["variables"]
|
||||||
|
if !ok {
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
bts, _ := json.Marshal(data)
|
||||||
|
|
||||||
|
kvData := make([]domain.KV, 0)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(bts, &kvData); err != nil {
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range kvData {
|
||||||
|
rs[kv.Key] = kv.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -11,9 +11,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type localAccess struct {
|
type localAccess struct {
|
||||||
Command string `json:"command"`
|
|
||||||
CertPath string `json:"certPath"`
|
|
||||||
KeyPath string `json:"keyPath"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type local struct {
|
type local struct {
|
||||||
@ -41,18 +38,27 @@ func (l *local) Deploy(ctx context.Context) error {
|
|||||||
if err := json.Unmarshal([]byte(l.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(l.option.Access), access); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preCommand := getDeployString(l.option.DeployConfig, "preCommand")
|
||||||
|
|
||||||
|
if preCommand != "" {
|
||||||
|
if err := execCmd(preCommand); err != nil {
|
||||||
|
return fmt.Errorf("执行前置命令失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 复制文件
|
// 复制文件
|
||||||
if err := copyFile(l.option.Certificate.Certificate, access.CertPath); err != nil {
|
if err := copyFile(l.option.Certificate.Certificate, getDeployString(l.option.DeployConfig, "certPath")); err != nil {
|
||||||
return fmt.Errorf("复制证书失败: %w", err)
|
return fmt.Errorf("复制证书失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := copyFile(l.option.Certificate.PrivateKey, access.KeyPath); err != nil {
|
if err := copyFile(l.option.Certificate.PrivateKey, getDeployString(l.option.DeployConfig, "keyPath")); err != nil {
|
||||||
return fmt.Errorf("复制私钥失败: %w", err)
|
return fmt.Errorf("复制私钥失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行命令
|
// 执行命令
|
||||||
|
|
||||||
if err := execCmd(access.Command); err != nil {
|
if err := execCmd(getDeployString(l.option.DeployConfig, "command")); err != nil {
|
||||||
return fmt.Errorf("执行命令失败: %w", err)
|
return fmt.Errorf("执行命令失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ func (q *qiuniu) Deploy(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *qiuniu) enableHttps(certId string) error {
|
func (q *qiuniu) enableHttps(certId string) error {
|
||||||
path := fmt.Sprintf("/domain/%s/sslize", q.option.Domain)
|
path := fmt.Sprintf("/domain/%s/sslize", getDeployString(q.option.DeployConfig, "domain"))
|
||||||
|
|
||||||
body := &modifyDomainCertReq{
|
body := &modifyDomainCertReq{
|
||||||
CertID: certId,
|
CertID: certId,
|
||||||
@ -104,7 +104,7 @@ type domainInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *qiuniu) getDomainInfo() (*domainInfo, error) {
|
func (q *qiuniu) getDomainInfo() (*domainInfo, error) {
|
||||||
path := fmt.Sprintf("/domain/%s", q.option.Domain)
|
path := fmt.Sprintf("/domain/%s", getDeployString(q.option.DeployConfig, "domain"))
|
||||||
|
|
||||||
res, err := q.req(qiniuGateway+path, http.MethodGet, nil)
|
res, err := q.req(qiniuGateway+path, http.MethodGet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -135,8 +135,8 @@ func (q *qiuniu) uploadCert() (string, error) {
|
|||||||
path := "/sslcert"
|
path := "/sslcert"
|
||||||
|
|
||||||
body := &uploadCertReq{
|
body := &uploadCertReq{
|
||||||
Name: q.option.Domain,
|
Name: getDeployString(q.option.DeployConfig, "domain"),
|
||||||
CommonName: q.option.Domain,
|
CommonName: getDeployString(q.option.DeployConfig, "domain"),
|
||||||
Pri: q.option.Certificate.PrivateKey,
|
Pri: q.option.Certificate.PrivateKey,
|
||||||
Ca: q.option.Certificate.Certificate,
|
Ca: q.option.Certificate.Certificate,
|
||||||
}
|
}
|
||||||
@ -166,7 +166,7 @@ type modifyDomainCertReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *qiuniu) modifyDomainCert(certId string) error {
|
func (q *qiuniu) modifyDomainCert(certId string) error {
|
||||||
path := fmt.Sprintf("/domain/%s/httpsconf", q.option.Domain)
|
path := fmt.Sprintf("/domain/%s/httpsconf", getDeployString(q.option.DeployConfig, "domain"))
|
||||||
|
|
||||||
body := &modifyDomainCertReq{
|
body := &modifyDomainCertReq{
|
||||||
CertID: certId,
|
CertID: certId,
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
xpath "path"
|
xpath "path"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
sshPkg "golang.org/x/crypto/ssh"
|
sshPkg "golang.org/x/crypto/ssh"
|
||||||
@ -24,10 +23,6 @@ type sshAccess struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Port string `json:"port"`
|
Port string `json:"port"`
|
||||||
PreCommand string `json:"preCommand"`
|
|
||||||
Command string `json:"command"`
|
|
||||||
CertPath string `json:"certPath"`
|
|
||||||
KeyPath string `json:"keyPath"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSSH(option *DeployerOption) (Deployer, error) {
|
func NewSSH(option *DeployerOption) (Deployer, error) {
|
||||||
@ -50,16 +45,6 @@ func (s *ssh) Deploy(ctx context.Context) error {
|
|||||||
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将证书路径和命令中的变量替换为实际值
|
|
||||||
for k, v := range s.option.Variables {
|
|
||||||
key := fmt.Sprintf("${%s}", k)
|
|
||||||
access.CertPath = strings.ReplaceAll(access.CertPath, key, v)
|
|
||||||
access.KeyPath = strings.ReplaceAll(access.KeyPath, key, v)
|
|
||||||
access.Command = strings.ReplaceAll(access.Command, key, v)
|
|
||||||
access.PreCommand = strings.ReplaceAll(access.PreCommand, key, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接
|
// 连接
|
||||||
client, err := s.getClient(access)
|
client, err := s.getClient(access)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,29 +55,30 @@ func (s *ssh) Deploy(ctx context.Context) error {
|
|||||||
s.infos = append(s.infos, toStr("ssh连接成功", nil))
|
s.infos = append(s.infos, toStr("ssh连接成功", nil))
|
||||||
|
|
||||||
// 执行前置命令
|
// 执行前置命令
|
||||||
if access.PreCommand != "" {
|
preCommand := getDeployString(s.option.DeployConfig, "preCommand")
|
||||||
err, stdout, stderr := s.sshExecCommand(client, access.PreCommand)
|
if preCommand != "" {
|
||||||
|
err, stdout, stderr := s.sshExecCommand(client, preCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传证书
|
// 上传证书
|
||||||
if err := s.upload(client, s.option.Certificate.Certificate, access.CertPath); err != nil {
|
if err := s.upload(client, s.option.Certificate.Certificate, getDeployString(s.option.DeployConfig, "certPath")); err != nil {
|
||||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
return fmt.Errorf("failed to upload certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.infos = append(s.infos, toStr("ssh上传证书成功", nil))
|
s.infos = append(s.infos, toStr("ssh上传证书成功", nil))
|
||||||
|
|
||||||
// 上传私钥
|
// 上传私钥
|
||||||
if err := s.upload(client, s.option.Certificate.PrivateKey, access.KeyPath); err != nil {
|
if err := s.upload(client, s.option.Certificate.PrivateKey, getDeployString(s.option.DeployConfig, "keyPath")); err != nil {
|
||||||
return fmt.Errorf("failed to upload private key: %w", err)
|
return fmt.Errorf("failed to upload private key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.infos = append(s.infos, toStr("ssh上传私钥成功", nil))
|
s.infos = append(s.infos, toStr("ssh上传私钥成功", nil))
|
||||||
|
|
||||||
// 执行命令
|
// 执行命令
|
||||||
err, stdout, stderr := s.sshExecCommand(client, access.Command)
|
err, stdout, stderr := s.sshExecCommand(client, getDeployString(s.option.DeployConfig, "command"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ type hookData struct {
|
|||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
PrivateKey string `json:"privateKey"`
|
PrivateKey string `json:"privateKey"`
|
||||||
|
Variables map[string]string `json:"variables"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type webhook struct {
|
type webhook struct {
|
||||||
@ -50,6 +51,7 @@ func (w *webhook) Deploy(ctx context.Context) error {
|
|||||||
Domain: w.option.Domain,
|
Domain: w.option.Domain,
|
||||||
Certificate: w.option.Certificate.Certificate,
|
Certificate: w.option.Certificate.Certificate,
|
||||||
PrivateKey: w.option.Certificate.PrivateKey,
|
PrivateKey: w.option.Certificate.PrivateKey,
|
||||||
|
Variables: getDeployVariables(w.option.DeployConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(data)
|
body, _ := json.Marshal(data)
|
||||||
|
20
internal/domain/domains.go
Normal file
20
internal/domain/domains.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type ApplyConfig struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Access string `json:"access"`
|
||||||
|
Timeout int64 `json:"timeout"`
|
||||||
|
Nameservers string `json:"nameservers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeployConfig struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Access string `json:"access"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Config map[string]any `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KV struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"certimate/internal/deployer"
|
"certimate/internal/deployer"
|
||||||
"certimate/internal/utils/app"
|
"certimate/internal/utils/app"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -41,18 +40,6 @@ func deploy(ctx context.Context, record *models.Record) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
history.record(checkPhase, "获取记录成功", nil)
|
history.record(checkPhase, "获取记录成功", nil)
|
||||||
if errs := app.GetApp().Dao().ExpandRecord(currRecord, []string{"access", "targetAccess", "group"}, nil); len(errs) > 0 {
|
|
||||||
|
|
||||||
errList := make([]error, 0)
|
|
||||||
for name, err := range errs {
|
|
||||||
errList = append(errList, fmt.Errorf("展开记录失败,%s: %w", name, err))
|
|
||||||
}
|
|
||||||
err = errors.Join(errList...)
|
|
||||||
app.GetApp().Logger().Error("展开记录失败", "err", err)
|
|
||||||
history.record(checkPhase, "获取授权信息失败", &RecordInfo{Err: err})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
history.record(checkPhase, "获取授权信息成功", nil)
|
|
||||||
|
|
||||||
cert := currRecord.GetString("certificate")
|
cert := currRecord.GetString("certificate")
|
||||||
expiredAt := currRecord.GetDateTime("expiredAt").Time()
|
expiredAt := currRecord.GetDateTime("expiredAt").Time()
|
||||||
@ -106,6 +93,13 @@ func deploy(ctx context.Context, record *models.Record) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 没有部署配置,也算成功
|
||||||
|
if len(deployers) == 0 {
|
||||||
|
history.record(deployPhase, "没有部署配置", &RecordInfo{Info: []string{"没有部署配置"}})
|
||||||
|
history.setWholeSuccess(true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, deployer := range deployers {
|
for _, deployer := range deployers {
|
||||||
if err = deployer.Deploy(ctx); err != nil {
|
if err = deployer.Deploy(ctx); err != nil {
|
||||||
|
|
||||||
|
731
migrations/1728778480_collections_snapshot.go
Normal file
731
migrations/1728778480_collections_snapshot.go
Normal file
@ -0,0 +1,731 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
jsonData := `[
|
||||||
|
{
|
||||||
|
"id": "z3p974ainxjqlvs",
|
||||||
|
"created": "2024-07-29 10:02:48.334Z",
|
||||||
|
"updated": "2024-10-08 06:50:56.637Z",
|
||||||
|
"name": "domains",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "iuaerpl2",
|
||||||
|
"name": "domain",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "ukkhuw85",
|
||||||
|
"name": "email",
|
||||||
|
"type": "email",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"exceptDomains": null,
|
||||||
|
"onlyDomains": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "v98eebqq",
|
||||||
|
"name": "crontab",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "alc8e9ow",
|
||||||
|
"name": "access",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "4yzbv8urny5ja1e",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "topsc9bj",
|
||||||
|
"name": "certUrl",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "vixgq072",
|
||||||
|
"name": "certStableUrl",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "g3a3sza5",
|
||||||
|
"name": "privateKey",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "gr6iouny",
|
||||||
|
"name": "certificate",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "tk6vnrmn",
|
||||||
|
"name": "issuerCertificate",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "sjo6ibse",
|
||||||
|
"name": "csr",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "x03n1bkj",
|
||||||
|
"name": "expiredAt",
|
||||||
|
"type": "date",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": "",
|
||||||
|
"max": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "srybpixz",
|
||||||
|
"name": "targetType",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"aliyun-oss",
|
||||||
|
"aliyun-cdn",
|
||||||
|
"aliyun-dcdn",
|
||||||
|
"ssh",
|
||||||
|
"webhook",
|
||||||
|
"tencent-cdn",
|
||||||
|
"qiniu-cdn",
|
||||||
|
"local"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "xy7yk0mb",
|
||||||
|
"name": "targetAccess",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "4yzbv8urny5ja1e",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "6jqeyggw",
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "bool",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "hdsjcchf",
|
||||||
|
"name": "deployed",
|
||||||
|
"type": "bool",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "aiya3rev",
|
||||||
|
"name": "rightnow",
|
||||||
|
"type": "bool",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "ixznmhzc",
|
||||||
|
"name": "lastDeployedAt",
|
||||||
|
"type": "date",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": "",
|
||||||
|
"max": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "ghtlkn5j",
|
||||||
|
"name": "lastDeployment",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "0a1o4e6sstp694f",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "zfnyj9he",
|
||||||
|
"name": "variables",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "1bspzuku",
|
||||||
|
"name": "group",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "teolp9pl72dxlxq",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "g65gfh7a",
|
||||||
|
"name": "nameservers",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "wwrzc3jo",
|
||||||
|
"name": "applyConfig",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "474iwy8r",
|
||||||
|
"name": "deployConfig",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
||||||
|
],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4yzbv8urny5ja1e",
|
||||||
|
"created": "2024-07-29 10:04:39.685Z",
|
||||||
|
"updated": "2024-10-11 13:55:13.777Z",
|
||||||
|
"name": "access",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "geeur58v",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "iql7jpwx",
|
||||||
|
"name": "config",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "hwy7m03o",
|
||||||
|
"name": "configType",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"aliyun",
|
||||||
|
"tencent",
|
||||||
|
"huaweicloud",
|
||||||
|
"qiniu",
|
||||||
|
"cloudflare",
|
||||||
|
"namesilo",
|
||||||
|
"godaddy",
|
||||||
|
"local",
|
||||||
|
"ssh",
|
||||||
|
"webhook"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "lr33hiwg",
|
||||||
|
"name": "deleted",
|
||||||
|
"type": "date",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": "",
|
||||||
|
"max": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "hsxcnlvd",
|
||||||
|
"name": "usage",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"apply",
|
||||||
|
"deploy",
|
||||||
|
"all"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "c8egzzwj",
|
||||||
|
"name": "group",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "teolp9pl72dxlxq",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||||
|
],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0a1o4e6sstp694f",
|
||||||
|
"created": "2024-07-30 06:30:27.801Z",
|
||||||
|
"updated": "2024-09-26 12:29:38.334Z",
|
||||||
|
"name": "deployments",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "farvlzk7",
|
||||||
|
"name": "domain",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "z3p974ainxjqlvs",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "jx5f69i3",
|
||||||
|
"name": "log",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "qbxdtg9q",
|
||||||
|
"name": "phase",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"check",
|
||||||
|
"apply",
|
||||||
|
"deploy"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "rglrp1hz",
|
||||||
|
"name": "phaseSuccess",
|
||||||
|
"type": "bool",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "lt1g1blu",
|
||||||
|
"name": "deployedAt",
|
||||||
|
"type": "date",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": "",
|
||||||
|
"max": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "wledpzgb",
|
||||||
|
"name": "wholeSuccess",
|
||||||
|
"type": "bool",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "_pb_users_auth_",
|
||||||
|
"created": "2024-09-12 13:09:54.234Z",
|
||||||
|
"updated": "2024-09-26 12:29:38.334Z",
|
||||||
|
"name": "users",
|
||||||
|
"type": "auth",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "users_name",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "users_avatar",
|
||||||
|
"name": "avatar",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp"
|
||||||
|
],
|
||||||
|
"thumbs": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"maxSize": 5242880,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": "id = @request.auth.id",
|
||||||
|
"viewRule": "id = @request.auth.id",
|
||||||
|
"createRule": "",
|
||||||
|
"updateRule": "id = @request.auth.id",
|
||||||
|
"deleteRule": "id = @request.auth.id",
|
||||||
|
"options": {
|
||||||
|
"allowEmailAuth": true,
|
||||||
|
"allowOAuth2Auth": true,
|
||||||
|
"allowUsernameAuth": true,
|
||||||
|
"exceptEmailDomains": null,
|
||||||
|
"manageRule": null,
|
||||||
|
"minPasswordLength": 8,
|
||||||
|
"onlyEmailDomains": null,
|
||||||
|
"onlyVerified": false,
|
||||||
|
"requireEmail": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dy6ccjb60spfy6p",
|
||||||
|
"created": "2024-09-12 23:12:21.677Z",
|
||||||
|
"updated": "2024-09-26 12:29:38.334Z",
|
||||||
|
"name": "settings",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "1tcmdsdf",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "f9wyhypi",
|
||||||
|
"name": "content",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||||
|
],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "teolp9pl72dxlxq",
|
||||||
|
"created": "2024-09-13 12:51:05.611Z",
|
||||||
|
"updated": "2024-09-26 12:29:38.334Z",
|
||||||
|
"name": "access_groups",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "7sajiv6i",
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "xp8admif",
|
||||||
|
"name": "access",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "4yzbv8urny5ja1e",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": null,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||||
|
],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
|
||||||
|
collections := []*models.Collection{}
|
||||||
|
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return daos.New(db).ImportCollections(collections, true, nil)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
16
node_modules/.package-lock.json
generated
vendored
Normal file
16
node_modules/.package-lock.json
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "certimate",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
node_modules/immer/LICENSE
generated
vendored
Normal file
21
node_modules/immer/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Michel Weststrate
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
1276
node_modules/immer/dist/cjs/immer.cjs.development.js
generated
vendored
Normal file
1276
node_modules/immer/dist/cjs/immer.cjs.development.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
node_modules/immer/dist/cjs/immer.cjs.development.js.map
generated
vendored
Normal file
1
node_modules/immer/dist/cjs/immer.cjs.development.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/immer/dist/cjs/immer.cjs.production.js
generated
vendored
Normal file
2
node_modules/immer/dist/cjs/immer.cjs.production.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/immer/dist/cjs/immer.cjs.production.js.map
generated
vendored
Normal file
1
node_modules/immer/dist/cjs/immer.cjs.production.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
8
node_modules/immer/dist/cjs/index.js
generated
vendored
Normal file
8
node_modules/immer/dist/cjs/index.js
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./immer.cjs.production.js')
|
||||||
|
} else {
|
||||||
|
module.exports = require('./immer.cjs.development.js')
|
||||||
|
}
|
111
node_modules/immer/dist/cjs/index.js.flow
generated
vendored
Normal file
111
node_modules/immer/dist/cjs/index.js.flow
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
export interface Patch {
|
||||||
|
op: "replace" | "remove" | "add";
|
||||||
|
path: (string | number)[];
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
|
||||||
|
|
||||||
|
type Base = {...} | Array<any>
|
||||||
|
interface IProduce {
|
||||||
|
/**
|
||||||
|
* Immer takes a state, and runs a function against it.
|
||||||
|
* That function can freely mutate the state, as it will create copies-on-write.
|
||||||
|
* This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
|
||||||
|
*
|
||||||
|
* If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
|
||||||
|
* any time it is called with the current state.
|
||||||
|
*
|
||||||
|
* @param currentState - the state to start with
|
||||||
|
* @param recipe - function that receives a proxy of the current state as first argument and which can be freely modified
|
||||||
|
* @param initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
|
||||||
|
* @returns The next state: a new state, or the current state if nothing was modified
|
||||||
|
*/
|
||||||
|
<S: Base>(
|
||||||
|
currentState: S,
|
||||||
|
recipe: (draftState: S) => S | void,
|
||||||
|
patchListener?: PatchListener
|
||||||
|
): S;
|
||||||
|
// curried invocations with initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
|
||||||
|
initialState: S
|
||||||
|
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => S;
|
||||||
|
// curried invocations without initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
|
||||||
|
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProduceWithPatches {
|
||||||
|
/**
|
||||||
|
* Like `produce`, but instead of just returning the new state,
|
||||||
|
* a tuple is returned with [nextState, patches, inversePatches]
|
||||||
|
*
|
||||||
|
* Like produce, this function supports currying
|
||||||
|
*/
|
||||||
|
<S: Base>(
|
||||||
|
currentState: S,
|
||||||
|
recipe: (draftState: S) => S | void
|
||||||
|
): [S, Patch[], Patch[]];
|
||||||
|
// curried invocations with initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
|
||||||
|
initialState: S
|
||||||
|
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
|
||||||
|
// curried invocations without initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
|
||||||
|
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare export var produce: IProduce
|
||||||
|
|
||||||
|
declare export var produceWithPatches: IProduceWithPatches
|
||||||
|
|
||||||
|
declare export var nothing: typeof undefined
|
||||||
|
|
||||||
|
declare export var immerable: Symbol
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically freezes any state trees generated by immer.
|
||||||
|
* This protects against accidental modifications of the state tree outside of an immer function.
|
||||||
|
* This comes with a performance impact, so it is recommended to disable this option in production.
|
||||||
|
* By default it is turned on during local development, and turned off in production.
|
||||||
|
*/
|
||||||
|
declare export function setAutoFreeze(autoFreeze: boolean): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass false to disable strict shallow copy.
|
||||||
|
*
|
||||||
|
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
|
||||||
|
*/
|
||||||
|
declare export function setUseStrictShallowCopy(useStrictShallowCopy: boolean): void
|
||||||
|
|
||||||
|
declare export function applyPatches<S>(state: S, patches: Patch[]): S
|
||||||
|
|
||||||
|
declare export function original<S>(value: S): S
|
||||||
|
|
||||||
|
declare export function current<S>(value: S): S
|
||||||
|
|
||||||
|
declare export function isDraft(value: any): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mutable draft from an (immutable) object / array.
|
||||||
|
* The draft can be modified until `finishDraft` is called
|
||||||
|
*/
|
||||||
|
declare export function createDraft<T>(base: T): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a draft that was created using `createDraft`,
|
||||||
|
* finalizes the draft into a new immutable object.
|
||||||
|
* Optionally a patch-listener can be provided to gather the patches that are needed to construct the object.
|
||||||
|
*/
|
||||||
|
declare export function finishDraft<T>(base: T, listener?: PatchListener): T
|
||||||
|
|
||||||
|
declare export function enableMapSet(): void
|
||||||
|
declare export function enablePatches(): void
|
||||||
|
|
||||||
|
declare export function freeze<T>(obj: T, freeze?: boolean): T
|
262
node_modules/immer/dist/immer.d.ts
generated
vendored
Normal file
262
node_modules/immer/dist/immer.d.ts
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
/**
|
||||||
|
* The sentinel value returned by producers to replace the draft with undefined.
|
||||||
|
*/
|
||||||
|
declare const NOTHING: unique symbol;
|
||||||
|
/**
|
||||||
|
* To let Immer treat your class instances as plain immutable objects
|
||||||
|
* (albeit with a custom prototype), you must define either an instance property
|
||||||
|
* or a static property on each of your custom classes.
|
||||||
|
*
|
||||||
|
* Otherwise, your class instance will never be drafted, which means it won't be
|
||||||
|
* safe to mutate in a produce callback.
|
||||||
|
*/
|
||||||
|
declare const DRAFTABLE: unique symbol;
|
||||||
|
|
||||||
|
type AnyFunc = (...args: any[]) => any;
|
||||||
|
type PrimitiveType = number | string | boolean;
|
||||||
|
/** Object types that should never be mapped */
|
||||||
|
type AtomicObject = Function | Promise<any> | Date | RegExp;
|
||||||
|
/**
|
||||||
|
* If the lib "ES2015.Collection" is not included in tsconfig.json,
|
||||||
|
* types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
|
||||||
|
* or `{}` (from the node types), in both cases entering an infinite recursion in
|
||||||
|
* pattern matching type mappings
|
||||||
|
* This type can be used to cast these types to `void` in these cases.
|
||||||
|
*/
|
||||||
|
type IfAvailable<T, Fallback = void> = true | false extends (T extends never ? true : false) ? Fallback : keyof T extends never ? Fallback : T;
|
||||||
|
/**
|
||||||
|
* These should also never be mapped but must be tested after regular Map and
|
||||||
|
* Set
|
||||||
|
*/
|
||||||
|
type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>;
|
||||||
|
type WritableDraft<T> = {
|
||||||
|
-readonly [K in keyof T]: Draft<T[K]>;
|
||||||
|
};
|
||||||
|
/** Convert a readonly type into a mutable type, if possible */
|
||||||
|
type Draft<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap<infer K, infer V> ? Map<Draft<K>, Draft<V>> : T extends ReadonlySet<infer V> ? Set<Draft<V>> : T extends WeakReferences ? T : T extends object ? WritableDraft<T> : T;
|
||||||
|
/** Convert a mutable type into a readonly type */
|
||||||
|
type Immutable<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<Immutable<V>> : T extends WeakReferences ? T : T extends object ? {
|
||||||
|
readonly [K in keyof T]: Immutable<T[K]>;
|
||||||
|
} : T;
|
||||||
|
interface Patch {
|
||||||
|
op: "replace" | "remove" | "add";
|
||||||
|
path: (string | number)[];
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void;
|
||||||
|
/**
|
||||||
|
* Utility types
|
||||||
|
*/
|
||||||
|
type PatchesTuple<T> = readonly [T, Patch[], Patch[]];
|
||||||
|
type ValidRecipeReturnType<State> = State | void | undefined | (State extends undefined ? typeof NOTHING : never);
|
||||||
|
type ReturnTypeWithPatchesIfNeeded<State, UsePatches extends boolean> = UsePatches extends true ? PatchesTuple<State> : State;
|
||||||
|
/**
|
||||||
|
* Core Producer inference
|
||||||
|
*/
|
||||||
|
type InferRecipeFromCurried<Curried> = Curried extends (base: infer State, ...rest: infer Args) => any ? ReturnType<Curried> extends State ? (draft: Draft<State>, ...rest: Args) => ValidRecipeReturnType<Draft<State>> : never : never;
|
||||||
|
type InferInitialStateFromCurried<Curried> = Curried extends (base: infer State, ...rest: any[]) => any ? State : never;
|
||||||
|
type InferCurriedFromRecipe<Recipe, UsePatches extends boolean> = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any ? ReturnType<Recipe> extends ValidRecipeReturnType<DraftState> ? (base: Immutable<DraftState>, ...args: RestArgs) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> : never : never;
|
||||||
|
type InferCurriedFromInitialStateAndRecipe<State, Recipe, UsePatches extends boolean> = Recipe extends (draft: Draft<State>, ...rest: infer RestArgs) => ValidRecipeReturnType<State> ? (base?: State | undefined, ...args: RestArgs) => ReturnTypeWithPatchesIfNeeded<State, UsePatches> : never;
|
||||||
|
/**
|
||||||
|
* The `produce` function takes a value and a "recipe function" (whose
|
||||||
|
* return value often depends on the base state). The recipe function is
|
||||||
|
* free to mutate its first argument however it wants. All mutations are
|
||||||
|
* only ever applied to a __copy__ of the base state.
|
||||||
|
*
|
||||||
|
* Pass only a function to create a "curried producer" which relieves you
|
||||||
|
* from passing the recipe function every time.
|
||||||
|
*
|
||||||
|
* Only plain objects and arrays are made mutable. All other objects are
|
||||||
|
* considered uncopyable.
|
||||||
|
*
|
||||||
|
* Note: This function is __bound__ to its `Immer` instance.
|
||||||
|
*
|
||||||
|
* @param {any} base - the initial state
|
||||||
|
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||||
|
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||||
|
* @returns {any} a new state, or the initial state if nothing was modified
|
||||||
|
*/
|
||||||
|
interface IProduce {
|
||||||
|
/** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */
|
||||||
|
<Curried>(recipe: InferRecipeFromCurried<Curried>, initialState?: InferInitialStateFromCurried<Curried>): Curried;
|
||||||
|
/** Curried producer that infers curried from the recipe */
|
||||||
|
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, false>;
|
||||||
|
/** Curried producer that infers curried from the State generic, which is explicitly passed in. */
|
||||||
|
<State>(recipe: (state: Draft<State>, initialState: State) => ValidRecipeReturnType<State>): (state?: State) => State;
|
||||||
|
<State, Args extends any[]>(recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>, initialState: State): (state?: State, ...args: Args) => State;
|
||||||
|
<State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (state: State) => State;
|
||||||
|
<State, Args extends any[]>(recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>): (state: State, ...args: Args) => State;
|
||||||
|
/** Curried producer with initial state, infers recipe from initial state */
|
||||||
|
<State, Recipe extends Function>(recipe: Recipe, initialState: State): InferCurriedFromInitialStateAndRecipe<State, Recipe, false>;
|
||||||
|
/** Normal producer */
|
||||||
|
<Base, D = Draft<Base>>(// By using a default inferred D, rather than Draft<Base> in the recipe, we can override it.
|
||||||
|
base: Base, recipe: (draft: D) => ValidRecipeReturnType<D>, listener?: PatchListener): Base;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Like `produce`, but instead of just returning the new state,
|
||||||
|
* a tuple is returned with [nextState, patches, inversePatches]
|
||||||
|
*
|
||||||
|
* Like produce, this function supports currying
|
||||||
|
*/
|
||||||
|
interface IProduceWithPatches {
|
||||||
|
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, true>;
|
||||||
|
<State, Recipe extends Function>(recipe: Recipe, initialState: State): InferCurriedFromInitialStateAndRecipe<State, Recipe, true>;
|
||||||
|
<Base, D = Draft<Base>>(base: Base, recipe: (draft: D) => ValidRecipeReturnType<D>, listener?: PatchListener): PatchesTuple<Base>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The type for `recipe function`
|
||||||
|
*/
|
||||||
|
type Producer<T> = (draft: Draft<T>) => ValidRecipeReturnType<Draft<T>>;
|
||||||
|
|
||||||
|
type Objectish = AnyObject | AnyArray | AnyMap | AnySet;
|
||||||
|
type AnyObject = {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
type AnyArray = Array<any>;
|
||||||
|
type AnySet = Set<any>;
|
||||||
|
type AnyMap = Map<any, any>;
|
||||||
|
|
||||||
|
/** Returns true if the given value is an Immer draft */
|
||||||
|
declare function isDraft(value: any): boolean;
|
||||||
|
/** Returns true if the given value can be drafted by Immer */
|
||||||
|
declare function isDraftable(value: any): boolean;
|
||||||
|
/** Get the underlying object that is represented by the given draft */
|
||||||
|
declare function original<T>(value: T): T | undefined;
|
||||||
|
/**
|
||||||
|
* Freezes draftable objects. Returns the original object.
|
||||||
|
* By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* @param deep
|
||||||
|
*/
|
||||||
|
declare function freeze<T>(obj: T, deep?: boolean): T;
|
||||||
|
|
||||||
|
interface ProducersFns {
|
||||||
|
produce: IProduce;
|
||||||
|
produceWithPatches: IProduceWithPatches;
|
||||||
|
}
|
||||||
|
type StrictMode = boolean | "class_only";
|
||||||
|
declare class Immer implements ProducersFns {
|
||||||
|
autoFreeze_: boolean;
|
||||||
|
useStrictShallowCopy_: StrictMode;
|
||||||
|
constructor(config?: {
|
||||||
|
autoFreeze?: boolean;
|
||||||
|
useStrictShallowCopy?: StrictMode;
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* The `produce` function takes a value and a "recipe function" (whose
|
||||||
|
* return value often depends on the base state). The recipe function is
|
||||||
|
* free to mutate its first argument however it wants. All mutations are
|
||||||
|
* only ever applied to a __copy__ of the base state.
|
||||||
|
*
|
||||||
|
* Pass only a function to create a "curried producer" which relieves you
|
||||||
|
* from passing the recipe function every time.
|
||||||
|
*
|
||||||
|
* Only plain objects and arrays are made mutable. All other objects are
|
||||||
|
* considered uncopyable.
|
||||||
|
*
|
||||||
|
* Note: This function is __bound__ to its `Immer` instance.
|
||||||
|
*
|
||||||
|
* @param {any} base - the initial state
|
||||||
|
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||||
|
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||||
|
* @returns {any} a new state, or the initial state if nothing was modified
|
||||||
|
*/
|
||||||
|
produce: IProduce;
|
||||||
|
produceWithPatches: IProduceWithPatches;
|
||||||
|
createDraft<T extends Objectish>(base: T): Draft<T>;
|
||||||
|
finishDraft<D extends Draft<any>>(draft: D, patchListener?: PatchListener): D extends Draft<infer T> ? T : never;
|
||||||
|
/**
|
||||||
|
* Pass true to automatically freeze all copies created by Immer.
|
||||||
|
*
|
||||||
|
* By default, auto-freezing is enabled.
|
||||||
|
*/
|
||||||
|
setAutoFreeze(value: boolean): void;
|
||||||
|
/**
|
||||||
|
* Pass true to enable strict shallow copy.
|
||||||
|
*
|
||||||
|
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
|
||||||
|
*/
|
||||||
|
setUseStrictShallowCopy(value: StrictMode): void;
|
||||||
|
applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */
|
||||||
|
declare function current<T>(value: T): T;
|
||||||
|
|
||||||
|
declare function enablePatches(): void;
|
||||||
|
|
||||||
|
declare function enableMapSet(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `produce` function takes a value and a "recipe function" (whose
|
||||||
|
* return value often depends on the base state). The recipe function is
|
||||||
|
* free to mutate its first argument however it wants. All mutations are
|
||||||
|
* only ever applied to a __copy__ of the base state.
|
||||||
|
*
|
||||||
|
* Pass only a function to create a "curried producer" which relieves you
|
||||||
|
* from passing the recipe function every time.
|
||||||
|
*
|
||||||
|
* Only plain objects and arrays are made mutable. All other objects are
|
||||||
|
* considered uncopyable.
|
||||||
|
*
|
||||||
|
* Note: This function is __bound__ to its `Immer` instance.
|
||||||
|
*
|
||||||
|
* @param {any} base - the initial state
|
||||||
|
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||||
|
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||||
|
* @returns {any} a new state, or the initial state if nothing was modified
|
||||||
|
*/
|
||||||
|
declare const produce: IProduce;
|
||||||
|
/**
|
||||||
|
* Like `produce`, but `produceWithPatches` always returns a tuple
|
||||||
|
* [nextState, patches, inversePatches] (instead of just the next state)
|
||||||
|
*/
|
||||||
|
declare const produceWithPatches: IProduceWithPatches;
|
||||||
|
/**
|
||||||
|
* Pass true to automatically freeze all copies created by Immer.
|
||||||
|
*
|
||||||
|
* Always freeze by default, even in production mode
|
||||||
|
*/
|
||||||
|
declare const setAutoFreeze: (value: boolean) => void;
|
||||||
|
/**
|
||||||
|
* Pass true to enable strict shallow copy.
|
||||||
|
*
|
||||||
|
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
|
||||||
|
*/
|
||||||
|
declare const setUseStrictShallowCopy: (value: StrictMode) => void;
|
||||||
|
/**
|
||||||
|
* Apply an array of Immer patches to the first argument.
|
||||||
|
*
|
||||||
|
* This function is a producer, which means copy-on-write is in effect.
|
||||||
|
*/
|
||||||
|
declare const applyPatches: <T extends Objectish>(base: T, patches: readonly Patch[]) => T;
|
||||||
|
/**
|
||||||
|
* Create an Immer draft from the given base state, which may be a draft itself.
|
||||||
|
* The draft can be modified until you finalize it with the `finishDraft` function.
|
||||||
|
*/
|
||||||
|
declare const createDraft: <T extends Objectish>(base: T) => Draft<T>;
|
||||||
|
/**
|
||||||
|
* Finalize an Immer draft from a `createDraft` call, returning the base state
|
||||||
|
* (if no changes were made) or a modified copy. The draft must *not* be
|
||||||
|
* mutated afterwards.
|
||||||
|
*
|
||||||
|
* Pass a function as the 2nd argument to generate Immer patches based on the
|
||||||
|
* changes that were made.
|
||||||
|
*/
|
||||||
|
declare const finishDraft: <D extends unknown>(draft: D, patchListener?: PatchListener | undefined) => D extends Draft<infer T> ? T : never;
|
||||||
|
/**
|
||||||
|
* This function is actually a no-op, but can be used to cast an immutable type
|
||||||
|
* to an draft type and make TypeScript happy
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
declare function castDraft<T>(value: T): Draft<T>;
|
||||||
|
/**
|
||||||
|
* This function is actually a no-op, but can be used to cast a mutable type
|
||||||
|
* to an immutable type and make TypeScript happy
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
declare function castImmutable<T>(value: T): Immutable<T>;
|
||||||
|
|
||||||
|
export { Draft, Immer, Immutable, Objectish, Patch, PatchListener, Producer, StrictMode, WritableDraft, applyPatches, castDraft, castImmutable, createDraft, current, enableMapSet, enablePatches, finishDraft, freeze, DRAFTABLE as immerable, isDraft, isDraftable, NOTHING as nothing, original, produce, produceWithPatches, setAutoFreeze, setUseStrictShallowCopy };
|
1250
node_modules/immer/dist/immer.legacy-esm.js
generated
vendored
Normal file
1250
node_modules/immer/dist/immer.legacy-esm.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
node_modules/immer/dist/immer.legacy-esm.js.map
generated
vendored
Normal file
1
node_modules/immer/dist/immer.legacy-esm.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1231
node_modules/immer/dist/immer.mjs
generated
vendored
Normal file
1231
node_modules/immer/dist/immer.mjs
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
node_modules/immer/dist/immer.mjs.map
generated
vendored
Normal file
1
node_modules/immer/dist/immer.mjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/immer/dist/immer.production.mjs
generated
vendored
Normal file
2
node_modules/immer/dist/immer.production.mjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/immer/dist/immer.production.mjs.map
generated
vendored
Normal file
1
node_modules/immer/dist/immer.production.mjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
87
node_modules/immer/package.json
generated
vendored
Normal file
87
node_modules/immer/package.json
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"name": "immer",
|
||||||
|
"version": "10.1.1",
|
||||||
|
"description": "Create your next immutable state by mutating the current one",
|
||||||
|
"main": "./dist/cjs/index.js",
|
||||||
|
"module": "./dist/immer.legacy-esm.js",
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"types": "./dist/immer.d.ts",
|
||||||
|
"import": "./dist/immer.mjs",
|
||||||
|
"require": "./dist/cjs/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jsnext:main": "dist/immer.mjs",
|
||||||
|
"react-native": "./dist/immer.legacy-esm.js",
|
||||||
|
"source": "src/immer.ts",
|
||||||
|
"types": "./dist/immer.d.ts",
|
||||||
|
"sideEffects": false,
|
||||||
|
"scripts": {
|
||||||
|
"pretest": "yarn build",
|
||||||
|
"test": "jest && yarn test:build && yarn test:flow",
|
||||||
|
"test:perf": "cd __performance_tests__ && node add-data.mjs && node todo.mjs && node incremental.mjs && node large-obj.mjs",
|
||||||
|
"test:flow": "yarn flow check __tests__/flow",
|
||||||
|
"test:build": "NODE_ENV='production' yarn jest --config jest.config.build.js",
|
||||||
|
"watch": "jest --watch",
|
||||||
|
"coverage": "jest --coverage",
|
||||||
|
"coveralls": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage",
|
||||||
|
"build": "tsup",
|
||||||
|
"publish-docs": "cd website && GIT_USER=mweststrate USE_SSH=true yarn docusaurus deploy",
|
||||||
|
"start": "cd website && yarn start",
|
||||||
|
"test:size": "yarn build && yarn import-size --report . produce enableMapSet enablePatches",
|
||||||
|
"test:sizequick": "yarn build && yarn import-size . produce"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "pretty-quick --staged"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/immerjs/immer.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"immutable",
|
||||||
|
"mutable",
|
||||||
|
"copy-on-write"
|
||||||
|
],
|
||||||
|
"author": "Michel Weststrate <info@michel.codes>",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/immerjs/immer/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/immerjs/immer#readme",
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"compat",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.21.3",
|
||||||
|
"@types/jest": "^25.1.2",
|
||||||
|
"coveralls": "^3.0.0",
|
||||||
|
"cpx2": "^3.0.0",
|
||||||
|
"deep-freeze": "^0.0.1",
|
||||||
|
"flow-bin": "^0.123.0",
|
||||||
|
"husky": "^1.2.0",
|
||||||
|
"immutable": "^3.8.2",
|
||||||
|
"import-size": "^1.0.2",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"prettier": "1.19.1",
|
||||||
|
"pretty-quick": "^1.8.0",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
|
"seamless-immutable": "^7.1.3",
|
||||||
|
"semantic-release": "^17.0.2",
|
||||||
|
"ts-jest": "^29.0.0",
|
||||||
|
"tsup": "^6.7.0",
|
||||||
|
"typescript": "^5.0.2"
|
||||||
|
}
|
||||||
|
}
|
33
node_modules/immer/readme.md
generated
vendored
Normal file
33
node_modules/immer/readme.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<img src="images/immer-logo.svg" height="200px" align="right"/>
|
||||||
|
|
||||||
|
# Immer
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/immer) [](https://github.com/immerjs/immer/actions?query=branch%3Amain) [](https://coveralls.io/github/immerjs/immer?branch=main) [](https://github.com/prettier/prettier) [](#backers) [](#sponsors) [](https://gitpod.io/#https://github.com/immerjs/immer)
|
||||||
|
|
||||||
|
_Create the next immutable state tree by simply modifying the current tree_
|
||||||
|
|
||||||
|
Winner of the "Breakthrough of the year" [React open source award](https://osawards.com/react/) and "Most impactful contribution" [JavaScript open source award](https://osawards.com/javascript/) in 2019
|
||||||
|
|
||||||
|
## Contribute using one-click online setup
|
||||||
|
|
||||||
|
You can use Gitpod (a free online VS Code like IDE) for contributing online. With a single click it will launch a workspace and automatically:
|
||||||
|
|
||||||
|
- clone the immer repo.
|
||||||
|
- install the dependencies.
|
||||||
|
- run `yarn run start`.
|
||||||
|
|
||||||
|
so that you can start coding straight away.
|
||||||
|
|
||||||
|
[](https://gitpod.io/from-referrer/)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The documentation of this package is hosted at https://immerjs.github.io/immer/
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Did Immer make a difference to your project? Join the open collective at https://opencollective.com/immer!
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
|
||||||
|
https://github.com/immerjs/immer/releases
|
40
node_modules/immer/src/core/current.ts
generated
vendored
Normal file
40
node_modules/immer/src/core/current.ts
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
die,
|
||||||
|
isDraft,
|
||||||
|
shallowCopy,
|
||||||
|
each,
|
||||||
|
DRAFT_STATE,
|
||||||
|
set,
|
||||||
|
ImmerState,
|
||||||
|
isDraftable,
|
||||||
|
isFrozen
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */
|
||||||
|
export function current<T>(value: T): T
|
||||||
|
export function current(value: any): any {
|
||||||
|
if (!isDraft(value)) die(10, value)
|
||||||
|
return currentImpl(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentImpl(value: any): any {
|
||||||
|
if (!isDraftable(value) || isFrozen(value)) return value
|
||||||
|
const state: ImmerState | undefined = value[DRAFT_STATE]
|
||||||
|
let copy: any
|
||||||
|
if (state) {
|
||||||
|
if (!state.modified_) return state.base_
|
||||||
|
// Optimization: avoid generating new drafts during copying
|
||||||
|
state.finalized_ = true
|
||||||
|
copy = shallowCopy(value, state.scope_.immer_.useStrictShallowCopy_)
|
||||||
|
} else {
|
||||||
|
copy = shallowCopy(value, true)
|
||||||
|
}
|
||||||
|
// recurse
|
||||||
|
each(copy, (key, childValue) => {
|
||||||
|
set(copy, key, currentImpl(childValue))
|
||||||
|
})
|
||||||
|
if (state) {
|
||||||
|
state.finalized_ = false
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
165
node_modules/immer/src/core/finalize.ts
generated
vendored
Normal file
165
node_modules/immer/src/core/finalize.ts
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import {
|
||||||
|
ImmerScope,
|
||||||
|
DRAFT_STATE,
|
||||||
|
isDraftable,
|
||||||
|
NOTHING,
|
||||||
|
PatchPath,
|
||||||
|
each,
|
||||||
|
has,
|
||||||
|
freeze,
|
||||||
|
ImmerState,
|
||||||
|
isDraft,
|
||||||
|
SetState,
|
||||||
|
set,
|
||||||
|
ArchType,
|
||||||
|
getPlugin,
|
||||||
|
die,
|
||||||
|
revokeScope,
|
||||||
|
isFrozen
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
export function processResult(result: any, scope: ImmerScope) {
|
||||||
|
scope.unfinalizedDrafts_ = scope.drafts_.length
|
||||||
|
const baseDraft = scope.drafts_![0]
|
||||||
|
const isReplaced = result !== undefined && result !== baseDraft
|
||||||
|
if (isReplaced) {
|
||||||
|
if (baseDraft[DRAFT_STATE].modified_) {
|
||||||
|
revokeScope(scope)
|
||||||
|
die(4)
|
||||||
|
}
|
||||||
|
if (isDraftable(result)) {
|
||||||
|
// Finalize the result in case it contains (or is) a subset of the draft.
|
||||||
|
result = finalize(scope, result)
|
||||||
|
if (!scope.parent_) maybeFreeze(scope, result)
|
||||||
|
}
|
||||||
|
if (scope.patches_) {
|
||||||
|
getPlugin("Patches").generateReplacementPatches_(
|
||||||
|
baseDraft[DRAFT_STATE].base_,
|
||||||
|
result,
|
||||||
|
scope.patches_,
|
||||||
|
scope.inversePatches_!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Finalize the base draft.
|
||||||
|
result = finalize(scope, baseDraft, [])
|
||||||
|
}
|
||||||
|
revokeScope(scope)
|
||||||
|
if (scope.patches_) {
|
||||||
|
scope.patchListener_!(scope.patches_, scope.inversePatches_!)
|
||||||
|
}
|
||||||
|
return result !== NOTHING ? result : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
|
||||||
|
// Don't recurse in tho recursive data structures
|
||||||
|
if (isFrozen(value)) return value
|
||||||
|
|
||||||
|
const state: ImmerState = value[DRAFT_STATE]
|
||||||
|
// A plain object, might need freezing, might contain drafts
|
||||||
|
if (!state) {
|
||||||
|
each(value, (key, childValue) =>
|
||||||
|
finalizeProperty(rootScope, state, value, key, childValue, path)
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// Never finalize drafts owned by another scope.
|
||||||
|
if (state.scope_ !== rootScope) return value
|
||||||
|
// Unmodified draft, return the (frozen) original
|
||||||
|
if (!state.modified_) {
|
||||||
|
maybeFreeze(rootScope, state.base_, true)
|
||||||
|
return state.base_
|
||||||
|
}
|
||||||
|
// Not finalized yet, let's do that now
|
||||||
|
if (!state.finalized_) {
|
||||||
|
state.finalized_ = true
|
||||||
|
state.scope_.unfinalizedDrafts_--
|
||||||
|
const result = state.copy_
|
||||||
|
// Finalize all children of the copy
|
||||||
|
// For sets we clone before iterating, otherwise we can get in endless loop due to modifying during iteration, see #628
|
||||||
|
// To preserve insertion order in all cases we then clear the set
|
||||||
|
// And we let finalizeProperty know it needs to re-add non-draft children back to the target
|
||||||
|
let resultEach = result
|
||||||
|
let isSet = false
|
||||||
|
if (state.type_ === ArchType.Set) {
|
||||||
|
resultEach = new Set(result)
|
||||||
|
result.clear()
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
each(resultEach, (key, childValue) =>
|
||||||
|
finalizeProperty(rootScope, state, result, key, childValue, path, isSet)
|
||||||
|
)
|
||||||
|
// everything inside is frozen, we can freeze here
|
||||||
|
maybeFreeze(rootScope, result, false)
|
||||||
|
// first time finalizing, let's create those patches
|
||||||
|
if (path && rootScope.patches_) {
|
||||||
|
getPlugin("Patches").generatePatches_(
|
||||||
|
state,
|
||||||
|
path,
|
||||||
|
rootScope.patches_,
|
||||||
|
rootScope.inversePatches_!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state.copy_
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeProperty(
|
||||||
|
rootScope: ImmerScope,
|
||||||
|
parentState: undefined | ImmerState,
|
||||||
|
targetObject: any,
|
||||||
|
prop: string | number,
|
||||||
|
childValue: any,
|
||||||
|
rootPath?: PatchPath,
|
||||||
|
targetIsSet?: boolean
|
||||||
|
) {
|
||||||
|
if (process.env.NODE_ENV !== "production" && childValue === targetObject)
|
||||||
|
die(5)
|
||||||
|
if (isDraft(childValue)) {
|
||||||
|
const path =
|
||||||
|
rootPath &&
|
||||||
|
parentState &&
|
||||||
|
parentState!.type_ !== ArchType.Set && // Set objects are atomic since they have no keys.
|
||||||
|
!has((parentState as Exclude<ImmerState, SetState>).assigned_!, prop) // Skip deep patches for assigned keys.
|
||||||
|
? rootPath!.concat(prop)
|
||||||
|
: undefined
|
||||||
|
// Drafts owned by `scope` are finalized here.
|
||||||
|
const res = finalize(rootScope, childValue, path)
|
||||||
|
set(targetObject, prop, res)
|
||||||
|
// Drafts from another scope must prevented to be frozen
|
||||||
|
// if we got a draft back from finalize, we're in a nested produce and shouldn't freeze
|
||||||
|
if (isDraft(res)) {
|
||||||
|
rootScope.canAutoFreeze_ = false
|
||||||
|
} else return
|
||||||
|
} else if (targetIsSet) {
|
||||||
|
targetObject.add(childValue)
|
||||||
|
}
|
||||||
|
// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
|
||||||
|
if (isDraftable(childValue) && !isFrozen(childValue)) {
|
||||||
|
if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) {
|
||||||
|
// optimization: if an object is not a draft, and we don't have to
|
||||||
|
// deepfreeze everything, and we are sure that no drafts are left in the remaining object
|
||||||
|
// cause we saw and finalized all drafts already; we can stop visiting the rest of the tree.
|
||||||
|
// This benefits especially adding large data tree's without further processing.
|
||||||
|
// See add-data.js perf test
|
||||||
|
return
|
||||||
|
}
|
||||||
|
finalize(rootScope, childValue)
|
||||||
|
// Immer deep freezes plain objects, so if there is no parent state, we freeze as well
|
||||||
|
// Per #590, we never freeze symbolic properties. Just to make sure don't accidentally interfere
|
||||||
|
// with other frameworks.
|
||||||
|
if (
|
||||||
|
(!parentState || !parentState.scope_.parent_) &&
|
||||||
|
typeof prop !== "symbol" &&
|
||||||
|
Object.prototype.propertyIsEnumerable.call(targetObject, prop)
|
||||||
|
)
|
||||||
|
maybeFreeze(rootScope, childValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeFreeze(scope: ImmerScope, value: any, deep = false) {
|
||||||
|
// we never freeze for a non-root scope; as it would prevent pruning for drafts inside wrapping objects
|
||||||
|
if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) {
|
||||||
|
freeze(value, deep)
|
||||||
|
}
|
||||||
|
}
|
218
node_modules/immer/src/core/immerClass.ts
generated
vendored
Normal file
218
node_modules/immer/src/core/immerClass.ts
generated
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import {
|
||||||
|
IProduceWithPatches,
|
||||||
|
IProduce,
|
||||||
|
ImmerState,
|
||||||
|
Drafted,
|
||||||
|
isDraftable,
|
||||||
|
processResult,
|
||||||
|
Patch,
|
||||||
|
Objectish,
|
||||||
|
DRAFT_STATE,
|
||||||
|
Draft,
|
||||||
|
PatchListener,
|
||||||
|
isDraft,
|
||||||
|
isMap,
|
||||||
|
isSet,
|
||||||
|
createProxyProxy,
|
||||||
|
getPlugin,
|
||||||
|
die,
|
||||||
|
enterScope,
|
||||||
|
revokeScope,
|
||||||
|
leaveScope,
|
||||||
|
usePatchesInScope,
|
||||||
|
getCurrentScope,
|
||||||
|
NOTHING,
|
||||||
|
freeze,
|
||||||
|
current
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
interface ProducersFns {
|
||||||
|
produce: IProduce
|
||||||
|
produceWithPatches: IProduceWithPatches
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StrictMode = boolean | "class_only";
|
||||||
|
|
||||||
|
export class Immer implements ProducersFns {
|
||||||
|
autoFreeze_: boolean = true
|
||||||
|
useStrictShallowCopy_: StrictMode = false
|
||||||
|
|
||||||
|
constructor(config?: {
|
||||||
|
autoFreeze?: boolean
|
||||||
|
useStrictShallowCopy?: StrictMode
|
||||||
|
}) {
|
||||||
|
if (typeof config?.autoFreeze === "boolean")
|
||||||
|
this.setAutoFreeze(config!.autoFreeze)
|
||||||
|
if (typeof config?.useStrictShallowCopy === "boolean")
|
||||||
|
this.setUseStrictShallowCopy(config!.useStrictShallowCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `produce` function takes a value and a "recipe function" (whose
|
||||||
|
* return value often depends on the base state). The recipe function is
|
||||||
|
* free to mutate its first argument however it wants. All mutations are
|
||||||
|
* only ever applied to a __copy__ of the base state.
|
||||||
|
*
|
||||||
|
* Pass only a function to create a "curried producer" which relieves you
|
||||||
|
* from passing the recipe function every time.
|
||||||
|
*
|
||||||
|
* Only plain objects and arrays are made mutable. All other objects are
|
||||||
|
* considered uncopyable.
|
||||||
|
*
|
||||||
|
* Note: This function is __bound__ to its `Immer` instance.
|
||||||
|
*
|
||||||
|
* @param {any} base - the initial state
|
||||||
|
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||||
|
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||||
|
* @returns {any} a new state, or the initial state if nothing was modified
|
||||||
|
*/
|
||||||
|
produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
|
||||||
|
// curried invocation
|
||||||
|
if (typeof base === "function" && typeof recipe !== "function") {
|
||||||
|
const defaultBase = recipe
|
||||||
|
recipe = base
|
||||||
|
|
||||||
|
const self = this
|
||||||
|
return function curriedProduce(
|
||||||
|
this: any,
|
||||||
|
base = defaultBase,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof recipe !== "function") die(6)
|
||||||
|
if (patchListener !== undefined && typeof patchListener !== "function")
|
||||||
|
die(7)
|
||||||
|
|
||||||
|
let result
|
||||||
|
|
||||||
|
// Only plain objects, arrays, and "immerable classes" are drafted.
|
||||||
|
if (isDraftable(base)) {
|
||||||
|
const scope = enterScope(this)
|
||||||
|
const proxy = createProxy(base, undefined)
|
||||||
|
let hasError = true
|
||||||
|
try {
|
||||||
|
result = recipe(proxy)
|
||||||
|
hasError = false
|
||||||
|
} finally {
|
||||||
|
// finally instead of catch + rethrow better preserves original stack
|
||||||
|
if (hasError) revokeScope(scope)
|
||||||
|
else leaveScope(scope)
|
||||||
|
}
|
||||||
|
usePatchesInScope(scope, patchListener)
|
||||||
|
return processResult(result, scope)
|
||||||
|
} else if (!base || typeof base !== "object") {
|
||||||
|
result = recipe(base)
|
||||||
|
if (result === undefined) result = base
|
||||||
|
if (result === NOTHING) result = undefined
|
||||||
|
if (this.autoFreeze_) freeze(result, true)
|
||||||
|
if (patchListener) {
|
||||||
|
const p: Patch[] = []
|
||||||
|
const ip: Patch[] = []
|
||||||
|
getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
|
||||||
|
patchListener(p, ip)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} else die(1, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
|
||||||
|
// curried invocation
|
||||||
|
if (typeof base === "function") {
|
||||||
|
return (state: any, ...args: any[]) =>
|
||||||
|
this.produceWithPatches(state, (draft: any) => base(draft, ...args))
|
||||||
|
}
|
||||||
|
|
||||||
|
let patches: Patch[], inversePatches: Patch[]
|
||||||
|
const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => {
|
||||||
|
patches = p
|
||||||
|
inversePatches = ip
|
||||||
|
})
|
||||||
|
return [result, patches!, inversePatches!]
|
||||||
|
}
|
||||||
|
|
||||||
|
createDraft<T extends Objectish>(base: T): Draft<T> {
|
||||||
|
if (!isDraftable(base)) die(8)
|
||||||
|
if (isDraft(base)) base = current(base)
|
||||||
|
const scope = enterScope(this)
|
||||||
|
const proxy = createProxy(base, undefined)
|
||||||
|
proxy[DRAFT_STATE].isManual_ = true
|
||||||
|
leaveScope(scope)
|
||||||
|
return proxy as any
|
||||||
|
}
|
||||||
|
|
||||||
|
finishDraft<D extends Draft<any>>(
|
||||||
|
draft: D,
|
||||||
|
patchListener?: PatchListener
|
||||||
|
): D extends Draft<infer T> ? T : never {
|
||||||
|
const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
|
||||||
|
if (!state || !state.isManual_) die(9)
|
||||||
|
const {scope_: scope} = state
|
||||||
|
usePatchesInScope(scope, patchListener)
|
||||||
|
return processResult(undefined, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass true to automatically freeze all copies created by Immer.
|
||||||
|
*
|
||||||
|
* By default, auto-freezing is enabled.
|
||||||
|
*/
|
||||||
|
setAutoFreeze(value: boolean) {
|
||||||
|
this.autoFreeze_ = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass true to enable strict shallow copy.
|
||||||
|
*
|
||||||
|
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
|
||||||
|
*/
|
||||||
|
setUseStrictShallowCopy(value: StrictMode) {
|
||||||
|
this.useStrictShallowCopy_ = value
|
||||||
|
}
|
||||||
|
|
||||||
|
applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T {
|
||||||
|
// If a patch replaces the entire state, take that replacement as base
|
||||||
|
// before applying patches
|
||||||
|
let i: number
|
||||||
|
for (i = patches.length - 1; i >= 0; i--) {
|
||||||
|
const patch = patches[i]
|
||||||
|
if (patch.path.length === 0 && patch.op === "replace") {
|
||||||
|
base = patch.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there was a patch that replaced the entire state, start from the
|
||||||
|
// patch after that.
|
||||||
|
if (i > -1) {
|
||||||
|
patches = patches.slice(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyPatchesImpl = getPlugin("Patches").applyPatches_
|
||||||
|
if (isDraft(base)) {
|
||||||
|
// N.B: never hits if some patch a replacement, patches are never drafts
|
||||||
|
return applyPatchesImpl(base, patches)
|
||||||
|
}
|
||||||
|
// Otherwise, produce a copy of the base state.
|
||||||
|
return this.produce(base, (draft: Drafted) =>
|
||||||
|
applyPatchesImpl(draft, patches)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProxy<T extends Objectish>(
|
||||||
|
value: T,
|
||||||
|
parent?: ImmerState
|
||||||
|
): Drafted<T, ImmerState> {
|
||||||
|
// precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
|
||||||
|
const draft: Drafted = isMap(value)
|
||||||
|
? getPlugin("MapSet").proxyMap_(value, parent)
|
||||||
|
: isSet(value)
|
||||||
|
? getPlugin("MapSet").proxySet_(value, parent)
|
||||||
|
: createProxyProxy(value, parent)
|
||||||
|
|
||||||
|
const scope = parent ? parent.scope_ : getCurrentScope()
|
||||||
|
scope.drafts_.push(draft)
|
||||||
|
return draft
|
||||||
|
}
|
292
node_modules/immer/src/core/proxy.ts
generated
vendored
Normal file
292
node_modules/immer/src/core/proxy.ts
generated
vendored
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
import {
|
||||||
|
each,
|
||||||
|
has,
|
||||||
|
is,
|
||||||
|
isDraftable,
|
||||||
|
shallowCopy,
|
||||||
|
latest,
|
||||||
|
ImmerBaseState,
|
||||||
|
ImmerState,
|
||||||
|
Drafted,
|
||||||
|
AnyObject,
|
||||||
|
AnyArray,
|
||||||
|
Objectish,
|
||||||
|
getCurrentScope,
|
||||||
|
getPrototypeOf,
|
||||||
|
DRAFT_STATE,
|
||||||
|
die,
|
||||||
|
createProxy,
|
||||||
|
ArchType,
|
||||||
|
ImmerScope
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
interface ProxyBaseState extends ImmerBaseState {
|
||||||
|
assigned_: {
|
||||||
|
[property: string]: boolean
|
||||||
|
}
|
||||||
|
parent_?: ImmerState
|
||||||
|
revoke_(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProxyObjectState extends ProxyBaseState {
|
||||||
|
type_: ArchType.Object
|
||||||
|
base_: any
|
||||||
|
copy_: any
|
||||||
|
draft_: Drafted<AnyObject, ProxyObjectState>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProxyArrayState extends ProxyBaseState {
|
||||||
|
type_: ArchType.Array
|
||||||
|
base_: AnyArray
|
||||||
|
copy_: AnyArray | null
|
||||||
|
draft_: Drafted<AnyArray, ProxyArrayState>
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyState = ProxyObjectState | ProxyArrayState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new draft of the `base` object.
|
||||||
|
*
|
||||||
|
* The second argument is the parent draft-state (used internally).
|
||||||
|
*/
|
||||||
|
export function createProxyProxy<T extends Objectish>(
|
||||||
|
base: T,
|
||||||
|
parent?: ImmerState
|
||||||
|
): Drafted<T, ProxyState> {
|
||||||
|
const isArray = Array.isArray(base)
|
||||||
|
const state: ProxyState = {
|
||||||
|
type_: isArray ? ArchType.Array : (ArchType.Object as any),
|
||||||
|
// Track which produce call this is associated with.
|
||||||
|
scope_: parent ? parent.scope_ : getCurrentScope()!,
|
||||||
|
// True for both shallow and deep changes.
|
||||||
|
modified_: false,
|
||||||
|
// Used during finalization.
|
||||||
|
finalized_: false,
|
||||||
|
// Track which properties have been assigned (true) or deleted (false).
|
||||||
|
assigned_: {},
|
||||||
|
// The parent draft state.
|
||||||
|
parent_: parent,
|
||||||
|
// The base state.
|
||||||
|
base_: base,
|
||||||
|
// The base proxy.
|
||||||
|
draft_: null as any, // set below
|
||||||
|
// The base copy with any updated values.
|
||||||
|
copy_: null,
|
||||||
|
// Called by the `produce` function.
|
||||||
|
revoke_: null as any,
|
||||||
|
isManual_: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// the traps must target something, a bit like the 'real' base.
|
||||||
|
// but also, we need to be able to determine from the target what the relevant state is
|
||||||
|
// (to avoid creating traps per instance to capture the state in closure,
|
||||||
|
// and to avoid creating weird hidden properties as well)
|
||||||
|
// So the trick is to use 'state' as the actual 'target'! (and make sure we intercept everything)
|
||||||
|
// Note that in the case of an array, we put the state in an array to have better Reflect defaults ootb
|
||||||
|
let target: T = state as any
|
||||||
|
let traps: ProxyHandler<object | Array<any>> = objectTraps
|
||||||
|
if (isArray) {
|
||||||
|
target = [state] as any
|
||||||
|
traps = arrayTraps
|
||||||
|
}
|
||||||
|
|
||||||
|
const {revoke, proxy} = Proxy.revocable(target, traps)
|
||||||
|
state.draft_ = proxy as any
|
||||||
|
state.revoke_ = revoke
|
||||||
|
return proxy as any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object drafts
|
||||||
|
*/
|
||||||
|
export const objectTraps: ProxyHandler<ProxyState> = {
|
||||||
|
get(state, prop) {
|
||||||
|
if (prop === DRAFT_STATE) return state
|
||||||
|
|
||||||
|
const source = latest(state)
|
||||||
|
if (!has(source, prop)) {
|
||||||
|
// non-existing or non-own property...
|
||||||
|
return readPropFromProto(state, source, prop)
|
||||||
|
}
|
||||||
|
const value = source[prop]
|
||||||
|
if (state.finalized_ || !isDraftable(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// Check for existing draft in modified state.
|
||||||
|
// Assigned values are never drafted. This catches any drafts we created, too.
|
||||||
|
if (value === peek(state.base_, prop)) {
|
||||||
|
prepareCopy(state)
|
||||||
|
return (state.copy_![prop as any] = createProxy(value, state))
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
has(state, prop) {
|
||||||
|
return prop in latest(state)
|
||||||
|
},
|
||||||
|
ownKeys(state) {
|
||||||
|
return Reflect.ownKeys(latest(state))
|
||||||
|
},
|
||||||
|
set(
|
||||||
|
state: ProxyObjectState,
|
||||||
|
prop: string /* strictly not, but helps TS */,
|
||||||
|
value
|
||||||
|
) {
|
||||||
|
const desc = getDescriptorFromProto(latest(state), prop)
|
||||||
|
if (desc?.set) {
|
||||||
|
// special case: if this write is captured by a setter, we have
|
||||||
|
// to trigger it with the correct context
|
||||||
|
desc.set.call(state.draft_, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!state.modified_) {
|
||||||
|
// the last check is because we need to be able to distinguish setting a non-existing to undefined (which is a change)
|
||||||
|
// from setting an existing property with value undefined to undefined (which is not a change)
|
||||||
|
const current = peek(latest(state), prop)
|
||||||
|
// special case, if we assigning the original value to a draft, we can ignore the assignment
|
||||||
|
const currentState: ProxyObjectState = current?.[DRAFT_STATE]
|
||||||
|
if (currentState && currentState.base_ === value) {
|
||||||
|
state.copy_![prop] = value
|
||||||
|
state.assigned_[prop] = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (is(value, current) && (value !== undefined || has(state.base_, prop)))
|
||||||
|
return true
|
||||||
|
prepareCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(state.copy_![prop] === value &&
|
||||||
|
// special case: handle new props with value 'undefined'
|
||||||
|
(value !== undefined || prop in state.copy_)) ||
|
||||||
|
// special case: NaN
|
||||||
|
(Number.isNaN(value) && Number.isNaN(state.copy_![prop]))
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
state.copy_![prop] = value
|
||||||
|
state.assigned_[prop] = true
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
deleteProperty(state, prop: string) {
|
||||||
|
// The `undefined` check is a fast path for pre-existing keys.
|
||||||
|
if (peek(state.base_, prop) !== undefined || prop in state.base_) {
|
||||||
|
state.assigned_[prop] = false
|
||||||
|
prepareCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
} else {
|
||||||
|
// if an originally not assigned property was deleted
|
||||||
|
delete state.assigned_[prop]
|
||||||
|
}
|
||||||
|
if (state.copy_) {
|
||||||
|
delete state.copy_[prop]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// Note: We never coerce `desc.value` into an Immer draft, because we can't make
|
||||||
|
// the same guarantee in ES5 mode.
|
||||||
|
getOwnPropertyDescriptor(state, prop) {
|
||||||
|
const owner = latest(state)
|
||||||
|
const desc = Reflect.getOwnPropertyDescriptor(owner, prop)
|
||||||
|
if (!desc) return desc
|
||||||
|
return {
|
||||||
|
writable: true,
|
||||||
|
configurable: state.type_ !== ArchType.Array || prop !== "length",
|
||||||
|
enumerable: desc.enumerable,
|
||||||
|
value: owner[prop]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defineProperty() {
|
||||||
|
die(11)
|
||||||
|
},
|
||||||
|
getPrototypeOf(state) {
|
||||||
|
return getPrototypeOf(state.base_)
|
||||||
|
},
|
||||||
|
setPrototypeOf() {
|
||||||
|
die(12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array drafts
|
||||||
|
*/
|
||||||
|
|
||||||
|
const arrayTraps: ProxyHandler<[ProxyArrayState]> = {}
|
||||||
|
each(objectTraps, (key, fn) => {
|
||||||
|
// @ts-ignore
|
||||||
|
arrayTraps[key] = function() {
|
||||||
|
arguments[0] = arguments[0][0]
|
||||||
|
return fn.apply(this, arguments)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
arrayTraps.deleteProperty = function(state, prop) {
|
||||||
|
if (process.env.NODE_ENV !== "production" && isNaN(parseInt(prop as any)))
|
||||||
|
die(13)
|
||||||
|
// @ts-ignore
|
||||||
|
return arrayTraps.set!.call(this, state, prop, undefined)
|
||||||
|
}
|
||||||
|
arrayTraps.set = function(state, prop, value) {
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== "production" &&
|
||||||
|
prop !== "length" &&
|
||||||
|
isNaN(parseInt(prop as any))
|
||||||
|
)
|
||||||
|
die(14)
|
||||||
|
return objectTraps.set!.call(this, state[0], prop, value, state[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access a property without creating an Immer draft.
|
||||||
|
function peek(draft: Drafted, prop: PropertyKey) {
|
||||||
|
const state = draft[DRAFT_STATE]
|
||||||
|
const source = state ? latest(state) : draft
|
||||||
|
return source[prop]
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPropFromProto(state: ImmerState, source: any, prop: PropertyKey) {
|
||||||
|
const desc = getDescriptorFromProto(source, prop)
|
||||||
|
return desc
|
||||||
|
? `value` in desc
|
||||||
|
? desc.value
|
||||||
|
: // This is a very special case, if the prop is a getter defined by the
|
||||||
|
// prototype, we should invoke it with the draft as context!
|
||||||
|
desc.get?.call(state.draft_)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDescriptorFromProto(
|
||||||
|
source: any,
|
||||||
|
prop: PropertyKey
|
||||||
|
): PropertyDescriptor | undefined {
|
||||||
|
// 'in' checks proto!
|
||||||
|
if (!(prop in source)) return undefined
|
||||||
|
let proto = getPrototypeOf(source)
|
||||||
|
while (proto) {
|
||||||
|
const desc = Object.getOwnPropertyDescriptor(proto, prop)
|
||||||
|
if (desc) return desc
|
||||||
|
proto = getPrototypeOf(proto)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markChanged(state: ImmerState) {
|
||||||
|
if (!state.modified_) {
|
||||||
|
state.modified_ = true
|
||||||
|
if (state.parent_) {
|
||||||
|
markChanged(state.parent_)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareCopy(state: {
|
||||||
|
base_: any
|
||||||
|
copy_: any
|
||||||
|
scope_: ImmerScope
|
||||||
|
}) {
|
||||||
|
if (!state.copy_) {
|
||||||
|
state.copy_ = shallowCopy(
|
||||||
|
state.base_,
|
||||||
|
state.scope_.immer_.useStrictShallowCopy_
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
80
node_modules/immer/src/core/scope.ts
generated
vendored
Normal file
80
node_modules/immer/src/core/scope.ts
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
Patch,
|
||||||
|
PatchListener,
|
||||||
|
Drafted,
|
||||||
|
Immer,
|
||||||
|
DRAFT_STATE,
|
||||||
|
ImmerState,
|
||||||
|
ArchType,
|
||||||
|
getPlugin
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
/** Each scope represents a `produce` call. */
|
||||||
|
|
||||||
|
export interface ImmerScope {
|
||||||
|
patches_?: Patch[]
|
||||||
|
inversePatches_?: Patch[]
|
||||||
|
canAutoFreeze_: boolean
|
||||||
|
drafts_: any[]
|
||||||
|
parent_?: ImmerScope
|
||||||
|
patchListener_?: PatchListener
|
||||||
|
immer_: Immer
|
||||||
|
unfinalizedDrafts_: number
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentScope: ImmerScope | undefined
|
||||||
|
|
||||||
|
export function getCurrentScope() {
|
||||||
|
return currentScope!
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScope(
|
||||||
|
parent_: ImmerScope | undefined,
|
||||||
|
immer_: Immer
|
||||||
|
): ImmerScope {
|
||||||
|
return {
|
||||||
|
drafts_: [],
|
||||||
|
parent_,
|
||||||
|
immer_,
|
||||||
|
// Whenever the modified draft contains a draft from another scope, we
|
||||||
|
// need to prevent auto-freezing so the unowned draft can be finalized.
|
||||||
|
canAutoFreeze_: true,
|
||||||
|
unfinalizedDrafts_: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePatchesInScope(
|
||||||
|
scope: ImmerScope,
|
||||||
|
patchListener?: PatchListener
|
||||||
|
) {
|
||||||
|
if (patchListener) {
|
||||||
|
getPlugin("Patches") // assert we have the plugin
|
||||||
|
scope.patches_ = []
|
||||||
|
scope.inversePatches_ = []
|
||||||
|
scope.patchListener_ = patchListener
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function revokeScope(scope: ImmerScope) {
|
||||||
|
leaveScope(scope)
|
||||||
|
scope.drafts_.forEach(revokeDraft)
|
||||||
|
// @ts-ignore
|
||||||
|
scope.drafts_ = null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function leaveScope(scope: ImmerScope) {
|
||||||
|
if (scope === currentScope) {
|
||||||
|
currentScope = scope.parent_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enterScope(immer: Immer) {
|
||||||
|
return (currentScope = createScope(currentScope, immer))
|
||||||
|
}
|
||||||
|
|
||||||
|
function revokeDraft(draft: Drafted) {
|
||||||
|
const state: ImmerState = draft[DRAFT_STATE]
|
||||||
|
if (state.type_ === ArchType.Object || state.type_ === ArchType.Array)
|
||||||
|
state.revoke_()
|
||||||
|
else state.revoked_ = true
|
||||||
|
}
|
117
node_modules/immer/src/immer.ts
generated
vendored
Normal file
117
node_modules/immer/src/immer.ts
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import {
|
||||||
|
IProduce,
|
||||||
|
IProduceWithPatches,
|
||||||
|
Immer,
|
||||||
|
Draft,
|
||||||
|
Immutable
|
||||||
|
} from "./internal"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Draft,
|
||||||
|
WritableDraft,
|
||||||
|
Immutable,
|
||||||
|
Patch,
|
||||||
|
PatchListener,
|
||||||
|
Producer,
|
||||||
|
original,
|
||||||
|
current,
|
||||||
|
isDraft,
|
||||||
|
isDraftable,
|
||||||
|
NOTHING as nothing,
|
||||||
|
DRAFTABLE as immerable,
|
||||||
|
freeze,
|
||||||
|
Objectish,
|
||||||
|
StrictMode
|
||||||
|
} from "./internal"
|
||||||
|
|
||||||
|
const immer = new Immer()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `produce` function takes a value and a "recipe function" (whose
|
||||||
|
* return value often depends on the base state). The recipe function is
|
||||||
|
* free to mutate its first argument however it wants. All mutations are
|
||||||
|
* only ever applied to a __copy__ of the base state.
|
||||||
|
*
|
||||||
|
* Pass only a function to create a "curried producer" which relieves you
|
||||||
|
* from passing the recipe function every time.
|
||||||
|
*
|
||||||
|
* Only plain objects and arrays are made mutable. All other objects are
|
||||||
|
* considered uncopyable.
|
||||||
|
*
|
||||||
|
* Note: This function is __bound__ to its `Immer` instance.
|
||||||
|
*
|
||||||
|
* @param {any} base - the initial state
|
||||||
|
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||||
|
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||||
|
* @returns {any} a new state, or the initial state if nothing was modified
|
||||||
|
*/
|
||||||
|
export const produce: IProduce = immer.produce
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like `produce`, but `produceWithPatches` always returns a tuple
|
||||||
|
* [nextState, patches, inversePatches] (instead of just the next state)
|
||||||
|
*/
|
||||||
|
export const produceWithPatches: IProduceWithPatches = immer.produceWithPatches.bind(
|
||||||
|
immer
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass true to automatically freeze all copies created by Immer.
|
||||||
|
*
|
||||||
|
* Always freeze by default, even in production mode
|
||||||
|
*/
|
||||||
|
export const setAutoFreeze = immer.setAutoFreeze.bind(immer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass true to enable strict shallow copy.
|
||||||
|
*
|
||||||
|
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
|
||||||
|
*/
|
||||||
|
export const setUseStrictShallowCopy = immer.setUseStrictShallowCopy.bind(immer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an array of Immer patches to the first argument.
|
||||||
|
*
|
||||||
|
* This function is a producer, which means copy-on-write is in effect.
|
||||||
|
*/
|
||||||
|
export const applyPatches = immer.applyPatches.bind(immer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Immer draft from the given base state, which may be a draft itself.
|
||||||
|
* The draft can be modified until you finalize it with the `finishDraft` function.
|
||||||
|
*/
|
||||||
|
export const createDraft = immer.createDraft.bind(immer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize an Immer draft from a `createDraft` call, returning the base state
|
||||||
|
* (if no changes were made) or a modified copy. The draft must *not* be
|
||||||
|
* mutated afterwards.
|
||||||
|
*
|
||||||
|
* Pass a function as the 2nd argument to generate Immer patches based on the
|
||||||
|
* changes that were made.
|
||||||
|
*/
|
||||||
|
export const finishDraft = immer.finishDraft.bind(immer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is actually a no-op, but can be used to cast an immutable type
|
||||||
|
* to an draft type and make TypeScript happy
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
export function castDraft<T>(value: T): Draft<T> {
|
||||||
|
return value as any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is actually a no-op, but can be used to cast a mutable type
|
||||||
|
* to an immutable type and make TypeScript happy
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
export function castImmutable<T>(value: T): Immutable<T> {
|
||||||
|
return value as any
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Immer}
|
||||||
|
|
||||||
|
export {enablePatches} from "./plugins/patches"
|
||||||
|
export {enableMapSet} from "./plugins/mapset"
|
11
node_modules/immer/src/internal.ts
generated
vendored
Normal file
11
node_modules/immer/src/internal.ts
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export * from "./utils/env"
|
||||||
|
export * from "./utils/errors"
|
||||||
|
export * from "./types/types-external"
|
||||||
|
export * from "./types/types-internal"
|
||||||
|
export * from "./utils/common"
|
||||||
|
export * from "./utils/plugins"
|
||||||
|
export * from "./core/scope"
|
||||||
|
export * from "./core/finalize"
|
||||||
|
export * from "./core/proxy"
|
||||||
|
export * from "./core/immerClass"
|
||||||
|
export * from "./core/current"
|
304
node_modules/immer/src/plugins/mapset.ts
generated
vendored
Normal file
304
node_modules/immer/src/plugins/mapset.ts
generated
vendored
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
// types only!
|
||||||
|
import {
|
||||||
|
ImmerState,
|
||||||
|
AnyMap,
|
||||||
|
AnySet,
|
||||||
|
MapState,
|
||||||
|
SetState,
|
||||||
|
DRAFT_STATE,
|
||||||
|
getCurrentScope,
|
||||||
|
latest,
|
||||||
|
isDraftable,
|
||||||
|
createProxy,
|
||||||
|
loadPlugin,
|
||||||
|
markChanged,
|
||||||
|
die,
|
||||||
|
ArchType,
|
||||||
|
each
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
export function enableMapSet() {
|
||||||
|
class DraftMap extends Map {
|
||||||
|
[DRAFT_STATE]: MapState
|
||||||
|
|
||||||
|
constructor(target: AnyMap, parent?: ImmerState) {
|
||||||
|
super()
|
||||||
|
this[DRAFT_STATE] = {
|
||||||
|
type_: ArchType.Map,
|
||||||
|
parent_: parent,
|
||||||
|
scope_: parent ? parent.scope_ : getCurrentScope()!,
|
||||||
|
modified_: false,
|
||||||
|
finalized_: false,
|
||||||
|
copy_: undefined,
|
||||||
|
assigned_: undefined,
|
||||||
|
base_: target,
|
||||||
|
draft_: this as any,
|
||||||
|
isManual_: false,
|
||||||
|
revoked_: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get size(): number {
|
||||||
|
return latest(this[DRAFT_STATE]).size
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key: any): boolean {
|
||||||
|
return latest(this[DRAFT_STATE]).has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: any, value: any) {
|
||||||
|
const state: MapState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
if (!latest(state).has(key) || latest(state).get(key) !== value) {
|
||||||
|
prepareMapCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
state.assigned_!.set(key, true)
|
||||||
|
state.copy_!.set(key, value)
|
||||||
|
state.assigned_!.set(key, true)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key: any): boolean {
|
||||||
|
if (!this.has(key)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: MapState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
prepareMapCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
if (state.base_.has(key)) {
|
||||||
|
state.assigned_!.set(key, false)
|
||||||
|
} else {
|
||||||
|
state.assigned_!.delete(key)
|
||||||
|
}
|
||||||
|
state.copy_!.delete(key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
const state: MapState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
if (latest(state).size) {
|
||||||
|
prepareMapCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
state.assigned_ = new Map()
|
||||||
|
each(state.base_, key => {
|
||||||
|
state.assigned_!.set(key, false)
|
||||||
|
})
|
||||||
|
state.copy_!.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(cb: (value: any, key: any, self: any) => void, thisArg?: any) {
|
||||||
|
const state: MapState = this[DRAFT_STATE]
|
||||||
|
latest(state).forEach((_value: any, key: any, _map: any) => {
|
||||||
|
cb.call(thisArg, this.get(key), key, this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: any): any {
|
||||||
|
const state: MapState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
const value = latest(state).get(key)
|
||||||
|
if (state.finalized_ || !isDraftable(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (value !== state.base_.get(key)) {
|
||||||
|
return value // either already drafted or reassigned
|
||||||
|
}
|
||||||
|
// despite what it looks, this creates a draft only once, see above condition
|
||||||
|
const draft = createProxy(value, state)
|
||||||
|
prepareMapCopy(state)
|
||||||
|
state.copy_!.set(key, draft)
|
||||||
|
return draft
|
||||||
|
}
|
||||||
|
|
||||||
|
keys(): IterableIterator<any> {
|
||||||
|
return latest(this[DRAFT_STATE]).keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
values(): IterableIterator<any> {
|
||||||
|
const iterator = this.keys()
|
||||||
|
return {
|
||||||
|
[Symbol.iterator]: () => this.values(),
|
||||||
|
next: () => {
|
||||||
|
const r = iterator.next()
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (r.done) return r
|
||||||
|
const value = this.get(r.value)
|
||||||
|
return {
|
||||||
|
done: false,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
|
entries(): IterableIterator<[any, any]> {
|
||||||
|
const iterator = this.keys()
|
||||||
|
return {
|
||||||
|
[Symbol.iterator]: () => this.entries(),
|
||||||
|
next: () => {
|
||||||
|
const r = iterator.next()
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (r.done) return r
|
||||||
|
const value = this.get(r.value)
|
||||||
|
return {
|
||||||
|
done: false,
|
||||||
|
value: [r.value, value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this.entries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T {
|
||||||
|
// @ts-ignore
|
||||||
|
return new DraftMap(target, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareMapCopy(state: MapState) {
|
||||||
|
if (!state.copy_) {
|
||||||
|
state.assigned_ = new Map()
|
||||||
|
state.copy_ = new Map(state.base_)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DraftSet extends Set {
|
||||||
|
[DRAFT_STATE]: SetState
|
||||||
|
constructor(target: AnySet, parent?: ImmerState) {
|
||||||
|
super()
|
||||||
|
this[DRAFT_STATE] = {
|
||||||
|
type_: ArchType.Set,
|
||||||
|
parent_: parent,
|
||||||
|
scope_: parent ? parent.scope_ : getCurrentScope()!,
|
||||||
|
modified_: false,
|
||||||
|
finalized_: false,
|
||||||
|
copy_: undefined,
|
||||||
|
base_: target,
|
||||||
|
draft_: this,
|
||||||
|
drafts_: new Map(),
|
||||||
|
revoked_: false,
|
||||||
|
isManual_: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get size(): number {
|
||||||
|
return latest(this[DRAFT_STATE]).size
|
||||||
|
}
|
||||||
|
|
||||||
|
has(value: any): boolean {
|
||||||
|
const state: SetState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
// bit of trickery here, to be able to recognize both the value, and the draft of its value
|
||||||
|
if (!state.copy_) {
|
||||||
|
return state.base_.has(value)
|
||||||
|
}
|
||||||
|
if (state.copy_.has(value)) return true
|
||||||
|
if (state.drafts_.has(value) && state.copy_.has(state.drafts_.get(value)))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
add(value: any): any {
|
||||||
|
const state: SetState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
if (!this.has(value)) {
|
||||||
|
prepareSetCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
state.copy_!.add(value)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(value: any): any {
|
||||||
|
if (!this.has(value)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: SetState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
prepareSetCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
return (
|
||||||
|
state.copy_!.delete(value) ||
|
||||||
|
(state.drafts_.has(value)
|
||||||
|
? state.copy_!.delete(state.drafts_.get(value))
|
||||||
|
: /* istanbul ignore next */ false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
const state: SetState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
if (latest(state).size) {
|
||||||
|
prepareSetCopy(state)
|
||||||
|
markChanged(state)
|
||||||
|
state.copy_!.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values(): IterableIterator<any> {
|
||||||
|
const state: SetState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
prepareSetCopy(state)
|
||||||
|
return state.copy_!.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
entries(): IterableIterator<[any, any]> {
|
||||||
|
const state: SetState = this[DRAFT_STATE]
|
||||||
|
assertUnrevoked(state)
|
||||||
|
prepareSetCopy(state)
|
||||||
|
return state.copy_!.entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
keys(): IterableIterator<any> {
|
||||||
|
return this.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(cb: any, thisArg?: any) {
|
||||||
|
const iterator = this.values()
|
||||||
|
let result = iterator.next()
|
||||||
|
while (!result.done) {
|
||||||
|
cb.call(thisArg, result.value, result.value, this)
|
||||||
|
result = iterator.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T {
|
||||||
|
// @ts-ignore
|
||||||
|
return new DraftSet(target, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareSetCopy(state: SetState) {
|
||||||
|
if (!state.copy_) {
|
||||||
|
// create drafts for all entries to preserve insertion order
|
||||||
|
state.copy_ = new Set()
|
||||||
|
state.base_.forEach(value => {
|
||||||
|
if (isDraftable(value)) {
|
||||||
|
const draft = createProxy(value, state)
|
||||||
|
state.drafts_.set(value, draft)
|
||||||
|
state.copy_!.add(draft)
|
||||||
|
} else {
|
||||||
|
state.copy_!.add(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
|
||||||
|
if (state.revoked_) die(3, JSON.stringify(latest(state)))
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPlugin("MapSet", {proxyMap_, proxySet_})
|
||||||
|
}
|
317
node_modules/immer/src/plugins/patches.ts
generated
vendored
Normal file
317
node_modules/immer/src/plugins/patches.ts
generated
vendored
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import {immerable} from "../immer"
|
||||||
|
import {
|
||||||
|
ImmerState,
|
||||||
|
Patch,
|
||||||
|
SetState,
|
||||||
|
ProxyArrayState,
|
||||||
|
MapState,
|
||||||
|
ProxyObjectState,
|
||||||
|
PatchPath,
|
||||||
|
get,
|
||||||
|
each,
|
||||||
|
has,
|
||||||
|
getArchtype,
|
||||||
|
getPrototypeOf,
|
||||||
|
isSet,
|
||||||
|
isMap,
|
||||||
|
loadPlugin,
|
||||||
|
ArchType,
|
||||||
|
die,
|
||||||
|
isDraft,
|
||||||
|
isDraftable,
|
||||||
|
NOTHING,
|
||||||
|
errors
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
export function enablePatches() {
|
||||||
|
const errorOffset = 16
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
errors.push(
|
||||||
|
'Sets cannot have "replace" patches.',
|
||||||
|
function(op: string) {
|
||||||
|
return "Unsupported patch operation: " + op
|
||||||
|
},
|
||||||
|
function(path: string) {
|
||||||
|
return "Cannot apply patch, path doesn't resolve: " + path
|
||||||
|
},
|
||||||
|
"Patching reserved attributes like __proto__, prototype and constructor is not allowed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const REPLACE = "replace"
|
||||||
|
const ADD = "add"
|
||||||
|
const REMOVE = "remove"
|
||||||
|
|
||||||
|
function generatePatches_(
|
||||||
|
state: ImmerState,
|
||||||
|
basePath: PatchPath,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
): void {
|
||||||
|
switch (state.type_) {
|
||||||
|
case ArchType.Object:
|
||||||
|
case ArchType.Map:
|
||||||
|
return generatePatchesFromAssigned(
|
||||||
|
state,
|
||||||
|
basePath,
|
||||||
|
patches,
|
||||||
|
inversePatches
|
||||||
|
)
|
||||||
|
case ArchType.Array:
|
||||||
|
return generateArrayPatches(state, basePath, patches, inversePatches)
|
||||||
|
case ArchType.Set:
|
||||||
|
return generateSetPatches(
|
||||||
|
(state as any) as SetState,
|
||||||
|
basePath,
|
||||||
|
patches,
|
||||||
|
inversePatches
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateArrayPatches(
|
||||||
|
state: ProxyArrayState,
|
||||||
|
basePath: PatchPath,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
) {
|
||||||
|
let {base_, assigned_} = state
|
||||||
|
let copy_ = state.copy_!
|
||||||
|
|
||||||
|
// Reduce complexity by ensuring `base` is never longer.
|
||||||
|
if (copy_.length < base_.length) {
|
||||||
|
// @ts-ignore
|
||||||
|
;[base_, copy_] = [copy_, base_]
|
||||||
|
;[patches, inversePatches] = [inversePatches, patches]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process replaced indices.
|
||||||
|
for (let i = 0; i < base_.length; i++) {
|
||||||
|
if (assigned_[i] && copy_[i] !== base_[i]) {
|
||||||
|
const path = basePath.concat([i])
|
||||||
|
patches.push({
|
||||||
|
op: REPLACE,
|
||||||
|
path,
|
||||||
|
// Need to maybe clone it, as it can in fact be the original value
|
||||||
|
// due to the base/copy inversion at the start of this function
|
||||||
|
value: clonePatchValueIfNeeded(copy_[i])
|
||||||
|
})
|
||||||
|
inversePatches.push({
|
||||||
|
op: REPLACE,
|
||||||
|
path,
|
||||||
|
value: clonePatchValueIfNeeded(base_[i])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process added indices.
|
||||||
|
for (let i = base_.length; i < copy_.length; i++) {
|
||||||
|
const path = basePath.concat([i])
|
||||||
|
patches.push({
|
||||||
|
op: ADD,
|
||||||
|
path,
|
||||||
|
// Need to maybe clone it, as it can in fact be the original value
|
||||||
|
// due to the base/copy inversion at the start of this function
|
||||||
|
value: clonePatchValueIfNeeded(copy_[i])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (let i = copy_.length - 1; base_.length <= i; --i) {
|
||||||
|
const path = basePath.concat([i])
|
||||||
|
inversePatches.push({
|
||||||
|
op: REMOVE,
|
||||||
|
path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used for both Map objects and normal objects.
|
||||||
|
function generatePatchesFromAssigned(
|
||||||
|
state: MapState | ProxyObjectState,
|
||||||
|
basePath: PatchPath,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
) {
|
||||||
|
const {base_, copy_} = state
|
||||||
|
each(state.assigned_!, (key, assignedValue) => {
|
||||||
|
const origValue = get(base_, key)
|
||||||
|
const value = get(copy_!, key)
|
||||||
|
const op = !assignedValue ? REMOVE : has(base_, key) ? REPLACE : ADD
|
||||||
|
if (origValue === value && op === REPLACE) return
|
||||||
|
const path = basePath.concat(key as any)
|
||||||
|
patches.push(op === REMOVE ? {op, path} : {op, path, value})
|
||||||
|
inversePatches.push(
|
||||||
|
op === ADD
|
||||||
|
? {op: REMOVE, path}
|
||||||
|
: op === REMOVE
|
||||||
|
? {op: ADD, path, value: clonePatchValueIfNeeded(origValue)}
|
||||||
|
: {op: REPLACE, path, value: clonePatchValueIfNeeded(origValue)}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSetPatches(
|
||||||
|
state: SetState,
|
||||||
|
basePath: PatchPath,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
) {
|
||||||
|
let {base_, copy_} = state
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
base_.forEach((value: any) => {
|
||||||
|
if (!copy_!.has(value)) {
|
||||||
|
const path = basePath.concat([i])
|
||||||
|
patches.push({
|
||||||
|
op: REMOVE,
|
||||||
|
path,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
inversePatches.unshift({
|
||||||
|
op: ADD,
|
||||||
|
path,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
})
|
||||||
|
i = 0
|
||||||
|
copy_!.forEach((value: any) => {
|
||||||
|
if (!base_.has(value)) {
|
||||||
|
const path = basePath.concat([i])
|
||||||
|
patches.push({
|
||||||
|
op: ADD,
|
||||||
|
path,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
inversePatches.unshift({
|
||||||
|
op: REMOVE,
|
||||||
|
path,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateReplacementPatches_(
|
||||||
|
baseValue: any,
|
||||||
|
replacement: any,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
): void {
|
||||||
|
patches.push({
|
||||||
|
op: REPLACE,
|
||||||
|
path: [],
|
||||||
|
value: replacement === NOTHING ? undefined : replacement
|
||||||
|
})
|
||||||
|
inversePatches.push({
|
||||||
|
op: REPLACE,
|
||||||
|
path: [],
|
||||||
|
value: baseValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPatches_<T>(draft: T, patches: readonly Patch[]): T {
|
||||||
|
patches.forEach(patch => {
|
||||||
|
const {path, op} = patch
|
||||||
|
|
||||||
|
let base: any = draft
|
||||||
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
|
const parentType = getArchtype(base)
|
||||||
|
let p = path[i]
|
||||||
|
if (typeof p !== "string" && typeof p !== "number") {
|
||||||
|
p = "" + p
|
||||||
|
}
|
||||||
|
|
||||||
|
// See #738, avoid prototype pollution
|
||||||
|
if (
|
||||||
|
(parentType === ArchType.Object || parentType === ArchType.Array) &&
|
||||||
|
(p === "__proto__" || p === "constructor")
|
||||||
|
)
|
||||||
|
die(errorOffset + 3)
|
||||||
|
if (typeof base === "function" && p === "prototype")
|
||||||
|
die(errorOffset + 3)
|
||||||
|
base = get(base, p)
|
||||||
|
if (typeof base !== "object") die(errorOffset + 2, path.join("/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = getArchtype(base)
|
||||||
|
const value = deepClonePatchValue(patch.value) // used to clone patch to ensure original patch is not modified, see #411
|
||||||
|
const key = path[path.length - 1]
|
||||||
|
switch (op) {
|
||||||
|
case REPLACE:
|
||||||
|
switch (type) {
|
||||||
|
case ArchType.Map:
|
||||||
|
return base.set(key, value)
|
||||||
|
/* istanbul ignore next */
|
||||||
|
case ArchType.Set:
|
||||||
|
die(errorOffset)
|
||||||
|
default:
|
||||||
|
// if value is an object, then it's assigned by reference
|
||||||
|
// in the following add or remove ops, the value field inside the patch will also be modifyed
|
||||||
|
// so we use value from the cloned patch
|
||||||
|
// @ts-ignore
|
||||||
|
return (base[key] = value)
|
||||||
|
}
|
||||||
|
case ADD:
|
||||||
|
switch (type) {
|
||||||
|
case ArchType.Array:
|
||||||
|
return key === "-"
|
||||||
|
? base.push(value)
|
||||||
|
: base.splice(key as any, 0, value)
|
||||||
|
case ArchType.Map:
|
||||||
|
return base.set(key, value)
|
||||||
|
case ArchType.Set:
|
||||||
|
return base.add(value)
|
||||||
|
default:
|
||||||
|
return (base[key] = value)
|
||||||
|
}
|
||||||
|
case REMOVE:
|
||||||
|
switch (type) {
|
||||||
|
case ArchType.Array:
|
||||||
|
return base.splice(key as any, 1)
|
||||||
|
case ArchType.Map:
|
||||||
|
return base.delete(key)
|
||||||
|
case ArchType.Set:
|
||||||
|
return base.delete(patch.value)
|
||||||
|
default:
|
||||||
|
return delete base[key]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
die(errorOffset + 1, op)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return draft
|
||||||
|
}
|
||||||
|
|
||||||
|
// optimize: this is quite a performance hit, can we detect intelligently when it is needed?
|
||||||
|
// E.g. auto-draft when new objects from outside are assigned and modified?
|
||||||
|
// (See failing test when deepClone just returns obj)
|
||||||
|
function deepClonePatchValue<T>(obj: T): T
|
||||||
|
function deepClonePatchValue(obj: any) {
|
||||||
|
if (!isDraftable(obj)) return obj
|
||||||
|
if (Array.isArray(obj)) return obj.map(deepClonePatchValue)
|
||||||
|
if (isMap(obj))
|
||||||
|
return new Map(
|
||||||
|
Array.from(obj.entries()).map(([k, v]) => [k, deepClonePatchValue(v)])
|
||||||
|
)
|
||||||
|
if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue))
|
||||||
|
const cloned = Object.create(getPrototypeOf(obj))
|
||||||
|
for (const key in obj) cloned[key] = deepClonePatchValue(obj[key])
|
||||||
|
if (has(obj, immerable)) cloned[immerable] = obj[immerable]
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
function clonePatchValueIfNeeded<T>(obj: T): T {
|
||||||
|
if (isDraft(obj)) {
|
||||||
|
return deepClonePatchValue(obj)
|
||||||
|
} else return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPlugin("Patches", {
|
||||||
|
applyPatches_,
|
||||||
|
generatePatches_,
|
||||||
|
generateReplacementPatches_
|
||||||
|
})
|
||||||
|
}
|
1
node_modules/immer/src/types/globals.d.ts
generated
vendored
Normal file
1
node_modules/immer/src/types/globals.d.ts
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare const __DEV__: boolean
|
111
node_modules/immer/src/types/index.js.flow
generated
vendored
Normal file
111
node_modules/immer/src/types/index.js.flow
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
export interface Patch {
|
||||||
|
op: "replace" | "remove" | "add";
|
||||||
|
path: (string | number)[];
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
|
||||||
|
|
||||||
|
type Base = {...} | Array<any>
|
||||||
|
interface IProduce {
|
||||||
|
/**
|
||||||
|
* Immer takes a state, and runs a function against it.
|
||||||
|
* That function can freely mutate the state, as it will create copies-on-write.
|
||||||
|
* This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
|
||||||
|
*
|
||||||
|
* If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
|
||||||
|
* any time it is called with the current state.
|
||||||
|
*
|
||||||
|
* @param currentState - the state to start with
|
||||||
|
* @param recipe - function that receives a proxy of the current state as first argument and which can be freely modified
|
||||||
|
* @param initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
|
||||||
|
* @returns The next state: a new state, or the current state if nothing was modified
|
||||||
|
*/
|
||||||
|
<S: Base>(
|
||||||
|
currentState: S,
|
||||||
|
recipe: (draftState: S) => S | void,
|
||||||
|
patchListener?: PatchListener
|
||||||
|
): S;
|
||||||
|
// curried invocations with initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
|
||||||
|
initialState: S
|
||||||
|
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => S;
|
||||||
|
// curried invocations without initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
|
||||||
|
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProduceWithPatches {
|
||||||
|
/**
|
||||||
|
* Like `produce`, but instead of just returning the new state,
|
||||||
|
* a tuple is returned with [nextState, patches, inversePatches]
|
||||||
|
*
|
||||||
|
* Like produce, this function supports currying
|
||||||
|
*/
|
||||||
|
<S: Base>(
|
||||||
|
currentState: S,
|
||||||
|
recipe: (draftState: S) => S | void
|
||||||
|
): [S, Patch[], Patch[]];
|
||||||
|
// curried invocations with initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
|
||||||
|
initialState: S
|
||||||
|
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
|
||||||
|
// curried invocations without initial state
|
||||||
|
<S: Base, A = void, B = void, C = void>(
|
||||||
|
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
|
||||||
|
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare export var produce: IProduce
|
||||||
|
|
||||||
|
declare export var produceWithPatches: IProduceWithPatches
|
||||||
|
|
||||||
|
declare export var nothing: typeof undefined
|
||||||
|
|
||||||
|
declare export var immerable: Symbol
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically freezes any state trees generated by immer.
|
||||||
|
* This protects against accidental modifications of the state tree outside of an immer function.
|
||||||
|
* This comes with a performance impact, so it is recommended to disable this option in production.
|
||||||
|
* By default it is turned on during local development, and turned off in production.
|
||||||
|
*/
|
||||||
|
declare export function setAutoFreeze(autoFreeze: boolean): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass false to disable strict shallow copy.
|
||||||
|
*
|
||||||
|
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
|
||||||
|
*/
|
||||||
|
declare export function setUseStrictShallowCopy(useStrictShallowCopy: boolean): void
|
||||||
|
|
||||||
|
declare export function applyPatches<S>(state: S, patches: Patch[]): S
|
||||||
|
|
||||||
|
declare export function original<S>(value: S): S
|
||||||
|
|
||||||
|
declare export function current<S>(value: S): S
|
||||||
|
|
||||||
|
declare export function isDraft(value: any): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mutable draft from an (immutable) object / array.
|
||||||
|
* The draft can be modified until `finishDraft` is called
|
||||||
|
*/
|
||||||
|
declare export function createDraft<T>(base: T): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a draft that was created using `createDraft`,
|
||||||
|
* finalizes the draft into a new immutable object.
|
||||||
|
* Optionally a patch-listener can be provided to gather the patches that are needed to construct the object.
|
||||||
|
*/
|
||||||
|
declare export function finishDraft<T>(base: T, listener?: PatchListener): T
|
||||||
|
|
||||||
|
declare export function enableMapSet(): void
|
||||||
|
declare export function enablePatches(): void
|
||||||
|
|
||||||
|
declare export function freeze<T>(obj: T, freeze?: boolean): T
|
239
node_modules/immer/src/types/types-external.ts
generated
vendored
Normal file
239
node_modules/immer/src/types/types-external.ts
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import {NOTHING} from "../internal"
|
||||||
|
|
||||||
|
type AnyFunc = (...args: any[]) => any
|
||||||
|
|
||||||
|
type PrimitiveType = number | string | boolean
|
||||||
|
|
||||||
|
/** Object types that should never be mapped */
|
||||||
|
type AtomicObject = Function | Promise<any> | Date | RegExp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the lib "ES2015.Collection" is not included in tsconfig.json,
|
||||||
|
* types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
|
||||||
|
* or `{}` (from the node types), in both cases entering an infinite recursion in
|
||||||
|
* pattern matching type mappings
|
||||||
|
* This type can be used to cast these types to `void` in these cases.
|
||||||
|
*/
|
||||||
|
export type IfAvailable<T, Fallback = void> =
|
||||||
|
// fallback if any
|
||||||
|
true | false extends (T extends never
|
||||||
|
? true
|
||||||
|
: false)
|
||||||
|
? Fallback // fallback if empty type
|
||||||
|
: keyof T extends never
|
||||||
|
? Fallback // original type
|
||||||
|
: T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These should also never be mapped but must be tested after regular Map and
|
||||||
|
* Set
|
||||||
|
*/
|
||||||
|
type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>
|
||||||
|
|
||||||
|
export type WritableDraft<T> = {-readonly [K in keyof T]: Draft<T[K]>}
|
||||||
|
|
||||||
|
/** Convert a readonly type into a mutable type, if possible */
|
||||||
|
export type Draft<T> = T extends PrimitiveType
|
||||||
|
? T
|
||||||
|
: T extends AtomicObject
|
||||||
|
? T
|
||||||
|
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
|
||||||
|
? Map<Draft<K>, Draft<V>>
|
||||||
|
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
|
||||||
|
? Set<Draft<V>>
|
||||||
|
: T extends WeakReferences
|
||||||
|
? T
|
||||||
|
: T extends object
|
||||||
|
? WritableDraft<T>
|
||||||
|
: T
|
||||||
|
|
||||||
|
/** Convert a mutable type into a readonly type */
|
||||||
|
export type Immutable<T> = T extends PrimitiveType
|
||||||
|
? T
|
||||||
|
: T extends AtomicObject
|
||||||
|
? T
|
||||||
|
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
|
||||||
|
? ReadonlyMap<Immutable<K>, Immutable<V>>
|
||||||
|
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
|
||||||
|
? ReadonlySet<Immutable<V>>
|
||||||
|
: T extends WeakReferences
|
||||||
|
? T
|
||||||
|
: T extends object
|
||||||
|
? {readonly [K in keyof T]: Immutable<T[K]>}
|
||||||
|
: T
|
||||||
|
|
||||||
|
export interface Patch {
|
||||||
|
op: "replace" | "remove" | "add"
|
||||||
|
path: (string | number)[]
|
||||||
|
value?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
|
||||||
|
|
||||||
|
/** Converts `nothing` into `undefined` */
|
||||||
|
type FromNothing<T> = T extends typeof NOTHING ? undefined : T
|
||||||
|
|
||||||
|
/** The inferred return type of `produce` */
|
||||||
|
export type Produced<Base, Return> = Return extends void
|
||||||
|
? Base
|
||||||
|
: FromNothing<Return>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility types
|
||||||
|
*/
|
||||||
|
type PatchesTuple<T> = readonly [T, Patch[], Patch[]]
|
||||||
|
|
||||||
|
type ValidRecipeReturnType<State> =
|
||||||
|
| State
|
||||||
|
| void
|
||||||
|
| undefined
|
||||||
|
| (State extends undefined ? typeof NOTHING : never)
|
||||||
|
|
||||||
|
type ReturnTypeWithPatchesIfNeeded<
|
||||||
|
State,
|
||||||
|
UsePatches extends boolean
|
||||||
|
> = UsePatches extends true ? PatchesTuple<State> : State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core Producer inference
|
||||||
|
*/
|
||||||
|
type InferRecipeFromCurried<Curried> = Curried extends (
|
||||||
|
base: infer State,
|
||||||
|
...rest: infer Args
|
||||||
|
) => any // extra assertion to make sure this is a proper curried function (state, args) => state
|
||||||
|
? ReturnType<Curried> extends State
|
||||||
|
? (
|
||||||
|
draft: Draft<State>,
|
||||||
|
...rest: Args
|
||||||
|
) => ValidRecipeReturnType<Draft<State>>
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
|
||||||
|
type InferInitialStateFromCurried<Curried> = Curried extends (
|
||||||
|
base: infer State,
|
||||||
|
...rest: any[]
|
||||||
|
) => any // extra assertion to make sure this is a proper curried function (state, args) => state
|
||||||
|
? State
|
||||||
|
: never
|
||||||
|
|
||||||
|
type InferCurriedFromRecipe<
|
||||||
|
Recipe,
|
||||||
|
UsePatches extends boolean
|
||||||
|
> = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any // verify return type
|
||||||
|
? ReturnType<Recipe> extends ValidRecipeReturnType<DraftState>
|
||||||
|
? (
|
||||||
|
base: Immutable<DraftState>,
|
||||||
|
...args: RestArgs
|
||||||
|
) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> // N.b. we return mutable draftstate, in case the recipe's first arg isn't read only, and that isn't expected as output either
|
||||||
|
: never // incorrect return type
|
||||||
|
: never // not a function
|
||||||
|
|
||||||
|
type InferCurriedFromInitialStateAndRecipe<
|
||||||
|
State,
|
||||||
|
Recipe,
|
||||||
|
UsePatches extends boolean
|
||||||
|
> = Recipe extends (
|
||||||
|
draft: Draft<State>,
|
||||||
|
...rest: infer RestArgs
|
||||||
|
) => ValidRecipeReturnType<State>
|
||||||
|
? (
|
||||||
|
base?: State | undefined,
|
||||||
|
...args: RestArgs
|
||||||
|
) => ReturnTypeWithPatchesIfNeeded<State, UsePatches>
|
||||||
|
: never // recipe doesn't match initial state
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `produce` function takes a value and a "recipe function" (whose
|
||||||
|
* return value often depends on the base state). The recipe function is
|
||||||
|
* free to mutate its first argument however it wants. All mutations are
|
||||||
|
* only ever applied to a __copy__ of the base state.
|
||||||
|
*
|
||||||
|
* Pass only a function to create a "curried producer" which relieves you
|
||||||
|
* from passing the recipe function every time.
|
||||||
|
*
|
||||||
|
* Only plain objects and arrays are made mutable. All other objects are
|
||||||
|
* considered uncopyable.
|
||||||
|
*
|
||||||
|
* Note: This function is __bound__ to its `Immer` instance.
|
||||||
|
*
|
||||||
|
* @param {any} base - the initial state
|
||||||
|
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||||
|
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||||
|
* @returns {any} a new state, or the initial state if nothing was modified
|
||||||
|
*/
|
||||||
|
export interface IProduce {
|
||||||
|
/** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */
|
||||||
|
<Curried>(
|
||||||
|
recipe: InferRecipeFromCurried<Curried>,
|
||||||
|
initialState?: InferInitialStateFromCurried<Curried>
|
||||||
|
): Curried
|
||||||
|
|
||||||
|
/** Curried producer that infers curried from the recipe */
|
||||||
|
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<
|
||||||
|
Recipe,
|
||||||
|
false
|
||||||
|
>
|
||||||
|
|
||||||
|
/** Curried producer that infers curried from the State generic, which is explicitly passed in. */
|
||||||
|
<State>(
|
||||||
|
recipe: (
|
||||||
|
state: Draft<State>,
|
||||||
|
initialState: State
|
||||||
|
) => ValidRecipeReturnType<State>
|
||||||
|
): (state?: State) => State
|
||||||
|
<State, Args extends any[]>(
|
||||||
|
recipe: (
|
||||||
|
state: Draft<State>,
|
||||||
|
...args: Args
|
||||||
|
) => ValidRecipeReturnType<State>,
|
||||||
|
initialState: State
|
||||||
|
): (state?: State, ...args: Args) => State
|
||||||
|
<State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (
|
||||||
|
state: State
|
||||||
|
) => State
|
||||||
|
<State, Args extends any[]>(
|
||||||
|
recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>
|
||||||
|
): (state: State, ...args: Args) => State
|
||||||
|
|
||||||
|
/** Curried producer with initial state, infers recipe from initial state */
|
||||||
|
<State, Recipe extends Function>(
|
||||||
|
recipe: Recipe,
|
||||||
|
initialState: State
|
||||||
|
): InferCurriedFromInitialStateAndRecipe<State, Recipe, false>
|
||||||
|
|
||||||
|
/** Normal producer */
|
||||||
|
<Base, D = Draft<Base>>( // By using a default inferred D, rather than Draft<Base> in the recipe, we can override it.
|
||||||
|
base: Base,
|
||||||
|
recipe: (draft: D) => ValidRecipeReturnType<D>,
|
||||||
|
listener?: PatchListener
|
||||||
|
): Base
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like `produce`, but instead of just returning the new state,
|
||||||
|
* a tuple is returned with [nextState, patches, inversePatches]
|
||||||
|
*
|
||||||
|
* Like produce, this function supports currying
|
||||||
|
*/
|
||||||
|
export interface IProduceWithPatches {
|
||||||
|
// Types copied from IProduce, wrapped with PatchesTuple
|
||||||
|
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, true>
|
||||||
|
<State, Recipe extends Function>(
|
||||||
|
recipe: Recipe,
|
||||||
|
initialState: State
|
||||||
|
): InferCurriedFromInitialStateAndRecipe<State, Recipe, true>
|
||||||
|
<Base, D = Draft<Base>>(
|
||||||
|
base: Base,
|
||||||
|
recipe: (draft: D) => ValidRecipeReturnType<D>,
|
||||||
|
listener?: PatchListener
|
||||||
|
): PatchesTuple<Base>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type for `recipe function`
|
||||||
|
*/
|
||||||
|
export type Producer<T> = (draft: Draft<T>) => ValidRecipeReturnType<Draft<T>>
|
||||||
|
|
||||||
|
// Fixes #507: bili doesn't export the types of this file if there is no actual source in it..
|
||||||
|
// hopefully it get's tree-shaken away for everyone :)
|
||||||
|
export function never_used() {}
|
42
node_modules/immer/src/types/types-internal.ts
generated
vendored
Normal file
42
node_modules/immer/src/types/types-internal.ts
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
SetState,
|
||||||
|
ImmerScope,
|
||||||
|
ProxyObjectState,
|
||||||
|
ProxyArrayState,
|
||||||
|
MapState,
|
||||||
|
DRAFT_STATE
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
export type Objectish = AnyObject | AnyArray | AnyMap | AnySet
|
||||||
|
export type ObjectishNoSet = AnyObject | AnyArray | AnyMap
|
||||||
|
|
||||||
|
export type AnyObject = {[key: string]: any}
|
||||||
|
export type AnyArray = Array<any>
|
||||||
|
export type AnySet = Set<any>
|
||||||
|
export type AnyMap = Map<any, any>
|
||||||
|
|
||||||
|
export const enum ArchType {
|
||||||
|
Object,
|
||||||
|
Array,
|
||||||
|
Map,
|
||||||
|
Set
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImmerBaseState {
|
||||||
|
parent_?: ImmerState
|
||||||
|
scope_: ImmerScope
|
||||||
|
modified_: boolean
|
||||||
|
finalized_: boolean
|
||||||
|
isManual_: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ImmerState =
|
||||||
|
| ProxyObjectState
|
||||||
|
| ProxyArrayState
|
||||||
|
| MapState
|
||||||
|
| SetState
|
||||||
|
|
||||||
|
// The _internal_ type used for drafts (not to be confused with Draft, which is public facing)
|
||||||
|
export type Drafted<Base = any, T extends ImmerState = ImmerState> = {
|
||||||
|
[DRAFT_STATE]: T
|
||||||
|
} & Base
|
217
node_modules/immer/src/utils/common.ts
generated
vendored
Normal file
217
node_modules/immer/src/utils/common.ts
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import {
|
||||||
|
DRAFT_STATE,
|
||||||
|
DRAFTABLE,
|
||||||
|
Objectish,
|
||||||
|
Drafted,
|
||||||
|
AnyObject,
|
||||||
|
AnyMap,
|
||||||
|
AnySet,
|
||||||
|
ImmerState,
|
||||||
|
ArchType,
|
||||||
|
die,
|
||||||
|
StrictMode
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
export const getPrototypeOf = Object.getPrototypeOf
|
||||||
|
|
||||||
|
/** Returns true if the given value is an Immer draft */
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function isDraft(value: any): boolean {
|
||||||
|
return !!value && !!value[DRAFT_STATE]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if the given value can be drafted by Immer */
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function isDraftable(value: any): boolean {
|
||||||
|
if (!value) return false
|
||||||
|
return (
|
||||||
|
isPlainObject(value) ||
|
||||||
|
Array.isArray(value) ||
|
||||||
|
!!value[DRAFTABLE] ||
|
||||||
|
!!value.constructor?.[DRAFTABLE] ||
|
||||||
|
isMap(value) ||
|
||||||
|
isSet(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectCtorString = Object.prototype.constructor.toString()
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function isPlainObject(value: any): boolean {
|
||||||
|
if (!value || typeof value !== "object") return false
|
||||||
|
const proto = getPrototypeOf(value)
|
||||||
|
if (proto === null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const Ctor =
|
||||||
|
Object.hasOwnProperty.call(proto, "constructor") && proto.constructor
|
||||||
|
|
||||||
|
if (Ctor === Object) return true
|
||||||
|
|
||||||
|
return (
|
||||||
|
typeof Ctor == "function" &&
|
||||||
|
Function.toString.call(Ctor) === objectCtorString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the underlying object that is represented by the given draft */
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function original<T>(value: T): T | undefined
|
||||||
|
export function original(value: Drafted<any>): any {
|
||||||
|
if (!isDraft(value)) die(15, value)
|
||||||
|
return value[DRAFT_STATE].base_
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each iterates a map, set or array.
|
||||||
|
* Or, if any other kind of object, all of its own properties.
|
||||||
|
* Regardless whether they are enumerable or symbols
|
||||||
|
*/
|
||||||
|
export function each<T extends Objectish>(
|
||||||
|
obj: T,
|
||||||
|
iter: (key: string | number, value: any, source: T) => void
|
||||||
|
): void
|
||||||
|
export function each(obj: any, iter: any) {
|
||||||
|
if (getArchtype(obj) === ArchType.Object) {
|
||||||
|
Reflect.ownKeys(obj).forEach(key => {
|
||||||
|
iter(key, obj[key], obj)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
obj.forEach((entry: any, index: any) => iter(index, entry, obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function getArchtype(thing: any): ArchType {
|
||||||
|
const state: undefined | ImmerState = thing[DRAFT_STATE]
|
||||||
|
return state
|
||||||
|
? state.type_
|
||||||
|
: Array.isArray(thing)
|
||||||
|
? ArchType.Array
|
||||||
|
: isMap(thing)
|
||||||
|
? ArchType.Map
|
||||||
|
: isSet(thing)
|
||||||
|
? ArchType.Set
|
||||||
|
: ArchType.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function has(thing: any, prop: PropertyKey): boolean {
|
||||||
|
return getArchtype(thing) === ArchType.Map
|
||||||
|
? thing.has(prop)
|
||||||
|
: Object.prototype.hasOwnProperty.call(thing, prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function get(thing: AnyMap | AnyObject, prop: PropertyKey): any {
|
||||||
|
// @ts-ignore
|
||||||
|
return getArchtype(thing) === ArchType.Map ? thing.get(prop) : thing[prop]
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function set(thing: any, propOrOldValue: PropertyKey, value: any) {
|
||||||
|
const t = getArchtype(thing)
|
||||||
|
if (t === ArchType.Map) thing.set(propOrOldValue, value)
|
||||||
|
else if (t === ArchType.Set) {
|
||||||
|
thing.add(value)
|
||||||
|
} else thing[propOrOldValue] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function is(x: any, y: any): boolean {
|
||||||
|
// From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
|
||||||
|
if (x === y) {
|
||||||
|
return x !== 0 || 1 / x === 1 / y
|
||||||
|
} else {
|
||||||
|
return x !== x && y !== y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function isMap(target: any): target is AnyMap {
|
||||||
|
return target instanceof Map
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function isSet(target: any): target is AnySet {
|
||||||
|
return target instanceof Set
|
||||||
|
}
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function latest(state: ImmerState): any {
|
||||||
|
return state.copy_ || state.base_
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#__PURE__*/
|
||||||
|
export function shallowCopy(base: any, strict: StrictMode) {
|
||||||
|
if (isMap(base)) {
|
||||||
|
return new Map(base)
|
||||||
|
}
|
||||||
|
if (isSet(base)) {
|
||||||
|
return new Set(base)
|
||||||
|
}
|
||||||
|
if (Array.isArray(base)) return Array.prototype.slice.call(base)
|
||||||
|
|
||||||
|
const isPlain = isPlainObject(base)
|
||||||
|
|
||||||
|
if (strict === true || (strict === "class_only" && !isPlain)) {
|
||||||
|
// Perform a strict copy
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(base)
|
||||||
|
delete descriptors[DRAFT_STATE as any]
|
||||||
|
let keys = Reflect.ownKeys(descriptors)
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key: any = keys[i]
|
||||||
|
const desc = descriptors[key]
|
||||||
|
if (desc.writable === false) {
|
||||||
|
desc.writable = true
|
||||||
|
desc.configurable = true
|
||||||
|
}
|
||||||
|
// like object.assign, we will read any _own_, get/set accessors. This helps in dealing
|
||||||
|
// with libraries that trap values, like mobx or vue
|
||||||
|
// unlike object.assign, non-enumerables will be copied as well
|
||||||
|
if (desc.get || desc.set)
|
||||||
|
descriptors[key] = {
|
||||||
|
configurable: true,
|
||||||
|
writable: true, // could live with !!desc.set as well here...
|
||||||
|
enumerable: desc.enumerable,
|
||||||
|
value: base[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.create(getPrototypeOf(base), descriptors)
|
||||||
|
} else {
|
||||||
|
// perform a sloppy copy
|
||||||
|
const proto = getPrototypeOf(base)
|
||||||
|
if (proto !== null && isPlain) {
|
||||||
|
return {...base} // assumption: better inner class optimization than the assign below
|
||||||
|
}
|
||||||
|
const obj = Object.create(proto)
|
||||||
|
return Object.assign(obj, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freezes draftable objects. Returns the original object.
|
||||||
|
* By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* @param deep
|
||||||
|
*/
|
||||||
|
export function freeze<T>(obj: T, deep?: boolean): T
|
||||||
|
export function freeze<T>(obj: any, deep: boolean = false): T {
|
||||||
|
if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj
|
||||||
|
if (getArchtype(obj) > 1 /* Map or Set */) {
|
||||||
|
obj.set = obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any
|
||||||
|
}
|
||||||
|
Object.freeze(obj)
|
||||||
|
if (deep)
|
||||||
|
// See #590, don't recurse into non-enumerable / Symbol properties when freezing
|
||||||
|
// So use Object.entries (only string-like, enumerables) instead of each()
|
||||||
|
Object.entries(obj).forEach(([key, value]) => freeze(value, true))
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
function dontMutateFrozenCollections() {
|
||||||
|
die(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFrozen(obj: any): boolean {
|
||||||
|
return Object.isFrozen(obj)
|
||||||
|
}
|
18
node_modules/immer/src/utils/env.ts
generated
vendored
Normal file
18
node_modules/immer/src/utils/env.ts
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Should be no imports here!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sentinel value returned by producers to replace the draft with undefined.
|
||||||
|
*/
|
||||||
|
export const NOTHING: unique symbol = Symbol.for("immer-nothing")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To let Immer treat your class instances as plain immutable objects
|
||||||
|
* (albeit with a custom prototype), you must define either an instance property
|
||||||
|
* or a static property on each of your custom classes.
|
||||||
|
*
|
||||||
|
* Otherwise, your class instance will never be drafted, which means it won't be
|
||||||
|
* safe to mutate in a produce callback.
|
||||||
|
*/
|
||||||
|
export const DRAFTABLE: unique symbol = Symbol.for("immer-draftable")
|
||||||
|
|
||||||
|
export const DRAFT_STATE: unique symbol = Symbol.for("immer-state")
|
48
node_modules/immer/src/utils/errors.ts
generated
vendored
Normal file
48
node_modules/immer/src/utils/errors.ts
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
export const errors =
|
||||||
|
process.env.NODE_ENV !== "production"
|
||||||
|
? [
|
||||||
|
// All error codes, starting by 0:
|
||||||
|
function(plugin: string) {
|
||||||
|
return `The plugin for '${plugin}' has not been loaded into Immer. To enable the plugin, import and call \`enable${plugin}()\` when initializing your application.`
|
||||||
|
},
|
||||||
|
function(thing: string) {
|
||||||
|
return `produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '${thing}'`
|
||||||
|
},
|
||||||
|
"This object has been frozen and should not be mutated",
|
||||||
|
function(data: any) {
|
||||||
|
return (
|
||||||
|
"Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " +
|
||||||
|
data
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.",
|
||||||
|
"Immer forbids circular references",
|
||||||
|
"The first or second argument to `produce` must be a function",
|
||||||
|
"The third argument to `produce` must be a function or undefined",
|
||||||
|
"First argument to `createDraft` must be a plain object, an array, or an immerable object",
|
||||||
|
"First argument to `finishDraft` must be a draft returned by `createDraft`",
|
||||||
|
function(thing: string) {
|
||||||
|
return `'current' expects a draft, got: ${thing}`
|
||||||
|
},
|
||||||
|
"Object.defineProperty() cannot be used on an Immer draft",
|
||||||
|
"Object.setPrototypeOf() cannot be used on an Immer draft",
|
||||||
|
"Immer only supports deleting array indices",
|
||||||
|
"Immer only supports setting array indices and the 'length' property",
|
||||||
|
function(thing: string) {
|
||||||
|
return `'original' expects a draft, got: ${thing}`
|
||||||
|
}
|
||||||
|
// Note: if more errors are added, the errorOffset in Patches.ts should be increased
|
||||||
|
// See Patches.ts for additional errors
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
|
||||||
|
export function die(error: number, ...args: any[]): never {
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
const e = errors[error]
|
||||||
|
const msg = typeof e === "function" ? e.apply(null, args as any) : e
|
||||||
|
throw new Error(`[Immer] ${msg}`)
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`[Immer] minified error nr: ${error}. Full error at: https://bit.ly/3cXEKWf`
|
||||||
|
)
|
||||||
|
}
|
76
node_modules/immer/src/utils/plugins.ts
generated
vendored
Normal file
76
node_modules/immer/src/utils/plugins.ts
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
ImmerState,
|
||||||
|
Patch,
|
||||||
|
Drafted,
|
||||||
|
ImmerBaseState,
|
||||||
|
AnyMap,
|
||||||
|
AnySet,
|
||||||
|
ArchType,
|
||||||
|
die
|
||||||
|
} from "../internal"
|
||||||
|
|
||||||
|
/** Plugin utilities */
|
||||||
|
const plugins: {
|
||||||
|
Patches?: {
|
||||||
|
generatePatches_(
|
||||||
|
state: ImmerState,
|
||||||
|
basePath: PatchPath,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
): void
|
||||||
|
generateReplacementPatches_(
|
||||||
|
base: any,
|
||||||
|
replacement: any,
|
||||||
|
patches: Patch[],
|
||||||
|
inversePatches: Patch[]
|
||||||
|
): void
|
||||||
|
applyPatches_<T>(draft: T, patches: readonly Patch[]): T
|
||||||
|
}
|
||||||
|
MapSet?: {
|
||||||
|
proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T
|
||||||
|
proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T
|
||||||
|
}
|
||||||
|
} = {}
|
||||||
|
|
||||||
|
type Plugins = typeof plugins
|
||||||
|
|
||||||
|
export function getPlugin<K extends keyof Plugins>(
|
||||||
|
pluginKey: K
|
||||||
|
): Exclude<Plugins[K], undefined> {
|
||||||
|
const plugin = plugins[pluginKey]
|
||||||
|
if (!plugin) {
|
||||||
|
die(0, pluginKey)
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadPlugin<K extends keyof Plugins>(
|
||||||
|
pluginKey: K,
|
||||||
|
implementation: Plugins[K]
|
||||||
|
): void {
|
||||||
|
if (!plugins[pluginKey]) plugins[pluginKey] = implementation
|
||||||
|
}
|
||||||
|
/** Map / Set plugin */
|
||||||
|
|
||||||
|
export interface MapState extends ImmerBaseState {
|
||||||
|
type_: ArchType.Map
|
||||||
|
copy_: AnyMap | undefined
|
||||||
|
assigned_: Map<any, boolean> | undefined
|
||||||
|
base_: AnyMap
|
||||||
|
revoked_: boolean
|
||||||
|
draft_: Drafted<AnyMap, MapState>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetState extends ImmerBaseState {
|
||||||
|
type_: ArchType.Set
|
||||||
|
copy_: AnySet | undefined
|
||||||
|
base_: AnySet
|
||||||
|
drafts_: Map<any, Drafted> // maps the original value to the draft value in the new set
|
||||||
|
revoked_: boolean
|
||||||
|
draft_: Drafted<AnySet, SetState>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Patches plugin */
|
||||||
|
|
||||||
|
export type PatchPath = (string | number)[]
|
21
package-lock.json
generated
Normal file
21
package-lock.json
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "certimate",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.1.1"
|
||||||
|
}
|
||||||
|
}
|
1
ui/dist/assets/index-CWUb5Xuf.css
vendored
Normal file
1
ui/dist/assets/index-CWUb5Xuf.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-DOft-CKV.css
vendored
1
ui/dist/assets/index-DOft-CKV.css
vendored
File diff suppressed because one or more lines are too long
332
ui/dist/assets/index-DbwFzZm1.js
vendored
Normal file
332
ui/dist/assets/index-DbwFzZm1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
332
ui/dist/assets/index-DpHAV802.js
vendored
332
ui/dist/assets/index-DpHAV802.js
vendored
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DpHAV802.js"></script>
|
<script type="module" crossorigin src="/assets/index-DbwFzZm1.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DOft-CKV.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CWUb5Xuf.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background">
|
<body class="bg-background">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
28
ui/package-lock.json
generated
28
ui/package-lock.json
generated
@ -33,6 +33,7 @@
|
|||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide-react": "^0.417.0",
|
"lucide-react": "^0.417.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"nanoid": "^5.0.7",
|
||||||
"pocketbase": "^0.21.4",
|
"pocketbase": "^0.21.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -4159,9 +4160,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "5.0.7",
|
||||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.0.7.tgz",
|
||||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -4169,10 +4170,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bin": {
|
"bin": {
|
||||||
"nanoid": "bin/nanoid.cjs"
|
"nanoid": "bin/nanoid.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^18 || >=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
@ -4561,6 +4562,23 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss/node_modules/nanoid": {
|
||||||
|
"version": "3.3.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
|
||||||
|
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
@ -29,22 +29,23 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"i18next": "^23.15.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
|
"i18next-http-backend": "^2.6.1",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide-react": "^0.417.0",
|
"lucide-react": "^0.417.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"nanoid": "^5.0.7",
|
||||||
"pocketbase": "^0.21.4",
|
"pocketbase": "^0.21.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.52.1",
|
"react-hook-form": "^7.52.1",
|
||||||
|
"react-i18next": "^15.0.2",
|
||||||
"react-router-dom": "^6.25.1",
|
"react-router-dom": "^6.25.1",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.1",
|
"vaul": "^0.9.1",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8"
|
||||||
"i18next": "^23.15.1",
|
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
|
||||||
"i18next-http-backend": "^2.6.1",
|
|
||||||
"react-i18next": "^15.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { Access, accessFormType, getUsageByConfigType } from "@/domain/access";
|
||||||
Access,
|
|
||||||
accessFormType,
|
|
||||||
getUsageByConfigType,
|
|
||||||
LocalConfig,
|
|
||||||
SSHConfig,
|
|
||||||
} from "@/domain/access";
|
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfig } from "@/providers/config";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@ -20,7 +14,7 @@ import {
|
|||||||
} from "../ui/form";
|
} from "../ui/form";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Textarea } from "../ui/textarea";
|
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
@ -39,30 +33,19 @@ const AccessLocalForm = ({
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.form.name.not.empty")
|
||||||
|
.max(64, t("zod.rule.string.max", { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
|
|
||||||
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
|
||||||
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
|
||||||
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: LocalConfig = {
|
|
||||||
command: "sudo service nginx restart",
|
|
||||||
certPath: "/etc/nginx/ssl/certificate.crt",
|
|
||||||
keyPath: "/etc/nginx/ssl/private.key",
|
|
||||||
};
|
|
||||||
if (data) config = data.config as SSHConfig;
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name || '',
|
name: data?.name || "",
|
||||||
configType: "local",
|
configType: "local",
|
||||||
certPath: config.certPath,
|
|
||||||
keyPath: config.keyPath,
|
|
||||||
command: config.command,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,11 +56,7 @@ const AccessLocalForm = ({
|
|||||||
configType: data.configType,
|
configType: data.configType,
|
||||||
usage: getUsageByConfigType(data.configType),
|
usage: getUsageByConfigType(data.configType),
|
||||||
|
|
||||||
config: {
|
config: {},
|
||||||
command: data.command,
|
|
||||||
certPath: data.certPath,
|
|
||||||
keyPath: data.keyPath,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -128,9 +107,12 @@ const AccessLocalForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('name')}</FormLabel>
|
<FormLabel>{t("name")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
<Input
|
||||||
|
placeholder={t("access.form.name.not.empty")}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -143,7 +125,7 @@ const AccessLocalForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
<FormLabel>{t("access.form.config.field")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -158,7 +140,7 @@ const AccessLocalForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
<FormLabel>{t("access.form.config.field")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -168,55 +150,10 @@ const AccessLocalForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="certPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="keyPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="command"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t('save')}</Button>
|
<Button type="submit">{t("save")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
} from "../ui/form";
|
} from "../ui/form";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Textarea } from "../ui/textarea";
|
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
@ -66,7 +65,10 @@ const AccessSSHForm = ({
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, "access.form.name.not.empty")
|
||||||
|
.max(64, t("zod.rule.string.max", { max: 64 })),
|
||||||
configType: accessFormType,
|
configType: accessFormType,
|
||||||
host: z.string().refine(
|
host: z.string().refine(
|
||||||
(str) => {
|
(str) => {
|
||||||
@ -77,16 +79,23 @@ const AccessSSHForm = ({
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
group: z.string().optional(),
|
group: z.string().optional(),
|
||||||
port: z.string().min(1, 'access.form.ssh.port.not.empty').max(5, t('zod.rule.string.max', { max: 5 })),
|
port: z
|
||||||
username: z.string().min(1, 'username.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
.string()
|
||||||
password: z.string().min(0, 'password.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
.min(1, "access.form.ssh.port.not.empty")
|
||||||
key: z.string().min(0, 'access.form.ssh.key.not.empty').max(20480, t('zod.rule.string.max', { max: 20480 })),
|
.max(5, t("zod.rule.string.max", { max: 5 })),
|
||||||
|
username: z
|
||||||
|
.string()
|
||||||
|
.min(1, "username.not.empty")
|
||||||
|
.max(64, t("zod.rule.string.max", { max: 64 })),
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(0, "password.not.empty")
|
||||||
|
.max(64, t("zod.rule.string.max", { max: 64 })),
|
||||||
|
key: z
|
||||||
|
.string()
|
||||||
|
.min(0, "access.form.ssh.key.not.empty")
|
||||||
|
.max(20480, t("zod.rule.string.max", { max: 20480 })),
|
||||||
keyFile: z.any().optional(),
|
keyFile: z.any().optional(),
|
||||||
|
|
||||||
preCommand: z.string().min(0).max(2048, t('zod.rule.string.max', { max: 2048 })).optional(),
|
|
||||||
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
|
||||||
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
|
||||||
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: SSHConfig = {
|
let config: SSHConfig = {
|
||||||
@ -96,10 +105,6 @@ const AccessSSHForm = ({
|
|||||||
password: "",
|
password: "",
|
||||||
key: "",
|
key: "",
|
||||||
keyFile: "",
|
keyFile: "",
|
||||||
preCommand: "",
|
|
||||||
command: "sudo service nginx restart",
|
|
||||||
certPath: "/etc/nginx/ssl/certificate.crt",
|
|
||||||
keyPath: "/etc/nginx/ssl/private.key",
|
|
||||||
};
|
};
|
||||||
if (data) config = data.config as SSHConfig;
|
if (data) config = data.config as SSHConfig;
|
||||||
|
|
||||||
@ -107,7 +112,7 @@ const AccessSSHForm = ({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: data?.name || '',
|
name: data?.name || "",
|
||||||
configType: "ssh",
|
configType: "ssh",
|
||||||
group: data?.group,
|
group: data?.group,
|
||||||
host: config.host,
|
host: config.host,
|
||||||
@ -116,10 +121,6 @@ const AccessSSHForm = ({
|
|||||||
password: config.password,
|
password: config.password,
|
||||||
key: config.key,
|
key: config.key,
|
||||||
keyFile: config.keyFile,
|
keyFile: config.keyFile,
|
||||||
certPath: config.certPath,
|
|
||||||
keyPath: config.keyPath,
|
|
||||||
command: config.command,
|
|
||||||
preCommand: config.preCommand,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,10 +140,6 @@ const AccessSSHForm = ({
|
|||||||
username: data.username,
|
username: data.username,
|
||||||
password: data.password,
|
password: data.password,
|
||||||
key: data.key,
|
key: data.key,
|
||||||
command: data.command,
|
|
||||||
preCommand: data.preCommand,
|
|
||||||
certPath: data.certPath,
|
|
||||||
keyPath: data.keyPath,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -228,9 +225,12 @@ const AccessSSHForm = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('name')}</FormLabel>
|
<FormLabel>{t("name")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
<Input
|
||||||
|
placeholder={t("access.form.name.not.empty")}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -244,12 +244,12 @@ const AccessSSHForm = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="w-full flex justify-between">
|
<FormLabel className="w-full flex justify-between">
|
||||||
<div>{t('access.form.ssh.group.label')}</div>
|
<div>{t("access.form.ssh.group.label")}</div>
|
||||||
<AccessGroupEdit
|
<AccessGroupEdit
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
{t('add')}
|
{t("add")}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -264,7 +264,9 @@ const AccessSSHForm = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder={t('access.group.not.empty')} />
|
<SelectValue
|
||||||
|
placeholder={t("access.group.not.empty")}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="emptyId">
|
<SelectItem value="emptyId">
|
||||||
@ -304,7 +306,7 @@ const AccessSSHForm = ({
|
|||||||
name="id"
|
name="id"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
<FormLabel>{t("access.form.config.field")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -319,7 +321,7 @@ const AccessSSHForm = ({
|
|||||||
name="configType"
|
name="configType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="hidden">
|
<FormItem className="hidden">
|
||||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
<FormLabel>{t("access.form.config.field")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -334,9 +336,12 @@ const AccessSSHForm = ({
|
|||||||
name="host"
|
name="host"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="grow">
|
<FormItem className="grow">
|
||||||
<FormLabel>{t('access.form.ssh.host')}</FormLabel>
|
<FormLabel>{t("access.form.ssh.host")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t('access.form.ssh.host.not.empty')} {...field} />
|
<Input
|
||||||
|
placeholder={t("access.form.ssh.host.not.empty")}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -349,10 +354,10 @@ const AccessSSHForm = ({
|
|||||||
name="port"
|
name="port"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('access.form.ssh.port')}</FormLabel>
|
<FormLabel>{t("access.form.ssh.port")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('access.form.ssh.port.not.empty')}
|
placeholder={t("access.form.ssh.port.not.empty")}
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
@ -369,9 +374,9 @@ const AccessSSHForm = ({
|
|||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('username')}</FormLabel>
|
<FormLabel>{t("username")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t('username.not.empty')} {...field} />
|
<Input placeholder={t("username.not.empty")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -384,10 +389,10 @@ const AccessSSHForm = ({
|
|||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('password')}</FormLabel>
|
<FormLabel>{t("password")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('password.not.empty')}
|
placeholder={t("password.not.empty")}
|
||||||
{...field}
|
{...field}
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
@ -403,9 +408,12 @@ const AccessSSHForm = ({
|
|||||||
name="key"
|
name="key"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden>
|
<FormItem hidden>
|
||||||
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
|
<FormLabel>{t("access.form.ssh.key")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={t('access.form.ssh.key.not.empty')} {...field} />
|
<Input
|
||||||
|
placeholder={t("access.form.ssh.key.not.empty")}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -418,7 +426,7 @@ const AccessSSHForm = ({
|
|||||||
name="keyFile"
|
name="keyFile"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
|
<FormLabel>{t("access.form.ssh.key")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@ -428,10 +436,12 @@ const AccessSSHForm = ({
|
|||||||
className="w-48"
|
className="w-48"
|
||||||
onClick={handleSelectFileClick}
|
onClick={handleSelectFileClick}
|
||||||
>
|
>
|
||||||
{fileName ? fileName : t('access.form.ssh.key.file.not.empty')}
|
{fileName
|
||||||
|
? fileName
|
||||||
|
: t("access.form.ssh.key.file.not.empty")}
|
||||||
</Button>
|
</Button>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('access.form.ssh.key.not.empty')}
|
placeholder={t("access.form.ssh.key.not.empty")}
|
||||||
{...field}
|
{...field}
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
@ -447,70 +457,10 @@ const AccessSSHForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="certPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="keyPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="preCommand"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.pre.command')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea placeholder={t('access.form.ssh.pre.command.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="command"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t('save')}</Button>
|
<Button type="submit">{t("save")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
689
ui/src/components/certimate/DeployList.tsx
Normal file
689
ui/src/components/certimate/DeployList.tsx
Normal file
@ -0,0 +1,689 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { EditIcon, Plus, Trash2 } from "lucide-react";
|
||||||
|
import {
|
||||||
|
DeployConfig,
|
||||||
|
KVType,
|
||||||
|
targetTypeKeys,
|
||||||
|
targetTypeMap,
|
||||||
|
} from "@/domain/domain";
|
||||||
|
import Show from "../Show";
|
||||||
|
import { Alert, AlertDescription } from "../ui/alert";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "../ui/dialog";
|
||||||
|
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { useConfig } from "@/providers/config";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select";
|
||||||
|
import { accessTypeMap } from "@/domain/access";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { AccessEdit } from "./AccessEdit";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Textarea } from "../ui/textarea";
|
||||||
|
import KVList from "./KVList";
|
||||||
|
import { produce } from "immer";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
type DeployEditContextProps = {
|
||||||
|
deploy: DeployConfig;
|
||||||
|
error: Record<string, string>;
|
||||||
|
setDeploy: (deploy: DeployConfig) => void;
|
||||||
|
setError: (error: Record<string, string>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployEditContext = createContext<DeployEditContextProps>(
|
||||||
|
{} as DeployEditContextProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useDeployEditContext = () => {
|
||||||
|
return useContext(DeployEditContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeployListProps = {
|
||||||
|
deploys: DeployConfig[];
|
||||||
|
onChange: (deploys: DeployConfig[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployList = ({ deploys, onChange }: DeployListProps) => {
|
||||||
|
const [list, setList] = useState<DeployConfig[]>([]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setList(deploys);
|
||||||
|
}, [deploys]);
|
||||||
|
|
||||||
|
const handleAdd = (deploy: DeployConfig) => {
|
||||||
|
deploy.id = nanoid();
|
||||||
|
|
||||||
|
const newList = [...list, deploy];
|
||||||
|
|
||||||
|
setList(newList);
|
||||||
|
|
||||||
|
onChange(newList);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
const newList = list.filter((item) => item.id !== id);
|
||||||
|
|
||||||
|
setList(newList);
|
||||||
|
|
||||||
|
onChange(newList);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = (deploy: DeployConfig) => {
|
||||||
|
const newList = list.map((item) => {
|
||||||
|
if (item.id === deploy.id) {
|
||||||
|
return { ...deploy };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
setList(newList);
|
||||||
|
|
||||||
|
onChange(newList);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Show
|
||||||
|
when={list.length > 0}
|
||||||
|
fallback={
|
||||||
|
<Alert className="w-full border dark:border-stone-400">
|
||||||
|
<AlertDescription className="flex flex-col items-center">
|
||||||
|
<div>{t("deployment.not.added")}</div>
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<DeployEditDialog
|
||||||
|
onSave={(config: DeployConfig) => {
|
||||||
|
handleAdd(config);
|
||||||
|
}}
|
||||||
|
trigger={<Button size={"sm"}>{t("add")}</Button>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex justify-end py-2 border-b dark:border-stone-400">
|
||||||
|
<DeployEditDialog
|
||||||
|
trigger={<Button size={"sm"}>{t("add")}</Button>}
|
||||||
|
onSave={(config: DeployConfig) => {
|
||||||
|
handleAdd(config);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full md:w-[35em] rounded mt-5 border dark:border-stone-400">
|
||||||
|
<div className="">
|
||||||
|
{list.map((item) => (
|
||||||
|
<DeployItem
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
onDelete={() => {
|
||||||
|
handleDelete(item.id ?? "");
|
||||||
|
}}
|
||||||
|
onSave={(deploy: DeployConfig) => {
|
||||||
|
handleSave(deploy);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeployItemProps = {
|
||||||
|
item: DeployConfig;
|
||||||
|
onDelete: () => void;
|
||||||
|
onSave: (deploy: DeployConfig) => void;
|
||||||
|
};
|
||||||
|
const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
|
||||||
|
const {
|
||||||
|
config: { accesses },
|
||||||
|
} = useConfig();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const access = accesses.find((access) => access.id === item.access);
|
||||||
|
const getImg = () => {
|
||||||
|
if (!access) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessType = accessTypeMap.get(access.configType);
|
||||||
|
|
||||||
|
if (accessType) {
|
||||||
|
return accessType[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeName = () => {
|
||||||
|
if (!access) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessType = targetTypeMap.get(item.type);
|
||||||
|
|
||||||
|
if (accessType) {
|
||||||
|
return t(accessType[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between text-sm p-3 items-center text-stone-700">
|
||||||
|
<div className="flex space-x-2 items-center">
|
||||||
|
<div>
|
||||||
|
<img src={getImg()} className="w-9"></img>
|
||||||
|
</div>
|
||||||
|
<div className="text-stone-600 flex-col flex space-y-0">
|
||||||
|
<div>{getTypeName()}</div>
|
||||||
|
<div>{access?.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<DeployEditDialog
|
||||||
|
trigger={<EditIcon size={16} className="cursor-pointer" />}
|
||||||
|
deployConfig={item}
|
||||||
|
onSave={(deploy: DeployConfig) => {
|
||||||
|
onSave(deploy);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Trash2
|
||||||
|
size={16}
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
onDelete();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeployEditDialogProps = {
|
||||||
|
trigger: React.ReactNode;
|
||||||
|
deployConfig?: DeployConfig;
|
||||||
|
onSave: (deploy: DeployConfig) => void;
|
||||||
|
};
|
||||||
|
const DeployEditDialog = ({
|
||||||
|
trigger,
|
||||||
|
deployConfig,
|
||||||
|
onSave,
|
||||||
|
}: DeployEditDialogProps) => {
|
||||||
|
const {
|
||||||
|
config: { accesses },
|
||||||
|
} = useConfig();
|
||||||
|
|
||||||
|
const [deployType, setDeployType] = useState<TargetType>();
|
||||||
|
|
||||||
|
const [locDeployConfig, setLocDeployConfig] = useState<DeployConfig>({
|
||||||
|
access: "",
|
||||||
|
type: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [error, setError] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (deployConfig) {
|
||||||
|
setLocDeployConfig({ ...deployConfig });
|
||||||
|
} else {
|
||||||
|
setLocDeployConfig({
|
||||||
|
access: "",
|
||||||
|
type: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [deployConfig]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const temp = locDeployConfig.type.split("-");
|
||||||
|
console.log(temp);
|
||||||
|
let t;
|
||||||
|
if (temp && temp.length > 1) {
|
||||||
|
t = temp[1];
|
||||||
|
} else {
|
||||||
|
t = locDeployConfig.type;
|
||||||
|
}
|
||||||
|
setDeployType(t as TargetType);
|
||||||
|
setError({});
|
||||||
|
}, [locDeployConfig.type]);
|
||||||
|
|
||||||
|
const setDeploy = useCallback(
|
||||||
|
(deploy: DeployConfig) => {
|
||||||
|
if (deploy.type !== locDeployConfig.type) {
|
||||||
|
setLocDeployConfig({ ...deploy, access: "", config: {} });
|
||||||
|
} else {
|
||||||
|
setLocDeployConfig({ ...deploy });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[locDeployConfig.type]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const targetAccesses = accesses.filter((item) => {
|
||||||
|
if (item.usage == "apply") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locDeployConfig.type == "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const types = locDeployConfig.type.split("-");
|
||||||
|
return item.configType === types[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSaveClick = () => {
|
||||||
|
// 验证数据
|
||||||
|
// 保存数据
|
||||||
|
// 清理数据
|
||||||
|
// 关闭弹框
|
||||||
|
const newError = { ...error };
|
||||||
|
if (locDeployConfig.type === "") {
|
||||||
|
newError.type = t("domain.management.edit.access.not.empty.message");
|
||||||
|
} else {
|
||||||
|
newError.type = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locDeployConfig.access === "") {
|
||||||
|
newError.access = t("domain.management.edit.access.not.empty.message");
|
||||||
|
} else {
|
||||||
|
newError.access = "";
|
||||||
|
}
|
||||||
|
setError(newError);
|
||||||
|
|
||||||
|
for (const key in newError) {
|
||||||
|
if (newError[key] !== "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSave(locDeployConfig);
|
||||||
|
|
||||||
|
setLocDeployConfig({
|
||||||
|
access: "",
|
||||||
|
type: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
setError({});
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeployEditContext.Provider
|
||||||
|
value={{
|
||||||
|
deploy: locDeployConfig,
|
||||||
|
setDeploy: setDeploy,
|
||||||
|
error: error,
|
||||||
|
setError: setError,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger>{trigger}</DialogTrigger>
|
||||||
|
<DialogContent className="dark:text-stone-200">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t("deployment")}</DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
{/* 授权类型 */}
|
||||||
|
<div>
|
||||||
|
<Label>{t("deployment.access.type")}</Label>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={locDeployConfig.type}
|
||||||
|
onValueChange={(val: string) => {
|
||||||
|
setDeploy({ ...locDeployConfig, type: val });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-2">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"domain.management.edit.access.not.empty.message"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>
|
||||||
|
{t("domain.management.edit.access.label")}
|
||||||
|
</SelectLabel>
|
||||||
|
{targetTypeKeys.map((item) => (
|
||||||
|
<SelectItem key={item} value={item}>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
className="w-6"
|
||||||
|
src={targetTypeMap.get(item)?.[1]}
|
||||||
|
/>
|
||||||
|
<div>{t(targetTypeMap.get(item)?.[0] ?? "")}</div>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<div className="text-red-500 text-sm mt-1">{error.type}</div>
|
||||||
|
</div>
|
||||||
|
{/* 授权 */}
|
||||||
|
<div>
|
||||||
|
<Label className="flex justify-between">
|
||||||
|
<div>{t("deployment.access.config")}</div>
|
||||||
|
<AccessEdit
|
||||||
|
trigger={
|
||||||
|
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||||
|
<Plus size={14} />
|
||||||
|
{t("add")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
op="add"
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={locDeployConfig.access}
|
||||||
|
onValueChange={(val: string) => {
|
||||||
|
setDeploy({ ...locDeployConfig, access: val });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-2">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"domain.management.edit.access.not.empty.message"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>
|
||||||
|
{t("domain.management.edit.access.label")}
|
||||||
|
</SelectLabel>
|
||||||
|
{targetAccesses.map((item) => (
|
||||||
|
<SelectItem key={item.id} value={item.id}>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
className="w-6"
|
||||||
|
src={accessTypeMap.get(item.configType)?.[1]}
|
||||||
|
/>
|
||||||
|
<div>{item.name}</div>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<div className="text-red-500 text-sm mt-1">{error.access}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DeployEdit type={deployType!} />
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleSaveClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</DeployEditContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type TargetType = "ssh" | "cdn" | "webhook" | "local" | "oss" | "dcdn";
|
||||||
|
|
||||||
|
type DeployEditProps = {
|
||||||
|
type: TargetType;
|
||||||
|
};
|
||||||
|
const DeployEdit = ({ type }: DeployEditProps) => {
|
||||||
|
const getDeploy = () => {
|
||||||
|
switch (type) {
|
||||||
|
case "ssh":
|
||||||
|
return <DeploySSH />;
|
||||||
|
case "local":
|
||||||
|
return <DeploySSH />;
|
||||||
|
case "cdn":
|
||||||
|
return <DeployCDN />;
|
||||||
|
case "dcdn":
|
||||||
|
return <DeployCDN />;
|
||||||
|
case "oss":
|
||||||
|
return <DeployCDN />;
|
||||||
|
case "webhook":
|
||||||
|
return <DeployWebhook />;
|
||||||
|
default:
|
||||||
|
return <DeployCDN />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return getDeploy();
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeploySSH = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setError } = useDeployEditContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setError({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { deploy: data, setDeploy } = useDeployEditContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data.id) {
|
||||||
|
setDeploy({
|
||||||
|
...data,
|
||||||
|
config: {
|
||||||
|
certPath: "/etc/nginx/ssl/nginx.crt",
|
||||||
|
keyPath: "/etc/nginx/ssl/nginx.key",
|
||||||
|
preCommand: "",
|
||||||
|
command: "sudo service nginx reload",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<div>
|
||||||
|
<Label>{t("access.form.ssh.cert.path")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("access.form.ssh.cert.path")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={data?.config?.certPath}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
if (!draft.config) {
|
||||||
|
draft.config = {};
|
||||||
|
}
|
||||||
|
draft.config.certPath = e.target.value;
|
||||||
|
});
|
||||||
|
setDeploy(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>{t("access.form.ssh.key.path")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("access.form.ssh.key.path")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={data?.config?.keyPath}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
if (!draft.config) {
|
||||||
|
draft.config = {};
|
||||||
|
}
|
||||||
|
draft.config.keyPath = e.target.value;
|
||||||
|
});
|
||||||
|
setDeploy(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>{t("access.form.ssh.pre.command")}</Label>
|
||||||
|
<Textarea
|
||||||
|
className="mt-1"
|
||||||
|
value={data?.config?.preCommand}
|
||||||
|
placeholder={t("access.form.ssh.pre.command.not.empty")}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
if (!draft.config) {
|
||||||
|
draft.config = {};
|
||||||
|
}
|
||||||
|
draft.config.preCommand = e.target.value;
|
||||||
|
});
|
||||||
|
setDeploy(newData);
|
||||||
|
}}
|
||||||
|
></Textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>{t("access.form.ssh.command")}</Label>
|
||||||
|
<Textarea
|
||||||
|
className="mt-1"
|
||||||
|
value={data?.config?.command}
|
||||||
|
placeholder={t("access.form.ssh.command.not.empty")}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
if (!draft.config) {
|
||||||
|
draft.config = {};
|
||||||
|
}
|
||||||
|
draft.config.command = e.target.value;
|
||||||
|
});
|
||||||
|
setDeploy(newData);
|
||||||
|
}}
|
||||||
|
></Textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployCDN = () => {
|
||||||
|
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setError({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const resp = domainSchema.safeParse(data.config?.domain);
|
||||||
|
if (!resp.success) {
|
||||||
|
setError({
|
||||||
|
...error,
|
||||||
|
domain: JSON.parse(resp.error.message)[0].message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setError({
|
||||||
|
...error,
|
||||||
|
domain: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const domainSchema = z
|
||||||
|
.string()
|
||||||
|
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
|
message: t("domain.not.empty.verify.message"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<div>
|
||||||
|
<Label>{t("deployment.access.cdn.deploy.to.domain")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("deployment.access.cdn.deploy.to.domain")}
|
||||||
|
className="w-full mt-1"
|
||||||
|
value={data?.config?.domain}
|
||||||
|
onChange={(e) => {
|
||||||
|
const temp = e.target.value;
|
||||||
|
|
||||||
|
const resp = domainSchema.safeParse(temp);
|
||||||
|
if (!resp.success) {
|
||||||
|
setError({
|
||||||
|
...error,
|
||||||
|
domain: JSON.parse(resp.error.message)[0].message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setError({
|
||||||
|
...error,
|
||||||
|
domain: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
if (!draft.config) {
|
||||||
|
draft.config = {};
|
||||||
|
}
|
||||||
|
draft.config.domain = temp;
|
||||||
|
});
|
||||||
|
setDeploy(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployWebhook = () => {
|
||||||
|
const { deploy: data, setDeploy } = useDeployEditContext();
|
||||||
|
|
||||||
|
const { setError } = useDeployEditContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setError({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<KVList
|
||||||
|
variables={data?.config?.variables}
|
||||||
|
onValueChange={(variables: KVType[]) => {
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
if (!draft.config) {
|
||||||
|
draft.config = {};
|
||||||
|
}
|
||||||
|
draft.config.variables = variables;
|
||||||
|
});
|
||||||
|
setDeploy(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeployList;
|
252
ui/src/components/certimate/KVList.tsx
Normal file
252
ui/src/components/certimate/KVList.tsx
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import { KVType } from "@/domain/domain";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Edit, Plus, Trash2 } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Show from "../Show";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "../ui/dialog";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
import { produce } from "immer";
|
||||||
|
|
||||||
|
type KVListProps = {
|
||||||
|
variables?: KVType[];
|
||||||
|
onValueChange?: (variables: KVType[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const KVList = ({ variables, onValueChange }: KVListProps) => {
|
||||||
|
const [locVariables, setLocVariables] = useState<KVType[]>([]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (variables) {
|
||||||
|
setLocVariables(variables);
|
||||||
|
}
|
||||||
|
}, [variables]);
|
||||||
|
|
||||||
|
const handleAddClick = (variable: KVType) => {
|
||||||
|
// 查看是否存在key,存在则更新,不存在则添加
|
||||||
|
const index = locVariables.findIndex((item) => {
|
||||||
|
return item.key === variable.key;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newList = produce(locVariables, (draft) => {
|
||||||
|
if (index === -1) {
|
||||||
|
draft.push(variable);
|
||||||
|
} else {
|
||||||
|
draft[index] = variable;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setLocVariables(newList);
|
||||||
|
|
||||||
|
onValueChange?.(newList);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteClick = (index: number) => {
|
||||||
|
const newList = [...locVariables];
|
||||||
|
newList.splice(index, 1);
|
||||||
|
setLocVariables(newList);
|
||||||
|
|
||||||
|
onValueChange?.(newList);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditClick = (index: number, variable: KVType) => {
|
||||||
|
const newList = [...locVariables];
|
||||||
|
newList[index] = variable;
|
||||||
|
setLocVariables(newList);
|
||||||
|
|
||||||
|
onValueChange?.(newList);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex justify-between dark:text-stone-200">
|
||||||
|
<Label>{t("variable")}</Label>
|
||||||
|
<Show when={!!locVariables?.length}>
|
||||||
|
<KVEdit
|
||||||
|
variable={{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<div className="flex items-center text-primary">
|
||||||
|
<Plus size={16} className="cursor-pointer " />
|
||||||
|
|
||||||
|
<div className="text-sm ">{t("add")}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onSave={(variable) => {
|
||||||
|
handleAddClick(variable);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Show
|
||||||
|
when={!!locVariables?.length}
|
||||||
|
fallback={
|
||||||
|
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
{t("variable.not.added")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<KVEdit
|
||||||
|
trigger={
|
||||||
|
<div className="flex items-center text-primary">
|
||||||
|
<Plus size={16} className="cursor-pointer " />
|
||||||
|
|
||||||
|
<div className="text-sm ">{t("add")}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
variable={{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
}}
|
||||||
|
onSave={(variable) => {
|
||||||
|
handleAddClick(variable);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="border p-3 rounded-md text-stone-700 text-sm dark:text-stone-200">
|
||||||
|
{locVariables?.map((item, index) => (
|
||||||
|
<div key={index} className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
{item.key}={item.value}
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<KVEdit
|
||||||
|
trigger={<Edit size={16} className="cursor-pointer" />}
|
||||||
|
variable={item}
|
||||||
|
onSave={(variable) => {
|
||||||
|
handleEditClick(index, variable);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Trash2
|
||||||
|
size={16}
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteClick(index);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type KVEditProps = {
|
||||||
|
variable?: KVType;
|
||||||
|
trigger: React.ReactNode;
|
||||||
|
onSave: (variable: KVType) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
|
||||||
|
const [locVariable, setLocVariable] = useState<KVType>({
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (variable) setLocVariable(variable!);
|
||||||
|
}, [variable]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [err, setErr] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
|
const handleSaveClick = () => {
|
||||||
|
if (!locVariable.key) {
|
||||||
|
setErr({
|
||||||
|
key: t("variable.name.required"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!locVariable.value) {
|
||||||
|
setErr({
|
||||||
|
value: t("variable.value.required"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSave?.(locVariable);
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
|
||||||
|
setErr({});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={() => {
|
||||||
|
setOpen(!open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTrigger>{trigger}</DialogTrigger>
|
||||||
|
<DialogContent className="dark:text-stone-200">
|
||||||
|
<DialogHeader className="flex flex-col">
|
||||||
|
<DialogTitle>{t("variable")}</DialogTitle>
|
||||||
|
|
||||||
|
<div className="pt-5 flex flex-col items-start">
|
||||||
|
<Label>{t("variable.name")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("variable.name.placeholder")}
|
||||||
|
value={locVariable?.key}
|
||||||
|
onChange={(e) => {
|
||||||
|
setLocVariable({ ...locVariable, key: e.target.value });
|
||||||
|
}}
|
||||||
|
className="w-full mt-1"
|
||||||
|
/>
|
||||||
|
<div className="text-red-500 text-sm mt-1">{err?.key}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2 flex flex-col items-start">
|
||||||
|
<Label>{t("variable.value")}</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={t("variable.value.placeholder")}
|
||||||
|
value={locVariable?.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
setLocVariable({ ...locVariable, value: e.target.value });
|
||||||
|
}}
|
||||||
|
className="w-full mt-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="text-red-500 text-sm mt-1">{err?.value}</div>
|
||||||
|
</div>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleSaveClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KVList;
|
@ -20,13 +20,14 @@ import { Edit, Plus, Trash2 } from "lucide-react";
|
|||||||
type StringListProps = {
|
type StringListProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
value: string;
|
value: string;
|
||||||
valueType?: "domain" | "ip";
|
valueType?: ValueType;
|
||||||
onValueChange: (value: string) => void;
|
onValueChange: (value: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const titles: Record<string, string> = {
|
const titles: Record<string, string> = {
|
||||||
domain: "domain",
|
domain: "domain",
|
||||||
ip: "IP",
|
ip: "IP",
|
||||||
|
dns: "dns",
|
||||||
};
|
};
|
||||||
|
|
||||||
const StringList = ({
|
const StringList = ({
|
||||||
@ -100,7 +101,9 @@ const StringList = ({
|
|||||||
when={list.length > 0}
|
when={list.length > 0}
|
||||||
fallback={
|
fallback={
|
||||||
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
|
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
|
||||||
<div className="text-muted-foreground">暂未添加域名</div>
|
<div className="text-muted-foreground">
|
||||||
|
{t("not.added.yet." + valueType)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<StringEdit
|
<StringEdit
|
||||||
value={""}
|
value={""}
|
||||||
@ -150,7 +153,7 @@ const StringList = ({
|
|||||||
|
|
||||||
export default StringList;
|
export default StringList;
|
||||||
|
|
||||||
type ValueType = "domain" | "ip";
|
type ValueType = "domain" | "dns" | "host";
|
||||||
|
|
||||||
type StringEditProps = {
|
type StringEditProps = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -186,7 +189,8 @@ const StringEdit = ({
|
|||||||
|
|
||||||
const schedules: Record<ValueType, z.ZodString> = {
|
const schedules: Record<ValueType, z.ZodString> = {
|
||||||
domain: domainSchema,
|
domain: domainSchema,
|
||||||
ip: ipSchema,
|
dns: ipSchema,
|
||||||
|
host: ipSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSaveClick = useCallback(() => {
|
const onSaveClick = useCallback(() => {
|
||||||
|
115
ui/src/components/ui/breadcrumb.tsx
Normal file
115
ui/src/components/ui/breadcrumb.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Breadcrumb = React.forwardRef<
|
||||||
|
HTMLElement,
|
||||||
|
React.ComponentPropsWithoutRef<"nav"> & {
|
||||||
|
separator?: React.ReactNode
|
||||||
|
}
|
||||||
|
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
||||||
|
Breadcrumb.displayName = "Breadcrumb"
|
||||||
|
|
||||||
|
const BreadcrumbList = React.forwardRef<
|
||||||
|
HTMLOListElement,
|
||||||
|
React.ComponentPropsWithoutRef<"ol">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ol
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
BreadcrumbList.displayName = "BreadcrumbList"
|
||||||
|
|
||||||
|
const BreadcrumbItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentPropsWithoutRef<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li
|
||||||
|
ref={ref}
|
||||||
|
className={cn("inline-flex items-center gap-1.5", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
BreadcrumbItem.displayName = "BreadcrumbItem"
|
||||||
|
|
||||||
|
const BreadcrumbLink = React.forwardRef<
|
||||||
|
HTMLAnchorElement,
|
||||||
|
React.ComponentPropsWithoutRef<"a"> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
>(({ asChild, className, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "a"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
ref={ref}
|
||||||
|
className={cn("transition-colors hover:text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
BreadcrumbLink.displayName = "BreadcrumbLink"
|
||||||
|
|
||||||
|
const BreadcrumbPage = React.forwardRef<
|
||||||
|
HTMLSpanElement,
|
||||||
|
React.ComponentPropsWithoutRef<"span">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<span
|
||||||
|
ref={ref}
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
className={cn("font-normal text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
BreadcrumbPage.displayName = "BreadcrumbPage"
|
||||||
|
|
||||||
|
const BreadcrumbSeparator = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"li">) => (
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? <ChevronRight />}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
||||||
|
|
||||||
|
const BreadcrumbEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
BreadcrumbEllipsis,
|
||||||
|
}
|
@ -91,23 +91,15 @@ export type GodaddyConfig = {
|
|||||||
apiSecret: string;
|
apiSecret: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LocalConfig = {
|
export type LocalConfig = Record<string, string>;
|
||||||
command: string;
|
|
||||||
certPath: string;
|
|
||||||
keyPath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SSHConfig = {
|
export type SSHConfig = {
|
||||||
host: string;
|
host: string;
|
||||||
port: string;
|
port: string;
|
||||||
preCommand?: string;
|
|
||||||
command: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
keyFile?: string;
|
keyFile?: string;
|
||||||
certPath: string;
|
|
||||||
keyPath: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebhookConfig = {
|
export type WebhookConfig = {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Deployment, Pahse } from "./deployment";
|
import { Deployment, Pahse } from "./deployment";
|
||||||
|
|
||||||
export type Domain = {
|
export type Domain = {
|
||||||
id: string;
|
id?: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
crontab: string;
|
crontab: string;
|
||||||
access: string;
|
access: string;
|
||||||
targetAccess?: string;
|
targetAccess?: string;
|
||||||
targetType: string;
|
targetType?: string;
|
||||||
expiredAt?: string;
|
expiredAt?: string;
|
||||||
phase?: Pahse;
|
phase?: Pahse;
|
||||||
phaseSuccess?: boolean;
|
phaseSuccess?: boolean;
|
||||||
@ -31,10 +31,20 @@ export type Domain = {
|
|||||||
deployConfig?: DeployConfig[];
|
deployConfig?: DeployConfig[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type KVType = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type DeployConfig = {
|
export type DeployConfig = {
|
||||||
|
id?: string;
|
||||||
access: string;
|
access: string;
|
||||||
type: string;
|
type: string;
|
||||||
config?: Record<string, string>;
|
config?: {
|
||||||
|
[key: string]: string;
|
||||||
|
} & {
|
||||||
|
variables?: KVType[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApplyConfig = {
|
export type ApplyConfig = {
|
||||||
|
@ -1 +1 @@
|
|||||||
export const version = "Certimate v0.1.19";
|
export const version = "Certimate v0.2.0";
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
"username.not.empty": "Please enter username",
|
"username.not.empty": "Please enter username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"password.not.empty": "Please enter password",
|
"password.not.empty": "Please enter password",
|
||||||
|
"ip.not.empty.verify.message": "Please enter Ip",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"setting": "Settings",
|
"setting": "Settings",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"template": "Template",
|
"template": "Template",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"next": "Next",
|
||||||
"no.data": "No data available",
|
"no.data": "No data available",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"operation": "Operation",
|
"operation": "Operation",
|
||||||
@ -28,12 +30,15 @@
|
|||||||
"variables": "Variables",
|
"variables": "Variables",
|
||||||
"dns": "Domain Name Server",
|
"dns": "Domain Name Server",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"timeout": "Time Out",
|
||||||
|
"not.added.yet.domain": "Domain not added yet.",
|
||||||
|
"not.added.yet.dns": "Nameserver not added yet.",
|
||||||
"create.time": "CreateTime",
|
"create.time": "CreateTime",
|
||||||
"update.time": "UpdateTime",
|
"update.time": "UpdateTime",
|
||||||
"created.in": "Created in",
|
"created.in": "Created in",
|
||||||
"updated.in": "Updated in",
|
"updated.in": "Updated in",
|
||||||
"basic.setting": "Basic Settings",
|
"apply.setting": "Apply Settings",
|
||||||
"advanced.setting": "Advanced Settings",
|
"deploy.setting": "Deploy Settings",
|
||||||
"operation.succeed": "Operation Successful",
|
"operation.succeed": "Operation Successful",
|
||||||
"save.succeed": "Save Successful",
|
"save.succeed": "Save Successful",
|
||||||
"save.failed": "Save Failed",
|
"save.failed": "Save Failed",
|
||||||
@ -121,6 +126,10 @@
|
|||||||
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
|
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
|
||||||
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
|
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
|
||||||
"domain.management.add.succeed.tips": "Domain added successfully",
|
"domain.management.add.succeed.tips": "Domain added successfully",
|
||||||
|
"domain.management.edit.timeout.placeholder": "Timeout (seconds)",
|
||||||
|
"domain.management.edit.deploy.error": "Please save applyment configuration first",
|
||||||
|
"domain.management.enabled.failed": "Enable failed",
|
||||||
|
"domain.management.enabled.without.deployments": "Failed to enable, no deployment configuration found",
|
||||||
"email.add": "Add Email",
|
"email.add": "Add Email",
|
||||||
"email.list": "Email List",
|
"email.list": "Email List",
|
||||||
"email.valid.message": "Please enter a valid email address",
|
"email.valid.message": "Please enter a valid email address",
|
||||||
@ -215,5 +224,20 @@
|
|||||||
"access.form.ssh.pre.command.not.empty": "Command to be executed before deploying the certificate",
|
"access.form.ssh.pre.command.not.empty": "Command to be executed before deploying the certificate",
|
||||||
"access.form.ssh.command": "Command",
|
"access.form.ssh.command": "Command",
|
||||||
"access.form.ssh.command.not.empty": "Please enter command",
|
"access.form.ssh.command.not.empty": "Please enter command",
|
||||||
"access.form.ding.access.token.placeholder": "Signature for signed addition"
|
"access.form.ding.access.token.placeholder": "Signature for signed addition",
|
||||||
|
|
||||||
|
"variable": "Variable",
|
||||||
|
"variable.name": "Name",
|
||||||
|
"variable.value": "Value",
|
||||||
|
"variable.not.added": "Variable not added yet",
|
||||||
|
"variable.name.required": "Variable name cannot be empty",
|
||||||
|
"variable.value.required": "Variable value cannot be empty",
|
||||||
|
"variable.name.placeholder": "Variable name",
|
||||||
|
"variable.value.placeholder": "Variable value",
|
||||||
|
|
||||||
|
"deployment": "Deployment",
|
||||||
|
"deployment.not.added": "Deployment not added yet",
|
||||||
|
"deployment.access.type": "Access Type",
|
||||||
|
"deployment.access.config": "Access Configuration",
|
||||||
|
"deployment.access.cdn.deploy.to.domain": "Deploy to domain"
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
"username.not.empty": "请输入用户名",
|
"username.not.empty": "请输入用户名",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"password.not.empty": "请输入密码",
|
"password.not.empty": "请输入密码",
|
||||||
|
"ip.not.empty.verify.message": "请输入 IP",
|
||||||
"email": "邮箱",
|
"email": "邮箱",
|
||||||
"logout": "退出登录",
|
"logout": "退出登录",
|
||||||
"setting": "设置",
|
"setting": "设置",
|
||||||
"account": "账户",
|
"account": "账户",
|
||||||
"template": "模版",
|
"template": "模版",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
|
"next": "下一步",
|
||||||
"no.data": "暂无数据",
|
"no.data": "暂无数据",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"operation": "操作",
|
"operation": "操作",
|
||||||
@ -28,12 +30,15 @@
|
|||||||
"variables": "变量",
|
"variables": "变量",
|
||||||
"dns": "域名服务器",
|
"dns": "域名服务器",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
|
"timeout": "超时时间",
|
||||||
|
"not.added.yet.domain": "域名未添加",
|
||||||
|
"not.added.yet.dns": "域名服务器暂未添加",
|
||||||
"create.time": "创建时间",
|
"create.time": "创建时间",
|
||||||
"update.time": "更新时间",
|
"update.time": "更新时间",
|
||||||
"created.in": "创建于",
|
"created.in": "创建于",
|
||||||
"updated.in": "更新于",
|
"updated.in": "更新于",
|
||||||
"basic.setting": "基础设置",
|
"apply.setting": "申请设置",
|
||||||
"advanced.setting": "高级设置",
|
"deploy.setting": "部署设置",
|
||||||
"operation.succeed": "操作成功",
|
"operation.succeed": "操作成功",
|
||||||
"save.succeed": "保存成功",
|
"save.succeed": "保存成功",
|
||||||
"save.failed": "保存失败",
|
"save.failed": "保存失败",
|
||||||
@ -121,6 +126,10 @@
|
|||||||
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
|
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
|
||||||
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
|
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
|
||||||
"domain.management.add.succeed.tips": "域名添加成功",
|
"domain.management.add.succeed.tips": "域名添加成功",
|
||||||
|
"domain.management.edit.timeout.placeholder": "超时时间(单位:秒)",
|
||||||
|
"domain.management.edit.deploy.error": "请先保存申请配置",
|
||||||
|
"domain.management.enabled.failed": "启用失败",
|
||||||
|
"domain.management.enabled.without.deployments": "启用失败,请先设置部署配置",
|
||||||
"email.add": "添加邮箱",
|
"email.add": "添加邮箱",
|
||||||
"email.list": "邮箱列表",
|
"email.list": "邮箱列表",
|
||||||
"email.valid.message": "请输入正确的邮箱地址",
|
"email.valid.message": "请输入正确的邮箱地址",
|
||||||
@ -215,5 +224,20 @@
|
|||||||
"access.form.ssh.pre.command.not.empty": "在部署证书前执行的前置命令",
|
"access.form.ssh.pre.command.not.empty": "在部署证书前执行的前置命令",
|
||||||
"access.form.ssh.command": "Command",
|
"access.form.ssh.command": "Command",
|
||||||
"access.form.ssh.command.not.empty": "请输入要执行的命令",
|
"access.form.ssh.command.not.empty": "请输入要执行的命令",
|
||||||
"access.form.ding.access.token.placeholder": "加签的签名"
|
"access.form.ding.access.token.placeholder": "加签的签名",
|
||||||
|
|
||||||
|
"variable": "变量",
|
||||||
|
"variable.name": "变量名",
|
||||||
|
"variable.value": "值",
|
||||||
|
"variable.not.added": "尚未添加变量",
|
||||||
|
"variable.name.required": "变量名不能为空",
|
||||||
|
"variable.value.required": "变量值不能为空",
|
||||||
|
"variable.name.placeholder": "请输入变量名",
|
||||||
|
"variable.value.placeholder": "请输入变量值",
|
||||||
|
|
||||||
|
"deployment": "部署",
|
||||||
|
"deployment.not.added": "暂无部署配置,请添加后开始部署证书吧",
|
||||||
|
"deployment.access.type": "授权类型",
|
||||||
|
"deployment.access.config": "授权配置",
|
||||||
|
"deployment.access.cdn.deploy.to.domain": "部署到域名"
|
||||||
}
|
}
|
||||||
|
@ -23,36 +23,43 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfig } from "@/providers/config";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Domain, targetTypeKeys, targetTypeMap } from "@/domain/domain";
|
import { DeployConfig, Domain } from "@/domain/domain";
|
||||||
import { save, get } from "@/repository/domains";
|
import { save, get } from "@/repository/domains";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { AccessEdit } from "@/components/certimate/AccessEdit";
|
import { AccessEdit } from "@/components/certimate/AccessEdit";
|
||||||
import { accessTypeMap } from "@/domain/access";
|
import { accessTypeMap } from "@/domain/access";
|
||||||
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { EmailsSetting } from "@/domain/settings";
|
import { EmailsSetting } from "@/domain/settings";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import StringList from "@/components/certimate/StringList";
|
import StringList from "@/components/certimate/StringList";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import DeployList from "@/components/certimate/DeployList";
|
||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from "@/components/ui/breadcrumb";
|
||||||
|
|
||||||
const Edit = () => {
|
const Edit = () => {
|
||||||
const {
|
const {
|
||||||
config: { accesses, emails, accessGroups },
|
config: { accesses, emails },
|
||||||
} = useConfig();
|
} = useConfig();
|
||||||
|
|
||||||
const [domain, setDomain] = useState<Domain>();
|
const [domain, setDomain] = useState<Domain>({} as Domain);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [tab, setTab] = useState<"base" | "advance">("base");
|
const [tab, setTab] = useState<"apply" | "deploy">("apply");
|
||||||
|
|
||||||
const [targetType, setTargetType] = useState(domain ? domain.targetType : "");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Parsing query parameters
|
// Parsing query parameters
|
||||||
@ -62,7 +69,6 @@ const Edit = () => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const data = await get(id);
|
const data = await get(id);
|
||||||
setDomain(data);
|
setDomain(data);
|
||||||
setTargetType(data.targetType);
|
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
@ -77,13 +83,8 @@ const Edit = () => {
|
|||||||
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||||
message: "domain.management.edit.dns.access.not.empty.message",
|
message: "domain.management.edit.dns.access.not.empty.message",
|
||||||
}),
|
}),
|
||||||
targetAccess: z.string().optional(),
|
|
||||||
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
|
||||||
message: "domain.management.edit.target.type.not.empty.message",
|
|
||||||
}),
|
|
||||||
variables: z.string().optional(),
|
|
||||||
group: z.string().optional(),
|
|
||||||
nameservers: z.string().optional(),
|
nameservers: z.string().optional(),
|
||||||
|
timeout: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
@ -93,11 +94,8 @@ const Edit = () => {
|
|||||||
domain: "",
|
domain: "",
|
||||||
email: "",
|
email: "",
|
||||||
access: "",
|
access: "",
|
||||||
targetAccess: "",
|
|
||||||
targetType: "",
|
|
||||||
variables: "",
|
|
||||||
group: "",
|
|
||||||
nameservers: "",
|
nameservers: "",
|
||||||
|
timeout: 60,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,64 +104,35 @@ const Edit = () => {
|
|||||||
form.reset({
|
form.reset({
|
||||||
id: domain.id,
|
id: domain.id,
|
||||||
domain: domain.domain,
|
domain: domain.domain,
|
||||||
email: domain.email,
|
email: domain.applyConfig?.email,
|
||||||
access: domain.access,
|
access: domain.applyConfig?.access,
|
||||||
targetAccess: domain.targetAccess,
|
|
||||||
targetType: domain.targetType,
|
nameservers: domain.applyConfig?.nameservers,
|
||||||
variables: domain.variables,
|
timeout: domain.applyConfig?.timeout,
|
||||||
group: domain.group,
|
|
||||||
nameservers: domain.nameservers,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [domain, form]);
|
}, [domain, form]);
|
||||||
|
|
||||||
const targetAccesses = accesses.filter((item) => {
|
|
||||||
if (item.usage == "apply") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetType == "") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const types = targetType.split("-");
|
|
||||||
return item.configType === types[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
const group = data.group == "emptyId" ? "" : data.group;
|
console.log(data);
|
||||||
const targetAccess =
|
|
||||||
data.targetAccess === "emptyId" ? "" : data.targetAccess;
|
|
||||||
if (group == "" && targetAccess == "") {
|
|
||||||
form.setError("group", {
|
|
||||||
type: "manual",
|
|
||||||
message: "domain.management.edit.target.access.verify.msg",
|
|
||||||
});
|
|
||||||
form.setError("targetAccess", {
|
|
||||||
type: "manual",
|
|
||||||
message: "domain.management.edit.target.access.verify.msg",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const req: Domain = {
|
const req: Domain = {
|
||||||
id: data.id as string,
|
id: data.id as string,
|
||||||
crontab: "0 0 * * *",
|
crontab: "0 0 * * *",
|
||||||
domain: data.domain,
|
domain: data.domain,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
access: data.access,
|
access: data.access,
|
||||||
group: group,
|
applyConfig: {
|
||||||
targetAccess: targetAccess,
|
email: data.email ?? "",
|
||||||
targetType: data.targetType,
|
access: data.access,
|
||||||
variables: data.variables,
|
|
||||||
nameservers: data.nameservers,
|
nameservers: data.nameservers,
|
||||||
|
timeout: data.timeout,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await save(req);
|
const resp = await save(req);
|
||||||
let description = t("domain.management.edit.succeed.tips");
|
let description = t("domain.management.edit.succeed.tips");
|
||||||
if (req.id == "") {
|
if (req.id == "") {
|
||||||
description = t("domain.management.add.succeed.tips");
|
description = t("domain.management.add.succeed.tips");
|
||||||
@ -173,7 +142,44 @@ const Edit = () => {
|
|||||||
title: t("succeed"),
|
title: t("succeed"),
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
navigate("/domains");
|
|
||||||
|
if (!domain?.id) setTab("deploy");
|
||||||
|
setDomain({ ...resp });
|
||||||
|
} catch (e) {
|
||||||
|
const err = e as ClientResponseError;
|
||||||
|
|
||||||
|
Object.entries(err.response.data as PbErrorData).forEach(
|
||||||
|
([key, value]) => {
|
||||||
|
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||||
|
type: "manual",
|
||||||
|
message: value.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handelOnDeployListChange = async (list: DeployConfig[]) => {
|
||||||
|
const req = {
|
||||||
|
...domain,
|
||||||
|
deployConfig: list,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const resp = await save(req);
|
||||||
|
let description = t("domain.management.edit.succeed.tips");
|
||||||
|
if (req.id == "") {
|
||||||
|
description = t("domain.management.add.succeed.tips");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: t("succeed"),
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!domain?.id) setTab("deploy");
|
||||||
|
setDomain({ ...resp });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const err = e as ClientResponseError;
|
const err = e as ClientResponseError;
|
||||||
|
|
||||||
@ -195,45 +201,74 @@ const Edit = () => {
|
|||||||
<div className="">
|
<div className="">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className=" h-5 text-muted-foreground">
|
<div className=" h-5 text-muted-foreground">
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink href="#/domains">
|
||||||
|
{t("domain.management.name")}
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>
|
||||||
{domain?.id ? t("domain.edit") : t("domain.add")}
|
{domain?.id ? t("domain.edit") : t("domain.add")}
|
||||||
|
</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
|
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
|
||||||
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex">
|
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex md:mt-5">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer text-right",
|
"cursor-pointer text-right",
|
||||||
tab === "base" ? "text-primary" : ""
|
tab === "apply" ? "text-primary" : ""
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTab("base");
|
setTab("apply");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("basic.setting")}
|
{t("apply.setting")}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer text-right",
|
"cursor-pointer text-right",
|
||||||
tab === "advance" ? "text-primary" : ""
|
tab === "deploy" ? "text-primary" : ""
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTab("advance");
|
if (!domain?.id) {
|
||||||
|
toast({
|
||||||
|
title: t("domain.management.edit.deploy.error"),
|
||||||
|
description: t("domain.management.edit.deploy.error"),
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTab("deploy");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("advanced.setting")}
|
{t("deploy.setting")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
<div className="w-full md:w-[35em] bg-gray-100 dark:bg-gray-900 p-5 rounded mt-3 md:mt-0">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-full md:w-[35em] p-5 rounded mt-3 md:mt-0",
|
||||||
|
tab == "deploy" && "hidden"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-8 dark:text-stone-200"
|
className="space-y-8 dark:text-stone-200"
|
||||||
>
|
>
|
||||||
|
{/* 域名 */}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="domain"
|
name="domain"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem>
|
||||||
<>
|
<>
|
||||||
<StringList
|
<StringList
|
||||||
value={field.value}
|
value={field.value}
|
||||||
@ -248,11 +283,12 @@ const Edit = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{/* 邮箱 */}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem>
|
||||||
<FormLabel className="flex w-full justify-between">
|
<FormLabel className="flex w-full justify-between">
|
||||||
<div>
|
<div>
|
||||||
{t("email") +
|
{t("email") +
|
||||||
@ -301,11 +337,12 @@ const Edit = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{/* 授权 */}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="access"
|
name="access"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem>
|
||||||
<FormLabel className="flex w-full justify-between">
|
<FormLabel className="flex w-full justify-between">
|
||||||
<div>
|
<div>
|
||||||
{t("domain.management.edit.dns.access.label")}
|
{t("domain.management.edit.dns.access.label")}
|
||||||
@ -366,198 +403,28 @@ const Edit = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 超时时间 */}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="targetType"
|
name="timeout"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "base"}>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>{t("timeout")}</FormLabel>
|
||||||
{t("domain.management.edit.target.type")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Input
|
||||||
{...field}
|
type="number"
|
||||||
onValueChange={(value) => {
|
|
||||||
setTargetType(value);
|
|
||||||
form.setValue("targetType", value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"domain.management.edit.target.type.not.empty.message"
|
"domain.management.edit.timeout.placeholder"
|
||||||
)}
|
)}
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>
|
|
||||||
{t("domain.management.edit.target.type")}
|
|
||||||
</SelectLabel>
|
|
||||||
{targetTypeKeys.map((key) => (
|
|
||||||
<SelectItem key={key} value={key}>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<img
|
|
||||||
className="w-6"
|
|
||||||
src={targetTypeMap.get(key)?.[1]}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
{t(targetTypeMap.get(key)?.[0] || "")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="targetAccess"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem hidden={tab != "base"}>
|
|
||||||
<FormLabel className="w-full flex justify-between">
|
|
||||||
<div>{t("domain.management.edit.target.access")}</div>
|
|
||||||
<AccessEdit
|
|
||||||
trigger={
|
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
|
||||||
<Plus size={14} />
|
|
||||||
{t("add")}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
op="add"
|
|
||||||
/>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
form.setValue("targetAccess", value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"domain.management.edit.target.access.not.empty.message"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>
|
|
||||||
{t(
|
|
||||||
"domain.management.edit.target.access.content.label"
|
|
||||||
)}{" "}
|
|
||||||
{form.getValues().targetAccess}
|
|
||||||
</SelectLabel>
|
|
||||||
<SelectItem value="emptyId">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
--
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
{targetAccesses.map((item) => (
|
|
||||||
<SelectItem key={item.id} value={item.id}>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<img
|
|
||||||
className="w-6"
|
|
||||||
src={
|
|
||||||
accessTypeMap.get(item.configType)?.[1]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div>{item.name}</div>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="group"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
|
|
||||||
<FormLabel className="w-full flex justify-between">
|
|
||||||
<div>{t("domain.management.edit.group.label")}</div>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
defaultValue="emptyId"
|
onChange={(e) => {
|
||||||
onValueChange={(value) => {
|
form.setValue(
|
||||||
form.setValue("group", value);
|
"timeout",
|
||||||
}}
|
parseInt(e.target.value)
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"domain.management.edit.group.not.empty.message"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="emptyId">
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center space-x-2 rounded cursor-pointer"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
--
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
{accessGroups
|
|
||||||
.filter((item) => {
|
|
||||||
return (
|
|
||||||
item.expand && item.expand?.access.length > 0
|
|
||||||
);
|
);
|
||||||
})
|
}}
|
||||||
.map((item) => (
|
|
||||||
<SelectItem
|
|
||||||
value={item.id ? item.id : ""}
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center space-x-2 rounded cursor-pointer"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="variables"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem hidden={tab != "advance"}>
|
|
||||||
<FormLabel>{t("variables")}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
placeholder={t(
|
|
||||||
"domain.management.edit.variables.placeholder"
|
|
||||||
)}
|
|
||||||
{...field}
|
|
||||||
className="placeholder:whitespace-pre-wrap"
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
@ -566,21 +433,19 @@ const Edit = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* nameservers */}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="nameservers"
|
name="nameservers"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem hidden={tab != "advance"}>
|
<FormItem>
|
||||||
<FormLabel>{t("dns")}</FormLabel>
|
<StringList
|
||||||
<FormControl>
|
value={field.value ?? ""}
|
||||||
<Textarea
|
onValueChange={(val: string) => {
|
||||||
placeholder={t(
|
form.setValue("nameservers", val);
|
||||||
"domain.management.edit.dns.placeholder"
|
}}
|
||||||
)}
|
valueType="dns"
|
||||||
{...field}
|
></StringList>
|
||||||
className="placeholder:whitespace-pre-wrap"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -588,11 +453,28 @@ const Edit = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">{t("save")}</Button>
|
<Button type="submit">
|
||||||
|
{domain?.id ? t("save") : t("next")}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-5 w-full md:w-[35em]",
|
||||||
|
tab == "apply" && "hidden"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<DeployList
|
||||||
|
deploys={domain?.deployConfig ?? []}
|
||||||
|
onChange={(list: DeployConfig[]) => {
|
||||||
|
handelOnDeployListChange(list);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -113,8 +113,8 @@ const Home = () => {
|
|||||||
|
|
||||||
const handleRightNowClick = async (domain: Domain) => {
|
const handleRightNowClick = async (domain: Domain) => {
|
||||||
try {
|
try {
|
||||||
unsubscribeId(domain.id);
|
unsubscribeId(domain.id ?? "");
|
||||||
subscribeId(domain.id, (resp) => {
|
subscribeId(domain.id ?? "", (resp) => {
|
||||||
console.log(resp);
|
console.log(resp);
|
||||||
const updatedDomains = domains.map((domain) => {
|
const updatedDomains = domains.map((domain) => {
|
||||||
if (domain.id === resp.id) {
|
if (domain.id === resp.id) {
|
||||||
@ -282,7 +282,7 @@ const Home = () => {
|
|||||||
<Switch
|
<Switch
|
||||||
checked={domain.enabled}
|
checked={domain.enabled}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
handelCheckedChange(domain.id);
|
handelCheckedChange(domain.id ?? "");
|
||||||
}}
|
}}
|
||||||
></Switch>
|
></Switch>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@ -298,7 +298,7 @@ const Home = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant={"link"}
|
variant={"link"}
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleHistoryClick(domain.id)}
|
onClick={() => handleHistoryClick(domain.id ?? "")}
|
||||||
>
|
>
|
||||||
{t("deployment.log.name")}
|
{t("deployment.log.name")}
|
||||||
</Button>
|
</Button>
|
||||||
@ -363,7 +363,7 @@ const Home = () => {
|
|||||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteClick(domain.id);
|
handleDeleteClick(domain.id ?? "");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("confirm")}
|
{t("confirm")}
|
||||||
@ -376,7 +376,7 @@ const Home = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant={"link"}
|
variant={"link"}
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleEditClick(domain.id)}
|
onClick={() => handleEditClick(domain.id ?? "")}
|
||||||
>
|
>
|
||||||
{t("edit")}
|
{t("edit")}
|
||||||
</Button>
|
</Button>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user