mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-11 00:00:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
411402f9cb | ||
|
|
733889407e | ||
|
|
f50972a0e3 | ||
|
|
d50fa7d928 | ||
|
|
df513c49e9 | ||
|
|
10bd640a9b | ||
|
|
6b491710c9 | ||
|
|
b9c5fee411 | ||
|
|
78f2b6f72d | ||
|
|
9aaa387b23 | ||
|
|
a6fb0f1e89 | ||
|
|
cbe1e9b4e1 | ||
|
|
0a4e48c057 | ||
|
|
3dc829a630 | ||
|
|
bcd88c6eca | ||
|
|
4d279a521e | ||
|
|
3fb5e523a8 | ||
|
|
b2926019cf | ||
|
|
a9d8018e78 | ||
|
|
471a245732 | ||
|
|
55c71df906 | ||
|
|
f91831a5ea | ||
|
|
18cbcb7f13 | ||
|
|
e8d3b5fe93 | ||
|
|
360cdf3f77 | ||
|
|
c3a67eed87 | ||
|
|
e8bc1310da | ||
|
|
c873c18a42 |
@@ -14,7 +14,7 @@ import (
|
||||
// @Description 获取设备基础信息
|
||||
// @Success 200 {object} dto.DeviceBaseInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/device/base [get]
|
||||
// @Router /toolbox/device/base [post]
|
||||
func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) {
|
||||
data, err := deviceService.LoadBaseInfo()
|
||||
if err != nil {
|
||||
|
||||
@@ -131,28 +131,6 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Analysis host SSH logs
|
||||
// @Description 分析 SSH 登录日志
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchForAnalysis true "request"
|
||||
// @Success 200 {array} dto.SSHLogAnalysis
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/ssh/log/analysis [post]
|
||||
func (b *BaseApi) AnalysisLog(c *gin.Context) {
|
||||
var req dto.SearchForAnalysis
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := sshService.AnalysisLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Load host SSH logs
|
||||
// @Description 获取 SSH 登录日志
|
||||
|
||||
@@ -124,7 +124,7 @@ func (b *BaseApi) ObtainWebsiteCA(c *gin.Context) {
|
||||
// @Param request body request.WebsiteCAObtain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ca/obtain [post]
|
||||
// @Router /websites/ca/renew [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"自签 SSL 证书 [name]","formatEN":"Obtain SSL [name]"}
|
||||
func (b *BaseApi) RenewWebsiteCA(c *gin.Context) {
|
||||
var req request.WebsiteCARenew
|
||||
|
||||
@@ -16,7 +16,7 @@ type RuleSearch struct {
|
||||
}
|
||||
|
||||
type FirewallOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop disablePing enablePing"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
|
||||
}
|
||||
|
||||
type PortRuleOperate struct {
|
||||
|
||||
@@ -27,7 +27,6 @@ type ImagePull struct {
|
||||
}
|
||||
|
||||
type ImageTag struct {
|
||||
RepoID uint `json:"repoID"`
|
||||
SourceID string `json:"sourceID" validate:"required"`
|
||||
TargetName string `json:"targetName" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -77,9 +77,10 @@ type FilePathCheck struct {
|
||||
}
|
||||
|
||||
type FileWget struct {
|
||||
Url string `json:"url" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Url string `json:"url" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
IgnoreCertificate bool `json:"ignoreCertificate"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
|
||||
@@ -44,26 +44,6 @@ type SSHLog struct {
|
||||
FailedCount int `json:"failedCount"`
|
||||
}
|
||||
|
||||
type SearchForAnalysis struct {
|
||||
PageInfo
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=Success Failed"`
|
||||
}
|
||||
|
||||
type AnalysisRes struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []SSHLogAnalysis `json:"items"`
|
||||
SuccessfulCount int `json:"successfulCount"`
|
||||
FailedCount int `json:"failedCount"`
|
||||
}
|
||||
|
||||
type SSHLogAnalysis struct {
|
||||
Address string `json:"address"`
|
||||
Area string `json:"area"`
|
||||
SuccessfulCount int `json:"successfulCount"`
|
||||
FailedCount int `json:"failedCount"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type SSHHistory struct {
|
||||
Date time.Time `json:"date"`
|
||||
DateStr string `json:"dateStr"`
|
||||
|
||||
@@ -19,6 +19,7 @@ type IBackupRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteRecord(ctx context.Context, opts ...DBOption) error
|
||||
UpdateRecord(record *model.BackupRecord) error
|
||||
WithByDetailName(detailName string) DBOption
|
||||
WithByFileName(fileName string) DBOption
|
||||
WithByType(backupType string) DBOption
|
||||
@@ -105,6 +106,10 @@ func (u *BackupRepo) CreateRecord(record *model.BackupRecord) error {
|
||||
return global.DB.Create(record).Error
|
||||
}
|
||||
|
||||
func (u *BackupRepo) UpdateRecord(record *model.BackupRecord) error {
|
||||
return global.DB.Save(record).Error
|
||||
}
|
||||
|
||||
func (u *BackupRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.BackupAccount{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type IAcmeAccountRepo interface {
|
||||
Save(account model.WebsiteAcmeAccount) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
WithEmail(email string) DBOption
|
||||
WithType(acType string) DBOption
|
||||
}
|
||||
|
||||
func NewIAcmeAccountRepo() IAcmeAccountRepo {
|
||||
@@ -26,6 +27,11 @@ func (w *WebsiteAcmeAccountRepo) WithEmail(email string) DBOption {
|
||||
return db.Where("email = ?", email)
|
||||
}
|
||||
}
|
||||
func (w *WebsiteAcmeAccountRepo) WithType(acType string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("type = ?", acType)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteAcmeAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteAcmeAccount, error) {
|
||||
var accounts []model.WebsiteAcmeAccount
|
||||
|
||||
@@ -91,8 +91,10 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
|
||||
}
|
||||
itemPath := path.Join(records[i].FileDir, records[i].FileName)
|
||||
if records[i].Source == "LOCAL" {
|
||||
fileInfo, _ := os.Stat(itemPath)
|
||||
item.Size = fileInfo.Size()
|
||||
fileInfo, err := os.Stat(itemPath)
|
||||
if err == nil {
|
||||
item.Size = fileInfo.Size()
|
||||
}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
@@ -70,7 +71,7 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error {
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
@@ -125,7 +125,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
isOk := false
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
|
||||
if err != nil {
|
||||
|
||||
@@ -111,7 +111,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, backupDir, fileName string) err
|
||||
func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback bool) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(recoverFile) {
|
||||
return fmt.Errorf("%s file is not exist", recoverFile)
|
||||
return buserr.WithName("ErrFileNotFound", recoverFile)
|
||||
}
|
||||
|
||||
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
|
||||
|
||||
@@ -25,7 +25,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
record := &model.BackupRecord{
|
||||
Type: "website",
|
||||
Name: website.PrimaryDomain,
|
||||
DetailName: "",
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: backupDir,
|
||||
@@ -54,13 +54,13 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||
return buserr.WithName("ErrFileNotFound", req.File)
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("recover website %s from backup file %s", req.Name, req.File)
|
||||
if err := handleWebsiteRecover(&website, req.File, false); err != nil {
|
||||
@@ -79,15 +79,6 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
_ = os.RemoveAll(tmpPath)
|
||||
}()
|
||||
|
||||
temPathWithName := tmpPath + "/" + website.Alias
|
||||
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
|
||||
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
|
||||
}
|
||||
if website.Type == constant.Deployment {
|
||||
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
|
||||
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
|
||||
}
|
||||
}
|
||||
var oldWebsite model.Website
|
||||
websiteJson, err := os.ReadFile(tmpPath + "/website.json")
|
||||
if err != nil {
|
||||
@@ -101,6 +92,16 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
return err
|
||||
}
|
||||
|
||||
temPathWithName := tmpPath + "/" + website.Alias
|
||||
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
|
||||
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
|
||||
}
|
||||
if website.Type == constant.Deployment {
|
||||
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
|
||||
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
|
||||
}
|
||||
}
|
||||
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format("20060102150405")))
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -805,7 +807,25 @@ func checkImageExist(client *client.Client, image string) bool {
|
||||
}
|
||||
|
||||
func pullImages(ctx context.Context, client *client.Client, image string) error {
|
||||
out, err := client.ImagePull(ctx, image, types.ImagePullOptions{})
|
||||
options := types.ImagePullOptions{}
|
||||
repos, _ := imageRepoRepo.List()
|
||||
if len(repos) != 0 {
|
||||
for _, repo := range repos {
|
||||
if strings.HasPrefix(image, repo.DownloadUrl) && repo.Auth {
|
||||
authConfig := registry.AuthConfig{
|
||||
Username: repo.Username,
|
||||
Password: repo.Password,
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
options.RegistryAuth = authStr
|
||||
}
|
||||
}
|
||||
}
|
||||
out, err := client.ImagePull(ctx, image, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -55,10 +55,15 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
|
||||
baseInfo.Hosts = loadHosts()
|
||||
baseInfo.Hostname = loadHostname()
|
||||
baseInfo.User = loadUser()
|
||||
ntp, _ := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
||||
baseInfo.Ntp = ntp.Value
|
||||
ntp, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
||||
if err == nil {
|
||||
baseInfo.Ntp = ntp.Value
|
||||
}
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
swapInfo, err := mem.SwapMemory()
|
||||
if err != nil {
|
||||
return baseInfo, err
|
||||
}
|
||||
baseInfo.SwapMemoryTotal = swapInfo.Total
|
||||
baseInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
baseInfo.SwapMemoryUsed = swapInfo.Used
|
||||
@@ -375,7 +380,7 @@ func loadUser() string {
|
||||
|
||||
func loadSwap() []dto.SwapHelper {
|
||||
var data []dto.SwapHelper
|
||||
std, err := cmd.Execf("%s swapon --show --summary", cmd.SudoHandleCmd())
|
||||
std, err := cmd.Execf("%s swapon --summary", cmd.SudoHandleCmd())
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -39,12 +39,16 @@ func (u *Fail2BanService) LoadBaseInfo() (dto.Fail2BanBaseInfo, error) {
|
||||
baseInfo.IsEnable, baseInfo.IsActive, baseInfo.IsExist = client.Status()
|
||||
if !baseInfo.IsActive {
|
||||
baseInfo.Version = "-"
|
||||
return baseInfo, nil
|
||||
} else {
|
||||
baseInfo.Version = client.Version()
|
||||
}
|
||||
baseInfo.Version = client.Version()
|
||||
conf, err := os.ReadFile(defaultFail2BanPath)
|
||||
if err != nil {
|
||||
return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)
|
||||
if baseInfo.IsActive {
|
||||
return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)
|
||||
} else {
|
||||
return baseInfo, nil
|
||||
}
|
||||
}
|
||||
lines := strings.Split(string(conf), "\n")
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ func (f *FileService) ChangeName(req request.FileRename) error {
|
||||
func (f *FileService) Wget(w request.FileWget) (string, error) {
|
||||
fo := files.NewFileOp()
|
||||
key := "file-wget-" + common.GetUuid()
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key)
|
||||
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, w.IgnoreCertificate)
|
||||
}
|
||||
|
||||
func (f *FileService) MvFile(m request.FileMove) error {
|
||||
|
||||
@@ -196,6 +196,12 @@ func (u *FirewallService) OperateFirewall(operation string) error {
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
case "restart":
|
||||
if err := client.Restart(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
case "disablePing":
|
||||
return u.updatePingStatus("0")
|
||||
case "enablePing":
|
||||
@@ -582,24 +588,6 @@ func (u *FirewallService) addAddressRecord(req dto.AddrRuleOperate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func listIpRules(strategy string) ([]string, error) {
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs, err := client.ListAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rules []string
|
||||
for _, addr := range addrs {
|
||||
if addr.Strategy == strategy {
|
||||
rules = append(rules, addr.Address)
|
||||
}
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func checkPortUsed(ports, proto string, apps []portOfApp) string {
|
||||
var portList []int
|
||||
if strings.Contains(ports, "-") || strings.Contains(ports, ",") {
|
||||
|
||||
+80
-229
@@ -32,7 +32,6 @@ type ISSHService interface {
|
||||
UpdateByFile(value string) error
|
||||
Update(req dto.SSHUpdate) error
|
||||
GenerateSSH(req dto.GenerateSSH) error
|
||||
AnalysisLog(req dto.SearchForAnalysis) (*dto.AnalysisRes, error)
|
||||
LoadSSHSecret(mode string) (string, error)
|
||||
LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error)
|
||||
|
||||
@@ -49,7 +48,7 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||
Status: constant.StatusEnable,
|
||||
Message: "",
|
||||
Port: "22",
|
||||
ListenAddress: "0.0.0.0",
|
||||
ListenAddress: "",
|
||||
PasswordAuthentication: "yes",
|
||||
PubkeyAuthentication: "yes",
|
||||
PermitRootLogin: "yes",
|
||||
@@ -91,7 +90,12 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||
data.Port = strings.ReplaceAll(line, "Port ", "")
|
||||
}
|
||||
if strings.HasPrefix(line, "ListenAddress ") {
|
||||
data.ListenAddress = strings.ReplaceAll(line, "ListenAddress ", "")
|
||||
itemAddr := strings.ReplaceAll(line, "ListenAddress ", "")
|
||||
if len(data.ListenAddress) != 0 {
|
||||
data.ListenAddress += ("," + itemAddr)
|
||||
} else {
|
||||
data.ListenAddress = itemAddr
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(line, "PasswordAuthentication ") {
|
||||
data.PasswordAuthentication = strings.ReplaceAll(line, "PasswordAuthentication ", "")
|
||||
@@ -310,9 +314,9 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
case constant.StatusFailed:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", file.Name, command)
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' %s", file.Name, command)
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file.Name, command)
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
|
||||
@@ -322,7 +326,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
case constant.StatusFailed:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' %s", file.Name, command)
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file.Name, command)
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" %s", file.Name, command)
|
||||
}
|
||||
}
|
||||
dataItem, successCount, failedCount := loadSSHData(commandItem, showCountFrom, showCountTo, file.Year, qqWry, nyc)
|
||||
@@ -337,89 +341,6 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (u *SSHService) AnalysisLog(req dto.SearchForAnalysis) (*dto.AnalysisRes, error) {
|
||||
var fileList []string
|
||||
baseDir := "/var/log"
|
||||
if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) {
|
||||
if !strings.HasSuffix(info.Name(), ".gz") {
|
||||
fileList = append(fileList, pathItem)
|
||||
return nil
|
||||
}
|
||||
itemFileName := strings.TrimSuffix(pathItem, ".gz")
|
||||
if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) {
|
||||
if err := handleGunzip(pathItem); err == nil {
|
||||
fileList = append(fileList, itemFileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command := ""
|
||||
sortMap := make(map[string]dto.SSHLogAnalysis)
|
||||
for _, file := range fileList {
|
||||
commandItem := ""
|
||||
if strings.HasPrefix(path.Base(file), "secure") {
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file, command)
|
||||
}
|
||||
if strings.HasPrefix(path.Base(file), "auth.log") {
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file, command)
|
||||
}
|
||||
loadSSHDataForAnalysis(sortMap, commandItem)
|
||||
}
|
||||
var sortSlice []dto.SSHLogAnalysis
|
||||
for key, value := range sortMap {
|
||||
sortSlice = append(sortSlice, dto.SSHLogAnalysis{Address: key, SuccessfulCount: value.SuccessfulCount, FailedCount: value.FailedCount, Status: "accept"})
|
||||
}
|
||||
if req.OrderBy == constant.StatusSuccess {
|
||||
sort.Slice(sortSlice, func(i, j int) bool {
|
||||
return sortSlice[i].SuccessfulCount > sortSlice[j].SuccessfulCount
|
||||
})
|
||||
} else {
|
||||
sort.Slice(sortSlice, func(i, j int) bool {
|
||||
return sortSlice[i].FailedCount > sortSlice[j].FailedCount
|
||||
})
|
||||
}
|
||||
qqWry, _ := qqwry.NewQQwry()
|
||||
rules, _ := listIpRules("drop")
|
||||
for i := 0; i < len(sortSlice); i++ {
|
||||
sortSlice[i].Area = qqWry.Find(sortSlice[i].Address).Area
|
||||
for _, rule := range rules {
|
||||
if sortSlice[i].Address == rule {
|
||||
sortSlice[i].Status = "drop"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var backData dto.AnalysisRes
|
||||
for _, item := range sortSlice {
|
||||
backData.FailedCount += item.FailedCount
|
||||
backData.SuccessfulCount += item.SuccessfulCount
|
||||
}
|
||||
|
||||
var data []dto.SSHLogAnalysis
|
||||
total, start, end := len(sortSlice), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
data = make([]dto.SSHLogAnalysis, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
data = sortSlice[start:end]
|
||||
}
|
||||
backData.Items = data
|
||||
backData.Total = int64(total)
|
||||
|
||||
return &backData, nil
|
||||
}
|
||||
|
||||
func (u *SSHService) LoadSSHConf() (string, error) {
|
||||
if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
@@ -450,33 +371,34 @@ func sortFileList(fileNames []sshFileItem) []sshFileItem {
|
||||
return fileNames
|
||||
}
|
||||
|
||||
func updateSSHConf(oldFiles []string, param string, value interface{}) []string {
|
||||
hasKey := false
|
||||
func updateSSHConf(oldFiles []string, param string, value string) []string {
|
||||
var valueItems []string
|
||||
if param != "ListenAddress" {
|
||||
valueItems = append(valueItems, value)
|
||||
} else {
|
||||
if value != "" {
|
||||
valueItems = strings.Split(value, ",")
|
||||
}
|
||||
}
|
||||
var newFiles []string
|
||||
for _, line := range oldFiles {
|
||||
if strings.HasPrefix(line, param+" ") {
|
||||
newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
|
||||
hasKey = true
|
||||
lineItem := strings.TrimSpace(line)
|
||||
if (strings.HasPrefix(lineItem, param) || strings.HasPrefix(lineItem, fmt.Sprintf("#%s", param))) && len(valueItems) != 0 {
|
||||
newFiles = append(newFiles, fmt.Sprintf("%s %s", param, valueItems[0]))
|
||||
valueItems = valueItems[1:]
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(lineItem, param) && len(valueItems) == 0 {
|
||||
newFiles = append(newFiles, fmt.Sprintf("#%s", line))
|
||||
continue
|
||||
}
|
||||
newFiles = append(newFiles, line)
|
||||
}
|
||||
if !hasKey {
|
||||
newFiles = []string{}
|
||||
for _, line := range oldFiles {
|
||||
if strings.HasPrefix(line, fmt.Sprintf("#%s ", param)) && !hasKey {
|
||||
newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
|
||||
hasKey = true
|
||||
continue
|
||||
}
|
||||
newFiles = append(newFiles, line)
|
||||
if len(valueItems) != 0 {
|
||||
for _, item := range valueItems {
|
||||
newFiles = append(newFiles, fmt.Sprintf("%s %s", param, item))
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
newFiles = []string{}
|
||||
newFiles = append(newFiles, oldFiles...)
|
||||
newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
|
||||
}
|
||||
return newFiles
|
||||
}
|
||||
|
||||
@@ -529,147 +451,62 @@ func loadSSHData(command string, showCountFrom, showCountTo, currentYear int, qq
|
||||
return datas, successCount, failedCount
|
||||
}
|
||||
|
||||
func loadSSHDataForAnalysis(analysisMap map[string]dto.SSHLogAnalysis, commandItem string) {
|
||||
stdout, err := cmd.Exec(commandItem)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(string(stdout), "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
var itemData dto.SSHHistory
|
||||
switch {
|
||||
case strings.Contains(lines[i], "Failed password for"):
|
||||
itemData = loadFailedSecureDatas(lines[i])
|
||||
case strings.Contains(lines[i], "Connection closed by authenticating user"):
|
||||
itemData = loadFailedAuthDatas(lines[i])
|
||||
case strings.Contains(lines[i], "Accepted "):
|
||||
itemData = loadSuccessDatas(lines[i])
|
||||
}
|
||||
if len(itemData.Address) != 0 {
|
||||
if val, ok := analysisMap[itemData.Address]; ok {
|
||||
if itemData.Status == constant.StatusSuccess {
|
||||
val.SuccessfulCount++
|
||||
} else {
|
||||
val.FailedCount++
|
||||
}
|
||||
analysisMap[itemData.Address] = val
|
||||
} else {
|
||||
item := dto.SSHLogAnalysis{
|
||||
Address: itemData.Address,
|
||||
SuccessfulCount: 0,
|
||||
FailedCount: 0,
|
||||
}
|
||||
if itemData.Status == constant.StatusSuccess {
|
||||
item.SuccessfulCount = 1
|
||||
} else {
|
||||
item.FailedCount = 1
|
||||
}
|
||||
analysisMap[itemData.Address] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadSuccessDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
||||
if err != nil {
|
||||
if len(parts) < 14 {
|
||||
return data
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[6],
|
||||
User: parts[8],
|
||||
Address: parts[10],
|
||||
Port: parts[12],
|
||||
Status: constant.StatusSuccess,
|
||||
}
|
||||
} else {
|
||||
if len(parts) < 12 {
|
||||
return data
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: t.Format("2006 Jan 2 15:04:05"),
|
||||
AuthMode: parts[4],
|
||||
User: parts[6],
|
||||
Address: parts[8],
|
||||
Port: parts[10],
|
||||
Status: constant.StatusSuccess,
|
||||
}
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
data.AuthMode = parts[4+index]
|
||||
data.User = parts[6+index]
|
||||
data.Address = parts[8+index]
|
||||
data.Port = parts[10+index]
|
||||
data.Status = constant.StatusSuccess
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
||||
if err != nil {
|
||||
if len(parts) < 14 {
|
||||
return data
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[8],
|
||||
User: parts[10],
|
||||
Address: parts[11],
|
||||
Port: parts[13],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
} else {
|
||||
if len(parts) < 12 {
|
||||
return data
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: t.Format("2006 Jan 2 15:04:05"),
|
||||
AuthMode: parts[6],
|
||||
User: parts[7],
|
||||
Address: parts[9],
|
||||
Port: parts[11],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
if index == 2 {
|
||||
data.User = parts[10]
|
||||
} else {
|
||||
data.User = parts[7]
|
||||
}
|
||||
data.AuthMode = parts[6+index]
|
||||
data.Address = parts[9+index]
|
||||
data.Port = parts[11+index]
|
||||
data.Status = constant.StatusFailed
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedSecureDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
||||
if err != nil {
|
||||
if len(parts) < 14 {
|
||||
return data
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[6],
|
||||
User: parts[8],
|
||||
Address: parts[10],
|
||||
Port: parts[12],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
} else {
|
||||
if len(parts) < 12 {
|
||||
return data
|
||||
}
|
||||
index := 0
|
||||
if strings.Contains(line, " invalid user") {
|
||||
index = 2
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: t.Format("2006 Jan 2 15:04:05"),
|
||||
AuthMode: parts[4],
|
||||
User: parts[index+6],
|
||||
Address: parts[index+8],
|
||||
Port: parts[index+10],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
if strings.Contains(line, " invalid ") {
|
||||
data.AuthMode = parts[4+index]
|
||||
index += 2
|
||||
} else {
|
||||
data.AuthMode = parts[4+index]
|
||||
}
|
||||
data.User = parts[6+index]
|
||||
data.Address = parts[8+index]
|
||||
data.Port = parts[10+index]
|
||||
data.Status = constant.StatusFailed
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
}
|
||||
@@ -699,3 +536,17 @@ func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
|
||||
}
|
||||
return itemDate
|
||||
}
|
||||
|
||||
func analyzeDateStr(parts []string) (int, string) {
|
||||
t, err := time.Parse("2006-01-02T15:04:05.999999-07:00", parts[0])
|
||||
if err != nil {
|
||||
if len(parts) < 14 {
|
||||
return 0, ""
|
||||
}
|
||||
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
|
||||
}
|
||||
if len(parts) < 12 {
|
||||
return 0, ""
|
||||
}
|
||||
return 0, t.Format("2006 Jan 2 15:04:05")
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (w WebsiteAcmeAccountService) Page(search dto.PageInfo) (int64, []response.
|
||||
}
|
||||
|
||||
func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreate) (*response.WebsiteAcmeAccountDTO, error) {
|
||||
exist, _ := websiteAcmeRepo.GetFirst(websiteAcmeRepo.WithEmail(create.Email))
|
||||
exist, _ := websiteAcmeRepo.GetFirst(websiteAcmeRepo.WithEmail(create.Email), websiteAcmeRepo.WithType(create.Type))
|
||||
if exist != nil {
|
||||
return nil, buserr.New(constant.ErrEmailIsExist)
|
||||
}
|
||||
|
||||
@@ -92,9 +92,6 @@ func (w WebsiteCAService) Create(create request.WebsiteCACreate) (*request.Websi
|
||||
MaxPathLenZero: false,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
}
|
||||
var (
|
||||
caPEM = new(bytes.Buffer)
|
||||
)
|
||||
|
||||
interPrivateKey, interPublicKey, privateBytes, err := createPrivateKey(create.KeyType)
|
||||
if err != nil {
|
||||
@@ -102,15 +99,18 @@ func (w WebsiteCAService) Create(create request.WebsiteCACreate) (*request.Websi
|
||||
}
|
||||
ca.PrivateKey = string(privateBytes)
|
||||
|
||||
caBytes, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, interPublicKey, interPrivateKey)
|
||||
rootDer, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, interPublicKey, interPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootCert, err := x509.ParseCertificate(rootDer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: caBytes,
|
||||
Bytes: rootCert.Raw,
|
||||
}
|
||||
_ = pem.Encode(caPEM, certBlock)
|
||||
pemData := pem.EncodeToMemory(certBlock)
|
||||
ca.CSR = string(pemData)
|
||||
|
||||
@@ -297,15 +297,26 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interCertBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: interCert.Raw,
|
||||
}
|
||||
_, publicKey, privateKeyBytes, err := createPrivateKey(websiteSSL.KeyType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commonName := ""
|
||||
if len(domains) > 0 {
|
||||
commonName = domains[0]
|
||||
}
|
||||
if len(ips) > 0 {
|
||||
commonName = ips[0].String()
|
||||
}
|
||||
subject := rootCsr.Subject
|
||||
subject.CommonName = commonName
|
||||
csr := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: rootCsr.Subject,
|
||||
Subject: subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
@@ -329,8 +340,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
pemData := pem.EncodeToMemory(certBlock)
|
||||
websiteSSL.Pem = string(pemData)
|
||||
websiteSSL.Pem = string(pem.EncodeToMemory(certBlock)) + string(pem.EncodeToMemory(rootCertBlock)) + string(pem.EncodeToMemory(interCertBlock))
|
||||
websiteSSL.PrivateKey = string(privateKeyBytes)
|
||||
websiteSSL.ExpireDate = cert.NotAfter
|
||||
websiteSSL.StartDate = cert.NotBefore
|
||||
|
||||
@@ -414,14 +414,28 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
|
||||
return buserr.New("ErrSSLKeyFormat")
|
||||
}
|
||||
|
||||
certBlock, _ := pem.Decode([]byte(websiteSSL.Pem))
|
||||
if certBlock == nil {
|
||||
var (
|
||||
cert *x509.Certificate
|
||||
pemData = []byte(websiteSSL.Pem)
|
||||
)
|
||||
for {
|
||||
certBlock, reset := pem.Decode(pemData)
|
||||
if certBlock == nil {
|
||||
break
|
||||
}
|
||||
cert, err = x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cert.DNSNames) > 0 || len(cert.IPAddresses) > 0 {
|
||||
break
|
||||
}
|
||||
pemData = reset
|
||||
}
|
||||
if pemData == nil {
|
||||
return buserr.New("ErrSSLCertificateFormat")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
websiteSSL.ExpireDate = cert.NotAfter
|
||||
websiteSSL.StartDate = cert.NotBefore
|
||||
websiteSSL.Type = cert.Issuer.CommonName
|
||||
|
||||
@@ -60,6 +60,7 @@ func Init() {
|
||||
migrations.AddDatabaseSSL,
|
||||
migrations.AddDefaultCA,
|
||||
migrations.AddSettingRecycleBin,
|
||||
migrations.UpdateWebsiteBackupRecord,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -3,6 +3,7 @@ package migrations
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
@@ -87,3 +88,18 @@ var AddSettingRecycleBin = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateWebsiteBackupRecord = &gormigrate.Migration{
|
||||
ID: "20231218-update-backup-record-for-website",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
backupRepo := repo.NewIBackupRepo()
|
||||
websitesBackups, _ := backupRepo.ListRecord(repo.NewCommonRepo().WithByType("website"))
|
||||
if len(websitesBackups) > 0 {
|
||||
for _, backup := range websitesBackups {
|
||||
backup.DetailName = backup.Name
|
||||
_ = backupRepo.UpdateRecord(&backup)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
|
||||
hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret)
|
||||
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
||||
hostRouter.POST("/ssh/log/analysis", baseApi.AnalysisLog)
|
||||
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
|
||||
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func (s *ToolboxRouter) InitToolboxRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
toolboxRouter.GET("/device/base", baseApi.LoadDeviceBaseInfo)
|
||||
toolboxRouter.POST("/device/base", baseApi.LoadDeviceBaseInfo)
|
||||
toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption)
|
||||
toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf)
|
||||
toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost)
|
||||
|
||||
@@ -17,44 +17,60 @@ import (
|
||||
"github.com/mozillazg/go-pinyin"
|
||||
)
|
||||
|
||||
func CompareVersion(version1 string, version2 string) bool {
|
||||
if version1 == version2 {
|
||||
return false
|
||||
}
|
||||
version1s := strings.Split(version1, ".")
|
||||
version2s := strings.Split(version2, ".")
|
||||
func CompareVersion(version1, version2 string) bool {
|
||||
v1s := extractNumbers(version1)
|
||||
v2s := extractNumbers(version2)
|
||||
|
||||
if len(version2s) > len(version1s) {
|
||||
for i := 0; i < len(version2s)-len(version1s); i++ {
|
||||
version1s = append(version1s, "0")
|
||||
}
|
||||
}
|
||||
if len(version1s) > len(version2s) {
|
||||
for i := 0; i < len(version1s)-len(version2s); i++ {
|
||||
version2s = append(version2s, "0")
|
||||
}
|
||||
}
|
||||
maxLen := max(len(v1s), len(v2s))
|
||||
v1s = append(v1s, make([]string, maxLen-len(v1s))...)
|
||||
v2s = append(v2s, make([]string, maxLen-len(v2s))...)
|
||||
|
||||
n := min(len(version1s), len(version2s))
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
for i := 0; i < n; i++ {
|
||||
sVersion1s := re.FindAllString(version1s[i], -1)
|
||||
sVersion2s := re.FindAllString(version2s[i], -1)
|
||||
if len(sVersion1s) == 0 {
|
||||
return false
|
||||
for i := 0; i < maxLen; i++ {
|
||||
v1, err1 := strconv.Atoi(v1s[i])
|
||||
v2, err2 := strconv.Atoi(v2s[i])
|
||||
if err1 != nil {
|
||||
v1 = 0
|
||||
}
|
||||
if len(sVersion2s) == 0 {
|
||||
return false
|
||||
if err2 != nil {
|
||||
v2 = 0
|
||||
}
|
||||
v1num, _ := strconv.Atoi(sVersion1s[0])
|
||||
v2num, _ := strconv.Atoi(sVersion2s[0])
|
||||
if v1num == v2num {
|
||||
continue
|
||||
if v1 != v2 {
|
||||
return v1 > v2
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func extractNumbers(version string) []string {
|
||||
var numbers []string
|
||||
start := -1
|
||||
for i, r := range version {
|
||||
if isDigit(r) {
|
||||
if start == -1 {
|
||||
start = i
|
||||
}
|
||||
} else {
|
||||
return v1num > v2num
|
||||
if start != -1 {
|
||||
numbers = append(numbers, version[start:i])
|
||||
start = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
if start != -1 {
|
||||
numbers = append(numbers, version[start:])
|
||||
}
|
||||
return numbers
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
func max(x, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func GetSortedVersions(versions []string) []string {
|
||||
@@ -72,13 +88,6 @@ func IsCrossVersion(version1, version2 string) bool {
|
||||
return v2num > v1num
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func GetUuid() string {
|
||||
b := make([]byte, 16)
|
||||
_, _ = io.ReadFull(rand.Reader, b)
|
||||
|
||||
@@ -216,11 +216,12 @@ func (w *WriteCounter) SaveProcess() {
|
||||
}
|
||||
}
|
||||
|
||||
func (f FileOp) DownloadFileWithProcess(url, dst, key string) error {
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
func (f FileOp) DownloadFileWithProcess(url, dst, key string, ignoreCertificate bool) error {
|
||||
client := &http.Client{}
|
||||
if ignoreCertificate {
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ type FirewallClient interface {
|
||||
Name() string // ufw firewalld
|
||||
Start() error
|
||||
Stop() error
|
||||
Restart() error
|
||||
Reload() error
|
||||
Status() (string, error) // running not running
|
||||
Version() (string, error)
|
||||
|
||||
@@ -52,6 +52,14 @@ func (f *Firewall) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewall) Restart() error {
|
||||
stdout, err := cmd.Exec("systemctl restart firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("restart the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewall) Reload() error {
|
||||
stdout, err := cmd.Exec("firewall-cmd --reload")
|
||||
if err != nil {
|
||||
|
||||
@@ -64,6 +64,16 @@ func (f *Ufw) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ufw) Restart() error {
|
||||
if err := f.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Ufw) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func (f *Fail2ban) ListBanned() ([]string, error) {
|
||||
if err != nil {
|
||||
return lists, err
|
||||
}
|
||||
itemList := strings.Split(stdout, " ")
|
||||
itemList := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ")
|
||||
for _, item := range itemList {
|
||||
if len(item) != 0 {
|
||||
lists = append(lists, item)
|
||||
|
||||
+56
-86
@@ -6582,45 +6582,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/ssh/log/analysis": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "分析 SSH 登录日志",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"SSH"
|
||||
],
|
||||
"summary": "Analysis host SSH logs",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SearchForAnalysis"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SSHLogAnalysis"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/ssh/operate": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -10102,7 +10063,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/toolbox/device/base": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@@ -11135,6 +11096,57 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/websites/ca/obtain": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "自签 SSL 证书",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Obtain SSL",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCAObtain"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_cas",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Obtain SSL [name]",
|
||||
"formatZH": "自签 SSL 证书 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/renew": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -15064,6 +15076,7 @@ const docTemplate = `{
|
||||
"enum": [
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
"disablePing",
|
||||
"enablePing"
|
||||
]
|
||||
@@ -15460,9 +15473,6 @@ const docTemplate = `{
|
||||
"targetName"
|
||||
],
|
||||
"properties": {
|
||||
"repoID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sourceID": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16571,26 +16581,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SSHLogAnalysis": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string"
|
||||
},
|
||||
"area": {
|
||||
"type": "string"
|
||||
},
|
||||
"failedCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"successfulCount": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SSHUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -16645,29 +16635,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SearchForAnalysis": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"orderBy",
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"orderBy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Success",
|
||||
"Failed"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SearchForTree": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -18301,6 +18268,9 @@ const docTemplate = `{
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"ignoreCertificate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -6575,45 +6575,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/ssh/log/analysis": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "分析 SSH 登录日志",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"SSH"
|
||||
],
|
||||
"summary": "Analysis host SSH logs",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SearchForAnalysis"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SSHLogAnalysis"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/ssh/operate": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -10095,7 +10056,7 @@
|
||||
}
|
||||
},
|
||||
"/toolbox/device/base": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@@ -11128,6 +11089,57 @@
|
||||
}
|
||||
},
|
||||
"/websites/ca/obtain": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "自签 SSL 证书",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Obtain SSL",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCAObtain"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_cas",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Obtain SSL [name]",
|
||||
"formatZH": "自签 SSL 证书 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/renew": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -15057,6 +15069,7 @@
|
||||
"enum": [
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
"disablePing",
|
||||
"enablePing"
|
||||
]
|
||||
@@ -15453,9 +15466,6 @@
|
||||
"targetName"
|
||||
],
|
||||
"properties": {
|
||||
"repoID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sourceID": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16564,26 +16574,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SSHLogAnalysis": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string"
|
||||
},
|
||||
"area": {
|
||||
"type": "string"
|
||||
},
|
||||
"failedCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"successfulCount": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SSHUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -16638,29 +16628,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SearchForAnalysis": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"orderBy",
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"orderBy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Success",
|
||||
"Failed"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SearchForTree": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -18294,6 +18261,9 @@
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"ignoreCertificate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1132,6 +1132,7 @@ definitions:
|
||||
enum:
|
||||
- start
|
||||
- stop
|
||||
- restart
|
||||
- disablePing
|
||||
- enablePing
|
||||
type: string
|
||||
@@ -1398,8 +1399,6 @@ definitions:
|
||||
type: object
|
||||
dto.ImageTag:
|
||||
properties:
|
||||
repoID:
|
||||
type: integer
|
||||
sourceID:
|
||||
type: string
|
||||
targetName:
|
||||
@@ -2152,19 +2151,6 @@ definitions:
|
||||
totalCount:
|
||||
type: integer
|
||||
type: object
|
||||
dto.SSHLogAnalysis:
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
area:
|
||||
type: string
|
||||
failedCount:
|
||||
type: integer
|
||||
status:
|
||||
type: string
|
||||
successfulCount:
|
||||
type: integer
|
||||
type: object
|
||||
dto.SSHUpdate:
|
||||
properties:
|
||||
key:
|
||||
@@ -2202,22 +2188,6 @@ definitions:
|
||||
- ssl
|
||||
- sslType
|
||||
type: object
|
||||
dto.SearchForAnalysis:
|
||||
properties:
|
||||
orderBy:
|
||||
enum:
|
||||
- Success
|
||||
- Failed
|
||||
type: string
|
||||
page:
|
||||
type: integer
|
||||
pageSize:
|
||||
type: integer
|
||||
required:
|
||||
- orderBy
|
||||
- page
|
||||
- pageSize
|
||||
type: object
|
||||
dto.SearchForTree:
|
||||
properties:
|
||||
info:
|
||||
@@ -3300,6 +3270,8 @@ definitions:
|
||||
type: object
|
||||
request.FileWget:
|
||||
properties:
|
||||
ignoreCertificate:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
@@ -8966,30 +8938,6 @@ paths:
|
||||
summary: Load host SSH logs
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/log/analysis:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 分析 SSH 登录日志
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchForAnalysis'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.SSHLogAnalysis'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Analysis host SSH logs
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/operate:
|
||||
post:
|
||||
consumes:
|
||||
@@ -11190,7 +11138,7 @@ paths:
|
||||
formatZH: 清理系统垃圾文件
|
||||
paramKeys: []
|
||||
/toolbox/device/base:
|
||||
get:
|
||||
post:
|
||||
description: 获取设备基础信息
|
||||
responses:
|
||||
"200":
|
||||
@@ -11860,6 +11808,39 @@ paths:
|
||||
formatZH: 删除网站 ca [name]
|
||||
paramKeys: []
|
||||
/websites/ca/obtain:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 自签 SSL 证书
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteCAObtain'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Obtain SSL
|
||||
tags:
|
||||
- Website CA
|
||||
x-panel-log:
|
||||
BeforeFunctions:
|
||||
- db: website_cas
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: false
|
||||
output_column: name
|
||||
output_value: name
|
||||
bodyKeys:
|
||||
- id
|
||||
formatEN: Obtain SSL [name]
|
||||
formatZH: 自签 SSL 证书 [name]
|
||||
paramKeys: []
|
||||
/websites/ca/renew:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
|
||||
@@ -133,7 +133,6 @@ export namespace Container {
|
||||
imageName: string;
|
||||
}
|
||||
export interface ImageTag {
|
||||
repoID: number;
|
||||
sourceID: string;
|
||||
targetName: string;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ export namespace File {
|
||||
path: string;
|
||||
name: string;
|
||||
url: string;
|
||||
ignoreCertificate?: boolean;
|
||||
}
|
||||
|
||||
export interface FileWgetRes {
|
||||
|
||||
@@ -126,6 +126,3 @@ export const loadSecret = (mode: string) => {
|
||||
export const loadSSHLogs = (params: Host.searchSSHLog) => {
|
||||
return http.post<Host.sshLog>(`/hosts/ssh/log`, params);
|
||||
};
|
||||
export const loadAnalysis = (params: Host.analysisSSHLog) => {
|
||||
return http.post<Host.logAnalysisRes>(`/hosts/ssh/log/analysis`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
|
||||
@@ -6,25 +6,25 @@ import { TimeoutEnum } from '@/enums/http-enum';
|
||||
|
||||
// device
|
||||
export const getDeviceBase = () => {
|
||||
return http.get<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`);
|
||||
return http.post<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`, {}, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const loadTimeZoneOptions = () => {
|
||||
return http.get<Array<string>>(`/toolbox/device/zone/options`);
|
||||
};
|
||||
export const updateDevice = (key: string, value: string) => {
|
||||
return http.post(`/toolbox/device/update/conf`, { key: key, value: value });
|
||||
return http.post(`/toolbox/device/update/conf`, { key: key, value: value }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const updateDeviceHost = (param: Array<Toolbox.TimeZoneOptions>) => {
|
||||
return http.post(`/toolbox/device/update/host`, param);
|
||||
return http.post(`/toolbox/device/update/host`, param, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const updateDevicePasswd = (user: string, passwd: string) => {
|
||||
return http.post(`/toolbox/device/update/passwd`, { user: user, passwd: Base64.encode(passwd) });
|
||||
return http.post(`/toolbox/device/update/passwd`, { user: user, passwd: Base64.encode(passwd) }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const updateDeviceSwap = (params: Toolbox.SwapHelper) => {
|
||||
return http.post(`/toolbox/device/update/swap`, params, TimeoutEnum.T_5M);
|
||||
return http.post(`/toolbox/device/update/swap`, params, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const updateDeviceByConf = (name: string, file: string) => {
|
||||
return http.post(`/toolbox/device/update/byconf`, { name: name, file: file });
|
||||
return http.post(`/toolbox/device/update/byconf`, { name: name, file: file }, TimeoutEnum.T_5M);
|
||||
};
|
||||
export const checkDNS = (key: string, value: string) => {
|
||||
return http.post(`/toolbox/device/check/dns`, { key: key, value: value });
|
||||
@@ -54,17 +54,17 @@ export const searchFail2ban = (param: Toolbox.Fail2banSearch) => {
|
||||
};
|
||||
|
||||
export const operateFail2ban = (operate: string) => {
|
||||
return http.post(`/toolbox/fail2ban/operate`, { operation: operate });
|
||||
return http.post(`/toolbox/fail2ban/operate`, { operation: operate }, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const operatorFail2banSSHD = (param: Toolbox.Fail2banSet) => {
|
||||
return http.post(`/toolbox/fail2ban/operate/sshd`, param);
|
||||
return http.post(`/toolbox/fail2ban/operate/sshd`, param, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const updateFail2ban = (param: Toolbox.Fail2banUpdate) => {
|
||||
return http.post(`/toolbox/fail2ban/update`, param);
|
||||
return http.post(`/toolbox/fail2ban/update`, param, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const updateFail2banByFile = (param: UpdateByFile) => {
|
||||
return http.post(`/toolbox/fail2ban/update/byconf`, param);
|
||||
return http.post(`/toolbox/fail2ban/update/byconf`, param, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ const rowName = ref('');
|
||||
const data = ref();
|
||||
const loading = ref(false);
|
||||
const paths = ref<string[]>([]);
|
||||
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300 });
|
||||
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300, showHidden: true });
|
||||
const selectRow = ref();
|
||||
const popoverVisible = ref(false);
|
||||
|
||||
@@ -196,13 +196,17 @@ const search = async (req: File.ReqFile) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
req.path = props.path;
|
||||
if (props.path != '') {
|
||||
req.path = props.path;
|
||||
}
|
||||
rowName.value = '';
|
||||
search(req);
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
req.path = props.path;
|
||||
if (props.path != '') {
|
||||
req.path = props.path;
|
||||
}
|
||||
search(req);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -56,7 +56,8 @@ const newTerm = () => {
|
||||
},
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'underline',
|
||||
scrollback: 100,
|
||||
scrollback: 1000,
|
||||
scrollSensitivity: 15,
|
||||
tabStopWidth: 4,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -142,6 +142,9 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
} else {
|
||||
baseDir.value = `${pathRes.data}/uploads/${dir}/${name.value}/`;
|
||||
}
|
||||
if (type.value === 'website') {
|
||||
baseDir.value = `${pathRes.data}/uploads/database/${type.value}/${detailName.value}/`;
|
||||
}
|
||||
upVisible.value = true;
|
||||
search();
|
||||
};
|
||||
|
||||
@@ -577,6 +577,8 @@ const message = {
|
||||
|
||||
containerFromAppHelper:
|
||||
'Detected that this container originates from the app store. App operations may cause current edits to be invalidated.',
|
||||
containerFromAppHelper1:
|
||||
'Click the `Settings` button in the installed applications list to enter the editing page and modify the container name.',
|
||||
command: 'Command',
|
||||
console: 'Console Interaction',
|
||||
tty: 'TTY (-t)',
|
||||
@@ -588,18 +590,13 @@ const message = {
|
||||
privilegedHelper:
|
||||
'Allows the container to perform certain privileged operations on the host, which may increase container risks. Use with caution!',
|
||||
|
||||
upgradeHelper: 'This operation only supports upgrading container versions.',
|
||||
ignoreCompare: 'Force Upgrade',
|
||||
ignoreCompareHelper:
|
||||
'Skip the comparison between old and new versions, upgrade directly. This operation may result in service unavailability.',
|
||||
upgradeWarning: 'The target version is lower than the original image version. Please try again!',
|
||||
upgradeHelper: 'Repository Name/Image Name: Image Version',
|
||||
upgradeWarning2:
|
||||
'The upgrade operation requires rebuilding the container, and any non-persistent data will be lost. Do you want to continue?',
|
||||
oldImage: 'Current image',
|
||||
targetImage: 'Target image',
|
||||
imageLoadErr: 'System did not detect the container image name, please manually enter the full image name:tag ',
|
||||
appHelper:
|
||||
'This container is sourced from the application store. Upgrading it may cause the service to be unavailable.',
|
||||
'The upgrade operation requires rebuilding the container, any unpersisted data will be lost. Do you wish to continue?',
|
||||
oldImage: 'Current Image',
|
||||
targetImage: 'Target Image',
|
||||
imageLoadErr: 'No image name detected for the container',
|
||||
appHelper: 'This container is sourced from the app store; upgrading might render the service unavailable',
|
||||
|
||||
input: 'Input',
|
||||
forcePull: 'forced image pull ',
|
||||
@@ -640,6 +637,7 @@ const message = {
|
||||
imagePush: 'Image push',
|
||||
imageDelete: 'Image delete',
|
||||
imageDeleteTag: 'Image tag delete',
|
||||
imageTagDeleteHelper: 'Remove other tags associated with this image ID',
|
||||
repoName: 'Repo Name',
|
||||
imageName: 'Image name',
|
||||
pull: 'Pull',
|
||||
@@ -924,7 +922,7 @@ const message = {
|
||||
'When there are multiple DNS entries, they should be displayed on new lines. e.g.\n114.114.114.114\n8.8.8.8',
|
||||
hostsHelper: 'Hostname Resolution',
|
||||
hosts: 'Domain',
|
||||
toolbox: 'Toolbox',
|
||||
toolbox: 'Quick Setup',
|
||||
hostname: 'Hostname',
|
||||
passwd: 'System Password',
|
||||
passwdHelper: 'Input characters cannot include $ and &',
|
||||
@@ -1105,6 +1103,9 @@ const message = {
|
||||
wordWrap: 'Automatically wrap',
|
||||
deleteHelper2:
|
||||
'Are you sure you want to delete the selected file? The deletion operation cannot be rolled back',
|
||||
ignoreCertificate: 'Ignore Certificate',
|
||||
ignoreCertificateHelper:
|
||||
'Ignoring untrusted certificates during downloads may lead to data leakage or tampering. Please use this option with caution, only when trusting the download source.',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
@@ -1121,8 +1122,10 @@ const message = {
|
||||
'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?',
|
||||
portHelper: 'Specifies the port number monitored by the SSH service. The default port number is 22.',
|
||||
listenAddress: 'Listening address',
|
||||
addressHelper:
|
||||
'Specify the IP address monitored by the SSH service. The default value is 0.0.0.0. That is, all network interfaces are monitored.',
|
||||
allV4V6: '0.0.0.0:{0}(IPv4) and :::{0}(IPv6)',
|
||||
listenHelper:
|
||||
'Canceling IPv4 and IPv6 settings simultaneously will listen on both 0.0.0.0:{0}(IPv4) and :::{0}(IPv6)',
|
||||
addressHelper: 'Specify the IP address on which the SSH service will listen',
|
||||
permitRootLogin: 'root user',
|
||||
rootSettingHelper: 'The default login mode is SSH for user root.',
|
||||
rootHelper1: 'Allow SSH login',
|
||||
@@ -1174,6 +1177,8 @@ const message = {
|
||||
emailHelper: 'For password retrieval',
|
||||
title: 'Panel Alias',
|
||||
panelPort: 'Panel Port',
|
||||
titleHelper:
|
||||
'Supports non-special character starting, English, Chinese, numbers, spaces, .- and _, length 3-30',
|
||||
portHelper:
|
||||
'The recommended port range is 8888 to 65535. Note: If the server has a security group, permit the new port from the security group in advance',
|
||||
portChange: 'Port change',
|
||||
@@ -1364,7 +1369,6 @@ const message = {
|
||||
rollbackHelper:
|
||||
'This recovery is about to be rolled back, which will replace all the files recovered this time. In the process, docker and 1panel services may need to be restarted. Do you want to continue?',
|
||||
|
||||
upgrading: 'Please wait while the upgrade is underway...',
|
||||
upgradeHelper: 'The upgrade requires a service restart. Do you want to continue?',
|
||||
noUpgrade: 'It is currently the latest version',
|
||||
versionHelper:
|
||||
@@ -1884,6 +1888,7 @@ const message = {
|
||||
googleHelper:
|
||||
"<a target=“_blank” href='https://cloud.google.com/certificate-manager/docs/public-ca-tutorial?hl=zh-cn'>如何取得EAB HmacKey 和EAB kid</a > ",
|
||||
googleCloudHelper: 'Google Cloud API is not accessible in most parts of mainland China',
|
||||
httpHelper2: 'Unable to apply for a generic domain name certificate in HTTP mode',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Create rule',
|
||||
@@ -1908,6 +1913,7 @@ const message = {
|
||||
unUsed: 'Unused',
|
||||
firewallHelper: '{0} System firewall',
|
||||
firewallNotStart: 'The system firewall is not enabled at present, please enable it first!',
|
||||
restartFirewallHelper: 'This operation will restart the current firewall. Do you want to continue?',
|
||||
stopFirewallHelper:
|
||||
'After the system firewall is disabled, the server loses security protection. Do you want to continue?',
|
||||
startFirewallHelper:
|
||||
|
||||
@@ -561,6 +561,7 @@ const message = {
|
||||
cpuShareHelper: '容器默認份額為 1024 個 CPU,增大可使當前容器獲得更多的 CPU 時間',
|
||||
|
||||
containerFromAppHelper: '檢測到該容器來源於應用商店,應用操作可能會導致當前編輯失效',
|
||||
containerFromAppHelper1: '在已安裝應用程式列表點擊 `參數` 按鈕,進入編輯頁面即可修改容器名稱。',
|
||||
command: '命令',
|
||||
console: '控製臺交互',
|
||||
tty: '偽終端 ( -t )',
|
||||
@@ -571,14 +572,11 @@ const message = {
|
||||
privileged: '特權模式',
|
||||
privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!',
|
||||
|
||||
upgradeHelper: '該操作僅支持容器版本升級',
|
||||
ignoreCompare: '強製升級',
|
||||
ignoreCompareHelper: '跳過新老版本比較,直接升級,該操作可能導致服務不可用',
|
||||
upgradeWarning: '當前目標版本低於原鏡像版本,請重新輸入!',
|
||||
upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本',
|
||||
upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
||||
oldImage: '當前鏡像',
|
||||
targetImage: '目標鏡像',
|
||||
imageLoadErr: '系統未檢測到容器的鏡像名稱,請手動輸入完整的鏡像名稱:標籤',
|
||||
imageLoadErr: '未檢測到容器的鏡像名稱',
|
||||
appHelper: '該容器來源於應用商店,升級可能導致該服務不可用',
|
||||
|
||||
input: '手動輸入',
|
||||
@@ -620,6 +618,7 @@ const message = {
|
||||
imagePush: '推送鏡像',
|
||||
imageDelete: '刪除鏡像',
|
||||
imageDeleteTag: '刪除 Tag',
|
||||
imageTagDeleteHelper: '移除與該映像 ID 相關聯的其他標籤',
|
||||
repoName: '倉庫名',
|
||||
imageName: '鏡像名',
|
||||
httpRepo: 'http 倉庫添加授信需要重啟 docker 服務',
|
||||
@@ -878,7 +877,7 @@ const message = {
|
||||
dnsHelper1: '當存在多個DNS時,需換行顯示,例:\n114.114.114.114\n8.8.8.8',
|
||||
hostsHelper: '主機名解析',
|
||||
hosts: '域名',
|
||||
toolbox: '工具箱',
|
||||
toolbox: '快速設置',
|
||||
hostname: '主機名',
|
||||
passwd: '系統密碼',
|
||||
passwdHelper: '輸入的字符不能包含 $ 和 &',
|
||||
@@ -1051,6 +1050,9 @@ const message = {
|
||||
fileRecycleBinMsg: '已{0}回收站',
|
||||
wordWrap: '自動換行',
|
||||
deleteHelper2: '確定刪除所選檔案? 刪除操作不可回滾',
|
||||
ignoreCertificate: '忽略不可信證書',
|
||||
ignoreCertificateHelper:
|
||||
'下載時忽略不可信證書可能導致數據洩露或篡改。請謹慎使用此選項,僅在信任下載源的情況下啟用',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
@@ -1065,7 +1067,9 @@ const message = {
|
||||
port: '連接端口',
|
||||
portHelper: '指定 SSH 服務監聽的端口號,默認為 22。',
|
||||
listenAddress: '監聽地址',
|
||||
addressHelper: '指定 SSH 服務監聽的 IP 地址,默認為 0.0.0.0,即所有的網絡接口都會被監聽。',
|
||||
allV4V6: '0.0.0.0:{0}(IPv4)和 :::{0}(IPv6)',
|
||||
listenHelper: '同時取消 IPv4 和 IPv6 設置,將會同時監聽 0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)',
|
||||
addressHelper: '指定 SSH 服務監聽的 IP 地址',
|
||||
permitRootLogin: 'root 用戶',
|
||||
rootSettingHelper: 'root 用戶 SSH 登錄方式,默認所有 SSH 登錄。',
|
||||
rootHelper1: '允許 SSH 登錄',
|
||||
@@ -1115,6 +1119,7 @@ const message = {
|
||||
emailHelper: '用於密碼找回',
|
||||
title: '面板別名',
|
||||
panelPort: '面板端口',
|
||||
titleHelper: '支援非特殊字符開頭,英文、中文、數字、空格、.-和_, 長度3-30',
|
||||
portHelper: '建議端口範圍8888 - 65535,註意:有安全組的服務器請提前在安全組放行新端口',
|
||||
portChange: '端口修改',
|
||||
portChangeHelper: '服務端口修改需要重啟服務,是否繼續?',
|
||||
@@ -1767,6 +1772,7 @@ const message = {
|
||||
googleHelper:
|
||||
"<a target=“_blank” href='https://cloud.google.com/certificate-manager/docs/public-ca-tutorial?hl=zh-cn'>如何取得EAB HmacKey 和EAB kid</a > ",
|
||||
googleCloudHelper: 'Google Cloud API 大陸大部分地區無法存取',
|
||||
httpHelper2: 'HTTP 模式無法申請泛網域憑證',
|
||||
},
|
||||
firewall: {
|
||||
create: '創建規則',
|
||||
@@ -1789,6 +1795,7 @@ const message = {
|
||||
unUsed: '未使用',
|
||||
firewallHelper: '{0}系統防火墻',
|
||||
firewallNotStart: '當前未開啟系統防火墻,請先開啟!',
|
||||
restartFirewallHelper: '該操作將對當前防火牆進行重啟操作,是否繼續?',
|
||||
stopFirewallHelper: '系統防火墻關閉後,服務器將失去安全防護,是否繼續?',
|
||||
startFirewallHelper: '系統防火墻開啟後,可以更好的防護服務器安全,是否繼續?',
|
||||
noPing: '禁 ping',
|
||||
|
||||
@@ -562,6 +562,7 @@ const message = {
|
||||
cpuShareHelper: '容器默认份额为 1024 个 CPU,增大可使当前容器获得更多的 CPU 时间',
|
||||
|
||||
containerFromAppHelper: '检测到该容器来源于应用商店,应用操作可能会导致当前编辑失效',
|
||||
containerFromAppHelper1: '在已安装应用列表点击 `参数` 按钮,进入编辑页面即可修改容器名称。',
|
||||
command: '命令',
|
||||
console: '控制台交互',
|
||||
tty: '伪终端 ( -t )',
|
||||
@@ -572,14 +573,11 @@ const message = {
|
||||
privileged: '特权模式',
|
||||
privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!',
|
||||
|
||||
upgradeHelper: '该操作仅支持容器版本升级',
|
||||
ignoreCompare: '强制升级',
|
||||
ignoreCompareHelper: '跳过新老版本比较,直接升级,该操作可能导致服务不可用',
|
||||
upgradeWarning: '当前目标版本低于原镜像版本,请重新输入!',
|
||||
upgradeHelper: '仓库名称/镜像名称:镜像版本',
|
||||
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||
oldImage: '当前镜像',
|
||||
targetImage: '目标镜像',
|
||||
imageLoadErr: '系统未检测到容器的镜像名称,请手动输入完整的镜像名称:标签',
|
||||
imageLoadErr: '未检测到容器的镜像名称',
|
||||
appHelper: '该容器来源于应用商店,升级可能导致该服务不可用',
|
||||
|
||||
input: '手动输入',
|
||||
@@ -621,6 +619,7 @@ const message = {
|
||||
imagePush: '推送镜像',
|
||||
imageDelete: '删除镜像',
|
||||
imageDeleteTag: '删除 Tag',
|
||||
imageTagDeleteHelper: '移除与该镜像 ID 相关联的其他标签',
|
||||
repoName: '仓库名',
|
||||
imageName: '镜像名',
|
||||
httpRepo: 'http 仓库添加授信需要重启 docker 服务',
|
||||
@@ -879,7 +878,7 @@ const message = {
|
||||
dnsHelper1: '当存在多个 DNS 时,需换行显示,例:\n114.114.114.114\n8.8.8.8',
|
||||
hostsHelper: '主机名解析',
|
||||
hosts: '域名',
|
||||
toolbox: '工具箱',
|
||||
toolbox: '快速设置',
|
||||
hostname: '主机名',
|
||||
passwd: '系统密码',
|
||||
passwdHelper: '输入字符不能包含 $ 和 &',
|
||||
@@ -1052,6 +1051,9 @@ const message = {
|
||||
fileRecycleBinMsg: '已{0}回收站',
|
||||
wordWrap: '自动换行',
|
||||
deleteHelper2: '确定删除所选文件?删除操作不可回滚',
|
||||
ignoreCertificate: '忽略不可信证书',
|
||||
ignoreCertificateHelper:
|
||||
'下载时忽略不可信证书可能导致数据泄露或篡改。请谨慎使用此选项,仅在信任下载源的情况下启用',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
@@ -1066,7 +1068,9 @@ const message = {
|
||||
port: '连接端口',
|
||||
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
|
||||
listenAddress: '监听地址',
|
||||
addressHelper: '指定 SSH 服务监听的 IP 地址,默认为 0.0.0.0,即所有的网络接口都会被监听。',
|
||||
allV4V6: '0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)',
|
||||
listenHelper: '同时取消 IPv4 和 IPv6 设置,将会同时监听 0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)',
|
||||
addressHelper: '指定 SSH 服务监听的 IP 地址',
|
||||
permitRootLogin: 'root 用户',
|
||||
rootSettingHelper: 'root 用户 SSH 登录方式,默认所有 SSH 登录。',
|
||||
rootHelper1: '允许 SSH 登录',
|
||||
@@ -1115,6 +1119,7 @@ const message = {
|
||||
passwd: '面板密码',
|
||||
emailHelper: '用于密码找回',
|
||||
title: '面板别名',
|
||||
titleHelper: '支持非特殊字符开头,英文、中文、数字、空格、.-和_,长度3-30',
|
||||
panelPort: '面板端口',
|
||||
portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口',
|
||||
portChange: '端口修改',
|
||||
@@ -1767,6 +1772,7 @@ const message = {
|
||||
googleHelper:
|
||||
"<a target=“_blank” href='https://cloud.google.com/certificate-manager/docs/public-ca-tutorial?hl=zh-cn'>如何获取 EAB HmacKey 和 EAB kid</a> ",
|
||||
googleCloudHelper: 'Google Cloud API 大陆大部分地区无法访问',
|
||||
httpHelper2: 'HTTP 模式无法申请泛域名证书',
|
||||
},
|
||||
firewall: {
|
||||
create: '创建规则',
|
||||
@@ -1789,6 +1795,7 @@ const message = {
|
||||
unUsed: '未使用',
|
||||
firewallHelper: '{0}系统防火墙',
|
||||
firewallNotStart: '当前未开启系统防火墙,请先开启!',
|
||||
restartFirewallHelper: '该操作将对当前防火墙进行重启操作,是否继续?',
|
||||
stopFirewallHelper: '系统防火墙关闭后,服务器将失去安全防护,是否继续?',
|
||||
startFirewallHelper: '系统防火墙开启后,可以更好的防护服务器安全,是否继续?',
|
||||
noPing: '禁 ping',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const cronRouter = {
|
||||
sort: 6,
|
||||
sort: 8,
|
||||
path: '/cronjobs',
|
||||
component: Layout,
|
||||
redirect: '/cronjobs',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const hostRouter = {
|
||||
sort: 7,
|
||||
sort: 6,
|
||||
path: '/hosts',
|
||||
component: Layout,
|
||||
redirect: '/hosts/security',
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
placeholder="#Define or paste the content of your docker-compose file here"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 375px)"
|
||||
style="width: 100%; height: calc(100vh - 376px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
|
||||
@@ -27,12 +27,30 @@
|
||||
<el-alert
|
||||
v-if="dialogData.title === 'edit' && isFromApp(dialogData.rowData!)"
|
||||
:title="$t('container.containerFromAppHelper')"
|
||||
class="common-prompt"
|
||||
:closable="false"
|
||||
type="error"
|
||||
/>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.name" />
|
||||
<el-form-item class="mt-5" :label="$t('commons.table.name')" prop="name">
|
||||
<el-input
|
||||
:disabled="isFromApp(dialogData.rowData!)"
|
||||
clearable
|
||||
v-model.trim="dialogData.rowData!.name"
|
||||
/>
|
||||
<div v-if="dialogData.title === 'edit' && isFromApp(dialogData.rowData!)">
|
||||
<span class="input-help">
|
||||
{{ $t('container.containerFromAppHelper1') }}
|
||||
<el-button
|
||||
style="margin-left: -5px"
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
@click="goRouter()"
|
||||
>
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.image')" prop="image">
|
||||
<el-checkbox v-model="dialogData.rowData!.imageInput" :label="$t('container.input')" />
|
||||
@@ -278,6 +296,7 @@ import {
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { checkIpV4V6, checkPort } from '@/utils/util';
|
||||
import router from '@/routers';
|
||||
|
||||
const loading = ref(false);
|
||||
interface DialogProps {
|
||||
@@ -374,6 +393,10 @@ const handlePortsDelete = (index: number) => {
|
||||
dialogData.value.rowData!.exposedPorts.splice(index, 1);
|
||||
};
|
||||
|
||||
const goRouter = async () => {
|
||||
router.push({ name: 'AppInstalled' });
|
||||
};
|
||||
|
||||
const handleVolumesAdd = () => {
|
||||
let item = {
|
||||
sourceDir: '',
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('commons.button.upgrade')" :resource="form.name" :back="handleClose" />
|
||||
<DrawerHeader :header="$t('commons.button.upgrade')" :resource="form.containerName" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-alert
|
||||
v-if="form.fromApp"
|
||||
:title="$t('container.appHelper')"
|
||||
v-if="form.fromApp"
|
||||
class="common-prompt"
|
||||
:closable="false"
|
||||
type="error"
|
||||
@@ -26,30 +26,14 @@
|
||||
</el-form-item>
|
||||
<el-form-item prop="newImageName" :rules="Rules.imageName">
|
||||
<template #label>
|
||||
<el-tooltip
|
||||
placement="top-start"
|
||||
:content="form.imageHelper"
|
||||
v-if="form.imageHelper.length > 40"
|
||||
>
|
||||
<span>
|
||||
{{
|
||||
$t('container.targetImage') + ' (' + form.imageHelper.substring(0, 40) + '...)'
|
||||
}}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
{{ $t('container.targetImage') + ' (' + form.imageHelper + ')' }}
|
||||
{{ $t('container.targetImage') }}
|
||||
<span v-if="!form.hasName">
|
||||
{{ ' (' + $t('container.imageLoadErr') + ')' }}
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.newImageName" :placeholder="$t('container.imageNameHelper')" />
|
||||
<el-input v-model="form.newImageName" />
|
||||
<span class="input-help">{{ $t('container.upgradeHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ignoreCompare">
|
||||
<el-checkbox v-model="form.ignoreCompare">
|
||||
{{ $t('container.ignoreCompare') }}
|
||||
</el-checkbox>
|
||||
<span class="input-help">{{ $t('container.ignoreCompareHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="forcePull">
|
||||
<el-checkbox v-model="form.forcePull">
|
||||
{{ $t('container.forcePull') }}
|
||||
@@ -76,7 +60,7 @@
|
||||
import { upgradeContainer } from '@/api/modules/container';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
@@ -84,14 +68,13 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
const loading = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
containerName: '',
|
||||
oldImageName: '',
|
||||
newImageName: '',
|
||||
imageHelper: '',
|
||||
hasName: true,
|
||||
|
||||
fromApp: false,
|
||||
forcePull: false,
|
||||
|
||||
ignoreCompare: false,
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
@@ -105,17 +88,15 @@ interface DialogProps {
|
||||
fromApp: boolean;
|
||||
}
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
form.name = props.container;
|
||||
form.containerName = props.container;
|
||||
form.oldImageName = props.image;
|
||||
form.fromApp = props.fromApp;
|
||||
form.ignoreCompare = false;
|
||||
|
||||
if (props.image.indexOf('sha256:') !== -1) {
|
||||
form.imageHelper = i18n.global.t('container.imageLoadErr');
|
||||
drawerVisible.value = true;
|
||||
return;
|
||||
form.hasName = props.image.indexOf('sha256:') === -1;
|
||||
if (form.hasName) {
|
||||
form.newImageName = props.image;
|
||||
} else {
|
||||
form.newImageName = '';
|
||||
}
|
||||
form.imageHelper = props.image.indexOf(':') !== -1 ? props.image.split(':')[0] : props.image;
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
@@ -124,16 +105,12 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (!form.ignoreCompare && !compareVersion(form.newImageName, form.oldImageName)) {
|
||||
MsgWarning(i18n.global.t('container.upgradeWarning'));
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm(i18n.global.t('container.upgradeWarning2'), i18n.global.t('commons.button.upgrade'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await upgradeContainer(form.name, form.newImageName, form.forcePull)
|
||||
await upgradeContainer(form.containerName, form.newImageName, form.forcePull)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
@@ -151,41 +128,6 @@ const handleClose = async () => {
|
||||
emit('search');
|
||||
};
|
||||
|
||||
function compareVersion(vNew, vOld) {
|
||||
let newImageName = vNew.indexOf(':') !== -1 ? vNew.split(':')[0] : vNew;
|
||||
let oldImageName = vOld.indexOf(':') !== -1 ? vOld.split(':')[0] : vOld;
|
||||
if (newImageName !== oldImageName) {
|
||||
return true;
|
||||
}
|
||||
let newTag = vNew.indexOf(':') !== -1 ? vNew.split(':')[1] : 'latest';
|
||||
|
||||
if (newTag === 'latest') {
|
||||
return true;
|
||||
}
|
||||
let oldTag = vOld.indexOf(':') !== -1 ? vOld.split(':')[1] : 'latest';
|
||||
let v1 = newTag
|
||||
.replace('-', '.')
|
||||
.replace(/[^\d.]/g, '')
|
||||
.split('.');
|
||||
let v2 = oldTag
|
||||
.replace('-', '.')
|
||||
.replace(/[^\d.]/g, '')
|
||||
.split('.');
|
||||
|
||||
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
|
||||
let num1 = parseInt(v1[i] || 0);
|
||||
let num2 = parseInt(v2[i] || 0);
|
||||
|
||||
if (num1 > num2) {
|
||||
return true;
|
||||
} else if (num1 < num2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ $t('container.removeAll') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<el-checkbox-group v-model="form.deleteTags" @change="handleCheckedCitiesChange">
|
||||
<el-checkbox-group v-model="form.deleteTags" @change="handleCheckedChange">
|
||||
<div>
|
||||
<el-checkbox
|
||||
style="width: 100%"
|
||||
@@ -91,7 +91,7 @@ const handleCheckAllChange = (val: boolean) => {
|
||||
form.deleteTags = val ? form.tags : [];
|
||||
isIndeterminate.value = false;
|
||||
};
|
||||
const handleCheckedCitiesChange = (value: string[]) => {
|
||||
const handleCheckedChange = (value: string[]) => {
|
||||
const checkedCount = value.length;
|
||||
deleteAll.value = checkedCount === form.tags.length;
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < form.tags.length;
|
||||
|
||||
@@ -243,9 +243,9 @@ const buttons = [
|
||||
label: i18n.global.t('container.tag'),
|
||||
click: (row: Container.ImageInfo) => {
|
||||
let params = {
|
||||
itemName: row.tags && row.tags?.length !== 0 ? row.tags[0].split(':')[0] : '',
|
||||
repos: repos.value,
|
||||
sourceID: row.id,
|
||||
imageID: row.id,
|
||||
tags: row.tags,
|
||||
};
|
||||
dialogTagRef.value!.acceptParams(params);
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
:rules="Rules.requiredSelect"
|
||||
prop="repoID"
|
||||
>
|
||||
<el-select style="width: 100%" filterable v-model="form.repoID">
|
||||
<el-select clearable style="width: 100%" filterable v-model="form.repoID">
|
||||
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -36,7 +36,7 @@
|
||||
:config="logConfig"
|
||||
:default-button="false"
|
||||
v-if="showLog"
|
||||
:style="'height: calc(100vh - 370px);min-height: 200px'"
|
||||
:style="'height: calc(100vh - 397px);min-height: 200px'"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.repoName')" :rules="Rules.requiredSelect" prop="repoID">
|
||||
<el-select style="width: 100%" filterable v-model="form.repoID">
|
||||
<el-select clearable style="width: 100%" filterable v-model="form.repoID">
|
||||
<el-option
|
||||
v-for="item in dialogData.repos"
|
||||
:key="item.id"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('container.imageTag')" :resource="form.itemName" :back="handleClose" />
|
||||
<DrawerHeader :header="$t('container.imageTag')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form v-loading="loading" label-position="top" ref="formRef" :model="form" label-width="80px">
|
||||
<el-row type="flex" justify="center">
|
||||
@@ -13,16 +13,29 @@
|
||||
v-if="form.fromRepo"
|
||||
:label="$t('container.repoName')"
|
||||
:rules="Rules.requiredSelect"
|
||||
prop="repoID"
|
||||
prop="repo"
|
||||
>
|
||||
<el-select style="width: 100%" filterable v-model="form.repoID">
|
||||
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
|
||||
<el-select style="width: 100%" clearable filterable v-model="form.repo" @change="changeRepo">
|
||||
<el-option v-for="item in repos" :key="item.id" :value="item.name" :label="item.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.imageName')" :rules="Rules.imageName" prop="targetName">
|
||||
<el-input v-model="form.targetName">
|
||||
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||
</el-input>
|
||||
<el-form-item :label="$t('container.imageTag')" :rules="Rules.imageName" prop="targetName">
|
||||
<el-input v-model="form.targetName" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-checkbox style="width: 100%" v-model="form.deleteTag">
|
||||
{{ $t('container.imageTagDeleteHelper') }}
|
||||
</el-checkbox>
|
||||
<el-checkbox-group class="ml-5" v-if="form.deleteTag" v-model="form.deleteTags">
|
||||
<el-checkbox
|
||||
style="width: 100%"
|
||||
v-for="item in tags"
|
||||
:key="item"
|
||||
:value="item"
|
||||
:label="item"
|
||||
/>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -46,7 +59,7 @@ import { reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { imageTag } from '@/api/modules/container';
|
||||
import { imageRemove, imageTag } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -55,28 +68,35 @@ const loading = ref(false);
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const repos = ref();
|
||||
const tags = ref();
|
||||
const form = reactive({
|
||||
itemName: '',
|
||||
sourceID: '',
|
||||
fromRepo: true,
|
||||
repoID: 1,
|
||||
imageID: '',
|
||||
fromRepo: false,
|
||||
repo: '',
|
||||
originName: '',
|
||||
targetName: '',
|
||||
|
||||
deleteTag: false,
|
||||
deleteTags: [],
|
||||
});
|
||||
|
||||
interface DialogProps {
|
||||
itemName: string;
|
||||
repos: Array<Container.RepoOptions>;
|
||||
sourceID: string;
|
||||
imageID: string;
|
||||
tags: Array<string>;
|
||||
}
|
||||
|
||||
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
drawerVisible.value = true;
|
||||
form.repoID = 1;
|
||||
form.itemName = params.itemName;
|
||||
form.sourceID = params.sourceID;
|
||||
form.targetName = '';
|
||||
form.fromRepo = true;
|
||||
form.imageID = params.imageID;
|
||||
form.originName = params.tags?.length !== 0 ? params.tags[0] : '';
|
||||
form.targetName = params.tags?.length !== 0 ? params.tags[0] : '';
|
||||
form.fromRepo = false;
|
||||
form.repo = '';
|
||||
form.deleteTag = false;
|
||||
form.deleteTags = [];
|
||||
repos.value = params.repos;
|
||||
tags.value = params.tags;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
@@ -91,13 +111,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (!form.fromRepo) {
|
||||
form.repoID = 0;
|
||||
}
|
||||
let params = {
|
||||
sourceID: form.imageID,
|
||||
targetName: form.targetName,
|
||||
};
|
||||
loading.value = true;
|
||||
await imageTag(form)
|
||||
.then(() => {
|
||||
await imageTag(params)
|
||||
.then(async () => {
|
||||
loading.value = false;
|
||||
if (form.deleteTag && form.deleteTags.length !== 0) {
|
||||
await imageRemove({ names: form.deleteTags });
|
||||
}
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
@@ -108,14 +132,18 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
});
|
||||
};
|
||||
|
||||
function loadDetailInfo(id: number) {
|
||||
const changeRepo = (val) => {
|
||||
if (val === 'Docker Hub') {
|
||||
form.targetName = form.originName;
|
||||
return;
|
||||
}
|
||||
for (const item of repos.value) {
|
||||
if (item.id === id) {
|
||||
return item.downloadUrl;
|
||||
if (item.name == val) {
|
||||
form.targetName = item.downloadUrl + '/' + form.originName;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
|
||||
@@ -150,7 +150,7 @@ import { ElForm } from 'element-plus';
|
||||
import { createNetwork } from '@/api/modules/container';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { checkIpV6 } from '@/utils/util';
|
||||
import { checkIp, checkIpV6 } from '@/utils/util';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -214,9 +214,7 @@ function checkGateway(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
}
|
||||
const reg =
|
||||
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
|
||||
if (!reg.test(value) && value !== '') {
|
||||
if (checkIp(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
|
||||
}
|
||||
callback();
|
||||
@@ -225,27 +223,11 @@ function checkGateway(rule: any, value: any, callback: any) {
|
||||
function checkGatewayV6(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
} else {
|
||||
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
|
||||
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
|
||||
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
|
||||
const IPv6AddressRegExp = new RegExp(
|
||||
'^(' +
|
||||
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
|
||||
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
|
||||
')(%[0-9a-zA-Z-.:]{1,})?$',
|
||||
);
|
||||
if (!IPv6AddressRegExp.test(value) && value !== '') {
|
||||
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
if (checkIpV6(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function checkCidr(rule: any, value: any, callback: any) {
|
||||
|
||||
@@ -70,6 +70,9 @@ function checkMirrors(rule: any, value: any, callback: any) {
|
||||
const reg = /^https?:\/\/[a-zA-Z0-9.-]+$/;
|
||||
let mirrors = form.mirrors.split('\n');
|
||||
for (const item of mirrors) {
|
||||
if (item === '') {
|
||||
continue;
|
||||
}
|
||||
if (!reg.test(item)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.mirror')));
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ function checkRegistries(rule: any, value: any, callback: any) {
|
||||
const reg = /^[a-zA-Z0-9]{1}[a-z:A-Z0-9_/.-]{0,150}$/;
|
||||
let regis = form.registries.split('\n');
|
||||
for (const item of regis) {
|
||||
if (item === '') {
|
||||
continue;
|
||||
}
|
||||
if (!reg.test(item)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
{{ $t('home.free') }}: {{ formatNumber(currentInfo.swapMemoryAvailable / 1024 / 1024) }} MB
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.swapMemoryUsedPercent * 100) }}%
|
||||
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.swapMemoryUsedPercent) }}%
|
||||
</el-tag>
|
||||
</div>
|
||||
<template #reference>
|
||||
|
||||
@@ -94,6 +94,7 @@ import i18n from '@/lang';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Close } from '@element-plus/icons-vue';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
|
||||
interface UploadFileProps {
|
||||
path: string;
|
||||
@@ -235,7 +236,7 @@ const submit = async () => {
|
||||
const fileSize = file.size;
|
||||
|
||||
uploadHelper.value = i18n.global.t('file.fileUploadStart', [file.name]);
|
||||
if (fileSize <= 1024 * 1024 * 10) {
|
||||
if (fileSize <= 1024 * 1024 * 5) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.raw);
|
||||
if (file.raw.webkitRelativePath != '') {
|
||||
@@ -254,7 +255,7 @@ const submit = async () => {
|
||||
success++;
|
||||
uploaderFiles.value[i].status = 'success';
|
||||
} else {
|
||||
const CHUNK_SIZE = 1024 * 1024 * 10;
|
||||
const CHUNK_SIZE = 1024 * 1024 * 5;
|
||||
const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
let uploadedChunkCount = 0;
|
||||
for (let c = 0; c < chunkCount; c++) {
|
||||
@@ -281,7 +282,7 @@ const submit = async () => {
|
||||
);
|
||||
uploadPrecent.value = progress;
|
||||
},
|
||||
timeout: 40000,
|
||||
timeout: TimeoutEnum.T_60S,
|
||||
});
|
||||
uploadedChunkCount++;
|
||||
} catch (error) {
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="addForm.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="addForm.ignoreCertificate">
|
||||
{{ $t('file.ignoreCertificate') }}
|
||||
</el-checkbox>
|
||||
<span class="input-help">{{ $t('file.ignoreCertificateHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -67,6 +73,7 @@ const addForm = reactive({
|
||||
url: '',
|
||||
path: '',
|
||||
name: '',
|
||||
ignoreCertificate: false,
|
||||
});
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
@@ -111,6 +118,7 @@ const acceptParams = (props: WgetProps) => {
|
||||
addForm.path = props.path;
|
||||
open.value = true;
|
||||
submitData.value = false;
|
||||
addForm.ignoreCertificate = false;
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
<span v-if="onPing !== 'None'">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
|
||||
@@ -73,6 +77,8 @@ const loadBaseInfo = async (search: boolean) => {
|
||||
})
|
||||
.catch(() => {
|
||||
emit('update:loading', false);
|
||||
emit('update:maskShow', true);
|
||||
emit('update:name', '-');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('menu.home')" :back="handleClose" />
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<el-form label-position="top" class="ml-5">
|
||||
<el-row type="flex" justify="center" :gutter="20">
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('ssh.successful') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ successfulTotalCount }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('ssh.failed') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ failedTotalCount }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<el-button type="primary" @click="onChangeStatus('accept', null)" :disabled="selects.length === 0">
|
||||
{{ $t('firewall.allow') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onChangeStatus('drop', null)" :disabled="selects.length === 0">
|
||||
{{ $t('firewall.deny') }}
|
||||
</el-button>
|
||||
<ComplexTable v-model:selects="selects" class="mt-5" :data="data" @header-click="changeSort">
|
||||
<el-table-column type="selection" fix :selectable="selectable" />
|
||||
<el-table-column :label="$t('logs.loginIP')" prop="address" min-width="40" />
|
||||
<el-table-column :label="$t('ssh.belong')" prop="area" min-width="40" />
|
||||
<el-table-column prop="successfulCount" min-width="20">
|
||||
<template #header>
|
||||
{{ $t('ssh.successful') }}
|
||||
<el-icon style="cursor: pointer" @click="(orderBy = 'Success') && search()">
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link>{{ row.successfulCount }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="failedCount" min-width="20">
|
||||
<template #header>
|
||||
{{ $t('ssh.failed') }}
|
||||
<el-icon style="cursor: pointer" @click="(orderBy = 'Failed') && search()">
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-button type="danger" link>{{ row.failedCount }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :min-width="30" :label="$t('commons.table.status')" prop="strategy">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 'accept'"
|
||||
:disabled="!selectable(row)"
|
||||
@click="onChangeStatus('drop', row)"
|
||||
link
|
||||
type="success"
|
||||
>
|
||||
{{ $t('commons.status.accept') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
link
|
||||
:disabled="!selectable(row)"
|
||||
type="danger"
|
||||
@click="onChangeStatus('accept', row)"
|
||||
>
|
||||
{{ $t('commons.status.deny') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ComplexTable>
|
||||
<fu-table-pagination
|
||||
class="float-right mt-4"
|
||||
v-model:current-page="paginationConfig.currentPage"
|
||||
v-model:page-size="paginationConfig.pageSize"
|
||||
v-bind="paginationConfig"
|
||||
@change="search()"
|
||||
:layout="'prev, pager, next'"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('firewall.' + (operation === 'drop' ? 'deny' : 'allow'))"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-alert :title="msg" show-icon type="error" :closable="false"></el-alert>
|
||||
<div class="resource">
|
||||
<ul v-for="(row, index) in operationList" :key="index">
|
||||
<li>
|
||||
<span>{{ row }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitOperation" v-loading="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { loadAnalysis, operateIPRule } from '@/api/modules/host';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
import { Host } from '@/api/interface/host';
|
||||
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
const data = ref();
|
||||
const successfulTotalCount = ref();
|
||||
const failedTotalCount = ref();
|
||||
const selects = ref<any>([]);
|
||||
const orderBy = ref<string>('Failed');
|
||||
|
||||
const dialogVisible = ref();
|
||||
const msg = ref();
|
||||
const operation = ref();
|
||||
const operationList = ref();
|
||||
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const acceptParams = (): void => {
|
||||
search();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
orderBy: orderBy.value,
|
||||
};
|
||||
loading.value = true;
|
||||
loadAnalysis(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
data.value = res.data.items || [];
|
||||
successfulTotalCount.value = res.data.successfulCount;
|
||||
failedTotalCount.value = res.data.failedCount;
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
function selectable(row: any): boolean {
|
||||
return row.address !== '127.0.0.1' && row.address !== '::1';
|
||||
}
|
||||
|
||||
const changeSort = (column: any) => {
|
||||
switch (column.property) {
|
||||
case 'successfulCount':
|
||||
orderBy.value = 'Success';
|
||||
search();
|
||||
return;
|
||||
case 'failedCount':
|
||||
orderBy.value = 'Failed';
|
||||
search();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeStatus = async (status: string, row: Host.logAnalysis | null) => {
|
||||
operationList.value = [];
|
||||
if (row) {
|
||||
if (row.status !== status) {
|
||||
operationList.value.push(row.address);
|
||||
}
|
||||
} else {
|
||||
for (const item of selects.value) {
|
||||
if (item.status !== status) {
|
||||
operationList.value.push(item.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (operationList.value.length === 0) {
|
||||
MsgError(
|
||||
i18n.global.t('ssh.noAddrWarning', [i18n.global.t('firewall.' + (status === 'drop' ? 'deny' : 'allow'))]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
operation.value = status;
|
||||
msg.value = status === 'drop' ? i18n.global.t('ssh.denyHelper') : i18n.global.t('ssh.acceptHelper');
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const submitOperation = async () => {
|
||||
loading.value = true;
|
||||
operateIPRule({
|
||||
operation: operation.value === 'drop' ? 'add' : 'remove',
|
||||
address: operationList.value.join(','),
|
||||
strategy: 'drop',
|
||||
description: '',
|
||||
})
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
dialogVisible.value = false;
|
||||
search();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.resource {
|
||||
margin-top: 10px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -10,15 +10,40 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('ssh.listenAddress')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
@submit.prevent
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item
|
||||
:label="$t('ssh.listenAddress')"
|
||||
prop="listenAddress"
|
||||
:rules="Rules.ipV4V6OrDomain"
|
||||
>
|
||||
<el-input clearable v-model="form.listenAddress" />
|
||||
<el-alert class="common-prompt" :closable="false" type="error">
|
||||
<template #default>
|
||||
<span>
|
||||
{{ $t('ssh.listenHelper', [form.port]) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form-item label="IPv4" prop="listenAddressV4">
|
||||
<el-checkbox
|
||||
v-model="form.ipv4All"
|
||||
@change="form.listenAddressV4 = form.ipv4All ? '0.0.0.0' : form.listenAddressV4"
|
||||
>
|
||||
{{ $t('setting.bindAll') }}
|
||||
</el-checkbox>
|
||||
<el-input :disabled="form.ipv4All" clearable v-model="form.listenAddressV4"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="IPv6" prop="listenAddressV6">
|
||||
<el-checkbox
|
||||
v-model="form.ipv6All"
|
||||
@change="form.listenAddressV6 = form.ipv6All ? '::' : form.listenAddressV6"
|
||||
>
|
||||
{{ $t('setting.bindAll') }}
|
||||
</el-checkbox>
|
||||
<el-input :disabled="form.ipv6All" clearable v-model="form.listenAddressV6"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -39,26 +64,66 @@ import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { updateSSH } from '@/api/modules/host';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { checkIp, checkIpV6 } from '@/utils/util';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
interface DialogProps {
|
||||
port: number;
|
||||
address: string;
|
||||
}
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
|
||||
const form = reactive({
|
||||
listenAddress: '0.0.0.0',
|
||||
port: 22,
|
||||
ipv4All: false,
|
||||
ipv6All: false,
|
||||
listenAddressV4: '',
|
||||
listenAddressV6: '',
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
listenAddressV4: [{ validator: checkIPv4, trigger: 'blur' }],
|
||||
listenAddressV6: [{ validator: checkIPv6, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
function checkIPv4(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
}
|
||||
if (checkIp(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.ip')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function checkIPv6(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
}
|
||||
if (checkIpV6(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.ip')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
form.listenAddress = params.address;
|
||||
let items = params.address.split(',');
|
||||
for (const item of items) {
|
||||
if (item.indexOf(':') !== -1) {
|
||||
form.listenAddressV6 = item;
|
||||
form.ipv6All = item === '::';
|
||||
continue;
|
||||
}
|
||||
form.listenAddressV4 = item;
|
||||
form.ipv4All = item === '0.0.0.0';
|
||||
}
|
||||
form.port = params.port;
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
@@ -66,8 +131,19 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let itemAddr = [];
|
||||
if (form.listenAddressV4 !== '') {
|
||||
itemAddr.push(form.listenAddressV4);
|
||||
}
|
||||
if (form.listenAddressV6 !== '') {
|
||||
itemAddr.push(form.listenAddressV6);
|
||||
}
|
||||
let addr =
|
||||
itemAddr.join(',') === '' || itemAddr.join(',') === '0.0.0.0,::'
|
||||
? i18n.global.t('ssh.allV4V6', [form.port])
|
||||
: itemAddr.join(',');
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.listenAddress'), form.listenAddress]),
|
||||
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.listenAddress'), addr]),
|
||||
i18n.global.t('ssh.sshChange'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
@@ -79,7 +155,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
let params = {
|
||||
key: 'ListenAddress',
|
||||
oldValue: '',
|
||||
newValue: form.listenAddress,
|
||||
newValue: itemAddr.join(','),
|
||||
};
|
||||
loading.value = true;
|
||||
await updateSSH(params)
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<span class="input-help">{{ $t('ssh.portHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress">
|
||||
<el-input disabled v-model="form.listenAddress">
|
||||
<el-input disabled v-model="form.listenAddressItem">
|
||||
<template #append>
|
||||
<el-button @click="onChangeAddress" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
@@ -130,7 +130,7 @@
|
||||
placeholder="# The SSH configuration file does not exist or is empty (/etc/ssh/sshd_config)"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="margin-top: 10px; height: calc(100vh - 330px)"
|
||||
style="margin-top: 10px; height: calc(100vh - 405px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
@@ -184,6 +184,7 @@ const form = reactive({
|
||||
message: '',
|
||||
port: 22,
|
||||
listenAddress: '',
|
||||
listenAddressItem: '',
|
||||
passwordAuthentication: 'yes',
|
||||
pubkeyAuthentication: 'yes',
|
||||
encryptionMode: '',
|
||||
@@ -222,7 +223,7 @@ const onChangeRoot = () => {
|
||||
rootsRef.value.acceptParams({ permitRootLogin: form.permitRootLogin });
|
||||
};
|
||||
const onChangeAddress = () => {
|
||||
addressRef.value.acceptParams({ address: form.listenAddress });
|
||||
addressRef.value.acceptParams({ address: form.listenAddress, port: form.port });
|
||||
};
|
||||
|
||||
const onOperate = async (operation: string) => {
|
||||
@@ -325,6 +326,10 @@ const search = async () => {
|
||||
form.port = Number(res.data.port);
|
||||
autoStart.value = res.data.autoStart ? 'enable' : 'disable';
|
||||
form.listenAddress = res.data.listenAddress;
|
||||
form.listenAddressItem =
|
||||
form.listenAddress === '' || form.listenAddress === '0.0.0.0,::'
|
||||
? i18n.global.t('ssh.allV4V6', [form.port])
|
||||
: form.listenAddress;
|
||||
form.passwordAuthentication = res.data.passwordAuthentication;
|
||||
form.pubkeyAuthentication = res.data.pubkeyAuthentication;
|
||||
form.permitRootLogin = res.data.permitRootLogin;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<el-col :span="22">
|
||||
<el-form ref="hostInfoRef" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||
<el-form-item :label="$t('terminal.ip')" prop="addr">
|
||||
<span v-if="dialogData.rowData!.addr === '127.0.0.1' && dialogData.title === 'edit'">
|
||||
<el-tag v-if="dialogData.rowData!.addr === '127.0.0.1' && dialogData.title === 'edit'">
|
||||
{{ dialogData.rowData!.addr }}
|
||||
</span>
|
||||
</el-tag>
|
||||
<el-input @change="isOK = false" v-else clearable v-model.trim="dialogData.rowData!.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')" prop="user">
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
/>
|
||||
<el-form-item :label="$t('terminal.ip')" prop="addr">
|
||||
<el-input @change="isOK = false" v-if="!isLocal" clearable v-model.trim="hostInfo.addr" />
|
||||
<div style="margin-left: 12px">
|
||||
<span v-if="isLocal">{{ hostInfo.addr }}</span>
|
||||
</div>
|
||||
<el-tag v-if="isLocal">{{ hostInfo.addr }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')" prop="user">
|
||||
<el-input @change="isOK = false" clearable v-model="hostInfo.user" />
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<Terminal
|
||||
style="height: calc(100vh - 227px); background-color: #000"
|
||||
style="height: calc(100vh - 229px); background-color: #000"
|
||||
:ref="'t-' + item.index"
|
||||
:key="item.Refresh"
|
||||
></Terminal>
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="search-button" style="float: none">
|
||||
<el-input
|
||||
v-model="hostFilterInfo"
|
||||
style="margin-top: 5px"
|
||||
style="margin-top: 5px; width: 90%"
|
||||
clearable
|
||||
suffix-icon="Search"
|
||||
:placeholder="$t('commons.button.search')"
|
||||
|
||||
@@ -4,10 +4,17 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('setting.title')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('setting.title')" prop="panelName" :rules="Rules.simpleName">
|
||||
<el-form-item :label="$t('setting.title')" prop="panelName">
|
||||
<el-input clearable v-model="form.panelName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -30,7 +37,6 @@ import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { updateSetting } from '@/api/modules/setting';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { GlobalStore } from '@/store';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
const globalStore = GlobalStore();
|
||||
@@ -47,6 +53,20 @@ const loading = ref();
|
||||
const form = reactive({
|
||||
panelName: '',
|
||||
});
|
||||
const rules = reactive({
|
||||
panelName: [{ validator: checkPanelName, trigger: 'blur', required: true }],
|
||||
});
|
||||
|
||||
function checkPanelName(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
return callback(new Error(i18n.global.t('setting.titleHelper')));
|
||||
}
|
||||
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_ .\u4e00-\u9fa5-]{2,29}$/;
|
||||
if (!reg.test(value)) {
|
||||
return callback(new Error(i18n.global.t('setting.titleHelper')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ function checkAddress(rule: any, value: any, callback: any) {
|
||||
if (form.allowIPs !== '') {
|
||||
let addrs = form.allowIPs.split('\n');
|
||||
for (const item of addrs) {
|
||||
if (item === '') {
|
||||
continue;
|
||||
}
|
||||
if (item === '0.0.0.0') {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
@@ -102,8 +105,14 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
|
||||
await updateSetting({ key: 'AllowIPs', value: form.allowIPs.replaceAll('\n', ',') })
|
||||
let ipItems = form.allowIPs.split('\n');
|
||||
let ips = [];
|
||||
for (const item of ipItems) {
|
||||
if (item !== '') {
|
||||
ips.push(item);
|
||||
}
|
||||
}
|
||||
await updateSetting({ key: 'AllowIPs', value: ips.join(',') })
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<DrawerHeader header="DNS" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
<el-row type="flex" justify="center">
|
||||
<el-row type="flex" justify="center" v-loading="loading">
|
||||
<el-col :span="22">
|
||||
<el-alert
|
||||
:title="$t('toolbox.device.dnsAlert')"
|
||||
@@ -24,7 +24,6 @@
|
||||
label-position="top"
|
||||
@submit.prevent
|
||||
:model="form"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item label="DNS" prop="dns">
|
||||
<el-input
|
||||
@@ -41,7 +40,7 @@
|
||||
placeholder="# The DNS configuration file does not exist or is empty (/etc/resolv.conf)"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="margin-top: 10px; height: calc(100vh - 200px)"
|
||||
style="margin-top: 10px; height: calc(100vh - 260px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<DrawerHeader :header="$t('toolbox.device.hostname')" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
<el-row type="flex" justify="center">
|
||||
<el-row type="flex" justify="center" v-loading="loading">
|
||||
<el-col :span="22">
|
||||
<el-alert
|
||||
:title="$t('toolbox.device.hostnameHelper')"
|
||||
@@ -13,7 +13,7 @@
|
||||
:closable="false"
|
||||
type="warning"
|
||||
/>
|
||||
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent>
|
||||
<el-form-item
|
||||
:label="$t('toolbox.device.hostname')"
|
||||
prop="hostname"
|
||||
@@ -76,6 +76,7 @@ const onSaveHostame = async (formEl: FormInstance | undefined) => {
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
loading.value = true;
|
||||
await updateDevice('Hostname', form.hostname)
|
||||
.then(async () => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<DrawerHeader header="Hosts" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
<el-row type="flex" justify="center">
|
||||
<el-row type="flex" justify="center" v-loading="loading">
|
||||
<el-col :span="22">
|
||||
<el-radio-group v-model="confShowType" @change="changeMode">
|
||||
<el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div>
|
||||
<el-drawer v-model="passwordVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('setting.changePassword')" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
<el-row type="flex" justify="center">
|
||||
<el-row type="flex" justify="center" v-loading="loading">
|
||||
<el-col :span="22">
|
||||
<el-form ref="formRef" label-position="top" :model="form" :rules="passRules">
|
||||
<el-alert
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader header="Swap" :back="handleClose" />
|
||||
@@ -147,7 +147,7 @@ const loadData = (path: string) => {
|
||||
item.size = itemSize.size;
|
||||
item.sizeUnit = itemSize.unit;
|
||||
}
|
||||
if (!isExist && path !== '') {
|
||||
if (!isExist) {
|
||||
form.swapDetails.push({
|
||||
path: path + '/.1panel_swap',
|
||||
size: 0,
|
||||
|
||||
@@ -40,17 +40,14 @@
|
||||
</div>
|
||||
|
||||
<div v-if="form.isExist">
|
||||
<el-card v-if="!form.isActive" class="mask-prompt">
|
||||
<span>{{ $t('toolbox.fail2ban.unActive') }}</span>
|
||||
</el-card>
|
||||
<LayoutContent title="Fail2ban" :divider="true" :class="{ mask: !form.isActive }">
|
||||
<LayoutContent title="Fail2ban" :divider="true">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" plain @click="onLoadList('ignore')">
|
||||
<el-button :disabled="!form.isActive" type="primary" plain @click="onLoadList('ignore')">
|
||||
{{ $t('toolbox.fail2ban.ignoreIP') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onLoadList('banned')">
|
||||
<el-button :disabled="!form.isActive" type="primary" plain @click="onLoadList('banned')">
|
||||
{{ $t('toolbox.fail2ban.bannedIP') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
|
||||
@@ -49,6 +49,9 @@ function checkIPs(rule: any, value: any, callback: any) {
|
||||
if (form.ips !== '') {
|
||||
let addr = form.ips.split('\n');
|
||||
for (const item of addr) {
|
||||
if (item === '') {
|
||||
continue;
|
||||
}
|
||||
if (item.indexOf('/') !== -1) {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
@@ -95,9 +98,16 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
let ipItems = form.ips.split('\n');
|
||||
let ips = [];
|
||||
for (const item of ipItems) {
|
||||
if (item !== '') {
|
||||
ips.push(item);
|
||||
}
|
||||
}
|
||||
let param = {
|
||||
operate: form.operate,
|
||||
ips: form.ips.split('\n'),
|
||||
ips: ips,
|
||||
};
|
||||
loading.value = true;
|
||||
await operatorFail2banSSHD(param)
|
||||
|
||||
@@ -89,7 +89,7 @@ const setting = () => {
|
||||
};
|
||||
|
||||
const toDoc = async () => {
|
||||
window.open('https://1panel.cn/docs/user_manual/hosts/supervisor/', '_blank', 'noopener,noreferrer');
|
||||
window.open('https://1panel.cn/docs/user_manual/toolbox/supervisor/', '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<span class="input-help" v-if="account.type === 'buypass'">
|
||||
{{ $t('ssl.buypassHelper') }}
|
||||
</span>
|
||||
<span class="input-help" v-if="account.type == 'google'">
|
||||
{{ $t('ssl.googleCloudHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.keyType')" prop="keyType">
|
||||
<el-select v-model="account.keyType">
|
||||
@@ -34,7 +37,6 @@
|
||||
:value="keyType.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help">{{ $t('ssl.googleCloudHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="account.type == 'google'">
|
||||
<el-form-item label="EAB kid" prop="eabKid">
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
<span class="input-help" v-if="ssl.provider === 'http'">
|
||||
{{ $t('ssl.httpHelper') }}
|
||||
</span>
|
||||
<span class="input-help text-red-500" v-if="ssl.provider === 'http'">
|
||||
{{ $t('ssl.httpHelper2') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('website.dnsAccount')"
|
||||
|
||||
@@ -40,7 +40,12 @@
|
||||
:key="index"
|
||||
:label="acme.email"
|
||||
:value="acme.id"
|
||||
></el-option>
|
||||
>
|
||||
<span>
|
||||
{{ acme.email }}
|
||||
<el-tag class="ml-5">{{ getAccountName(acme.type) }}</el-tag>
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.ssl')" prop="websiteSSLId" :hide-required-asterisk="true">
|
||||
@@ -162,7 +167,7 @@ import { ElMessageBox, FormInstance } from 'element-plus';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { dateFormatSimple, getProvider } from '@/utils/util';
|
||||
import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain">
|
||||
<el-input v-model="form.primaryDomain"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.alias')" prop="primaryDomain">
|
||||
<el-input v-model="form.alias" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.group')" prop="webSiteGroupID">
|
||||
<el-select v-model="form.webSiteGroupId">
|
||||
<el-option
|
||||
@@ -21,6 +24,7 @@
|
||||
<el-form-item prop="IPV6">
|
||||
<el-checkbox v-model="form.IPV6" :label="$t('website.ipv6')" size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit(websiteForm)" :disabled="loading">
|
||||
{{ $t('commons.button.save') }}
|
||||
@@ -58,6 +62,7 @@ const form = reactive({
|
||||
remark: '',
|
||||
webSiteGroupId: 0,
|
||||
IPV6: false,
|
||||
alias: '',
|
||||
});
|
||||
const rules = ref({
|
||||
primaryDomain: [Rules.requiredInput],
|
||||
@@ -91,6 +96,7 @@ const search = async () => {
|
||||
form.remark = res.data.remark;
|
||||
form.webSiteGroupId = res.data.webSiteGroupId;
|
||||
form.IPV6 = res.data.IPV6;
|
||||
form.alias = res.data.alias;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const logType = computed(() => {
|
||||
const id = computed(() => {
|
||||
return props.id;
|
||||
});
|
||||
const style = ref('height: calc(100vh - 400px); width: 100%; min-height: 400px');
|
||||
const style = ref('height: calc(100vh - 402px); width: 100%; min-height: 300px');
|
||||
const loading = ref(false);
|
||||
const data = ref({
|
||||
enable: false,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:placeholder="$t('commons.msg.noneData')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="max-height: 700px"
|
||||
style="height: calc(100vh - 352px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:placeholder="$t('commons.msg.noneData')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="max-height: 700px"
|
||||
style="height: calc(100vh - 352px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
|
||||
@@ -379,7 +379,7 @@ const buttons = [
|
||||
let params = {
|
||||
type: 'website',
|
||||
name: row.primaryDomain,
|
||||
detailName: '',
|
||||
detailName: row.alias,
|
||||
};
|
||||
dialogBackupRef.value!.acceptParams(params);
|
||||
},
|
||||
@@ -390,7 +390,7 @@ const buttons = [
|
||||
let params = {
|
||||
type: 'website',
|
||||
name: row.primaryDomain,
|
||||
detailName: '',
|
||||
detailName: row.alias,
|
||||
};
|
||||
uploadRef.value!.acceptParams(params);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user