Compare commits

...
90 changed files with 1154 additions and 475 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ type WebsiteSSLSearch struct {
dto.PageInfo
AcmeAccountID string `json:"acmeAccountID"`
Domain string `json:"domain"`
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at expire_date"`
OrderBy string `json:"orderBy" validate:"omitempty,oneof=created_at updated_at expire_date"`
Order string `json:"order" validate:"omitempty,oneof=null ascending descending"`
}
+20 -6
View File
@@ -2,8 +2,10 @@ package repo
import (
"context"
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"gorm.io/gorm"
)
@@ -21,6 +23,7 @@ type ISSLRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
List(opts ...DBOption) ([]model.WebsiteSSL, error)
TryMarkApplying(id uint) (bool, error)
Create(ctx context.Context, ssl *model.WebsiteSSL) error
Save(ssl *model.WebsiteSSL) error
DeleteBy(opts ...DBOption) error
@@ -76,12 +79,12 @@ func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.W
}
func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) {
var website *model.WebsiteSSL
var website model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil {
return website, err
return nil, err
}
return website, nil
return &website, nil
}
func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
@@ -93,14 +96,25 @@ func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
return websites, nil
}
func (w WebsiteSSLRepo) TryMarkApplying(id uint) (bool, error) {
db := getDb().Model(&model.WebsiteSSL{}).
Where("id = ? AND status <> ?", id, constant.SSLApply).
Updates(map[string]interface{}{
"status": constant.SSLApply,
"updated_at": time.Now(),
})
if db.Error != nil {
return false, db.Error
}
return db.RowsAffected > 0, nil
}
func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebsiteSSL) error {
return getTx(ctx).Create(ssl).Error
}
func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error {
return getDb().Model(&model.WebsiteSSL{BaseModel: model.BaseModel{
ID: ssl.ID,
}}).Save(&ssl).Error
return getDb().Save(ssl).Error
}
func (w WebsiteSSLRepo) SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error {
+9
View File
@@ -788,6 +788,15 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err
}
exist, err := dockerCLi.ImageExists(image)
if err != nil {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err
}
if !exist {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s not found", image))
return err
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
+6 -2
View File
@@ -59,8 +59,12 @@ func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreat
acmeAccount.CaDirURL = create.CaDirURL
}
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
var client *ssl.AcmeClient
if err := withLegoLogger(nil, func() error {
var err error
client, err = ssl.NewRegisterClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
return err
}); err != nil {
return nil, err
}
privateKey, err := ssl.GetPrivateKey(client.User.GetPrivateKey(), ssl.KeyType(create.KeyType))
+165 -64
View File
@@ -4,6 +4,7 @@ import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"log"
@@ -12,6 +13,7 @@ import (
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/go-acme/lego/v5/certificate"
@@ -32,11 +34,66 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/1Panel-dev/1Panel/agent/utils/ssl"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
gormv2 "gorm.io/gorm"
)
type WebsiteSSLService struct {
}
const sslObtainTimeout = time.Hour
var legoLogMu sync.Mutex
func withLegoLogger(logger *log.Logger, fn func() error) error {
if logger == nil {
logger = log.New(io.Discard, "", log.LstdFlags)
}
legoLogMu.Lock()
defer legoLogMu.Unlock()
oldLogger := legoLogger.Default()
legoLogger.SetDefault(slog.New(slog.NewTextHandler(logger.Writer(), nil)))
defer legoLogger.SetDefault(oldLogger)
return fn()
}
func withLegoLoggerTimeout(logger *log.Logger, timeout time.Duration, fn func(context.Context) error) error {
return withLegoLogger(logger, func() error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return fn(ctx)
})
}
func newWebsiteSSLLegoClient(ctx context.Context, acmeAccount *model.WebsiteAcmeAccount) (*ssl.AcmeClient, error) {
client, err := ssl.NewAcmeClientWithContext(ctx, acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err == nil {
return client, nil
}
if !errors.Is(err, ssl.ErrAcmeAccountURLMissing) {
return nil, err
}
client, err = ssl.NewRegisterClientWithContext(ctx, acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
return nil, err
}
if client.User.GetRegistration() == nil || client.User.GetRegistration().Location == "" {
return nil, ssl.ErrAcmeAccountURLMissing
}
acmeAccount.URL = client.User.GetRegistration().Location
if acmeAccount.PrivateKey == "" {
return nil, errors.New("private key can not blank")
}
if err := websiteAcmeRepo.Save(*acmeAccount); err != nil {
return nil, err
}
return client, nil
}
type IWebsiteSSLService interface {
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
@@ -66,7 +123,7 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
if search.OrderBy != "" && search.Order != "null" {
opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order))
} else {
opts = append(opts, repo.WithOrderDesc("created_at"))
opts = append(opts, repo.WithOrderDesc("updated_at"))
}
if search.Domain != "" {
opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain))
@@ -99,7 +156,7 @@ func (w WebsiteSSLService) Search(search request.WebsiteSSLListReq) ([]response.
opts []repo.DBOption
result []response.WebsiteSSLDTO
)
opts = append(opts, repo.WithOrderDesc("created_at"))
opts = append(opts, repo.WithOrderDesc("updated_at"))
if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil {
@@ -330,19 +387,23 @@ func (w WebsiteSSLService) AutoRenewSSL(id uint) error {
func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
var (
err error
websiteSSL *model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount
client *ssl.AcmeClient
manualClient *ssl.ManualClient
resource certificate.Resource
err error
websiteSSL *model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount
logFile *os.File
logger *log.Logger
resource certificate.Resource
httpRoot string
)
websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(id))
if err != nil {
return err
}
if websiteSSL.Status == constant.SSLApply {
return buserr.New("InExecuting")
}
acmeAccount, err = websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
return err
@@ -352,20 +413,12 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
domains = append(domains, strings.Split(websiteSSL.Domains, ",")...)
}
if websiteSSL.Provider != constant.DnsManual {
client, err = ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
return err
}
switch websiteSSL.Provider {
case constant.DNSAccount:
dnsAccount, err = websiteDnsRepo.GetFirst(repo.WithByID(websiteSSL.DnsAccountID))
if err != nil {
return err
}
if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization, *websiteSSL); err != nil {
return err
}
case constant.Http:
appInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
@@ -379,55 +432,83 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
return buserr.New("ErrWildcardDomain")
}
}
if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil {
return err
}
httpRoot = path.Join(appInstall.GetPath(), "root")
}
}
websiteSSL.Status = constant.SSLApply
err = websiteSSLRepo.Save(websiteSSL)
marked, err := websiteSSLRepo.TryMarkApplying(websiteSSL.ID)
if err != nil {
return err
}
if !marked {
return buserr.New("InExecuting")
}
websiteSSL.Status = constant.SSLApply
go func() {
logFile, logger := newWebsiteSSLLogger(websiteSSL, autoRenew)
logFile, logger = newWebsiteSSLLogger(websiteSSL, autoRenew)
logFileOwnedByGoroutine := false
if logFile != nil {
defer func() {
if !logFileOwnedByGoroutine {
_ = logFile.Close()
}
}()
}
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
logger.Println(startMsg)
logFileOwnedByGoroutine = true
go func(logFile *os.File, logger *log.Logger) {
if logFile != nil {
defer logFile.Close()
defer func() {
_ = logFile.Close()
}()
}
// lego v5 switched to slog. Bridge it to the existing *log.Logger
// so the SSL apply log is still written to the per-domain file
// under SSLLogDir.
legoLogger.SetDefault(slog.New(slog.NewTextHandler(logger.Writer(), nil)))
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
logger.Println(startMsg)
if websiteSSL.Provider != constant.DnsManual {
privateKey, err := ssl.GetPrivateKeyByType(websiteSSL.KeyType, websiteSSL.PrivateKey)
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
if websiteSSL.IsIp {
resource, err = client.ObtainIPSSL(domains[0], privateKey)
} else {
resource, err = client.ObtainSSL(domains, privateKey)
}
err = withLegoLoggerTimeout(logger, sslObtainTimeout, func(ctx context.Context) error {
client, err := newWebsiteSSLLegoClient(ctx, acmeAccount)
if err != nil {
return err
}
switch websiteSSL.Provider {
case constant.DNSAccount:
if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization, *websiteSSL); err != nil {
return err
}
case constant.Http:
if err = client.UseHTTP(httpRoot); err != nil {
return err
}
}
if websiteSSL.IsIp {
resource, err = client.ObtainIPSSL(ctx, domains[0], privateKey)
} else {
resource, err = client.ObtainSSL(ctx, domains, privateKey)
}
return err
})
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
} else {
manualClient, err = ssl.NewCustomAcmeClient(acmeAccount, logger)
manualClient, err := ssl.NewCustomAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy), logger)
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
resource, err = manualClient.RequestCertificate(context.Background(), websiteSSL)
ctx, cancel := context.WithTimeout(context.Background(), sslObtainTimeout)
resource, err = manualClient.RequestCertificate(ctx, websiteSSL)
cancel()
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
}
@@ -435,10 +516,9 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
websiteSSL.PrivateKey = string(resource.PrivateKey)
websiteSSL.Pem = string(resource.Certificate)
websiteSSL.CertURL = resource.CertURL
certBlock, _ := pem.Decode(resource.Certificate)
cert, err := x509.ParseCertificate(certBlock.Bytes)
cert, err := parseCertificatePEM(resource.Certificate)
if err != nil {
handleError(websiteSSL, err)
handleError(websiteSSL, logger, err)
return
}
websiteSSL.ExpireDate = cert.NotAfter
@@ -494,7 +574,7 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
}
printSSLLog(logger, "PushSSLToNodeSuccess", nil)
}
}()
}(logFile, logger)
return nil
}
@@ -516,16 +596,24 @@ func runShellScriptFile(workDir, shell string, logger *log.Logger) error {
return cmdMgr.Run("bash", file.Name())
}
func handleError(websiteSSL *model.WebsiteSSL, err error) {
func parseCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
certBlock, _ := pem.Decode(certPEM)
if certBlock == nil {
return nil, errors.New("invalid certificate PEM")
}
return x509.ParseCertificate(certBlock.Bytes)
}
func handleError(websiteSSL *model.WebsiteSSL, logger *log.Logger, err error) {
if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError {
websiteSSL.Status = constant.StatusError
} else {
websiteSSL.Status = constant.SSLApplyError
}
websiteSSL.Message = err.Error()
// lego v5 uses slog; use the same global default logger to write the
// failure message to the SSL log.
legoLogger.Default().Error(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()}))
if logger != nil {
logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()}))
}
_ = websiteSSLRepo.Save(websiteSSL)
}
@@ -534,7 +622,7 @@ func (w WebsiteSSLService) GetDNSResolve(req request.WebsiteDNSReq) ([]response.
if err != nil {
return nil, err
}
client, err := ssl.NewCustomAcmeClient(acmeAccount, nil)
client, err := ssl.NewCustomAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy), nil)
if err != nil {
return nil, err
}
@@ -580,7 +668,7 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
for _, id := range ids {
if websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(id)); len(websites) > 0 {
oldSSL, _ := websiteSSLRepo.GetFirst(repo.WithByID(id))
if oldSSL.ID > 0 {
if oldSSL != nil && oldSSL.ID > 0 {
websiteSSLS = append(websiteSSLS, oldSSL.PrimaryDomain)
}
continue
@@ -608,12 +696,13 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
global.LOG.Errorf("Failed to get acme account for SSL revoke, err: %v", err)
return
}
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
global.LOG.Errorf("Failed to create ACME client for SSL revoke, err: %v", err)
return
}
err = client.RevokeSSL([]byte(websiteSSL.Pem))
err = withLegoLogger(nil, func() error {
client, err := newWebsiteSSLLegoClient(context.Background(), acmeAccount)
if err != nil {
return err
}
return client.RevokeSSL([]byte(websiteSSL.Pem))
})
if err != nil {
global.LOG.Errorf("Failed to revoke SSL for domain %s, err: %v", websiteSSL.PrimaryDomain, err)
return
@@ -851,7 +940,13 @@ func (w WebsiteSSLService) SyncForRestart() error {
}
func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error {
websiteSSL, _ := websiteSSLRepo.GetFirst(websiteSSLRepo.WithByMasterSSLID(create.ID))
websiteSSL, err := websiteSSLRepo.GetFirst(websiteSSLRepo.WithByMasterSSLID(create.ID))
if err != nil {
if !errors.Is(err, gormv2.ErrRecordNotFound) {
return err
}
websiteSSL = &model.WebsiteSSL{}
}
websiteSSL.Status = constant.SSLReady
websiteSSL.Provider = constant.FromMaster
websiteSSL.PrimaryDomain = create.PrimaryDomain
@@ -865,8 +960,14 @@ func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error {
websiteSSL.Organization = create.Organization
websiteSSL.MasterSSLID = create.ID
websiteSSL.Domains = create.Domains
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
if websiteSSL.ID == 0 {
if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil {
return err
}
} else {
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
}
}
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) == 0 {
+1 -16
View File
@@ -240,7 +240,7 @@ func (c *CommandHelper) pipeContext() (context.Context, context.CancelFunc) {
func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd {
cmds := make([]*exec.Cmd, 0, len(commands))
for _, item := range commands {
cmdItem := exec.CommandContext(ctx, item.Name, filterEmptyArgs(item.Args)...)
cmdItem := exec.CommandContext(ctx, item.Name, item.Args...)
cmdItem.Env = append(os.Environ(), c.env...)
cmdItem.Env = append(cmdItem.Env, item.Env...)
cmdItem.Dir = c.workDir
@@ -305,7 +305,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
var newContext context.Context
var cancel context.CancelFunc
var outputFile *os.File
arg = filterEmptyArgs(arg)
if c.timeout != 0 {
if c.context == nil {
@@ -412,20 +411,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
}
}
func filterEmptyArgs(args []string) []string {
if len(args) == 0 {
return args
}
filtered := args[:0]
for _, arg := range args {
if arg == "" {
continue
}
filtered = append(filtered, arg)
}
return filtered
}
func contextDone(ctx context.Context) <-chan struct{} {
if ctx == nil {
return nil
+6
View File
@@ -208,6 +208,12 @@ func (c Client) PullImageWithProcessAndOptions(task *task.Task, imageName string
}
return err
}
if msg, ok := progress["errorDetail"]; ok {
return fmt.Errorf("image pull failed, err: %v", msg)
}
if msg, ok := progress["error"]; ok {
return fmt.Errorf("image pull failed, err: %v", msg)
}
status, _ := progress["status"].(string)
if status == "Downloading" || status == "Extracting" {
logProcess(progress, task)
+69 -53
View File
@@ -20,8 +20,6 @@ import (
"strings"
"time"
"golang.org/x/crypto/acme"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
@@ -30,10 +28,9 @@ import (
"github.com/go-acme/lego/v5/certcrypto/compat"
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/registration"
"golang.org/x/crypto/acme"
)
var Orders = make(map[uint]*acme.Order)
// parsePrivateKeyPEM decodes a PEM-encoded private key produced by either
// lego v4 (SEC1 for EC, PKCS#1 for RSA) or lego v5 (PKCS#8 for both). It tries
// PKCS#8 first because that is what v5 and any modern tooling emits, then
@@ -139,6 +136,62 @@ func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) {
}
func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) {
return NewRegisterClientWithContext(context.Background(), acmeAccount, proxy)
}
func NewRegisterClientWithContext(ctx context.Context, acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) {
client, err := newAcmeClient(acmeAccount, proxy, nil)
if err != nil {
return nil, err
}
var reg *legoacme.ExtendedAccount
if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" || (acmeAccount.Type == "custom" && acmeAccount.UseEAB) {
if acmeAccount.Type == "zerossl" {
var res *zeroSSLRes
res, err = getZeroSSLEabCredentials(ctx, acmeAccount.Email)
if err != nil {
return nil, err
}
if res.Success {
acmeAccount.EabKid = res.EabKid
acmeAccount.EabHmacKey = res.EabHmacKey
} else {
return nil, fmt.Errorf("get zero ssl eab credentials failed")
}
}
eabOptions := registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: acmeAccount.EabKid,
HmacEncoded: acmeAccount.EabHmacKey,
}
reg, err = client.Client.Registration.RegisterWithExternalAccountBinding(ctx, eabOptions)
if err != nil {
return nil, err
}
} else {
reg, err = client.Client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
}
client.User.Registration = reg
if reg != nil {
acmeAccount.URL = reg.Location
}
if acmeAccount.PrivateKey == "" {
privateKey, err := GetPrivateKey(client.User.GetPrivateKey(), normalizeKeyType(acmeAccount.KeyType))
if err != nil {
return nil, err
}
acmeAccount.PrivateKey = string(privateKey)
}
return client, nil
}
func newAcmeClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy, registration *legoacme.ExtendedAccount) (*AcmeClient, error) {
var (
priKey crypto.Signer
err error
@@ -165,8 +218,9 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP
}
myUser := &AcmeUser{
Email: acmeAccount.Email,
Key: priKey,
Email: acmeAccount.Email,
Registration: registration,
Key: priKey,
}
config := NewConfigWithProxy(myUser, acmeAccount.Type, acmeAccount.CaDirURL, proxy)
client, err := lego.NewClient(config)
@@ -174,41 +228,6 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP
return nil, err
}
ctx := context.Background()
var reg *legoacme.ExtendedAccount
if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" || (acmeAccount.Type == "custom" && acmeAccount.UseEAB) {
if acmeAccount.Type == "zerossl" {
var res *zeroSSLRes
res, err = getZeroSSLEabCredentials(acmeAccount.Email)
if err != nil {
return nil, err
}
if res.Success {
acmeAccount.EabKid = res.EabKid
acmeAccount.EabHmacKey = res.EabHmacKey
} else {
return nil, fmt.Errorf("get zero ssl eab credentials failed")
}
}
eabOptions := registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: acmeAccount.EabKid,
HmacEncoded: acmeAccount.EabHmacKey,
}
reg, err = client.Registration.RegisterWithExternalAccountBinding(ctx, eabOptions)
if err != nil {
return nil, err
}
} else {
reg, err = client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
}
myUser.Registration = reg
acmeClient := &AcmeClient{
User: myUser,
Client: client,
@@ -252,15 +271,12 @@ func NewConfigWithProxy(user registration.User, accountType, customCaURL string,
}
// lego v5 removed lego.CertificateConfig.KeyType; the key type is now
// taken from the user's account for each ObtainRequest call.
return &lego.Config{
CADirURL: caDirURL,
UserAgent: "1Panel",
User: user,
HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword),
Certificate: lego.CertificateConfig{
Timeout: 60 * time.Second,
},
}
config := lego.NewConfig(user)
config.CADirURL = caDirURL
config.UserAgent = "1Panel"
config.HTTPClient = createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword)
config.Certificate.Timeout = 60 * time.Second
return config
}
func initCertPool() *x509.CertPool {
@@ -319,20 +335,20 @@ func createHTTPClientWithProxy(proxyURL, username, password string) *http.Client
}
}
func getZeroSSLEabCredentials(email string) (*zeroSSLRes, error) {
func getZeroSSLEabCredentials(ctx context.Context, email string) (*zeroSSLRes, error) {
baseURL := "https://api.zerossl.com/acme/eab-credentials-email"
params := url.Values{}
params.Add("email", email)
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
req, err := http.NewRequest("POST", requestURL, nil)
req, err := http.NewRequestWithContext(ctx, "POST", requestURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
client := &http.Client{Timeout: 2 * time.Minute}
defer client.CloseIdleConnections()
resp, err := client.Do(req)
if err != nil {
+97 -47
View File
@@ -7,13 +7,16 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
legoacme "github.com/go-acme/lego/v5/acme"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/challenge/dns01"
"github.com/go-acme/lego/v5/lego"
@@ -28,6 +31,8 @@ import (
// occasionally fail propagation checks against the wrong resolver.
var dnsChallengeMu sync.Mutex
var ErrAcmeAccountURLMissing = errors.New("acme account url is empty")
type AcmeClientOption func(*AcmeClientOptions)
type AcmeClientOptions struct {
@@ -40,18 +45,35 @@ type AcmeClient struct {
User *AcmeUser
ProxyURL string
// dnsChallengeLocked records whether this client currently holds
// dnsChallengeMu. It is set by UseDns and cleared by ObtainSSL/
// ObtainIPSSL once the DNS-01 flow finishes. UseHTTP does not touch it.
dnsChallengeLocked bool
dnsChallengeConfig *dnsChallengeConfig
}
type dnsChallengeConfig struct {
recursiveNameservers []string
timeout time.Duration
disableCNAME bool
}
func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) {
return NewAcmeClientWithContext(context.Background(), acmeAccount, systemProxy)
}
func NewAcmeClientWithContext(ctx context.Context, acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if acmeAccount.Email == "" {
return nil, errors.New("email can not blank")
}
accountURL := strings.TrimSpace(acmeAccount.URL)
if accountURL == "" {
return nil, ErrAcmeAccountURLMissing
}
if strings.TrimSpace(acmeAccount.PrivateKey) == "" {
return nil, errors.New("private key can not blank")
}
client, err := NewRegisterClient(acmeAccount, systemProxy)
client, err := newAcmeClient(acmeAccount, systemProxy, &legoacme.ExtendedAccount{Location: accountURL})
if err != nil {
return nil, err
}
@@ -59,7 +81,11 @@ func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.Syste
}
func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.WebsiteSSL) error {
p, err := getDNSProviderConfig(dnsType, params)
var httpClient *http.Client
if c.Config != nil {
httpClient = c.Config.HTTPClient
}
p, err := getDNSProviderConfig(dnsType, params, httpClient)
if err != nil {
return err
}
@@ -71,46 +97,20 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
nameservers = append(nameservers, websiteSSL.Nameserver2)
}
// Hold the global DNS-01 lock for the entire flow that follows, including
// the Obtain call. lego v5 reads dns01.DefaultClient() inside its own
// propagation-precheck loop, so the lock cannot be released right after
// SetDefaultClient/SetDNS01Provider; it has to span the whole DNS-01
// challenge. ObtainSSL / ObtainIPSSL release it once the request returns.
dnsChallengeMu.Lock()
c.dnsChallengeLocked = true
if websiteSSL.DisableCNAME {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true")
} else {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "false")
}
// lego v5 removed dns01.AddRecursiveNameservers and dns01.AddDNSTimeout;
// configure them via dns01.NewClient(&dns01.Options{...}) + SetDefaultClient.
dns01.SetDefaultClient(dns01.NewClient(&dns01.Options{
RecursiveNameservers: nameservers,
Timeout: dnsTimeOut,
}))
var opts []dns01.ChallengeOption
if websiteSSL.SkipDNS {
opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement())
}
if err := c.Client.Challenge.SetDNS01Provider(p, opts...); err != nil {
c.releaseDNSLock()
return err
}
return nil
}
// releaseDNSLock releases dnsChallengeMu if it was acquired by UseDns.
// Safe to call multiple times; calls after the first one are no-ops.
func (c *AcmeClient) releaseDNSLock() {
if c.dnsChallengeLocked {
c.dnsChallengeLocked = false
dnsChallengeMu.Unlock()
c.dnsChallengeConfig = &dnsChallengeConfig{
recursiveNameservers: append([]string(nil), nameservers...),
timeout: dnsTimeOut,
disableCNAME: websiteSSL.DisableCNAME,
}
return nil
}
func (c *AcmeClient) UseHTTP(path string) error {
@@ -123,11 +123,14 @@ func (c *AcmeClient) UseHTTP(path string) error {
if err != nil {
return err
}
c.dnsChallengeConfig = nil
return nil
}
func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (certificate.Resource, error) {
defer c.releaseDNSLock()
func (c *AcmeClient) ObtainSSL(ctx context.Context, domains []string, privateKey crypto.Signer) (certificate.Resource, error) {
unlockDNSChallenge := c.lockDNSChallenge()
defer unlockDNSChallenge()
// lego v5 disables Common Name by default; explicitly enable it to keep
// the v4 behaviour, so legacy Java/router clients that still rely on the
// CommonName field do not fail TLS handshake.
@@ -138,8 +141,6 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
EnableCommonName: true,
}
ctx := context.Background()
var certificates *certificate.Resource
var err error
@@ -152,7 +153,9 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
if isHTTP503Error(err) && attempt < maxRetryAttempts {
global.LOG.Warnf("ACME server returned 503, retrying in %v (attempt %d/%d)",
retryDelayOn503, attempt, maxRetryAttempts)
time.Sleep(retryDelayOn503)
if err := waitForRetry(ctx, retryDelayOn503); err != nil {
return certificate.Resource{}, err
}
continue
}
@@ -163,8 +166,10 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
return certificate.Resource{}, err
}
func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certificate.Resource, error) {
defer c.releaseDNSLock()
func (c *AcmeClient) ObtainIPSSL(ctx context.Context, ipAddress string, privKey crypto.Signer) (certificate.Resource, error) {
unlockDNSChallenge := c.lockDNSChallenge()
defer unlockDNSChallenge()
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "",
@@ -192,8 +197,6 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
Bundle: true,
}
ctx := context.Background()
var certificates *certificate.Resource
for attempt := 1; attempt <= maxRetryAttempts; attempt++ {
certificates, err = c.Client.Certificate.ObtainForCSR(ctx, req)
@@ -204,7 +207,9 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
if isHTTP503Error(err) && attempt < maxRetryAttempts {
global.LOG.Warnf("ACME server returned 503 for IP SSL, retrying in %v (attempt %d/%d)",
retryDelayOn503, attempt, maxRetryAttempts)
time.Sleep(retryDelayOn503)
if err := waitForRetry(ctx, retryDelayOn503); err != nil {
return certificate.Resource{}, err
}
continue
}
@@ -217,3 +222,48 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
func (c *AcmeClient) RevokeSSL(pemSSL []byte) error {
return c.Client.Certificate.Revoke(context.Background(), pemSSL)
}
func (c *AcmeClient) lockDNSChallenge() func() {
if c.dnsChallengeConfig == nil {
return func() {}
}
dnsChallengeMu.Lock()
oldCNAME, hadCNAME := os.LookupEnv("LEGO_DISABLE_CNAME_SUPPORT")
if c.dnsChallengeConfig.disableCNAME {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true")
} else {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "false")
}
previousClient := dns01.DefaultClient()
// lego v5 removed dns01.AddRecursiveNameservers and dns01.AddDNSTimeout;
// configure them via dns01.NewClient(&dns01.Options{...}) + SetDefaultClient.
dns01.SetDefaultClient(dns01.NewClient(&dns01.Options{
RecursiveNameservers: c.dnsChallengeConfig.recursiveNameservers,
Timeout: c.dnsChallengeConfig.timeout,
}))
return func() {
dns01.SetDefaultClient(previousClient)
if hadCNAME {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", oldCNAME)
} else {
_ = os.Unsetenv("LEGO_DISABLE_CNAME_SUPPORT")
}
dnsChallengeMu.Unlock()
}
}
func waitForRetry(ctx context.Context, delay time.Duration) error {
timer := time.NewTimer(delay)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return nil
}
}
+53 -27
View File
@@ -3,6 +3,8 @@ package ssl
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"time"
"github.com/go-acme/lego/v5/challenge"
@@ -95,7 +97,7 @@ var (
manualDnsTimeout = 10 * time.Minute
)
func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, error) {
func getDNSProviderConfig(dnsType DnsType, params string, httpClient *http.Client) (challenge.Provider, error) {
var (
param DNSParam
p challenge.Provider
@@ -106,7 +108,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
}
switch dnsType {
case AliYun:
config := alidns.NewDefaultConfig()
config := newDNSProviderConfig(alidns.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey
config.APIKey = param.AccessKey
config.PropagationTimeout = propagationTimeout
@@ -114,7 +116,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = alidns.NewDNSProviderConfig(config)
case AliESA:
config := aliesa.NewDefaultConfig()
config := newDNSProviderConfig(aliesa.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey
config.APIKey = param.AccessKey
config.PropagationTimeout = propagationTimeout
@@ -122,7 +124,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = aliesa.NewDNSProviderConfig(config)
case AWSRoute53:
config := route53.NewDefaultConfig()
config := newDNSProviderConfig(route53.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey
config.Region = param.Region
@@ -135,7 +137,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = route53.NewDNSProviderConfig(config)
case CloudFlare:
config := cloudflare.NewDefaultConfig()
config := newDNSProviderConfig(cloudflare.NewDefaultConfig(), httpClient)
config.AuthEmail = param.Email
config.AuthToken = param.APIkey
config.PropagationTimeout = propagationTimeout
@@ -143,7 +145,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = cloudflare.NewDNSProviderConfig(config)
case CloudDns:
config := clouddns.NewDefaultConfig()
config := newDNSProviderConfig(clouddns.NewDefaultConfig(), httpClient)
config.ClientID = param.ClientID
config.Email = param.Email
config.Password = param.Password
@@ -152,7 +154,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = clouddns.NewDNSProviderConfig(config)
case NameCheap:
config := namecheap.NewDefaultConfig()
config := newDNSProviderConfig(namecheap.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APIUser = param.APIUser
config.PropagationTimeout = propagationTimeout
@@ -160,14 +162,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = namecheap.NewDNSProviderConfig(config)
case NameSilo:
config := namesilo.NewDefaultConfig()
config := newDNSProviderConfig(namesilo.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = namesilo.NewDNSProviderConfig(config)
case Godaddy:
config := godaddy.NewDefaultConfig()
config := newDNSProviderConfig(godaddy.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout
@@ -175,7 +177,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = godaddy.NewDNSProviderConfig(config)
case NameCom:
config := namedotcom.NewDefaultConfig()
config := newDNSProviderConfig(namedotcom.NewDefaultConfig(), httpClient)
config.APIToken = param.Token
config.Username = param.APIUser
config.PropagationTimeout = propagationTimeout
@@ -183,7 +185,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = namedotcom.NewDNSProviderConfig(config)
case TencentCloud:
config := tencentcloud.NewDefaultConfig()
config := newDNSProviderConfig(tencentcloud.NewDefaultConfig(), httpClient)
config.SecretID = param.SecretID
config.SecretKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
@@ -191,14 +193,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = tencentcloud.NewDNSProviderConfig(config)
case RainYun:
config := rainyun.NewDefaultConfig()
config := newDNSProviderConfig(rainyun.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = rainyun.NewDNSProviderConfig(config)
case Volcengine:
config := volcengine.NewDefaultConfig()
config := newDNSProviderConfig(volcengine.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey
config.AccessKey = param.AccessKey
config.PropagationTimeout = propagationTimeout
@@ -206,7 +208,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = volcengine.NewDNSProviderConfig(config)
case HuaweiCloud:
config := huaweicloud.NewDefaultConfig()
config := newDNSProviderConfig(huaweicloud.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey
config.Region = param.Region
@@ -218,19 +220,19 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = int32(ttl)
p, err = huaweicloud.NewDNSProviderConfig(config)
case FreeMyIP:
config := freemyip.NewDefaultConfig()
config := newDNSProviderConfig(freemyip.NewDefaultConfig(), httpClient)
config.Token = param.Token
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
p, err = freemyip.NewDNSProviderConfig(config)
case Vercel:
config := vercel.NewDefaultConfig()
config := newDNSProviderConfig(vercel.NewDefaultConfig(), httpClient)
config.AuthToken = param.Token
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
p, err = vercel.NewDNSProviderConfig(config)
case Spaceship:
config := spaceship.NewDefaultConfig()
config := newDNSProviderConfig(spaceship.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout
@@ -238,7 +240,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = spaceship.NewDNSProviderConfig(config)
case WestCN:
config := westcn.NewDefaultConfig()
config := newDNSProviderConfig(westcn.NewDefaultConfig(), httpClient)
config.Username = param.Username
config.Password = param.Password
config.PropagationTimeout = propagationTimeout
@@ -246,7 +248,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = westcn.NewDNSProviderConfig(config)
case ClouDNS:
config := cloudns.NewDefaultConfig()
config := newDNSProviderConfig(cloudns.NewDefaultConfig(), httpClient)
config.AuthID = param.AuthID
config.SubAuthID = param.SubAuthID
config.AuthPassword = param.AuthPassword
@@ -255,7 +257,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = cloudns.NewDNSProviderConfig(config)
case RegRu:
config := regru.NewDefaultConfig()
config := newDNSProviderConfig(regru.NewDefaultConfig(), httpClient)
config.Username = param.Username
config.Password = param.Password
config.PropagationTimeout = propagationTimeout
@@ -263,14 +265,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = regru.NewDNSProviderConfig(config)
case Dynu:
config := dynu.NewDefaultConfig()
config := newDNSProviderConfig(dynu.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = dynu.NewDNSProviderConfig(config)
case Dynadot:
config := dynadot.NewDefaultConfig()
config := newDNSProviderConfig(dynadot.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout
@@ -278,7 +280,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = dynadot.NewDNSProviderConfig(config)
case BaiduCloud:
config := baiducloud.NewDefaultConfig()
config := newDNSProviderConfig(baiducloud.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
@@ -286,7 +288,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = baiducloud.NewDNSProviderConfig(config)
case Ovh:
config := ovh.NewDefaultConfig()
config := newDNSProviderConfig(ovh.NewDefaultConfig(), httpClient)
config.APIEndpoint = param.Endpoint
config.AccessToken = param.AccessToken
config.PropagationTimeout = propagationTimeout
@@ -294,12 +296,12 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = ovh.NewDNSProviderConfig(config)
case AcmeDNS:
config := acmedns.NewDefaultConfig()
config := newDNSProviderConfig(acmedns.NewDefaultConfig(), httpClient)
config.APIBase = param.Endpoint
config.StorageBaseURL = param.BaseURL
p, err = acmedns.NewDNSProviderConfig(config)
case PorkBun:
config := porkbun.NewDefaultConfig()
config := newDNSProviderConfig(porkbun.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey
config.SecretAPIKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
@@ -307,7 +309,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl
p, err = porkbun.NewDNSProviderConfig(config)
case Technitium:
config := technitium.NewDefaultConfig()
config := newDNSProviderConfig(technitium.NewDefaultConfig(), httpClient)
config.BaseURL = param.BaseURL
config.APIToken = param.Token
config.PropagationTimeout = propagationTimeout
@@ -331,3 +333,27 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
}
return p, nil
}
func newDNSProviderConfig[T any](config *T, httpClient *http.Client) *T {
setDNSProviderHTTPClient(config, httpClient)
return config
}
func setDNSProviderHTTPClient(config any, httpClient *http.Client) {
if config == nil || httpClient == nil {
return
}
value := reflect.ValueOf(config)
if value.Kind() != reflect.Ptr || value.IsNil() {
return
}
field := value.Elem().FieldByName("HTTPClient")
if !field.IsValid() || !field.CanSet() {
return
}
httpClientType := reflect.TypeOf((*http.Client)(nil))
if field.Type() != httpClientType {
return
}
field.Set(reflect.ValueOf(httpClient))
}
+60 -12
View File
@@ -10,14 +10,17 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"net"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/go-acme/lego/v5/certificate"
"github.com/miekg/dns"
"golang.org/x/crypto/acme"
"log"
"net"
"strings"
"time"
)
type ManualClient struct {
@@ -30,7 +33,34 @@ type RequestCertRequest struct {
WebsiteSSL *model.WebsiteSSL
}
func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logger) (*ManualClient, error) {
var manualOrders sync.Map
func loadManualOrder(sslID uint) (*acme.Order, bool) {
value, ok := manualOrders.Load(sslID)
if !ok {
return nil, false
}
order, ok := value.(*acme.Order)
return order, ok && order != nil
}
func storeManualOrder(sslID uint, order *acme.Order) {
if order == nil {
manualOrders.Delete(sslID)
return
}
manualOrders.Store(sslID, order)
}
func deleteManualOrder(sslID uint, order *acme.Order) {
if order == nil {
manualOrders.Delete(sslID)
return
}
manualOrders.CompareAndDelete(sslID, order)
}
func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy, logger *log.Logger) (*ManualClient, error) {
key, err := parsePrivateKeyPEM([]byte(acmeAccount.PrivateKey))
if err != nil {
return nil, err
@@ -38,9 +68,20 @@ func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logg
if logger == nil {
logger = log.Default()
}
var (
proxyURL string
proxyUser string
proxyPassword string
)
if proxy != nil {
proxyURL = fmt.Sprintf("%s://%s:%s", proxy.Type, proxy.URL, proxy.Port)
proxyUser = proxy.User
proxyPassword = proxy.Password
}
client := &acme.Client{
Key: key,
HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword),
DirectoryURL: getCaDirURL(acmeAccount.Type, acmeAccount.CaDirURL),
}
return &ManualClient{
@@ -61,7 +102,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
var err error
// Check if we have an existing valid order for this SSL
existingOrder, exists := Orders[websiteSSL.ID]
existingOrder, exists := loadManualOrder(websiteSSL.ID)
if exists && existingOrder != nil {
// Verify the order is still valid (not expired and still pending)
// If Expires is zero, order is still valid (ACME doesn't always set expiry immediately)
@@ -75,7 +116,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
// If extraction failed, fall through to create a new order
}
// Existing order is expired or invalid, remove it
delete(Orders, websiteSSL.ID)
deleteManualOrder(websiteSSL.ID, existingOrder)
}
// Create a new order
@@ -83,7 +124,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
if err != nil {
return nil, err
}
Orders[websiteSSL.ID] = order
storeManualOrder(websiteSSL.ID, order)
return c.extractDNSChallenges(ctx, order)
}
@@ -178,6 +219,9 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
c.logger.Printf("[INFO] [%s] acme: Checking TXT record %s", domain, expectedRecord)
for {
if err := ctx.Err(); err != nil {
return err
}
c.logger.Printf("[INFO] [%s] acme: Checking DNS record propagation.", domain)
var currentRecords map[string][]string
var queryErr error
@@ -218,7 +262,9 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
c.logger.Printf("[INFO] [%s] TXT record mismatch for %s: expected %s, got %v", domain, domain, expectedRecord, providedRecords)
return fmt.Errorf("TXT record mismatch for %s: expected %s, got %v", domain, expectedRecord, providedRecords)
}
time.Sleep(pollingInterval)
if err := waitForRetry(ctx, pollingInterval); err != nil {
return err
}
}
_, err = c.client.Accept(ctx, dnsChallenge)
@@ -226,7 +272,9 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
return fmt.Errorf("failed to accept challenge: %v", err)
}
for {
time.Sleep(pollingInterval)
if err := waitForRetry(ctx, pollingInterval); err != nil {
return err
}
authz, err = c.client.GetAuthorization(ctx, authzURL)
if err != nil {
return fmt.Errorf("failed to get authorization while polling: %v", err)
@@ -299,11 +347,11 @@ func (c *ManualClient) RequestCertificate(ctx context.Context, websiteSSL *model
return res, err
}
order, ok := Orders[websiteSSL.ID]
order, ok := loadManualOrder(websiteSSL.ID)
if !ok {
return res, fmt.Errorf("order not found")
}
defer delete(Orders, websiteSSL.ID)
defer deleteManualOrder(websiteSSL.ID, order)
for _, authzURL := range order.AuthzURLs {
if err := c.handleAuthorization(ctx, authzURL, getNameservers(*websiteSSL)); err != nil {
+1 -16
View File
@@ -219,7 +219,7 @@ func (c *CommandHelper) pipeContext() (context.Context, context.CancelFunc) {
func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd {
cmds := make([]*exec.Cmd, 0, len(commands))
for _, item := range commands {
cmdItem := exec.CommandContext(ctx, item.Name, filterEmptyArgs(item.Args)...)
cmdItem := exec.CommandContext(ctx, item.Name, item.Args...)
cmdItem.Env = append(os.Environ(), c.env...)
cmdItem.Env = append(cmdItem.Env, item.Env...)
cmdItem.Dir = c.workDir
@@ -284,7 +284,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
var newContext context.Context
var cancel context.CancelFunc
var outputFile *os.File
arg = filterEmptyArgs(arg)
if c.timeout != 0 {
if c.context == nil {
@@ -379,20 +378,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
}
}
func filterEmptyArgs(args []string) []string {
if len(args) == 0 {
return args
}
filtered := args[:0]
for _, arg := range args {
if arg == "" {
continue
}
filtered = append(filtered, arg)
}
return filtered
}
func contextDone(ctx context.Context) <-chan struct{} {
if ctx == nil {
return nil
-4
View File
@@ -56,10 +56,6 @@ export const listAppNodes = () => {
return http.get<Array<Setting.NodeAppItem>>(`/core/xpack/nodes/apps/update`, {}, { timeout: TimeoutEnum.T_60S });
};
// enterprise
export const loadNodeByUser = () => {
return http.get<Array<Setting.NodeItem>>(`/core/enterprise/users/nodes`);
};
export const uploadEnterpriseLicense = (params: FormData) => {
return http.upload('/core/enterprise/licenses/upload', params);
};
+1 -1
View File
@@ -141,7 +141,7 @@ export const getSSL = (id: number) => {
};
export const obtainSSL = (req: Website.SSLObtain) => {
return http.post<any>(`/websites/ssl/obtain`, req);
return http.post<any>(`/websites/ssl/obtain`, req, TimeoutEnum.T_10M);
};
export const updateSSL = (req: Website.SSLUpdate) => {
@@ -171,10 +171,11 @@ const closeRightClick = () => {
const disabled = computed(() => {
return function (btn: any) {
let permissionDisabled = false;
const permissionOptions = { nodeAdmin: btn.nodeAdmin === true };
if (btn.permission === true) {
permissionDisabled = !hasManagePermissionAccess();
permissionDisabled = !hasManagePermissionAccess(undefined, permissionOptions);
} else if (btn.permission !== undefined) {
permissionDisabled = !hasPermissionAccess(btn.permission);
permissionDisabled = !hasPermissionAccess(btn.permission, permissionOptions);
}
const buttonDisabled =
typeof btn.disabled === 'function' ? btn.disabled(rightClick.value.currentRow) : btn.disabled;
@@ -206,10 +206,11 @@ const getMoreButtons = (row: any) => {
const isButtonDisabled = (button: FuTableOperationButton, row: any) => {
let permissionDisabled = false;
const permissionOptions = { nodeAdmin: button.nodeAdmin === true };
if (button.permission === true) {
permissionDisabled = !hasManagePermissionAccess();
permissionDisabled = !hasManagePermissionAccess(undefined, permissionOptions);
} else if (button.permission !== undefined) {
permissionDisabled = !hasPermissionAccess(button.permission);
permissionDisabled = !hasPermissionAccess(button.permission, permissionOptions);
}
return permissionDisabled || Boolean(resolveMaybeFn(button.disabled ?? false, row));
};
+1
View File
@@ -14,6 +14,7 @@ export interface FuTableOperationButton {
click?: (row: any) => void;
disabled?: boolean | ((row: any) => boolean);
permission?: true | PermissionBindingValue;
nodeAdmin?: boolean;
show?: boolean | ((row: any) => boolean);
type?: string;
icon?: any;
@@ -38,7 +38,7 @@ const handleChange = (value) => {
const search = async () => {
try {
const res = await listNodes('all');
nodes.value = res.data || [];
nodes.value = res || [];
} catch (error) {}
};
@@ -148,6 +148,61 @@ function initChart() {
}
const grid = props.option.grid || { left: '7%', right: '7%', bottom: '20%' };
const defaultTooltip = {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
switch (props.option.formatStr) {
case 'KB/s':
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
computeSizeFromKBs(item.data) +
'<br/>';
}
break;
case 'KB':
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
computeSizeFromKB(item.data) +
'<br/>';
}
break;
case 'MB':
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
computeSizeFromMB(item.data) +
'<br/>';
}
break;
default:
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
item.data +
props.option.formatStr +
'<br/>';
}
break;
}
return res;
},
};
const tooltip = props.option.tooltip || defaultTooltip;
const option = {
title: [
{
@@ -158,59 +213,10 @@ function initChart() {
],
zlevel: 1,
z: 1,
tooltip: props.option.tooltip || {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
switch (props.option.formatStr) {
case 'KB/s':
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
computeSizeFromKBs(item.data) +
'<br/>';
}
break;
case 'KB':
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
computeSizeFromKB(item.data) +
'<br/>';
}
break;
case 'MB':
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
computeSizeFromMB(item.data) +
'<br/>';
}
break;
default:
for (const item of datas) {
res +=
item.marker +
' ' +
item.seriesName +
i18n.global.t('commons.colon') +
item.data +
props.option.formatStr +
'<br/>';
}
break;
}
return res;
},
tooltip: {
appendToBody: true,
...tooltip,
extraCssText: `${tooltip.extraCssText || ''}; z-index: 3000;`,
},
grid,
legend: itemSelect || {
+2 -1
View File
@@ -1,9 +1,10 @@
import { App, Directive } from 'vue';
import integerInput from './modules/integer';
import permission from './modules/permission';
import permission, { nodeAdminDirective } from './modules/permission';
const directivesList: { [key: string]: Directive } = {
'integer-input': integerInput,
'node-admin': nodeAdminDirective,
permission,
};
+20 -2
View File
@@ -14,6 +14,7 @@ const PERMISSION_DISABLED_ATTR = 'data-permission-disabled';
const PERMISSION_POINTER_EVENTS_ATTR = 'data-permission-pointer-events';
const PERMISSION_NATIVE_DISABLED_ATTR = 'data-permission-native-disabled';
const PERMISSION_TABINDEX_ATTR = 'data-permission-tabindex';
const NODE_ADMIN_PERMISSION_ATTR = 'data-node-admin-permission';
const getDisableTargets = (el: HTMLElement) => {
const targets = [el, ...Array.from(el.querySelectorAll<HTMLElement>('button, input, select, textarea'))];
@@ -102,11 +103,16 @@ const getPermissionMode = (binding: DirectiveBinding<PermissionBindingValue>): P
return binding.arg === 'view' ? 'view' : 'manage';
};
const hasNodeAdminDirective = (el: HTMLElement, vnode: VNode) => {
return el.hasAttribute(NODE_ADMIN_PERMISSION_ATTR) || !!vnode.dirs?.some((item) => item.dir === nodeAdminDirective);
};
const applyPermission = (el: HTMLElement, binding: DirectiveBinding<PermissionBindingValue>, vnode: VNode) => {
const options = { nodeAdmin: hasNodeAdminDirective(el, vnode) };
const disabled =
getPermissionMode(binding) === 'view'
? !hasPermissionAccess(binding.value)
: !hasManagePermissionAccess(binding.value);
? !hasPermissionAccess(binding.value, options)
: !hasManagePermissionAccess(binding.value, options);
const controller = getComponentPermissionController(vnode);
if (controller?.setPermissionDisabled) {
@@ -130,4 +136,16 @@ const permissionDirective: Directive<HTMLElement, PermissionBindingValue> = {
},
};
export const nodeAdminDirective: Directive<HTMLElement, boolean | undefined> = {
mounted(el) {
el.setAttribute(NODE_ADMIN_PERMISSION_ATTR, 'true');
},
updated(el) {
el.setAttribute(NODE_ADMIN_PERMISSION_ATTR, 'true');
},
unmounted(el) {
el.removeAttribute(NODE_ADMIN_PERMISSION_ATTR);
},
};
export default permissionDirective;
+3
View File
@@ -2054,11 +2054,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AI Proxy',
ai_benchmark: 'Benchmark',
groups: 'Group',
hosts: 'Host',
apps: 'App',
openresty: 'OpenResty',
websites: 'Website',
monitor: 'Website Monitor',
containers: 'Container',
files: 'File',
runtimes: 'Runtime',
@@ -2077,6 +2079,7 @@ const message = {
commands: 'Quick Commands',
opsReport: 'Ops Report',
users: 'User Management',
roles: 'Role',
auth: 'Login Authentication',
alert: 'Alert Notifications',
},
+3
View File
@@ -2109,11 +2109,13 @@ const message = {
ai: 'IA',
skills_hub: 'Skills Hub',
ai_proxy: 'Proxy de IA',
ai_benchmark: 'Benchmark',
groups: 'Grupo',
hosts: 'Host',
apps: 'Aplicación',
openresty: 'OpenResty',
websites: 'Sitio web',
monitor: 'Monitoreo de sitios web',
containers: 'Contenedor',
files: 'Archivo',
runtimes: 'Runtime',
@@ -2132,6 +2134,7 @@ const message = {
commands: 'Comandos rápidos',
opsReport: 'Informe de operaciones',
users: 'Gestión de usuarios',
roles: 'Rol',
auth: 'Autenticación de inicio de sesión',
alert: 'Notificaciones de alerta',
},
+3
View File
@@ -2080,11 +2080,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AIプロキシ',
ai_benchmark: 'ベンチマーク',
groups: 'グループ',
hosts: 'ホスト',
apps: 'アプリケーション',
openresty: 'OpenResty',
websites: 'ウェブサイト',
monitor: 'ウェブサイト監視',
containers: 'コンテナ',
files: 'ファイル管理',
runtimes: 'ランタイム',
@@ -2103,6 +2105,7 @@ const message = {
commands: 'クイックコマンド',
opsReport: '運用レポート',
users: 'ユーザー管理',
roles: 'ロール',
auth: 'ログイン認証',
alert: 'アラート通知',
},
+3
View File
@@ -2038,11 +2038,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AI 프록시',
ai_benchmark: '벤치마크',
groups: '그룹',
hosts: '호스트',
apps: '애플리케이션',
openresty: 'OpenResty',
websites: '웹사이트',
monitor: '웹사이트 모니터링',
containers: '컨테이너',
files: '파일 관리',
runtimes: '실행 환경',
@@ -2061,6 +2063,7 @@ const message = {
commands: '빠른 명령',
opsReport: '운영 보고서',
users: '사용자 관리',
roles: '역할',
auth: '로그인 인증',
alert: '경고 알림',
},
+3
View File
@@ -2107,11 +2107,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'Proksi AI',
ai_benchmark: 'Penanda Aras',
groups: 'Kumpulan',
hosts: 'Hos',
apps: 'Aplikasi',
openresty: 'OpenResty',
websites: 'Laman Web',
monitor: 'Pemantauan Laman Web',
containers: 'Kontena',
files: 'Pengurusan Fail',
runtimes: 'Persekitaran Jalankan',
@@ -2130,6 +2132,7 @@ const message = {
commands: 'Perintah Pantas',
opsReport: 'Laporan Operasi',
users: 'Pengurusan pengguna',
roles: 'Peranan',
auth: 'Pengesahan log masuk',
alert: 'Pemberitahuan amaran',
},
+3
View File
@@ -2221,11 +2221,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'Proxy de IA',
ai_benchmark: 'Benchmark',
groups: 'Grupos',
hosts: 'Hosts',
apps: 'Aplicativos',
openresty: 'OpenResty',
websites: 'Sites',
monitor: 'Monitoramento de Sites',
containers: 'Contêineres',
files: 'Gerenciamento de Arquivos',
runtimes: 'Ambientes de Execução',
@@ -2244,6 +2246,7 @@ const message = {
commands: 'Comandos Rápidos',
opsReport: 'Relatório de Operações',
users: 'Gerenciamento de usuários',
roles: 'Função',
auth: 'Autenticação de login',
alert: 'Notificações de alerta',
},
+3
View File
@@ -2094,11 +2094,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AI Proxy',
ai_benchmark: 'Бенчмарк',
groups: 'Группы',
hosts: 'Хосты',
apps: 'Приложения',
openresty: 'OpenResty',
websites: 'Вебсайты',
monitor: 'Мониторинг веб-сайтов',
containers: 'Контейнеры',
files: 'Управление файлами',
runtimes: 'Среды выполнения',
@@ -2117,6 +2119,7 @@ const message = {
commands: 'Быстрые команды',
opsReport: 'Операционный отчет',
users: 'Управление пользователями',
roles: 'Роль',
auth: 'Аутентификация входа',
alert: 'Уведомления о тревогах',
},
+3
View File
@@ -2103,11 +2103,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AI Proxy',
ai_benchmark: 'Benchmark',
groups: 'Grup',
hosts: 'Ana Bilgisayar',
apps: 'Uygulama',
openresty: 'OpenResty',
websites: 'Website',
monitor: 'Website İzleme',
containers: 'Konteyner',
files: 'Dosya',
runtimes: 'Çalışma Zamanı',
@@ -2126,6 +2128,7 @@ const message = {
commands: 'Hızlı Komutlar',
opsReport: 'Operasyon Raporu',
users: 'Kullanıcı yönetimi',
roles: 'Rol',
auth: 'Giriş kimlik doğrulaması',
alert: 'Uyarı bildirimleri',
},
+3
View File
@@ -1950,11 +1950,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AI 網關',
ai_benchmark: '基準測試',
groups: '分組',
hosts: '主機',
apps: '應用',
openresty: 'OpenResty',
websites: '網站',
monitor: '網站監控',
containers: '容器',
files: '檔案管理',
runtimes: '執行環境',
@@ -1973,6 +1975,7 @@ const message = {
commands: '快速指令',
opsReport: '運維報表',
users: '使用者管理',
roles: '角色',
auth: '登入認證',
alert: '告警通知',
},
+3
View File
@@ -1934,11 +1934,13 @@ const message = {
ai: 'AI',
skills_hub: 'Skills Hub',
ai_proxy: 'AI 网关',
ai_benchmark: '基准测试',
groups: '分组',
hosts: '主机',
apps: '应用',
openresty: 'OpenResty',
websites: '网站',
monitor: '网站监控',
containers: '容器',
files: '文件管理',
runtimes: '运行环境',
@@ -1957,6 +1959,7 @@ const message = {
commands: '快速命令',
opsReport: '运维报表',
users: '用户管理',
roles: '角色',
auth: '登陆认证',
alert: '告警通知',
},
+3 -9
View File
@@ -1,5 +1,5 @@
import { Setting } from '@/api/interface/setting';
import { listNodeOptions, loadNodeByUser } from '@/api/modules/setting';
import { listNodeOptions } from '@/api/modules/setting';
import { GlobalStore } from '@/store';
export const changeToLocal = async () => {
@@ -23,15 +23,9 @@ export const changeToLocal = async () => {
};
export async function listNodes(type: string): Promise<Array<Setting.NodeItem>> {
const globalStore = GlobalStore();
try {
if (globalStore.isAdmin) {
const res = await listNodeOptions(type);
return res.data || [];
} else {
const res = await loadNodeByUser();
return res.data || [];
}
const res = await listNodeOptions(type);
return res.data || [];
} catch (error) {
return [];
}
+15 -5
View File
@@ -4,6 +4,9 @@ import { normalizeToManageCode } from '@/utils/permission-codes';
export type PermissionBindingValue = string | string[] | undefined;
export type PermissionMode = 'manage' | 'view';
export type PermissionAccessOptions = {
nodeAdmin?: boolean;
};
const getRoutePermission = (): PermissionBindingValue => {
const route = router.currentRoute.value;
@@ -46,8 +49,15 @@ export const toPermissionList = (value: PermissionBindingValue) => {
return routePermission ? [routePermission] : [];
};
const hasPermissionAccessByMode = (mode: PermissionMode, value?: PermissionBindingValue) => {
const hasPermissionAccessByMode = (
mode: PermissionMode,
value?: PermissionBindingValue,
options: PermissionAccessOptions = {},
) => {
const globalStore = GlobalStore();
if (options.nodeAdmin && globalStore.isNodeAdmin) {
return true;
}
const permissions = toPermissionList(value);
const normalizedPermissions = mode === 'manage' ? permissions.map(toManagePermission).filter(Boolean) : permissions;
if (normalizedPermissions.length === 0) {
@@ -56,10 +66,10 @@ const hasPermissionAccessByMode = (mode: PermissionMode, value?: PermissionBindi
return normalizedPermissions.some((permission) => globalStore.hasPermission(permission));
};
export const hasManagePermissionAccess = (value?: PermissionBindingValue) => {
return hasPermissionAccessByMode('manage', value);
export const hasManagePermissionAccess = (value?: PermissionBindingValue, options?: PermissionAccessOptions) => {
return hasPermissionAccessByMode('manage', value, options);
};
export const hasPermissionAccess = (value?: PermissionBindingValue) => {
return hasPermissionAccessByMode('view', value);
export const hasPermissionAccess = (value?: PermissionBindingValue, options?: PermissionAccessOptions) => {
return hasPermissionAccessByMode('view', value, options);
};
+19 -6
View File
@@ -2,15 +2,16 @@
<div>
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')">
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission @click="onOpenGroupDialog()">
<el-button v-permission v-node-admin @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }}
</el-button>
<el-button-group>
<el-button
v-permission
v-node-admin
plain
:disabled="selects.length === 0"
@click="onBatchChangeStatus('enable')"
@@ -19,22 +20,23 @@
</el-button>
<el-button
v-permission
v-node-admin
plain
:disabled="selects.length === 0"
@click="onBatchChangeStatus('disable')"
>
{{ $t('commons.button.disable') }}
</el-button>
<el-button v-permission plain :disabled="selects.length === 0" @click="onDelete(null)">
<el-button v-permission v-node-admin plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</el-button-group>
<el-button-group>
<el-button v-permission @click="onImport">
<el-button v-permission v-node-admin @click="onImport">
{{ $t('commons.button.import') }}
</el-button>
<el-button v-permission :disabled="selects.length === 0" @click="onExport">
<el-button v-permission v-node-admin :disabled="selects.length === 0" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</el-button-group>
@@ -80,7 +82,12 @@
</el-table-column>
<el-table-column :label="$t('commons.table.group')" min-width="120" prop="group">
<template #default="{ row }">
<fu-select-rw-switch v-permission v-model="row.groupID" @change="updateGroup(row)">
<fu-select-rw-switch
v-permission
v-node-admin
v-model="row.groupID"
@change="updateGroup(row)"
>
<template #read>
{{ row.groupBelong === 'Default' ? $t('commons.table.default') : row.groupBelong }}
</template>
@@ -99,6 +106,7 @@
<template #default="{ row }">
<Status
v-permission
v-node-admin
v-if="row.status === 'Enable'"
@click="onChangeStatus(row.id, 'disable')"
:status="row.status"
@@ -106,6 +114,7 @@
/>
<Status
v-permission
v-node-admin
v-if="row.status === 'Disable'"
@click="onChangeStatus(row.id, 'enable')"
:status="row.status"
@@ -139,6 +148,7 @@
<template #default="{ row }">
<el-button
v-permission
v-node-admin
v-if="hasBackup(row.type)"
@click="loadBackups(row)"
plain
@@ -511,6 +521,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.handle'),
permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => {
onHandle(row);
},
@@ -527,6 +538,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => {
onOpenDialog(row.id + '');
},
@@ -534,6 +546,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => {
onDelete(row);
},
@@ -33,6 +33,7 @@
<el-button
type="primary"
v-permission
v-node-admin
:disabled="dialogData.rowData.status === 'Pending'"
@click="onHandle(dialogData.rowData)"
link
@@ -44,6 +45,7 @@
type="primary"
v-if="dialogData.rowData.status === 'Enable'"
v-permission
v-node-admin
@click="onChangeStatus(dialogData.rowData.id, 'disable')"
link
>
@@ -53,13 +55,21 @@
type="primary"
v-if="dialogData.rowData.status === 'Disable'"
v-permission
v-node-admin
@click="onChangeStatus(dialogData.rowData.id, 'enable')"
link
>
{{ $t('commons.button.enable') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission :disabled="!hasRecords" type="primary" @click="onClean" link>
<el-button
v-permission
v-node-admin
:disabled="!hasRecords"
type="primary"
@click="onClean"
link
>
{{ $t('commons.button.clean') }}
</el-button>
</div>
+19 -6
View File
@@ -2,7 +2,7 @@
<div>
<LayoutContent v-loading="loading" :title="$t('logs.login')">
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-dropdown @command="handleSyncOp" class="mr-2.5">
@@ -12,23 +12,33 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<fu-dropdown-item v-permission command="sync">
<fu-dropdown-item v-permission v-node-admin command="sync">
{{ $t('cronjob.library.syncNow') }}
</fu-dropdown-item>
<fu-dropdown-item v-if="scriptSync === 'Disable'" v-permission command="turnOnSync">
<fu-dropdown-item
v-if="scriptSync === 'Disable'"
v-permission
v-node-admin
command="turnOnSync"
>
{{ $t('cronjob.library.turnOnSync') }}
</fu-dropdown-item>
<fu-dropdown-item v-if="scriptSync === 'Enable'" v-permission command="turnOffSync">
<fu-dropdown-item
v-if="scriptSync === 'Enable'"
v-permission
v-node-admin
command="turnOffSync"
>
{{ $t('cronjob.library.turnOffSync') }}
</fu-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button v-permission type="primary" plain @click="onOpenGroupDialog()">
<el-button v-permission v-node-admin type="primary" plain @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }}
</el-button>
<el-button v-permission plain :disabled="selects.length === 0" @click="onDelete(null)">
<el-button v-permission v-node-admin plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
@@ -326,6 +336,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.clone'),
permission: true,
nodeAdmin: true,
disabled: (row: any) => {
return !row.isSystem;
},
@@ -341,6 +352,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
disabled: (row: any) => {
return row.isSystem;
},
@@ -351,6 +363,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
disabled: (row: any) => {
return row.isSystem;
},
@@ -48,7 +48,13 @@
diskInfo.mountPoint == ''
"
>
<el-button v-permission type="primary" size="small" @click="handlePartition(diskInfo)">
<el-button
v-permission
v-node-admin
type="primary"
size="small"
@click="handlePartition(diskInfo)"
>
{{ $t('disk.handlePartition') }}
</el-button>
</div>
@@ -94,6 +100,7 @@
<el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text>
<el-button
v-permission
v-node-admin
type="primary"
link
v-else-if="row.mountPoint != ''"
@@ -101,7 +108,7 @@
>
{{ $t('disk.unmount') }}
</el-button>
<el-button v-permission type="primary" link v-else @click="mount(row)">
<el-button v-permission v-node-admin type="primary" link v-else @click="mount(row)">
{{ $t('disk.mount') }}
</el-button>
</template>
@@ -147,6 +154,7 @@
<el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text>
<el-button
v-permission
v-node-admin
type="primary"
link
v-else-if="row.mountPoint != ''"
@@ -154,7 +162,7 @@
>
{{ $t('disk.unmount') }}
</el-button>
<el-button v-permission type="primary" link v-else @click="mount(row)">
<el-button v-permission v-node-admin type="primary" link v-else @click="mount(row)">
{{ $t('disk.mount') }}
</el-button>
</template>
@@ -30,20 +30,25 @@
<el-form-item :label="$t('disk.mountPoint')" prop="mountPoint">
<el-input v-model="form.mountPoint">
<template #prepend>
<el-button icon="Folder" v-permission @click="fileRef.acceptParams({ dir: true })" />
<el-button
icon="Folder"
v-permission
v-node-admin
@click="fileRef.acceptParams({ dir: true })"
/>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('disk.autoMount')" prop="autoMount">
<el-switch v-permission v-model="form.autoMount" />
<el-switch v-permission v-node-admin v-model="form.autoMount" />
</el-form-item>
<el-form-item :label="$t('disk.noFail')" prop="noFail" v-if="form.autoMount">
<el-switch v-permission v-model="form.noFail" />
<el-switch v-permission v-node-admin v-model="form.noFail" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission type="primary" @click="submit" :disabled="loading">
<el-button v-permission v-node-admin type="primary" @click="submit" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
@@ -94,6 +94,7 @@
<el-button
class="w-full sm:w-auto !ml-0"
v-permission
v-node-admin
:disabled="selected.length === 0"
:loading="deleteLoading"
@click="deleteSelected(null)"
@@ -152,7 +153,13 @@
<el-button link type="primary" @click.stop="openHistoryRecord(row)">
{{ $t('commons.button.view') }}
</el-button>
<el-button v-permission link type="primary" @click.stop="deleteSelected(row)">
<el-button
v-permission
v-node-admin
link
type="primary"
@click.stop="deleteSelected(row)"
>
{{ $t('commons.button.delete') }}
</el-button>
</template>
@@ -184,6 +191,7 @@
<el-button
v-if="canRestoreSelected"
v-permission
v-node-admin
type="primary"
:loading="restoreLoading"
@click="confirmRestoreSelected"
@@ -32,7 +32,7 @@
<div class="flex justify-start items-center gap-x-4 card-action">
<el-text class="cursor-pointer" @click="handleReset">{{ $t('commons.button.reset') }}</el-text>
<el-divider direction="vertical" class="!mx-0" />
<el-text v-permission class="cursor-pointer ml-0" @click="saveContent()">
<el-text v-permission v-node-admin class="cursor-pointer ml-0" @click="saveContent()">
{{ $t('commons.button.save') }}
</el-text>
<el-divider direction="vertical" class="!mx-0" />
@@ -127,11 +127,11 @@
</el-text>
<template #dropdown>
<el-dropdown-menu>
<fu-dropdown-item v-permission command="dir" class="!px-2">
<fu-dropdown-item v-permission v-node-admin command="dir" class="!px-2">
<svg-icon class="!w-5 !h-5" iconName="p-file-folder"></svg-icon>
{{ $t('file.dir') }}
</fu-dropdown-item>
<fu-dropdown-item v-permission command="file" class="!px-2">
<fu-dropdown-item v-permission v-node-admin command="file" class="!px-2">
<svg-icon class="!w-5 !h-5" iconName="p-file-normal"></svg-icon>
{{ $t('menu.files') }}
</fu-dropdown-item>
@@ -90,6 +90,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: any) => {
singleDel(row.id);
},
@@ -222,7 +222,14 @@
</template>
</el-input>
</div>
<el-button v-permission class="max-w-20" plain type="primary" @click="openAiSearchDrawer">
<el-button
v-permission
v-node-admin
class="max-w-20"
plain
type="primary"
@click="openAiSearchDrawer"
>
{{ $t('file.aiSearch') }}
</el-button>
</div>
@@ -246,11 +253,11 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<fu-dropdown-item v-permission command="dir">
<fu-dropdown-item v-permission v-node-admin command="dir">
<svg-icon iconName="p-file-folder"></svg-icon>
{{ $t('file.dir') }}
</fu-dropdown-item>
<fu-dropdown-item v-permission command="file">
<fu-dropdown-item v-permission v-node-admin command="file">
<svg-icon iconName="p-file-normal"></svg-icon>
{{ $t('menu.files') }}
</fu-dropdown-item>
@@ -264,11 +271,11 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<fu-dropdown-item v-permission @click="openUpload">
<fu-dropdown-item v-permission v-node-admin @click="openUpload">
<el-icon><ElUpload /></el-icon>
{{ $t('commons.button.upload') }}
</fu-dropdown-item>
<fu-dropdown-item v-permission @click="openWget">
<fu-dropdown-item v-permission v-node-admin @click="openWget">
<el-icon><ElDownload /></el-icon>
{{ $t('file.remoteFile') }}
</fu-dropdown-item>
@@ -437,6 +444,7 @@
<el-dropdown-menu>
<fu-dropdown-item
v-permission
v-node-admin
v-for="btn in moreButtons"
:key="btn.label"
@click="btn.action"
@@ -453,6 +461,7 @@
<template v-for="btn in visibleButtons" :key="btn.label">
<el-button
v-permission
v-node-admin
plain
@click="btn.action"
:disabled="selects.length === 0"
@@ -469,6 +478,7 @@
<el-dropdown-menu>
<fu-dropdown-item
v-permission
v-node-admin
v-for="btn in moreButtons"
:key="btn.label"
@click="btn.action"
@@ -555,6 +565,7 @@
<el-button
v-if="row.shareCode"
v-permission
v-node-admin
link
type="primary"
size="large"
@@ -566,6 +577,7 @@
<el-button
v-if="row.favoriteID > 0"
v-permission
v-node-admin
link
type="warning"
size="large"
@@ -576,6 +588,7 @@
<el-button
v-if="hoveredRowPath === row.path"
v-permission
v-node-admin
link
icon="Star"
@click="addToFavorite(row)"
@@ -587,7 +600,7 @@
</el-table-column>
<el-table-column :label="$t('file.mode')" prop="mode" width="80">
<template #default="{ row }">
<el-link v-permission underline="never" @click="openMode(row)">
<el-link v-permission v-node-admin underline="never" @click="openMode(row)">
{{ row.mode }}
</el-link>
</template>
@@ -599,7 +612,7 @@
width="200"
>
<template #default="{ row }">
<el-link v-permission underline="never" @click="openChown(row)">
<el-link v-permission v-node-admin underline="never" @click="openChown(row)">
{{ row.user ? row.user : '-' }} ({{ row.uid }}) /
{{ row.group ? row.group : '-' }} ({{ row.gid }})
</el-link>
@@ -1981,16 +1994,19 @@ const beforeButtons = [
{
label: i18n.global.t('commons.button.copy'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => openMoveBtn('copy', row),
},
{
label: i18n.global.t('file.move'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => openMoveBtn('cut', row),
},
{
label: i18n.global.t('file.paste'),
permission: true,
nodeAdmin: true,
click: openPaste,
disabled: () => {
return !moveOpen.value;
@@ -1999,6 +2015,7 @@ const beforeButtons = [
{
label: i18n.global.t('file.compress'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
openCompress([row]);
},
@@ -2013,6 +2030,7 @@ const beforeButtons = [
{
label: i18n.global.t('file.editPermissions'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
openBatchRole([row]);
},
@@ -2021,6 +2039,7 @@ const beforeButtons = [
label: i18n.global.t('file.setRemark'),
hideOnRemarkBlackList: true,
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
openRemark(row);
},
@@ -2030,6 +2049,7 @@ const afterButtons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
disabled: (row: File.File) => {
return row.name == '.1panel_clash';
},
@@ -2043,6 +2063,7 @@ const afterButtons = [
{
label: i18n.global.t('file.addFavoriteAction'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
addToFavorite(row);
},
@@ -2051,6 +2072,7 @@ const afterButtons = [
{
label: i18n.global.t('file.removeFavoriteAction'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
remove(row?.favoriteID);
},
@@ -2059,6 +2081,7 @@ const afterButtons = [
{
label: i18n.global.t('file.shareFile'),
permission: true,
nodeAdmin: true,
click: openShareFile,
show: (row: File.File) => {
return !row?.isDir && !row?.shareCode;
@@ -2067,6 +2090,7 @@ const afterButtons = [
{
label: i18n.global.t('file.shareCancel'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
removeShareByPath(row.path);
},
@@ -2077,6 +2101,7 @@ const afterButtons = [
{
label: i18n.global.t('file.convert'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
openConvert(row);
},
@@ -2099,6 +2124,7 @@ const rightBtnRename = [
{
label: i18n.global.t('file.rename'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
openRename(row, 'right');
},
@@ -2108,6 +2134,7 @@ const moreBtnRename = [
{
label: i18n.global.t('file.rename'),
permission: true,
nodeAdmin: true,
click: (row: File.File) => {
openRename(row, 'more');
},
@@ -2,18 +2,35 @@
<DrawerPro v-model="open" :header="$t('file.recycleBin')" @close="handleClose" size="large">
<template #content>
<div class="flex space-x-4">
<el-button v-permission @click="clear" type="primary" :disabled="data == null || data.length == 0">
<el-button
v-permission
v-node-admin
@click="clear"
type="primary"
:disabled="data == null || data.length == 0"
>
{{ $t('file.clearRecycleBin') }}
</el-button>
<el-button v-permission @click="patchDelete" :disabled="data == null || selects.length == 0">
<el-button
v-permission
v-node-admin
@click="patchDelete"
:disabled="data == null || selects.length == 0"
>
{{ $t('commons.button.delete') }}
</el-button>
<el-button v-permission @click="patchReduce" :disabled="data == null || selects.length == 0">
<el-button
v-permission
v-node-admin
@click="patchReduce"
:disabled="data == null || selects.length == 0"
>
{{ $t('file.reduce') }}
</el-button>
<el-form-item :label="$t('file.fileRecycleBin')">
<el-switch
v-permission
v-node-admin
v-model="status"
active-value="Enable"
inactive-value="Disable"
@@ -188,11 +205,13 @@ const buttons = [
{
label: i18n.global.t('file.reduce'),
permission: true,
nodeAdmin: true,
click: rdFile,
},
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: singleDel,
},
];
@@ -90,6 +90,7 @@ const buttons = [
{
label: i18n.global.t('file.shareClose'),
permission: true,
nodeAdmin: true,
click: (row: File.FileShareInfo) => {
closeShare(row.path);
},
@@ -63,13 +63,13 @@
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button v-permission v-if="shareInfo" :disabled="loading" @click="cancelShare">
<el-button v-permission v-node-admin v-if="shareInfo" :disabled="loading" @click="cancelShare">
{{ $t('file.shareClose') }}
</el-button>
<el-button v-if="shareInfo" :disabled="loading" @click="copyLink">
{{ $t('file.shareCopyLink') }}
</el-button>
<el-button v-permission type="primary" :disabled="loading" @click="generate">
<el-button v-permission v-node-admin type="primary" :disabled="loading" @click="generate">
{{ shareInfo ? $t('file.shareRegenerate') : $t('file.shareGenerate') }}
</el-button>
</span>
@@ -36,16 +36,22 @@
<el-alert type="info" :closable="false" :title="loadPrompt()" />
</template>
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('firewall.create') }}
</el-button>
<el-button v-if="isBind" v-permission plain @click="onUnBind">
<el-button v-if="isBind" v-permission v-node-admin plain @click="onUnBind">
{{ $t('commons.button.unbind') }}
</el-button>
<el-button v-if="!isBind" v-permission plain @click="onBind">
<el-button v-if="!isBind" v-permission v-node-admin plain @click="onBind">
{{ $t('commons.button.bind') }}
</el-button>
<el-button v-permission @click="onDelete(null)" plain :disabled="selects.length === 0">
<el-button
v-permission
v-node-admin
@click="onDelete(null)"
plain
:disabled="selects.length === 0"
>
{{ $t('commons.button.delete') }}
</el-button>
</template>
@@ -118,6 +124,7 @@
<fu-input-rw-switch
v-model="row.description"
v-permission
v-node-admin
@enter="onChange(row)"
@blur="onChange(row)"
/>
@@ -339,6 +346,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Host.IptablesRules) => {
onDelete(row);
},
@@ -19,17 +19,23 @@
<LayoutContent :title="$t('firewall.forwardRule', 2)" :class="{ mask: !isActive }">
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission @click="onDelete(null)" plain :disabled="selects.length === 0">
<el-button
v-permission
v-node-admin
@click="onDelete(null)"
plain
:disabled="selects.length === 0"
>
{{ $t('commons.button.delete') }}
</el-button>
<el-button-group>
<el-button v-permission @click="onImport">
<el-button v-permission v-node-admin @click="onImport">
{{ $t('commons.button.import') }}
</el-button>
<el-button v-permission :disabled="selects.length === 0" @click="onExport">
<el-button v-permission v-node-admin :disabled="selects.length === 0" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</el-button-group>
@@ -255,6 +261,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Host.RuleForward) => {
onOpenDialog('edit', row);
},
@@ -262,6 +269,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Host.RuleForward) => {
onDelete(row);
},
+15 -4
View File
@@ -29,17 +29,23 @@
</div>
</template>
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission @click="onDelete(null)" plain :disabled="selects.length === 0">
<el-button
v-permission
v-node-admin
@click="onDelete(null)"
plain
:disabled="selects.length === 0"
>
{{ $t('commons.button.delete') }}
</el-button>
<el-button-group>
<el-button v-permission @click="onImport">
<el-button v-permission v-node-admin @click="onImport">
{{ $t('commons.button.import') }}
</el-button>
<el-button v-permission :disabled="selects.length === 0" @click="onExport">
<el-button v-permission v-node-admin :disabled="selects.length === 0" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</el-button-group>
@@ -75,6 +81,7 @@
<el-button
v-if="row.strategy === 'accept'"
v-permission
v-node-admin
@click="onChangeStatus(row, 'drop')"
link
type="success"
@@ -86,6 +93,7 @@
link
type="danger"
v-permission
v-node-admin
@click="onChangeStatus(row, 'accept')"
>
{{ $t('firewall.deny') }}
@@ -102,6 +110,7 @@
<fu-input-rw-switch
v-model="row.description"
v-permission
v-node-admin
@enter="onChange(row)"
@blur="onChange(row)"
/>
@@ -323,6 +332,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Host.RuleIP) => {
onOpenDialog('edit', row);
},
@@ -330,6 +340,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Host.RuleIP) => {
onDelete(row);
},
@@ -43,17 +43,23 @@
</el-alert>
</template>
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission @click="onDelete(null)" plain :disabled="selects.length === 0">
<el-button
v-permission
v-node-admin
@click="onDelete(null)"
plain
:disabled="selects.length === 0"
>
{{ $t('commons.button.delete') }}
</el-button>
<el-button-group>
<el-button v-permission @click="onImport">
<el-button v-permission v-node-admin @click="onImport">
{{ $t('commons.button.import') }}
</el-button>
<el-button v-permission :disabled="selects.length === 0" @click="onExport">
<el-button v-permission v-node-admin :disabled="selects.length === 0" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</el-button-group>
@@ -140,6 +146,7 @@
<el-button
v-if="row.strategy === 'accept'"
v-permission
v-node-admin
@click="onChangeStatus(row, 'drop')"
link
type="success"
@@ -151,6 +158,7 @@
link
type="danger"
v-permission
v-node-admin
@click="onChangeStatus(row, 'accept')"
>
{{ $t('firewall.drop') }}
@@ -173,6 +181,7 @@
<fu-input-rw-switch
v-model="row.description"
v-permission
v-node-admin
@enter="onChange(row)"
@blur="onChange(row)"
/>
@@ -656,6 +665,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Host.RulePort) => {
onOpenDialog('edit', row);
},
@@ -663,6 +673,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Host.RuleInfo) => {
onDelete(row);
},
@@ -12,6 +12,7 @@
<template v-if="baseInfo.name !== 'iptables'">
<el-button
v-permission
v-node-admin
type="primary"
v-if="baseInfo.isActive"
@click="onOperate('stop')"
@@ -21,6 +22,7 @@
</el-button>
<el-button
v-permission
v-node-admin
type="primary"
v-if="!baseInfo.isActive"
@click="onOperate('start')"
@@ -29,29 +31,43 @@
{{ $t('commons.button.start') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" @click="onOperate('restart')" link>
<el-button v-permission v-node-admin type="primary" @click="onOperate('restart')" link>
{{ $t('commons.button.restart') }}
</el-button>
</template>
<template v-if="!baseInfo.isInit || (props.currentTab === 'forward' && !baseInfo.isBind)">
<el-divider direction="vertical" />
<el-button v-permission type="primary" link @click="onInit">
<el-button v-permission v-node-admin type="primary" link @click="onInit">
{{ $t('commons.button.init') }}
</el-button>
</template>
<template v-if="baseInfo.name === 'iptables' && baseInfo.isInit && props.currentTab == 'base'">
<el-divider direction="vertical" />
<el-button v-if="baseInfo.isBind" v-permission type="primary" link @click="onUnBind">
<el-button
v-if="baseInfo.isBind"
v-permission
v-node-admin
type="primary"
link
@click="onUnBind"
>
{{ $t('commons.button.unbind') }}
</el-button>
<el-button v-if="!baseInfo.isBind" v-permission type="primary" link @click="onBind">
<el-button
v-if="!baseInfo.isBind"
v-permission
v-node-admin
type="primary"
link
@click="onBind"
>
{{ $t('commons.button.bind') }}
</el-button>
</template>
<template v-if="props.currentTab == 'base'">
<el-divider direction="vertical" />
<el-button link type="primary" v-permission @click="onOpenWhiteList" plain>
<el-button link type="primary" v-permission v-node-admin @click="onOpenWhiteList" plain>
{{ $t('firewall.portWhiteList') }}
</el-button>
</template>
@@ -61,6 +77,7 @@
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
<el-switch
v-permission
v-node-admin
size="small"
class="ml-2"
inactive-value="Disable"
@@ -11,6 +11,7 @@
<el-form-item :label="$t('monitor.enableMonitor')" prop="monitorStatus">
<el-switch
v-permission
v-node-admin
@change="onSaveStatus"
v-model="form.monitorStatus"
active-value="Enable"
@@ -20,7 +21,7 @@
<el-form-item :label="$t('monitor.storeDays')" prop="monitorStoreDays">
<el-input disabled v-model="form.monitorStoreDays">
<template #append>
<el-button v-permission @click="onChangeStoreDays" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeStoreDays" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -29,7 +30,7 @@
<el-form-item :label="$t('monitor.interval')" prop="monitorIntervalItem">
<el-input disabled v-model="form.monitorIntervalItem">
<template #append>
<el-button v-permission @click="onChangeInterval" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeInterval" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -38,7 +39,7 @@
<el-form-item :label="$t('monitor.defaultNetwork')">
<el-input disabled v-model="form.defaultNetwork">
<template #append>
<el-button v-permission @click="onChangeNetwork" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeNetwork" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -48,7 +49,7 @@
<el-form-item :label="$t('monitor.defaultIO')">
<el-input disabled v-model="form.defaultIO">
<template #append>
<el-button v-permission @click="onChangeIO" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeIO" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -56,7 +57,7 @@
<span class="input-help">{{ $t('monitor.defaultIOHelper') }}</span>
</el-form-item>
<el-form-item>
<el-button v-permission @click="onClean()" icon="Delete">
<el-button v-permission v-node-admin @click="onClean()" icon="Delete">
{{ $t('monitor.cleanMonitor') }}
</el-button>
</el-form-item>
+2 -2
View File
@@ -6,7 +6,7 @@
<div class="mt-2"><el-alert type="info" :title="$t('ssh.sshAlert')" :closable="false" /></div>
</template>
<template #leftToolBar>
<el-button v-permission type="primary" @click="onExport">
<el-button v-permission v-node-admin type="primary" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</template>
@@ -79,7 +79,7 @@
<el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button v-permission type="primary" @click="onSubmitExport" :disabled="loading">
<el-button v-permission v-node-admin type="primary" @click="onSubmitExport" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -42,6 +42,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.disConn'),
permission: true,
nodeAdmin: true,
click: function (row: any) {
stop(row.PID);
},
@@ -4,13 +4,13 @@
<div class="mb-4">
<el-alert :closable="false">{{ $t('ssh.pubKeyHelper', [currentUser]) }}</el-alert>
</div>
<el-button v-permission type="primary" plain @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" plain @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission plain @click="onSync()">
<el-button v-permission v-node-admin plain @click="onSync()">
{{ $t('commons.button.sync') }}
</el-button>
<el-button v-permission plain :disabled="selects.length === 0" @click="onDelete(null)">
<el-button v-permission v-node-admin plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
<ComplexTable
@@ -262,6 +262,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Host.RootCertInfo) => {
onOpenDialog('edit', row);
},
@@ -276,6 +277,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Host.RootCertInfo) => {
onDelete(row);
},
+34 -9
View File
@@ -10,14 +10,28 @@
<Status class="mt-0.5" :status="form.isActive ? 'enable' : 'disable'" :msg="form.message" />
</div>
<div class="mt-0.5">
<el-button v-if="form.isActive" type="primary" v-permission @click="onOperate('stop')" link>
<el-button
v-if="form.isActive"
type="primary"
v-permission
v-node-admin
@click="onOperate('stop')"
link
>
{{ $t('commons.button.stop') }}
</el-button>
<el-button v-if="!form.isActive" type="primary" v-permission @click="onOperate('start')" link>
<el-button
v-if="!form.isActive"
type="primary"
v-permission
v-node-admin
@click="onOperate('start')"
link
>
{{ $t('commons.button.start') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" @click="onOperate('restart')" link>
<el-button v-permission v-node-admin type="primary" @click="onOperate('restart')" link>
{{ $t('commons.button.restart') }}
</el-button>
<el-divider direction="vertical" />
@@ -26,6 +40,7 @@
</el-button>
<el-switch
v-permission
v-node-admin
size="small"
class="ml-2"
inactive-value="disable"
@@ -45,8 +60,8 @@
<el-radio-button value="all">{{ $t('database.allConf') }}</el-radio-button>
</el-radio-group>
<el-button v-permission @click="onOpenDrawer">{{ $t('ssh.pubkey') }}</el-button>
<el-button v-permission @click="onOpenAuthKeys">{{ $t('ssh.authKeys') }}</el-button>
<el-button v-permission v-node-admin @click="onOpenDrawer">{{ $t('ssh.pubkey') }}</el-button>
<el-button v-permission v-node-admin @click="onOpenAuthKeys">{{ $t('ssh.authKeys') }}</el-button>
</template>
<template #main>
<el-row class="mt-10" v-if="confShowType === 'base'">
@@ -55,7 +70,7 @@
<el-form-item :label="$t('ssh.port')" prop="port">
<el-input disabled v-model="form.port">
<template #append>
<el-button v-permission @click="onChangePort" icon="Setting">
<el-button v-permission v-node-admin @click="onChangePort" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -65,7 +80,7 @@
<el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress">
<el-input disabled v-model="form.listenAddressItem">
<template #append>
<el-button v-permission @click="onChangeAddress" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeAddress" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -75,7 +90,7 @@
<el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLoginItem">
<el-input disabled v-model="form.permitRootLoginItem">
<template #append>
<el-button v-permission @click="onChangeRoot" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeRoot" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -85,6 +100,7 @@
<el-form-item :label="$t('ssh.passwordAuthentication')" prop="passwordAuthentication">
<el-switch
v-permission
v-node-admin
active-value="yes"
inactive-value="no"
@change="onSave(formRef, 'PasswordAuthentication', form.passwordAuthentication)"
@@ -95,6 +111,7 @@
<el-form-item :label="$t('ssh.pubkeyAuthentication')" prop="pubkeyAuthentication">
<el-switch
v-permission
v-node-admin
active-value="yes"
inactive-value="no"
@change="onSave(formRef, 'PubkeyAuthentication', form.pubkeyAuthentication)"
@@ -105,6 +122,7 @@
<el-form-item :label="$t('ssh.useDNS')" prop="useDNS">
<el-switch
v-permission
v-node-admin
active-value="yes"
inactive-value="no"
@change="onSave(formRef, 'UseDNS', form.useDNS)"
@@ -134,7 +152,14 @@
mode="nginx"
placeholder="# The SSH configuration file does not exist or is empty (/etc/ssh/sshd_config)"
></CodemirrorPro>
<el-button v-permission :disabled="loading" type="primary" @click="onSaveFile" class="mt-2.5">
<el-button
v-permission
v-node-admin
:disabled="loading"
type="primary"
@click="onSaveFile"
class="mt-2.5"
>
{{ $t('commons.button.save') }}
</el-button>
</div>
@@ -16,9 +16,11 @@
<el-option :label="$t('logs.detail.apps')" value="apps" />
<el-option :label="$t('logs.detail.openresty')" value="openresty" />
<el-option :label="$t('logs.detail.websites')" value="websites" />
<el-option :label="$t('logs.detail.monitor')" value="monitor" />
<el-option :label="$t('logs.detail.runtimes')" value="runtimes" />
<el-option :label="$t('logs.detail.ai')" value="ai" />
<el-option :label="$t('logs.detail.ai_proxy')" value="ai-proxy" />
<el-option :label="$t('logs.detail.ai_benchmark')" value="ai_benchmark" />
<el-option :label="$t('logs.detail.skills_hub')" value="skills-hub" />
<el-option :label="$t('logs.detail.databases')" value="databases" />
<el-option :label="$t('logs.detail.containers')" value="containers" />
@@ -37,6 +39,7 @@
<el-option :label="$t('logs.detail.alert')" value="alert" />
<el-option :label="$t('logs.detail.backups')" value="backups" />
<el-option :label="$t('logs.detail.groups')" value="groups" />
<el-option :label="$t('logs.detail.roles')" value="roles" />
<el-option :label="$t('logs.detail.commands')" value="commands" />
</el-select>
<el-select v-model="searchStatus" @change="search()" clearable class="p-w-200">
+6 -3
View File
@@ -6,7 +6,7 @@
class="bg-white shadow-lg relative z-10 border border-gray-200 flex overflow-hidden"
id="login-container"
>
<div class="grid grid-cols-1 md:grid-cols-2 items-stretch w-full">
<div class="grid items-stretch w-full" :style="loginGridStyle">
<div v-if="showLogo" class="flex justify-center" :style="{ height: containerHeight }">
<img
v-show="imgLoaded"
@@ -144,9 +144,12 @@ const { width } = useWindowSize();
const showLogo = computed(() => width.value >= FIXED_WIDTH);
const containerWidth = computed(() => `${FIXED_WIDTH}px`);
const containerHeight = computed(() => `${FIXED_HEIGHT}px`);
const loginGridStyle = computed(() => ({
gridTemplateColumns: showLogo.value ? 'repeat(2, minmax(0, 1fr))' : 'minmax(0, 1fr)',
}));
const loginFormClass = computed(() => {
return showLogo.value
? 'hidden md:flex items-center justify-center p-4'
: 'flex items-center justify-center p-4 w-full';
? 'flex items-center justify-center p-4 min-w-0'
: 'flex items-center justify-center p-4 w-full min-w-0';
});
</script>
+12 -1
View File
@@ -20,11 +20,18 @@
/>
</template>
<template #leftToolBar v-if="clamStatus.isExist">
<el-button v-permission type="primary" :disabled="!clamStatus.isRunning" @click="onOpenDialog('add')">
<el-button
v-permission
v-node-admin
type="primary"
:disabled="!clamStatus.isRunning"
@click="onOpenDialog('add')"
>
{{ $t('toolbox.clam.clamCreate') }}
</el-button>
<el-button
v-permission
v-node-admin
plain
:disabled="selects.length === 0 || !clamStatus.isRunning"
@click="onDelete(null)"
@@ -145,6 +152,7 @@
<fu-input-rw-switch
v-model="row.description"
v-permission
v-node-admin
@enter="onChange(row)"
@blur="onChange(row)"
/>
@@ -344,6 +352,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.handle'),
permission: true,
nodeAdmin: true,
click: async (row: Toolbox.ClamInfo) => {
loading.value = true;
await handleClamScan(row.id)
@@ -360,6 +369,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Toolbox.ClamInfo) => {
onOpenDialog('edit', row);
},
@@ -373,6 +383,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Toolbox.ClamInfo) => {
onDelete(row);
},
@@ -19,7 +19,12 @@
<el-form-item :label="$t('toolbox.clam.scanDir')" prop="path">
<el-input v-model="dialogData.rowData!.path">
<template #prepend>
<el-button icon="Folder" v-permission @click="scanDirRef.acceptParams({ dir: true })" />
<el-button
icon="Folder"
v-permission
v-node-admin
@click="scanDirRef.acceptParams({ dir: true })"
/>
</template>
</el-input>
</el-form-item>
@@ -41,6 +46,7 @@
<el-button
icon="Folder"
v-permission
v-node-admin
@click="infectedDirRef.acceptParams({ dir: true })"
/>
</template>
@@ -211,7 +217,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSubmit(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -30,11 +30,24 @@
</el-tag>
<span class="mt-0.5">
<el-button type="primary" v-permission @click="onHandle(dialogData.rowData)" link>
<el-button
type="primary"
v-permission
v-node-admin
@click="onHandle(dialogData.rowData)"
link
>
{{ $t('commons.button.handle') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission :disabled="!hasRecords" type="primary" @click="onClean" link>
<el-button
v-permission
v-node-admin
:disabled="!hasRecords"
type="primary"
@click="onClean"
link
>
{{ $t('commons.button.clean') }}
</el-button>
</span>
@@ -37,7 +37,7 @@
:disabled="!canUpdate()"
:placeholder="$t('commons.msg.noneData')"
></CodemirrorPro>
<el-button type="primary" class="mt-5" v-if="canUpdate()" v-permission @click="onSave">
<el-button type="primary" class="mt-5" v-if="canUpdate()" v-permission v-node-admin @click="onSave">
{{ $t('commons.button.save') }}
</el-button>
</div>
@@ -14,6 +14,7 @@
v-if="!data.isActive"
link
v-permission
v-node-admin
@click="onOperate('ClamAV', 'start')"
>
{{ $t('commons.operate.start') }}
@@ -23,16 +24,23 @@
v-if="data.isActive"
link
v-permission
v-node-admin
@click="onOperate('ClamAV', 'stop')"
>
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link v-permission @click="onOperate('ClamAV', 'restart')">
<el-button
type="primary"
link
v-permission
v-node-admin
@click="onOperate('ClamAV', 'restart')"
>
{{ $t('commons.button.restart') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" link @click="setting">
<el-button v-permission v-node-admin type="primary" link @click="setting">
{{ $t('commons.button.set') }}
</el-button>
<el-divider direction="vertical" />
@@ -56,6 +64,7 @@
v-if="!data.freshIsActive"
link
v-permission
v-node-admin
@click="onOperate('FreshClam', 'start')"
>
{{ $t('commons.operate.start') }}
@@ -65,12 +74,19 @@
v-if="data.freshIsActive"
link
v-permission
v-node-admin
@click="onOperate('FreshClam', 'stop')"
>
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link v-permission @click="onOperate('FreshClam', 'restart')">
<el-button
type="primary"
link
v-permission
v-node-admin
@click="onOperate('FreshClam', 'restart')"
>
{{ $t('commons.button.restart') }}
</el-button>
</div>
+2 -1
View File
@@ -2,7 +2,7 @@
<div v-loading="loading">
<LayoutContent :title="$t('setting.diskClean')" :divider="true">
<template #leftToolBar>
<el-button v-permission type="primary" @click="scanData">
<el-button v-permission v-node-admin type="primary" @click="scanData">
{{ $t('clean.scan') }}
</el-button>
</template>
@@ -79,6 +79,7 @@
<el-text class="clean_title">{{ $t('clean.totalScan') }} {{ computeSize(totalSize) }}</el-text>
<el-button
v-permission
v-node-admin
type="primary"
class="-mt-2 ml-20"
:disabled="selectSize <= 0"
@@ -31,7 +31,7 @@
<el-button :disabled="loading" @click="onTest()">
{{ $t('toolbox.device.dnsCheck') }}
</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave()">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -9,7 +9,13 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSaveHostname(formRef)">
<el-button
v-permission
v-node-admin
:disabled="loading"
type="primary"
@click="onSaveHostname(formRef)"
>
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -24,7 +24,7 @@
</template>
</el-table-column>
</el-table>
<el-button v-permission class="ml-3 mt-2" @click="handleHostsAdd()">
<el-button v-permission v-node-admin class="ml-3 mt-2" @click="handleHostsAdd()">
{{ $t('commons.button.add') }}
</el-button>
</div>
@@ -39,7 +39,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave()">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
+14 -8
View File
@@ -14,7 +14,7 @@
<el-form-item label="DNS" prop="dnsItem">
<el-input disabled v-model="form.dnsItem">
<template #append>
<el-button v-permission @click="onChangeDNS" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeDNS" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -23,7 +23,7 @@
<el-form-item label="Hosts" prop="hosts">
<el-input disabled v-model="form.hostItem">
<template #append>
<el-button v-permission @click="onChangeHost" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeHost" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -32,7 +32,7 @@
<el-form-item label="Swap" prop="swap">
<el-input disabled v-model="form.swapItem">
<template #append>
<el-button v-permission @click="onChangeSwap" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeSwap" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -41,7 +41,7 @@
<el-form-item :label="$t('toolbox.device.hostname')" prop="hostname">
<el-input disabled v-model="form.hostname">
<template #append>
<el-button v-permission @click="onChangeHostname" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeHostname" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -50,7 +50,7 @@
<el-form-item :label="$t('toolbox.device.passwd')" prop="passwd">
<el-input disabled v-model="form.passwd" type="password">
<template #append>
<el-button v-permission @click="onChangePasswd" icon="Setting">
<el-button v-permission v-node-admin @click="onChangePasswd" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -59,7 +59,7 @@
<el-form-item :label="$t('toolbox.device.syncSite')" prop="ntp">
<el-input disabled v-model="form.ntp">
<template #append>
<el-button v-permission @click="onChangeNtp" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeNtp" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -68,7 +68,7 @@
<el-form-item :label="$t('toolbox.device.timeZone')" prop="timeZone">
<el-input disabled v-model="form.timeZone">
<template #append>
<el-button v-permission @click="onChangeTimeZone" icon="Setting">
<el-button v-permission v-node-admin @click="onChangeTimeZone" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -77,7 +77,13 @@
<el-form-item :label="$t('toolbox.device.localTime')" prop="localTime">
<el-input disabled v-model="form.localTime">
<template #append>
<el-button v-permission @click="onChangeLocalTime" icon="Refresh" width="150px">
<el-button
v-permission
v-node-admin
@click="onChangeLocalTime"
icon="Refresh"
width="150px"
>
{{ $t('commons.button.sync') }}
</el-button>
</template>
@@ -3,13 +3,34 @@
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form-item :label="$t('toolbox.device.syncSite')" prop="ntpSite" :rules="Rules.domain">
<el-input v-model="form.ntpSite" />
<el-button type="primary" link class="tagClass" v-permission @click="form.ntpSite = 'pool.ntp.org'">
<el-button
type="primary"
link
class="tagClass"
v-permission
v-node-admin
@click="form.ntpSite = 'pool.ntp.org'"
>
{{ $t('commons.table.default') }}
</el-button>
<el-button type="primary" link class="tagClass" v-permission @click="form.ntpSite = 'ntp.aliyun.com'">
<el-button
type="primary"
link
class="tagClass"
v-permission
v-node-admin
@click="form.ntpSite = 'ntp.aliyun.com'"
>
{{ $t('toolbox.device.ntpALi') }}
</el-button>
<el-button type="primary" link class="tagClass" v-permission @click="form.ntpSite = 'time.google.com'">
<el-button
type="primary"
link
class="tagClass"
v-permission
v-node-admin
@click="form.ntpSite = 'time.google.com'"
>
{{ $t('toolbox.device.ntpGoogle') }}
</el-button>
</el-form-item>
@@ -17,7 +38,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSyncTime(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSyncTime(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -23,7 +23,13 @@
<el-button :disabled="loading" @click="passwordVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="submitChangePassword(formRef)">
<el-button
v-permission
v-node-admin
:disabled="loading"
type="primary"
@click="submitChangePassword(formRef)"
>
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -67,7 +67,7 @@
</el-table-column>
<el-table-column min-width="70">
<template #default="scope">
<el-button v-permission link type="primary" @click="onSave(scope.row)">
<el-button v-permission v-node-admin link type="primary" @click="onSave(scope.row)">
{{ $t('commons.button.save') }}
</el-button>
</template>
@@ -33,7 +33,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -18,7 +18,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -18,7 +18,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
+59 -10
View File
@@ -9,14 +9,28 @@
<el-tag>{{ form.version }}</el-tag>
</div>
<div class="mt-0.5">
<el-button v-if="form.isActive" v-permission type="primary" @click="onOperate('stop')" link>
<el-button
v-if="form.isActive"
v-permission
v-node-admin
type="primary"
@click="onOperate('stop')"
link
>
{{ $t('commons.button.stop') }}
</el-button>
<el-button v-if="!form.isActive" v-permission type="primary" @click="onOperate('start')" link>
<el-button
v-if="!form.isActive"
v-permission
v-node-admin
type="primary"
@click="onOperate('start')"
link
>
{{ $t('commons.button.start') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" @click="onOperate('restart')" link>
<el-button v-permission v-node-admin type="primary" @click="onOperate('restart')" link>
{{ $t('commons.button.restart') }}
</el-button>
<el-divider direction="vertical" />
@@ -25,6 +39,7 @@
</el-button>
<el-switch
v-permission
v-node-admin
size="small"
class="ml-2"
inactive-value="disable"
@@ -52,6 +67,7 @@
<template #leftToolBar>
<el-button
v-permission
v-node-admin
:disabled="!form.isActive"
type="primary"
plain
@@ -61,6 +77,7 @@
</el-button>
<el-button
v-permission
v-node-admin
:disabled="!form.isActive"
type="primary"
plain
@@ -81,7 +98,7 @@
<el-form-item :label="$t('toolbox.fail2ban.sshPort')" prop="port">
<el-input disabled v-model="form.port">
<template #append>
<el-button v-permission @click="onChangePort" icon="Setting">
<el-button v-permission v-node-admin @click="onChangePort" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -91,7 +108,12 @@
<el-form-item :label="$t('toolbox.fail2ban.maxRetry')" prop="maxRetry">
<el-input disabled v-model="form.maxRetry">
<template #append>
<el-button v-permission @click="onChangeMaxRetry" icon="Setting">
<el-button
v-permission
v-node-admin
@click="onChangeMaxRetry"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -100,7 +122,12 @@
<el-form-item :label="$t('toolbox.fail2ban.banTime')" prop="banTime">
<el-input disabled v-model="form.banTimeItem">
<template #append>
<el-button v-permission @click="onChangeBanTime" icon="Setting">
<el-button
v-permission
v-node-admin
@click="onChangeBanTime"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -110,7 +137,12 @@
<el-form-item :label="$t('toolbox.fail2ban.findTime')" prop="findTime">
<el-input disabled v-model="form.findTimeItem">
<template #append>
<el-button v-permission @click="onChangeFindTime" icon="Setting">
<el-button
v-permission
v-node-admin
@click="onChangeFindTime"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -119,7 +151,12 @@
<el-form-item :label="$t('toolbox.fail2ban.banAction')" prop="banAction">
<el-input disabled v-model="form.banAction">
<template #append>
<el-button v-permission @click="onChangeBanAction" icon="Setting">
<el-button
v-permission
v-node-admin
@click="onChangeBanAction"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -128,7 +165,12 @@
<el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath">
<el-input disabled v-model="form.logPath">
<template #append>
<el-button v-permission @click="onChangeLogPath" icon="Setting">
<el-button
v-permission
v-node-admin
@click="onChangeLogPath"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</template>
@@ -145,7 +187,14 @@
v-model="fail2banConf"
:heightDiff="460"
></CodemirrorPro>
<el-button v-permission :disabled="loading" type="primary" @click="onSaveFile" class="mt-2.5">
<el-button
v-permission
v-node-admin
:disabled="loading"
type="primary"
@click="onSaveFile"
class="mt-2.5"
>
{{ $t('commons.button.save') }}
</el-button>
</div>
@@ -13,7 +13,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -4,7 +4,7 @@
<el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath" :rules="Rules.requiredInput">
<el-input v-model="form.logPath">
<template #prepend>
<el-button v-permission icon="Folder" @click="fileRef.acceptParams({})" />
<el-button v-permission v-node-admin icon="Folder" @click="fileRef.acceptParams({})" />
</template>
</el-input>
<span class="input-help">{{ $t('toolbox.fail2ban.logPathHelper') }}</span>
@@ -13,7 +13,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -10,7 +10,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -8,7 +8,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
+23 -6
View File
@@ -8,14 +8,28 @@
<Status class="mt-0.5" :status="form.isActive ? 'enable' : 'disable'" />
</div>
<div class="mt-0.5">
<el-button v-if="form.isActive" v-permission type="primary" @click="onOperate('stop')" link>
<el-button
v-if="form.isActive"
v-permission
v-node-admin
type="primary"
@click="onOperate('stop')"
link
>
{{ $t('commons.button.stop') }}
</el-button>
<el-button v-if="!form.isActive" v-permission type="primary" @click="onOperate('start')" link>
<el-button
v-if="!form.isActive"
v-permission
v-node-admin
type="primary"
@click="onOperate('start')"
link
>
{{ $t('commons.button.start') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" @click="onOperate('restart')" link>
<el-button v-permission v-node-admin type="primary" @click="onOperate('restart')" link>
{{ $t('commons.button.restart') }}
</el-button>
</div>
@@ -35,13 +49,13 @@
</el-alert>
</template>
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('add')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('add')">
{{ $t('commons.button.add') }}
</el-button>
<el-button v-permission @click="onSync()">
<el-button v-permission v-node-admin @click="onSync()">
{{ $t('commons.button.sync') }}
</el-button>
<el-button v-permission plain :disabled="selects.length === 0" @click="onDelete(null)">
<el-button v-permission v-node-admin plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
@@ -126,6 +140,7 @@
<fu-input-rw-switch
v-model="row.description"
v-permission
v-node-admin
@enter="onChange(row)"
@blur="onChange(row)"
/>
@@ -337,6 +352,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
disabled: (row: Toolbox.FtpInfo) => {
return row.status === 'deleted';
},
@@ -356,6 +372,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Toolbox.FtpInfo) => {
onDelete(row);
},
@@ -13,7 +13,7 @@
<el-form-item :label="$t('commons.login.password')" prop="password">
<el-input type="password" clearable v-model="dialogData.rowData!.password" show-password>
<template #append>
<el-button v-permission @click="random">
<el-button v-permission v-node-admin @click="random">
{{ $t('commons.button.random') }}
</el-button>
</template>
@@ -22,7 +22,12 @@
<el-form-item :label="$t('file.root')" prop="path">
<el-input v-model="dialogData.rowData!.path">
<template #prepend>
<el-button v-permission icon="Folder" @click="fileRef.acceptParams({ dir: true })" />
<el-button
v-permission
v-node-admin
icon="Folder"
@click="fileRef.acceptParams({ dir: true })"
/>
</template>
</el-input>
<span class="input-help">{{ $t('toolbox.ftp.dirHelper') }}</span>
@@ -34,7 +39,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission :disabled="loading" type="primary" @click="onSubmit(formRef)">
<el-button v-permission v-node-admin :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
+2 -2
View File
@@ -3,11 +3,11 @@
<RouterButton :buttons="buttons">
<template #route-button>
<div class="router-button">
<el-button v-permission link type="primary" @click="onRestart('1panel')">
<el-button v-permission v-node-admin link type="primary" @click="onRestart('1panel')">
{{ $t('home.restart_1panel') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission link type="primary" @click="onRestart('system')">
<el-button v-permission v-node-admin link type="primary" @click="onRestart('system')">
{{ $t('home.restart_system') }}
</el-button>
</div>
@@ -10,7 +10,7 @@
<span class="input-help">{{ $t('tool.supervisor.serviceNameHelper') }}</span>
</el-form-item>
<el-form-item>
<el-button v-permission type="primary" @click="submit(initForm)" :disabled="loading">
<el-button v-permission v-node-admin type="primary" @click="submit(initForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</el-form-item>
@@ -2,7 +2,7 @@
<div v-loading="loading">
<CodemirrorPro class="mt-5" v-model="content" :heightDiff="375"></CodemirrorPro>
<div class="mt-5">
<el-button v-permission type="primary" @click="submit()" :disabled="loading">
<el-button v-permission v-node-admin type="primary" @click="submit()" :disabled="loading">
{{ $t('commons.button.save') }}
</el-button>
</div>
@@ -22,7 +22,12 @@
<el-form-item :label="$t('tool.supervisor.dir')" prop="dir">
<el-input v-model.trim="process.dir">
<template #prepend>
<el-button v-permission icon="Folder" @click="fileRef.acceptParams({ dir: true })" />
<el-button
v-permission
v-node-admin
icon="Folder"
@click="fileRef.acceptParams({ dir: true })"
/>
</template>
</el-input>
</el-form-item>
@@ -47,7 +52,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission type="primary" @click="submit(processForm)" :disabled="loading">
<el-button v-permission v-node-admin type="primary" @click="submit(processForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -15,7 +15,7 @@
<template #footer>
<span>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button v-permission type="primary" :disabled="loading" @click="submit()">
<el-button v-permission v-node-admin type="primary" :disabled="loading" @click="submit()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -23,7 +23,7 @@
/>
</template>
<template v-if="showTable" #leftToolBar>
<el-button v-permission type="primary" @click="openCreate" :disabled="showStopped">
<el-button v-permission v-node-admin type="primary" @click="openCreate" :disabled="showStopped">
{{ $t('commons.button.create') }}
</el-button>
</template>
@@ -75,17 +75,25 @@
<div v-if="row.status && row.status.length > 0 && row.hasLoad">
<Status
v-permission
v-node-admin
v-if="checkStatus(row.status) === 'RUNNING'"
status="running"
@click="operate('stop', row.name)"
/>
<Status
v-permission
v-node-admin
v-else-if="checkStatus(row.status) === 'WARNING'"
status="unhealthy"
@click="operate('restart', row.name)"
/>
<Status v-else v-permission status="stopped" @click="operate('start', row.name)" />
<Status
v-else
v-permission
v-node-admin
status="stopped"
@click="operate('start', row.name)"
/>
</div>
<div v-if="!row.hasLoad">
<el-button link loading></el-button>
@@ -342,6 +350,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) {
edit(row);
},
@@ -349,6 +358,7 @@ const buttons = [
{
label: i18n.global.t('website.sourceFile'),
permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) {
getFile(row.name, 'config');
},
@@ -362,6 +372,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.restart'),
permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) {
operate('restart', row.name);
},
@@ -369,6 +380,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) {
operate('delete', row.name);
},
@@ -22,7 +22,13 @@
:height-diff="300"
>
<template #button>
<el-button v-permission @click="cleanLog" icon="Delete" :disabled="hasContent === false">
<el-button
v-permission
v-node-admin
@click="cleanLog"
icon="Delete"
:disabled="hasContent === false"
>
{{ $t('commons.button.clean') }}
</el-button>
</template>
@@ -11,6 +11,7 @@
<div class="mt-0.5" v-if="!data.init">
<el-button
v-permission
v-node-admin
type="primary"
v-if="data.status != 'running'"
link
@@ -20,6 +21,7 @@
</el-button>
<el-button
v-permission
v-node-admin
type="primary"
v-if="data.status == 'running'"
link
@@ -28,16 +30,16 @@
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" link @click="onOperate('restart')">
<el-button v-permission v-node-admin type="primary" link @click="onOperate('restart')">
{{ $t('commons.button.restart') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission type="primary" link @click="setting">
<el-button v-permission v-node-admin type="primary" link @click="setting">
{{ $t('commons.button.set') }}
</el-button>
</div>
<div class="mt-0.5" v-else>
<el-button v-permission type="primary" link @click="init">
<el-button v-permission v-node-admin type="primary" link @click="init">
{{ $t('commons.button.init') }}
</el-button>
</div>
@@ -28,7 +28,7 @@
<el-button @click="handleClose()" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button v-permission type="primary" @click="openSubmit(initForm)" :disabled="loading">
<el-button v-permission v-node-admin type="primary" @click="openSubmit(initForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
+3 -3
View File
@@ -231,8 +231,8 @@ let selects = ref<any>([]);
const columns = ref([]);
const req = reactive({
domain: '',
orderBy: 'expire_date',
order: 'ascending',
orderBy: 'updated_at',
order: 'descending',
});
const routerButton = [
@@ -325,7 +325,7 @@ const onDownload = (ssl: Website.SSLDTO) => {
};
const changeSort = ({ order }) => {
req.orderBy = 'expire_date';
req.orderBy = order ? 'expire_date' : 'updated_at';
req.order = order || 'descending';
search();
};