Compare commits

...
15 Commits
102 changed files with 1272 additions and 561 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ type WebsiteSSLSearch struct {
dto.PageInfo dto.PageInfo
AcmeAccountID string `json:"acmeAccountID"` AcmeAccountID string `json:"acmeAccountID"`
Domain string `json:"domain"` 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"` Order string `json:"order" validate:"omitempty,oneof=null ascending descending"`
} }
+20 -6
View File
@@ -2,8 +2,10 @@ package repo
import ( import (
"context" "context"
"time"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -21,6 +23,7 @@ type ISSLRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
List(opts ...DBOption) ([]model.WebsiteSSL, error) List(opts ...DBOption) ([]model.WebsiteSSL, error)
TryMarkApplying(id uint) (bool, error)
Create(ctx context.Context, ssl *model.WebsiteSSL) error Create(ctx context.Context, ssl *model.WebsiteSSL) error
Save(ssl *model.WebsiteSSL) error Save(ssl *model.WebsiteSSL) error
DeleteBy(opts ...DBOption) 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) { func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) {
var website *model.WebsiteSSL var website model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{}) db := getDb(opts...).Model(&model.WebsiteSSL{})
if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil { 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) { 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 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 { func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebsiteSSL) error {
return getTx(ctx).Create(ssl).Error return getTx(ctx).Create(ssl).Error
} }
func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error { func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error {
return getDb().Model(&model.WebsiteSSL{BaseModel: model.BaseModel{ return getDb().Save(ssl).Error
ID: ssl.ID,
}}).Save(&ssl).Error
} }
func (w WebsiteSSLRepo) SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error { func (w WebsiteSSLRepo) SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error {
+23 -27
View File
@@ -765,33 +765,6 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
install.Env = string(paramByte) install.Env = string(paramByte)
content = setVllmImageInEnvContent(content, image) content = setVllmImageInEnvContent(content, image)
} }
if req.PullImage {
composeContent := []byte(detail.DockerCompose)
if install.App.Key == vllmAppKeyForUpgrade {
composeContent = []byte(install.DockerCompose)
}
if req.DockerCompose != "" {
composeContent = []byte(req.DockerCompose)
}
images, err := docker.GetImagesFromDockerCompose(content, composeContent)
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
_ = copyAppDetailMissing(fileOp, detailDir, install.GetPath()) _ = copyAppDetailMissing(fileOp, detailDir, install.GetPath())
if install.App.Key == constant.AppOpenresty { if install.App.Key == constant.AppOpenresty {
installBuildDir := path.Join(install.GetPath(), "build") installBuildDir := path.Join(install.GetPath(), "build")
@@ -847,6 +820,29 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
install.Version = detail.Version install.Version = detail.Version
install.AppDetailId = req.DetailID install.AppDetailId = req.DetailID
if req.PullImage {
images, err := docker.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
defer dockerCLi.Close()
for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image))
if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
return buserr.WithNameAndErr("ErrDockerPullImage", "", err)
}
exist, err := dockerCLi.ImageExists(image)
if err != nil || !exist {
return buserr.WithNameAndErr("ErrDockerPullImage", "", fmt.Errorf("image %s does not exist after pull: %v", image, err))
}
t.LogSuccess(i18n.GetMsgByKey("PullImage"))
}
}
if out, err := compose.Down(install.GetComposePath()); err != nil { if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" { if out != "" {
upErr = errors.New(out) upErr = errors.New(out)
+1 -1
View File
@@ -502,7 +502,7 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
return err return err
} }
defer client.Close() defer client.Close()
taskItem, err := task.NewTaskWithOps(task.TaskScopeImage, task.TaskDelete, task.TaskScopeContainer, req.TaskID, 1) taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, ","), task.TaskDelete, task.TaskScopeImage, req.TaskID, 1)
if err != nil { if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err) global.LOG.Errorf("new task for create container failed, err: %v", err)
return err return err
+6 -2
View File
@@ -59,8 +59,12 @@ func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreat
acmeAccount.CaDirURL = create.CaDirURL acmeAccount.CaDirURL = create.CaDirURL
} }
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy)) var client *ssl.AcmeClient
if err != nil { if err := withLegoLogger(nil, func() error {
var err error
client, err = ssl.NewRegisterClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
return err
}); err != nil {
return nil, err return nil, err
} }
privateKey, err := ssl.GetPrivateKey(client.User.GetPrivateKey(), ssl.KeyType(create.KeyType)) privateKey, err := ssl.GetPrivateKey(client.User.GetPrivateKey(), ssl.KeyType(create.KeyType))
+165 -64
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -12,6 +13,7 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/go-acme/lego/v5/certificate" "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/req_helper"
"github.com/1Panel-dev/1Panel/agent/utils/ssl" "github.com/1Panel-dev/1Panel/agent/utils/ssl"
"github.com/1Panel-dev/1Panel/agent/utils/xpack" "github.com/1Panel-dev/1Panel/agent/utils/xpack"
gormv2 "gorm.io/gorm"
) )
type WebsiteSSLService struct { 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 { type IWebsiteSSLService interface {
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
GetSSL(id uint) (*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" { if search.OrderBy != "" && search.Order != "null" {
opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order)) opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order))
} else { } else {
opts = append(opts, repo.WithOrderDesc("created_at")) opts = append(opts, repo.WithOrderDesc("updated_at"))
} }
if search.Domain != "" { if search.Domain != "" {
opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain)) opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain))
@@ -99,7 +156,7 @@ func (w WebsiteSSLService) Search(search request.WebsiteSSLListReq) ([]response.
opts []repo.DBOption opts []repo.DBOption
result []response.WebsiteSSLDTO result []response.WebsiteSSLDTO
) )
opts = append(opts, repo.WithOrderDesc("created_at")) opts = append(opts, repo.WithOrderDesc("updated_at"))
if search.AcmeAccountID != "" { if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64) acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil { if err != nil {
@@ -330,19 +387,23 @@ func (w WebsiteSSLService) AutoRenewSSL(id uint) error {
func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error { func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
var ( var (
err error err error
websiteSSL *model.WebsiteSSL websiteSSL *model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount dnsAccount *model.WebsiteDnsAccount
client *ssl.AcmeClient logFile *os.File
manualClient *ssl.ManualClient logger *log.Logger
resource certificate.Resource resource certificate.Resource
httpRoot string
) )
websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(id)) websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(id))
if err != nil { if err != nil {
return err return err
} }
if websiteSSL.Status == constant.SSLApply {
return buserr.New("InExecuting")
}
acmeAccount, err = websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID)) acmeAccount, err = websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID))
if err != nil { if err != nil {
return err return err
@@ -352,20 +413,12 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
domains = append(domains, strings.Split(websiteSSL.Domains, ",")...) domains = append(domains, strings.Split(websiteSSL.Domains, ",")...)
} }
if websiteSSL.Provider != constant.DnsManual { if websiteSSL.Provider != constant.DnsManual {
client, err = ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
return err
}
switch websiteSSL.Provider { switch websiteSSL.Provider {
case constant.DNSAccount: case constant.DNSAccount:
dnsAccount, err = websiteDnsRepo.GetFirst(repo.WithByID(websiteSSL.DnsAccountID)) dnsAccount, err = websiteDnsRepo.GetFirst(repo.WithByID(websiteSSL.DnsAccountID))
if err != nil { if err != nil {
return err return err
} }
if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization, *websiteSSL); err != nil {
return err
}
case constant.Http: case constant.Http:
appInstall, err := getAppInstallByKey(constant.AppOpenresty) appInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil { if err != nil {
@@ -379,55 +432,83 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
return buserr.New("ErrWildcardDomain") return buserr.New("ErrWildcardDomain")
} }
} }
if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil { httpRoot = path.Join(appInstall.GetPath(), "root")
return err
}
} }
} }
websiteSSL.Status = constant.SSLApply marked, err := websiteSSLRepo.TryMarkApplying(websiteSSL.ID)
err = websiteSSLRepo.Save(websiteSSL)
if err != nil { if err != nil {
return err 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 { 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 { if websiteSSL.Provider != constant.DnsManual {
privateKey, err := ssl.GetPrivateKeyByType(websiteSSL.KeyType, websiteSSL.PrivateKey) privateKey, err := ssl.GetPrivateKeyByType(websiteSSL.KeyType, websiteSSL.PrivateKey)
if err != nil { if err != nil {
handleError(websiteSSL, err) handleError(websiteSSL, logger, err)
return return
} }
if websiteSSL.IsIp { err = withLegoLoggerTimeout(logger, sslObtainTimeout, func(ctx context.Context) error {
resource, err = client.ObtainIPSSL(domains[0], privateKey) client, err := newWebsiteSSLLegoClient(ctx, acmeAccount)
} else { if err != nil {
resource, err = client.ObtainSSL(domains, privateKey) 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 { if err != nil {
handleError(websiteSSL, err) handleError(websiteSSL, logger, err)
return return
} }
} else { } else {
manualClient, err = ssl.NewCustomAcmeClient(acmeAccount, logger) manualClient, err := ssl.NewCustomAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy), logger)
if err != nil { if err != nil {
handleError(websiteSSL, err) handleError(websiteSSL, logger, err)
return 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 { if err != nil {
handleError(websiteSSL, err) handleError(websiteSSL, logger, err)
return return
} }
} }
@@ -435,10 +516,9 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
websiteSSL.PrivateKey = string(resource.PrivateKey) websiteSSL.PrivateKey = string(resource.PrivateKey)
websiteSSL.Pem = string(resource.Certificate) websiteSSL.Pem = string(resource.Certificate)
websiteSSL.CertURL = resource.CertURL websiteSSL.CertURL = resource.CertURL
certBlock, _ := pem.Decode(resource.Certificate) cert, err := parseCertificatePEM(resource.Certificate)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil { if err != nil {
handleError(websiteSSL, err) handleError(websiteSSL, logger, err)
return return
} }
websiteSSL.ExpireDate = cert.NotAfter websiteSSL.ExpireDate = cert.NotAfter
@@ -494,7 +574,7 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
} }
printSSLLog(logger, "PushSSLToNodeSuccess", nil) printSSLLog(logger, "PushSSLToNodeSuccess", nil)
} }
}() }(logFile, logger)
return nil return nil
} }
@@ -516,16 +596,24 @@ func runShellScriptFile(workDir, shell string, logger *log.Logger) error {
return cmdMgr.Run("bash", file.Name()) 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 { if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError {
websiteSSL.Status = constant.StatusError websiteSSL.Status = constant.StatusError
} else { } else {
websiteSSL.Status = constant.SSLApplyError websiteSSL.Status = constant.SSLApplyError
} }
websiteSSL.Message = err.Error() websiteSSL.Message = err.Error()
// lego v5 uses slog; use the same global default logger to write the if logger != nil {
// failure message to the SSL log. logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()}))
legoLogger.Default().Error(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()})) }
_ = websiteSSLRepo.Save(websiteSSL) _ = websiteSSLRepo.Save(websiteSSL)
} }
@@ -534,7 +622,7 @@ func (w WebsiteSSLService) GetDNSResolve(req request.WebsiteDNSReq) ([]response.
if err != nil { if err != nil {
return nil, err return nil, err
} }
client, err := ssl.NewCustomAcmeClient(acmeAccount, nil) client, err := ssl.NewCustomAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -580,7 +668,7 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
for _, id := range ids { for _, id := range ids {
if websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(id)); len(websites) > 0 { if websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(id)); len(websites) > 0 {
oldSSL, _ := websiteSSLRepo.GetFirst(repo.WithByID(id)) oldSSL, _ := websiteSSLRepo.GetFirst(repo.WithByID(id))
if oldSSL.ID > 0 { if oldSSL != nil && oldSSL.ID > 0 {
websiteSSLS = append(websiteSSLS, oldSSL.PrimaryDomain) websiteSSLS = append(websiteSSLS, oldSSL.PrimaryDomain)
} }
continue 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) global.LOG.Errorf("Failed to get acme account for SSL revoke, err: %v", err)
return return
} }
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy)) err = withLegoLogger(nil, func() error {
if err != nil { client, err := newWebsiteSSLLegoClient(context.Background(), acmeAccount)
global.LOG.Errorf("Failed to create ACME client for SSL revoke, err: %v", err) if err != nil {
return return err
} }
err = client.RevokeSSL([]byte(websiteSSL.Pem)) return client.RevokeSSL([]byte(websiteSSL.Pem))
})
if err != nil { if err != nil {
global.LOG.Errorf("Failed to revoke SSL for domain %s, err: %v", websiteSSL.PrimaryDomain, err) global.LOG.Errorf("Failed to revoke SSL for domain %s, err: %v", websiteSSL.PrimaryDomain, err)
return return
@@ -851,7 +940,13 @@ func (w WebsiteSSLService) SyncForRestart() error {
} }
func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) 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.Status = constant.SSLReady
websiteSSL.Provider = constant.FromMaster websiteSSL.Provider = constant.FromMaster
websiteSSL.PrimaryDomain = create.PrimaryDomain websiteSSL.PrimaryDomain = create.PrimaryDomain
@@ -865,8 +960,14 @@ func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error {
websiteSSL.Organization = create.Organization websiteSSL.Organization = create.Organization
websiteSSL.MasterSSLID = create.ID websiteSSL.MasterSSLID = create.ID
websiteSSL.Domains = create.Domains websiteSSL.Domains = create.Domains
if err := websiteSSLRepo.Save(websiteSSL); err != nil { if websiteSSL.ID == 0 {
return err 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)) websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) == 0 { if len(websites) == 0 {
@@ -434,5 +434,12 @@
"file": "lz4.so", "file": "lz4.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"],
"installed": false "installed": false
},
{
"name": "msgpack",
"check": "msgpack",
"file": "msgpack.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"],
"installed": false
} }
] ]
+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 { func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd {
cmds := make([]*exec.Cmd, 0, len(commands)) cmds := make([]*exec.Cmd, 0, len(commands))
for _, item := range 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(os.Environ(), c.env...)
cmdItem.Env = append(cmdItem.Env, item.Env...) cmdItem.Env = append(cmdItem.Env, item.Env...)
cmdItem.Dir = c.workDir cmdItem.Dir = c.workDir
@@ -305,7 +305,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
var newContext context.Context var newContext context.Context
var cancel context.CancelFunc var cancel context.CancelFunc
var outputFile *os.File var outputFile *os.File
arg = filterEmptyArgs(arg)
if c.timeout != 0 { if c.timeout != 0 {
if c.context == nil { 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{} { func contextDone(ctx context.Context) <-chan struct{} {
if ctx == nil { if ctx == nil {
return nil return nil
+18 -5
View File
@@ -199,20 +199,33 @@ func (c Client) PullImageWithProcessAndOptions(task *task.Task, imageName string
return err return err
} }
defer out.Close() defer out.Close()
return handlePullImageProcess(out, task)
}
func handlePullImageProcess(out io.Reader, task *task.Task) error {
decoder := json.NewDecoder(out) decoder := json.NewDecoder(out)
for { for {
var progress map[string]interface{} var progress map[string]interface{}
if err = decoder.Decode(&progress); err != nil { if err := decoder.Decode(&progress); err != nil {
if err == io.EOF { if err == io.EOF {
break break
} }
return err return err
} }
status, _ := progress["status"].(string) if msg, ok := progress["errorDetail"]; ok {
if status == "Downloading" || status == "Extracting" { return fmt.Errorf("image pull failed, err: %v", msg)
logProcess(progress, task)
} }
if status == "Pull complete" || status == "Download complete" { if msg, ok := progress["error"]; ok {
return fmt.Errorf("image pull failed, err: %v", msg)
}
if task == nil {
continue
}
status, _ := progress["status"].(string)
switch status {
case "Downloading", "Extracting":
logProcess(progress, task)
case "Pull complete", "Download complete", "Already exists", "Verifying Checksum":
id, _ := progress["id"].(string) id, _ := progress["id"].(string)
progressStr := fmt.Sprintf("%s %s", status, id) progressStr := fmt.Sprintf("%s %s", status, id)
_ = setLog(id, progressStr, task) _ = setLog(id, progressStr, task)
+69 -53
View File
@@ -20,8 +20,6 @@ import (
"strings" "strings"
"time" "time"
"golang.org/x/crypto/acme"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr" "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/certcrypto/compat"
"github.com/go-acme/lego/v5/lego" "github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/registration" "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 // 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 // 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 // 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) { 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 ( var (
priKey crypto.Signer priKey crypto.Signer
err error err error
@@ -165,8 +218,9 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP
} }
myUser := &AcmeUser{ myUser := &AcmeUser{
Email: acmeAccount.Email, Email: acmeAccount.Email,
Key: priKey, Registration: registration,
Key: priKey,
} }
config := NewConfigWithProxy(myUser, acmeAccount.Type, acmeAccount.CaDirURL, proxy) config := NewConfigWithProxy(myUser, acmeAccount.Type, acmeAccount.CaDirURL, proxy)
client, err := lego.NewClient(config) client, err := lego.NewClient(config)
@@ -174,41 +228,6 @@ func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemP
return nil, err 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{ acmeClient := &AcmeClient{
User: myUser, User: myUser,
Client: client, 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 // lego v5 removed lego.CertificateConfig.KeyType; the key type is now
// taken from the user's account for each ObtainRequest call. // taken from the user's account for each ObtainRequest call.
return &lego.Config{ config := lego.NewConfig(user)
CADirURL: caDirURL, config.CADirURL = caDirURL
UserAgent: "1Panel", config.UserAgent = "1Panel"
User: user, config.HTTPClient = createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword)
HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword), config.Certificate.Timeout = 60 * time.Second
Certificate: lego.CertificateConfig{ return config
Timeout: 60 * time.Second,
},
}
} }
func initCertPool() *x509.CertPool { 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" baseURL := "https://api.zerossl.com/acme/eab-credentials-email"
params := url.Values{} params := url.Values{}
params.Add("email", email) params.Add("email", email)
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode()) 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 { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
client := &http.Client{} client := &http.Client{Timeout: 2 * time.Minute}
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
+97 -47
View File
@@ -7,13 +7,16 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"net" "net"
"net/http"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global" "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/certificate"
"github.com/go-acme/lego/v5/challenge/dns01" "github.com/go-acme/lego/v5/challenge/dns01"
"github.com/go-acme/lego/v5/lego" "github.com/go-acme/lego/v5/lego"
@@ -28,6 +31,8 @@ import (
// occasionally fail propagation checks against the wrong resolver. // occasionally fail propagation checks against the wrong resolver.
var dnsChallengeMu sync.Mutex var dnsChallengeMu sync.Mutex
var ErrAcmeAccountURLMissing = errors.New("acme account url is empty")
type AcmeClientOption func(*AcmeClientOptions) type AcmeClientOption func(*AcmeClientOptions)
type AcmeClientOptions struct { type AcmeClientOptions struct {
@@ -40,18 +45,35 @@ type AcmeClient struct {
User *AcmeUser User *AcmeUser
ProxyURL string ProxyURL string
// dnsChallengeLocked records whether this client currently holds dnsChallengeConfig *dnsChallengeConfig
// dnsChallengeMu. It is set by UseDns and cleared by ObtainSSL/ }
// ObtainIPSSL once the DNS-01 flow finishes. UseHTTP does not touch it.
dnsChallengeLocked bool type dnsChallengeConfig struct {
recursiveNameservers []string
timeout time.Duration
disableCNAME bool
} }
func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) { 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 == "" { if acmeAccount.Email == "" {
return nil, errors.New("email can not blank") 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 { if err != nil {
return nil, err 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 { 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 { if err != nil {
return err return err
} }
@@ -71,46 +97,20 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
nameservers = append(nameservers, websiteSSL.Nameserver2) 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 var opts []dns01.ChallengeOption
if websiteSSL.SkipDNS { if websiteSSL.SkipDNS {
opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement()) opts = append(opts, dns01.DisableAuthoritativeNssPropagationRequirement())
} }
if err := c.Client.Challenge.SetDNS01Provider(p, opts...); err != nil { if err := c.Client.Challenge.SetDNS01Provider(p, opts...); err != nil {
c.releaseDNSLock()
return err return err
} }
return nil c.dnsChallengeConfig = &dnsChallengeConfig{
} recursiveNameservers: append([]string(nil), nameservers...),
timeout: dnsTimeOut,
// releaseDNSLock releases dnsChallengeMu if it was acquired by UseDns. disableCNAME: websiteSSL.DisableCNAME,
// 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()
} }
return nil
} }
func (c *AcmeClient) UseHTTP(path string) error { func (c *AcmeClient) UseHTTP(path string) error {
@@ -123,11 +123,14 @@ func (c *AcmeClient) UseHTTP(path string) error {
if err != nil { if err != nil {
return err return err
} }
c.dnsChallengeConfig = nil
return nil return nil
} }
func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (certificate.Resource, error) { func (c *AcmeClient) ObtainSSL(ctx context.Context, domains []string, privateKey crypto.Signer) (certificate.Resource, error) {
defer c.releaseDNSLock() unlockDNSChallenge := c.lockDNSChallenge()
defer unlockDNSChallenge()
// lego v5 disables Common Name by default; explicitly enable it to keep // 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 // the v4 behaviour, so legacy Java/router clients that still rely on the
// CommonName field do not fail TLS handshake. // CommonName field do not fail TLS handshake.
@@ -138,8 +141,6 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
EnableCommonName: true, EnableCommonName: true,
} }
ctx := context.Background()
var certificates *certificate.Resource var certificates *certificate.Resource
var err error var err error
@@ -152,7 +153,9 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
if isHTTP503Error(err) && attempt < maxRetryAttempts { if isHTTP503Error(err) && attempt < maxRetryAttempts {
global.LOG.Warnf("ACME server returned 503, retrying in %v (attempt %d/%d)", global.LOG.Warnf("ACME server returned 503, retrying in %v (attempt %d/%d)",
retryDelayOn503, attempt, maxRetryAttempts) retryDelayOn503, attempt, maxRetryAttempts)
time.Sleep(retryDelayOn503) if err := waitForRetry(ctx, retryDelayOn503); err != nil {
return certificate.Resource{}, err
}
continue continue
} }
@@ -163,8 +166,10 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.Signer) (cert
return certificate.Resource{}, err return certificate.Resource{}, err
} }
func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certificate.Resource, error) { func (c *AcmeClient) ObtainIPSSL(ctx context.Context, ipAddress string, privKey crypto.Signer) (certificate.Resource, error) {
defer c.releaseDNSLock() unlockDNSChallenge := c.lockDNSChallenge()
defer unlockDNSChallenge()
csrTemplate := &x509.CertificateRequest{ csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "", CommonName: "",
@@ -192,8 +197,6 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
Bundle: true, Bundle: true,
} }
ctx := context.Background()
var certificates *certificate.Resource var certificates *certificate.Resource
for attempt := 1; attempt <= maxRetryAttempts; attempt++ { for attempt := 1; attempt <= maxRetryAttempts; attempt++ {
certificates, err = c.Client.Certificate.ObtainForCSR(ctx, req) 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 { if isHTTP503Error(err) && attempt < maxRetryAttempts {
global.LOG.Warnf("ACME server returned 503 for IP SSL, retrying in %v (attempt %d/%d)", global.LOG.Warnf("ACME server returned 503 for IP SSL, retrying in %v (attempt %d/%d)",
retryDelayOn503, attempt, maxRetryAttempts) retryDelayOn503, attempt, maxRetryAttempts)
time.Sleep(retryDelayOn503) if err := waitForRetry(ctx, retryDelayOn503); err != nil {
return certificate.Resource{}, err
}
continue continue
} }
@@ -217,3 +222,48 @@ func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.Signer) (certi
func (c *AcmeClient) RevokeSSL(pemSSL []byte) error { func (c *AcmeClient) RevokeSSL(pemSSL []byte) error {
return c.Client.Certificate.Revoke(context.Background(), pemSSL) 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 ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"reflect"
"time" "time"
"github.com/go-acme/lego/v5/challenge" "github.com/go-acme/lego/v5/challenge"
@@ -95,7 +97,7 @@ var (
manualDnsTimeout = 10 * time.Minute 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 ( var (
param DNSParam param DNSParam
p challenge.Provider p challenge.Provider
@@ -106,7 +108,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
} }
switch dnsType { switch dnsType {
case AliYun: case AliYun:
config := alidns.NewDefaultConfig() config := newDNSProviderConfig(alidns.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey config.SecretKey = param.SecretKey
config.APIKey = param.AccessKey config.APIKey = param.AccessKey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -114,7 +116,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = alidns.NewDNSProviderConfig(config) p, err = alidns.NewDNSProviderConfig(config)
case AliESA: case AliESA:
config := aliesa.NewDefaultConfig() config := newDNSProviderConfig(aliesa.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey config.SecretKey = param.SecretKey
config.APIKey = param.AccessKey config.APIKey = param.AccessKey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -122,7 +124,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = aliesa.NewDNSProviderConfig(config) p, err = aliesa.NewDNSProviderConfig(config)
case AWSRoute53: case AWSRoute53:
config := route53.NewDefaultConfig() config := newDNSProviderConfig(route53.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey config.SecretAccessKey = param.SecretKey
config.Region = param.Region config.Region = param.Region
@@ -135,7 +137,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = route53.NewDNSProviderConfig(config) p, err = route53.NewDNSProviderConfig(config)
case CloudFlare: case CloudFlare:
config := cloudflare.NewDefaultConfig() config := newDNSProviderConfig(cloudflare.NewDefaultConfig(), httpClient)
config.AuthEmail = param.Email config.AuthEmail = param.Email
config.AuthToken = param.APIkey config.AuthToken = param.APIkey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -143,7 +145,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = cloudflare.NewDNSProviderConfig(config) p, err = cloudflare.NewDNSProviderConfig(config)
case CloudDns: case CloudDns:
config := clouddns.NewDefaultConfig() config := newDNSProviderConfig(clouddns.NewDefaultConfig(), httpClient)
config.ClientID = param.ClientID config.ClientID = param.ClientID
config.Email = param.Email config.Email = param.Email
config.Password = param.Password config.Password = param.Password
@@ -152,7 +154,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = clouddns.NewDNSProviderConfig(config) p, err = clouddns.NewDNSProviderConfig(config)
case NameCheap: case NameCheap:
config := namecheap.NewDefaultConfig() config := newDNSProviderConfig(namecheap.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.APIUser = param.APIUser config.APIUser = param.APIUser
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -160,14 +162,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = namecheap.NewDNSProviderConfig(config) p, err = namecheap.NewDNSProviderConfig(config)
case NameSilo: case NameSilo:
config := namesilo.NewDefaultConfig() config := newDNSProviderConfig(namesilo.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval config.PollingInterval = pollingInterval
config.TTL = ttl config.TTL = ttl
p, err = namesilo.NewDNSProviderConfig(config) p, err = namesilo.NewDNSProviderConfig(config)
case Godaddy: case Godaddy:
config := godaddy.NewDefaultConfig() config := newDNSProviderConfig(godaddy.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.APISecret = param.APISecret config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -175,7 +177,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = godaddy.NewDNSProviderConfig(config) p, err = godaddy.NewDNSProviderConfig(config)
case NameCom: case NameCom:
config := namedotcom.NewDefaultConfig() config := newDNSProviderConfig(namedotcom.NewDefaultConfig(), httpClient)
config.APIToken = param.Token config.APIToken = param.Token
config.Username = param.APIUser config.Username = param.APIUser
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -183,7 +185,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = namedotcom.NewDNSProviderConfig(config) p, err = namedotcom.NewDNSProviderConfig(config)
case TencentCloud: case TencentCloud:
config := tencentcloud.NewDefaultConfig() config := newDNSProviderConfig(tencentcloud.NewDefaultConfig(), httpClient)
config.SecretID = param.SecretID config.SecretID = param.SecretID
config.SecretKey = param.SecretKey config.SecretKey = param.SecretKey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -191,14 +193,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = tencentcloud.NewDNSProviderConfig(config) p, err = tencentcloud.NewDNSProviderConfig(config)
case RainYun: case RainYun:
config := rainyun.NewDefaultConfig() config := newDNSProviderConfig(rainyun.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval config.PollingInterval = pollingInterval
config.TTL = ttl config.TTL = ttl
p, err = rainyun.NewDNSProviderConfig(config) p, err = rainyun.NewDNSProviderConfig(config)
case Volcengine: case Volcengine:
config := volcengine.NewDefaultConfig() config := newDNSProviderConfig(volcengine.NewDefaultConfig(), httpClient)
config.SecretKey = param.SecretKey config.SecretKey = param.SecretKey
config.AccessKey = param.AccessKey config.AccessKey = param.AccessKey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -206,7 +208,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = volcengine.NewDNSProviderConfig(config) p, err = volcengine.NewDNSProviderConfig(config)
case HuaweiCloud: case HuaweiCloud:
config := huaweicloud.NewDefaultConfig() config := newDNSProviderConfig(huaweicloud.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey config.SecretAccessKey = param.SecretKey
config.Region = param.Region config.Region = param.Region
@@ -218,19 +220,19 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = int32(ttl) config.TTL = int32(ttl)
p, err = huaweicloud.NewDNSProviderConfig(config) p, err = huaweicloud.NewDNSProviderConfig(config)
case FreeMyIP: case FreeMyIP:
config := freemyip.NewDefaultConfig() config := newDNSProviderConfig(freemyip.NewDefaultConfig(), httpClient)
config.Token = param.Token config.Token = param.Token
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval config.PollingInterval = pollingInterval
p, err = freemyip.NewDNSProviderConfig(config) p, err = freemyip.NewDNSProviderConfig(config)
case Vercel: case Vercel:
config := vercel.NewDefaultConfig() config := newDNSProviderConfig(vercel.NewDefaultConfig(), httpClient)
config.AuthToken = param.Token config.AuthToken = param.Token
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval config.PollingInterval = pollingInterval
p, err = vercel.NewDNSProviderConfig(config) p, err = vercel.NewDNSProviderConfig(config)
case Spaceship: case Spaceship:
config := spaceship.NewDefaultConfig() config := newDNSProviderConfig(spaceship.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.APISecret = param.APISecret config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -238,7 +240,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = spaceship.NewDNSProviderConfig(config) p, err = spaceship.NewDNSProviderConfig(config)
case WestCN: case WestCN:
config := westcn.NewDefaultConfig() config := newDNSProviderConfig(westcn.NewDefaultConfig(), httpClient)
config.Username = param.Username config.Username = param.Username
config.Password = param.Password config.Password = param.Password
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -246,7 +248,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = westcn.NewDNSProviderConfig(config) p, err = westcn.NewDNSProviderConfig(config)
case ClouDNS: case ClouDNS:
config := cloudns.NewDefaultConfig() config := newDNSProviderConfig(cloudns.NewDefaultConfig(), httpClient)
config.AuthID = param.AuthID config.AuthID = param.AuthID
config.SubAuthID = param.SubAuthID config.SubAuthID = param.SubAuthID
config.AuthPassword = param.AuthPassword config.AuthPassword = param.AuthPassword
@@ -255,7 +257,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = cloudns.NewDNSProviderConfig(config) p, err = cloudns.NewDNSProviderConfig(config)
case RegRu: case RegRu:
config := regru.NewDefaultConfig() config := newDNSProviderConfig(regru.NewDefaultConfig(), httpClient)
config.Username = param.Username config.Username = param.Username
config.Password = param.Password config.Password = param.Password
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -263,14 +265,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = regru.NewDNSProviderConfig(config) p, err = regru.NewDNSProviderConfig(config)
case Dynu: case Dynu:
config := dynu.NewDefaultConfig() config := newDNSProviderConfig(dynu.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval config.PollingInterval = pollingInterval
config.TTL = ttl config.TTL = ttl
p, err = dynu.NewDNSProviderConfig(config) p, err = dynu.NewDNSProviderConfig(config)
case Dynadot: case Dynadot:
config := dynadot.NewDefaultConfig() config := newDNSProviderConfig(dynadot.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.APISecret = param.APISecret config.APISecret = param.APISecret
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -278,7 +280,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = dynadot.NewDNSProviderConfig(config) p, err = dynadot.NewDNSProviderConfig(config)
case BaiduCloud: case BaiduCloud:
config := baiducloud.NewDefaultConfig() config := newDNSProviderConfig(baiducloud.NewDefaultConfig(), httpClient)
config.AccessKeyID = param.AccessKey config.AccessKeyID = param.AccessKey
config.SecretAccessKey = param.SecretKey config.SecretAccessKey = param.SecretKey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -286,7 +288,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = baiducloud.NewDNSProviderConfig(config) p, err = baiducloud.NewDNSProviderConfig(config)
case Ovh: case Ovh:
config := ovh.NewDefaultConfig() config := newDNSProviderConfig(ovh.NewDefaultConfig(), httpClient)
config.APIEndpoint = param.Endpoint config.APIEndpoint = param.Endpoint
config.AccessToken = param.AccessToken config.AccessToken = param.AccessToken
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -294,12 +296,12 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = ovh.NewDNSProviderConfig(config) p, err = ovh.NewDNSProviderConfig(config)
case AcmeDNS: case AcmeDNS:
config := acmedns.NewDefaultConfig() config := newDNSProviderConfig(acmedns.NewDefaultConfig(), httpClient)
config.APIBase = param.Endpoint config.APIBase = param.Endpoint
config.StorageBaseURL = param.BaseURL config.StorageBaseURL = param.BaseURL
p, err = acmedns.NewDNSProviderConfig(config) p, err = acmedns.NewDNSProviderConfig(config)
case PorkBun: case PorkBun:
config := porkbun.NewDefaultConfig() config := newDNSProviderConfig(porkbun.NewDefaultConfig(), httpClient)
config.APIKey = param.APIkey config.APIKey = param.APIkey
config.SecretAPIKey = param.SecretKey config.SecretAPIKey = param.SecretKey
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -307,7 +309,7 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.TTL = ttl config.TTL = ttl
p, err = porkbun.NewDNSProviderConfig(config) p, err = porkbun.NewDNSProviderConfig(config)
case Technitium: case Technitium:
config := technitium.NewDefaultConfig() config := newDNSProviderConfig(technitium.NewDefaultConfig(), httpClient)
config.BaseURL = param.BaseURL config.BaseURL = param.BaseURL
config.APIToken = param.Token config.APIToken = param.Token
config.PropagationTimeout = propagationTimeout config.PropagationTimeout = propagationTimeout
@@ -331,3 +333,27 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
} }
return p, nil 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" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"log"
"net"
"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/app/model"
"github.com/go-acme/lego/v5/certificate" "github.com/go-acme/lego/v5/certificate"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/crypto/acme" "golang.org/x/crypto/acme"
"log"
"net"
"strings"
"time"
) )
type ManualClient struct { type ManualClient struct {
@@ -30,7 +33,34 @@ type RequestCertRequest struct {
WebsiteSSL *model.WebsiteSSL 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)) key, err := parsePrivateKeyPEM([]byte(acmeAccount.PrivateKey))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -38,9 +68,20 @@ func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logg
if logger == nil { if logger == nil {
logger = log.Default() 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{ client := &acme.Client{
Key: key, Key: key,
HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword),
DirectoryURL: getCaDirURL(acmeAccount.Type, acmeAccount.CaDirURL), DirectoryURL: getCaDirURL(acmeAccount.Type, acmeAccount.CaDirURL),
} }
return &ManualClient{ return &ManualClient{
@@ -61,7 +102,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
var err error var err error
// Check if we have an existing valid order for this SSL // 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 { if exists && existingOrder != nil {
// Verify the order is still valid (not expired and still pending) // 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) // 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 // If extraction failed, fall through to create a new order
} }
// Existing order is expired or invalid, remove it // Existing order is expired or invalid, remove it
delete(Orders, websiteSSL.ID) deleteManualOrder(websiteSSL.ID, existingOrder)
} }
// Create a new order // Create a new order
@@ -83,7 +124,7 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
if err != nil { if err != nil {
return nil, err return nil, err
} }
Orders[websiteSSL.ID] = order storeManualOrder(websiteSSL.ID, order)
return c.extractDNSChallenges(ctx, 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) c.logger.Printf("[INFO] [%s] acme: Checking TXT record %s", domain, expectedRecord)
for { for {
if err := ctx.Err(); err != nil {
return err
}
c.logger.Printf("[INFO] [%s] acme: Checking DNS record propagation.", domain) c.logger.Printf("[INFO] [%s] acme: Checking DNS record propagation.", domain)
var currentRecords map[string][]string var currentRecords map[string][]string
var queryErr error 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) 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) 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) _, 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) return fmt.Errorf("failed to accept challenge: %v", err)
} }
for { for {
time.Sleep(pollingInterval) if err := waitForRetry(ctx, pollingInterval); err != nil {
return err
}
authz, err = c.client.GetAuthorization(ctx, authzURL) authz, err = c.client.GetAuthorization(ctx, authzURL)
if err != nil { if err != nil {
return fmt.Errorf("failed to get authorization while polling: %v", err) 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 return res, err
} }
order, ok := Orders[websiteSSL.ID] order, ok := loadManualOrder(websiteSSL.ID)
if !ok { if !ok {
return res, fmt.Errorf("order not found") return res, fmt.Errorf("order not found")
} }
defer delete(Orders, websiteSSL.ID) defer deleteManualOrder(websiteSSL.ID, order)
for _, authzURL := range order.AuthzURLs { for _, authzURL := range order.AuthzURLs {
if err := c.handleAuthorization(ctx, authzURL, getNameservers(*websiteSSL)); err != nil { if err := c.handleAuthorization(ctx, authzURL, getNameservers(*websiteSSL)); err != nil {
+18 -17
View File
@@ -57,23 +57,24 @@ var WebUrlMap = map[string]struct{}{
"/apps/upgrade": {}, "/apps/upgrade": {},
"/apps/setting": {}, "/apps/setting": {},
"/ai": {}, "/ai": {},
"/ai/ai-proxy": {}, "/ai/ai-proxy": {},
"/ai/ai-proxy/model-pool": {}, "/ai/ai-proxy/model-pool": {},
"/ai/ai-proxy/api-keys": {}, "/ai/ai-proxy/api-keys": {},
"/ai/ai-proxy/groups": {}, "/ai/ai-proxy/groups": {},
"/ai/ai-proxy/usage": {}, "/ai/ai-proxy/model-groups": {},
"/ai/ai-proxy/content": {}, "/ai/ai-proxy/usage": {},
"/ai/skills-hub": {}, "/ai/ai-proxy/content": {},
"/ai/skills-hub/targets": {}, "/ai/skills-hub": {},
"/ai/benchmark": {}, "/ai/skills-hub/targets": {},
"/ai/model/account": {}, "/ai/benchmark": {},
"/ai/model/local": {}, "/ai/model/account": {},
"/ai/gpu": {}, "/ai/model/local": {},
"/ai/gpu/current": {}, "/ai/gpu": {},
"/ai/gpu/history": {}, "/ai/gpu/current": {},
"/ai/mcp": {}, "/ai/gpu/history": {},
"/ai/agents/agent": {}, "/ai/mcp": {},
"/ai/agents/agent": {},
"/containers": {}, "/containers": {},
"/containers/container/operate": {}, "/containers/container/operate": {},
-18
View File
@@ -1,18 +0,0 @@
package run
import (
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
)
func Init() {
scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync")
if !global.CONF.Base.IsOffline && scriptSync == constant.StatusEnable {
if err := service.NewIScriptService().Sync(dto.OperateByTaskID{}); err != nil {
global.LOG.Errorf("sync scripts from remote failed, err: %v", err)
}
}
}
+2
View File
@@ -27,6 +27,8 @@ func PasswordExpired() gin.HandlerFunc {
c.Request.URL.Path == "/api/v2/core/settings/search" || c.Request.URL.Path == "/api/v2/core/settings/search" ||
c.Request.URL.Path == "/api/v2/core/settings/search/base" || c.Request.URL.Path == "/api/v2/core/settings/search/base" ||
c.Request.URL.Path == "/api/v2/core/xpack/settings/search" || c.Request.URL.Path == "/api/v2/core/xpack/settings/search" ||
c.Request.URL.Path == "/api/v2/core/xapp/verifyQRCode" ||
c.Request.URL.Path == "/api/v2/core/enterprise/users/info" ||
c.Request.URL.Path == "/api/v2/core/enterprise/licenses/info" || c.Request.URL.Path == "/api/v2/core/enterprise/licenses/info" ||
c.Request.URL.Path == "/api/v2/core/enterprise/licenses/status" || c.Request.URL.Path == "/api/v2/core/enterprise/licenses/status" ||
c.Request.URL.Path == "/api/v2/core/enterprise/licenses/upload" { c.Request.URL.Path == "/api/v2/core/enterprise/licenses/upload" {
-2
View File
@@ -18,7 +18,6 @@ import (
"github.com/1Panel-dev/1Panel/core/init/log" "github.com/1Panel-dev/1Panel/core/init/log"
"github.com/1Panel-dev/1Panel/core/init/migration" "github.com/1Panel-dev/1Panel/core/init/migration"
"github.com/1Panel-dev/1Panel/core/init/proxy" "github.com/1Panel-dev/1Panel/core/init/proxy"
"github.com/1Panel-dev/1Panel/core/init/run"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/soheilhy/cmux" "github.com/soheilhy/cmux"
@@ -50,7 +49,6 @@ func Start() {
hook.Init() hook.Init()
InitOthers() InitOthers()
run.Init()
proxy.Init() proxy.Init()
rootRouter := router.Routers() rootRouter := router.Routers()
+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 { func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd {
cmds := make([]*exec.Cmd, 0, len(commands)) cmds := make([]*exec.Cmd, 0, len(commands))
for _, item := range 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(os.Environ(), c.env...)
cmdItem.Env = append(cmdItem.Env, item.Env...) cmdItem.Env = append(cmdItem.Env, item.Env...)
cmdItem.Dir = c.workDir cmdItem.Dir = c.workDir
@@ -284,7 +284,6 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
var newContext context.Context var newContext context.Context
var cancel context.CancelFunc var cancel context.CancelFunc
var outputFile *os.File var outputFile *os.File
arg = filterEmptyArgs(arg)
if c.timeout != 0 { if c.timeout != 0 {
if c.context == nil { 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{} { func contextDone(ctx context.Context) <-chan struct{} {
if ctx == nil { if ctx == nil {
return 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 }); 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) => { export const uploadEnterpriseLicense = (params: FormData) => {
return http.upload('/core/enterprise/licenses/upload', params); 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) => { 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) => { export const updateSSL = (req: Website.SSLUpdate) => {
@@ -171,10 +171,11 @@ const closeRightClick = () => {
const disabled = computed(() => { const disabled = computed(() => {
return function (btn: any) { return function (btn: any) {
let permissionDisabled = false; let permissionDisabled = false;
const permissionOptions = { nodeAdmin: btn.nodeAdmin === true };
if (btn.permission === true) { if (btn.permission === true) {
permissionDisabled = !hasManagePermissionAccess(); permissionDisabled = !hasManagePermissionAccess(undefined, permissionOptions);
} else if (btn.permission !== undefined) { } else if (btn.permission !== undefined) {
permissionDisabled = !hasPermissionAccess(btn.permission); permissionDisabled = !hasPermissionAccess(btn.permission, permissionOptions);
} }
const buttonDisabled = const buttonDisabled =
typeof btn.disabled === 'function' ? btn.disabled(rightClick.value.currentRow) : btn.disabled; 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) => { const isButtonDisabled = (button: FuTableOperationButton, row: any) => {
let permissionDisabled = false; let permissionDisabled = false;
const permissionOptions = { nodeAdmin: button.nodeAdmin === true };
if (button.permission === true) { if (button.permission === true) {
permissionDisabled = !hasManagePermissionAccess(); permissionDisabled = !hasManagePermissionAccess(undefined, permissionOptions);
} else if (button.permission !== undefined) { } else if (button.permission !== undefined) {
permissionDisabled = !hasPermissionAccess(button.permission); permissionDisabled = !hasPermissionAccess(button.permission, permissionOptions);
} }
return permissionDisabled || Boolean(resolveMaybeFn(button.disabled ?? false, row)); return permissionDisabled || Boolean(resolveMaybeFn(button.disabled ?? false, row));
}; };
+1
View File
@@ -14,6 +14,7 @@ export interface FuTableOperationButton {
click?: (row: any) => void; click?: (row: any) => void;
disabled?: boolean | ((row: any) => boolean); disabled?: boolean | ((row: any) => boolean);
permission?: true | PermissionBindingValue; permission?: true | PermissionBindingValue;
nodeAdmin?: boolean;
show?: boolean | ((row: any) => boolean); show?: boolean | ((row: any) => boolean);
type?: string; type?: string;
icon?: any; icon?: any;
@@ -38,7 +38,7 @@ const handleChange = (value) => {
const search = async () => { const search = async () => {
try { try {
const res = await listNodes('all'); const res = await listNodes('all');
nodes.value = res.data || []; nodes.value = res || [];
} catch (error) {} } catch (error) {}
}; };
@@ -148,6 +148,61 @@ function initChart() {
} }
const grid = props.option.grid || { left: '7%', right: '7%', bottom: '20%' }; 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 = { const option = {
title: [ title: [
{ {
@@ -158,59 +213,10 @@ function initChart() {
], ],
zlevel: 1, zlevel: 1,
z: 1, z: 1,
tooltip: props.option.tooltip || { tooltip: {
trigger: 'axis', appendToBody: true,
formatter: function (datas: any) { ...tooltip,
let res = datas[0].name + '<br/>'; extraCssText: `${tooltip.extraCssText || ''}; z-index: 3000;`,
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;
},
}, },
grid, grid,
legend: itemSelect || { legend: itemSelect || {
+2 -1
View File
@@ -1,9 +1,10 @@
import { App, Directive } from 'vue'; import { App, Directive } from 'vue';
import integerInput from './modules/integer'; import integerInput from './modules/integer';
import permission from './modules/permission'; import permission, { nodeAdminDirective } from './modules/permission';
const directivesList: { [key: string]: Directive } = { const directivesList: { [key: string]: Directive } = {
'integer-input': integerInput, 'integer-input': integerInput,
'node-admin': nodeAdminDirective,
permission, 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_POINTER_EVENTS_ATTR = 'data-permission-pointer-events';
const PERMISSION_NATIVE_DISABLED_ATTR = 'data-permission-native-disabled'; const PERMISSION_NATIVE_DISABLED_ATTR = 'data-permission-native-disabled';
const PERMISSION_TABINDEX_ATTR = 'data-permission-tabindex'; const PERMISSION_TABINDEX_ATTR = 'data-permission-tabindex';
const NODE_ADMIN_PERMISSION_ATTR = 'data-node-admin-permission';
const getDisableTargets = (el: HTMLElement) => { const getDisableTargets = (el: HTMLElement) => {
const targets = [el, ...Array.from(el.querySelectorAll<HTMLElement>('button, input, select, textarea'))]; 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'; 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 applyPermission = (el: HTMLElement, binding: DirectiveBinding<PermissionBindingValue>, vnode: VNode) => {
const options = { nodeAdmin: hasNodeAdminDirective(el, vnode) };
const disabled = const disabled =
getPermissionMode(binding) === 'view' getPermissionMode(binding) === 'view'
? !hasPermissionAccess(binding.value) ? !hasPermissionAccess(binding.value, options)
: !hasManagePermissionAccess(binding.value); : !hasManagePermissionAccess(binding.value, options);
const controller = getComponentPermissionController(vnode); const controller = getComponentPermissionController(vnode);
if (controller?.setPermissionDisabled) { 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; export default permissionDirective;
+5
View File
@@ -2054,11 +2054,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AI Proxy', ai_proxy: 'AI Proxy',
ai_benchmark: 'Benchmark',
groups: 'Group', groups: 'Group',
hosts: 'Host', hosts: 'Host',
apps: 'App', apps: 'App',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'Website', websites: 'Website',
monitor: 'Website Monitor',
containers: 'Container', containers: 'Container',
files: 'File', files: 'File',
runtimes: 'Runtime', runtimes: 'Runtime',
@@ -2077,6 +2079,7 @@ const message = {
commands: 'Quick Commands', commands: 'Quick Commands',
opsReport: 'Ops Report', opsReport: 'Ops Report',
users: 'User Management', users: 'User Management',
roles: 'Role',
auth: 'Login Authentication', auth: 'Login Authentication',
alert: 'Alert Notifications', alert: 'Alert Notifications',
}, },
@@ -2885,6 +2888,8 @@ const message = {
'Please ensure there is sufficient disk space on the server (Snapshot file size: {0}, Available space: {1})', 'Please ensure there is sufficient disk space on the server (Snapshot file size: {0}, Available space: {1})',
recoverHelper3: recoverHelper3:
'Please ensure the server architecture matches the architecture of the server where the snapshot was created (Current server architecture: {0})', 'Please ensure the server architecture matches the architecture of the server where the snapshot was created (Current server architecture: {0})',
recoverHelper4:
'This operation only rolls back the primary node. If any upgraded secondary nodes exist, roll them back first before continuing.',
rollback: 'Rollback', rollback: 'Rollback',
rollbackHelper: rollbackHelper:
'Rolling back this recovery will replace all files from this recovery, and may require restarting Docker and 1Panel services. Continue?', 'Rolling back this recovery will replace all files from this recovery, and may require restarting Docker and 1Panel services. Continue?',
+5
View File
@@ -2109,11 +2109,13 @@ const message = {
ai: 'IA', ai: 'IA',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'Proxy de IA', ai_proxy: 'Proxy de IA',
ai_benchmark: 'Benchmark',
groups: 'Grupo', groups: 'Grupo',
hosts: 'Host', hosts: 'Host',
apps: 'Aplicación', apps: 'Aplicación',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'Sitio web', websites: 'Sitio web',
monitor: 'Monitoreo de sitios web',
containers: 'Contenedor', containers: 'Contenedor',
files: 'Archivo', files: 'Archivo',
runtimes: 'Runtime', runtimes: 'Runtime',
@@ -2132,6 +2134,7 @@ const message = {
commands: 'Comandos rápidos', commands: 'Comandos rápidos',
opsReport: 'Informe de operaciones', opsReport: 'Informe de operaciones',
users: 'Gestión de usuarios', users: 'Gestión de usuarios',
roles: 'Rol',
auth: 'Autenticación de inicio de sesión', auth: 'Autenticación de inicio de sesión',
alert: 'Notificaciones de alerta', alert: 'Notificaciones de alerta',
}, },
@@ -2959,6 +2962,8 @@ const message = {
'Asegúrate de que haya suficiente espacio en disco en el servidor (Tamaño del archivo de instantánea: {0}, Espacio disponible: {1})', 'Asegúrate de que haya suficiente espacio en disco en el servidor (Tamaño del archivo de instantánea: {0}, Espacio disponible: {1})',
recoverHelper3: recoverHelper3:
'Asegúrate de que la arquitectura del servidor coincida con la del servidor donde se creó la instantánea (Arquitectura actual: {0})', 'Asegúrate de que la arquitectura del servidor coincida con la del servidor donde se creó la instantánea (Arquitectura actual: {0})',
recoverHelper4:
'Esta operación solo revierte el nodo principal. Si existen nodos secundarios actualizados, reviértelos primero antes de continuar.',
rollback: 'Reversión', rollback: 'Reversión',
rollbackHelper: rollbackHelper:
'Revertir esta recuperación reemplazará todos los archivos de esta recuperación y puede requerir reiniciar Docker y los servicios de 1Panel. ¿Deseas continuar?', 'Revertir esta recuperación reemplazará todos los archivos de esta recuperación y puede requerir reiniciar Docker y los servicios de 1Panel. ¿Deseas continuar?',
+5
View File
@@ -2080,11 +2080,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AIプロキシ', ai_proxy: 'AIプロキシ',
ai_benchmark: 'ベンチマーク',
groups: 'グループ', groups: 'グループ',
hosts: 'ホスト', hosts: 'ホスト',
apps: 'アプリケーション', apps: 'アプリケーション',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'ウェブサイト', websites: 'ウェブサイト',
monitor: 'ウェブサイト監視',
containers: 'コンテナ', containers: 'コンテナ',
files: 'ファイル管理', files: 'ファイル管理',
runtimes: 'ランタイム', runtimes: 'ランタイム',
@@ -2103,6 +2105,7 @@ const message = {
commands: 'クイックコマンド', commands: 'クイックコマンド',
opsReport: '運用レポート', opsReport: '運用レポート',
users: 'ユーザー管理', users: 'ユーザー管理',
roles: 'ロール',
auth: 'ログイン認証', auth: 'ログイン認証',
alert: 'アラート通知', alert: 'アラート通知',
}, },
@@ -2891,6 +2894,8 @@ const message = {
'サーバーに十分なディスクスペースがあることを確認してください(スナップショットファイルサイズ:{0}、利用可能なスペース:{1})', 'サーバーに十分なディスクスペースがあることを確認してください(スナップショットファイルサイズ:{0}、利用可能なスペース:{1})',
recoverHelper3: recoverHelper3:
'サーバーアーキテクチャが、スナップショットが作成されたサーバーのアーキテクチャと一致していることを確認してください(現在のサーバーアーキテクチャ:{0})', 'サーバーアーキテクチャが、スナップショットが作成されたサーバーのアーキテクチャと一致していることを確認してください(現在のサーバーアーキテクチャ:{0})',
recoverHelper4:
'この操作はプライマリノードのみをロールバックします。アップグレード済みの子ノードがある場合は、先に子ノードをロールバックしてから続行してください。',
rollback: 'ロールバック', rollback: 'ロールバック',
rollbackHelper: rollbackHelper:
'この回復をロールバックすると、この回復からすべてのファイルを置き換え、Dockerサービスと1パネルサービスを再起動する必要がある場合があります。続けたいですか?', 'この回復をロールバックすると、この回復からすべてのファイルを置き換え、Dockerサービスと1パネルサービスを再起動する必要がある場合があります。続けたいですか?',
+5
View File
@@ -2038,11 +2038,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AI 프록시', ai_proxy: 'AI 프록시',
ai_benchmark: '벤치마크',
groups: '그룹', groups: '그룹',
hosts: '호스트', hosts: '호스트',
apps: '애플리케이션', apps: '애플리케이션',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: '웹사이트', websites: '웹사이트',
monitor: '웹사이트 모니터링',
containers: '컨테이너', containers: '컨테이너',
files: '파일 관리', files: '파일 관리',
runtimes: '실행 환경', runtimes: '실행 환경',
@@ -2061,6 +2063,7 @@ const message = {
commands: '빠른 명령', commands: '빠른 명령',
opsReport: '운영 보고서', opsReport: '운영 보고서',
users: '사용자 관리', users: '사용자 관리',
roles: '역할',
auth: '로그인 인증', auth: '로그인 인증',
alert: '경고 알림', alert: '경고 알림',
}, },
@@ -2831,6 +2834,8 @@ const message = {
recoverHelper2: '서버에 충분한 디스크 공간이 있는지 확인하세요. (스냅샷 파일 크기: {0}, 사용 가능한 공간: {1})', recoverHelper2: '서버에 충분한 디스크 공간이 있는지 확인하세요. (스냅샷 파일 크기: {0}, 사용 가능한 공간: {1})',
recoverHelper3: recoverHelper3:
'서버 아키텍처가 스냅샷이 생성된 서버의 아키텍처와 일치하는지 확인하세요. (현재 서버 아키텍처: {0})', '서버 아키텍처가 스냅샷이 생성된 서버의 아키텍처와 일치하는지 확인하세요. (현재 서버 아키텍처: {0})',
recoverHelper4:
'이 작업은 기본 노드만 롤백합니다. 업그레이드된 하위 노드가 있다면 먼저 하위 노드를 롤백한 후 계속 진행하세요.',
rollback: '롤백', rollback: '롤백',
rollbackHelper: rollbackHelper:
'이 복구를 롤백하면 해당 복구의 모든 파일이 대체되며 Docker 및 1Panel 서비스를 재시작해야 할 수 있습니다. 계속하시겠습니까?', '이 복구를 롤백하면 해당 복구의 모든 파일이 대체되며 Docker 및 1Panel 서비스를 재시작해야 할 수 있습니다. 계속하시겠습니까?',
+5
View File
@@ -2107,11 +2107,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'Proksi AI', ai_proxy: 'Proksi AI',
ai_benchmark: 'Penanda Aras',
groups: 'Kumpulan', groups: 'Kumpulan',
hosts: 'Hos', hosts: 'Hos',
apps: 'Aplikasi', apps: 'Aplikasi',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'Laman Web', websites: 'Laman Web',
monitor: 'Pemantauan Laman Web',
containers: 'Kontena', containers: 'Kontena',
files: 'Pengurusan Fail', files: 'Pengurusan Fail',
runtimes: 'Persekitaran Jalankan', runtimes: 'Persekitaran Jalankan',
@@ -2130,6 +2132,7 @@ const message = {
commands: 'Perintah Pantas', commands: 'Perintah Pantas',
opsReport: 'Laporan Operasi', opsReport: 'Laporan Operasi',
users: 'Pengurusan pengguna', users: 'Pengurusan pengguna',
roles: 'Peranan',
auth: 'Pengesahan log masuk', auth: 'Pengesahan log masuk',
alert: 'Pemberitahuan amaran', alert: 'Pemberitahuan amaran',
}, },
@@ -2943,6 +2946,8 @@ const message = {
'Pastikan terdapat ruang cakera yang mencukupi pada pelayan (Saiz fail snapshot: {0}, Ruang tersedia: {1})', 'Pastikan terdapat ruang cakera yang mencukupi pada pelayan (Saiz fail snapshot: {0}, Ruang tersedia: {1})',
recoverHelper3: recoverHelper3:
'Pastikan seni bina pelayan sepadan dengan seni bina pelayan di mana snapshot dicipta (Seni bina pelayan semasa: {0})', 'Pastikan seni bina pelayan sepadan dengan seni bina pelayan di mana snapshot dicipta (Seni bina pelayan semasa: {0})',
recoverHelper4:
'Operasi ini hanya menggulung balik nod utama. Jika terdapat nod anak yang telah dinaik taraf, sila gulungkan balik nod anak terlebih dahulu sebelum meneruskan.',
rollback: 'Pulangkan semula', rollback: 'Pulangkan semula',
rollbackHelper: rollbackHelper:
'Pulangkan semula pemulihan ini akan menggantikan semua fail dari pemulihan ini, dan mungkin memerlukan mulakan semula perkhidmatan Docker dan 1Panel. Adakah anda mahu meneruskan?', 'Pulangkan semula pemulihan ini akan menggantikan semua fail dari pemulihan ini, dan mungkin memerlukan mulakan semula perkhidmatan Docker dan 1Panel. Adakah anda mahu meneruskan?',
+5
View File
@@ -2221,11 +2221,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'Proxy de IA', ai_proxy: 'Proxy de IA',
ai_benchmark: 'Benchmark',
groups: 'Grupos', groups: 'Grupos',
hosts: 'Hosts', hosts: 'Hosts',
apps: 'Aplicativos', apps: 'Aplicativos',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'Sites', websites: 'Sites',
monitor: 'Monitoramento de Sites',
containers: 'Contêineres', containers: 'Contêineres',
files: 'Gerenciamento de Arquivos', files: 'Gerenciamento de Arquivos',
runtimes: 'Ambientes de Execução', runtimes: 'Ambientes de Execução',
@@ -2244,6 +2246,7 @@ const message = {
commands: 'Comandos Rápidos', commands: 'Comandos Rápidos',
opsReport: 'Relatório de Operações', opsReport: 'Relatório de Operações',
users: 'Gerenciamento de usuários', users: 'Gerenciamento de usuários',
roles: 'Função',
auth: 'Autenticação de login', auth: 'Autenticação de login',
alert: 'Notificações de alerta', alert: 'Notificações de alerta',
}, },
@@ -3056,6 +3059,8 @@ const message = {
'Por favor, assegure-se de que há espaço suficiente em disco no servidor (Tamanho do arquivo de snapshot: {0}, Espaço disponível: {1})', 'Por favor, assegure-se de que há espaço suficiente em disco no servidor (Tamanho do arquivo de snapshot: {0}, Espaço disponível: {1})',
recoverHelper3: recoverHelper3:
'Por favor, assegure-se de que a arquitetura do servidor corresponda à arquitetura do servidor onde o snapshot foi criado (Arquitetura do servidor atual: {0})', 'Por favor, assegure-se de que a arquitetura do servidor corresponda à arquitetura do servidor onde o snapshot foi criado (Arquitetura do servidor atual: {0})',
recoverHelper4:
'Esta operação faz rollback apenas do nó principal. Se houver nós secundários atualizados, faça o rollback deles primeiro antes de continuar.',
rollback: 'Rollback', rollback: 'Rollback',
rollbackHelper: rollbackHelper:
'Reverter essa recuperação substituirá todos os arquivos dessa recuperação e pode exigir reiniciar os serviços Docker e 1Panel. Você deseja continuar?', 'Reverter essa recuperação substituirá todos os arquivos dessa recuperação e pode exigir reiniciar os serviços Docker e 1Panel. Você deseja continuar?',
+5
View File
@@ -2094,11 +2094,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AI Proxy', ai_proxy: 'AI Proxy',
ai_benchmark: 'Бенчмарк',
groups: 'Группы', groups: 'Группы',
hosts: 'Хосты', hosts: 'Хосты',
apps: 'Приложения', apps: 'Приложения',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'Вебсайты', websites: 'Вебсайты',
monitor: 'Мониторинг веб-сайтов',
containers: 'Контейнеры', containers: 'Контейнеры',
files: 'Управление файлами', files: 'Управление файлами',
runtimes: 'Среды выполнения', runtimes: 'Среды выполнения',
@@ -2117,6 +2119,7 @@ const message = {
commands: 'Быстрые команды', commands: 'Быстрые команды',
opsReport: 'Операционный отчет', opsReport: 'Операционный отчет',
users: 'Управление пользователями', users: 'Управление пользователями',
roles: 'Роль',
auth: 'Аутентификация входа', auth: 'Аутентификация входа',
alert: 'Уведомления о тревогах', alert: 'Уведомления о тревогах',
}, },
@@ -2920,6 +2923,8 @@ const message = {
'Пожалуйста, убедитесь, что на сервере достаточно места на диске (Размер файла снапшота: {0}, Доступное место: {1})', 'Пожалуйста, убедитесь, что на сервере достаточно места на диске (Размер файла снапшота: {0}, Доступное место: {1})',
recoverHelper3: recoverHelper3:
'Пожалуйста, убедитесь, что архитектура сервера соответствует архитектуре сервера, где был создан снапшот (Текущая архитектура сервера: {0})', 'Пожалуйста, убедитесь, что архитектура сервера соответствует архитектуре сервера, где был создан снапшот (Текущая архитектура сервера: {0})',
recoverHelper4:
'Это действие откатывает только основной узел. Если есть обновлённые дочерние узлы, сначала откатите их, прежде чем продолжать.',
rollback: 'Откатить', rollback: 'Откатить',
rollbackHelper: rollbackHelper:
'Откат этого восстановления заменит все файлы из этого восстановления и может потребовать перезапуска служб Docker и 1Panel. Хотите продолжить?', 'Откат этого восстановления заменит все файлы из этого восстановления и может потребовать перезапуска служб Docker и 1Panel. Хотите продолжить?',
+5
View File
@@ -2103,11 +2103,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AI Proxy', ai_proxy: 'AI Proxy',
ai_benchmark: 'Benchmark',
groups: 'Grup', groups: 'Grup',
hosts: 'Ana Bilgisayar', hosts: 'Ana Bilgisayar',
apps: 'Uygulama', apps: 'Uygulama',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: 'Website', websites: 'Website',
monitor: 'Website İzleme',
containers: 'Konteyner', containers: 'Konteyner',
files: 'Dosya', files: 'Dosya',
runtimes: 'Çalışma Zamanı', runtimes: 'Çalışma Zamanı',
@@ -2126,6 +2128,7 @@ const message = {
commands: 'Hızlı Komutlar', commands: 'Hızlı Komutlar',
opsReport: 'Operasyon Raporu', opsReport: 'Operasyon Raporu',
users: 'Kullanıcı yönetimi', users: 'Kullanıcı yönetimi',
roles: 'Rol',
auth: 'Giriş kimlik doğrulaması', auth: 'Giriş kimlik doğrulaması',
alert: 'Uyarı bildirimleri', alert: 'Uyarı bildirimleri',
}, },
@@ -2946,6 +2949,8 @@ const message = {
'Sunucuda yeterli disk alanının olduğundan emin olun (Anlık görüntü dosyası boyutu: {0}, Mevcut alan: {1})', 'Sunucuda yeterli disk alanının olduğundan emin olun (Anlık görüntü dosyası boyutu: {0}, Mevcut alan: {1})',
recoverHelper3: recoverHelper3:
'Sunucu mimarisinin, anlık görüntünün oluşturulduğu sunucu mimarisiyle eşleştiğinden emin olun (Geçerli sunucu mimarisi: {0})', 'Sunucu mimarisinin, anlık görüntünün oluşturulduğu sunucu mimarisiyle eşleştiğinden emin olun (Geçerli sunucu mimarisi: {0})',
recoverHelper4:
'Bu işlem yalnızca ana düğümü geri alır. Yükseltilmiş alt düğümler varsa, devam etmeden önce önce bunları geri alın.',
rollback: 'Geri al', rollback: 'Geri al',
rollbackHelper: rollbackHelper:
'Bu kurtarmayı geri almak, bu kurtarmadan gelen tüm dosyaları değiştirecek ve Docker ile 1Panel servislerinin yeniden başlatılmasını gerektirebilir. Devam etmek istiyor musunuz?', 'Bu kurtarmayı geri almak, bu kurtarmadan gelen tüm dosyaları değiştirecek ve Docker ile 1Panel servislerinin yeniden başlatılmasını gerektirebilir. Devam etmek istiyor musunuz?',
+4
View File
@@ -1950,11 +1950,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AI 網關', ai_proxy: 'AI 網關',
ai_benchmark: '基準測試',
groups: '分組', groups: '分組',
hosts: '主機', hosts: '主機',
apps: '應用', apps: '應用',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: '網站', websites: '網站',
monitor: '網站監控',
containers: '容器', containers: '容器',
files: '檔案管理', files: '檔案管理',
runtimes: '執行環境', runtimes: '執行環境',
@@ -1973,6 +1975,7 @@ const message = {
commands: '快速指令', commands: '快速指令',
opsReport: '運維報表', opsReport: '運維報表',
users: '使用者管理', users: '使用者管理',
roles: '角色',
auth: '登入認證', auth: '登入認證',
alert: '告警通知', alert: '告警通知',
}, },
@@ -2608,6 +2611,7 @@ const message = {
recoverHelper1: '復原需要重新啟動 Docker 以及 1Panel 服務', recoverHelper1: '復原需要重新啟動 Docker 以及 1Panel 服務',
recoverHelper2: '請確保伺服器磁碟空間充足 ( 快照檔案大小: {0}, 可用空間: {1} )', recoverHelper2: '請確保伺服器磁碟空間充足 ( 快照檔案大小: {0}, 可用空間: {1} )',
recoverHelper3: '請確保伺服器架構與建立快照伺服器架構資訊保持一致 (目前伺服器架構: {0} )', recoverHelper3: '請確保伺服器架構與建立快照伺服器架構資訊保持一致 (目前伺服器架構: {0} )',
recoverHelper4: '此操作僅會回滾主節點,如存在已升級的子節點,請先回滾子節點後再執行此操作',
rollback: '回滾', rollback: '回滾',
rollbackHelper: rollbackHelper:
'即將回滾本次復原,回滾將取代所有本次復原的檔案,過程中可能需要重新啟動 Docker 以及 1Panel 服務,是否繼續?', '即將回滾本次復原,回滾將取代所有本次復原的檔案,過程中可能需要重新啟動 Docker 以及 1Panel 服務,是否繼續?',
+4
View File
@@ -1934,11 +1934,13 @@ const message = {
ai: 'AI', ai: 'AI',
skills_hub: 'Skills Hub', skills_hub: 'Skills Hub',
ai_proxy: 'AI 网关', ai_proxy: 'AI 网关',
ai_benchmark: '基准测试',
groups: '分组', groups: '分组',
hosts: '主机', hosts: '主机',
apps: '应用', apps: '应用',
openresty: 'OpenResty', openresty: 'OpenResty',
websites: '网站', websites: '网站',
monitor: '网站监控',
containers: '容器', containers: '容器',
files: '文件管理', files: '文件管理',
runtimes: '运行环境', runtimes: '运行环境',
@@ -1957,6 +1959,7 @@ const message = {
commands: '快速命令', commands: '快速命令',
opsReport: '运维报表', opsReport: '运维报表',
users: '用户管理', users: '用户管理',
roles: '角色',
auth: '登陆认证', auth: '登陆认证',
alert: '告警通知', alert: '告警通知',
}, },
@@ -2582,6 +2585,7 @@ const message = {
recoverHelper1: '恢复需要重启 Docker 以及 1Panel 服务', recoverHelper1: '恢复需要重启 Docker 以及 1Panel 服务',
recoverHelper2: '请确保服务器磁盘空间充足 ( 快照文件大小: {0}, 可用空间: {1} )', recoverHelper2: '请确保服务器磁盘空间充足 ( 快照文件大小: {0}, 可用空间: {1} )',
recoverHelper3: '请确保服务器架构与创建快照服务器架构信息保持一致 (当前服务器架构: {0} )', recoverHelper3: '请确保服务器架构与创建快照服务器架构信息保持一致 (当前服务器架构: {0} )',
recoverHelper4: '该操作仅回滚主节点,如存在已升级的子节点,请先回滚子节点后再执行此操作',
rollback: '回滚', rollback: '回滚',
rollbackHelper: rollbackHelper:
'即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 Docker 以及 1Panel 服务,是否继续?', '即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 Docker 以及 1Panel 服务,是否继续?',
+3
View File
@@ -105,6 +105,9 @@ router.beforeEach(async (to, from, next) => {
if (to.path === '/apps/all' && to.query.install != undefined) { if (to.path === '/apps/all' && to.query.install != undefined) {
return next(); return next();
} }
if (to.name === 'Expired') {
return next();
}
const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || ''); const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || '');
if (to.query.uncached != undefined) { if (to.query.uncached != undefined) {
const query = { ...to.query }; const query = { ...to.query };
+3 -9
View File
@@ -1,5 +1,5 @@
import { Setting } from '@/api/interface/setting'; import { Setting } from '@/api/interface/setting';
import { listNodeOptions, loadNodeByUser } from '@/api/modules/setting'; import { listNodeOptions } from '@/api/modules/setting';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
export const changeToLocal = async () => { export const changeToLocal = async () => {
@@ -23,15 +23,9 @@ export const changeToLocal = async () => {
}; };
export async function listNodes(type: string): Promise<Array<Setting.NodeItem>> { export async function listNodes(type: string): Promise<Array<Setting.NodeItem>> {
const globalStore = GlobalStore();
try { try {
if (globalStore.isAdmin) { const res = await listNodeOptions(type);
const res = await listNodeOptions(type); return res.data || [];
return res.data || [];
} else {
const res = await loadNodeByUser();
return res.data || [];
}
} catch (error) { } catch (error) {
return []; return [];
} }
+15 -5
View File
@@ -4,6 +4,9 @@ import { normalizeToManageCode } from '@/utils/permission-codes';
export type PermissionBindingValue = string | string[] | undefined; export type PermissionBindingValue = string | string[] | undefined;
export type PermissionMode = 'manage' | 'view'; export type PermissionMode = 'manage' | 'view';
export type PermissionAccessOptions = {
nodeAdmin?: boolean;
};
const getRoutePermission = (): PermissionBindingValue => { const getRoutePermission = (): PermissionBindingValue => {
const route = router.currentRoute.value; const route = router.currentRoute.value;
@@ -46,8 +49,15 @@ export const toPermissionList = (value: PermissionBindingValue) => {
return routePermission ? [routePermission] : []; return routePermission ? [routePermission] : [];
}; };
const hasPermissionAccessByMode = (mode: PermissionMode, value?: PermissionBindingValue) => { const hasPermissionAccessByMode = (
mode: PermissionMode,
value?: PermissionBindingValue,
options: PermissionAccessOptions = {},
) => {
const globalStore = GlobalStore(); const globalStore = GlobalStore();
if (options.nodeAdmin && globalStore.isNodeAdmin) {
return true;
}
const permissions = toPermissionList(value); const permissions = toPermissionList(value);
const normalizedPermissions = mode === 'manage' ? permissions.map(toManagePermission).filter(Boolean) : permissions; const normalizedPermissions = mode === 'manage' ? permissions.map(toManagePermission).filter(Boolean) : permissions;
if (normalizedPermissions.length === 0) { if (normalizedPermissions.length === 0) {
@@ -56,10 +66,10 @@ const hasPermissionAccessByMode = (mode: PermissionMode, value?: PermissionBindi
return normalizedPermissions.some((permission) => globalStore.hasPermission(permission)); return normalizedPermissions.some((permission) => globalStore.hasPermission(permission));
}; };
export const hasManagePermissionAccess = (value?: PermissionBindingValue) => { export const hasManagePermissionAccess = (value?: PermissionBindingValue, options?: PermissionAccessOptions) => {
return hasPermissionAccessByMode('manage', value); return hasPermissionAccessByMode('manage', value, options);
}; };
export const hasPermissionAccess = (value?: PermissionBindingValue) => { export const hasPermissionAccess = (value?: PermissionBindingValue, options?: PermissionAccessOptions) => {
return hasPermissionAccessByMode('view', value); return hasPermissionAccessByMode('view', value, options);
}; };
+2 -2
View File
@@ -455,9 +455,9 @@ const openCreateFromQuery = async () => {
await router.replace({ path: route.path, query: nextQuery }); await router.replace({ path: route.path, query: nextQuery });
}; };
const openTaskLog = (taskID: string) => { const openTaskLog = (taskID: string, operateNode?: string) => {
if (taskLogRef.value?.openWithTaskID) { if (taskLogRef.value?.openWithTaskID) {
taskLogRef.value.openWithTaskID(taskID); taskLogRef.value.openWithTaskID(taskID, undefined, operateNode);
} }
}; };
+32 -8
View File
@@ -23,12 +23,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineAsyncComponent } from 'vue'; import { computed, defineAsyncComponent, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import OllamaView from '@/views/ai/model/ollama/index.vue'; import OllamaView from '@/views/ai/model/ollama/index.vue';
import TensorRTView from '@/views/ai/model/tensorrt/index.vue'; import TensorRTView from '@/views/ai/model/tensorrt/index.vue';
import { loadOptionalComponent } from '@/extensions/optional'; import { loadOptionalComponent } from '@/extensions/optional';
import i18n from '@/lang'; import i18n from '@/lang';
import { useGlobalStore } from '@/composables/useGlobalStore';
const VllmView = defineAsyncComponent(() => loadOptionalComponent('/src/xpack/views/vllm/index.vue')); const VllmView = defineAsyncComponent(() => loadOptionalComponent('/src/xpack/views/vllm/index.vue'));
const ModelDownloaderView = defineAsyncComponent(() => const ModelDownloaderView = defineAsyncComponent(() =>
@@ -39,6 +40,7 @@ type LocalTab = 'ollama' | 'vllm' | 'tensorrt' | 'downloader';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { isFxplay } = useGlobalStore();
const tabLabels: Record<LocalTab, string> = { const tabLabels: Record<LocalTab, string> = {
ollama: 'Ollama', ollama: 'Ollama',
@@ -47,16 +49,21 @@ const tabLabels: Record<LocalTab, string> = {
downloader: i18n.global.t('aiTools.model.downloader'), downloader: i18n.global.t('aiTools.model.downloader'),
}; };
const buttons: Array<{ label: string; value: LocalTab }> = [ const buttons = computed<Array<{ label: string; value: LocalTab }>>(() => {
{ label: tabLabels.ollama, value: 'ollama' }, const items: Array<{ label: string; value: LocalTab }> = [
{ label: tabLabels.vllm, value: 'vllm' }, { label: tabLabels.ollama, value: 'ollama' },
{ label: tabLabels.tensorrt, value: 'tensorrt' }, { label: tabLabels.vllm, value: 'vllm' },
{ label: tabLabels.downloader, value: 'downloader' }, ];
]; if (isFxplay.value) {
items.push({ label: tabLabels.tensorrt, value: 'tensorrt' });
}
items.push({ label: tabLabels.downloader, value: 'downloader' });
return items;
});
const currentTab = computed<LocalTab>(() => { const currentTab = computed<LocalTab>(() => {
const tab = route.query.tab; const tab = route.query.tab;
if (tab === 'vllm' || tab === 'tensorrt' || tab === 'downloader') { if (tab === 'vllm' || tab === 'downloader' || (tab === 'tensorrt' && isFxplay.value)) {
return tab; return tab;
} }
return 'ollama'; return 'ollama';
@@ -84,6 +91,23 @@ const handleChange = async (target: LocalTab) => {
}, },
}); });
}; };
watch(
[() => route.query.tab, isFxplay],
async ([tab, fxplay]) => {
if (tab !== 'tensorrt' || fxplay) {
return;
}
await router.replace({
path: '/ai/model/local',
query: {
...route.query,
tab: 'ollama',
},
});
},
{ immediate: true },
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+19 -6
View File
@@ -2,15 +2,16 @@
<div> <div>
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')"> <LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')">
<template #leftToolBar> <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') }} {{ $t('commons.button.create') }}
</el-button> </el-button>
<el-button v-permission @click="onOpenGroupDialog()"> <el-button v-permission v-node-admin @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }} {{ $t('commons.table.group') }}
</el-button> </el-button>
<el-button-group> <el-button-group>
<el-button <el-button
v-permission v-permission
v-node-admin
plain plain
:disabled="selects.length === 0" :disabled="selects.length === 0"
@click="onBatchChangeStatus('enable')" @click="onBatchChangeStatus('enable')"
@@ -19,22 +20,23 @@
</el-button> </el-button>
<el-button <el-button
v-permission v-permission
v-node-admin
plain plain
:disabled="selects.length === 0" :disabled="selects.length === 0"
@click="onBatchChangeStatus('disable')" @click="onBatchChangeStatus('disable')"
> >
{{ $t('commons.button.disable') }} {{ $t('commons.button.disable') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
</el-button-group> </el-button-group>
<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') }} {{ $t('commons.button.import') }}
</el-button> </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') }} {{ $t('commons.button.export') }}
</el-button> </el-button>
</el-button-group> </el-button-group>
@@ -80,7 +82,12 @@
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.group')" min-width="120" prop="group"> <el-table-column :label="$t('commons.table.group')" min-width="120" prop="group">
<template #default="{ row }"> <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> <template #read>
{{ row.groupBelong === 'Default' ? $t('commons.table.default') : row.groupBelong }} {{ row.groupBelong === 'Default' ? $t('commons.table.default') : row.groupBelong }}
</template> </template>
@@ -99,6 +106,7 @@
<template #default="{ row }"> <template #default="{ row }">
<Status <Status
v-permission v-permission
v-node-admin
v-if="row.status === 'Enable'" v-if="row.status === 'Enable'"
@click="onChangeStatus(row.id, 'disable')" @click="onChangeStatus(row.id, 'disable')"
:status="row.status" :status="row.status"
@@ -106,6 +114,7 @@
/> />
<Status <Status
v-permission v-permission
v-node-admin
v-if="row.status === 'Disable'" v-if="row.status === 'Disable'"
@click="onChangeStatus(row.id, 'enable')" @click="onChangeStatus(row.id, 'enable')"
:status="row.status" :status="row.status"
@@ -139,6 +148,7 @@
<template #default="{ row }"> <template #default="{ row }">
<el-button <el-button
v-permission v-permission
v-node-admin
v-if="hasBackup(row.type)" v-if="hasBackup(row.type)"
@click="loadBackups(row)" @click="loadBackups(row)"
plain plain
@@ -511,6 +521,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.handle'), label: i18n.global.t('commons.button.handle'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => { click: (row: Cronjob.CronjobInfo) => {
onHandle(row); onHandle(row);
}, },
@@ -527,6 +538,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => { click: (row: Cronjob.CronjobInfo) => {
onOpenDialog(row.id + ''); onOpenDialog(row.id + '');
}, },
@@ -534,6 +546,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => { click: (row: Cronjob.CronjobInfo) => {
onDelete(row); onDelete(row);
}, },
@@ -33,6 +33,7 @@
<el-button <el-button
type="primary" type="primary"
v-permission v-permission
v-node-admin
:disabled="dialogData.rowData.status === 'Pending'" :disabled="dialogData.rowData.status === 'Pending'"
@click="onHandle(dialogData.rowData)" @click="onHandle(dialogData.rowData)"
link link
@@ -44,6 +45,7 @@
type="primary" type="primary"
v-if="dialogData.rowData.status === 'Enable'" v-if="dialogData.rowData.status === 'Enable'"
v-permission v-permission
v-node-admin
@click="onChangeStatus(dialogData.rowData.id, 'disable')" @click="onChangeStatus(dialogData.rowData.id, 'disable')"
link link
> >
@@ -53,13 +55,21 @@
type="primary" type="primary"
v-if="dialogData.rowData.status === 'Disable'" v-if="dialogData.rowData.status === 'Disable'"
v-permission v-permission
v-node-admin
@click="onChangeStatus(dialogData.rowData.id, 'enable')" @click="onChangeStatus(dialogData.rowData.id, 'enable')"
link link
> >
{{ $t('commons.button.enable') }} {{ $t('commons.button.enable') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.clean') }}
</el-button> </el-button>
</div> </div>
+19 -6
View File
@@ -2,7 +2,7 @@
<div> <div>
<LayoutContent v-loading="loading" :title="$t('logs.login')"> <LayoutContent v-loading="loading" :title="$t('logs.login')">
<template #leftToolBar> <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') }} {{ $t('commons.button.create') }}
</el-button> </el-button>
<el-dropdown @command="handleSyncOp" class="mr-2.5"> <el-dropdown @command="handleSyncOp" class="mr-2.5">
@@ -12,23 +12,33 @@
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<fu-dropdown-item v-permission command="sync"> <fu-dropdown-item v-permission v-node-admin command="sync">
{{ $t('cronjob.library.syncNow') }} {{ $t('cronjob.library.syncNow') }}
</fu-dropdown-item> </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') }} {{ $t('cronjob.library.turnOnSync') }}
</fu-dropdown-item> </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') }} {{ $t('cronjob.library.turnOffSync') }}
</fu-dropdown-item> </fu-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </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') }} {{ $t('commons.table.group') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
</template> </template>
@@ -326,6 +336,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.clone'), label: i18n.global.t('commons.button.clone'),
permission: true, permission: true,
nodeAdmin: true,
disabled: (row: any) => { disabled: (row: any) => {
return !row.isSystem; return !row.isSystem;
}, },
@@ -341,6 +352,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
disabled: (row: any) => { disabled: (row: any) => {
return row.isSystem; return row.isSystem;
}, },
@@ -351,6 +363,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
disabled: (row: any) => { disabled: (row: any) => {
return row.isSystem; return row.isSystem;
}, },
@@ -48,7 +48,13 @@
diskInfo.mountPoint == '' 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') }} {{ $t('disk.handlePartition') }}
</el-button> </el-button>
</div> </div>
@@ -94,6 +100,7 @@
<el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text> <el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text>
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
link link
v-else-if="row.mountPoint != ''" v-else-if="row.mountPoint != ''"
@@ -101,7 +108,7 @@
> >
{{ $t('disk.unmount') }} {{ $t('disk.unmount') }}
</el-button> </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') }} {{ $t('disk.mount') }}
</el-button> </el-button>
</template> </template>
@@ -147,6 +154,7 @@
<el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text> <el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text>
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
link link
v-else-if="row.mountPoint != ''" v-else-if="row.mountPoint != ''"
@@ -154,7 +162,7 @@
> >
{{ $t('disk.unmount') }} {{ $t('disk.unmount') }}
</el-button> </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') }} {{ $t('disk.mount') }}
</el-button> </el-button>
</template> </template>
@@ -30,20 +30,25 @@
<el-form-item :label="$t('disk.mountPoint')" prop="mountPoint"> <el-form-item :label="$t('disk.mountPoint')" prop="mountPoint">
<el-input v-model="form.mountPoint"> <el-input v-model="form.mountPoint">
<template #prepend> <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> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('disk.autoMount')" prop="autoMount"> <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>
<el-form-item :label="$t('disk.noFail')" prop="noFail" v-if="form.autoMount"> <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-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</template> </template>
@@ -94,6 +94,7 @@
<el-button <el-button
class="w-full sm:w-auto !ml-0" class="w-full sm:w-auto !ml-0"
v-permission v-permission
v-node-admin
:disabled="selected.length === 0" :disabled="selected.length === 0"
:loading="deleteLoading" :loading="deleteLoading"
@click="deleteSelected(null)" @click="deleteSelected(null)"
@@ -152,7 +153,13 @@
<el-button link type="primary" @click.stop="openHistoryRecord(row)"> <el-button link type="primary" @click.stop="openHistoryRecord(row)">
{{ $t('commons.button.view') }} {{ $t('commons.button.view') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
</template> </template>
@@ -184,6 +191,7 @@
<el-button <el-button
v-if="canRestoreSelected" v-if="canRestoreSelected"
v-permission v-permission
v-node-admin
type="primary" type="primary"
:loading="restoreLoading" :loading="restoreLoading"
@click="confirmRestoreSelected" @click="confirmRestoreSelected"
@@ -32,7 +32,7 @@
<div class="flex justify-start items-center gap-x-4 card-action"> <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-text class="cursor-pointer" @click="handleReset">{{ $t('commons.button.reset') }}</el-text>
<el-divider direction="vertical" class="!mx-0" /> <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') }} {{ $t('commons.button.save') }}
</el-text> </el-text>
<el-divider direction="vertical" class="!mx-0" /> <el-divider direction="vertical" class="!mx-0" />
@@ -127,11 +127,11 @@
</el-text> </el-text>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <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> <svg-icon class="!w-5 !h-5" iconName="p-file-folder"></svg-icon>
{{ $t('file.dir') }} {{ $t('file.dir') }}
</fu-dropdown-item> </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> <svg-icon class="!w-5 !h-5" iconName="p-file-normal"></svg-icon>
{{ $t('menu.files') }} {{ $t('menu.files') }}
</fu-dropdown-item> </fu-dropdown-item>
@@ -1007,7 +1007,7 @@ const loadHistoryVersionCount = async (path: string) => {
}; };
const saveContent = async () => { const saveContent = async () => {
if (!hasManagePermissionAccess()) { if (!hasManagePermissionAccess(undefined, { nodeAdmin: true })) {
MsgError(i18n.global.t('commons.res.forbidden')); MsgError(i18n.global.t('commons.res.forbidden'));
return; return;
} }
@@ -90,6 +90,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: any) => { click: (row: any) => {
singleDel(row.id); singleDel(row.id);
}, },
@@ -222,7 +222,14 @@
</template> </template>
</el-input> </el-input>
</div> </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') }} {{ $t('file.aiSearch') }}
</el-button> </el-button>
</div> </div>
@@ -246,11 +253,11 @@
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <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> <svg-icon iconName="p-file-folder"></svg-icon>
{{ $t('file.dir') }} {{ $t('file.dir') }}
</fu-dropdown-item> </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> <svg-icon iconName="p-file-normal"></svg-icon>
{{ $t('menu.files') }} {{ $t('menu.files') }}
</fu-dropdown-item> </fu-dropdown-item>
@@ -264,11 +271,11 @@
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <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> <el-icon><ElUpload /></el-icon>
{{ $t('commons.button.upload') }} {{ $t('commons.button.upload') }}
</fu-dropdown-item> </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> <el-icon><ElDownload /></el-icon>
{{ $t('file.remoteFile') }} {{ $t('file.remoteFile') }}
</fu-dropdown-item> </fu-dropdown-item>
@@ -437,6 +444,7 @@
<el-dropdown-menu> <el-dropdown-menu>
<fu-dropdown-item <fu-dropdown-item
v-permission v-permission
v-node-admin
v-for="btn in moreButtons" v-for="btn in moreButtons"
:key="btn.label" :key="btn.label"
@click="btn.action" @click="btn.action"
@@ -453,6 +461,7 @@
<template v-for="btn in visibleButtons" :key="btn.label"> <template v-for="btn in visibleButtons" :key="btn.label">
<el-button <el-button
v-permission v-permission
v-node-admin
plain plain
@click="btn.action" @click="btn.action"
:disabled="selects.length === 0" :disabled="selects.length === 0"
@@ -469,6 +478,7 @@
<el-dropdown-menu> <el-dropdown-menu>
<fu-dropdown-item <fu-dropdown-item
v-permission v-permission
v-node-admin
v-for="btn in moreButtons" v-for="btn in moreButtons"
:key="btn.label" :key="btn.label"
@click="btn.action" @click="btn.action"
@@ -555,6 +565,7 @@
<el-button <el-button
v-if="row.shareCode" v-if="row.shareCode"
v-permission v-permission
v-node-admin
link link
type="primary" type="primary"
size="large" size="large"
@@ -566,6 +577,7 @@
<el-button <el-button
v-if="row.favoriteID > 0" v-if="row.favoriteID > 0"
v-permission v-permission
v-node-admin
link link
type="warning" type="warning"
size="large" size="large"
@@ -576,6 +588,7 @@
<el-button <el-button
v-if="hoveredRowPath === row.path" v-if="hoveredRowPath === row.path"
v-permission v-permission
v-node-admin
link link
icon="Star" icon="Star"
@click="addToFavorite(row)" @click="addToFavorite(row)"
@@ -587,7 +600,7 @@
</el-table-column> </el-table-column>
<el-table-column :label="$t('file.mode')" prop="mode" width="80"> <el-table-column :label="$t('file.mode')" prop="mode" width="80">
<template #default="{ row }"> <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 }} {{ row.mode }}
</el-link> </el-link>
</template> </template>
@@ -599,7 +612,7 @@
width="200" width="200"
> >
<template #default="{ row }"> <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.user ? row.user : '-' }} ({{ row.uid }}) /
{{ row.group ? row.group : '-' }} ({{ row.gid }}) {{ row.group ? row.group : '-' }} ({{ row.gid }})
</el-link> </el-link>
@@ -1354,11 +1367,11 @@ const handleCreate = (command: string) => {
}; };
const delFile = async (row: File.File | null) => { const delFile = async (row: File.File | null) => {
deleteRef.value.acceptParams([row]); deleteRef.value.acceptParams([row], currentNode.value);
}; };
const batchDelFiles = () => { const batchDelFiles = () => {
deleteRef.value.acceptParams(selects.value); deleteRef.value.acceptParams(selects.value, currentNode.value);
}; };
const formatFileSize = (size: number) => { const formatFileSize = (size: number) => {
@@ -1981,16 +1994,19 @@ const beforeButtons = [
{ {
label: i18n.global.t('commons.button.copy'), label: i18n.global.t('commons.button.copy'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => openMoveBtn('copy', row), click: (row: File.File) => openMoveBtn('copy', row),
}, },
{ {
label: i18n.global.t('file.move'), label: i18n.global.t('file.move'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => openMoveBtn('cut', row), click: (row: File.File) => openMoveBtn('cut', row),
}, },
{ {
label: i18n.global.t('file.paste'), label: i18n.global.t('file.paste'),
permission: true, permission: true,
nodeAdmin: true,
click: openPaste, click: openPaste,
disabled: () => { disabled: () => {
return !moveOpen.value; return !moveOpen.value;
@@ -1999,6 +2015,7 @@ const beforeButtons = [
{ {
label: i18n.global.t('file.compress'), label: i18n.global.t('file.compress'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
openCompress([row]); openCompress([row]);
}, },
@@ -2013,6 +2030,7 @@ const beforeButtons = [
{ {
label: i18n.global.t('file.editPermissions'), label: i18n.global.t('file.editPermissions'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
openBatchRole([row]); openBatchRole([row]);
}, },
@@ -2021,6 +2039,7 @@ const beforeButtons = [
label: i18n.global.t('file.setRemark'), label: i18n.global.t('file.setRemark'),
hideOnRemarkBlackList: true, hideOnRemarkBlackList: true,
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
openRemark(row); openRemark(row);
}, },
@@ -2030,6 +2049,7 @@ const afterButtons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
disabled: (row: File.File) => { disabled: (row: File.File) => {
return row.name == '.1panel_clash'; return row.name == '.1panel_clash';
}, },
@@ -2043,6 +2063,7 @@ const afterButtons = [
{ {
label: i18n.global.t('file.addFavoriteAction'), label: i18n.global.t('file.addFavoriteAction'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
addToFavorite(row); addToFavorite(row);
}, },
@@ -2051,6 +2072,7 @@ const afterButtons = [
{ {
label: i18n.global.t('file.removeFavoriteAction'), label: i18n.global.t('file.removeFavoriteAction'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
remove(row?.favoriteID); remove(row?.favoriteID);
}, },
@@ -2059,6 +2081,7 @@ const afterButtons = [
{ {
label: i18n.global.t('file.shareFile'), label: i18n.global.t('file.shareFile'),
permission: true, permission: true,
nodeAdmin: true,
click: openShareFile, click: openShareFile,
show: (row: File.File) => { show: (row: File.File) => {
return !row?.isDir && !row?.shareCode; return !row?.isDir && !row?.shareCode;
@@ -2067,6 +2090,7 @@ const afterButtons = [
{ {
label: i18n.global.t('file.shareCancel'), label: i18n.global.t('file.shareCancel'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
removeShareByPath(row.path); removeShareByPath(row.path);
}, },
@@ -2077,6 +2101,7 @@ const afterButtons = [
{ {
label: i18n.global.t('file.convert'), label: i18n.global.t('file.convert'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
openConvert(row); openConvert(row);
}, },
@@ -2099,6 +2124,7 @@ const rightBtnRename = [
{ {
label: i18n.global.t('file.rename'), label: i18n.global.t('file.rename'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
openRename(row, 'right'); openRename(row, 'right');
}, },
@@ -2108,6 +2134,7 @@ const moreBtnRename = [
{ {
label: i18n.global.t('file.rename'), label: i18n.global.t('file.rename'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.File) => { click: (row: File.File) => {
openRename(row, 'more'); openRename(row, 'more');
}, },
@@ -2,18 +2,35 @@
<DrawerPro v-model="open" :header="$t('file.recycleBin')" @close="handleClose" size="large"> <DrawerPro v-model="open" :header="$t('file.recycleBin')" @close="handleClose" size="large">
<template #content> <template #content>
<div class="flex space-x-4"> <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') }} {{ $t('file.clearRecycleBin') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </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') }} {{ $t('file.reduce') }}
</el-button> </el-button>
<el-form-item :label="$t('file.fileRecycleBin')"> <el-form-item :label="$t('file.fileRecycleBin')">
<el-switch <el-switch
v-permission v-permission
v-node-admin
v-model="status" v-model="status"
active-value="Enable" active-value="Enable"
inactive-value="Disable" inactive-value="Disable"
@@ -188,11 +205,13 @@ const buttons = [
{ {
label: i18n.global.t('file.reduce'), label: i18n.global.t('file.reduce'),
permission: true, permission: true,
nodeAdmin: true,
click: rdFile, click: rdFile,
}, },
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: singleDel, click: singleDel,
}, },
]; ];
@@ -90,6 +90,7 @@ const buttons = [
{ {
label: i18n.global.t('file.shareClose'), label: i18n.global.t('file.shareClose'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: File.FileShareInfo) => { click: (row: File.FileShareInfo) => {
closeShare(row.path); closeShare(row.path);
}, },
@@ -63,13 +63,13 @@
<el-button :disabled="loading" @click="handleClose"> <el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }} {{ $t('commons.button.cancel') }}
</el-button> </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') }} {{ $t('file.shareClose') }}
</el-button> </el-button>
<el-button v-if="shareInfo" :disabled="loading" @click="copyLink"> <el-button v-if="shareInfo" :disabled="loading" @click="copyLink">
{{ $t('file.shareCopyLink') }} {{ $t('file.shareCopyLink') }}
</el-button> </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') }} {{ shareInfo ? $t('file.shareRegenerate') : $t('file.shareGenerate') }}
</el-button> </el-button>
</span> </span>
@@ -36,16 +36,22 @@
<el-alert type="info" :closable="false" :title="loadPrompt()" /> <el-alert type="info" :closable="false" :title="loadPrompt()" />
</template> </template>
<template #leftToolBar> <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') }} {{ $t('firewall.create') }}
</el-button> </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') }} {{ $t('commons.button.unbind') }}
</el-button> </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') }} {{ $t('commons.button.bind') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
</template> </template>
@@ -118,6 +124,7 @@
<fu-input-rw-switch <fu-input-rw-switch
v-model="row.description" v-model="row.description"
v-permission v-permission
v-node-admin
@enter="onChange(row)" @enter="onChange(row)"
@blur="onChange(row)" @blur="onChange(row)"
/> />
@@ -339,6 +346,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.IptablesRules) => { click: (row: Host.IptablesRules) => {
onDelete(row); onDelete(row);
}, },
@@ -19,17 +19,23 @@
<LayoutContent :title="$t('firewall.forwardRule', 2)" :class="{ mask: !isActive }"> <LayoutContent :title="$t('firewall.forwardRule', 2)" :class="{ mask: !isActive }">
<template #leftToolBar> <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') }} {{ $t('commons.button.create') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </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') }} {{ $t('commons.button.import') }}
</el-button> </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') }} {{ $t('commons.button.export') }}
</el-button> </el-button>
</el-button-group> </el-button-group>
@@ -255,6 +261,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RuleForward) => { click: (row: Host.RuleForward) => {
onOpenDialog('edit', row); onOpenDialog('edit', row);
}, },
@@ -262,6 +269,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RuleForward) => { click: (row: Host.RuleForward) => {
onDelete(row); onDelete(row);
}, },
+15 -4
View File
@@ -29,17 +29,23 @@
</div> </div>
</template> </template>
<template #leftToolBar> <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') }} {{ $t('commons.button.create') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </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') }} {{ $t('commons.button.import') }}
</el-button> </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') }} {{ $t('commons.button.export') }}
</el-button> </el-button>
</el-button-group> </el-button-group>
@@ -75,6 +81,7 @@
<el-button <el-button
v-if="row.strategy === 'accept'" v-if="row.strategy === 'accept'"
v-permission v-permission
v-node-admin
@click="onChangeStatus(row, 'drop')" @click="onChangeStatus(row, 'drop')"
link link
type="success" type="success"
@@ -86,6 +93,7 @@
link link
type="danger" type="danger"
v-permission v-permission
v-node-admin
@click="onChangeStatus(row, 'accept')" @click="onChangeStatus(row, 'accept')"
> >
{{ $t('firewall.deny') }} {{ $t('firewall.deny') }}
@@ -102,6 +110,7 @@
<fu-input-rw-switch <fu-input-rw-switch
v-model="row.description" v-model="row.description"
v-permission v-permission
v-node-admin
@enter="onChange(row)" @enter="onChange(row)"
@blur="onChange(row)" @blur="onChange(row)"
/> />
@@ -323,6 +332,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RuleIP) => { click: (row: Host.RuleIP) => {
onOpenDialog('edit', row); onOpenDialog('edit', row);
}, },
@@ -330,6 +340,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RuleIP) => { click: (row: Host.RuleIP) => {
onDelete(row); onDelete(row);
}, },
@@ -43,17 +43,23 @@
</el-alert> </el-alert>
</template> </template>
<template #leftToolBar> <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') }} {{ $t('commons.button.create') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </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') }} {{ $t('commons.button.import') }}
</el-button> </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') }} {{ $t('commons.button.export') }}
</el-button> </el-button>
</el-button-group> </el-button-group>
@@ -140,6 +146,7 @@
<el-button <el-button
v-if="row.strategy === 'accept'" v-if="row.strategy === 'accept'"
v-permission v-permission
v-node-admin
@click="onChangeStatus(row, 'drop')" @click="onChangeStatus(row, 'drop')"
link link
type="success" type="success"
@@ -151,6 +158,7 @@
link link
type="danger" type="danger"
v-permission v-permission
v-node-admin
@click="onChangeStatus(row, 'accept')" @click="onChangeStatus(row, 'accept')"
> >
{{ $t('firewall.drop') }} {{ $t('firewall.drop') }}
@@ -173,6 +181,7 @@
<fu-input-rw-switch <fu-input-rw-switch
v-model="row.description" v-model="row.description"
v-permission v-permission
v-node-admin
@enter="onChange(row)" @enter="onChange(row)"
@blur="onChange(row)" @blur="onChange(row)"
/> />
@@ -656,6 +665,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RulePort) => { click: (row: Host.RulePort) => {
onOpenDialog('edit', row); onOpenDialog('edit', row);
}, },
@@ -663,6 +673,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RuleInfo) => { click: (row: Host.RuleInfo) => {
onDelete(row); onDelete(row);
}, },
@@ -12,6 +12,7 @@
<template v-if="baseInfo.name !== 'iptables'"> <template v-if="baseInfo.name !== 'iptables'">
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
v-if="baseInfo.isActive" v-if="baseInfo.isActive"
@click="onOperate('stop')" @click="onOperate('stop')"
@@ -21,6 +22,7 @@
</el-button> </el-button>
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
v-if="!baseInfo.isActive" v-if="!baseInfo.isActive"
@click="onOperate('start')" @click="onOperate('start')"
@@ -29,29 +31,43 @@
{{ $t('commons.button.start') }} {{ $t('commons.button.start') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
</template> </template>
<template v-if="!baseInfo.isInit || (props.currentTab === 'forward' && !baseInfo.isBind)"> <template v-if="!baseInfo.isInit || (props.currentTab === 'forward' && !baseInfo.isBind)">
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.init') }}
</el-button> </el-button>
</template> </template>
<template v-if="baseInfo.name === 'iptables' && baseInfo.isInit && props.currentTab == 'base'"> <template v-if="baseInfo.name === 'iptables' && baseInfo.isInit && props.currentTab == 'base'">
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.unbind') }}
</el-button> </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') }} {{ $t('commons.button.bind') }}
</el-button> </el-button>
</template> </template>
<template v-if="props.currentTab == 'base'"> <template v-if="props.currentTab == 'base'">
<el-divider direction="vertical" /> <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') }} {{ $t('firewall.portWhiteList') }}
</el-button> </el-button>
</template> </template>
@@ -61,6 +77,7 @@
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button> <el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
<el-switch <el-switch
v-permission v-permission
v-node-admin
size="small" size="small"
class="ml-2" class="ml-2"
inactive-value="Disable" inactive-value="Disable"
@@ -11,6 +11,7 @@
<el-form-item :label="$t('monitor.enableMonitor')" prop="monitorStatus"> <el-form-item :label="$t('monitor.enableMonitor')" prop="monitorStatus">
<el-switch <el-switch
v-permission v-permission
v-node-admin
@change="onSaveStatus" @change="onSaveStatus"
v-model="form.monitorStatus" v-model="form.monitorStatus"
active-value="Enable" active-value="Enable"
@@ -20,7 +21,7 @@
<el-form-item :label="$t('monitor.storeDays')" prop="monitorStoreDays"> <el-form-item :label="$t('monitor.storeDays')" prop="monitorStoreDays">
<el-input disabled v-model="form.monitorStoreDays"> <el-input disabled v-model="form.monitorStoreDays">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -29,7 +30,7 @@
<el-form-item :label="$t('monitor.interval')" prop="monitorIntervalItem"> <el-form-item :label="$t('monitor.interval')" prop="monitorIntervalItem">
<el-input disabled v-model="form.monitorIntervalItem"> <el-input disabled v-model="form.monitorIntervalItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -38,7 +39,7 @@
<el-form-item :label="$t('monitor.defaultNetwork')"> <el-form-item :label="$t('monitor.defaultNetwork')">
<el-input disabled v-model="form.defaultNetwork"> <el-input disabled v-model="form.defaultNetwork">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -48,7 +49,7 @@
<el-form-item :label="$t('monitor.defaultIO')"> <el-form-item :label="$t('monitor.defaultIO')">
<el-input disabled v-model="form.defaultIO"> <el-input disabled v-model="form.defaultIO">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -56,7 +57,7 @@
<span class="input-help">{{ $t('monitor.defaultIOHelper') }}</span> <span class="input-help">{{ $t('monitor.defaultIOHelper') }}</span>
</el-form-item> </el-form-item>
<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') }} {{ $t('monitor.cleanMonitor') }}
</el-button> </el-button>
</el-form-item> </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> <div class="mt-2"><el-alert type="info" :title="$t('ssh.sshAlert')" :closable="false" /></div>
</template> </template>
<template #leftToolBar> <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') }} {{ $t('commons.button.export') }}
</el-button> </el-button>
</template> </template>
@@ -79,7 +79,7 @@
<el-button @click="open = false" :disabled="loading"> <el-button @click="open = false" :disabled="loading">
{{ $t('commons.button.cancel') }} {{ $t('commons.button.cancel') }}
</el-button> </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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -42,6 +42,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.disConn'), label: i18n.global.t('commons.button.disConn'),
permission: true, permission: true,
nodeAdmin: true,
click: function (row: any) { click: function (row: any) {
stop(row.PID); stop(row.PID);
}, },
@@ -4,13 +4,13 @@
<div class="mb-4"> <div class="mb-4">
<el-alert :closable="false">{{ $t('ssh.pubKeyHelper', [currentUser]) }}</el-alert> <el-alert :closable="false">{{ $t('ssh.pubKeyHelper', [currentUser]) }}</el-alert>
</div> </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') }} {{ $t('commons.button.create') }}
</el-button> </el-button>
<el-button v-permission plain @click="onSync()"> <el-button v-permission v-node-admin plain @click="onSync()">
{{ $t('commons.button.sync') }} {{ $t('commons.button.sync') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
<ComplexTable <ComplexTable
@@ -262,6 +262,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RootCertInfo) => { click: (row: Host.RootCertInfo) => {
onOpenDialog('edit', row); onOpenDialog('edit', row);
}, },
@@ -276,6 +277,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Host.RootCertInfo) => { click: (row: Host.RootCertInfo) => {
onDelete(row); onDelete(row);
}, },
+34 -9
View File
@@ -10,14 +10,28 @@
<Status class="mt-0.5" :status="form.isActive ? 'enable' : 'disable'" :msg="form.message" /> <Status class="mt-0.5" :status="form.isActive ? 'enable' : 'disable'" :msg="form.message" />
</div> </div>
<div class="mt-0.5"> <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') }} {{ $t('commons.button.stop') }}
</el-button> </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') }} {{ $t('commons.button.start') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
@@ -26,6 +40,7 @@
</el-button> </el-button>
<el-switch <el-switch
v-permission v-permission
v-node-admin
size="small" size="small"
class="ml-2" class="ml-2"
inactive-value="disable" inactive-value="disable"
@@ -45,8 +60,8 @@
<el-radio-button value="all">{{ $t('database.allConf') }}</el-radio-button> <el-radio-button value="all">{{ $t('database.allConf') }}</el-radio-button>
</el-radio-group> </el-radio-group>
<el-button v-permission @click="onOpenDrawer">{{ $t('ssh.pubkey') }}</el-button> <el-button v-permission v-node-admin @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="onOpenAuthKeys">{{ $t('ssh.authKeys') }}</el-button>
</template> </template>
<template #main> <template #main>
<el-row class="mt-10" v-if="confShowType === 'base'"> <el-row class="mt-10" v-if="confShowType === 'base'">
@@ -55,7 +70,7 @@
<el-form-item :label="$t('ssh.port')" prop="port"> <el-form-item :label="$t('ssh.port')" prop="port">
<el-input disabled v-model="form.port"> <el-input disabled v-model="form.port">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -65,7 +80,7 @@
<el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress"> <el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress">
<el-input disabled v-model="form.listenAddressItem"> <el-input disabled v-model="form.listenAddressItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -75,7 +90,7 @@
<el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLoginItem"> <el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLoginItem">
<el-input disabled v-model="form.permitRootLoginItem"> <el-input disabled v-model="form.permitRootLoginItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -85,6 +100,7 @@
<el-form-item :label="$t('ssh.passwordAuthentication')" prop="passwordAuthentication"> <el-form-item :label="$t('ssh.passwordAuthentication')" prop="passwordAuthentication">
<el-switch <el-switch
v-permission v-permission
v-node-admin
active-value="yes" active-value="yes"
inactive-value="no" inactive-value="no"
@change="onSave(formRef, 'PasswordAuthentication', form.passwordAuthentication)" @change="onSave(formRef, 'PasswordAuthentication', form.passwordAuthentication)"
@@ -95,6 +111,7 @@
<el-form-item :label="$t('ssh.pubkeyAuthentication')" prop="pubkeyAuthentication"> <el-form-item :label="$t('ssh.pubkeyAuthentication')" prop="pubkeyAuthentication">
<el-switch <el-switch
v-permission v-permission
v-node-admin
active-value="yes" active-value="yes"
inactive-value="no" inactive-value="no"
@change="onSave(formRef, 'PubkeyAuthentication', form.pubkeyAuthentication)" @change="onSave(formRef, 'PubkeyAuthentication', form.pubkeyAuthentication)"
@@ -105,6 +122,7 @@
<el-form-item :label="$t('ssh.useDNS')" prop="useDNS"> <el-form-item :label="$t('ssh.useDNS')" prop="useDNS">
<el-switch <el-switch
v-permission v-permission
v-node-admin
active-value="yes" active-value="yes"
inactive-value="no" inactive-value="no"
@change="onSave(formRef, 'UseDNS', form.useDNS)" @change="onSave(formRef, 'UseDNS', form.useDNS)"
@@ -134,7 +152,14 @@
mode="nginx" mode="nginx"
placeholder="# The SSH configuration file does not exist or is empty (/etc/ssh/sshd_config)" placeholder="# The SSH configuration file does not exist or is empty (/etc/ssh/sshd_config)"
></CodemirrorPro> ></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') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
</div> </div>
@@ -16,9 +16,11 @@
<el-option :label="$t('logs.detail.apps')" value="apps" /> <el-option :label="$t('logs.detail.apps')" value="apps" />
<el-option :label="$t('logs.detail.openresty')" value="openresty" /> <el-option :label="$t('logs.detail.openresty')" value="openresty" />
<el-option :label="$t('logs.detail.websites')" value="websites" /> <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.runtimes')" value="runtimes" />
<el-option :label="$t('logs.detail.ai')" value="ai" /> <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_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.skills_hub')" value="skills-hub" />
<el-option :label="$t('logs.detail.databases')" value="databases" /> <el-option :label="$t('logs.detail.databases')" value="databases" />
<el-option :label="$t('logs.detail.containers')" value="containers" /> <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.alert')" value="alert" />
<el-option :label="$t('logs.detail.backups')" value="backups" /> <el-option :label="$t('logs.detail.backups')" value="backups" />
<el-option :label="$t('logs.detail.groups')" value="groups" /> <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-option :label="$t('logs.detail.commands')" value="commands" />
</el-select> </el-select>
<el-select v-model="searchStatus" @change="search()" clearable class="p-w-200"> <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" class="bg-white shadow-lg relative z-10 border border-gray-200 flex overflow-hidden"
id="login-container" 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 }"> <div v-if="showLogo" class="flex justify-center" :style="{ height: containerHeight }">
<img <img
v-show="imgLoaded" v-show="imgLoaded"
@@ -144,9 +144,12 @@ const { width } = useWindowSize();
const showLogo = computed(() => width.value >= FIXED_WIDTH); const showLogo = computed(() => width.value >= FIXED_WIDTH);
const containerWidth = computed(() => `${FIXED_WIDTH}px`); const containerWidth = computed(() => `${FIXED_WIDTH}px`);
const containerHeight = computed(() => `${FIXED_HEIGHT}px`); const containerHeight = computed(() => `${FIXED_HEIGHT}px`);
const loginGridStyle = computed(() => ({
gridTemplateColumns: showLogo.value ? 'repeat(2, minmax(0, 1fr))' : 'minmax(0, 1fr)',
}));
const loginFormClass = computed(() => { const loginFormClass = computed(() => {
return showLogo.value return showLogo.value
? 'hidden md:flex items-center justify-center p-4' ? 'flex items-center justify-center p-4 min-w-0'
: 'flex items-center justify-center p-4 w-full'; : 'flex items-center justify-center p-4 w-full min-w-0';
}); });
</script> </script>
@@ -213,11 +213,11 @@ const openView = async (
title: string, title: string,
rowData: Partial<Alert.AlertInfo> = { rowData: Partial<Alert.AlertInfo> = {
type: isMaster.value && !isEE.value ? 'panelPwdEndTime' : 'sshLogin', type: isMaster.value && !isEE.value ? 'panelPwdEndTime' : 'sshLogin',
cycle: 15, cycle: isMaster.value && !isEE.value ? 15 : 30,
count: isMaster.value && !isEE.value ? 0 : 3, count: isMaster.value && !isEE.value ? 0 : 3,
sendCount: 3, sendCount: 3,
method: '', method: '',
project: '', project: isMaster.value && !isEE.value ? '' : 'all',
status: 'Enable', status: 'Enable',
title: '', title: '',
}, },
@@ -570,7 +570,6 @@ const acceptParams = (params: DialogProps): void => {
} }
initOptions(dialogData.value.rowData.type, dialogData.value.rowData.subType); initOptions(dialogData.value.rowData.type, dialogData.value.rowData.subType);
visible.value = true; visible.value = true;
console.log(dialogData.value.rowData);
}; };
const rules = reactive({ const rules = reactive({
@@ -7,6 +7,10 @@
<el-button style="margin-top: -4px" type="warning" link icon="WarningFilled" /> <el-button style="margin-top: -4px" type="warning" link icon="WarningFilled" />
{{ $t('setting.recoverHelper1') }} {{ $t('setting.recoverHelper1') }}
</div> </div>
<div>
<el-button style="margin-top: -4px" type="warning" link icon="WarningFilled" />
{{ $t('setting.recoverHelper4') }}
</div>
<div> <div>
<el-button <el-button
style="margin-top: -4px" style="margin-top: -4px"
+12 -1
View File
@@ -20,11 +20,18 @@
/> />
</template> </template>
<template #leftToolBar v-if="clamStatus.isExist"> <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') }} {{ $t('toolbox.clam.clamCreate') }}
</el-button> </el-button>
<el-button <el-button
v-permission v-permission
v-node-admin
plain plain
:disabled="selects.length === 0 || !clamStatus.isRunning" :disabled="selects.length === 0 || !clamStatus.isRunning"
@click="onDelete(null)" @click="onDelete(null)"
@@ -145,6 +152,7 @@
<fu-input-rw-switch <fu-input-rw-switch
v-model="row.description" v-model="row.description"
v-permission v-permission
v-node-admin
@enter="onChange(row)" @enter="onChange(row)"
@blur="onChange(row)" @blur="onChange(row)"
/> />
@@ -344,6 +352,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.handle'), label: i18n.global.t('commons.button.handle'),
permission: true, permission: true,
nodeAdmin: true,
click: async (row: Toolbox.ClamInfo) => { click: async (row: Toolbox.ClamInfo) => {
loading.value = true; loading.value = true;
await handleClamScan(row.id) await handleClamScan(row.id)
@@ -360,6 +369,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Toolbox.ClamInfo) => { click: (row: Toolbox.ClamInfo) => {
onOpenDialog('edit', row); onOpenDialog('edit', row);
}, },
@@ -373,6 +383,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Toolbox.ClamInfo) => { click: (row: Toolbox.ClamInfo) => {
onDelete(row); onDelete(row);
}, },
@@ -19,7 +19,12 @@
<el-form-item :label="$t('toolbox.clam.scanDir')" prop="path"> <el-form-item :label="$t('toolbox.clam.scanDir')" prop="path">
<el-input v-model="dialogData.rowData!.path"> <el-input v-model="dialogData.rowData!.path">
<template #prepend> <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> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -41,6 +46,7 @@
<el-button <el-button
icon="Folder" icon="Folder"
v-permission v-permission
v-node-admin
@click="infectedDirRef.acceptParams({ dir: true })" @click="infectedDirRef.acceptParams({ dir: true })"
/> />
</template> </template>
@@ -211,7 +217,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -30,11 +30,24 @@
</el-tag> </el-tag>
<span class="mt-0.5"> <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') }} {{ $t('commons.button.handle') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.clean') }}
</el-button> </el-button>
</span> </span>
@@ -37,7 +37,7 @@
:disabled="!canUpdate()" :disabled="!canUpdate()"
:placeholder="$t('commons.msg.noneData')" :placeholder="$t('commons.msg.noneData')"
></CodemirrorPro> ></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') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
</div> </div>
@@ -14,6 +14,7 @@
v-if="!data.isActive" v-if="!data.isActive"
link link
v-permission v-permission
v-node-admin
@click="onOperate('ClamAV', 'start')" @click="onOperate('ClamAV', 'start')"
> >
{{ $t('commons.operate.start') }} {{ $t('commons.operate.start') }}
@@ -23,16 +24,23 @@
v-if="data.isActive" v-if="data.isActive"
link link
v-permission v-permission
v-node-admin
@click="onOperate('ClamAV', 'stop')" @click="onOperate('ClamAV', 'stop')"
> >
{{ $t('commons.operate.stop') }} {{ $t('commons.operate.stop') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
@@ -56,6 +64,7 @@
v-if="!data.freshIsActive" v-if="!data.freshIsActive"
link link
v-permission v-permission
v-node-admin
@click="onOperate('FreshClam', 'start')" @click="onOperate('FreshClam', 'start')"
> >
{{ $t('commons.operate.start') }} {{ $t('commons.operate.start') }}
@@ -65,12 +74,19 @@
v-if="data.freshIsActive" v-if="data.freshIsActive"
link link
v-permission v-permission
v-node-admin
@click="onOperate('FreshClam', 'stop')" @click="onOperate('FreshClam', 'stop')"
> >
{{ $t('commons.operate.stop') }} {{ $t('commons.operate.stop') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
</div> </div>
+2 -1
View File
@@ -2,7 +2,7 @@
<div v-loading="loading"> <div v-loading="loading">
<LayoutContent :title="$t('setting.diskClean')" :divider="true"> <LayoutContent :title="$t('setting.diskClean')" :divider="true">
<template #leftToolBar> <template #leftToolBar>
<el-button v-permission type="primary" @click="scanData"> <el-button v-permission v-node-admin type="primary" @click="scanData">
{{ $t('clean.scan') }} {{ $t('clean.scan') }}
</el-button> </el-button>
</template> </template>
@@ -79,6 +79,7 @@
<el-text class="clean_title">{{ $t('clean.totalScan') }} {{ computeSize(totalSize) }}</el-text> <el-text class="clean_title">{{ $t('clean.totalScan') }} {{ computeSize(totalSize) }}</el-text>
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
class="-mt-2 ml-20" class="-mt-2 ml-20"
:disabled="selectSize <= 0" :disabled="selectSize <= 0"
@@ -31,7 +31,7 @@
<el-button :disabled="loading" @click="onTest()"> <el-button :disabled="loading" @click="onTest()">
{{ $t('toolbox.device.dnsCheck') }} {{ $t('toolbox.device.dnsCheck') }}
</el-button> </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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -9,7 +9,13 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -24,7 +24,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </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') }} {{ $t('commons.button.add') }}
</el-button> </el-button>
</div> </div>
@@ -39,7 +39,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
+14 -8
View File
@@ -14,7 +14,7 @@
<el-form-item label="DNS" prop="dnsItem"> <el-form-item label="DNS" prop="dnsItem">
<el-input disabled v-model="form.dnsItem"> <el-input disabled v-model="form.dnsItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -23,7 +23,7 @@
<el-form-item label="Hosts" prop="hosts"> <el-form-item label="Hosts" prop="hosts">
<el-input disabled v-model="form.hostItem"> <el-input disabled v-model="form.hostItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -32,7 +32,7 @@
<el-form-item label="Swap" prop="swap"> <el-form-item label="Swap" prop="swap">
<el-input disabled v-model="form.swapItem"> <el-input disabled v-model="form.swapItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -41,7 +41,7 @@
<el-form-item :label="$t('toolbox.device.hostname')" prop="hostname"> <el-form-item :label="$t('toolbox.device.hostname')" prop="hostname">
<el-input disabled v-model="form.hostname"> <el-input disabled v-model="form.hostname">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -50,7 +50,7 @@
<el-form-item :label="$t('toolbox.device.passwd')" prop="passwd"> <el-form-item :label="$t('toolbox.device.passwd')" prop="passwd">
<el-input disabled v-model="form.passwd" type="password"> <el-input disabled v-model="form.passwd" type="password">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -59,7 +59,7 @@
<el-form-item :label="$t('toolbox.device.syncSite')" prop="ntp"> <el-form-item :label="$t('toolbox.device.syncSite')" prop="ntp">
<el-input disabled v-model="form.ntp"> <el-input disabled v-model="form.ntp">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -68,7 +68,7 @@
<el-form-item :label="$t('toolbox.device.timeZone')" prop="timeZone"> <el-form-item :label="$t('toolbox.device.timeZone')" prop="timeZone">
<el-input disabled v-model="form.timeZone"> <el-input disabled v-model="form.timeZone">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -77,7 +77,13 @@
<el-form-item :label="$t('toolbox.device.localTime')" prop="localTime"> <el-form-item :label="$t('toolbox.device.localTime')" prop="localTime">
<el-input disabled v-model="form.localTime"> <el-input disabled v-model="form.localTime">
<template #append> <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') }} {{ $t('commons.button.sync') }}
</el-button> </el-button>
</template> </template>
@@ -3,13 +3,34 @@
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading"> <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-form-item :label="$t('toolbox.device.syncSite')" prop="ntpSite" :rules="Rules.domain">
<el-input v-model="form.ntpSite" /> <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') }} {{ $t('commons.table.default') }}
</el-button> </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') }} {{ $t('toolbox.device.ntpALi') }}
</el-button> </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') }} {{ $t('toolbox.device.ntpGoogle') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
@@ -17,7 +38,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -23,7 +23,13 @@
<el-button :disabled="loading" @click="passwordVisible = false"> <el-button :disabled="loading" @click="passwordVisible = false">
{{ $t('commons.button.cancel') }} {{ $t('commons.button.cancel') }}
</el-button> </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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -67,7 +67,7 @@
</el-table-column> </el-table-column>
<el-table-column min-width="70"> <el-table-column min-width="70">
<template #default="scope"> <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') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
</template> </template>
@@ -33,7 +33,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -18,7 +18,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -18,7 +18,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
+59 -10
View File
@@ -9,14 +9,28 @@
<el-tag>{{ form.version }}</el-tag> <el-tag>{{ form.version }}</el-tag>
</div> </div>
<div class="mt-0.5"> <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') }} {{ $t('commons.button.stop') }}
</el-button> </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') }} {{ $t('commons.button.start') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
@@ -25,6 +39,7 @@
</el-button> </el-button>
<el-switch <el-switch
v-permission v-permission
v-node-admin
size="small" size="small"
class="ml-2" class="ml-2"
inactive-value="disable" inactive-value="disable"
@@ -52,6 +67,7 @@
<template #leftToolBar> <template #leftToolBar>
<el-button <el-button
v-permission v-permission
v-node-admin
:disabled="!form.isActive" :disabled="!form.isActive"
type="primary" type="primary"
plain plain
@@ -61,6 +77,7 @@
</el-button> </el-button>
<el-button <el-button
v-permission v-permission
v-node-admin
:disabled="!form.isActive" :disabled="!form.isActive"
type="primary" type="primary"
plain plain
@@ -81,7 +98,7 @@
<el-form-item :label="$t('toolbox.fail2ban.sshPort')" prop="port"> <el-form-item :label="$t('toolbox.fail2ban.sshPort')" prop="port">
<el-input disabled v-model="form.port"> <el-input disabled v-model="form.port">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -91,7 +108,12 @@
<el-form-item :label="$t('toolbox.fail2ban.maxRetry')" prop="maxRetry"> <el-form-item :label="$t('toolbox.fail2ban.maxRetry')" prop="maxRetry">
<el-input disabled v-model="form.maxRetry"> <el-input disabled v-model="form.maxRetry">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -100,7 +122,12 @@
<el-form-item :label="$t('toolbox.fail2ban.banTime')" prop="banTime"> <el-form-item :label="$t('toolbox.fail2ban.banTime')" prop="banTime">
<el-input disabled v-model="form.banTimeItem"> <el-input disabled v-model="form.banTimeItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -110,7 +137,12 @@
<el-form-item :label="$t('toolbox.fail2ban.findTime')" prop="findTime"> <el-form-item :label="$t('toolbox.fail2ban.findTime')" prop="findTime">
<el-input disabled v-model="form.findTimeItem"> <el-input disabled v-model="form.findTimeItem">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -119,7 +151,12 @@
<el-form-item :label="$t('toolbox.fail2ban.banAction')" prop="banAction"> <el-form-item :label="$t('toolbox.fail2ban.banAction')" prop="banAction">
<el-input disabled v-model="form.banAction"> <el-input disabled v-model="form.banAction">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -128,7 +165,12 @@
<el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath"> <el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath">
<el-input disabled v-model="form.logPath"> <el-input disabled v-model="form.logPath">
<template #append> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</template> </template>
@@ -145,7 +187,14 @@
v-model="fail2banConf" v-model="fail2banConf"
:heightDiff="460" :heightDiff="460"
></CodemirrorPro> ></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') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
</div> </div>
@@ -13,7 +13,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -4,7 +4,7 @@
<el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath" :rules="Rules.requiredInput"> <el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath" :rules="Rules.requiredInput">
<el-input v-model="form.logPath"> <el-input v-model="form.logPath">
<template #prepend> <template #prepend>
<el-button v-permission icon="Folder" @click="fileRef.acceptParams({})" /> <el-button v-permission v-node-admin icon="Folder" @click="fileRef.acceptParams({})" />
</template> </template>
</el-input> </el-input>
<span class="input-help">{{ $t('toolbox.fail2ban.logPathHelper') }}</span> <span class="input-help">{{ $t('toolbox.fail2ban.logPathHelper') }}</span>
@@ -13,7 +13,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -10,7 +10,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -8,7 +8,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
+23 -6
View File
@@ -8,14 +8,28 @@
<Status class="mt-0.5" :status="form.isActive ? 'enable' : 'disable'" /> <Status class="mt-0.5" :status="form.isActive ? 'enable' : 'disable'" />
</div> </div>
<div class="mt-0.5"> <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') }} {{ $t('commons.button.stop') }}
</el-button> </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') }} {{ $t('commons.button.start') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
</div> </div>
@@ -35,13 +49,13 @@
</el-alert> </el-alert>
</template> </template>
<template #leftToolBar> <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') }} {{ $t('commons.button.add') }}
</el-button> </el-button>
<el-button v-permission @click="onSync()"> <el-button v-permission v-node-admin @click="onSync()">
{{ $t('commons.button.sync') }} {{ $t('commons.button.sync') }}
</el-button> </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') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
</template> </template>
@@ -126,6 +140,7 @@
<fu-input-rw-switch <fu-input-rw-switch
v-model="row.description" v-model="row.description"
v-permission v-permission
v-node-admin
@enter="onChange(row)" @enter="onChange(row)"
@blur="onChange(row)" @blur="onChange(row)"
/> />
@@ -337,6 +352,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
disabled: (row: Toolbox.FtpInfo) => { disabled: (row: Toolbox.FtpInfo) => {
return row.status === 'deleted'; return row.status === 'deleted';
}, },
@@ -356,6 +372,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: (row: Toolbox.FtpInfo) => { click: (row: Toolbox.FtpInfo) => {
onDelete(row); onDelete(row);
}, },
@@ -13,7 +13,7 @@
<el-form-item :label="$t('commons.login.password')" prop="password"> <el-form-item :label="$t('commons.login.password')" prop="password">
<el-input type="password" clearable v-model="dialogData.rowData!.password" show-password> <el-input type="password" clearable v-model="dialogData.rowData!.password" show-password>
<template #append> <template #append>
<el-button v-permission @click="random"> <el-button v-permission v-node-admin @click="random">
{{ $t('commons.button.random') }} {{ $t('commons.button.random') }}
</el-button> </el-button>
</template> </template>
@@ -22,7 +22,12 @@
<el-form-item :label="$t('file.root')" prop="path"> <el-form-item :label="$t('file.root')" prop="path">
<el-input v-model="dialogData.rowData!.path"> <el-input v-model="dialogData.rowData!.path">
<template #prepend> <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> </template>
</el-input> </el-input>
<span class="input-help">{{ $t('toolbox.ftp.dirHelper') }}</span> <span class="input-help">{{ $t('toolbox.ftp.dirHelper') }}</span>
@@ -34,7 +39,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
+2 -2
View File
@@ -3,11 +3,11 @@
<RouterButton :buttons="buttons"> <RouterButton :buttons="buttons">
<template #route-button> <template #route-button>
<div class="router-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') }} {{ $t('home.restart_1panel') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('home.restart_system') }}
</el-button> </el-button>
</div> </div>
@@ -10,7 +10,7 @@
<span class="input-help">{{ $t('tool.supervisor.serviceNameHelper') }}</span> <span class="input-help">{{ $t('tool.supervisor.serviceNameHelper') }}</span>
</el-form-item> </el-form-item>
<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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
@@ -2,7 +2,7 @@
<div v-loading="loading"> <div v-loading="loading">
<CodemirrorPro class="mt-5" v-model="content" :heightDiff="375"></CodemirrorPro> <CodemirrorPro class="mt-5" v-model="content" :heightDiff="375"></CodemirrorPro>
<div class="mt-5"> <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') }} {{ $t('commons.button.save') }}
</el-button> </el-button>
</div> </div>
@@ -22,7 +22,12 @@
<el-form-item :label="$t('tool.supervisor.dir')" prop="dir"> <el-form-item :label="$t('tool.supervisor.dir')" prop="dir">
<el-input v-model.trim="process.dir"> <el-input v-model.trim="process.dir">
<template #prepend> <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> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -47,7 +52,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -15,7 +15,7 @@
<template #footer> <template #footer>
<span> <span>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button> <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') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
@@ -23,7 +23,7 @@
/> />
</template> </template>
<template v-if="showTable" #leftToolBar> <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') }} {{ $t('commons.button.create') }}
</el-button> </el-button>
</template> </template>
@@ -75,17 +75,25 @@
<div v-if="row.status && row.status.length > 0 && row.hasLoad"> <div v-if="row.status && row.status.length > 0 && row.hasLoad">
<Status <Status
v-permission v-permission
v-node-admin
v-if="checkStatus(row.status) === 'RUNNING'" v-if="checkStatus(row.status) === 'RUNNING'"
status="running" status="running"
@click="operate('stop', row.name)" @click="operate('stop', row.name)"
/> />
<Status <Status
v-permission v-permission
v-node-admin
v-else-if="checkStatus(row.status) === 'WARNING'" v-else-if="checkStatus(row.status) === 'WARNING'"
status="unhealthy" status="unhealthy"
@click="operate('restart', row.name)" @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>
<div v-if="!row.hasLoad"> <div v-if="!row.hasLoad">
<el-button link loading></el-button> <el-button link loading></el-button>
@@ -342,6 +350,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
permission: true, permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) { click: function (row: HostTool.SupervisorProcess) {
edit(row); edit(row);
}, },
@@ -349,6 +358,7 @@ const buttons = [
{ {
label: i18n.global.t('website.sourceFile'), label: i18n.global.t('website.sourceFile'),
permission: true, permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) { click: function (row: HostTool.SupervisorProcess) {
getFile(row.name, 'config'); getFile(row.name, 'config');
}, },
@@ -362,6 +372,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.restart'), label: i18n.global.t('commons.button.restart'),
permission: true, permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) { click: function (row: HostTool.SupervisorProcess) {
operate('restart', row.name); operate('restart', row.name);
}, },
@@ -369,6 +380,7 @@ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
permission: true, permission: true,
nodeAdmin: true,
click: function (row: HostTool.SupervisorProcess) { click: function (row: HostTool.SupervisorProcess) {
operate('delete', row.name); operate('delete', row.name);
}, },
@@ -22,7 +22,13 @@
:height-diff="300" :height-diff="300"
> >
<template #button> <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') }} {{ $t('commons.button.clean') }}
</el-button> </el-button>
</template> </template>
@@ -11,6 +11,7 @@
<div class="mt-0.5" v-if="!data.init"> <div class="mt-0.5" v-if="!data.init">
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
v-if="data.status != 'running'" v-if="data.status != 'running'"
link link
@@ -20,6 +21,7 @@
</el-button> </el-button>
<el-button <el-button
v-permission v-permission
v-node-admin
type="primary" type="primary"
v-if="data.status == 'running'" v-if="data.status == 'running'"
link link
@@ -28,16 +30,16 @@
{{ $t('commons.operate.stop') }} {{ $t('commons.operate.stop') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.restart') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <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') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
</div> </div>
<div class="mt-0.5" v-else> <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') }} {{ $t('commons.button.init') }}
</el-button> </el-button>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More