Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
887db0aff7 | ||
![]() |
4a974b7e0a | ||
![]() |
fb286d2def | ||
![]() |
89cb9e6693 | ||
![]() |
927def4472 | ||
![]() |
84fcd31704 | ||
![]() |
005e5cc01f | ||
![]() |
2896409b3a | ||
![]() |
791641f3e1 | ||
![]() |
79f266bbda | ||
![]() |
c9edaf0d1d | ||
![]() |
ac5f73c687 | ||
![]() |
cc0667429a | ||
![]() |
92a5d6faeb | ||
![]() |
1111b6b494 | ||
![]() |
be5a7c99e1 | ||
![]() |
d6a963c087 | ||
![]() |
af753bdffe | ||
![]() |
59b025353f | ||
![]() |
355a6b0205 | ||
![]() |
7de80e9d5a | ||
![]() |
4c6d8cd20c | ||
![]() |
31da89d63a | ||
![]() |
e044ca7d12 | ||
![]() |
7c037b68cd | ||
![]() |
9080824a59 | ||
![]() |
11f4bc2c89 | ||
![]() |
a64ddd1eb8 | ||
![]() |
2fdaecfafe | ||
![]() |
6d1fe20736 | ||
![]() |
f61bc047cd | ||
![]() |
4f52580938 | ||
![]() |
281d0cf880 | ||
![]() |
fdf9215d43 | ||
![]() |
8c182e907d | ||
![]() |
e55af04568 | ||
![]() |
2462ffdbab | ||
![]() |
bfeb57e24f | ||
![]() |
9bb28cda27 |
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -25,7 +25,7 @@ body:
|
||||
required: true
|
||||
attributes:
|
||||
label: "1Panel 版本"
|
||||
description: "可通过系统右上角下拉菜单中的`关于`选项,或查看安装目录中的 version 文件获取。"
|
||||
description: "登录 1Panel Web 控制台,在页面右下角查看当前版本。"
|
||||
- type: markdown
|
||||
id: details
|
||||
attributes:
|
||||
|
2
.github/ISSUE_TEMPLATE/feature.yml
vendored
2
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
required: true
|
||||
attributes:
|
||||
label: "1Panel 版本"
|
||||
description: "可通过系统右上角下拉菜单中的`关于`选项,或查看安装目录中的 version 文件获取。"
|
||||
description: "登录 1Panel Web 控制台,在页面右下角查看当前版本。"
|
||||
- type: markdown
|
||||
id: details
|
||||
attributes:
|
||||
|
17
.github/workflows/add-labels-for-pr.yml
vendored
Normal file
17
.github/workflows/add-labels-for-pr.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on: pull_request
|
||||
|
||||
name: 1Panel 通用 PR 处理
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
generic_handler:
|
||||
name: 为 PR 添加标签
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUBTOKEN }}
|
||||
labels: ${{ github.base_ref }}
|
17
.github/workflows/create-pr-from-push.yml
vendored
Normal file
17
.github/workflows/create-pr-from-push.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'pr@**'
|
||||
- 'repr@**'
|
||||
|
||||
name: 针对特定分支名自动创建 PR
|
||||
|
||||
jobs:
|
||||
generic_handler:
|
||||
name: 自动创建 PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create pull request
|
||||
uses: jumpserver/action-generic-handler@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }}
|
16
.github/workflows/issue-close.yml
vendored
Normal file
16
.github/workflows/issue-close.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Issue Close Check
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
issue-close-remove-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remove labels
|
||||
uses: actions-cool/issues-helper@v2
|
||||
if: ${{ !github.event.issue.pull_request }}
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
labels: '状态:待处理'
|
38
.github/workflows/issue-comment.yml
vendored
Normal file
38
.github/workflows/issue-comment.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
name: Add issues workflow labels
|
||||
|
||||
jobs:
|
||||
add-label-if-is-author:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (github.event.issue.user.id == github.event.comment.user.id) && (!github.event.issue.pull_request) }}
|
||||
steps:
|
||||
- name: Add require handle label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
labels: '状态:待处理'
|
||||
|
||||
- name: Remove require reply label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
labels: '状态:待用户反馈'
|
||||
|
||||
add-label-if-not-author:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (github.event.issue.user.id != github.event.comment.user.id) && (!github.event.issue.pull_request) && (github.event.issue.state == 'open') }}
|
||||
steps:
|
||||
- name: Add require replay label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
labels: '状态:待用户反馈'
|
||||
|
||||
- name: Remove require handle label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
labels: '状态:待处理'
|
16
.github/workflows/issue-open.yml
vendored
Normal file
16
.github/workflows/issue-open.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Issue Open Check
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
issue-open-add-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add labels
|
||||
uses: actions-cool/issues-helper@v2
|
||||
if: ${{ !github.event.issue.pull_request }}
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
labels: '状态:待处理'
|
17
.github/workflows/issue-recent-alert.yml
vendored
Normal file
17
.github/workflows/issue-recent-alert.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
name: Check recent handle issues
|
||||
|
||||
jobs:
|
||||
check-recent-issues-not-handle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check recent issues and send msg
|
||||
uses: jumpserver/action-issues-alert@master
|
||||
with:
|
||||
hook: ${{ secrets.WECHAT_GROUP_WEB_HOOK }}
|
||||
type: recent
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
17
.github/workflows/issue-untimely-alert.yml
vendored
Normal file
17
.github/workflows/issue-untimely-alert.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 9 * * 1-5"
|
||||
|
||||
name: Check untimely handle issues
|
||||
|
||||
jobs:
|
||||
check-untimely-handle-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check untimely issues and send msg
|
||||
uses: jumpserver/action-issues-alert@master
|
||||
with:
|
||||
hook: ${{ secrets.WECHAT_GROUP_WEB_HOOK }}
|
||||
type: untimely
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,5 +22,4 @@ cmd/server/__debug_bin
|
||||
cmd/server/web/assets
|
||||
cmd/server/web/monacoeditorwork
|
||||
cmd/server/web/index.html
|
||||
cmd/server/web/favicon.png
|
||||
frontend/auto-imports.d.ts
|
||||
|
11
OWNERS
Normal file
11
OWNERS
Normal file
@@ -0,0 +1,11 @@
|
||||
reviewers:
|
||||
- zhengkunwang223
|
||||
- ssongliu
|
||||
- wanghe-fit2cloud
|
||||
- wangdan-fit2cloud
|
||||
|
||||
approvers:
|
||||
- zhengkunwang223
|
||||
- ssongliu
|
||||
- wanghe-fit2cloud
|
||||
- wangdan-fit2cloud
|
14
README.md
14
README.md
@@ -11,8 +11,8 @@
|
||||
|
||||
1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括:
|
||||
|
||||
- **快速建站**:深度集成 Wordpress 和 Halo,域名绑定、SSL 证书配置等一键搞定;
|
||||
- **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理及常用应用软件管理;
|
||||
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定;
|
||||
- **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括应用管理、主机监控、文件管理、数据库管理、容器管理等;
|
||||
- **安全可靠**:最小漏洞暴露面,提供防火墙和安全审计等功能;
|
||||
- **一键备份**:支持一键备份和恢复,备份数据云端存储,永不丢失。
|
||||
|
||||
@@ -30,16 +30,16 @@
|
||||
|
||||
**一键安装**
|
||||
|
||||
以 root 用户执行如下命令一键安装 1Panel:
|
||||
执行如下命令一键安装 1Panel:
|
||||
|
||||
```sh
|
||||
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sh quick_start.sh
|
||||
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
|
||||
```
|
||||
|
||||
**学习资料**
|
||||
|
||||
- [在线文档](https://1panel.cn/docs/)
|
||||
- [入门视频](https://1panel.cn/video.html)
|
||||
- [教学视频](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760)
|
||||
|
||||
## 社区
|
||||
|
||||
@@ -56,6 +56,10 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
|
||||
- 邮箱:support@fit2cloud.com
|
||||
- 电话:400-052-0755
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#1Panel-dev/1Panel&Date)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
|
||||
|
@@ -1,6 +1,8 @@
|
||||
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"
|
||||
@@ -27,6 +29,23 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if len(req.Credential) != 0 {
|
||||
credential, err := base64.StdEncoding.DecodeString(req.Credential)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Credential = string(credential)
|
||||
}
|
||||
if len(req.AccessKey) != 0 {
|
||||
accessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.AccessKey = string(accessKey)
|
||||
}
|
||||
|
||||
if err := backupService.Create(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -52,6 +71,23 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if len(req.Credential) != 0 {
|
||||
credential, err := base64.StdEncoding.DecodeString(req.Credential)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Credential = string(credential)
|
||||
}
|
||||
if len(req.AccessKey) != 0 {
|
||||
accessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.AccessKey = string(accessKey)
|
||||
}
|
||||
|
||||
buckets, err := backupService.GetBuckets(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@@ -139,7 +175,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
c.File(filePath)
|
||||
helper.SuccessWithData(c, filePath)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
@@ -188,6 +224,23 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if len(req.Credential) != 0 {
|
||||
credential, err := base64.StdEncoding.DecodeString(req.Credential)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Credential = string(credential)
|
||||
}
|
||||
if len(req.AccessKey) != 0 {
|
||||
accessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.AccessKey = string(accessKey)
|
||||
}
|
||||
|
||||
if err := backupService.Update(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
@@ -198,7 +198,7 @@ func (b *BaseApi) TargetDownload(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
c.File(filePath)
|
||||
helper.SuccessWithData(c, filePath)
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
|
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -29,6 +30,15 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
|
||||
if _, err := mysqlService.Create(context.Background(), req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -81,6 +91,15 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if len(req.Value) != 0 {
|
||||
value, err := base64.StdEncoding.DecodeString(req.Value)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Value = string(value)
|
||||
}
|
||||
|
||||
if err := mysqlService.ChangePassword(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -106,6 +107,15 @@ func (b *BaseApi) ChangeRedisPassword(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if len(req.Value) != 0 {
|
||||
value, err := base64.StdEncoding.DecodeString(req.Value)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Value = string(value)
|
||||
}
|
||||
|
||||
if err := redisService.ChangePassword(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
@@ -3,6 +3,14 @@ package v1
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
@@ -14,13 +22,6 @@ import (
|
||||
websocket2 "github.com/1Panel-dev/1Panel/backend/utils/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// @Tags File
|
||||
@@ -444,6 +445,28 @@ func (b *BaseApi) Download(c *gin.Context) {
|
||||
c.File(filePath)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Download file with path
|
||||
// @Description 下载指定文件
|
||||
// @Accept json
|
||||
// @Param request body request.FilePath true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/download/bypath [post]
|
||||
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [path]","formatEN":"Download file [path]"}
|
||||
func (b *BaseApi) DownloadFile(c *gin.Context) {
|
||||
var req dto.FilePath
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
c.File(req.Path)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Load file size
|
||||
// @Description 获取文件夹大小
|
||||
@@ -558,7 +581,7 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
filename := c.PostForm("filename")
|
||||
fileDir := filepath.Join(global.CONF.System.DataDir, "upload", filename)
|
||||
|
||||
os.MkdirAll(fileDir, 0755)
|
||||
_ = os.MkdirAll(fileDir, 0755)
|
||||
filePath := filepath.Join(fileDir, filename)
|
||||
|
||||
emptyFile, err := os.Create(filePath)
|
||||
@@ -584,7 +607,6 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
if chunkIndex+1 == chunkCount {
|
||||
err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrAppDelete, err)
|
||||
return
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
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"
|
||||
@@ -29,6 +31,23 @@ 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
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
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
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
}
|
||||
|
||||
host, err := hostService.Create(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@@ -55,6 +74,22 @@ func (b *BaseApi) TestByInfo(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
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
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
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
}
|
||||
|
||||
var connInfo ssh.ConnInfo
|
||||
_ = copier.Copy(&connInfo, &req)
|
||||
@@ -211,6 +246,22 @@ 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)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
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
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
|
@@ -22,6 +22,7 @@ type ContainerInfo struct {
|
||||
State string `json:"state"`
|
||||
RunTime string `json:"runTime"`
|
||||
|
||||
IsFromApp bool `json:"isFromApp"`
|
||||
IsFromCompose bool `json:"isFromCompose"`
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,7 @@ type ICronjobRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
StartRecords(cronjobID uint, targetPath string) model.JobRecords
|
||||
StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords
|
||||
EndRecords(record model.JobRecords, status, message, records string)
|
||||
}
|
||||
|
||||
@@ -112,10 +112,11 @@ func (c *CronjobRepo) WithByJobID(id int) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath string) model.JobRecords {
|
||||
func (u *CronjobRepo) StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords {
|
||||
var record model.JobRecords
|
||||
record.StartTime = time.Now()
|
||||
record.CronjobID = cronjobID
|
||||
record.FromLocal = fromLocal
|
||||
record.Status = constant.StatusWaiting
|
||||
if err := global.DB.Create(&record).Error; err != nil {
|
||||
global.LOG.Errorf("create record status failed, err: %v", err)
|
||||
|
@@ -134,7 +134,7 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
tempPath := fmt.Sprintf("%sdownload%s", constant.DataDir, info.FileDir)
|
||||
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(tempPath, os.ModePerm); err != nil {
|
||||
fmt.Println(err)
|
||||
global.LOG.Errorf("mkdir %s failed, err: %v", tempPath, err)
|
||||
}
|
||||
}
|
||||
targetPath := tempPath + info.FileName
|
||||
|
@@ -181,16 +181,19 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
return err
|
||||
}
|
||||
if err := handleMysqlRecover(mysqlInfo, tmpPath, db.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
|
||||
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := handleUnTar(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key)); err != nil {
|
||||
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
oldInstall.Status = constant.Running
|
||||
if err := appInstallRepo.Save(install); err != nil {
|
||||
global.LOG.Errorf("save db app install failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
|
@@ -106,8 +106,8 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
||||
}
|
||||
defer func() {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := handleWebsiteRecover(website, rollbackFile, true); err != nil {
|
||||
global.LOG.Errorf("rollback website %s from %s failed, err: %v", website.Alias, rollbackFile, err)
|
||||
return
|
||||
@@ -126,6 +126,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
}
|
||||
nginxConfPath := fmt.Sprintf("%s/openresty/%s/conf/conf.d", constant.AppInstallDir, nginxInfo.Name)
|
||||
if err := fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), nginxConfPath); err != nil {
|
||||
global.LOG.Errorf("handle recover from conf.d failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -135,22 +136,27 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
return err
|
||||
}
|
||||
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true); err != nil {
|
||||
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.App.Key, app.Name)); err != nil {
|
||||
global.LOG.Errorf("docker-compose restart failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
siteDir := fmt.Sprintf("%s/openresty/%s/www/sites", constant.AppInstallDir, nginxInfo.Name)
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir); err != nil {
|
||||
global.LOG.Errorf("handle recover from web.tar.gz failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("nginx -s reload failed, err: %s", stdout)
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
||||
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
|
||||
global.LOG.Errorf("handle save website data failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
|
@@ -98,6 +98,10 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
if _, ok := container.Labels[composeProjectLabel]; ok {
|
||||
IsFromCompose = true
|
||||
}
|
||||
IsFromApp := false
|
||||
if created, ok := container.Labels[composeCreatedBy]; ok && created == "Apps" {
|
||||
IsFromApp = true
|
||||
}
|
||||
backDatas = append(backDatas, dto.ContainerInfo{
|
||||
ContainerID: container.ID,
|
||||
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
@@ -106,6 +110,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
ImageName: container.Image,
|
||||
State: container.State,
|
||||
RunTime: container.Status,
|
||||
IsFromApp: IsFromApp,
|
||||
IsFromCompose: IsFromCompose,
|
||||
})
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -152,11 +153,16 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
req.Path = path
|
||||
}
|
||||
global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name)
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
||||
if req.From == "path" {
|
||||
req.Name = path.Base(strings.ReplaceAll(req.Path, "/docker-compose.yml", ""))
|
||||
}
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
_, _ = compose.Down(req.Path)
|
||||
return errors.New(stdout)
|
||||
}
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
@@ -80,6 +82,9 @@ func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
|
||||
}
|
||||
for _, id := range req.Names {
|
||||
if err := client.NetworkRemove(context.TODO(), id); err != nil {
|
||||
if strings.Contains(err.Error(), "has active endpoints") {
|
||||
return buserr.WithDetail(constant.ErrInUsed, id, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -2,12 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"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/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -96,6 +96,9 @@ func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
|
||||
}
|
||||
for _, id := range req.Names {
|
||||
if err := client.VolumeRemove(context.TODO(), id, true); err != nil {
|
||||
if strings.Contains(err.Error(), "volume is in use") {
|
||||
return buserr.WithDetail(constant.ErrInUsed, id, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -122,12 +125,8 @@ func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error {
|
||||
DriverOpts: stringsToMap(req.Options),
|
||||
Labels: stringsToMap(req.Labels),
|
||||
}
|
||||
stat, err := client.VolumeCreate(context.TODO(), options)
|
||||
if err != nil {
|
||||
if _, err := client.VolumeCreate(context.TODO(), options); err != nil {
|
||||
return err
|
||||
}
|
||||
// if len(stat.CreatedAt) != 0 {
|
||||
fmt.Println(stat)
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
@@ -2,17 +2,14 @@ package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
@@ -92,69 +89,28 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
|
||||
if cronjob.ID == 0 {
|
||||
return "", constant.ErrRecordNotFound
|
||||
}
|
||||
|
||||
global.LOG.Infof("start to download records %s from %s", cronjob.Type, backup.Type)
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
if backup.Type == "LOCAL" {
|
||||
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
|
||||
return "", constant.ErrRecordNotFound
|
||||
}
|
||||
return record.File, nil
|
||||
}
|
||||
if record.FromLocal {
|
||||
local, _ := loadLocalDir()
|
||||
if _, err := os.Stat(local + "/" + record.File); err == nil {
|
||||
return local + "/" + record.File, nil
|
||||
}
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
varMap["type"] = backup.Type
|
||||
if backup.Type != "LOCAL" {
|
||||
varMap["bucket"] = backup.Bucket
|
||||
switch backup.Type {
|
||||
case constant.Sftp:
|
||||
varMap["username"] = backup.AccessKey
|
||||
varMap["password"] = backup.Credential
|
||||
case constant.OSS, constant.S3, constant.MinIo:
|
||||
varMap["accessKey"] = backup.AccessKey
|
||||
varMap["secretKey"] = backup.Credential
|
||||
}
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Info("new backup client successful")
|
||||
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
|
||||
name := fmt.Sprintf("%s%s.tar.gz", commonDir, record.StartTime.Format("20060102150405"))
|
||||
if cronjob.Type == "database" {
|
||||
name = fmt.Sprintf("%s%s.gz", commonDir, record.StartTime.Format("20060102150405"))
|
||||
}
|
||||
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, commonDir)
|
||||
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(tempPath, os.ModePerm); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
global.LOG.Infof("download records %s from %s to %s", name, commonDir, tempPath)
|
||||
targetPath := tempPath + strings.ReplaceAll(name, commonDir, "")
|
||||
if _, err = os.Stat(targetPath); err != nil && os.IsNotExist(err) {
|
||||
isOK, err := backClient.Download(name, targetPath)
|
||||
if !isOK {
|
||||
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
return targetPath, nil
|
||||
}
|
||||
if _, ok := varMap["dir"]; !ok {
|
||||
return "", errors.New("load local backup dir failed")
|
||||
}
|
||||
global.LOG.Infof("record is save in local dir %s", varMap["dir"])
|
||||
|
||||
switch cronjob.Type {
|
||||
case "website":
|
||||
return fmt.Sprintf("%v/website/%s/website_%s_%s.tar.gz", varMap["dir"], cronjob.Website, cronjob.Website, record.StartTime.Format("20060102150405")), nil
|
||||
case "database":
|
||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("load mysqlInfo failed, err: %v", err)
|
||||
}
|
||||
return fmt.Sprintf("%v/database/mysql/%s/%s/db_%s_%s.sql.gz", varMap["dir"], mysqlInfo.Name, cronjob.DBName, cronjob.DBName, record.StartTime.Format("20060102150405")), nil
|
||||
case "directory":
|
||||
return fmt.Sprintf("%v/%s/%s/directory%s_%s.tar.gz", varMap["dir"], cronjob.Type, cronjob.Name, strings.ReplaceAll(cronjob.SourceDir, "/", "_"), record.StartTime.Format("20060102150405")), nil
|
||||
default:
|
||||
return "", fmt.Errorf("not support type %s", cronjob.Type)
|
||||
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, record.File)
|
||||
isOK, _ := client.Download(record.File, tempPath)
|
||||
if !isOK || err != nil {
|
||||
return "", constant.ErrRecordNotFound
|
||||
}
|
||||
return tempPath, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) HandleOnce(id uint) error {
|
||||
|
@@ -22,8 +22,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
message []byte
|
||||
err error
|
||||
)
|
||||
record := cronjobRepo.StartRecords(cronjob.ID, "")
|
||||
record.FromLocal = cronjob.KeepLocal
|
||||
record := cronjobRepo.StartRecords(cronjob.ID, cronjob.KeepLocal, "")
|
||||
go func() {
|
||||
switch cronjob.Type {
|
||||
case "shell":
|
||||
@@ -118,6 +117,8 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
if len(record.Name) != 0 {
|
||||
record.FileName = fileName
|
||||
record.FileDir = backupDir
|
||||
@@ -125,7 +126,7 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
record.BackupType = backup.Type
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
record.FileDir = itemFileDir
|
||||
}
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
@@ -133,7 +134,10 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
}
|
||||
}
|
||||
|
||||
fullPath := fmt.Sprintf("%s/%s", record.FileDir, fileName)
|
||||
fullPath := fmt.Sprintf("%s/%s", backupDir, fileName)
|
||||
if backup.Type != "LOCAL" {
|
||||
fullPath = fmt.Sprintf("%s/%s", itemFileDir, fileName)
|
||||
}
|
||||
if backup.Type == "LOCAL" {
|
||||
u.HandleRmExpired(backup.Type, backupDir, cronjob, nil)
|
||||
return fullPath, nil
|
||||
@@ -148,10 +152,13 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
if err != nil {
|
||||
return fullPath, err
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+fileName, fullPath); err != nil {
|
||||
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
|
||||
return fullPath, err
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, backupDir, cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, itemFileDir, cronjob, client)
|
||||
if cronjob.KeepLocal {
|
||||
u.HandleRmExpired("LOCAL", backupDir, cronjob, client)
|
||||
}
|
||||
return fullPath, nil
|
||||
}
|
||||
|
||||
@@ -185,9 +192,7 @@ func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *mo
|
||||
for i := 0; i < len(currentObjs)-int(cronjob.RetainCopies); i++ {
|
||||
_, _ = backClient.Delete(currentObjs[i].(string))
|
||||
}
|
||||
if !cronjob.KeepLocal {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
files, err := ioutil.ReadDir(backupDir)
|
||||
if err != nil {
|
||||
|
@@ -292,7 +292,7 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
|
||||
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user if not exists '%s'@'%s' identified by '%s';", mysql.Username, info.Value, mysql.Password)); err != nil {
|
||||
return err
|
||||
}
|
||||
grantStr := fmt.Sprintf("grant all privileges on %s.* to '%s'@'%s'", mysql.Name, mysql.Username, info.Value)
|
||||
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to '%s'@'%s'", mysql.Name, mysql.Username, info.Value)
|
||||
if app.Version == "5.7.39" {
|
||||
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, mysql.Password)
|
||||
}
|
||||
|
@@ -15,6 +15,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/docker"
|
||||
@@ -378,8 +379,14 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ids := range req.Names {
|
||||
if _, err := client.ImageRemove(context.TODO(), ids, types.ImageRemoveOptions{Force: true, PruneChildren: true}); err != nil {
|
||||
for _, id := range req.Names {
|
||||
if _, err := client.ImageRemove(context.TODO(), id, types.ImageRemoveOptions{Force: true, PruneChildren: true}); err != nil {
|
||||
if strings.Contains(err.Error(), "image is being used") {
|
||||
if strings.Contains(id, "sha256:") {
|
||||
return buserr.New(constant.ErrObjectInUsed)
|
||||
}
|
||||
return buserr.WithDetail(constant.ErrInUsed, id, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,15 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LogService struct{}
|
||||
|
||||
const logs = "https://resource.fit2cloud.com/installation-log.sh"
|
||||
|
||||
type ILogService interface {
|
||||
CreateLoginLog(operation model.LoginLog) error
|
||||
PageLoginLog(search dto.SearchLgLogWithPage) (int64, interface{}, error)
|
||||
@@ -77,3 +80,7 @@ func (u *LogService) CleanLogs(logtype string) error {
|
||||
}
|
||||
return logRepo.CleanLogin()
|
||||
}
|
||||
|
||||
func writeLogs(version string) {
|
||||
_, _ = cmd.Execf("curl -sfL %s | sh -s 1p upgrade %s", logs, version)
|
||||
}
|
||||
|
@@ -6,13 +6,13 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"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/files"
|
||||
)
|
||||
|
||||
@@ -43,8 +43,8 @@ func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isNew, err := compareVersion(currentVersion.Value, string(version))
|
||||
if !isNew || err != nil {
|
||||
isNew := common.CompareVersion(string(version), currentVersion.Value)
|
||||
if !isNew {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
}
|
||||
|
||||
global.LOG.Info("upgrade successful!")
|
||||
go writeLogs(req.Version)
|
||||
_ = settingRepo.Update("SystemVersion", req.Version)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
@@ -175,43 +176,3 @@ func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func compareVersion(version, newVersion string) (bool, error) {
|
||||
if version == newVersion {
|
||||
return false, nil
|
||||
}
|
||||
if len(version) == 0 || len(newVersion) == 0 {
|
||||
return false, fmt.Errorf("incorrect version or new version entered %v -- %v", version, newVersion)
|
||||
}
|
||||
versions := strings.Split(strings.ReplaceAll(version, "v", ""), ".")
|
||||
if len(versions) != 3 {
|
||||
return false, fmt.Errorf("incorrect version input %v", version)
|
||||
}
|
||||
newVersions := strings.Split(strings.ReplaceAll(newVersion, "v", ""), ".")
|
||||
if len(newVersions) != 3 {
|
||||
return false, fmt.Errorf("incorrect newVersions input %v", version)
|
||||
}
|
||||
version1, _ := strconv.Atoi(versions[0])
|
||||
newVersion1, _ := strconv.Atoi(newVersions[0])
|
||||
if newVersion1 > version1 {
|
||||
return true, nil
|
||||
} else if newVersion1 == version1 {
|
||||
version2, _ := strconv.Atoi(versions[1])
|
||||
newVersion2, _ := strconv.Atoi(newVersions[1])
|
||||
if newVersion2 > version2 {
|
||||
return true, nil
|
||||
} else if newVersion2 == version2 {
|
||||
version3, _ := strconv.Atoi(versions[2])
|
||||
newVersion3, _ := strconv.Atoi(newVersions[2])
|
||||
if newVersion3 > version3 {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
@@ -99,6 +99,10 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
|
||||
if err := client.UseHTTP(path.Join(constant.AppInstallDir, constant.AppOpenresty, appInstall.Name, "root")); err != nil {
|
||||
return res, err
|
||||
}
|
||||
case constant.DnsManual:
|
||||
if err := client.UseManualDns(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
domains := []string{create.PrimaryDomain}
|
||||
|
@@ -95,3 +95,9 @@ var (
|
||||
var (
|
||||
ErrTypeOfRedis = "ErrTypeOfRedis"
|
||||
)
|
||||
|
||||
//container
|
||||
var (
|
||||
ErrInUsed = "ErrInUsed"
|
||||
ErrObjectInUsed = "ErrObjectInUsed"
|
||||
)
|
||||
|
@@ -52,4 +52,8 @@ ErrUserIsExist: "The current user already exists. Please enter a new user"
|
||||
ErrDatabaseIsExist: "The current database already exists. Please enter a new database"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "The recovery file type does not match the current persistence mode. Modify the file type and try again"
|
||||
ErrTypeOfRedis: "The recovery file type does not match the current persistence mode. Modify the file type and try again"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
|
||||
ErrObjectInUsed: "This object is in use and cannot be deleted"
|
@@ -52,4 +52,8 @@ ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
ErrDatabaseIsExist: "当前数据库已存在,请重新输入"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
|
||||
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
|
||||
ErrObjectInUsed: "该对象正被使用,无法删除"
|
@@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -35,12 +34,12 @@ func createDir(fileOp files.FileOp, dirPath string) {
|
||||
func createDefaultDockerNetwork() {
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
fmt.Println("init docker client error", err.Error())
|
||||
global.LOG.Errorf("init docker client error", err.Error())
|
||||
return
|
||||
}
|
||||
if !cli.NetworkExist("1panel-network") {
|
||||
if err := cli.CreateNetwork("1panel-network"); err != nil {
|
||||
fmt.Println("init docker client error", err.Error())
|
||||
global.LOG.Errorf("init docker client error", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
@@ -13,7 +12,6 @@ func Init() {
|
||||
return
|
||||
}
|
||||
if setting.AppStoreVersion != "" {
|
||||
fmt.Println(setting.AppStoreVersion)
|
||||
global.LOG.Info("do not sync")
|
||||
return
|
||||
}
|
||||
|
@@ -1,9 +1,6 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@@ -14,6 +11,9 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
@@ -261,7 +261,6 @@ func (w *Writer) CompressFile(logFile string) error {
|
||||
comFileName := path.Base(logFile) + ".gz"
|
||||
filePath := path.Dir(logFile)
|
||||
|
||||
fmt.Println(path.Dir(logFile))
|
||||
if err := op.Compress([]string{logFile}, filePath, comFileName, files.Gz); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -96,7 +96,6 @@ func OperationLog() gin.HandlerFunc {
|
||||
var names []string
|
||||
if funcs.IsList {
|
||||
sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColume, funcs.DB, funcs.InputColume)
|
||||
fmt.Println(value)
|
||||
_ = global.DB.Raw(sql, value).Scan(&names)
|
||||
} else {
|
||||
_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColume, funcs.DB, funcs.InputColume), value).Scan(&names)
|
||||
|
@@ -32,6 +32,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/wget", baseApi.WgetFile)
|
||||
fileRouter.POST("/move", baseApi.MoveFile)
|
||||
fileRouter.POST("/download", baseApi.Download)
|
||||
fileRouter.POST("/download/bypath", baseApi.DownloadFile)
|
||||
fileRouter.POST("/size", baseApi.Size)
|
||||
fileRouter.GET("/ws", baseApi.Ws)
|
||||
fileRouter.GET("/keys", baseApi.Keys)
|
||||
|
@@ -77,7 +77,7 @@ func (oss ossClient) Upload(src, target string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = bucket.PutObjectFromFile(target, src)
|
||||
err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (oss ossClient) Download(src, target string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = bucket.GetObjectToFile(src, target)
|
||||
err = bucket.DownloadFile(src, target, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func Up(filePath string) (string, error) {
|
||||
stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
|
||||
stdout, err := cmd.Execf("docker-compose -f %s up -d --quiet-pull", filePath)
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
|
@@ -1,21 +0,0 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSSH(t *testing.T) {
|
||||
ss := ConnInfo{
|
||||
Addr: "172.16.10.111",
|
||||
Port: 22,
|
||||
User: "root",
|
||||
AuthMode: "password",
|
||||
Password: "Calong@2015",
|
||||
}
|
||||
_, err := ss.NewClient()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(ss.Run("ip a"))
|
||||
}
|
@@ -89,6 +89,7 @@ func NewPrivateKeyClient(email string, privateKey string) (*AcmeClient, error) {
|
||||
func newConfig(user *AcmeUser) *lego.Config {
|
||||
config := lego.NewConfig(user)
|
||||
config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
config.UserAgent = "acm_go/0.0.1"
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
return config
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme/api"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
@@ -154,10 +155,10 @@ func TestSSL(t *testing.T) {
|
||||
// panic(err)
|
||||
//}
|
||||
|
||||
// err = client.Challenge.SetDNS01Provider(&plainDnsProvider{}, dns01.AddDNSTimeout(6*time.Minute))
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
err = client.Challenge.SetDNS01Provider(&manualDnsProvider{}, dns01.AddDNSTimeout(6*time.Minute))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, reg.URI, priKey)
|
||||
if err != nil {
|
||||
|
@@ -115,22 +115,12 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string) error {
|
||||
return c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute))
|
||||
}
|
||||
|
||||
func (c *AcmeClient) UseManualDns(domains []string) (*Resolve, error) {
|
||||
func (c *AcmeClient) UseManualDns() error {
|
||||
p := &manualDnsProvider{}
|
||||
if err := c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute)); err != nil {
|
||||
return nil, nil
|
||||
return err
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
_, err := c.Client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Resolve, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AcmeClient) UseHTTP(path string) error {
|
||||
|
@@ -62,6 +62,25 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/checkupdate": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取应用更新版本",
|
||||
"tags": [
|
||||
"App"
|
||||
],
|
||||
"summary": "Get app list update",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/detail/:appId/:version": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -470,6 +489,48 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/installed/params/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改应用参数",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"App"
|
||||
],
|
||||
"summary": "Change app params",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.AppInstalledUpdate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"installId"
|
||||
],
|
||||
"formatEN": "Application param update [installId]",
|
||||
"formatZH": "应用参数修改 [installId]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/installed/port/change": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -683,6 +744,20 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/demo": {
|
||||
"get": {
|
||||
"description": "判断是否为demo环境",
|
||||
"tags": [
|
||||
"Auth"
|
||||
],
|
||||
"summary": "Check System isDemo",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/init": {
|
||||
"post": {
|
||||
"description": "初始化用户",
|
||||
@@ -3899,6 +3974,43 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/chunkupload": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "分片上传文件",
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "ChunkUpload file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "request",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"path"
|
||||
],
|
||||
"formatEN": "Upload file [path]",
|
||||
"formatZH": "上传文件 [path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/compress": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -4112,6 +4224,48 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/download/bypath": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "下载指定文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "Download file with path",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FilePath"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"path"
|
||||
],
|
||||
"formatEN": "Download file [path]",
|
||||
"formatZH": "下载文件 [path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/loadfile": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -8623,8 +8777,7 @@ var doc = `{
|
||||
"dto.ComposeCreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"name"
|
||||
"from"
|
||||
],
|
||||
"properties": {
|
||||
"file": {
|
||||
@@ -8860,9 +9013,7 @@ var doc = `{
|
||||
],
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "integer",
|
||||
"maximum": 31,
|
||||
"minimum": 1
|
||||
"type": "integer"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -8871,17 +9022,13 @@ var doc = `{
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer",
|
||||
"maximum": 23,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"keepLocal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer",
|
||||
"maximum": 59,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -8942,9 +9089,7 @@ var doc = `{
|
||||
],
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "integer",
|
||||
"maximum": 31,
|
||||
"minimum": 1
|
||||
"type": "integer"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -8953,9 +9098,7 @@ var doc = `{
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer",
|
||||
"maximum": 23,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
@@ -8964,9 +9107,7 @@ var doc = `{
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer",
|
||||
"maximum": 59,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -9060,15 +9201,9 @@ var doc = `{
|
||||
},
|
||||
"dto.DaemonJsonUpdateByFile": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9254,6 +9389,12 @@ var doc = `{
|
||||
"restart",
|
||||
"stop"
|
||||
]
|
||||
},
|
||||
"stopService": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stopSocket": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11042,6 +11183,22 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.AppInstalledUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"installId",
|
||||
"params"
|
||||
],
|
||||
"properties": {
|
||||
"installId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.AppSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -11284,6 +11441,17 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.FilePath": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.FilePathCheck": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -12042,7 +12210,22 @@ var doc = `{
|
||||
"response.AppParam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"edit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"labelEn": {
|
||||
"type": "string"
|
||||
},
|
||||
"labelZh": {
|
||||
"type": "string"
|
||||
},
|
||||
"rule": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {}
|
||||
@@ -12185,6 +12368,9 @@ var doc = `{
|
||||
"appInstallId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"appName": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@@ -48,6 +48,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/checkupdate": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取应用更新版本",
|
||||
"tags": [
|
||||
"App"
|
||||
],
|
||||
"summary": "Get app list update",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/detail/:appId/:version": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -456,6 +475,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/installed/params/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改应用参数",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"App"
|
||||
],
|
||||
"summary": "Change app params",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.AppInstalledUpdate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"installId"
|
||||
],
|
||||
"formatEN": "Application param update [installId]",
|
||||
"formatZH": "应用参数修改 [installId]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/apps/installed/port/change": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -669,6 +730,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/demo": {
|
||||
"get": {
|
||||
"description": "判断是否为demo环境",
|
||||
"tags": [
|
||||
"Auth"
|
||||
],
|
||||
"summary": "Check System isDemo",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/init": {
|
||||
"post": {
|
||||
"description": "初始化用户",
|
||||
@@ -3885,6 +3960,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/chunkupload": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "分片上传文件",
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "ChunkUpload file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "request",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"path"
|
||||
],
|
||||
"formatEN": "Upload file [path]",
|
||||
"formatZH": "上传文件 [path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/compress": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -4098,6 +4210,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/download/bypath": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "下载指定文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "Download file with path",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FilePath"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"path"
|
||||
],
|
||||
"formatEN": "Download file [path]",
|
||||
"formatZH": "下载文件 [path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/loadfile": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -8609,8 +8763,7 @@
|
||||
"dto.ComposeCreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"name"
|
||||
"from"
|
||||
],
|
||||
"properties": {
|
||||
"file": {
|
||||
@@ -8846,9 +8999,7 @@
|
||||
],
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "integer",
|
||||
"maximum": 31,
|
||||
"minimum": 1
|
||||
"type": "integer"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -8857,17 +9008,13 @@
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer",
|
||||
"maximum": 23,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"keepLocal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer",
|
||||
"maximum": 59,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -8928,9 +9075,7 @@
|
||||
],
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "integer",
|
||||
"maximum": 31,
|
||||
"minimum": 1
|
||||
"type": "integer"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -8939,9 +9084,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer",
|
||||
"maximum": 23,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
@@ -8950,9 +9093,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer",
|
||||
"maximum": 59,
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -9046,15 +9187,9 @@
|
||||
},
|
||||
"dto.DaemonJsonUpdateByFile": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9240,6 +9375,12 @@
|
||||
"restart",
|
||||
"stop"
|
||||
]
|
||||
},
|
||||
"stopService": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stopSocket": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11028,6 +11169,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.AppInstalledUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"installId",
|
||||
"params"
|
||||
],
|
||||
"properties": {
|
||||
"installId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.AppSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -11270,6 +11427,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.FilePath": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.FilePathCheck": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -12028,7 +12196,22 @@
|
||||
"response.AppParam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"edit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"labelEn": {
|
||||
"type": "string"
|
||||
},
|
||||
"labelZh": {
|
||||
"type": "string"
|
||||
},
|
||||
"rule": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {}
|
||||
@@ -12171,6 +12354,9 @@
|
||||
"appInstallId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"appName": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@@ -152,7 +152,6 @@ definitions:
|
||||
type: integer
|
||||
required:
|
||||
- from
|
||||
- name
|
||||
type: object
|
||||
dto.ComposeOperation:
|
||||
properties:
|
||||
@@ -294,22 +293,16 @@ definitions:
|
||||
dto.CronjobCreate:
|
||||
properties:
|
||||
day:
|
||||
maximum: 31
|
||||
minimum: 1
|
||||
type: integer
|
||||
dbName:
|
||||
type: string
|
||||
exclusionRules:
|
||||
type: string
|
||||
hour:
|
||||
maximum: 23
|
||||
minimum: 0
|
||||
type: integer
|
||||
keepLocal:
|
||||
type: boolean
|
||||
minute:
|
||||
maximum: 59
|
||||
minimum: 0
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
@@ -352,24 +345,18 @@ definitions:
|
||||
dto.CronjobUpdate:
|
||||
properties:
|
||||
day:
|
||||
maximum: 31
|
||||
minimum: 1
|
||||
type: integer
|
||||
dbName:
|
||||
type: string
|
||||
exclusionRules:
|
||||
type: string
|
||||
hour:
|
||||
maximum: 23
|
||||
minimum: 0
|
||||
type: integer
|
||||
id:
|
||||
type: integer
|
||||
keepLocal:
|
||||
type: boolean
|
||||
minute:
|
||||
maximum: 59
|
||||
minimum: 0
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
@@ -439,10 +426,6 @@ definitions:
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
dto.DashboardBase:
|
||||
properties:
|
||||
@@ -564,6 +547,10 @@ definitions:
|
||||
- restart
|
||||
- stop
|
||||
type: string
|
||||
stopService:
|
||||
type: boolean
|
||||
stopSocket:
|
||||
type: boolean
|
||||
required:
|
||||
- operation
|
||||
type: object
|
||||
@@ -1752,6 +1739,17 @@ definitions:
|
||||
- page
|
||||
- pageSize
|
||||
type: object
|
||||
request.AppInstalledUpdate:
|
||||
properties:
|
||||
installId:
|
||||
type: integer
|
||||
params:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
required:
|
||||
- installId
|
||||
- params
|
||||
type: object
|
||||
request.AppSearch:
|
||||
properties:
|
||||
name:
|
||||
@@ -1914,6 +1912,13 @@ definitions:
|
||||
showHidden:
|
||||
type: boolean
|
||||
type: object
|
||||
request.FilePath:
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
request.FilePathCheck:
|
||||
properties:
|
||||
path:
|
||||
@@ -2421,7 +2426,17 @@ definitions:
|
||||
type: object
|
||||
response.AppParam:
|
||||
properties:
|
||||
label:
|
||||
edit:
|
||||
type: boolean
|
||||
key:
|
||||
type: string
|
||||
labelEn:
|
||||
type: string
|
||||
labelZh:
|
||||
type: string
|
||||
rule:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
value: {}
|
||||
type: object
|
||||
@@ -2515,6 +2530,8 @@ definitions:
|
||||
type: string
|
||||
appInstallId:
|
||||
type: integer
|
||||
appName:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
defaultServer:
|
||||
@@ -2628,6 +2645,17 @@ paths:
|
||||
summary: Search app by key
|
||||
tags:
|
||||
- App
|
||||
/apps/checkupdate:
|
||||
get:
|
||||
description: 获取应用更新版本
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Get app list update
|
||||
tags:
|
||||
- App
|
||||
/apps/detail/:appId/:version:
|
||||
get:
|
||||
consumes:
|
||||
@@ -2887,6 +2915,33 @@ paths:
|
||||
summary: Search params by appInstallId
|
||||
tags:
|
||||
- App
|
||||
/apps/installed/params/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 修改应用参数
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.AppInstalledUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Change app params
|
||||
tags:
|
||||
- App
|
||||
x-panel-log:
|
||||
BeforeFuntions: []
|
||||
bodyKeys:
|
||||
- installId
|
||||
formatEN: Application param update [installId]
|
||||
formatZH: 应用参数修改 [installId]
|
||||
paramKeys: []
|
||||
/apps/installed/port/change:
|
||||
post:
|
||||
consumes:
|
||||
@@ -3022,6 +3077,15 @@ paths:
|
||||
summary: Load captcha
|
||||
tags:
|
||||
- Auth
|
||||
/auth/demo:
|
||||
get:
|
||||
description: 判断是否为demo环境
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
summary: Check System isDemo
|
||||
tags:
|
||||
- Auth
|
||||
/auth/init:
|
||||
post:
|
||||
consumes:
|
||||
@@ -5065,6 +5129,30 @@ paths:
|
||||
formatEN: Check whether file [path] exists
|
||||
formatZH: 检测文件 [path] 是否存在
|
||||
paramKeys: []
|
||||
/files/chunkupload:
|
||||
post:
|
||||
description: 分片上传文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: ChunkUpload file
|
||||
tags:
|
||||
- File
|
||||
x-panel-log:
|
||||
BeforeFuntions: []
|
||||
bodyKeys:
|
||||
- path
|
||||
formatEN: Upload file [path]
|
||||
formatZH: 上传文件 [path]
|
||||
paramKeys: []
|
||||
/files/compress:
|
||||
post:
|
||||
consumes:
|
||||
@@ -5202,6 +5290,33 @@ paths:
|
||||
formatEN: Download file [name]
|
||||
formatZH: 下载文件 [name]
|
||||
paramKeys: []
|
||||
/files/download/bypath:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 下载指定文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.FilePath'
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Download file with path
|
||||
tags:
|
||||
- File
|
||||
x-panel-log:
|
||||
BeforeFuntions: []
|
||||
bodyKeys:
|
||||
- path
|
||||
formatEN: Download file [path]
|
||||
formatZH: 下载文件 [path]
|
||||
paramKeys: []
|
||||
/files/loadfile:
|
||||
post:
|
||||
consumes:
|
||||
|
BIN
cmd/server/web/favicon.png
Normal file
BIN
cmd/server/web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -87,6 +87,7 @@ declare module 'vue' {
|
||||
SvgIcon: typeof import('./src/components/svg-icon/svg-icon.vue')['default']
|
||||
SystemUpgrade: typeof import('./src/components/system-upgrade/index.vue')['default']
|
||||
TableSetting: typeof import('./src/components/table-setting/index.vue')['default']
|
||||
Tooltip: typeof import('./src/components/tooltip/index.vue')['default']
|
||||
Upload: typeof import('./src/components/upload/index.vue')['default']
|
||||
VCharts: typeof import('./src/components/v-charts/index.vue')['default']
|
||||
}
|
||||
|
27
frontend/package-lock.json
generated
27
frontend/package-lock.json
generated
@@ -30,7 +30,6 @@
|
||||
"sass-loader": "^13.0.2",
|
||||
"screenfull": "^6.0.2",
|
||||
"unplugin-vue-define-options": "^0.7.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.2.25",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
@@ -10274,14 +10273,6 @@
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-monaco-editor": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
|
||||
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
|
||||
"peerDependencies": {
|
||||
"monaco-editor": ">=0.33.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-vue-setup-extend": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-vue-setup-extend/-/vite-plugin-vue-setup-extend-0.4.0.tgz",
|
||||
@@ -10643,9 +10634,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"version": "5.76.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
|
||||
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
@@ -18367,12 +18358,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vite-plugin-monaco-editor": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
|
||||
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
|
||||
"requires": {}
|
||||
},
|
||||
"vite-plugin-vue-setup-extend": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-vue-setup-extend/-/vite-plugin-vue-setup-extend-0.4.0.tgz",
|
||||
@@ -18679,9 +18664,9 @@
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"version": "5.76.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
|
||||
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
|
@@ -42,7 +42,6 @@
|
||||
"sass-loader": "^13.0.2",
|
||||
"screenfull": "^6.0.2",
|
||||
"unplugin-vue-define-options": "^0.7.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.2.25",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
|
@@ -43,7 +43,8 @@ export namespace Container {
|
||||
state: string;
|
||||
runTime: string;
|
||||
|
||||
isFromCompose: string;
|
||||
isFromApp: boolean;
|
||||
isFromCompose: boolean;
|
||||
}
|
||||
export interface ContainerStats {
|
||||
cpuPercent: number;
|
||||
|
@@ -6,7 +6,7 @@ export const searchContainer = (params: Container.ContainerSearch) => {
|
||||
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
|
||||
};
|
||||
export const createContainer = (params: Container.ContainerCreate) => {
|
||||
return http.post(`/containers`, params);
|
||||
return http.post(`/containers`, params, 1200000);
|
||||
};
|
||||
export const logContainer = (params: Container.ContainerLogSearch) => {
|
||||
return http.post<string>(`/containers/search/log`, params);
|
||||
@@ -38,10 +38,10 @@ export const imagePush = (params: Container.ImagePush) => {
|
||||
return http.post<string>(`/containers/image/push`, params);
|
||||
};
|
||||
export const imageLoad = (params: Container.ImageLoad) => {
|
||||
return http.post(`/containers/image/load`, params);
|
||||
return http.post(`/containers/image/load`, params, 1200000);
|
||||
};
|
||||
export const imageSave = (params: Container.ImageSave) => {
|
||||
return http.post(`/containers/image/save`, params);
|
||||
return http.post(`/containers/image/save`, params, 1200000);
|
||||
};
|
||||
export const imageTag = (params: Container.ImageTag) => {
|
||||
return http.post(`/containers/image/tag`, params);
|
||||
@@ -117,13 +117,13 @@ export const searchCompose = (params: SearchWithPage) => {
|
||||
return http.post<ResPage<Container.ComposeInfo>>(`/containers/compose/search`, params);
|
||||
};
|
||||
export const upCompose = (params: Container.ComposeCreate) => {
|
||||
return http.post(`/containers/compose`, params);
|
||||
return http.post(`/containers/compose`, params, 600000);
|
||||
};
|
||||
export const composeOperator = (params: Container.ComposeOpration) => {
|
||||
return http.post(`/containers/compose/operate`, params);
|
||||
};
|
||||
export const composeUpdate = (params: Container.ComposeUpdate) => {
|
||||
return http.post(`/containers/compose/update`, params);
|
||||
return http.post(`/containers/compose/update`, params, 600000);
|
||||
};
|
||||
|
||||
// docker
|
||||
|
@@ -31,7 +31,7 @@ export const updateStatus = (params: Cronjob.UpdateStatus) => {
|
||||
};
|
||||
|
||||
export const download = (params: Cronjob.Download) => {
|
||||
return http.download<BlobPart>(`cronjobs/download`, params, { responseType: 'blob' });
|
||||
return http.post<string>(`cronjobs/download`, params);
|
||||
};
|
||||
|
||||
export const handleOnce = (id: number) => {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import http from '@/api';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { SearchWithPage, ResPage, DescriptionUpdate } from '../interface';
|
||||
import { Database } from '../interface/database';
|
||||
|
||||
@@ -7,13 +9,21 @@ export const searchMysqlDBs = (params: SearchWithPage) => {
|
||||
};
|
||||
|
||||
export const addMysqlDB = (params: Database.MysqlDBCreate) => {
|
||||
return http.post(`/databases`, params);
|
||||
let reqest = deepCopy(params) as Database.MysqlDBCreate;
|
||||
if (reqest.password) {
|
||||
reqest.password = Base64.encode(reqest.password);
|
||||
}
|
||||
return http.post(`/databases`, reqest);
|
||||
};
|
||||
export const updateMysqlAccess = (params: Database.ChangeInfo) => {
|
||||
return http.post(`/databases/change/access`, params);
|
||||
};
|
||||
export const updateMysqlPassword = (params: Database.ChangeInfo) => {
|
||||
return http.post(`/databases/change/password`, params);
|
||||
let reqest = deepCopy(params) as Database.ChangeInfo;
|
||||
if (reqest.value) {
|
||||
reqest.value = Base64.encode(reqest.value);
|
||||
}
|
||||
return http.post(`/databases/change/password`, reqest);
|
||||
};
|
||||
export const updateMysqlDescription = (params: DescriptionUpdate) => {
|
||||
return http.post(`/databases/description/update`, params);
|
||||
@@ -58,7 +68,11 @@ export const redisPersistenceConf = () => {
|
||||
return http.get<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`);
|
||||
};
|
||||
export const changeRedisPassword = (params: Database.ChangeInfo) => {
|
||||
return http.post(`/databases/redis/password`, params);
|
||||
let reqest = deepCopy(params) as Database.ChangeInfo;
|
||||
if (reqest.value) {
|
||||
reqest.value = Base64.encode(reqest.value);
|
||||
}
|
||||
return http.post(`/databases/redis/password`, reqest);
|
||||
};
|
||||
export const updateRedisPersistenceConf = (params: Database.RedisConfPersistenceUpdate) => {
|
||||
return http.post(`/databases/redis/persistence/update`, params);
|
||||
|
@@ -79,6 +79,10 @@ export const DownloadFile = (params: File.FileDownload) => {
|
||||
return http.download<BlobPart>('files/download', params, { responseType: 'blob', timeout: 20000 });
|
||||
};
|
||||
|
||||
export const DownloadByPath = (path: string) => {
|
||||
return http.download<BlobPart>('files/download/bypath', { path: path }, { responseType: 'blob', timeout: 40000 });
|
||||
};
|
||||
|
||||
export const ComputeDirSize = (params: File.DirSizeReq) => {
|
||||
return http.post<File.DirSizeRes>('files/size', params);
|
||||
};
|
||||
|
@@ -3,6 +3,8 @@ import { ResPage } from '../interface';
|
||||
import { Command } from '../interface/command';
|
||||
import { Group } from '../interface/group';
|
||||
import { Host } from '../interface/host';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
|
||||
export const searchHosts = (params: Host.SearchWithPage) => {
|
||||
return http.post<ResPage<Host.Host>>(`/hosts/search`, params);
|
||||
@@ -14,16 +16,37 @@ export const getHostInfo = (id: number) => {
|
||||
return http.get<Host.Host>(`/hosts/` + id);
|
||||
};
|
||||
export const addHost = (params: Host.HostOperate) => {
|
||||
return http.post<Host.HostOperate>(`/hosts`, params);
|
||||
let reqest = deepCopy(params) as Host.HostOperate;
|
||||
if (reqest.password) {
|
||||
reqest.password = Base64.encode(reqest.password);
|
||||
}
|
||||
if (reqest.privateKey) {
|
||||
reqest.privateKey = Base64.encode(reqest.privateKey);
|
||||
}
|
||||
return http.post<Host.HostOperate>(`/hosts`, reqest);
|
||||
};
|
||||
export const testByInfo = (params: Host.HostConnTest) => {
|
||||
return http.post<boolean>(`/hosts/test/byinfo`, params);
|
||||
let reqest = deepCopy(params) as Host.HostOperate;
|
||||
if (reqest.password) {
|
||||
reqest.password = Base64.encode(reqest.password);
|
||||
}
|
||||
if (reqest.privateKey) {
|
||||
reqest.privateKey = Base64.encode(reqest.privateKey);
|
||||
}
|
||||
return http.post<boolean>(`/hosts/test/byinfo`, reqest);
|
||||
};
|
||||
export const testByID = (id: number) => {
|
||||
return http.post<boolean>(`/hosts/test/byid/${id}`);
|
||||
};
|
||||
export const editHost = (params: Host.HostOperate) => {
|
||||
return http.post(`/hosts/update`, params);
|
||||
let reqest = deepCopy(params) as Host.HostOperate;
|
||||
if (reqest.password) {
|
||||
reqest.password = Base64.encode(reqest.password);
|
||||
}
|
||||
if (reqest.privateKey) {
|
||||
reqest.privateKey = Base64.encode(reqest.privateKey);
|
||||
}
|
||||
return http.post(`/hosts/update`, reqest);
|
||||
};
|
||||
export const editHostGroup = (params: Host.GroupChange) => {
|
||||
return http.post(`/hosts/update/group`, params);
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import http from '@/api';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { ResPage, SearchWithPage, DescriptionUpdate } from '../interface';
|
||||
import { Backup } from '../interface/backup';
|
||||
import { Setting } from '../interface/setting';
|
||||
@@ -52,16 +54,16 @@ export const loadBaseDir = () => {
|
||||
|
||||
// backup
|
||||
export const handleBackup = (params: Backup.Backup) => {
|
||||
return http.post(`/settings/backup/backup`, params);
|
||||
return http.post(`/settings/backup/backup`, params, 400000);
|
||||
};
|
||||
export const handleRecover = (params: Backup.Recover) => {
|
||||
return http.post(`/settings/backup/recover`, params);
|
||||
return http.post(`/settings/backup/recover`, params, 400000);
|
||||
};
|
||||
export const handleRecoverByUpload = (params: Backup.Recover) => {
|
||||
return http.post(`/settings/backup/recover/byupload`, params);
|
||||
return http.post(`/settings/backup/recover/byupload`, params, 400000);
|
||||
};
|
||||
export const downloadBackupRecord = (params: Backup.RecordDownload) => {
|
||||
return http.download<BlobPart>(`/settings/backup/record/download`, params, { responseType: 'blob' });
|
||||
return http.post<string>(`/settings/backup/record/download`, params);
|
||||
};
|
||||
export const deleteBackupRecord = (params: { ids: number[] }) => {
|
||||
return http.post(`/settings/backup/record/del`, params);
|
||||
@@ -77,16 +79,37 @@ export const getFilesFromBackup = (type: string) => {
|
||||
return http.post<Array<any>>(`/settings/backup/search/files`, { type: type });
|
||||
};
|
||||
export const addBackup = (params: Backup.BackupOperate) => {
|
||||
return http.post<Backup.BackupOperate>(`/settings/backup`, params);
|
||||
let reqest = deepCopy(params) as Backup.BackupOperate;
|
||||
if (reqest.accessKey) {
|
||||
reqest.accessKey = Base64.encode(reqest.accessKey);
|
||||
}
|
||||
if (reqest.credential) {
|
||||
reqest.credential = Base64.encode(reqest.credential);
|
||||
}
|
||||
return http.post<Backup.BackupOperate>(`/settings/backup`, reqest);
|
||||
};
|
||||
export const editBackup = (params: Backup.BackupOperate) => {
|
||||
return http.post(`/settings/backup/update`, params);
|
||||
let reqest = deepCopy(params) as Backup.BackupOperate;
|
||||
if (reqest.accessKey) {
|
||||
reqest.accessKey = Base64.encode(reqest.accessKey);
|
||||
}
|
||||
if (reqest.credential) {
|
||||
reqest.credential = Base64.encode(reqest.credential);
|
||||
}
|
||||
return http.post(`/settings/backup/update`, reqest);
|
||||
};
|
||||
export const deleteBackup = (params: { ids: number[] }) => {
|
||||
return http.post(`/settings/backup/del`, params);
|
||||
};
|
||||
export const listBucket = (params: Backup.ForBucket) => {
|
||||
return http.post(`/settings/backup/buckets`, params);
|
||||
let reqest = deepCopy(params) as Backup.BackupOperate;
|
||||
if (reqest.accessKey) {
|
||||
reqest.accessKey = Base64.encode(reqest.accessKey);
|
||||
}
|
||||
if (reqest.credential) {
|
||||
reqest.credential = Base64.encode(reqest.credential);
|
||||
}
|
||||
return http.post(`/settings/backup/buckets`, reqest);
|
||||
};
|
||||
|
||||
// snapshot
|
||||
|
@@ -58,6 +58,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
|
||||
import { Backup } from '@/api/interface/backup';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { DownloadByPath } from '@/api/modules/files';
|
||||
|
||||
const selects = ref<any>([]);
|
||||
const loading = ref();
|
||||
@@ -145,14 +146,16 @@ const onDownload = async (row: Backup.RecordInfo) => {
|
||||
fileDir: row.fileDir,
|
||||
fileName: row.fileName,
|
||||
};
|
||||
const res = await downloadBackupRecord(params);
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = row.fileName;
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
await downloadBackupRecord(params).then(async (res) => {
|
||||
const file = await DownloadByPath(res.data);
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([file]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = row.fileName;
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
});
|
||||
};
|
||||
|
||||
const onBatchDelete = async (row: Backup.RecordInfo | null) => {
|
||||
|
@@ -53,7 +53,6 @@ defineProps({
|
||||
const emit = defineEmits(['search', 'update:selects']);
|
||||
const condition = ref({});
|
||||
function search(conditions: any, e: any) {
|
||||
console.log(conditions);
|
||||
if (conditions) {
|
||||
condition.value = conditions;
|
||||
}
|
||||
|
@@ -77,7 +77,7 @@ const timeOptions = ref([
|
||||
|
||||
const searchLogs = async () => {
|
||||
const res = await logContainer(logSearch);
|
||||
logInfo.value = res.data;
|
||||
logInfo.value = res.data || '';
|
||||
nextTick(() => {
|
||||
const state = view.value.state;
|
||||
view.value.dispatch({
|
||||
|
@@ -100,7 +100,6 @@ const props = defineProps({
|
||||
const em = defineEmits(['choose']);
|
||||
|
||||
const checkFile = (row: any) => {
|
||||
console.log(row.path);
|
||||
rowName.value = row.name;
|
||||
em('choose', row.path);
|
||||
popoverVisible.value = false;
|
||||
|
52
frontend/src/components/tooltip/index.vue
Normal file
52
frontend/src/components/tooltip/index.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="tooltip-container">
|
||||
<el-tooltip :disabled="showTooltip">
|
||||
<template #content>
|
||||
<div :style="{ width: tootipWidth, 'word-break': 'break-all' }">{{ text }}</div>
|
||||
</template>
|
||||
<p ref="tooltipBox" class="text-box">
|
||||
<span v-if="islink" ref="tooltipItem" class="table-link">{{ text }}</span>
|
||||
<span v-else ref="tooltipItem" class="">{{ text }}</span>
|
||||
</p>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const showTooltip = ref();
|
||||
const tooltipBox = ref();
|
||||
const tooltipItem = ref();
|
||||
|
||||
const tootipWidth = ref();
|
||||
|
||||
defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
islink: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const boxWidth = tooltipBox.value.offsetWidth;
|
||||
const itemWidth = tooltipItem.value.offsetWidth;
|
||||
tootipWidth.value = itemWidth > 250 ? '250px' : itemWidth + 'px';
|
||||
showTooltip.value = boxWidth > itemWidth;
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.tooltip-container {
|
||||
width: 100%;
|
||||
.text-box {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -119,7 +119,11 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
if (type.value === 'website' || type.value === 'app') {
|
||||
title.value = name.value;
|
||||
}
|
||||
baseDir.value = `${pathRes.data}/uploads/${type.value}/${name.value}/${detailName.value}/`;
|
||||
if (detailName.value) {
|
||||
baseDir.value = `${pathRes.data}/uploads/${type.value}/${name.value}/${detailName.value}/`;
|
||||
} else {
|
||||
baseDir.value = `${pathRes.data}/uploads/${type.value}/${name.value}/`;
|
||||
}
|
||||
upVisiable.value = true;
|
||||
search();
|
||||
};
|
||||
@@ -198,7 +202,7 @@ const onSubmit = async () => {
|
||||
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
||||
return;
|
||||
}
|
||||
let reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-z:A-Z0-9_.\u4e00-\u9fa5-]{0,50}$/;
|
||||
let reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-z:A-Z0-9_.\u4e00-\u9fa5-]{0,256}$/;
|
||||
if (!reg.test(uploaderFiles.value[0]!.raw.name)) {
|
||||
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
||||
return;
|
||||
|
@@ -15,6 +15,21 @@ const checkIp = (rule: any, value: any, callback: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const checkHost = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
|
||||
} else {
|
||||
const regIP =
|
||||
/^(\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])$/;
|
||||
const regHost = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
|
||||
if (!regIP.test(value) && !regHost.test(value)) {
|
||||
callback(new Error(i18n.global.t('commons.rule.host')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const complexityPassword = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.complexityPassword')));
|
||||
@@ -265,6 +280,7 @@ interface CommonRule {
|
||||
number: FormItemRule;
|
||||
integerNumber: FormItemRule;
|
||||
ip: FormItemRule;
|
||||
host: FormItemRule;
|
||||
port: FormItemRule;
|
||||
domain: FormItemRule;
|
||||
databaseName: FormItemRule;
|
||||
@@ -363,6 +379,11 @@ export const Rules: CommonRule = {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
},
|
||||
host: {
|
||||
validator: checkHost,
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
},
|
||||
port: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
||||
const message = {
|
||||
commons: {
|
||||
true: 'true',
|
||||
false: 'false',
|
||||
@@ -96,7 +97,7 @@ export default {
|
||||
unSupportSize: 'The uploaded file exceeds {0}M, please confirm!',
|
||||
fileExist: 'The file already exists in the current folder. Repeat uploading is not supported!',
|
||||
fileNameErr:
|
||||
'You can upload only files whose name contains 1 to 50 characters, including English, Chinese, digits, or periods (.-_)',
|
||||
'You can upload only files whose name contains 1 to 256 characters, including English, Chinese, digits, or periods (.-_)',
|
||||
comfimNoNull: 'Make sure the value {0} is not empty',
|
||||
},
|
||||
login: {
|
||||
@@ -107,8 +108,8 @@ export default {
|
||||
welcome: 'Welcome back, please enter your username and password to log in!',
|
||||
errorAuthInfo: 'The user name or password you entered is incorrect, please re-enter!',
|
||||
errorMfaInfo: 'Incorrect authentication information, please try again!',
|
||||
captchaHelper: 'Please enter the verification code',
|
||||
errorCaptcha: 'Verification code error!',
|
||||
captchaHelper: 'Captcha',
|
||||
errorCaptcha: 'Captcha code error!',
|
||||
safeEntrance: 'Please use the correct entry to log in to the panel',
|
||||
reason: 'Cause of error:',
|
||||
reasonHelper:
|
||||
@@ -119,7 +120,7 @@ export default {
|
||||
warnning:
|
||||
'Note: [Closing the security entrance] will make your panel login address directly exposed to the Internet, very dangerous, please exercise caution',
|
||||
codeInput: 'Please enter the 6-digit verification code of the MFA validator',
|
||||
title: 'Linux server operation and maintenance management panel',
|
||||
title: 'Linux Server Management Panel',
|
||||
},
|
||||
rule: {
|
||||
username: 'Please enter a username',
|
||||
@@ -134,12 +135,13 @@ export default {
|
||||
imageName: 'Support English, numbers, :/.-_, length 1-150',
|
||||
volumeName: 'Support English, numbers, .-_, length 2-30',
|
||||
complexityPassword:
|
||||
'Enter a password that is longer than eight characters and contains at least two letters, digits, and special characters',
|
||||
'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',
|
||||
email: 'Email format error',
|
||||
number: 'Please enter the correct number',
|
||||
integer: 'Please enter the correct positive integer',
|
||||
ip: 'Please enter the correct IP address',
|
||||
host: 'Enter the correct IP address or domain name',
|
||||
port: 'Please enter the correct port',
|
||||
selectHelper: 'Please select the correct {0} file',
|
||||
domain: 'domain name format error',
|
||||
@@ -407,6 +409,8 @@ export default {
|
||||
createContainer: 'Create container',
|
||||
containerList: 'Container list',
|
||||
operatorHelper: '{0} will be performed on the selected container. Do you want to continue?',
|
||||
operatorAppHelper:
|
||||
'There is a container from the App store. The {0} operation may affect the normal use of this service. Are you sure?',
|
||||
start: 'Start',
|
||||
stop: 'Stop',
|
||||
restart: 'Restart',
|
||||
@@ -994,7 +998,7 @@ export default {
|
||||
addAccount: 'Add new account',
|
||||
acmeAccount: 'Acme Account',
|
||||
provider: 'Verification method',
|
||||
dnsCommon: 'Manual resolution',
|
||||
dnsManual: 'Manual resolution',
|
||||
expireDate: 'Expiration Time',
|
||||
brand: 'Issuer',
|
||||
deploySSL: 'Deployment',
|
||||
@@ -1161,3 +1165,8 @@ export default {
|
||||
cookieBlockList: 'Cookie Blacklist',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudEnLocale,
|
||||
...message,
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
import fit2cloudZhLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-cn';
|
||||
const message = {
|
||||
commons: {
|
||||
true: '是',
|
||||
false: '否',
|
||||
@@ -101,7 +102,7 @@ export default {
|
||||
unSupportType: '不支持当前文件类型!',
|
||||
unSupportSize: '上传文件超过 {0}M,请确认!',
|
||||
fileExist: '当前文件夹已存在该文件,不支持重复上传!',
|
||||
fileNameErr: '仅支持上传名称包含英文、中文、数字或者 .-_ ,长度 1-50 位的文件',
|
||||
fileNameErr: '仅支持上传名称包含英文、中文、数字或者 .-_ ,长度 1-256 位的文件',
|
||||
comfimNoNull: '请确认 {0} 值不为空',
|
||||
},
|
||||
login: {
|
||||
@@ -112,7 +113,7 @@ export default {
|
||||
welcome: '欢迎回来,请输入用户名和密码登录!',
|
||||
errorAuthInfo: '您输入的用户名或密码不正确,请重新输入!',
|
||||
errorMfaInfo: '错误的验证信息,请重试!',
|
||||
captchaHelper: '请输入验证码',
|
||||
captchaHelper: '验证码',
|
||||
errorCaptcha: '验证码错误!',
|
||||
safeEntrance: '请使用正确的入口登录面板',
|
||||
reason: '错误原因:',
|
||||
@@ -144,6 +145,7 @@ export default {
|
||||
number: '请输入正确的数字',
|
||||
integer: '请输入正确的正整数',
|
||||
ip: '请输入正确的 IP 地址',
|
||||
host: '请输入正确的 IP 或者域名',
|
||||
port: '请输入正确的端口,1-65535',
|
||||
selectHelper: '请选择正确的 {0} 文件',
|
||||
domain: '域名格式错误',
|
||||
@@ -419,6 +421,7 @@ export default {
|
||||
createContainer: '创建容器',
|
||||
containerList: '容器列表',
|
||||
operatorHelper: '将对选中容器进行 {0} 操作,是否继续?',
|
||||
operatorAppHelper: '存在来源于应用商店的容器,{0} 操作可能会影响到该服务的正常使用,是否确认?',
|
||||
start: '启动',
|
||||
stop: '停止',
|
||||
restart: '重启',
|
||||
@@ -1003,7 +1006,7 @@ export default {
|
||||
addAccount: '创建账户',
|
||||
acmeAccount: 'Acme 账户',
|
||||
provider: '验证方式',
|
||||
dnsCommon: '手动解析',
|
||||
dnsManual: '手动解析',
|
||||
expireDate: '过期时间',
|
||||
brand: '颁发者',
|
||||
deploySSL: '部署',
|
||||
@@ -1165,3 +1168,7 @@ export default {
|
||||
cookieBlockList: 'Cookie 黑名单',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudZhLocale,
|
||||
...message,
|
||||
};
|
||||
|
@@ -133,4 +133,8 @@ const showBack = computed(() => {
|
||||
border: 0;
|
||||
border-top: var(--panel-border);
|
||||
}
|
||||
|
||||
.main-box {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,11 +15,12 @@ import SvgIcon from './components/svg-icon/svg-icon.vue';
|
||||
import ElementPlus from 'element-plus';
|
||||
import Fit2CloudPlus from 'fit2cloud-ui-plus';
|
||||
import * as Icons from '@element-plus/icons-vue';
|
||||
|
||||
const app = createApp(App);
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
app.use(ElementPlus);
|
||||
app.use(Fit2CloudPlus);
|
||||
|
||||
app.use(Fit2CloudPlus, { locale: I18n.global.messages.value[localStorage.getItem('lang') || 'zh'] });
|
||||
|
||||
Object.keys(Icons).forEach((key) => {
|
||||
app.component(key, Icons[key as keyof typeof Icons]);
|
||||
});
|
||||
|
@@ -176,9 +176,12 @@
|
||||
.mask-prompt {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 25%;
|
||||
left: 50%;
|
||||
top: 220px;
|
||||
left: 45%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table-button {
|
||||
@@ -287,3 +290,12 @@
|
||||
color: #646a73;
|
||||
}
|
||||
|
||||
.table-link {
|
||||
color: $primary-color;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table-link:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<el-row :gutter="20">
|
||||
<div>
|
||||
<el-col :span="3">
|
||||
<el-avatar shape="square" :size="180" :src="'data:image/png;base64,' + app.icon" />
|
||||
<el-avatar shape="square" :size="180" :src="app.icon" />
|
||||
</el-col>
|
||||
</div>
|
||||
<el-col :span="18">
|
||||
@@ -120,6 +120,7 @@ const getApp = async () => {
|
||||
try {
|
||||
const res = await GetApp(props.appKey);
|
||||
app.value = res.data;
|
||||
app.value.icon = 'data:image/png;base64,' + res.data.icon;
|
||||
version.value = app.value.versions[0];
|
||||
getDetail(app.value.id, version.value);
|
||||
} finally {
|
||||
|
@@ -101,7 +101,6 @@ const get = async () => {
|
||||
if (res.data && res.data.length > 0) {
|
||||
res.data.forEach((d) => {
|
||||
if (d.edit) {
|
||||
console.log(d.edit);
|
||||
canEdit.value = true;
|
||||
}
|
||||
let value = d.value;
|
||||
|
@@ -69,15 +69,9 @@
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
min-width="100"
|
||||
prop="name"
|
||||
fix
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="100" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-link @click="onInspect(row.containerID)" type="primary">{{ row.name }}</el-link>
|
||||
<Tooltip @click="onInspect(row.containerID)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -120,6 +114,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||
@@ -228,15 +223,18 @@ const checkStatus = (operation: string) => {
|
||||
};
|
||||
|
||||
const onOperate = async (operation: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + operation)]),
|
||||
i18n.global.t('container.' + operation),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(() => {
|
||||
let msg = i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + operation)]);
|
||||
for (const item of selects.value) {
|
||||
if (item.isFromApp) {
|
||||
msg = i18n.global.t('container.operatorAppHelper', [i18n.global.t('container.' + operation)]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ElMessageBox.confirm(msg, i18n.global.t('container.' + operation), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(() => {
|
||||
let ps = [];
|
||||
for (const item of selects.value) {
|
||||
const param = {
|
||||
|
@@ -47,15 +47,9 @@
|
||||
:data="data"
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
min-width="100"
|
||||
prop="name"
|
||||
fix
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="100" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-link @click="loadDetail(row)" type="primary">{{ row.name }}</el-link>
|
||||
<Tooltip @click="loadDetail(row)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.from')" prop="createdBy" min-width="80" fix>
|
||||
@@ -89,6 +83,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
|
@@ -220,9 +220,9 @@ const form = reactive({
|
||||
cmd: [] as Array<string>,
|
||||
publishAllPorts: false,
|
||||
exposedPorts: [] as Array<Container.Port>,
|
||||
nanoCPUs: 1,
|
||||
memory: 100,
|
||||
memoryItem: 100,
|
||||
nanoCPUs: 0,
|
||||
memory: 0,
|
||||
memoryItem: 0,
|
||||
memoryUnit: 'MB',
|
||||
cpuUnit: 'Core',
|
||||
volumes: [] as Array<Container.Volume>,
|
||||
@@ -250,9 +250,9 @@ const handlReset = () => {
|
||||
form.cmd = [];
|
||||
form.publishAllPorts = false;
|
||||
form.exposedPorts = [];
|
||||
form.nanoCPUs = 1;
|
||||
form.memory = 100;
|
||||
form.memoryItem = 100;
|
||||
form.nanoCPUs = 0;
|
||||
form.memory = 0;
|
||||
form.memoryItem = 0;
|
||||
form.memoryUnit = 'MB';
|
||||
form.cpuUnit = 'Core';
|
||||
form.volumes = [];
|
||||
|
@@ -66,15 +66,9 @@
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
min-width="80"
|
||||
prop="name"
|
||||
fix
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="80" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-link @click="onInspect(row.containerID)" type="primary">{{ row.name }}</el-link>
|
||||
<Tooltip @click="onInspect(row.containerID)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -118,6 +112,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import ReNameDialog from '@/views/container/container/rename/index.vue';
|
||||
@@ -252,15 +247,18 @@ const checkStatus = (operation: string) => {
|
||||
}
|
||||
};
|
||||
const onOperate = async (operation: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + operation)]),
|
||||
i18n.global.t('container.' + operation),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(() => {
|
||||
let msg = i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + operation)]);
|
||||
for (const item of selects.value) {
|
||||
if (item.isFromApp) {
|
||||
msg = i18n.global.t('container.operatorAppHelper', [i18n.global.t('container.' + operation)]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ElMessageBox.confirm(msg, i18n.global.t('container.' + operation), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(() => {
|
||||
let ps = [];
|
||||
for (const item of selects.value) {
|
||||
const param = {
|
||||
@@ -306,7 +304,6 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('container.rename'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
console.log(row.name);
|
||||
dialogReNameRef.value!.acceptParams({ container: row.name });
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
|
@@ -96,7 +96,7 @@ const handleClose = async () => {
|
||||
|
||||
const searchLogs = async () => {
|
||||
const res = await logContainer(logSearch);
|
||||
logInfo.value = res.data;
|
||||
logInfo.value = res.data || '';
|
||||
nextTick(() => {
|
||||
const state = view.value.state;
|
||||
view.value.dispatch({
|
||||
|
@@ -40,7 +40,11 @@
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
<el-table-column label="ID" show-overflow-tooltip prop="id" min-width="60" />
|
||||
<el-table-column label="ID" prop="id" min-width="60">
|
||||
<template #default="{ row }">
|
||||
<Tooltip :islink="false" :text="row.id" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.tag')" prop="tags" min-width="160" fix>
|
||||
<template #default="{ row }">
|
||||
<el-tag style="margin-left: 5px" v-for="(item, index) of row.tags" :key="index">
|
||||
@@ -76,6 +80,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
@@ -193,6 +198,7 @@ const buttons = [
|
||||
click: async (row: Container.ImageInfo) => {
|
||||
if (row.tags.length <= 1) {
|
||||
await useDeleteData(imageRemove, { names: [row.id] }, 'commons.msg.delete');
|
||||
search();
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
|
@@ -47,16 +47,14 @@
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="selectable" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
min-width="80"
|
||||
prop="name"
|
||||
fix
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="80" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-link @click="onInspect(row.id)" type="primary">{{ row.name }}</el-link>
|
||||
<el-tag effect="dark" round v-if="row.isSystem" style="margin-left: 5px">system</el-tag>
|
||||
<Tooltip @click="onInspect(row.id)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="50">
|
||||
<template #default="{ row }">
|
||||
<el-tag effect="dark" round v-if="row.isSystem">system</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -99,6 +97,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||
|
@@ -272,7 +272,6 @@ const onSubmitSave = async () => {
|
||||
const loadDockerConf = async () => {
|
||||
const res = await loadDaemonJsonFile();
|
||||
if (res.data === 'daemon.json is not find in path') {
|
||||
console.log(res.data);
|
||||
showDaemonJsonAlert.value = true;
|
||||
} else {
|
||||
dockerConf.value = res.data;
|
||||
|
@@ -47,15 +47,9 @@
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
min-width="100"
|
||||
prop="name"
|
||||
fix
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="100" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-link @click="onOpenDetail(row)" type="primary">{{ row.name }}</el-link>
|
||||
<Tooltip @click="onOpenDetail(row)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.description')" prop="description" min-width="200" fix />
|
||||
@@ -75,6 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
|
@@ -45,17 +45,7 @@
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="80" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-tooltip v-if="row.name.length > 20" effect="dark" placement="bottom">
|
||||
<template #content>
|
||||
<div style="width: 300px; word-break: break-all">{{ row.name }}</div>
|
||||
</template>
|
||||
<el-link @click="onInspect(row.name)" type="primary">
|
||||
{{ row.name.substring(0, 20) }}...
|
||||
</el-link>
|
||||
</el-tooltip>
|
||||
<el-link v-else @click="onInspect(row.name)" type="primary">
|
||||
{{ row.name }}
|
||||
</el-link>
|
||||
<Tooltip @click="onInspect(row.name)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -88,6 +78,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||
|
@@ -45,17 +45,7 @@
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column :label="$t('cronjob.taskName')" :min-width="120" prop="name">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip effect="dark" v-if="row.name.length > 12" placement="top">
|
||||
<template #content>
|
||||
<div style="width: 300px; word-break: break-all">{{ row.name }}</div>
|
||||
</template>
|
||||
<el-link @click="loadDetail(row)" type="primary">
|
||||
{{ row.name.substring(0, 15) }}...
|
||||
</el-link>
|
||||
</el-tooltip>
|
||||
<el-link v-else @click="loadDetail(row)" type="primary">
|
||||
{{ row.name }}
|
||||
</el-link>
|
||||
<Tooltip @click="loadDetail(row)" :text="row.name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" :min-width="80" prop="status">
|
||||
@@ -136,6 +126,7 @@
|
||||
<script lang="ts" setup>
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import OperatrDialog from '@/views/cronjob/operate/index.vue';
|
||||
import Records from '@/views/cronjob/record/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
|
@@ -401,7 +401,6 @@ function hasScript() {
|
||||
|
||||
function checkScript() {
|
||||
let row = dialogData.value.rowData;
|
||||
console.log(row.specType, row.week, row.day, row.hour, row.minute);
|
||||
switch (row.specType) {
|
||||
case 'perMonth':
|
||||
return row.day > 0 && row.day < 32 && row.hour >= 0 && row.hour < 24 && row.minute >= 0 && row.minute < 60;
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">
|
||||
{{ $t('cronjob.' + dialogData.rowData.type) }} - {{ dialogData.rowData.name }}
|
||||
{{ $t('cronjob.' + dialogData.rowData.type) }}
|
||||
</el-tag>
|
||||
<el-tag v-if="dialogData.rowData.status === 'Enable'" round class="status-content" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
@@ -283,7 +283,7 @@ import { searchRecords, download, handleOnce, updateStatus } from '@/api/modules
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { LoadFile } from '@/api/modules/files';
|
||||
import { DownloadByPath, LoadFile } from '@/api/modules/files';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
@@ -472,17 +472,19 @@ const onDownload = async (record: any, backupID: number) => {
|
||||
recordID: record.id,
|
||||
backupAccountID: backupID,
|
||||
};
|
||||
const res = await download(params);
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
if (record.file && record.file.indexOf('/') !== -1) {
|
||||
let pathItem = record.file.split('/');
|
||||
a.download = pathItem[pathItem.length - 1];
|
||||
}
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
await download(params).then(async (res) => {
|
||||
const file = await DownloadByPath(res.data);
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([file]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
if (record.file && record.file.indexOf('/') !== -1) {
|
||||
let pathItem = record.file.split('/');
|
||||
a.download = pathItem[pathItem.length - 1];
|
||||
}
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
});
|
||||
};
|
||||
|
||||
const nextPage = async () => {
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<template #app>
|
||||
<AppStatus
|
||||
:app-key="'mysql'"
|
||||
style="margin-top: 20px"
|
||||
v-model:loading="loading"
|
||||
@setting="onSetting"
|
||||
@is-exist="checkExist"
|
||||
></AppStatus>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<el-drawer v-model="changeVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.changePassword')" :back="handleClose" />
|
||||
<DrawerHeader :header="title" :back="handleClose" />
|
||||
</template>
|
||||
<el-form>
|
||||
<el-form v-loading="loading" ref="changeFormRef" :model="changeForm" label-position="top">
|
||||
@@ -78,6 +78,7 @@ const loading = ref();
|
||||
const changeVisiable = ref(false);
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const changeFormRef = ref<FormInstance>();
|
||||
const title = ref();
|
||||
const changeForm = reactive({
|
||||
id: 0,
|
||||
mysqlName: '',
|
||||
@@ -101,6 +102,10 @@ interface DialogProps {
|
||||
value: string;
|
||||
}
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
title.value =
|
||||
params.operation === 'password'
|
||||
? i18n.global.t('database.changePassword')
|
||||
: i18n.global.t('database.permission');
|
||||
changeForm.id = params.id;
|
||||
changeForm.mysqlName = params.mysqlName;
|
||||
changeForm.userName = params.username;
|
||||
|
@@ -244,6 +244,7 @@ const onSubmitChangeConf = async () => {
|
||||
loading.value = true;
|
||||
await updateMysqlConfByFile(param)
|
||||
.then(() => {
|
||||
useOld.value = false;
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
@@ -303,6 +304,7 @@ const loadSlowLogs = async () => {
|
||||
};
|
||||
|
||||
const loadMysqlConf = async (path: string) => {
|
||||
useOld.value = false;
|
||||
const res = await LoadFile({ path: path });
|
||||
loading.value = false;
|
||||
mysqlConf.value = res.data;
|
||||
|
@@ -13,13 +13,15 @@
|
||||
<div style="float: left">
|
||||
<el-input type="number" v-model.number="variables.long_query_time" />
|
||||
</div>
|
||||
<el-button style="float: left; margin-left: 10px" @click="openSlowLogs">
|
||||
<el-button style="float: left; margin-left: 10px" @click="changeSlowLogs">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
<div style="float: left; margin-left: 20px">
|
||||
<el-checkbox border v-model="isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
|
||||
<el-checkbox style="margin-top: 2px" :disabled="!currentStatus" border v-model="isWatch">
|
||||
{{ $t('commons.button.watch') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<el-button style="margin-left: 20px" @click="onDownload" icon="Download">
|
||||
<el-button :disabled="!currentStatus" style="margin-left: 20px" @click="onDownload" icon="Download">
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -29,7 +31,7 @@
|
||||
:placeholder="$t('database.noData')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: calc(100vh - 427px); width: 100%"
|
||||
style="height: calc(100vh - 428px); width: 100%"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
@@ -56,7 +58,7 @@ import { updateMysqlVariables } from '@/api/modules/database';
|
||||
import { dateFormatForName } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
const slowLogs = ref();
|
||||
@@ -65,7 +67,7 @@ const handleReady = (payload) => {
|
||||
view.value = payload.view;
|
||||
};
|
||||
const detailShow = ref();
|
||||
const isOnEdit = ref();
|
||||
const currentStatus = ref();
|
||||
|
||||
const confirmDialogRef = ref();
|
||||
|
||||
@@ -77,7 +79,6 @@ const variables = reactive({
|
||||
slow_query_log: 'OFF',
|
||||
long_query_time: 10,
|
||||
});
|
||||
const oldVariables = ref();
|
||||
|
||||
interface DialogProps {
|
||||
mysqlName: string;
|
||||
@@ -88,29 +89,24 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
variables.slow_query_log = params.variables.slow_query_log;
|
||||
variables.long_query_time = Number(params.variables.long_query_time);
|
||||
|
||||
const pathRes = await loadBaseDir();
|
||||
let path = `${pathRes.data}/apps/mysql/${mysqlName.value}/data/1Panel-slow.log`;
|
||||
if (variables.slow_query_log === 'ON') {
|
||||
currentStatus.value = true;
|
||||
detailShow.value = true;
|
||||
const pathRes = await loadBaseDir();
|
||||
let path = `${pathRes.data}/apps/mysql/${mysqlName.value}/data/1Panel-slow.log`;
|
||||
loadMysqlSlowlogs(path);
|
||||
timer = setInterval(() => {
|
||||
if (variables.slow_query_log === 'ON' && isWatch.value) {
|
||||
loadMysqlSlowlogs(path);
|
||||
}
|
||||
}, 1000 * 5);
|
||||
}
|
||||
timer = setInterval(() => {
|
||||
if (variables.slow_query_log === 'ON' && isWatch.value) {
|
||||
loadMysqlSlowlogs(path);
|
||||
}
|
||||
}, 1000 * 5);
|
||||
oldVariables.value = { ...variables };
|
||||
};
|
||||
const emit = defineEmits(['loading']);
|
||||
|
||||
const handleSlowLogs = async () => {
|
||||
if (variables.slow_query_log === 'ON') {
|
||||
detailShow.value = true;
|
||||
isOnEdit.value = true;
|
||||
return;
|
||||
}
|
||||
if (isOnEdit.value) {
|
||||
detailShow.value = false;
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
@@ -121,7 +117,7 @@ const handleSlowLogs = async () => {
|
||||
confirmDialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const openSlowLogs = () => {
|
||||
const changeSlowLogs = () => {
|
||||
if (!(variables.long_query_time > 0 && variables.long_query_time <= 600)) {
|
||||
MsgError(i18n.global.t('database.thresholdRangeHelper'));
|
||||
return;
|
||||
@@ -135,14 +131,13 @@ const openSlowLogs = () => {
|
||||
};
|
||||
|
||||
const onCancle = async () => {
|
||||
variables.slow_query_log = variables.slow_query_log === 'ON' ? 'OFF' : 'ON';
|
||||
variables.slow_query_log = currentStatus.value ? 'ON' : 'OFF';
|
||||
detailShow.value = currentStatus.value;
|
||||
};
|
||||
|
||||
const onSave = async () => {
|
||||
let param = [] as Array<Database.VariablesUpdate>;
|
||||
if (variables.slow_query_log !== oldVariables.value.slow_query_log) {
|
||||
param.push({ param: 'slow_query_log', value: variables.slow_query_log });
|
||||
}
|
||||
param.push({ param: 'slow_query_log', value: variables.slow_query_log });
|
||||
if (variables.slow_query_log === 'ON') {
|
||||
param.push({ param: 'long_query_time', value: variables.long_query_time + '' });
|
||||
param.push({ param: 'slow_query_log_file', value: '/var/lib/mysql/1Panel-slow.log' });
|
||||
@@ -151,10 +146,8 @@ const onSave = async () => {
|
||||
await updateMysqlVariables(param)
|
||||
.then(() => {
|
||||
emit('loading', false);
|
||||
isOnEdit.value = false;
|
||||
if (variables.slow_query_log !== 'ON') {
|
||||
detailShow.value = false;
|
||||
}
|
||||
currentStatus.value = variables.slow_query_log === 'ON';
|
||||
detailShow.value = variables.slow_query_log === 'ON';
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -163,6 +156,10 @@ const onSave = async () => {
|
||||
};
|
||||
|
||||
const onDownload = async () => {
|
||||
if (!slowLogs.value) {
|
||||
MsgInfo(i18n.global.t('database.noData'));
|
||||
return;
|
||||
}
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([slowLogs.value]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
@@ -174,7 +171,7 @@ const onDownload = async () => {
|
||||
|
||||
const loadMysqlSlowlogs = async (path: string) => {
|
||||
const res = await LoadFile({ path: path });
|
||||
slowLogs.value = res.data;
|
||||
slowLogs.value = res.data || '';
|
||||
nextTick(() => {
|
||||
const state = view.value.state;
|
||||
view.value.dispatch({
|
||||
|
@@ -152,7 +152,7 @@ let mysqlVariables = reactive({
|
||||
});
|
||||
const variablesRules = reactive({
|
||||
key_buffer_size: [Rules.number, checkNumberRange(1, 102400)],
|
||||
query_cache_size: [Rules.number, checkNumberRange(1, 102400)],
|
||||
query_cache_size: [Rules.number, checkNumberRange(0, 102400)],
|
||||
tmp_table_size: [Rules.number, checkNumberRange(1, 102400)],
|
||||
innodb_buffer_pool_size: [Rules.number, checkNumberRange(1, 102400)],
|
||||
innodb_log_buffer_size: [Rules.number, checkNumberRange(1, 102400)],
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<template #app>
|
||||
<AppStatus
|
||||
:app-key="'redis'"
|
||||
style="margin-top: 20px"
|
||||
v-model:loading="loading"
|
||||
@before="onBefore"
|
||||
@setting="onSetting"
|
||||
@is-exist="checkExist"
|
||||
@@ -129,7 +129,6 @@ const closeTerminal = async (isKeepShow: boolean) => {
|
||||
|
||||
const onBefore = () => {
|
||||
closeTerminal(true);
|
||||
loading.value = true;
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
closeTerminal(false);
|
||||
|
@@ -163,7 +163,7 @@ const form = reactive({
|
||||
const rules = reactive({
|
||||
port: [Rules.port],
|
||||
timeout: [Rules.number, checkNumberRange(0, 9999999)],
|
||||
maxclients: [Rules.number, checkNumberRange(1, 999999)],
|
||||
maxclients: [Rules.number, checkNumberRange(1, 65504)],
|
||||
maxmemory: [Rules.number, checkNumberRange(0, 999999)],
|
||||
});
|
||||
|
||||
@@ -244,7 +244,6 @@ const onChangePort = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
const result = await formEl.validateField('port', callback);
|
||||
if (!result) {
|
||||
console.log('daqdwq');
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
@@ -318,6 +317,7 @@ const submtiFile = async () => {
|
||||
loading.value = true;
|
||||
await updateRedisConfByFile(param)
|
||||
.then(() => {
|
||||
useOld.value = false;
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
@@ -338,6 +338,7 @@ const loadform = async () => {
|
||||
const loadConfFile = async () => {
|
||||
const pathRes = await loadBaseDir();
|
||||
let path = `${pathRes.data}/apps/redis/${redisName.value}/conf/redis.conf`;
|
||||
useOld.value = false;
|
||||
loading.value = true;
|
||||
await LoadFile({ path: path })
|
||||
.then((res) => {
|
||||
|
@@ -9,12 +9,12 @@
|
||||
>
|
||||
<el-form :inline="true" :model="config">
|
||||
<el-form-item :label="$t('file.theme')">
|
||||
<el-select v-model="config.theme" @change="initEditor()">
|
||||
<el-select v-model="config.theme" @change="changeTheme()">
|
||||
<el-option v-for="item in themes" :key="item.label" :value="item.value" :label="item.label" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('file.language')">
|
||||
<el-select v-model="config.language" @change="initEditor()">
|
||||
<el-select v-model="config.language" @change="changeLanguage()">
|
||||
<el-option v-for="lang in Languages" :key="lang.label" :value="lang.value" :label="lang.label" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -36,11 +36,34 @@ import { SaveFileContent } from '@/api/modules/files';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { nextTick, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import { Languages } from '@/global/mimetype';
|
||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
||||
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||
|
||||
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
|
||||
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(workerId, label) {
|
||||
if (label === 'json') {
|
||||
return new jsonWorker();
|
||||
}
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return new cssWorker();
|
||||
}
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return new htmlWorker();
|
||||
}
|
||||
if (['typescript', 'javascript'].includes(label)) {
|
||||
return new tsWorker();
|
||||
}
|
||||
return new EditorWorker();
|
||||
},
|
||||
};
|
||||
|
||||
interface EditProps {
|
||||
language: string;
|
||||
content: string;
|
||||
@@ -90,29 +113,39 @@ const handleClose = () => {
|
||||
}
|
||||
em('close', false);
|
||||
};
|
||||
const changeLanguage = () => {
|
||||
monaco.editor.setModelLanguage(editor.getModel(), config.language);
|
||||
};
|
||||
|
||||
const changeTheme = () => {
|
||||
monaco.editor.setTheme(config.theme);
|
||||
};
|
||||
|
||||
const initEditor = () => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
const codeBox = document.getElementById('codeBox');
|
||||
editor = monaco.editor.create(codeBox as HTMLElement, {
|
||||
theme: config.theme,
|
||||
value: form.value.content,
|
||||
readOnly: false,
|
||||
automaticLayout: true,
|
||||
language: config.language,
|
||||
folding: true,
|
||||
roundedSelection: false,
|
||||
});
|
||||
nextTick(() => {
|
||||
const codeBox = document.getElementById('codeBox');
|
||||
editor = monaco.editor.create(codeBox as HTMLElement, {
|
||||
theme: config.theme,
|
||||
value: form.value.content,
|
||||
readOnly: false,
|
||||
automaticLayout: true,
|
||||
language: config.language,
|
||||
folding: true,
|
||||
roundedSelection: false,
|
||||
overviewRulerBorder: false,
|
||||
});
|
||||
|
||||
editor.onDidChangeModelContent(() => {
|
||||
if (editor) {
|
||||
form.value.content = editor.getValue();
|
||||
}
|
||||
});
|
||||
editor.onDidChangeModelContent(() => {
|
||||
if (editor) {
|
||||
form.value.content = editor.getValue();
|
||||
}
|
||||
});
|
||||
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, quickSave);
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, quickSave);
|
||||
});
|
||||
};
|
||||
|
||||
const quickSave = () => {
|
||||
@@ -139,6 +172,12 @@ const onOpen = () => {
|
||||
initEditor();
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
||||
|
||||
|
@@ -88,7 +88,7 @@
|
||||
<template #default="{ row }">
|
||||
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
|
||||
<svg-icon v-else className="table-icon" :iconName="getIconName(row.extension)"></svg-icon>
|
||||
<el-link :underline="false" @click="open(row)" type="primary">{{ row.name }}</el-link>
|
||||
<span class="table-link" @click="open(row)" type="primary">{{ row.name }}</span>
|
||||
<span v-if="row.isSymlink">-> {{ row.linkPath }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@@ -79,7 +79,7 @@ import type { ElForm } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { addHost, editHost, testByInfo, GetGroupList } from '@/api/modules/host';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref();
|
||||
|
||||
@@ -109,7 +109,7 @@ type FormInstance = InstanceType<typeof ElForm>;
|
||||
const hostInfoRef = ref<FormInstance>();
|
||||
const rules = reactive({
|
||||
groupID: [Rules.requiredSelect],
|
||||
addr: [Rules.requiredInput],
|
||||
addr: [Rules.host],
|
||||
port: [Rules.requiredInput, Rules.port],
|
||||
user: [Rules.requiredInput],
|
||||
authMode: [Rules.requiredSelect],
|
||||
@@ -165,7 +165,7 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
||||
if (res.data) {
|
||||
MsgSuccess(i18n.global.t('terminal.connTestOk'));
|
||||
} else {
|
||||
MsgSuccess(i18n.global.t('terminal.connTestFailed'));
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -95,7 +95,7 @@ let hostInfo = reactive<Host.HostOperate>({
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
addr: [Rules.ip],
|
||||
addr: [Rules.host],
|
||||
port: [Rules.requiredInput, Rules.port],
|
||||
user: [Rules.requiredInput],
|
||||
authMode: [Rules.requiredSelect],
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user