Compare commits

...
28 Commits
Author SHA1 Message Date
ssongliu 411402f9cb fix: 修改 UFW 防火墙重启方式 (#3402) 2023-12-20 07:34:09 +00:00
ssongliu 733889407e feat: 优化文本域换行符校验 (#3399) 2023-12-20 05:46:09 +00:00
ssongliu f50972a0e3 feat: 容器名称修改增加来源判断 (#3390) 2023-12-19 14:02:07 +00:00
ssongliu d50fa7d928 fix: 优化部分界面滚动条样式 (#3389) 2023-12-19 09:38:07 +00:00
ssongliu df513c49e9 fix: 解决暗黑模式下主机地址样式问题 (#3386)
Refs #3378
2023-12-19 08:40:06 +00:00
zhengkunwang 10bd640a9b fix: 解决本地备份文件被删除导致的备份列表读取错误 (#3384) 2023-12-19 08:22:07 +00:00
ssongliu 6b491710c9 feat: SSH 支持同时监听 IPv4 及 IPv6 (#3381)
Refs #3359
2023-12-19 08:20:06 +00:00
ssongliu b9c5fee411 feat: 面板别名支持中文及空格 (#3382)
Refs #3380
2023-12-19 08:06:06 +00:00
zhengkunwang 78f2b6f72d fix: 解决同名网站备份恢复异常 (#3370)
Refs https://github.com/1Panel-dev/1Panel/issues/3355
2023-12-18 09:54:15 +00:00
zhengkunwang 9aaa387b23 fix: 解决自签证书在导入机构 Root CA 之后仍然显示不安全的问题 (#3366)
Refs https://github.com/1Panel-dev/1Panel/issues/3352
2023-12-18 04:04:12 +00:00
ssongliu a6fb0f1e89 feat: 工具箱操作增加超时时间 (#3365)
Refs #3358
2023-12-18 03:54:12 +00:00
ssongliu cbe1e9b4e1 feat: 防火墙增加重启按钮 (#3364)
Refs #3345
2023-12-18 03:42:13 +00:00
ssongliu 0a4e48c057 feat: 优化终端搜索样式及回滚条数显示 (#3363)
Refs #3330
2023-12-18 03:40:13 +00:00
zhengkunwang 3dc829a630 feat: 证书部分增加一些提示 (#3349) 2023-12-15 10:14:09 +00:00
ssongliu bcd88c6eca feat: 优化 SSH 日志获取 (#3348)
Refs #3337
2023-12-15 10:12:08 +00:00
ssongliu 4d279a521e feat: 镜像 Tag 逻辑修改 (#3346)
Refs #3316
2023-12-15 08:22:08 +00:00
zhengkunwang 3fb5e523a8 feat: 不同类型 ACME 账号支持使用同一个邮箱 (#3344)
Refs https://github.com/1Panel-dev/1Panel/issues/3333
2023-12-15 07:48:09 +00:00
zhengkunwang b2926019cf fix: 解决部分文件上传失败的问题 (#3343) 2023-12-15 07:46:08 +00:00
ssongliu a9d8018e78 feat: 容器升级界面调整 (#3341) 2023-12-15 04:10:08 +00:00
ssongliu 471a245732 feat: 调整菜单次序 (#3335) 2023-12-14 15:30:22 +00:00
ssongliu 55c71df906 fix: 增加工具箱获取超时时间 (#3328) 2023-12-14 08:38:22 +00:00
zhengkunwang f91831a5ea feat: 远程下载文件增加是否忽略不可信证书选项 (#3324)
Refs **https://github.com/1Panel-dev/1Panel/issues/3307**
2023-12-14 06:42:11 +00:00
ssongliu 18cbcb7f13 feat: 支持编辑失败状态下的 Fail2ban 配置 (#3322) 2023-12-14 04:56:11 +00:00
ssongliu e8d3b5fe93 fix: 修改 Supervisor 文档跳转路径 (#3321) 2023-12-14 04:42:11 +00:00
ssongliu 360cdf3f77 fix: 解决创建容器时拉取带认证仓库镜像失败的问题 (#3320)
Refs #3226
2023-12-14 04:02:11 +00:00
ssongliu c3a67eed87 fix: 解决 ssh 日志解析错误的问题 (#3318)
Refs #3309
2023-12-14 03:48:11 +00:00
zhengkunwang e8bc1310da feat: 目录选择增加显示隐藏文件夹 (#3315) 2023-12-14 03:22:12 +00:00
zhengkunwang c873c18a42 fix: 解决部分软件错误判断升级的问题 (#3314) 2023-12-14 03:20:11 +00:00
87 changed files with 848 additions and 1124 deletions
+1 -1
View File
@@ -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 {
-22
View File
@@ -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 登录日志
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 {
-1
View File
@@ -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"`
}
+4 -3
View File
@@ -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 {
-20
View File
@@ -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"`
+5
View File
@@ -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
}
+6
View File
@@ -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
+4 -2
View File
@@ -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
}
+2 -1
View File
@@ -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 -2
View File
@@ -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 {
+1 -1
View File
@@ -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")
+17 -16
View File
@@ -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")))
+21 -1
View File
@@ -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
}
+9 -4
View File
@@ -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
}
+7 -3
View File
@@ -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")
+1 -1
View File
@@ -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 {
+6 -18
View File
@@ -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
View File
@@ -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")
}
+1 -1
View File
@@ -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)
}
+21 -11
View File
@@ -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
+20 -6
View File
@@ -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
+1
View File
@@ -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
},
}
-1
View File
@@ -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)
+1 -1
View File
@@ -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)
+47 -38
View File
@@ -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)
+5 -4
View File
@@ -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 {
+1
View File
@@ -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 {
+10
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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"
},
+56 -86
View File
@@ -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"
},
+37 -56
View File
@@ -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
-1
View File
@@ -133,7 +133,6 @@ export namespace Container {
imageName: string;
}
export interface ImageTag {
repoID: number;
sourceID: string;
targetName: string;
}
+1
View File
@@ -109,6 +109,7 @@ export namespace File {
path: string;
name: string;
url: string;
ignoreCertificate?: boolean;
}
export interface FileWgetRes {
-3
View File
@@ -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);
};
+10 -10
View File
@@ -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);
};
+7 -3
View File
@@ -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>
+2 -1
View File
@@ -56,7 +56,8 @@ const newTerm = () => {
},
cursorBlink: true,
cursorStyle: 'underline',
scrollback: 100,
scrollback: 1000,
scrollSensitivity: 15,
tabStopWidth: 4,
});
};
+3
View File
@@ -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();
};
+21 -15
View File
@@ -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:
+14 -7
View File
@@ -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',
+14 -7
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
import { Layout } from '@/routers/constant';
const cronRouter = {
sort: 6,
sort: 8,
path: '/cronjobs',
component: Layout,
redirect: '/cronjobs',
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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')));
}
+1 -1
View File
@@ -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)
+8 -3
View File
@@ -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"
+2 -2
View File
@@ -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);
},