Compare commits

...
34 Commits
Author SHA1 Message Date
zhengkunwang 86ba53d6ec style: 应用商店样式修改 (#1711) 2023-07-18 08:10:01 +00:00
ssongliu 0ca559f1e1 fix: 快速命令增加提示信息 (#1710) 2023-07-18 07:34:00 +00:00
ssongliu 1fd31fffd6 fix: 优化首页样式 (#1709) 2023-07-18 07:26:02 +00:00
wangdan-fit2cloud c62c19a49e fix: 修复首页高度对齐问题 (#1708)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-18 06:28:35 +00:00
zhengkunwang aa8816f3ec feat: 网站已安装页面增加分页 (#1707) 2023-07-18 06:22:36 +00:00
zhengkunwang 920f1112fa feat: php 网站禁用函数支持输入下划线 (#1705) 2023-07-18 06:20:39 +00:00
ssongliu 4c2302dc94 fix: 国际化内容补全 (#1696) 2023-07-17 11:24:24 +00:00
ssongliu efebb26b5e fix: 解决容器创建端口选择 ipv6 校验问题 (#1695) 2023-07-17 08:38:21 +00:00
ssongliu 22fe2a6d51 fix: 本地连接失败时,打开添加主机抽屉 (#1691) 2023-07-17 08:36:24 +00:00
ssongliu e17b80cff4 fix: 解决部分接口命令注入问题 (#1690) 2023-07-17 08:34:29 +00:00
wangdan-fit2cloud 1d6f1b0ef3 fix: 优化首页对齐问题 (#1689)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-17 08:32:34 +00:00
ssongliu dac0c81d96 fix: 解决主机密钥无法正常连接终端的问题 (#1675) 2023-07-14 14:40:12 +00:00
wangdan-fit2cloud e555dcb903 fix: 优化echart线状图表 (#1673)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-14 14:38:16 +00:00
ssongliu 15c391e763 fix: 解决概览页左右高度不一致的问题 (#1671) 2023-07-14 08:22:11 +00:00
ssongliu 3b3584714c fix: 修改部分国际化内容 (#1669) 2023-07-14 07:18:11 +00:00
zhengkunwang 61a0244cfe fix: 修改部分国际化内容 (#1670) 2023-07-14 07:08:10 +00:00
wangdan-fit2cloud 73a61933c5 Pr@dev@dan (#1668)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-14 06:34:10 +00:00
zhengkunwang 72c7407b0b style: 文件列表增加操作列宽度 (#1667) 2023-07-14 06:22:10 +00:00
zhengkunwang 6538282a7b fix: 解决网站停止之后再启动运行目录错误的问题 (#1666)
Refs https://github.com/1Panel-dev/1Panel/issues/1665
2023-07-14 06:20:19 +00:00
ssongliu 87326e3292 fix: 解决监控数据异常导致溢出的问题 2023-07-14 11:55:33 +08:00
zhengkunwang 6eadb116c2 feat: 修复当网站主域名与网站目录不一致时,定时任务切割网站日志失败的问题 (#1659) 2023-07-14 03:50:10 +00:00
zhengkunwang c9ffd2564e feat: 基本名称名称长度限制增加到 128 (#1658) 2023-07-14 03:48:11 +00:00
wangdan-fit2cloud cd6e5f7905 fix: 优化暗色背景下禁用按钮样式 (#1663)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-14 03:38:10 +00:00
Mystery0 M b96e988f20 fix: 修复当网站主域名与网站目录不一致时,定时任务切割网站日志失败 (#1653)
* fix: 网站日志切割定时任务执行失败时打印失败原因,方便排查问题

* fix: 修复SiteDir与主域名不匹配时,切割网站日志失败
2023-07-13 22:25:58 +08:00
wzrove 30f7fa6afa fix: 优化监控页面echart资源释放 (#1651)
#### What this PR does / why we need it?

#1598 监控页面echart资源 组件卸载未释放

#### Summary of your change

优化监控页面echart资源释放

#### Please indicate you've done the following:

- [x] Made sure tests are passing and test coverage is added if needed.
- [x] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [x] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-13 12:47:18 +00:00
ssongliu 4605b19473 fix: 网站上传备份逻辑调整 (#1649) 2023-07-13 11:51:17 +00:00
zhengkunwang 87327053dc fix: 解决某些情况下应用状态异常的问题 (#1644) 2023-07-13 07:43:18 +00:00
ssongliu 5e7d5c0235 fix: 解决部分容器编辑无法正常打开的问题 (#1645) 2023-07-13 07:41:17 +00:00
zhengkunwang 1ccc56100c fix: 解决面板设置无法选择 https 证书的问题 (#1638) 2023-07-13 06:17:18 +00:00
ssongliu ef948bca76 fix: 解决概览页多磁盘刷新问题 (#1640) 2023-07-13 06:11:18 +00:00
ssongliu d01a964534 fix: 修改部分国际化问题 (#1639) 2023-07-13 06:09:23 +00:00
zhengkunwang 05004cb141 fix: 解决网站设置 HTTPS 证书显示错误的问题 (#1629)
Refs https://github.com/1Panel-dev/1Panel/issues/1626
2023-07-12 13:17:16 +00:00
zhengkunwang dfcef390d0 feat: 取消 openresty 安装时的端口开放提示 (#1627) 2023-07-12 13:13:16 +00:00
ssongliu ff3b41686c fix: 解决部分升级失败的问题 (#1624) 2023-07-12 13:11:20 +00:00
54 changed files with 645 additions and 610 deletions
+9 -59
View File
@@ -1,8 +1,6 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -30,44 +28,6 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
passwordItem, err := encrypt.StringEncrypt(string(password))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = passwordItem
req.PrivateKey = ""
req.PassPhrase = ""
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = keyItem
if len(req.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PassPhrase = pass
}
req.Password = ""
}
host, err := hostService.Create(req)
if err != nil {
@@ -216,40 +176,30 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
var err error
if len(req.Password) != 0 && req.AuthMode == "password" {
req.Password, err = hostService.EncryptHost(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
passwordItem, err := encrypt.StringEncrypt(string(password))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = passwordItem
req.PrivateKey = ""
req.PassPhrase = ""
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PrivateKey = keyItem
if len(req.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(req.PassPhrase)
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PassPhrase = pass
}
req.Password = ""
}
upMap := make(map[string]interface{})
+1 -1
View File
@@ -4,7 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/dto"
type WebsiteSSLSearch struct {
dto.PageInfo
AcmeAccountID uint `json:"acmeAccountId"`
AcmeAccountID string `json:"acmeAccountID"`
}
type WebsiteSSLCreate struct {
+22 -4
View File
@@ -61,7 +61,12 @@ func NewIAppInstalledService() IAppInstallService {
}
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
var opts []repo.DBOption
var (
opts []repo.DBOption
total int64
installs []model.AppInstall
err error
)
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
@@ -87,15 +92,25 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
opts = append(opts, appInstallRepo.WithAppIdsIn(appIds))
}
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
if req.Update {
installs, err = appInstallRepo.ListBy(opts...)
if err != nil {
return 0, nil, err
}
} else {
total, installs, err = appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}
}
installDTOs, err := handleInstalled(installs, req.Update)
if err != nil {
return 0, nil, err
}
if req.Update {
total = int64(len(installDTOs))
}
return total, installDTOs, nil
}
@@ -593,6 +608,9 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
config := getAppCommonConfig(envs)
config.DockerCompose = install.DockerCompose
res.Params = params
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
}
res.AppContainerConfig = config
return &res, nil
}
+3
View File
@@ -381,6 +381,9 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
for k := range containerMap {
containerNames = append(containerNames, k)
}
if len(containerNames) == 0 {
containerNames = append(containerNames, install.ContainerName)
}
return containerNames, nil
}
+33 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -80,11 +81,11 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
temPathWithName := tmpPath + "/" + website.Alias
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
return errors.New("the wrong recovery package does not have .conf or .web.tar.gz files")
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
}
if website.Type == constant.Deployment {
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
return errors.New("the wrong recovery package does not have .app.tar.gz files")
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
}
}
var oldWebsite model.Website
@@ -95,8 +96,9 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type || oldWebsite.ID != website.ID {
return errors.New("the current backup file does not match the application")
if err := checkValidOfWebsite(&oldWebsite, website); err != nil {
return err
}
isOk := false
@@ -155,6 +157,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
return errors.New(string(stdout))
}
oldWebsite.ID = website.ID
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
global.LOG.Errorf("handle save website data failed, err: %v", err)
return err
@@ -212,3 +215,29 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err
return nil
}
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type {
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil)
}
if oldWebsite.AppInstallID != 0 {
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "app", nil)
}
if app.App.Type != "website" {
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("appType: %s", app.App.Type), nil)
}
}
if oldWebsite.RuntimeID != 0 {
if _, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)); err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "runtime", nil)
}
}
if oldWebsite.WebsiteSSLID != 0 {
if _, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID)); err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "ssl", nil)
}
}
return nil
}
+4
View File
@@ -18,6 +18,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
@@ -552,6 +553,9 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
}
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error {
if cmd.CheckIllegal(container, since, tail) {
return buserr.New(constant.ErrCmdIllegal)
}
command := fmt.Sprintf("docker logs %s", container)
if tail != "0" {
command += " -n " + tail
+14
View File
@@ -14,8 +14,10 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
@@ -127,6 +129,9 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if cmd.CheckIllegal(req.Path) {
return false, buserr.New(constant.ErrCmdIllegal)
}
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
if composeItem.ID != 0 {
return false, constant.ErrRecordExist
@@ -143,6 +148,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
}
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
if cmd.CheckIllegal(req.Name, req.Path) {
return "", buserr.New(constant.ErrCmdIllegal)
}
if err := u.loadPath(&req); err != nil {
return "", err
}
@@ -177,6 +185,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if cmd.CheckIllegal(req.Path, req.Operation) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
@@ -195,6 +206,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
}
func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
if cmd.CheckIllegal(req.Name, req.Path) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
+2 -2
View File
@@ -359,10 +359,10 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
wg.Done()
return
}
websiteLogDir := path.Join(baseDir, website.PrimaryDomain, "log")
websiteLogDir := path.Join(baseDir, website.Alias, "log")
srcAccessLogPath := path.Join(websiteLogDir, "access.log")
srcErrorLogPath := path.Join(websiteLogDir, "error.log")
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.PrimaryDomain)
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.Alias)
if !fileOp.Stat(dstLogDir) {
_ = os.MkdirAll(dstLogDir, 0755)
}
+12
View File
@@ -17,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
_ "github.com/go-sql-driver/mysql"
@@ -77,6 +78,10 @@ var formatMap = map[string]string{
}
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Permission) {
return nil, buserr.New(constant.ErrCmdIllegal)
}
if req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
}
@@ -184,6 +189,10 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
}
func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
if cmd.CheckIllegal(info.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
var (
mysql model.DatabaseMysql
err error
@@ -253,6 +262,9 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
}
func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
if cmd.CheckIllegal(info.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
var (
mysql model.DatabaseMysql
err error
-1
View File
@@ -304,7 +304,6 @@ func OperateFirewallPort(oldPorts, newPorts []int) error {
return err
}
for _, port := range newPorts {
if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
+33
View File
@@ -24,6 +24,8 @@ type IHostService interface {
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
Update(id uint, upMap map[string]interface{}) error
Delete(id []uint) error
EncryptHost(itemVal string) (string, error)
}
func NewIHostService() IHostService {
@@ -220,6 +222,28 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
}
func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
var err error
if len(req.Password) != 0 && req.AuthMode == "password" {
req.Password, err = u.EncryptHost(req.Password)
if err != nil {
return nil, err
}
req.PrivateKey = ""
req.PassPhrase = ""
}
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
req.PrivateKey, err = u.EncryptHost(req.PrivateKey)
if err != nil {
return nil, err
}
if len(req.PassPhrase) != 0 {
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
return nil, err
}
}
req.Password = ""
}
var host model.Host
if err := copier.Copy(&host, &req); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
@@ -290,3 +314,12 @@ func (u *HostService) Delete(ids []uint) error {
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
return hostRepo.Update(id, upMap)
}
func (u *HostService) EncryptHost(itemVal string) (string, error) {
privateKey, err := base64.StdEncoding.DecodeString(itemVal)
if err != nil {
return "", err
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
return keyItem, err
}
+2 -2
View File
@@ -79,7 +79,7 @@ func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
return buserr.New(constant.ErrCmdIllegal)
}
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
if imageRepo.ID != 0 {
@@ -148,7 +148,7 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
return errors.New("The default value cannot be deleted !")
}
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
return buserr.New(constant.ErrCmdIllegal)
}
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
+10 -18
View File
@@ -76,29 +76,29 @@ func loadDiskIO() {
if io2.Name == io1.Name {
var itemIO model.MonitorIO
itemIO.Name = io1.Name
if io2.ReadBytes != 0 && io1.ReadBytes != 0 {
if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes {
itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
}
if io2.WriteBytes != 0 && io1.WriteBytes != 0 {
if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes {
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
}
if io2.ReadCount != 0 && io1.ReadCount != 0 {
if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount {
itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60)
}
writeCount := uint64(0)
if io2.WriteCount != 0 && io1.WriteCount != 0 {
if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount {
writeCount = uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
}
if writeCount > itemIO.Count {
itemIO.Count = writeCount
}
if io2.ReadTime != 0 && io1.ReadTime != 0 {
if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime {
itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
}
writeTime := uint64(0)
if io2.WriteTime != 0 && io1.WriteTime != 0 {
if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime {
writeTime = uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
}
if writeTime > itemIO.Time {
@@ -128,10 +128,10 @@ func loadNetIO() {
var itemNet model.MonitorNetwork
itemNet.Name = net1.Name
if net2.BytesSent != 0 && net1.BytesSent != 0 {
if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
}
if net2.BytesRecv != 0 && net1.BytesRecv != 0 {
if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
}
netList = append(netList, itemNet)
@@ -145,21 +145,13 @@ func loadNetIO() {
if net2.Name == net1.Name {
var itemNet model.MonitorNetwork
itemNet.Name = net1.Name
if net2.BytesSent != 0 && net1.BytesSent != 0 {
if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
}
if itemNet.Up > 10485760 {
itemNet.Up = 0
global.LOG.Errorf("net2: %v, net1: %v, BytesSent: %v \n", net2.BytesSent, net1.BytesSent, float64(net2.BytesSent-net1.BytesSent)/1024/60)
}
if net2.BytesRecv != 0 && net1.BytesRecv != 0 {
if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
}
if itemNet.Down > 10485760 {
itemNet.Down = 0
global.LOG.Errorf("net2: %v, net1: %v, BytesRecv: %v \n", net2.BytesRecv, net1.BytesRecv, float64(net2.BytesRecv-net1.BytesRecv)/1024/60)
}
netList = append(netList, itemNet)
break
}
+4
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -146,6 +147,9 @@ func (u *SSHService) UpdateByFile(value string) error {
}
func (u *SSHService) GenerateSSH(req dto.GenerateSSH) error {
if cmd.CheckIllegal(req.EncryptionMode, req.Password) {
return buserr.New(constant.ErrCmdIllegal)
}
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("load current user failed, err: %v", err)
+9 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
"path"
"strconv"
"strings"
)
@@ -66,7 +67,14 @@ func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.W
opts []repo.DBOption
result []response.WebsiteSSLDTO
)
opts = append(opts, commonRepo.WithOrderBy("created_at desc"), websiteSSLRepo.WithByAcmeAccountId(search.AcmeAccountID))
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil {
return nil, err
}
opts = append(opts, websiteSSLRepo.WithByAcmeAccountId(uint(acmeAccountID)))
}
sslList, err := websiteSSLRepo.List(opts...)
if err != nil {
return nil, err
+5 -2
View File
@@ -536,6 +536,10 @@ func opWebsite(website *model.Website, operate string) error {
if files.NewFileOp().Stat(absoluteRewritePath) {
server.UpdateDirective("include", []string{rewriteInclude})
}
rootIndex := path.Join("/www/sites", website.Alias, "index")
if website.SiteDir != "/" {
rootIndex = path.Join(rootIndex, website.SiteDir)
}
switch website.Type {
case constant.Deployment:
server.RemoveDirective("root", nil)
@@ -546,12 +550,11 @@ func opWebsite(website *model.Website, operate string) error {
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
server.UpdateRootProxy([]string{proxy})
case constant.Static:
server.UpdateRoot(path.Join("/www/sites", website.Alias, "index"))
server.UpdateRoot(rootIndex)
server.UpdateRootLocation()
case constant.Proxy:
server.RemoveDirective("root", nil)
case constant.Runtime:
rootIndex := path.Join("/www/sites", website.Alias, "index")
server.UpdateRoot(rootIndex)
localPath := ""
if website.ProxyType == constant.RuntimeProxyUnix {
+3 -1
View File
@@ -42,6 +42,7 @@ var (
ErrTypePasswordExpired = "ErrPasswordExpired"
ErrNameIsExist = "ErrNameIsExist"
ErrDemoEnvironment = "ErrDemoEnvironment"
ErrCmdIllegal = "ErrCmdIllegal"
)
// app
@@ -68,6 +69,8 @@ var (
ErrGroupIsUsed = "ErrGroupIsUsed"
ErrUsernameIsExist = "ErrUsernameIsExist"
ErrUsernameIsNotExist = "ErrUsernameIsNotExist"
ErrBackupMatch = "ErrBackupMatch"
ErrBackupExist = "ErrBackupExist"
)
// ssl
@@ -105,7 +108,6 @@ var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules"
ErrRepoConn = "ErrRepoConn"
)
// runtime
+4 -1
View File
@@ -13,6 +13,7 @@ ErrNotSupportType: "The system does not support the current type: {{ .detail }}"
ErrNameIsExist: "Name is already exist"
ErrDemoEnvironment: "Demo server, prohibit this operation!"
ErrCmdTimeout: "Command execution timed out!"
ErrCmdIllegal: "The command contains illegal characters. Please modify and try again!"
#app
ErrPortInUsed: "{{ .detail }} port already in use"
@@ -59,6 +60,8 @@ ErrDomainIsExist: "Domain is already exist"
ErrAliasIsExist: "Alias is already exist"
ErrAppDelete: 'Other Website use this App'
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
ErrBackupMatch: 'the backup file does not match the current partial data of the website: {{ .detail}}"'
ErrBackupExist: 'the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}"'
#ssl
ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed"
@@ -81,7 +84,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrRepoConn: "The repository information contains illegal characters"
ErrPortRules: "The number of ports does not match, please re-enter!"
#runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
+4 -1
View File
@@ -13,6 +13,7 @@ ErrNotSupportType: "系統暫不支持當前類型: {{ .detail }}"
ErrNameIsExist: "名稱已存在"
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
ErrCmdTimeout: "指令執行超時!"
ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!"
#app
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
@@ -59,6 +60,8 @@ ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代號已存在"
ErrAppDelete: '其他網站使用此應用,無法刪除'
ErrGroupIsUsed: '分組正在使用中,無法刪除'
ErrBackupMatch: '該備份文件與當前網站部分數據不匹配: {{ .detail}}"'
ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}"'
#ssl
ErrSSLCannotDelete: "證書正在被網站使用,無法刪除"
@@ -81,7 +84,7 @@ ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後
#container
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
ErrObjectInUsed: "該對象正被使用,無法刪除"
ErrRepoConn: "倉庫資訊中存在不合法的字符"
ErrPortRules: "端口數目不匹配,請重新輸入!"
#runtime
ErrDirNotFound: "build 文件夾不存在!請檢查文件完整性!"
+4 -1
View File
@@ -13,6 +13,7 @@ ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
ErrNameIsExist: "名称已存在"
ErrDemoEnvironment: "演示服务器,禁止此操作!"
ErrCmdTimeout: "命令执行超时!"
ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!"
#app
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
@@ -59,6 +60,8 @@ ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代号已存在"
ErrAppDelete: '其他网站使用此应用,无法删除'
ErrGroupIsUsed: '分组正在使用中,无法删除'
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}"'
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}"'
#ssl
ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
@@ -81,7 +84,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrRepoConn: "仓库信息中存在不合法的字符"
ErrPortRules: "端口数目不匹配,请重新输入!"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
+3 -3
View File
@@ -437,7 +437,7 @@ var EncryptHostPassword = &gormigrate.Migration{
ID: "20230703-encrypt-host-password",
Migrate: func(tx *gorm.DB) error {
var hosts []model.Host
if err := tx.Find(&hosts).Error; err != nil {
if err := tx.Where("1 = 1").Find(&hosts).Error; err != nil {
return err
}
@@ -462,7 +462,7 @@ var EncryptHostPassword = &gormigrate.Migration{
if err != nil {
return err
}
if err := tx.Model(&model.Host{}).Update("private_key", key).Error; err != nil {
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("private_key", key).Error; err != nil {
return err
}
}
@@ -471,7 +471,7 @@ var EncryptHostPassword = &gormigrate.Migration{
if err != nil {
return err
}
if err := tx.Model(&model.Host{}).Update("pass_phrase", pass).Error; err != nil {
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("pass_phrase", pass).Error; err != nil {
return err
}
}
@@ -4,6 +4,8 @@ import (
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
)
@@ -114,6 +116,10 @@ func (f *Firewall) ListAddress() ([]FireInfo, error) {
}
func (f *Firewall) Port(port FireInfo, operation string) error {
if cmd.CheckIllegal(operation, port.Protocol, port.Port) {
return buserr.New(constant.ErrCmdIllegal)
}
stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol)
if err != nil {
return fmt.Errorf("%s port failed, err: %s", operation, stdout)
@@ -122,6 +128,9 @@ func (f *Firewall) Port(port FireInfo, operation string) error {
}
func (f *Firewall) RichRules(rule FireInfo, operation string) error {
if cmd.CheckIllegal(operation, rule.Address, rule.Protocol, rule.Port, rule.Strategy) {
return buserr.New(constant.ErrCmdIllegal)
}
ruleStr := ""
if strings.Contains(rule.Address, "-") {
std, err := cmd.Execf("firewall-cmd --permanent --new-ipset=%s --type=hash:ip", rule.Address)
+9
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
)
@@ -131,6 +133,9 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
default:
return fmt.Errorf("unsupport strategy %s", port.Strategy)
}
if cmd.CheckIllegal(port.Protocol, port.Port) {
return buserr.New(constant.ErrCmdIllegal)
}
command := fmt.Sprintf("%s %s %s", f.CmdStr, port.Strategy, port.Port)
if operation == "remove" {
@@ -156,6 +161,10 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
return fmt.Errorf("unsupport strategy %s", rule.Strategy)
}
if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) {
return buserr.New(constant.ErrCmdIllegal)
}
ruleStr := fmt.Sprintf("%s %s ", f.CmdStr, rule.Strategy)
if operation == "remove" {
ruleStr = fmt.Sprintf("%s delete %s ", f.CmdStr, rule.Strategy)
+12 -6
View File
@@ -1,5 +1,5 @@
// Code generated by swaggo/swag. DO NOT EDIT.
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
@@ -11068,6 +11068,9 @@ const docTemplate = `{
"$ref": "#/definitions/dto.PortHelper"
}
},
"forcePull": {
"type": "boolean"
},
"image": {
"type": "string"
},
@@ -11078,13 +11081,13 @@ const docTemplate = `{
}
},
"memory": {
"type": "integer"
"type": "number"
},
"name": {
"type": "string"
},
"nanoCPUs": {
"type": "integer"
"type": "number"
},
"network": {
"type": "string"
@@ -11198,6 +11201,9 @@ const docTemplate = `{
"name"
],
"properties": {
"forcePull": {
"type": "boolean"
},
"image": {
"type": "string"
},
@@ -15159,8 +15165,8 @@ const docTemplate = `{
"pageSize"
],
"properties": {
"acmeAccountId": {
"type": "integer"
"acmeAccountID": {
"type": "string"
},
"page": {
"type": "integer"
+10 -4
View File
@@ -11061,6 +11061,9 @@
"$ref": "#/definitions/dto.PortHelper"
}
},
"forcePull": {
"type": "boolean"
},
"image": {
"type": "string"
},
@@ -11071,13 +11074,13 @@
}
},
"memory": {
"type": "integer"
"type": "number"
},
"name": {
"type": "string"
},
"nanoCPUs": {
"type": "integer"
"type": "number"
},
"network": {
"type": "string"
@@ -11191,6 +11194,9 @@
"name"
],
"properties": {
"forcePull": {
"type": "boolean"
},
"image": {
"type": "string"
},
@@ -15152,8 +15158,8 @@
"pageSize"
],
"properties": {
"acmeAccountId": {
"type": "integer"
"acmeAccountID": {
"type": "string"
},
"page": {
"type": "integer"
+8 -4
View File
@@ -327,6 +327,8 @@ definitions:
items:
$ref: '#/definitions/dto.PortHelper'
type: array
forcePull:
type: boolean
image:
type: string
labels:
@@ -334,11 +336,11 @@ definitions:
type: string
type: array
memory:
type: integer
type: number
name:
type: string
nanoCPUs:
type: integer
type: number
network:
type: string
publishAllPorts:
@@ -413,6 +415,8 @@ definitions:
type: object
dto.ContainerUpgrade:
properties:
forcePull:
type: boolean
image:
type: string
name:
@@ -3063,8 +3067,8 @@ definitions:
type: object
request.WebsiteSSLSearch:
properties:
acmeAccountId:
type: integer
acmeAccountID:
type: string
page:
type: integer
pageSize:
+1 -1
View File
@@ -208,7 +208,7 @@ export namespace Website {
export interface SSLReq {
name?: string;
acmeAccountID?: number;
acmeAccountID?: string;
}
export interface HTTPSReq {
@@ -31,9 +31,60 @@ const props = defineProps({
option: {
type: Object,
required: true,
}, // option: { title , xDatas, yDatas, formatStr }
}, // option: { title , xDatas, yDatas, formatStr, yAxis, grid, tooltip}
});
const seriesStyle = [
{
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
{
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(27, 143, 60, .5)',
},
{
offset: 1,
color: 'rgba(27, 143, 60, 0)',
},
]),
},
{
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(249, 199, 79, .5)',
},
{
offset: 1,
color: 'rgba(249, 199, 79, 0)',
},
]),
},
{
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(255, 173, 177, 0.5)',
},
{
offset: 1,
color: 'rgba(255, 173, 177, 0)',
},
]),
},
];
function initChart() {
let itemChart = echarts?.getInstanceByDom(document.getElementById(props.id) as HTMLElement);
// 如果不存在,就进行初始化
@@ -45,44 +96,32 @@ function initChart() {
const series = [];
if (props.option?.yDatas?.length) {
series.push({
name: props.option?.yDatas[0]?.name,
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
data: props.option?.yDatas[0]?.data,
showSymbol: false,
});
if (props.option?.yDatas?.length > 1) {
props.option?.yDatas.forEach((item: any, index: number) => {
series.push({
name: props.option?.yDatas[1]?.name,
name: item?.name,
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(27, 143, 60, .5)',
},
{
offset: 1,
color: 'rgba(27, 143, 60, 0)',
},
]),
},
data: props.option?.yDatas[1]?.data,
areaStyle: seriesStyle[index],
data: item?.data,
showSymbol: false,
yAxisIndex: item.yAxisIndex ? 1 : null,
});
}
});
}
const yAxis = [];
if (props.option.yAxis && props.option.yAxis.length > 0) {
props.option.yAxis.forEach((item: any) => {
yAxis.push({
splitLine: {
show: true,
//分隔辅助线
lineStyle: {
type: 'dashed', //线的类型 虚线0
opacity: theme === 'dark' ? 0.1 : 1, //透明度
},
},
...item,
});
});
}
// 把配置和数据放这里
@@ -96,7 +135,7 @@ function initChart() {
],
zlevel: 1,
z: 1,
tooltip: {
tooltip: props.option.tooltip || {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
@@ -127,7 +166,7 @@ function initChart() {
return res;
},
},
grid: { left: '7%', right: '7%', bottom: '20%' },
grid: props.option.grid || { left: '7%', right: '7%', bottom: '20%' },
legend: {
right: 10,
itemWidth: 8,
@@ -137,26 +176,27 @@ function initChart() {
icon: 'circle',
},
xAxis: { data: props.option.xDatas, boundaryGap: false },
yAxis: {
name: '( ' + props.option.formatStr + ' )',
splitLine: {
//分隔辅助线
lineStyle: {
type: 'dashed', //线的类型 虚线0
opacity: theme === 'dark' ? 0.1 : 1, //透明度
},
},
},
yAxis: props.option.yAxis
? yAxis
: {
name: '( ' + props.option.formatStr + ' )',
splitLine: {
//分隔辅助线
lineStyle: {
type: 'dashed', //线的类型 虚线0
opacity: theme === 'dark' ? 0.1 : 1, //透明度
},
},
},
series: series,
dataZoom: [{ startValue: props?.option.xDatas[0], show: props.dataZoom }],
};
// 渲染数据
itemChart.setOption(option, true);
}
window.onresize = function () {
//自适应大小
itemChart.resize();
};
function changeChartSize() {
echarts.getInstanceByDom(document.getElementById(props.id) as HTMLElement)?.resize();
}
watch(
@@ -173,11 +213,13 @@ watch(
onMounted(() => {
nextTick(() => {
initChart();
window.addEventListener('resize', changeChartSize);
});
});
onBeforeUnmount(() => {
echarts.getInstanceByDom(document.getElementById(props.id) as HTMLElement).dispose();
window.removeEventListener('resize', changeChartSize);
});
</script>
<style lang="scss" scoped></style>
+3 -3
View File
@@ -174,7 +174,7 @@ const checkLinuxName = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.linuxName', ['/\\:*?\'"<>|'])));
} else {
const reg = /^[^/\\\"'|<>?*]{1,30}$/;
const reg = /^[^/\\\"'|<>?*]{1,128}$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.linuxName', ['/\\:*?\'"<>|'])));
} else {
@@ -376,7 +376,7 @@ const checkDisableFunctions = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.disableFunction')));
} else {
const reg = /^[a-zA-Z,]+$/;
const reg = /^[a-zA-Z_,]+$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.disableFunction')));
} else {
@@ -402,7 +402,7 @@ const checkParamSimple = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback();
} else {
const reg = /^[a-z0-9][a-z0-9]{1,64}$/;
const reg = /^[a-z0-9][a-z0-9]{1,128}$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.paramSimple')));
} else {
+20 -14
View File
@@ -159,6 +159,7 @@ const message = {
complexityPassword:
'Longer than eight characters and contains at least two combinations of letters, digits, and special characters',
commonPassword: 'Please enter a password with more than 6 characters',
linuxName: 'Length 1-128, the name cannot contain symbols such as {0}',
email: 'Email format error',
number: 'Please enter the correct number',
integer: 'Please enter the correct positive integer',
@@ -178,9 +179,9 @@ const message = {
appName: 'Support English, numbers, - and _, length 2-30, and cannot start and end with -_',
containerName:
'Supports letters, numbers, underscores, hyphens and dots, cannot end with hyphen- or dot.1-127',
disableFunction: 'Only support letters and,',
disableFunction: 'Only support letters ,underscores,and,',
leechExts: 'Only support letters, numbers and,',
paramSimple: 'Support lowercase letters and numbers, length 1-64',
paramSimple: 'Support lowercase letters and numbers, length 1-128',
},
res: {
paramError: 'The request failed, please try again later!',
@@ -355,7 +356,7 @@ const message = {
threadCacheHitHelper: 'If it is too low, increase thread_cache_size',
indexHit: 'Index hit',
indexHitHelper: 'If it is too low, increase key_buffer_size',
innodbIndexHit: 'Innodb 索引命中率',
innodbIndexHit: 'Innodb index hit rate',
innodbIndexHitHelper: 'If it is too low, increase innodb_buffer_pool_size',
cacheHit: 'Querying the Cache Hit',
cacheHitHelper: 'If it is too low, increase query_cache_size',
@@ -443,7 +444,8 @@ const message = {
localIP: 'Local IP',
},
container: {
createContainer: 'Create container',
create: 'Create container',
edit: 'Edit container',
updateContaienrHelper:
'Container editing requires rebuilding the container. Any data that has not been persisted will be lost. Do you want to continue?',
containerList: 'Container list',
@@ -649,13 +651,13 @@ const message = {
startIn: ' to start',
},
cronjob: {
cronTask: 'Task',
cronTask: 'Cronjob',
changeStatus: 'Change status',
disableMsg:
'Stopping the scheduled task will result in the task no longer automatically executing. Do you want to continue?',
enableMsg:
'Enabling the scheduled task will allow the task to automatically execute on a regular basis. Do you want to continue?',
taskType: 'Task type',
taskType: 'Cronjob type',
record: 'Records',
shell: 'Shell script',
containerCheckBox: 'In container (no need to enter the container command)',
@@ -673,7 +675,7 @@ const message = {
syncDate: 'Synchronization time ',
releaseMemory: 'Free memory',
curl: 'Access URL',
taskName: 'Task name',
taskName: 'Cronjob name',
cronSpec: 'Lifecycle',
cronSpecHelper: 'Enter the correct execution period',
cleanHelper:
@@ -710,7 +712,7 @@ const message = {
sunday: 'Sunday',
shellContent: 'Script content',
errRecord: 'Incorrect logging',
errHandle: 'Task execution failure',
errHandle: 'Cronjob execution failure',
noRecord: 'The execution did not generate any logs',
cleanData: 'Clean data',
cleanDataHelper: 'Delete the backup file generated during this task.',
@@ -761,6 +763,7 @@ const message = {
fold: 'All contract',
batchInput: 'Batch input',
quickCommand: 'Quick command',
quickCommandHelper: 'Frequently used command list for quick selection at the bottom of the terminal interface',
groupDeleteHelper:
'After the group is removed, all connections in the group will be migrated to the default group. Confirm the information',
command: 'Command',
@@ -1017,7 +1020,7 @@ const message = {
entranceInputHelper: 'When the security entry is set to blank, the security entry is cancelled.',
randomGenerate: 'Random',
expirationTime: 'Expiration Time',
unSetting: 'Not Set',
unSetting: 'Unset',
noneSetting:
'Set the expiration time for the panel password. After the expiration, you need to reset the password',
expirationHelper: 'If the password expiration time is [0] days, the password expiration function is disabled',
@@ -1317,7 +1320,7 @@ const message = {
startHelper:
'After enabling the site, users can access the content of the site normally, do you want to continue? ',
sitePath: 'Website Directory',
siteAlias: 'Site Path Name',
siteAlias: 'Site Alias',
primaryPath: 'Main directory',
folderTitle: 'The main directory of the website mainly contains four folders',
wafFolder: 'Firewall Rules',
@@ -1417,11 +1420,11 @@ const message = {
'The password is asymmetrically encrypted and cannot be echoed. Editing needs to reset the password',
antiLeech: 'Anti-leech',
extends: 'Extension',
browserCache: 'browser cache',
browserCache: 'Cache',
leechLog: 'Record anti-leech log',
accessDomain: 'Allowed domain names',
accessDomain: 'Allowed domains',
leechReturn: 'Response resource',
noneRef: 'Allow the source to be empty',
noneRef: 'Allow empty source',
disable: 'not enabled',
disableLeechHelper: 'Whether to disable the anti-leech',
disableLeech: 'Disable anti-leech',
@@ -1499,6 +1502,8 @@ const message = {
acmeHelper: 'Acme account is used to apply for free certificates',
},
firewall: {
create: 'Create rule',
edit: 'Edit rule',
notSupport:
'No system firewall detected (firewalld or ufw). Please refer to the official documentation for installation.',
ccDeny: 'CC Protection',
@@ -1614,7 +1619,8 @@ const message = {
laddr: 'Source address/port',
raddr: 'Destination address/port',
stopProcess: 'End',
stopProcessWarn: 'Are you sure you want to end this process (PID:{0})? This operation cannot be rolled back',
stopProcessWarn: 'Are you sure you want to end this process (PID:{0})? ',
processName: 'ProcessName',
},
};
+12 -4
View File
@@ -159,7 +159,7 @@ const message = {
volumeName: '支持英文、數字、.-和_,長度2-30',
complexityPassword: '請輸入長度大於 8 位且包含字母、數字、特殊字符至少兩項的密碼組合',
commonPassword: '請輸入 6 位以上長度密碼',
linuxName: '長度1-30,名稱不能含有{0}等符號',
linuxName: '長度1-128,名稱不能含有{0}等符號',
email: '請輸入正確的郵箱',
number: '請輸入正確的數字',
integer: '請輸入正確的正整數',
@@ -177,9 +177,9 @@ const message = {
nginxDoc: '僅支持英文大小寫,數字,和.',
appName: '支持英文、數字、-和_,長度2-30,並且不能以-_開頭和結尾',
containerName: '支持字母、數字、下劃線、連字符和點,不能以連字符-或點.結尾,長度1-127',
disableFunction: '僅支持字母和,',
disableFunction: '僅支持字母、下劃線和,',
leechExts: '僅支持字母數字和,',
paramSimple: '支持小寫字母和數字,長度1-64',
paramSimple: '支持小寫字母和數字,長度 1-128',
},
res: {
paramError: '請求失敗,請稍後重試!',
@@ -737,6 +737,7 @@ const message = {
fold: '全部收縮',
batchInput: '批量輸入',
quickCommand: '快速命令',
quickCommandHelper: '常用命令列表,用於在終端界面底部快速選擇',
groupDeleteHelper: '移除組後,組內所有連接將遷移到 default 組內,是否確認',
command: '命令',
addHost: '添加主機',
@@ -887,6 +888,12 @@ const message = {
belong: '歸屬地',
local: '內網',
remote: '外網',
config: '配置',
session: '會話',
loginTime: '登錄時間',
loginIP: '登錄IP',
disconnect: '斷開',
stopSSHWarn: '是否斷開此SSH連接',
},
setting: {
all: '全部',
@@ -1530,7 +1537,8 @@ const message = {
laddr: '源地址/端口',
raddr: '目標地址/端口',
stopProcess: '結束',
stopProcessWarn: '是否確定結束此進程 (PID:{0})?此操作不可回滾',
stopProcessWarn: '是否確定結束此進程 (PID:{0})?',
processName: '進程名稱',
},
};
export default {
+5 -4
View File
@@ -159,7 +159,7 @@ const message = {
volumeName: '支持英文、数字、.-和_,长度2-30',
complexityPassword: '请输入长度大于 8 位且包含字母、数字、特殊字符至少两项的密码组合',
commonPassword: '请输入 6 位以上长度密码',
linuxName: '长度1-30,名称不能含有{0}等符号',
linuxName: '长度1-128,名称不能含有{0}等符号',
email: '请输入正确的邮箱',
number: '请输入正确的数字',
integer: '请输入正确的正整数',
@@ -177,9 +177,9 @@ const message = {
nginxDoc: '仅支持英文大小写,数字,和.',
appName: '支持英文、数字、-和_,长度2-30,并且不能以-_开头和结尾',
containerName: '支持字母、数字、下划线、连字符和点,不能以连字符-或点.结尾,长度1-127',
disableFunction: '仅支持字母和,',
disableFunction: '仅支持字母、下划线和,',
leechExts: '仅支持字母数字和,',
paramSimple: '支持小写字母和数字,长度1-64',
paramSimple: '支持小写字母和数字,长度1-128',
},
res: {
paramError: '请求失败,请稍后重试!',
@@ -737,6 +737,7 @@ const message = {
fold: '全部收缩',
batchInput: '批量输入',
quickCommand: '快速命令',
quickCommandHelper: '常用命令列表,用于在终端界面底部快速选择',
groupDeleteHelper: '移除组后,组内所有连接将迁移到 default 组内,是否确认',
command: '命令',
addHost: '添加主机',
@@ -1538,7 +1539,7 @@ const message = {
laddr: '源地址/端口',
raddr: '目标地址/端口',
stopProcess: '结束',
stopProcessWarn: '是否确定结束此进程 (PID:{0})?此操作不可回滚',
stopProcessWarn: '是否确定结束此进程 (PID:{0})?',
processName: '进程名称',
},
};
+3 -2
View File
@@ -1,3 +1,6 @@
html {
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, 微软雅黑, Arial, sans-serif;
}
.flx-center {
display: flex;
align-items: center;
@@ -122,8 +125,6 @@
font-size: 12px;
word-break: break-all;
color: #8f959e;
transform: scale(0.9);
transform-origin: left;
width: 110%;
display: inline-block;
}
+7 -1
View File
@@ -171,7 +171,7 @@ html.dark {
.mobile-header {
background-color: var(--panel-main-bg-color) !important;
border-bottom: var(--panel-border);
color: #ffffff;
color: #ffffff;
}
}
.system-label {
@@ -306,4 +306,10 @@ html.dark {
background-color: rgb(56, 59, 59);
color: var(--el-color-warning);
}
.el-dropdown-menu__item.is-disabled {
color: var(--panel-button-disabled);
}
.el-date-editor .el-range-separator {
color: var(--panel-button-disabled);
}
}
+6 -7
View File
@@ -287,17 +287,16 @@ onMounted(() => {
border: none;
}
}
.page-button {
float: right;
margin-bottom: 10px;
margin-top: 10px;
}
@media only screen and (min-width: 768px) and (max-width: 1200px) {
.app-col-12 {
max-width: 50%;
flex: 0 0 50%;
}
}
.page-button {
float: right;
margin-bottom: 10px;
margin-top: 10px;
}
</style>
@@ -224,7 +224,7 @@ const submit = async (formEl: FormInstance | undefined) => {
if (req.memoryLimit < 0) {
req.memoryLimit = 0;
}
if (req.advanced && !req.allowPort) {
if (installData.value.app.key != 'openresty' && req.advanced && !req.allowPort) {
ElMessageBox.confirm(i18n.global.t('app.installWarn'), i18n.global.t('app.checkTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
+6
View File
@@ -91,3 +91,9 @@
border: none;
}
}
.page-button {
float: right;
margin-bottom: 10px;
margin-top: 10px;
}
@@ -21,7 +21,7 @@
:type="activeTag === item.key ? 'primary' : ''"
:plain="activeTag !== item.key"
>
{{ item.name }}
{{ language == 'zh' || language == 'tw' ? item.name : item.key }}
</el-button>
</div>
</div>
@@ -234,6 +234,15 @@
</div>
</el-col>
</el-row>
<div class="page-button" v-if="mode === 'installed'">
<fu-table-pagination
v-model:current-page="paginationConfig.currentPage"
v-model:page-size="paginationConfig.pageSize"
v-bind="paginationConfig"
@change="search"
:layout="'total, sizes, prev, pager, next, jumper'"
/>
</div>
</template>
</LayoutContent>
<Backups ref="backupRef" @close="search" />
@@ -271,6 +280,7 @@ import { getAge } from '@/utils/util';
import { useRouter } from 'vue-router';
import { MsgSuccess } from '@/utils/message';
import { toFolder } from '@/global/business';
import { useI18n } from 'vue-i18n';
const data = ref<any>();
const loading = ref(false);
@@ -299,7 +309,7 @@ const tags = ref<App.Tag[]>([]);
const activeTag = ref('all');
const searchReq = reactive({
page: 1,
pageSize: 15,
pageSize: 20,
name: '',
tags: [],
update: false,
@@ -308,6 +318,8 @@ const router = useRouter();
const activeName = ref(i18n.global.t('app.installed'));
const mode = ref('installed');
const language = useI18n().locale.value;
const sync = () => {
syncLoading.value = true;
SyncInstalledApp()
@@ -119,7 +119,7 @@
</div>
<div v-if="row.expand && row.ports.length > 3">
<el-button type="primary" link @click="row.expand = false">
{{ $t('commons.button.shrink') }}
{{ $t('commons.button.collapse') }}
</el-button>
</div>
</div>
@@ -264,6 +264,7 @@ const acceptParams = (params: DialogProps): void => {
title.value = i18n.global.t('container.' + dialogData.value.title);
if (params.title === 'edit') {
dialogData.value.rowData.memory = Number(dialogData.value.rowData.memory.toFixed(2));
dialogData.value.rowData.cmd = dialogData.value.rowData.cmd || [];
let itemCmd = '';
for (const item of dialogData.value.rowData.cmd) {
itemCmd += `'${item}' `;
@@ -438,12 +439,12 @@ const checkPortValid = () => {
}
for (const port of dialogData.value.rowData!.exposedPorts) {
if (port.host.indexOf(':') !== -1) {
port.hostIP = port.host.split(':')[0];
port.hostIP = port.host.substring(0, port.host.lastIndexOf(':'));
if (checkIpV4V6(port.hostIP)) {
MsgError(i18n.global.t('firewall.addressFormatError'));
return false;
}
port.hostPort = port.host.split(':')[1];
port.hostPort = port.host.substring(port.host.lastIndexOf(':') + 1);
} else {
port.hostPort = port.host;
}
+1 -1
View File
@@ -66,7 +66,7 @@
fix
:formatter="dateFormat"
/>
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" />
<fu-table-operations width="200px" :buttons="buttons" :label="$t('commons.table.operate')" />
</ComplexTable>
</template>
</LayoutContent>
+2 -2
View File
@@ -80,7 +80,7 @@
<el-table-column :label="$t('cronjob.cronSpec')" :min-width="120">
<template #default="{ row }">
<span v-if="row.specType.indexOf('N') === -1 || row.specType === 'perWeek'">
{{ $t('cronjob.' + row.specType) }}
{{ $t('cronjob.' + row.specType) }}&nbsp;
</span>
<span v-else>{{ $t('cronjob.per') }}</span>
<span v-if="row.specType === 'perMonth'">
@@ -123,7 +123,7 @@
</template>
</el-table-column>
<fu-table-operations
width="200px"
width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
+9 -1
View File
@@ -1,6 +1,6 @@
<template>
<div>
<el-scrollbar max-height="500px">
<el-scrollbar height="525px" class="moz-height">
<div class="h-app-card" v-for="(app, index) in apps" :key="index">
<el-row :gutter="10">
<el-col :span="5">
@@ -88,6 +88,7 @@ defineExpose({
cursor: pointer;
padding: 10px 15px;
margin-right: 10px;
line-height: 18px;
.h-app-content {
padding-left: 15px;
@@ -118,4 +119,11 @@ defineExpose({
border: 0;
border-top: var(--panel-border);
}
/* FOR MOZILLA */
@-moz-document url-prefix() {
.moz-height {
height: 524px;
}
}
</style>
+65 -55
View File
@@ -126,7 +126,7 @@
<div v-if="chartOption === 'io'" style="margin-top: 40px" class="mobile-monitor-chart">
<v-charts
height="305px"
height="383px"
id="ioChart"
type="line"
:option="chartsOption['ioChart']"
@@ -136,7 +136,7 @@
</div>
<div v-if="chartOption === 'network'" style="margin-top: 40px" class="mobile-monitor-chart">
<v-charts
height="305px"
height="383px"
id="networkChart"
type="line"
:option="chartsOption['networkChart']"
@@ -151,56 +151,58 @@
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<CardWithHeader :header="$t('home.systemInfo')">
<template #body>
<el-descriptions :column="1" class="h-systemInfo">
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.hostname') }}
</span>
</template>
{{ baseInfo.hostname }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.platformVersion') }}
</span>
</template>
{{ baseInfo.platform }}-{{ baseInfo.platformVersion }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.kernelVersion') }}
</span>
</template>
{{ baseInfo.kernelVersion }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.kernelArch') }}
</span>
</template>
{{ baseInfo.kernelArch }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.uptime') }}
</span>
</template>
{{ currentInfo.timeSinceUptime }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.runningTime') }}
</span>
</template>
{{ loadUpTime(currentInfo.uptime) }}
</el-descriptions-item>
</el-descriptions>
<el-scrollbar>
<el-descriptions :column="1" class="h-systemInfo">
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.hostname') }}
</span>
</template>
{{ baseInfo.hostname }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.platformVersion') }}
</span>
</template>
{{ baseInfo.platform }}-{{ baseInfo.platformVersion }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.kernelVersion') }}
</span>
</template>
{{ baseInfo.kernelVersion }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.kernelArch') }}
</span>
</template>
{{ baseInfo.kernelArch }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.uptime') }}
</span>
</template>
{{ currentInfo.timeSinceUptime }}
</el-descriptions-item>
<el-descriptions-item class-name="system-content">
<template #label>
<span class="system-label">
{{ $t('home.runningTime') }}
</span>
</template>
{{ loadUpTime(currentInfo.uptime) }}
</el-descriptions-item>
</el-descriptions>
</el-scrollbar>
</template>
</CardWithHeader>
@@ -239,6 +241,7 @@ const isSafety = ref();
const chartOption = ref('network');
let timer: NodeJS.Timer | null = null;
let isInit = ref<boolean>(true);
let isStatusInit = ref<boolean>(true);
let isActive = ref(true);
const ioReadBytes = ref<Array<number>>([]);
@@ -355,7 +358,8 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
baseInfo.value = res.data;
currentInfo.value = baseInfo.value.currentInfo;
await onLoadCurrentInfo();
statuRef.value.acceptParams(currentInfo.value, baseInfo.value);
isStatusInit.value = false;
statuRef.value.acceptParams(currentInfo.value, baseInfo.value, isStatusInit.value);
appRef.value.acceptParams();
if (isInit) {
timer = setInterval(async () => {
@@ -421,7 +425,7 @@ const onLoadCurrentInfo = async () => {
}
loadData();
currentInfo.value = res.data;
statuRef.value.acceptParams(currentInfo.value, baseInfo.value);
statuRef.value.acceptParams(currentInfo.value, baseInfo.value, isStatusInit.value);
};
function loadUpTime(uptime: number) {
@@ -460,7 +464,7 @@ function loadUpTime(uptime: number) {
);
}
if (minutes !== 0) {
return minutes + i18n.global.t('home.Minute') + ' ' + seconds + i18n.global.t('commons.units.second');
return minutes + i18n.global.t('commons.units.minute') + ' ' + seconds + i18n.global.t('commons.units.second');
}
return seconds + i18n.global.t('commons.units.second');
}
@@ -561,6 +565,12 @@ onBeforeUnmount(() => {
.h-systemInfo {
margin-left: 18px;
height: 216px;
}
@-moz-document url-prefix() {
.h-systemInfo {
height: auto;
}
}
.system-label {
+2 -2
View File
@@ -201,7 +201,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
shotTime: new Date(),
});
const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo): void => {
const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo, isInit: boolean): void => {
currentInfo.value = current;
baseInfo.value = base;
freshChart('cpu', 'CPU', formatNumber(currentInfo.value.cpuUsedPercent));
@@ -215,7 +215,7 @@ const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo):
freshChart('disk' + i, itemPath, formatNumber(currentInfo.value.diskData[i].usedPercent));
}
if (currentInfo.value.diskData.length > 5) {
showMore.value = false;
showMore.value = isInit ? false : showMore.value || false;
}
});
};
@@ -156,7 +156,7 @@
:ellipsis="3"
:buttons="buttons"
:label="$t('commons.table.operate')"
min-width="200"
min-width="300"
fixed="right"
fix
/>
+138 -328
View File
@@ -37,12 +37,21 @@
></el-date-picker>
</div>
</template>
<div id="loadLoadChart" class="chart"></div>
<div class="chart">
<v-charts
height="400px"
id="loadLoadChart"
type="line"
:option="chartsOption['loadLoadChart']"
v-if="chartsOption['loadLoadChart']"
:dataZoom="true"
/>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
@@ -60,10 +69,19 @@
></el-date-picker>
</div>
</template>
<div id="loadCPUChart" class="chart"></div>
<div class="chart">
<v-charts
height="400px"
id="loadCPUChart"
type="line"
:option="chartsOption['loadCPUChart']"
v-if="chartsOption['loadCPUChart']"
:dataZoom="true"
/>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
@@ -81,12 +99,21 @@
></el-date-picker>
</div>
</template>
<div id="loadMemoryChart" class="chart"></div>
<div class="chart">
<v-charts
height="400px"
id="loadMemoryChart"
type="line"
:option="chartsOption['loadMemoryChart']"
v-if="chartsOption['loadMemoryChart']"
:dataZoom="true"
/>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
@@ -104,10 +131,19 @@
></el-date-picker>
</div>
</template>
<div id="loadIOChart" class="chart"></div>
<div class="chart">
<v-charts
height="400px"
id="loadIOChart"
type="line"
:option="chartsOption['loadIOChart']"
v-if="chartsOption['loadIOChart']"
:dataZoom="true"
/>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
@@ -148,7 +184,16 @@
></el-date-picker>
</div>
</template>
<div id="loadNetworkChart" class="chart"></div>
<div class="chart">
<v-charts
height="400px"
id="loadNetworkChart"
type="line"
:option="chartsOption['loadNetworkChart']"
v-if="chartsOption['loadNetworkChart']"
:dataZoom="true"
/>
</div>
</el-card>
</el-col>
</el-row>
@@ -156,8 +201,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue';
import * as echarts from 'echarts';
import { ref, reactive, onMounted, computed } from 'vue';
import { loadMonitor, getNetworkOptions } from '@/api/modules/monitor';
import { Monitor } from '@/api/interface/monitor';
import { computeSizeFromKBs, dateFormatWithoutYear } from '@/utils/util';
@@ -181,6 +225,8 @@ const timeRangeIO = ref<[Date, Date]>([new Date(new Date().setHours(0, 0, 0, 0))
const timeRangeNetwork = ref<[Date, Date]>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const networkChoose = ref();
const netOptions = ref();
const chartsOption = ref({ loadLoadChart: null, loadCPUChart: null, loadMemoryChart: null, loadNetworkChart: null });
const shortcuts = [
{
text: i18n.global.t('monitor.today'),
@@ -287,50 +333,34 @@ const search = async (param: string) => {
return item.cpu.toFixed(2);
});
cpuData = cpuData.length === 0 ? loadEmptyData() : cpuData;
let yDatasOfCpu = {
name: 'CPU',
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
data: cpuData,
showSymbol: false,
chartsOption.value['loadCPUChart'] = {
xDatas: baseDate,
yDatas: [
{
name: 'CPU',
data: cpuData,
},
],
formatStr: '%',
};
initCharts('loadCPUChart', baseDate, yDatasOfCpu, 'CPU', '%');
}
if (param === 'memory' || param === 'all') {
let memoryData = item.value.map(function (item: any) {
return item.memory.toFixed(2);
});
memoryData = memoryData.length === 0 ? loadEmptyData() : memoryData;
let yDatasOfMem = {
name: i18n.global.t('monitor.memory'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
data: memoryData,
showSymbol: false,
chartsOption.value['loadMemoryChart'] = {
xDatas: baseDate,
yDatas: [
{
name: i18n.global.t('monitor.memory'),
data: memoryData,
},
],
formatStr: '%',
};
initCharts('loadMemoryChart', baseDate, yDatasOfMem, i18n.global.t('monitor.memory'), '%');
}
if (param === 'load' || param === 'all') {
initLoadCharts(item);
@@ -348,47 +378,30 @@ const search = async (param: string) => {
return item.up.toFixed(2);
});
networkUp = networkUp.length === 0 ? loadEmptyData() : networkUp;
let yDatasOfUp = {
name: i18n.global.t('monitor.up'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
data: networkUp,
showSymbol: false,
};
let networkOut = item.value.map(function (item: any) {
return item.down.toFixed(2);
});
networkOut = networkOut.length === 0 ? loadEmptyData() : networkOut;
let yDatasOfDown = {
name: i18n.global.t('monitor.down'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(27, 143, 60, .5)',
},
{
offset: 1,
color: 'rgba(27, 143, 60, 0)',
},
]),
chartsOption.value['loadNetworkChart'] = {
xDatas: networkDate,
yDatas: [
{
name: i18n.global.t('monitor.up'),
data: networkUp,
},
{
name: i18n.global.t('monitor.down'),
data: networkOut,
},
],
grid: {
left: getSideWidth(true),
right: getSideWidth(true),
bottom: '20%',
},
data: networkOut,
showSymbol: false,
formatStr: 'KB/s',
};
initCharts('loadNetworkChart', networkDate, [yDatasOfUp, yDatasOfDown], 'KB/s', 'KB/s');
}
}
};
@@ -401,43 +414,6 @@ const loadNetworkOptions = async () => {
search('all');
};
function initCharts(chartName: string, xDatas: any, yDatas: any, yTitle: string, formatStr: string) {
const lineChart = echarts.init(document.getElementById(chartName) as HTMLElement);
const option = {
zlevel: 1,
z: 1,
tooltip: {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
if (chartName !== 'loadNetworkChart') {
for (const item of datas) {
res += item.marker + ' ' + item.seriesName + ':' + item.data + formatStr + '<br/>';
}
} else {
for (const item of datas) {
res += item.marker + ' ' + item.seriesName + ':' + computeSizeFromKBs(item.data) + '<br/>';
}
}
return res;
},
},
legend: {
data: chartName === 'loadNetworkChart' && [i18n.global.t('monitor.up'), i18n.global.t('monitor.down')],
},
grid: {
left: getSideWidth(chartName == 'loadNetworkChart'),
right: getSideWidth(chartName == 'loadNetworkChart'),
bottom: '20%',
},
xAxis: { data: xDatas },
yAxis: { name: '( ' + formatStr + ' )', axisLabel: { fontSize: chartName == 'loadNetworkChart' ? 10 : 12 } },
dataZoom: [{ startValue: zoomStart.value }],
series: yDatas,
};
lineChart.setOption(option, true);
}
function initLoadCharts(item: Monitor.MonitorData) {
let itemLoadDate = item.date.length === 0 ? loadEmptyDate(timeRangeLoad.value) : item.date;
let loadDate = itemLoadDate.map(function (item: any) {
@@ -459,33 +435,27 @@ function initLoadCharts(item: Monitor.MonitorData) {
return item.loadUsage.toFixed(2);
});
loadUsage = loadUsage.length === 0 ? loadEmptyData() : loadUsage;
const lineChart = echarts.init(document.getElementById('loadLoadChart') as HTMLElement);
const option = {
zlevel: 1,
z: 1,
tooltip: {
trigger: 'axis',
formatter: function (datas: any) {
let res = datas[0].name + '<br/>';
for (const item of datas) {
res += item.marker + ' ' + item.seriesName + ':' + item.data + '%' + '<br/>';
}
return res;
chartsOption.value['loadLoadChart'] = {
xDatas: loadDate,
yDatas: [
{
name: '1 ' + i18n.global.t('commons.units.minute'),
data: load1Data,
},
},
legend: {
data: [
'1 ' + i18n.global.t('commons.units.minute'),
'5 ' + i18n.global.t('commons.units.minute'),
'15 ' + i18n.global.t('commons.units.minute'),
i18n.global.t('monitor.resourceUsage'),
],
},
grid: { left: '7%', right: '7%', bottom: '20%' },
xAxis: {
data: loadDate,
},
{
name: '5 ' + i18n.global.t('commons.units.minute'),
data: load5Data,
},
{
name: '15 ' + i18n.global.t('commons.units.minute'),
data: load15Data,
},
{
name: i18n.global.t('monitor.resourceUsage'),
data: loadUsage,
yAxisIndex: 1,
},
],
yAxis: [
{ type: 'value', name: i18n.global.t('monitor.loadDetail') + ' ( % )' },
{
@@ -495,84 +465,8 @@ function initLoadCharts(item: Monitor.MonitorData) {
alignTicks: true,
},
],
dataZoom: [{ startValue: zoomStart.value }],
series: [
{
name: '1 ' + i18n.global.t('commons.units.minute'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
showSymbol: false,
data: load1Data,
},
{
name: '5 ' + i18n.global.t('commons.units.minute'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(27, 143, 60, .5)',
},
{
offset: 1,
color: 'rgba(27, 143, 60, 0)',
},
]),
},
showSymbol: false,
data: load5Data,
},
{
name: '15 ' + i18n.global.t('commons.units.minute'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(249, 199, 79, .5)',
},
{
offset: 1,
color: 'rgba(249, 199, 79, 0)',
},
]),
},
showSymbol: false,
data: load15Data,
},
{
name: i18n.global.t('monitor.resourceUsage'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(255, 173, 177, 0.5)',
},
{
offset: 1,
color: 'rgba(255, 173, 177, 0)',
},
]),
},
showSymbol: false,
data: loadUsage,
yAxisIndex: 1,
},
],
formatStr: '%',
};
lineChart.setOption(option, true);
}
function initIOCharts(item: Monitor.MonitorData) {
@@ -596,11 +490,28 @@ function initIOCharts(item: Monitor.MonitorData) {
return item.time;
});
ioTime = ioTime.length === 0 ? loadEmptyData() : ioTime;
const lineChart = echarts.init(document.getElementById('loadIOChart') as HTMLElement);
const option = {
zlevel: 1,
z: 1,
chartsOption.value['loadIOChart'] = {
xDatas: ioDate,
yDatas: [
{
name: i18n.global.t('monitor.read'),
data: ioRead,
},
{
name: i18n.global.t('monitor.write'),
data: ioWrite,
},
{
name: i18n.global.t('monitor.readWriteCount'),
data: ioCount,
yAxisIndex: 1,
},
{
name: i18n.global.t('monitor.readWriteTime'),
data: ioTime,
yAxisIndex: 1,
},
],
tooltip: {
trigger: 'axis',
formatter: function (datas: any) {
@@ -631,18 +542,7 @@ function initIOCharts(item: Monitor.MonitorData) {
return res;
},
},
legend: {
data: [
i18n.global.t('monitor.read'),
i18n.global.t('monitor.write'),
i18n.global.t('monitor.readWriteCount'),
i18n.global.t('monitor.readWriteTime'),
],
},
grid: { left: getSideWidth(true), right: getSideWidth(true), bottom: '20%' },
xAxis: {
data: ioDate,
},
yAxis: [
{ type: 'value', name: '( KB/s )', axisLabel: { fontSize: 10 } },
{
@@ -654,85 +554,7 @@ function initIOCharts(item: Monitor.MonitorData) {
},
},
],
dataZoom: [{ startValue: zoomStart.value }],
series: [
{
name: i18n.global.t('monitor.read'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
},
]),
},
showSymbol: false,
data: ioRead,
},
{
name: i18n.global.t('monitor.write'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(27, 143, 60, .5)',
},
{
offset: 1,
color: 'rgba(27, 143, 60, 0)',
},
]),
},
showSymbol: false,
data: ioWrite,
},
{
name: i18n.global.t('monitor.readWriteCount'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(249, 199, 79, .5)',
},
{
offset: 1,
color: 'rgba(249, 199, 79, 0)',
},
]),
},
showSymbol: false,
data: ioCount,
yAxisIndex: 1,
},
{
name: i18n.global.t('monitor.readWriteTime'),
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(255, 173, 177, 0.5)',
},
{
offset: 1,
color: 'rgba(255, 173, 177, 0)',
},
]),
},
showSymbol: false,
data: ioTime,
yAxisIndex: 1,
},
],
};
lineChart.setOption(option, true);
}
function loadEmptyDate(timeRange: any) {
@@ -751,21 +573,9 @@ function getSideWidth(b: boolean) {
return !b || document.body.clientWidth > 1600 ? '7%' : '10%';
}
function changeChartSize() {
echarts.getInstanceByDom(document.getElementById('loadLoadChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadCPUChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadMemoryChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadIOChart') as HTMLElement)?.resize();
echarts.getInstanceByDom(document.getElementById('loadNetworkChart') as HTMLElement)?.resize();
}
onMounted(() => {
zoomStart.value = dateFormatWithoutYear(new Date(new Date().setHours(0, 0, 0, 0)));
loadNetworkOptions();
window.addEventListener('resize', changeChartSize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', changeChartSize);
});
</script>
+1 -1
View File
@@ -54,7 +54,7 @@
<el-col :span="1"><br /></el-col>
<el-col :xs="24" :sm="20" :md="20" :lg="10" :xl="10">
<el-form :model="form" label-position="left" ref="formRef" label-width="120px">
<el-form-item :label="$t('ssh.port')" prop="port">
<el-form-item :label="$t('commons.table.port')" prop="port">
<el-input disabled v-model.number="form.port">
<template #append>
<el-button @click="onChangePort" icon="Setting">
@@ -8,12 +8,12 @@
size="30%"
>
<template #header>
<DrawerHeader :header="$t('ssh.port')" :back="handleClose" />
<DrawerHeader :header="$t('commons.table.port')" :back="handleClose" />
</template>
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('ssh.port')" prop="port" :rules="Rules.port">
<el-form-item :label="$t('commons.table.port')" prop="port" :rules="Rules.port">
<el-input clearable v-model.number="form.port" />
</el-form-item>
</el-col>
@@ -63,7 +63,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => {
if (!valid) return;
ElMessageBox.confirm(
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.port'), form.port]),
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('commons.table.port'), form.port]),
i18n.global.t('ssh.sshChange'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
@@ -1,6 +1,9 @@
<template>
<div>
<LayoutContent v-loading="loading" :title="$t('terminal.quickCommand')">
<template #prompt>
<el-alert type="info" :title="$t('terminal.quickCommandHelper')" :closable="false" />
</template>
<template #toolbar>
<el-button type="primary" @click="onCreate()">
{{ $t('commons.button.create') }}{{ $t('terminal.quickCommand') }}
@@ -332,9 +332,9 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
for (const tab of terminalTabs.value) {
if (tab.title.indexOf('@127.0.0.1:') !== -1 || tab.title === i18n.global.t('terminal.localhost')) {
onReconnect(tab);
return;
}
}
return;
}
terminalTabs.value.push({
index: tabIndex,
@@ -344,7 +344,7 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
latency: 0,
});
terminalValue.value = tabIndex;
if (!res.data && isLocal) {
if (!res.data && title === i18n.global.t('terminal.localhost')) {
dialogRef.value!.acceptParams({ isLocal: true });
}
nextTick(() => {
@@ -7,7 +7,7 @@
:rules="rules"
ref="leechRef"
label-position="right"
label-width="120px"
label-width="180px"
class="moblie-form"
>
<el-form-item :label="$t('website.enableOrNot')">
@@ -207,7 +207,7 @@ const rules = ref({
});
const resData = ref();
const sslReq = reactive({
acmeAccountID: 0,
acmeAccountID: '',
});
const getPrivateKeyPath = (path: string) => {
@@ -218,11 +218,20 @@ const getCertificatePath = (path: string) => {
form.certificatePath = path;
};
const listSSL = () => {
sslReq.acmeAccountID = form.acmeAccountID;
sslReq.acmeAccountID = String(form.acmeAccountID);
ListSSL(sslReq).then((res) => {
ssls.value = res.data || [];
if (ssls.value.length > 0) {
form.websiteSSLId = ssls.value[0].id;
let exist = false;
for (const ssl of ssls.value) {
if (ssl.id === form.websiteSSLId) {
exist = true;
break;
}
}
if (!exist) {
form.websiteSSLId = ssls.value[0].id;
}
changeSSl(form.websiteSSLId);
} else {
websiteSSL.value = {};
@@ -269,7 +278,6 @@ const get = () => {
if (res.data.SSL && res.data.SSL.id > 0) {
form.websiteSSLId = res.data.SSL.id;
websiteSSL.value = res.data.SSL;
sslReq.acmeAccountID = res.data.SSL.acmeAccountId;
form.acmeAccountID = res.data.SSL.acmeAccountId;
}
}
@@ -1,7 +1,7 @@
<template>
<el-row :gutter="20" v-loading="loading">
<el-col :xs="24" :sm="18" :md="8" :lg="8" :xl="8">
<el-form ref="websiteForm" label-position="right" label-width="80px" :model="form" :rules="rules">
<el-form ref="websiteForm" label-position="right" label-width="150px" :model="form" :rules="rules">
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain">
<el-input v-model="form.primaryDomain" disabled></el-input>
</el-form-item>