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