Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42e522abe1 | ||
|
|
fb5c3429e5 | ||
|
|
0fe1fd3c7b | ||
|
|
bdcca7f380 | ||
|
|
796d47d60e | ||
|
|
a9a45ce5ac | ||
|
|
d04121c551 | ||
|
|
dd06ff73e6 | ||
|
|
24b3501f38 | ||
|
|
ed11c0a4a6 | ||
|
|
7591a716f4 | ||
|
|
ce44ccdedb | ||
|
|
521fca93bd | ||
|
|
d6a0dc0125 | ||
|
|
0a483383b4 | ||
|
|
6c99e04aee | ||
|
|
e120bb0612 | ||
|
|
92a11863a7 | ||
|
|
2091fdbe5d | ||
|
|
b518463c90 | ||
|
|
94fbb265fa | ||
|
|
daefd650a5 | ||
|
|
4994cc39f1 | ||
|
|
2dec0bfb3c | ||
|
|
eb56b918a6 | ||
|
|
35098ce79c | ||
|
|
5a3a123be7 | ||
|
|
a94e78d31e | ||
|
|
5527ef73ad | ||
|
|
f1ed976c17 | ||
|
|
471bbb5c43 | ||
|
|
4ae8e580b9 | ||
|
|
ffb0e72a5b | ||
|
|
7f9793e4bb | ||
|
|
c332d0284b | ||
|
|
8b3d84d667 | ||
|
|
f009e6414a | ||
|
|
e36cbb0eb7 | ||
|
|
2fbddf3f30 | ||
|
|
8927b59bae | ||
|
|
90c7f9cc2c | ||
|
|
83ca72e153 | ||
|
|
9571d82932 | ||
|
|
c54451c733 | ||
|
|
e52ddd3f39 | ||
|
|
c488507d96 | ||
|
|
db5853df7d | ||
|
|
eb2e533d14 |
2
Makefile
2
Makefile
@@ -16,7 +16,7 @@ build_web:
|
||||
|
||||
build_bin:
|
||||
cd $(SERVER_PATH) \
|
||||
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_linux_on_mac:
|
||||
cd $(SERVER_PATH) \
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
以 root 用户执行如下命令一键安装 1Panel:
|
||||
|
||||
```sh
|
||||
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
|
||||
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sh quick_start.sh
|
||||
```
|
||||
|
||||
**学习资料**
|
||||
|
||||
@@ -128,3 +128,18 @@ func (b *BaseApi) GetAppTags(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, tags)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Get app list update
|
||||
// @Description 获取应用更新版本
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/checkupdate [get]
|
||||
func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
|
||||
res, err := appService.GetAppUpdate()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
@@ -167,6 +167,15 @@ func (b *BaseApi) InitUserInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Check System isDemo
|
||||
// @Description 判断是否为demo环境
|
||||
// @Success 200
|
||||
// @Router /auth/demo [get]
|
||||
func (b *BaseApi) CheckIsDemo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.CONF.System.IsDemo)
|
||||
}
|
||||
|
||||
func saveLoginLogs(c *gin.Context, err error) {
|
||||
var logs model.LoginLog
|
||||
if err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
websocket2 "github.com/1Panel-dev/1Panel/backend/utils/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -17,6 +18,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -492,6 +495,105 @@ func (b *BaseApi) LoadFromFile(c *gin.Context) {
|
||||
helper.SuccessWithData(c, string(content))
|
||||
}
|
||||
|
||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
||||
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i))
|
||||
chunkData, err := ioutil.ReadFile(chunkPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = targetFile.Write(chunkData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return files.NewFileOp().DeleteDir(fileDir)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary ChunkUpload file
|
||||
// @Description 分片上传文件
|
||||
// @Param file formData file true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/chunkupload [post]
|
||||
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"上传文件 [path]","formatEN":"Upload file [path]"}
|
||||
func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
fileForm, err := c.FormFile("chunk")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
uploadFile, err := fileForm.Open()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkCount, err := strconv.Atoi(c.PostForm("chunkCount"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
if err := fileOp.CreateDir("uploads", 0755); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
//fileID := uuid.New().String()
|
||||
filename := c.PostForm("filename")
|
||||
fileDir := filepath.Join(global.CONF.System.DataDir, "upload", filename)
|
||||
|
||||
os.MkdirAll(fileDir, 0755)
|
||||
filePath := filepath.Join(fileDir, filename)
|
||||
|
||||
emptyFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
emptyFile.Close()
|
||||
|
||||
chunkData, err := ioutil.ReadAll(uploadFile)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
||||
err = ioutil.WriteFile(chunkPath, chunkData, 0644)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var wsUpgrade = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
||||
//fileInfoList, err := ioutil.ReadDir(fileDir)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i))
|
||||
chunkData, err := ioutil.ReadFile(chunkPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = targetFile.Write(chunkData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return files.NewFileOp().DeleteDir(fileDir)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
fileForm, err := c.FormFile("chunk")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
uploadFile, err := fileForm.Open()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkCount, err := strconv.Atoi(c.PostForm("chunkCount"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
if err := fileOp.CreateDir("uploads", 0755); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
//fileID := uuid.New().String()
|
||||
filename := c.PostForm("filename")
|
||||
fileDir := filepath.Join(global.CONF.System.DataDir, "upload", filename)
|
||||
|
||||
_ = os.MkdirAll(fileDir, 0755)
|
||||
filePath := filepath.Join(fileDir, filename)
|
||||
|
||||
emptyFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
emptyFile.Close()
|
||||
|
||||
chunkData, err := ioutil.ReadAll(uploadFile)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
|
||||
err = ioutil.WriteFile(chunkPath, chunkData, 0644)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -57,15 +57,14 @@ func (b *BaseApi) TestByInfo(c *gin.Context) {
|
||||
}
|
||||
|
||||
var connInfo ssh.ConnInfo
|
||||
if err := copier.Copy(&connInfo, &req); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
}
|
||||
_ = copier.Copy(&connInfo, &req)
|
||||
connInfo.PrivateKey = []byte(req.PrivateKey)
|
||||
client, err := connInfo.NewClient()
|
||||
if err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
helper.SuccessWithData(c, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/terminal"
|
||||
@@ -39,10 +40,8 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
var connInfo ssh.ConnInfo
|
||||
if err := copier.Copy(&connInfo, &host); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, constant.ErrStructTransform)
|
||||
return
|
||||
}
|
||||
_ = copier.Copy(&connInfo, &host)
|
||||
connInfo.PrivateKey = []byte(host.PrivateKey)
|
||||
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
@@ -156,6 +155,15 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
cmds := fmt.Sprintf("docker exec %s %s", containerID, command)
|
||||
if len(user) != 0 {
|
||||
cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command)
|
||||
}
|
||||
stdout, err := cmd.Exec(cmds)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
|
||||
return
|
||||
}
|
||||
|
||||
commands := fmt.Sprintf("docker exec -it %s %s", containerID, command)
|
||||
if len(user) != 0 {
|
||||
commands = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command)
|
||||
@@ -187,8 +195,8 @@ func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handler ws faled:, err: %v", err)
|
||||
dt := time.Now().Add(time.Second)
|
||||
if err := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); err != nil {
|
||||
global.LOG.Errorf("websocket writes control message failed, err: %v", err)
|
||||
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ type ComposeContainer struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
type ComposeCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from" validate:"required,oneof=edit path template"`
|
||||
File string `json:"file"`
|
||||
Path string `json:"path"`
|
||||
|
||||
@@ -8,7 +8,7 @@ type HostOperate struct {
|
||||
ID uint `json:"id"`
|
||||
GroupID uint `json:"groupID"`
|
||||
Name string `json:"name"`
|
||||
Addr string `json:"addr" validate:"required,ip"`
|
||||
Addr string `json:"addr" validate:"required"`
|
||||
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||
User string `json:"user" validate:"required"`
|
||||
AuthMode string `json:"authMode" validate:"oneof=password key"`
|
||||
@@ -19,7 +19,7 @@ type HostOperate struct {
|
||||
}
|
||||
|
||||
type HostConnTest struct {
|
||||
Addr string `json:"addr" validate:"required,ip"`
|
||||
Addr string `json:"addr" validate:"required"`
|
||||
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||
User string `json:"user" validate:"required"`
|
||||
AuthMode string `json:"authMode" validate:"oneof=password key"`
|
||||
|
||||
@@ -19,6 +19,7 @@ type IMysqlRepo interface {
|
||||
Delete(ctx context.Context, opts ...DBOption) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
UpdateDatabaseInfo(id uint, vars map[string]interface{}) error
|
||||
DeleteAll(ctx context.Context) error
|
||||
}
|
||||
|
||||
func NewIMysqlRepo() IMysqlRepo {
|
||||
@@ -65,6 +66,10 @@ func (u *MysqlRepo) Delete(ctx context.Context, opts ...DBOption) error {
|
||||
return getTx(ctx, opts...).Delete(&model.DatabaseMysql{}).Error
|
||||
}
|
||||
|
||||
func (u *MysqlRepo) DeleteAll(ctx context.Context) error {
|
||||
return getTx(ctx).Where("1 = 1").Delete(&model.DatabaseMysql{}).Error
|
||||
}
|
||||
|
||||
func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.DatabaseMysql{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ type IAppService interface {
|
||||
GetAppDetail(appId uint, version string) (response.AppDetailDTO, error)
|
||||
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
|
||||
SyncAppList() error
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
}
|
||||
|
||||
func NewIAppService() IAppService {
|
||||
|
||||
@@ -275,16 +275,14 @@ func (a AppInstallService) SyncAll() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
for _, i := range allList {
|
||||
if i.Status == constant.Installing {
|
||||
continue
|
||||
}
|
||||
if err := syncById(i.ID); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
for _, i := range allList {
|
||||
if i.Status == constant.Installing {
|
||||
continue
|
||||
}
|
||||
}()
|
||||
if err := syncById(i.ID); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/env"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
@@ -13,6 +11,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/env"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
|
||||
@@ -159,7 +160,10 @@ func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBacku
|
||||
global.LOG.Infof("delete app %s-%s backups successful", install.App.Key, install.Name)
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
|
||||
|
||||
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key))
|
||||
if install.App.Key == constant.AppMysql {
|
||||
_ = mysqlRepo.DeleteAll(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -222,6 +222,12 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
if err := json.Unmarshal([]byte(req.Vars), &varMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldVars := backup.Vars
|
||||
oldDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["bucket"] = req.Bucket
|
||||
upMap["credential"] = req.Credential
|
||||
@@ -235,9 +241,8 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
if strings.HasSuffix(dirStr, "/") {
|
||||
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
|
||||
}
|
||||
if err := updateBackupDir(dirStr); err != nil {
|
||||
upMap["vars"] = backup.Vars
|
||||
_ = backupRepo.Update(req.ID, upMap)
|
||||
if err := updateBackupDir(dirStr, oldDir); err != nil {
|
||||
_ = backupRepo.Update(req.ID, (map[string]interface{}{"vars": oldVars}))
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -309,8 +314,7 @@ func loadLocalDir() (string, error) {
|
||||
return "", fmt.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
|
||||
func updateBackupDir(dir string) error {
|
||||
oldDir := global.CONF.System.Backup
|
||||
func updateBackupDir(dir, oldDir string) error {
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
@@ -323,6 +327,5 @@ func updateBackupDir(dir string) error {
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
global.CONF.System.Backup = dir
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
||||
}
|
||||
|
||||
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
if resource.ID != 0 {
|
||||
if resource.ID != 0 && resource.ResourceId != 0 {
|
||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -121,7 +121,10 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
|
||||
}
|
||||
|
||||
if appendonly == "yes" {
|
||||
if !strings.HasSuffix(recoverFile, ".tar.gz") || !strings.HasSuffix(recoverFile, ".aof") {
|
||||
if redisInfo.Version == "6.0.16" && !strings.HasSuffix(recoverFile, ".aof") {
|
||||
return buserr.New(constant.ErrTypeOfRedis)
|
||||
}
|
||||
if redisInfo.Version == "7.0.5" && !strings.HasSuffix(recoverFile, ".tar.gz") {
|
||||
return buserr.New(constant.ErrTypeOfRedis)
|
||||
}
|
||||
} else {
|
||||
@@ -170,7 +173,7 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
|
||||
}
|
||||
} else {
|
||||
itemName := "dump.rdb"
|
||||
if redisInfo.Version == "6.0.16" {
|
||||
if appendonly == "yes" && redisInfo.Version == "6.0.16" {
|
||||
itemName = "appendonly.aof"
|
||||
}
|
||||
input, err := ioutil.ReadFile(recoverFile)
|
||||
@@ -184,5 +187,6 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
|
||||
if _, err := compose.Up(composeDir + "/docker-compose.yml"); err != nil {
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ 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/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
@@ -136,6 +138,13 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
||||
if len(req.ExposedPorts) != 0 {
|
||||
for _, port := range req.ExposedPorts {
|
||||
if common.ScanPort(port.HostPort) {
|
||||
return buserr.WithDetail(constant.ErrPortInUsed, port.HostPort, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -170,7 +170,7 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
if req.Operation == "down" {
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
_ = os.RemoveAll(strings.ReplaceAll(req.Path, req.Name+"/docker-compose.yml", ""))
|
||||
_ = os.RemoveAll(strings.ReplaceAll(req.Path, "/docker-compose.yml", ""))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -178,24 +178,27 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
|
||||
cronjob.Spec = loadSpec(cronjob)
|
||||
|
||||
global.LOG.Infof("create cronjob %s successful, spec: %s", cronjob.Name, cronjob.Spec)
|
||||
if err := u.StartJob(&cronjob); err != nil {
|
||||
entryID, err := u.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cronjob.EntryID = uint64(entryID)
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) StartJob(cronjob *model.Cronjob) error {
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
|
||||
func (u *CronjobService) StartJob(cronjob *model.Cronjob) (int, error) {
|
||||
if cronjob.EntryID != 0 {
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
}
|
||||
entryID, err := u.AddCronJob(cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
_ = cronjobRepo.Update(cronjob.ID, map[string]interface{}{"entry_id": entryID})
|
||||
return nil
|
||||
global.LOG.Debug(global.Cron.Entries())
|
||||
return entryID, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Delete(ids []uint) error {
|
||||
@@ -225,12 +228,15 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
cronjob.EntryID = cronModel.EntryID
|
||||
cronjob.Type = cronModel.Type
|
||||
cronjob.Spec = loadSpec(cronjob)
|
||||
if err := u.StartJob(&cronjob); err != nil {
|
||||
newEntryID, err := u.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["entry_id"] = newEntryID
|
||||
upMap["name"] = req.Name
|
||||
upMap["script"] = req.Script
|
||||
upMap["spec_type"] = req.SpecType
|
||||
@@ -254,15 +260,20 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
if cronjob.ID == 0 {
|
||||
return errors.WithMessage(constant.ErrRecordNotFound, "record not found")
|
||||
}
|
||||
var (
|
||||
entryID int
|
||||
err error
|
||||
)
|
||||
if status == constant.StatusEnable {
|
||||
if err := u.StartJob(&cronjob); err != nil {
|
||||
entryID, err = u.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
|
||||
}
|
||||
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status})
|
||||
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_id": entryID})
|
||||
}
|
||||
|
||||
func (u *CronjobService) AddCronJob(cronjob *model.Cronjob) (int, error) {
|
||||
|
||||
@@ -97,7 +97,7 @@ func (f FileService) Create(op request.FileCreate) error {
|
||||
return fo.CreateDir(op.Path, fs.FileMode(op.Mode))
|
||||
} else {
|
||||
if op.IsLink {
|
||||
if !fo.Stat(op.Path) {
|
||||
if !fo.Stat(op.LinkPath) {
|
||||
return buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
return fo.LinkFile(op.LinkPath, op.Path, op.IsSymlink)
|
||||
|
||||
@@ -122,7 +122,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
if req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/docker/build/%s", constant.DataDir, req.Name)
|
||||
dir := fmt.Sprintf("%s/docker/build/%s", constant.DataDir, strings.ReplaceAll(req.Name, ":", "_"))
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
@@ -139,6 +139,8 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||
_, _ = write.WriteString(string(req.Dockerfile))
|
||||
write.Flush()
|
||||
req.Dockerfile = dir
|
||||
} else {
|
||||
req.Dockerfile = strings.ReplaceAll(req.Dockerfile, "/Dockerfile", "")
|
||||
}
|
||||
tar, err := archive.TarWithOptions(req.Dockerfile+"/", &archive.TarOptions{})
|
||||
if err != nil {
|
||||
@@ -161,13 +163,27 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||
go func() {
|
||||
defer file.Close()
|
||||
defer tar.Close()
|
||||
res, err := client.ImageBuild(context.TODO(), tar, opts)
|
||||
res, err := client.ImageBuild(context.Background(), tar, opts)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("build image %s failed, err: %v", req.Name, err)
|
||||
_, _ = file.WriteString("image build failed!")
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("build image %s failed, err: %v", req.Name, err)
|
||||
_, _ = file.WriteString(fmt.Sprintf("build image %s failed, err: %v", req.Name, err))
|
||||
_, _ = file.WriteString("image build failed!")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(string(body), "error") && strings.Contains(string(body), "failed:") {
|
||||
global.LOG.Errorf("build image %s failed", req.Name)
|
||||
_, _ = file.Write(body)
|
||||
_, _ = file.WriteString("image build failed!")
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("build image %s successful!", req.Name)
|
||||
_, _ = io.Copy(file, res.Body)
|
||||
_, _ = file.WriteString("image build successful!")
|
||||
|
||||
@@ -172,17 +172,17 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "snapshot", global.CONF.BaseDir+"/1panel", backupPanelDir, localDir, dockerDataDir); err != nil {
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "snapshot", global.CONF.System.BaseDir+"/1panel", backupPanelDir, localDir, dockerDataDir); err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
|
||||
snapJson := SnapshotJson{
|
||||
BaseDir: global.CONF.BaseDir,
|
||||
BaseDir: global.CONF.System.BaseDir,
|
||||
DockerDataDir: dockerDataDir,
|
||||
BackupDataDir: localDir,
|
||||
PanelDataDir: global.CONF.BaseDir + "/1panel",
|
||||
PanelDataDir: global.CONF.System.BaseDir + "/1panel",
|
||||
LiveRestoreEnabled: liveRestoreStatus,
|
||||
}
|
||||
if err := u.saveJson(snapJson, rootDir); err != nil {
|
||||
@@ -287,8 +287,8 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
u.OriginalPath = fmt.Sprintf("%s/original_%s", snapJson.BaseDir, snap.Name)
|
||||
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
|
||||
|
||||
snapJson.OldBaseDir = global.CONF.BaseDir
|
||||
snapJson.OldPanelDataDir = global.CONF.BaseDir + "/1panel"
|
||||
snapJson.OldBaseDir = global.CONF.System.BaseDir
|
||||
snapJson.OldPanelDataDir = global.CONF.System.BaseDir + "/1panel"
|
||||
snapJson.OldBackupDataDir = localDir
|
||||
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
|
||||
liveRestore := false
|
||||
|
||||
@@ -81,7 +81,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", runtime.GOARCH)
|
||||
_ = settingRepo.Update("SystemStatus", "Upgrading")
|
||||
go func() {
|
||||
if err := fileOp.DownloadFile(downloadPath+"/"+fileName, rootDir+"/service.tar.gz"); err != nil {
|
||||
if err := fileOp.DownloadFile(downloadPath+"/"+fileName, rootDir+"/"+fileName); err != nil {
|
||||
global.LOG.Errorf("download service file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
@@ -90,11 +90,12 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
defer func() {
|
||||
_ = os.Remove(rootDir)
|
||||
}()
|
||||
if err := fileOp.Decompress(rootDir+"/service.tar.gz", rootDir, files.TarGz); err != nil {
|
||||
if err := handleUnTar(rootDir+"/"+fileName, rootDir); err != nil {
|
||||
global.LOG.Errorf("decompress file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
tmpDir := rootDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "")
|
||||
|
||||
if err := u.handleBackup(fileOp, originalDir); err != nil {
|
||||
global.LOG.Errorf("handle backup original file failed, err: %v", err)
|
||||
@@ -103,17 +104,24 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
}
|
||||
global.LOG.Info("backup original data successful, now start to upgrade!")
|
||||
|
||||
if err := cpBinary(rootDir+"/1panel", "/usr/local/bin/1panel"); err != nil {
|
||||
if err := cpBinary(tmpDir+"/1panel", "/usr/local/bin/1panel"); err != nil {
|
||||
u.handleRollback(fileOp, originalDir, 1)
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := cpBinary(fmt.Sprintf("%s/1panel-online-installer-%s/1pctl", rootDir, req.Version), "/usr/local/bin/1pctl"); err != nil {
|
||||
|
||||
if err := cpBinary(tmpDir+"/1pctl", "/usr/local/bin/1pctl"); err != nil {
|
||||
u.handleRollback(fileOp, originalDir, 2)
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := cpBinary(fmt.Sprintf("%s/1panel-online-installer-%s/1panel/conf/1panel.service", rootDir, req.Version), "/etc/systemd/system/1panel.service"); err != nil {
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
|
||||
u.handleRollback(fileOp, originalDir, 2)
|
||||
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cpBinary(tmpDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil {
|
||||
u.handleRollback(fileOp, originalDir, 3)
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
return
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package configs
|
||||
|
||||
type ServerConfig struct {
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
System System `mapstructure:"system"`
|
||||
LogConfig LogConfig `mapstructure:"log"`
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ type System struct {
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
RepoUrl string `mapstructure:"repo_url"`
|
||||
Version string `mapstructure:"version"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/cron/job"
|
||||
@@ -44,8 +45,12 @@ func Run() {
|
||||
global.LOG.Errorf("start my cronjob failed, err: %v", err)
|
||||
}
|
||||
for _, cronjob := range cronJobs {
|
||||
if err := service.ServiceGroupApp.StartJob(&cronjob); err != nil {
|
||||
entryID, err := service.ServiceGroupApp.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("start %s job %s failed, err: %v", cronjob.Type, cronjob.Name, err)
|
||||
}
|
||||
if err := repo.NewICronjobRepo().Update(cronjob.ID, map[string]interface{}{"entry_id": entryID}); err != nil {
|
||||
global.LOG.Errorf("update cronjob %s %s failed, err: %v", cronjob.Type, cronjob.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ var AddTableSetting = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.Setting{Key: "DingVars", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: "v1.0.0"}).Error; err != nil {
|
||||
if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: global.CONF.System.Version}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SystemStatus", Value: "Free"}).Error; err != nil {
|
||||
|
||||
@@ -20,6 +20,7 @@ func Init() {
|
||||
baseDir := "/opt"
|
||||
port := "9999"
|
||||
mode := ""
|
||||
version := ""
|
||||
fileOp := files.NewFileOp()
|
||||
v := viper.NewWithOptions()
|
||||
v.SetConfigType("yaml")
|
||||
@@ -38,23 +39,9 @@ func Init() {
|
||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
||||
}
|
||||
} else {
|
||||
stdout, err := cmd.Exec("grep '^BASE_DIR=' /usr/bin/1pctl | cut -d'=' -f2")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
baseDir = strings.ReplaceAll(stdout, "\n", "")
|
||||
if len(baseDir) == 0 {
|
||||
panic("error `BASE_DIR` find in /usr/bin/1pctl")
|
||||
}
|
||||
|
||||
stdoutPort, err := cmd.Exec("grep '^PANEL_PORT=' /usr/bin/1pctl | cut -d'=' -f2")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
port = strings.ReplaceAll(stdoutPort, "\n", "")
|
||||
if len(port) == 0 {
|
||||
panic("error `PANEL_PORT` find in /usr/bin/1pctl")
|
||||
}
|
||||
baseDir = loadParams("BASE_DIR")
|
||||
port = loadParams("PANEL_PORT")
|
||||
version = loadParams("ORIGINAL_INSTALLED_VERSION")
|
||||
|
||||
if strings.HasSuffix(baseDir, "/") {
|
||||
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
|
||||
@@ -79,16 +66,32 @@ func Init() {
|
||||
if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") && serverConfig.System.Port != "" {
|
||||
port = serverConfig.System.Port
|
||||
}
|
||||
if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") && serverConfig.System.Version != "" {
|
||||
version = serverConfig.System.Version
|
||||
}
|
||||
|
||||
global.CONF = serverConfig
|
||||
global.CONF.BaseDir = baseDir
|
||||
global.CONF.System.BaseDir = baseDir
|
||||
global.CONF.System.IsDemo = v.GetBool("system.is_demo")
|
||||
global.CONF.System.DataDir = global.CONF.BaseDir + "/1panel"
|
||||
global.CONF.System.DataDir = global.CONF.System.BaseDir + "/1panel"
|
||||
global.CONF.System.Cache = global.CONF.System.DataDir + "/cache"
|
||||
global.CONF.System.Backup = global.CONF.System.DataDir + "/backup"
|
||||
global.CONF.System.DbPath = global.CONF.System.DataDir + "/db"
|
||||
global.CONF.System.LogPath = global.CONF.System.DataDir + "/log"
|
||||
global.CONF.System.TmpDir = global.CONF.System.DataDir + "/tmp"
|
||||
global.CONF.System.Port = port
|
||||
global.CONF.System.Version = version
|
||||
global.Viper = v
|
||||
}
|
||||
|
||||
func loadParams(param string) string {
|
||||
stdout, err := cmd.Execf("grep '^%s=' /usr/bin/1pctl | cut -d'=' -f2", param)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
baseDir := strings.ReplaceAll(stdout, "\n", "")
|
||||
if len(baseDir) == 0 {
|
||||
panic(fmt.Sprintf("error `%s` find in /usr/bin/1pctl", param))
|
||||
}
|
||||
return baseDir
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
appRouter.POST("/sync", baseApi.SyncApp)
|
||||
appRouter.GET("/checkupdate", baseApi.GetAppListUpdate)
|
||||
appRouter.POST("/search", baseApi.SearchApp)
|
||||
appRouter.GET("/:key", baseApi.GetApp)
|
||||
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
|
||||
|
||||
@@ -17,5 +17,6 @@ func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) {
|
||||
baseRouter.GET("/status", baseApi.CheckIsFirstLogin)
|
||||
baseRouter.POST("/init", baseApi.InitUserInfo)
|
||||
baseRouter.POST("/logout", baseApi.LogOut)
|
||||
baseRouter.GET("/demo", baseApi.CheckIsDemo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/content", baseApi.GetContent)
|
||||
fileRouter.POST("/save", baseApi.SaveContent)
|
||||
fileRouter.POST("/check", baseApi.CheckFile)
|
||||
fileRouter.POST("/upload", baseApi.UploadChunkFiles)
|
||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||
fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles)
|
||||
fileRouter.POST("/rename", baseApi.ChangeFileName)
|
||||
fileRouter.POST("/wget", baseApi.WgetFile)
|
||||
fileRouter.POST("/move", baseApi.MoveFile)
|
||||
|
||||
@@ -19,8 +19,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
router.POST("/search", baseApi.GetSettingInfo)
|
||||
router.POST("/expired/handle", baseApi.HandlePasswordExpired)
|
||||
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
||||
settingRouter.POST("/expired/handle", baseApi.HandlePasswordExpired)
|
||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||
settingRouter.POST("/port/update", baseApi.UpdatePort)
|
||||
settingRouter.POST("/password/update", baseApi.UpdatePassword)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -379,7 +381,9 @@ func getFormat(cType CompressType) archiver.CompressedArchive {
|
||||
format.Compression = archiver.Gz{}
|
||||
format.Archival = archiver.Tar{}
|
||||
case Zip:
|
||||
format.Archival = archiver.Zip{}
|
||||
format.Archival = archiver.Zip{
|
||||
Compression: zip.Deflate,
|
||||
}
|
||||
case Bz2:
|
||||
format.Compression = archiver.Bz2{}
|
||||
format.Archival = archiver.Tar{}
|
||||
@@ -413,9 +417,19 @@ func (f FileOp) Compress(srcRiles []string, dst string, name string, cType Compr
|
||||
return err
|
||||
}
|
||||
|
||||
err = format.Archive(context.Background(), out, files)
|
||||
if err != nil {
|
||||
return err
|
||||
switch cType {
|
||||
case Zip:
|
||||
if err := ZipFile(files, out); err != nil {
|
||||
_ = f.DeleteFile(dstFile)
|
||||
return err
|
||||
}
|
||||
default:
|
||||
err = format.Archive(context.Background(), out, files)
|
||||
if err != nil {
|
||||
_ = f.DeleteFile(dstFile)
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -488,3 +502,46 @@ func (f FileOp) CopyAndBackup(src string) (string, error) {
|
||||
}
|
||||
return backupPath, nil
|
||||
}
|
||||
|
||||
func ZipFile(files []archiver.File, dst afero.File) error {
|
||||
zw := zip.NewWriter(dst)
|
||||
defer zw.Close()
|
||||
|
||||
for _, file := range files {
|
||||
hdr, err := zip.FileInfoHeader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = file.NameInArchive
|
||||
if file.IsDir() {
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
hdr.Name += "/"
|
||||
}
|
||||
hdr.Method = zip.Store
|
||||
}
|
||||
w, err := zw.CreateHeader(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if file.LinkTarget != "" {
|
||||
_, err = w.Write([]byte(filepath.ToSlash(file.LinkTarget)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(w, fileReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -46,7 +44,7 @@ func (c *ConnInfo) NewClient() (*ConnInfo, error) {
|
||||
}
|
||||
config.Timeout = c.DialTimeOut
|
||||
|
||||
config.HostKeyCallback = func(hostname string, remote net.Addr, key gossh.PublicKey) error { return nil }
|
||||
config.HostKeyCallback = gossh.InsecureIgnoreHostKey()
|
||||
client, err := gossh.Dial("tcp", addr, config)
|
||||
if nil != err {
|
||||
return c, err
|
||||
@@ -73,9 +71,7 @@ func (c *ConnInfo) Run(shell string) (string, error) {
|
||||
}
|
||||
|
||||
func (c *ConnInfo) Close() {
|
||||
if err := c.Client.Close(); err != nil {
|
||||
global.LOG.Errorf("close ssh client failed, err: %v", err)
|
||||
}
|
||||
_ = c.Client.Close()
|
||||
}
|
||||
|
||||
type SshConn struct {
|
||||
|
||||
@@ -194,11 +194,11 @@ func (p *manualDnsProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
func (c *AcmeClient) GetDNSResolve(domains []string) (map[string]Resolve, error) {
|
||||
core, err := api.New(c.Config.HTTPClient, c.Config.UserAgent, c.Config.CADirURL, c.User.Registration.URI, c.User.Key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
order, err := core.Orders.New(domains)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
resolves := make(map[string]Resolve)
|
||||
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
||||
|
||||
3
frontend/components.d.ts
vendored
3
frontend/components.d.ts
vendored
@@ -19,7 +19,6 @@ declare module 'vue' {
|
||||
ContainerLog: typeof import('./src/components/container-log/index.vue')['default']
|
||||
DrawerHeader: typeof import('./src/components/drawer-header/index.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAlter: typeof import('element-plus/es')['ElAlter']
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
@@ -80,11 +79,13 @@ declare module 'vue' {
|
||||
Loading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
Logo: typeof import('./src/components/app-layout/menu/components/Logo.vue')['default']
|
||||
Menu: typeof import('./src/components/app-layout/menu/index.vue')['default']
|
||||
MsgInfo: typeof import('./src/components/msg-info/index.vue')['default']
|
||||
Popover: typeof import('element-plus/es')['ElPopoverDirective']
|
||||
RouterButton: typeof import('./src/components/router-button/index.vue')['default']
|
||||
Status: typeof import('./src/components/status/index.vue')['default']
|
||||
SubItem: typeof import('./src/components/app-layout/menu/components/sub-item.vue')['default']
|
||||
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']
|
||||
Upload: typeof import('./src/components/upload/index.vue')['default']
|
||||
VCharts: typeof import('./src/components/v-charts/index.vue')['default']
|
||||
|
||||
@@ -27,6 +27,11 @@ export namespace App {
|
||||
items: App.App[];
|
||||
}
|
||||
|
||||
export interface AppUpdateRes {
|
||||
version: string;
|
||||
canUpdate: boolean;
|
||||
}
|
||||
|
||||
export interface AppDetail extends CommonModel {
|
||||
appId: string;
|
||||
icon: string;
|
||||
|
||||
@@ -32,10 +32,3 @@ export interface DescriptionUpdate {
|
||||
id: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// * 文件上传模块
|
||||
export namespace Upload {
|
||||
export interface ResFileUrl {
|
||||
fileUrl: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ export const SyncApp = () => {
|
||||
return http.post<any>('apps/sync', {});
|
||||
};
|
||||
|
||||
export const GetAppListUpdate = () => {
|
||||
return http.get<App.AppUpdateRes>('apps/checkupdate');
|
||||
};
|
||||
|
||||
export const SearchApp = (req: App.AppReq) => {
|
||||
return http.post<App.AppResPage>('apps/search', req);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,11 @@ export const loginStatus = () => {
|
||||
export const checkIsFirst = () => {
|
||||
return http.get<boolean>('/auth/status');
|
||||
};
|
||||
|
||||
export const initUser = (params: Login.InitUser) => {
|
||||
return http.post(`/auth/init`, params);
|
||||
};
|
||||
|
||||
export const checkIsDemo = () => {
|
||||
return http.get<boolean>('/auth/demo');
|
||||
};
|
||||
|
||||
@@ -59,6 +59,10 @@ export const UploadFileData = (params: FormData, config: AxiosRequestConfig) =>
|
||||
return http.upload<File.File>('files/upload', params, config);
|
||||
};
|
||||
|
||||
export const ChunkUploadFileData = (params: FormData, config: AxiosRequestConfig) => {
|
||||
return http.upload<File.File>('files/chunkupload', params, config);
|
||||
};
|
||||
|
||||
export const RenameRile = (params: File.FileRename) => {
|
||||
return http.post<File.File>('files/rename', params);
|
||||
};
|
||||
@@ -72,7 +76,7 @@ export const MoveFile = (params: File.FileMove) => {
|
||||
};
|
||||
|
||||
export const DownloadFile = (params: File.FileDownload) => {
|
||||
return http.download<BlobPart>('files/download', params, { responseType: 'blob' });
|
||||
return http.download<BlobPart>('files/download', params, { responseType: 'blob', timeout: 20000 });
|
||||
};
|
||||
|
||||
export const ComputeDirSize = (params: File.DirSizeReq) => {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Upload } from '@/api/interface/index';
|
||||
import { PORT1 } from '@/api/config/service-port';
|
||||
|
||||
import http from '@/api';
|
||||
|
||||
/**
|
||||
* @name 文件上传模块
|
||||
*/
|
||||
// * 图片上传
|
||||
export const uploadImg = (params: FormData) => {
|
||||
return http.post<Upload.ResFileUrl>(PORT1 + `/file/upload/img`, params);
|
||||
};
|
||||
|
||||
// * 视频上传
|
||||
export const uploadVideo = (params: FormData) => {
|
||||
return http.post<Upload.ResFileUrl>(PORT1 + `/file/upload/video`, params);
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 576 KiB After Width: | Height: | Size: 170 KiB |
@@ -4,14 +4,18 @@
|
||||
</div>
|
||||
<div class="footer flx-justify-between">
|
||||
<div class="footer-left">
|
||||
<a href="https://fit2cloud.com/" target="_blank">杭州飞致云信息科技有限公司</a>
|
||||
<a href="https://fit2cloud.com/" target="_blank">Copyright @2014-2023 FIT2CLOUND 飞致云</a>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span>Version V1</span>
|
||||
<SystemUpgrade />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SystemUpgrade from '@/components/system-upgrade/index.vue';
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.footer {
|
||||
height: 45px;
|
||||
|
||||
@@ -57,15 +57,17 @@ const loadStatus = async () => {
|
||||
await getSystemAvailable()
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
location.reload();
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
location.reload();
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
}, 1000 * 20);
|
||||
}, 1000 * 10);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-table-column :label="$t('commons.table.operate')">
|
||||
<template #default="{ row, $index }">
|
||||
<div>
|
||||
<el-button link v-if="row.edit" type="primary" @click="saveGroup(groupForm, row, true)">
|
||||
<el-button link v-if="row.edit" type="primary" @click="saveGroup(groupForm, row)">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
<el-button link v-if="!row.edit" type="primary" @click="editGroup($index)">
|
||||
@@ -46,12 +46,7 @@
|
||||
<el-button link v-if="row.edit" type="primary" @click="search()">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
v-if="!row.edit && !row.isDefault"
|
||||
type="primary"
|
||||
@click="saveGroup(groupForm, row, false)"
|
||||
>
|
||||
<el-button link v-if="!row.edit && !row.isDefault" type="primary" @click="setDefault(row)">
|
||||
{{ $t('website.setDefault') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -83,7 +78,7 @@ interface DialogProps {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const groupForm = ref();
|
||||
const groupForm = ref<FormInstance>();
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
type.value = params.type;
|
||||
open.value = true;
|
||||
@@ -106,16 +101,13 @@ const search = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const saveGroup = async (formEl: FormInstance, group: Group.GroupInfo, isEdit: boolean) => {
|
||||
const saveGroup = async (formEl: FormInstance, group: Group.GroupInfo) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
group.type = type.value;
|
||||
if (!isEdit) {
|
||||
group.isDefault = true;
|
||||
}
|
||||
if (group.id == 0) {
|
||||
CreateGroup(group).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
@@ -130,6 +122,14 @@ const saveGroup = async (formEl: FormInstance, group: Group.GroupInfo, isEdit: b
|
||||
});
|
||||
};
|
||||
|
||||
const setDefault = (group: Group.GroupInfo) => {
|
||||
group.isDefault = true;
|
||||
UpdateGroup(group).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
search();
|
||||
});
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
for (const d of data.value) {
|
||||
if (d.name == '') {
|
||||
|
||||
34
frontend/src/components/msg-info/index.vue
Normal file
34
frontend/src/components/msg-info/index.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<el-tooltip placement="top-start">
|
||||
<template #content>
|
||||
<div class="info-break" :style="{ width: width + 'px' }">{{ info }}</div>
|
||||
</template>
|
||||
<div class="info-hidden" :style="{ width: width + 'px' }">{{ info }}</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
width: {
|
||||
type: String,
|
||||
default: '100',
|
||||
},
|
||||
info: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.info-hidden {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.info-break {
|
||||
width: 100px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -25,7 +25,7 @@ const getType = (status: string) => {
|
||||
case 'error':
|
||||
return 'danger';
|
||||
case 'stopped':
|
||||
return 'warning';
|
||||
return 'danger';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
115
frontend/src/components/system-upgrade/index.vue
Normal file
115
frontend/src/components/system-upgrade/index.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="version">{{ version }}</span>
|
||||
<el-button v-if="version !== 'Waiting'" type="primary" link @click="onLoadUpgradeInfo">
|
||||
{{ $t('setting.upgradeCheck') }}
|
||||
</el-button>
|
||||
<el-tag v-else round style="margin-left: 10px">{{ $t('setting.upgrading') }}</el-tag>
|
||||
</div>
|
||||
|
||||
<el-drawer :close-on-click-modal="false" :key="refresh" v-model="drawerVisiable" size="50%" append-to-body>
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('setting.upgrade')" :back="handleClose" />
|
||||
</template>
|
||||
<div class="panel-MdEditor">
|
||||
<div class="default-theme">
|
||||
<h2 class="inline-block">{{ $t('setting.newVersion') }}</h2>
|
||||
<el-tag class="inline-block tag">{{ upgradeInfo.newVersion }}</el-tag>
|
||||
</div>
|
||||
<MdEditor v-model="upgradeInfo.releaseNote" previewOnly />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onUpgrade">{{ $t('setting.upgradeNow') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getSettingInfo, loadUpgradeInfo, upgrade } from '@/api/modules/setting';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import i18n from '@/lang';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const version = ref();
|
||||
let loading = ref(false);
|
||||
const drawerVisiable = ref(false);
|
||||
const upgradeInfo = ref();
|
||||
const refresh = ref();
|
||||
|
||||
const search = async () => {
|
||||
const res = await getSettingInfo();
|
||||
version.value = res.data.systemVersion;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisiable.value = false;
|
||||
};
|
||||
|
||||
const onLoadUpgradeInfo = async () => {
|
||||
loading.value = true;
|
||||
await loadUpgradeInfo()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
|
||||
if (!res.data) {
|
||||
MsgSuccess(i18n.global.t('setting.noUpgrade'));
|
||||
return;
|
||||
}
|
||||
upgradeInfo.value = res.data;
|
||||
drawerVisiable.value = true;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onUpgrade = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('setting.upgradeHelper', i18n.global.t('setting.upgrade')), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
globalStore.isLoading = true;
|
||||
await upgrade(upgradeInfo.value.newVersion);
|
||||
drawerVisiable.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.version {
|
||||
font-size: 14px;
|
||||
color: #858585;
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.panel-MdEditor {
|
||||
height: calc(100vh - 330px);
|
||||
margin-left: 70px;
|
||||
.tag {
|
||||
margin-left: 20px;
|
||||
margin-top: -6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
:deep(.md-editor-preview) {
|
||||
font-size: 14px;
|
||||
}
|
||||
:deep(.default-theme h2) {
|
||||
margin: 13px 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -115,12 +115,11 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
const pathRes = await loadBaseDir();
|
||||
if (type.value === 'mysql') {
|
||||
title.value = name.value + ' [ ' + detailName.value + ' ]';
|
||||
baseDir.value = `${pathRes.data}/uploads/database/mysql/${name.value}/${detailName.value}/`;
|
||||
}
|
||||
if (type.value === 'website' || type.value === 'app') {
|
||||
title.value = name.value;
|
||||
baseDir.value = `${pathRes.data}/uploads/${type.value}/${name.value}/`;
|
||||
}
|
||||
baseDir.value = `${pathRes.data}/uploads/${type.value}/${name.value}/${detailName.value}/`;
|
||||
upVisiable.value = true;
|
||||
search();
|
||||
};
|
||||
@@ -204,7 +203,7 @@ const onSubmit = async () => {
|
||||
MsgError(i18n.global.t('commons.msg.fileNameErr'));
|
||||
return;
|
||||
}
|
||||
const res = await CheckFile(baseDir.value + '/' + uploaderFiles.value[0]!.raw.name);
|
||||
const res = await CheckFile(baseDir.value + uploaderFiles.value[0]!.raw.name);
|
||||
if (!res.data) {
|
||||
MsgError(i18n.global.t('commons.msg.fileExist'));
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@ export enum ResultEnum {
|
||||
EXPIRED = 405,
|
||||
ERRAUTH = 406,
|
||||
ERRGLOBALLOADDING = 407,
|
||||
TIMEOUT = 100000,
|
||||
TIMEOUT = 20000,
|
||||
TYPE = 'success',
|
||||
}
|
||||
|
||||
|
||||
@@ -15,3 +15,66 @@ export const Mimetypes = new Map([
|
||||
['gzip/document', CompressType.TarGz],
|
||||
['application/x-xz', CompressType.Xz],
|
||||
]);
|
||||
|
||||
export const Languages = [
|
||||
{
|
||||
label: 'plaintext',
|
||||
value: 'plaintext',
|
||||
},
|
||||
{
|
||||
label: 'json',
|
||||
value: 'json',
|
||||
},
|
||||
{
|
||||
label: 'go',
|
||||
value: 'go',
|
||||
},
|
||||
{
|
||||
label: 'html',
|
||||
value: 'html',
|
||||
},
|
||||
{
|
||||
label: 'javascript',
|
||||
value: 'javascript',
|
||||
},
|
||||
{
|
||||
label: 'java',
|
||||
value: 'java',
|
||||
},
|
||||
{
|
||||
label: 'kotlin',
|
||||
value: 'kotlin',
|
||||
},
|
||||
{
|
||||
label: 'markdown',
|
||||
value: 'markdown',
|
||||
},
|
||||
{
|
||||
label: 'mysql',
|
||||
value: 'mysql',
|
||||
},
|
||||
{
|
||||
label: 'php',
|
||||
value: 'php',
|
||||
},
|
||||
{
|
||||
label: 'redis',
|
||||
value: 'redis',
|
||||
},
|
||||
{
|
||||
label: 'shell',
|
||||
value: 'shell',
|
||||
},
|
||||
{
|
||||
label: 'sql',
|
||||
value: 'sql',
|
||||
},
|
||||
{
|
||||
label: 'yaml',
|
||||
value: 'yaml',
|
||||
},
|
||||
{
|
||||
label: 'css',
|
||||
value: 'css',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { ElNotification } from 'element-plus';
|
||||
|
||||
/**
|
||||
* @description 接收数据流生成blob,创建链接,下载文件
|
||||
* @param {Function} api 导出表格的api方法(必传)
|
||||
* @param {String} tempName 导出的文件名(必传)
|
||||
* @param {Object} params 导出的参数(默认为空对象)
|
||||
* @param {Boolean} isNotify 是否有导出消息提示(默认为 true)
|
||||
* @param {String} fileType 导出的文件格式(默认为.xlsx)
|
||||
* @return void
|
||||
* */
|
||||
export const useDownload = async (
|
||||
api: (param: any) => Promise<any>,
|
||||
tempName: string,
|
||||
params: any = {},
|
||||
isNotify: boolean = true,
|
||||
fileType: string = '.xlsx',
|
||||
) => {
|
||||
if (isNotify) {
|
||||
ElNotification({
|
||||
title: '温馨提示',
|
||||
message: '如果数据庞大会导致下载缓慢哦,请您耐心等待!',
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
try {
|
||||
const res = await api(params);
|
||||
// 这个地方的type,经测试不传也没事,因为zip文件不知道type是什么
|
||||
// const blob = new Blob([res], {
|
||||
// type: "application/vnd.ms-excel;charset=UTF-8"
|
||||
// });
|
||||
const blob = new Blob([res]);
|
||||
// 兼容edge不支持createObjectURL方法
|
||||
if ('msSaveOrOpenBlob' in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType);
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
const exportFile = document.createElement('a');
|
||||
exportFile.style.display = 'none';
|
||||
exportFile.download = `${tempName}${fileType}`;
|
||||
exportFile.href = blobUrl;
|
||||
document.body.appendChild(exportFile);
|
||||
exportFile.click();
|
||||
// 去除下载对url的影响
|
||||
document.body.removeChild(exportFile);
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import { onUnmounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
/**
|
||||
* @description 使用Echarts(只是为了添加图表响应式)
|
||||
* @param {Element} myChart Echarts实例(必传)
|
||||
* @param {Object} options 绘制Echarts的参数(必传)
|
||||
* @return void
|
||||
* */
|
||||
export const useEcharts = (myChart: echarts.ECharts, options: echarts.EChartsCoreOption) => {
|
||||
if (options && typeof options === 'object') {
|
||||
myChart.setOption(options);
|
||||
}
|
||||
const echartsResize = () => {
|
||||
myChart && myChart.resize();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', echartsResize, false);
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', echartsResize);
|
||||
});
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
/**
|
||||
* @description 网络是否可用
|
||||
* */
|
||||
export const useOnline = () => {
|
||||
const online = ref(true);
|
||||
const showStatus = (val: any) => {
|
||||
online.value = typeof val == 'boolean' ? val : val.target.online;
|
||||
};
|
||||
// 在页面加载后,设置正确的网络状态
|
||||
navigator.onLine ? showStatus(true) : showStatus(false);
|
||||
|
||||
onMounted(() => {
|
||||
// 开始监听网络状态的变化
|
||||
window.addEventListener('online', showStatus);
|
||||
window.addEventListener('offline', showStatus);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听网络状态的变化
|
||||
window.removeEventListener('online', showStatus);
|
||||
window.removeEventListener('offline', showStatus);
|
||||
});
|
||||
|
||||
return { online };
|
||||
};
|
||||
@@ -35,6 +35,7 @@ export default {
|
||||
saveAndEnable: 'Save and enable',
|
||||
import: 'Import',
|
||||
search: 'Search',
|
||||
refresh: 'Refresh',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Time start',
|
||||
@@ -118,7 +119,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: 'Modern、OpenSource Linux server operation and maintenance management panel',
|
||||
title: 'Linux server operation and maintenance management panel',
|
||||
},
|
||||
rule: {
|
||||
username: 'Please enter a username',
|
||||
@@ -277,9 +278,8 @@ export default {
|
||||
source: 'Source',
|
||||
backup: 'Backup',
|
||||
permission: 'Permission',
|
||||
permissionLocal: 'Local server',
|
||||
permissionForIP: 'IP',
|
||||
permissionAll: 'All of them (unsafe)',
|
||||
permissionAll: 'All of them(%)',
|
||||
rootPassword: 'Root password',
|
||||
backupList: 'Backup',
|
||||
backList: 'Return',
|
||||
@@ -747,6 +747,9 @@ export default {
|
||||
type: 'Type',
|
||||
list: 'File List',
|
||||
sub: 'Include subdirectory',
|
||||
downlodSuccess: 'Download Success',
|
||||
theme: 'Theme',
|
||||
language: 'Language',
|
||||
},
|
||||
setting: {
|
||||
all: 'All',
|
||||
|
||||
@@ -36,6 +36,7 @@ export default {
|
||||
saveAndEnable: '保存并启用',
|
||||
import: '导入',
|
||||
search: '搜索',
|
||||
refresh: '刷新',
|
||||
},
|
||||
search: {
|
||||
timeStart: '开始时间',
|
||||
@@ -122,7 +123,7 @@ export default {
|
||||
warnning: '注意:【关闭安全入口】将使您的面板登录地址被直接暴露在互联网上,非常危险,请谨慎操作',
|
||||
codeInput: '请输入 MFA 验证器的 6 位验证码',
|
||||
mfaTitle: 'MFA认证',
|
||||
title: '现代化、开源的 Linux 服务器运维管理面板',
|
||||
title: 'Linux 服务器运维管理面板',
|
||||
},
|
||||
rule: {
|
||||
username: '请输入用户名',
|
||||
@@ -284,9 +285,8 @@ export default {
|
||||
source: '来源',
|
||||
backup: '备份',
|
||||
permission: '权限',
|
||||
permissionLocal: '本地服务器',
|
||||
permissionForIP: '指定 IP',
|
||||
permissionAll: '所有人(不安全)',
|
||||
permissionAll: '所有人(%)',
|
||||
rootPassword: 'root 密码',
|
||||
backupList: '备份列表',
|
||||
backList: '返回列表',
|
||||
@@ -754,6 +754,9 @@ export default {
|
||||
root: '根目录',
|
||||
list: '文件列表',
|
||||
sub: '包含子目录',
|
||||
downlodSuccess: '下载完成',
|
||||
theme: '主题',
|
||||
language: '语言',
|
||||
},
|
||||
setting: {
|
||||
all: '全部',
|
||||
|
||||
@@ -286,3 +286,4 @@
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ html.dark {
|
||||
.el-tag.el-tag--success {
|
||||
--el-tag-border-color: var(--el-color-success);
|
||||
}
|
||||
.el-tag.el-tag--danger {
|
||||
--el-tag-border-color: var(--el-color-danger);
|
||||
}
|
||||
.el-card {
|
||||
--el-card-bg-color: #111417;
|
||||
color: #ffffff;
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
@use './element-dark.scss';
|
||||
@use './reset.scss';
|
||||
@use './var.scss';
|
||||
@use 'md-editor-v3/lib/style.css';
|
||||
|
||||
@@ -5,6 +5,11 @@ export function formatImageStdout(stdout: string) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (isJson(lines[i])) {
|
||||
const data = JSON.parse(lines[i]);
|
||||
if (data.errorDetail || data.error) {
|
||||
lines[i] = data.errorDetail || data.errorDetail;
|
||||
lines[i] = data.error || data.error;
|
||||
continue;
|
||||
}
|
||||
if (data.stream) {
|
||||
lines[i] = data.stream;
|
||||
continue;
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { GetAppTags, SearchApp, SyncApp } from '@/api/modules/app';
|
||||
import { GetAppListUpdate, GetAppTags, SearchApp, SyncApp } from '@/api/modules/app';
|
||||
import i18n from '@/lang';
|
||||
import Detail from '../detail/index.vue';
|
||||
import router from '@/routers';
|
||||
@@ -147,6 +147,7 @@ const sync = () => {
|
||||
SyncApp()
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('app.syncSuccess'));
|
||||
canUpdate.value = false;
|
||||
search(req);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -154,6 +155,11 @@ const sync = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getAppListUpdate = async () => {
|
||||
const res = await GetAppListUpdate();
|
||||
canUpdate.value = res.data.canUpdate;
|
||||
};
|
||||
|
||||
const changeTag = (key: string) => {
|
||||
req.tags = [];
|
||||
activeTag.value = key;
|
||||
@@ -169,6 +175,7 @@ const searchByName = (name: string) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getAppListUpdate();
|
||||
search(req);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutContent :title="$t('app.detail')" :back-name="'App'" :v-loading="loadingDetail" :divider="true">
|
||||
<LayoutContent :title="$t('app.detail')" :back-name="'App'" :divider="true">
|
||||
<template #main>
|
||||
<div class="brief">
|
||||
<div class="brief" v-loading="loadingApp">
|
||||
<el-row :gutter="20">
|
||||
<div>
|
||||
<el-col :span="3">
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div>
|
||||
<div v-if="!loadingDetail">
|
||||
<el-alert
|
||||
style="width: 300px"
|
||||
v-if="!appDetail.enable"
|
||||
@@ -94,7 +94,6 @@
|
||||
import { GetApp, GetAppDetail } from '@/api/modules/app';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Install from './install/index.vue';
|
||||
@@ -112,26 +111,30 @@ let app = ref<any>({});
|
||||
let appDetail = ref<any>({});
|
||||
let version = ref('');
|
||||
let loadingDetail = ref(false);
|
||||
let loadingApp = ref(false);
|
||||
// let appKey = ref();
|
||||
const installRef = ref();
|
||||
|
||||
const getApp = () => {
|
||||
GetApp(props.appKey).then((res) => {
|
||||
const getApp = async () => {
|
||||
loadingApp.value = true;
|
||||
try {
|
||||
const res = await GetApp(props.appKey);
|
||||
app.value = res.data;
|
||||
version.value = app.value.versions[0];
|
||||
getDetail(app.value.id, version.value);
|
||||
});
|
||||
} finally {
|
||||
loadingApp.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getDetail = (id: number, version: string) => {
|
||||
const getDetail = async (id: number, version: string) => {
|
||||
loadingDetail.value = true;
|
||||
GetAppDetail(id, version)
|
||||
.then((res) => {
|
||||
appDetail.value = res.data;
|
||||
})
|
||||
.finally(() => {
|
||||
loadingDetail.value = false;
|
||||
});
|
||||
try {
|
||||
const res = await GetAppDetail(id, version);
|
||||
appDetail.value = res.data;
|
||||
} finally {
|
||||
loadingDetail.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const toLink = (link: string) => {
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
:disabled="p.disabled"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-model="form[p.envKey]"
|
||||
v-model.number="form[p.envKey]"
|
||||
@blur="form[p.envKey] = Number(form[p.envKey])"
|
||||
v-if="p.type == 'number'"
|
||||
:type="p.type"
|
||||
maxlength="15"
|
||||
@change="updateParam"
|
||||
:disabled="p.disabled"
|
||||
></el-input>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-col :span="20" :offset="2" v-if="open">
|
||||
<br />
|
||||
<el-descriptions border :column="1">
|
||||
<el-descriptions-item v-for="(item, key) in map" :key="key">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<LayoutContent v-loading="loading" :title="activeName" :divider="true">
|
||||
<LayoutContent v-loading="loading || syncLoading" :title="activeName" :divider="true">
|
||||
<template #toolbar>
|
||||
<el-row :gutter="5">
|
||||
<el-col :span="20">
|
||||
@@ -203,6 +203,7 @@ import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let data = ref<any>();
|
||||
let loading = ref(false);
|
||||
let syncLoading = ref(false);
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
@@ -235,14 +236,14 @@ let activeName = ref(i18n.global.t('app.installed'));
|
||||
let mode = ref('installed');
|
||||
|
||||
const sync = () => {
|
||||
loading.value = true;
|
||||
syncLoading.value = true;
|
||||
SyncInstalledApp()
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('app.syncSuccess'));
|
||||
search();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
syncLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.from === 'edit'" prop="name">
|
||||
<el-form-item v-if="form.from === 'edit' || form.from === 'template'" prop="name">
|
||||
<el-input @input="changePath" v-model.trim="form.name">
|
||||
<template #prepend>{{ $t('file.dir') }}</template>
|
||||
</el-input>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<el-form-item :label="$t('container.cpuQuota')" prop="nanoCPUs">
|
||||
<el-input type="number" style="width: 40%" v-model.number="form.nanoCPUs">
|
||||
<template #append>
|
||||
<el-select v-model="form.cpuUnit" disabled style="width: 65px">
|
||||
<el-select v-model="form.cpuUnit" disabled style="width: 85px">
|
||||
<el-option label="Core" value="Core" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -94,7 +94,7 @@
|
||||
<el-form-item :label="$t('container.memoryLimit')" prop="memoryItem">
|
||||
<el-input style="width: 40%" v-model.number="form.memoryItem">
|
||||
<template #append>
|
||||
<el-select v-model="form.memoryUnit" placeholder="Select" style="width: 65px">
|
||||
<el-select v-model="form.memoryUnit" placeholder="Select" style="width: 85px">
|
||||
<el-option label="KB" value="KB" />
|
||||
<el-option label="MB" value="MB" />
|
||||
<el-option label="GB" value="GB" />
|
||||
|
||||
@@ -61,11 +61,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { ContainerStats } from '@/api/modules/container';
|
||||
import { dateFormatForSecond } from '@/utils/util';
|
||||
import VCharts from '@/components/v-charts/index.vue';
|
||||
// import * as echarts from 'echarts';
|
||||
import i18n from '@/lang';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
|
||||
@@ -223,6 +222,10 @@ const handleClose = async () => {
|
||||
chartsOption.value = { cpuChart: null, memoryChart: null, ioChart: null, networkChart: null };
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
handleClose;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<el-form ref="newNameRef" v-loading="loading" :model="renameForm" label-position="top">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('container.newName')" :rules="Rules.requiredInput" prop="newName">
|
||||
<el-form-item :label="$t('container.newName')" :rules="Rules.volumeName" prop="newName">
|
||||
<el-input v-model="renameForm.newName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -117,7 +117,7 @@ const form = reactive({
|
||||
tags: [] as Array<string>,
|
||||
});
|
||||
const varifyPath = (rule: any, value: any, callback: any) => {
|
||||
if (value.indexOf('docker-compose.yml') === -1) {
|
||||
if (value.indexOf('Dockerfile') === -1) {
|
||||
callback(new Error(i18n.global.t('commons.rule.selectHelper', ['Dockerfile'])));
|
||||
}
|
||||
callback();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.imageName')" :rules="Rules.requiredInput" prop="targetName">
|
||||
<el-form-item :label="$t('container.imageName')" :rules="Rules.imageName" prop="targetName">
|
||||
<el-input v-model="form.targetName">
|
||||
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||
</el-input>
|
||||
|
||||
@@ -83,6 +83,15 @@ const form = reactive({
|
||||
});
|
||||
|
||||
const acceptParams = (): void => {
|
||||
form.name = '';
|
||||
form.labelStr = '';
|
||||
form.labels = [];
|
||||
form.optionStr = '';
|
||||
form.options = [];
|
||||
form.driver = '';
|
||||
form.subnet = '';
|
||||
form.gateway = '';
|
||||
form.scope = '';
|
||||
drawerVisiable.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
@@ -92,7 +101,7 @@ const handleClose = () => {
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.imageName],
|
||||
name: [Rules.requiredInput],
|
||||
driver: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
:data="data"
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="selectable" fix />
|
||||
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="60" />
|
||||
<el-table-column
|
||||
:label="$t('container.downloadUrl')"
|
||||
@@ -129,11 +128,6 @@ const search = async () => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
function selectable(row) {
|
||||
return !(row.name === 'Docker Hub');
|
||||
}
|
||||
|
||||
const dialogRef = ref();
|
||||
const onOpenDialog = async (
|
||||
title: string,
|
||||
|
||||
@@ -219,6 +219,7 @@ const submitStop = async () => {
|
||||
await dockerOperate(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
stopVisiable.value = false;
|
||||
search();
|
||||
changeMode();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
{{ $t('cronjob.handle') }}
|
||||
</el-tag>
|
||||
<span class="buttons">
|
||||
<el-button type="primary" @click="onRefresh" link>
|
||||
{{ $t('commons.button.refresh') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onHandle(dialogData.rowData)" link>
|
||||
{{ $t('commons.button.handle') }}
|
||||
</el-button>
|
||||
@@ -134,7 +138,7 @@
|
||||
</template>
|
||||
<span class="status-count">{{ dialogData.rowData!.targetDir }}</span>
|
||||
<el-button
|
||||
v-if="currentRecord?.status! === 'Success'"
|
||||
v-if="currentRecord?.status === 'Success'"
|
||||
type="primary"
|
||||
style="margin-left: 10px"
|
||||
link
|
||||
@@ -399,6 +403,7 @@ const onHandle = async (row: Cronjob.CronjobInfo) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
searchInfo.pageSize = searchInfo.pageSize * searchInfo.page;
|
||||
searchInfo.page = 1;
|
||||
records.value = [];
|
||||
search();
|
||||
@@ -450,6 +455,14 @@ const search = async () => {
|
||||
loadRecord(currentRecord.value);
|
||||
searchInfo.recordTotal = res.data.total;
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
records.value = [];
|
||||
searchInfo.pageSize = searchInfo.pageSize * searchInfo.page;
|
||||
searchInfo.page = 1;
|
||||
search();
|
||||
};
|
||||
|
||||
const onDownload = async (record: any, backupID: number) => {
|
||||
if (!record.file || record.file.indexOf('/') === -1) {
|
||||
MsgError(i18n.global.t('cronjob.errPath', [record.file]));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<el-drawer v-model="createVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.create')" :back="handleClose" />
|
||||
</template>
|
||||
<el-drawer v-model="createVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('database.create')" :back="handleClose" />
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<el-form ref="formRef" label-position="top" :model="form" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
<el-form-item :label="$t('database.permission')" prop="permission">
|
||||
<el-select v-model="form.permission">
|
||||
<el-option value="localhost" :label="$t('database.permissionLocal')" />
|
||||
<el-option value="%" :label="$t('database.permissionAll')" />
|
||||
<el-option value="ip" :label="$t('database.permissionForIP')" />
|
||||
</el-select>
|
||||
@@ -42,16 +41,19 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="createVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="createVisiable = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -94,7 +96,7 @@ const acceptParams = (params: DialogProps): void => {
|
||||
form.format = 'utf8mb4';
|
||||
form.username = '';
|
||||
form.password = '';
|
||||
form.permission = 'localhost';
|
||||
form.permission = '%';
|
||||
form.permissionIPs = '';
|
||||
form.description = '';
|
||||
createVisiable.value = true;
|
||||
|
||||
@@ -331,7 +331,7 @@ const buttons = [
|
||||
privilegeIPs: '',
|
||||
password: '',
|
||||
};
|
||||
if (row.permission === '%' || row.permission === 'localhost') {
|
||||
if (row.permission === '%') {
|
||||
param.privilege = row.permission;
|
||||
} else {
|
||||
param.privilegeIPs = row.permission;
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<div v-if="changeForm.operation === 'privilege'">
|
||||
<el-form-item :label="$t('database.permission')" prop="privilege">
|
||||
<el-select style="width: 100%" v-model="changeForm.privilege">
|
||||
<el-option value="localhost" :label="$t('database.permissionLocal')" />
|
||||
<el-option value="%" :label="$t('database.permissionAll')" />
|
||||
<el-option value="ip" :label="$t('database.permissionForIP')" />
|
||||
</el-select>
|
||||
|
||||
@@ -199,7 +199,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount, ref, reactive } from 'vue';
|
||||
// import * as echarts from 'echarts';
|
||||
import Status from '@/views/home/status/index.vue';
|
||||
import App from '@/views/home/app/index.vue';
|
||||
import VCharts from '@/components/v-charts/index.vue';
|
||||
@@ -211,8 +210,6 @@ import { useRouter } from 'vue-router';
|
||||
import RouterButton from '@/components/router-button/index.vue';
|
||||
import { loadBaseInfo, loadCurrentInfo } from '@/api/modules/dashboard';
|
||||
import { getIOOptions, getNetworkOptions } from '@/api/modules/monitor';
|
||||
// import { GlobalStore } from '@/store';
|
||||
// const globalStore = GlobalStore();
|
||||
const router = useRouter();
|
||||
|
||||
const statuRef = ref();
|
||||
@@ -451,7 +448,6 @@ function loadUpTime(uptime: number) {
|
||||
const loadData = async () => {
|
||||
if (chartOption.value === 'io') {
|
||||
chartsOption.value['ioChart'] = {
|
||||
// title: i18n.global.t('home.io'),
|
||||
xDatas: timeIODatas.value,
|
||||
yDatas: [
|
||||
{
|
||||
@@ -465,54 +461,8 @@ const loadData = async () => {
|
||||
],
|
||||
formatStr: 'MB',
|
||||
};
|
||||
|
||||
// let ioReadYDatas = {
|
||||
// name: i18n.global.t('monitor.read'),
|
||||
// type: 'line',
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// offset: 0,
|
||||
// color: 'rgba(27, 143, 60, 1)',
|
||||
// },
|
||||
// {
|
||||
// offset: 1,
|
||||
// color: 'rgba(27, 143, 60, 0)',
|
||||
// },
|
||||
// ]),
|
||||
// },
|
||||
// data: ioReadBytes.value,
|
||||
// showSymbol: false,
|
||||
// };
|
||||
// let ioWriteYDatas = {
|
||||
// name: i18n.global.t('monitor.write'),
|
||||
// type: 'line',
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// offset: 0,
|
||||
// color: 'rgba(0, 94, 235, 1)',
|
||||
// },
|
||||
// {
|
||||
// offset: 1,
|
||||
// color: 'rgba(0, 94, 235, 0)',
|
||||
// },
|
||||
// ]),
|
||||
// },
|
||||
// data: ioWriteBytes.value,
|
||||
// showSymbol: false,
|
||||
// };
|
||||
// freshChart(
|
||||
// 'ioChart',
|
||||
// [i18n.global.t('monitor.read'), i18n.global.t('monitor.write')],
|
||||
// timeIODatas.value,
|
||||
// [ioReadYDatas, ioWriteYDatas],
|
||||
// i18n.global.t('home.io'),
|
||||
// 'MB',
|
||||
// );
|
||||
} else {
|
||||
chartsOption.value['networkChart'] = {
|
||||
// title: i18n.global.t('home.network'),
|
||||
xDatas: timeNetDatas.value,
|
||||
yDatas: [
|
||||
{
|
||||
@@ -526,116 +476,9 @@ const loadData = async () => {
|
||||
],
|
||||
formatStr: 'KB/s',
|
||||
};
|
||||
// let netTxYDatas = {
|
||||
// name: i18n.global.t('monitor.up'),
|
||||
// type: 'line',
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// offset: 0,
|
||||
// color: 'rgba(0, 94, 235, .5)',
|
||||
// },
|
||||
// {
|
||||
// offset: 1,
|
||||
// color: 'rgba(0, 94, 235, 0)',
|
||||
// },
|
||||
// ]),
|
||||
// },
|
||||
// data: netBytesRecvs.value,
|
||||
// showSymbol: false,
|
||||
// };
|
||||
// let netRxYDatas = {
|
||||
// name: i18n.global.t('monitor.down'),
|
||||
// type: 'line',
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// offset: 0,
|
||||
// color: 'rgba(27, 143, 60, .5)',
|
||||
// },
|
||||
// {
|
||||
// offset: 1,
|
||||
// color: 'rgba(27, 143, 60, 0)',
|
||||
// },
|
||||
// ]),
|
||||
// },
|
||||
// data: netBytesSents.value,
|
||||
// showSymbol: false,
|
||||
// };
|
||||
// freshChart(
|
||||
// 'networkChart',
|
||||
// [i18n.global.t('monitor.up'), i18n.global.t('monitor.down')],
|
||||
// timeNetDatas.value,
|
||||
// [netTxYDatas, netRxYDatas],
|
||||
// i18n.global.t('home.network'),
|
||||
// 'KB/s',
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
// function freshChart(chartName: string, legendDatas: any, xDatas: any, yDatas: any, yTitle: string, formatStr: string) {
|
||||
// if (isInit.value) {
|
||||
// echarts.init(document.getElementById(chartName) as HTMLElement);
|
||||
// isInit.value = false;
|
||||
// }
|
||||
// let itemChart = echarts.getInstanceByDom(document.getElementById(chartName) as HTMLElement);
|
||||
// const theme = globalStore.$state.themeConfig.theme || 'light';
|
||||
// const option = {
|
||||
// title: [
|
||||
// {
|
||||
// left: 'center',
|
||||
// text: yTitle,
|
||||
// show: false,
|
||||
// },
|
||||
// ],
|
||||
// zlevel: 1,
|
||||
// z: 1,
|
||||
// tooltip: {
|
||||
// trigger: 'axis',
|
||||
// formatter: function (datas: any) {
|
||||
// let res = datas[0].name + '<br/>';
|
||||
// for (const item of datas) {
|
||||
// res += item.marker + ' ' + item.seriesName + ':' + item.data + formatStr + '<br/>';
|
||||
// }
|
||||
// return res;
|
||||
// },
|
||||
// },
|
||||
// grid: { left: '7%', right: '7%', bottom: '20%' },
|
||||
// legend: {
|
||||
// data: legendDatas,
|
||||
// right: 10,
|
||||
// itemWidth: 8,
|
||||
// textStyle: {
|
||||
// color: '#646A73',
|
||||
// },
|
||||
// icon: 'circle',
|
||||
// },
|
||||
// xAxis: { data: xDatas, boundaryGap: false },
|
||||
// yAxis: {
|
||||
// name: '( ' + formatStr + ' )',
|
||||
// splitLine: {
|
||||
// //分隔辅助线
|
||||
// lineStyle: {
|
||||
// type: 'dashed', //线的类型 虚线0
|
||||
// opacity: theme === 'dark' ? 0.1 : 1, //透明度
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// series: yDatas,
|
||||
// dataZoom: [{ startValue: xDatas[0] }],
|
||||
// };
|
||||
// itemChart?.setOption(option, true);
|
||||
// }
|
||||
|
||||
// function changeChartSize() {
|
||||
// if (document.getElementById('ioChart') != null) {
|
||||
// echarts.getInstanceByDom(document.getElementById('ioChart') as HTMLElement)?.resize();
|
||||
// }
|
||||
// if (document.getElementById('networkChart') != null) {
|
||||
// echarts.getInstanceByDom(document.getElementById('networkChart') as HTMLElement)?.resize();
|
||||
// }
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
onLoadNetworkOptions();
|
||||
onLoadIOOptions();
|
||||
@@ -645,7 +488,6 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
// window.removeEventListener('resize', changeChartSize);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,9 +6,20 @@
|
||||
destroy-on-close
|
||||
width="70%"
|
||||
@opened="onOpen"
|
||||
class="coder-dialog"
|
||||
>
|
||||
<div v-loading="loading">
|
||||
<el-form :inline="true" :model="config">
|
||||
<el-form-item :label="$t('file.theme')">
|
||||
<el-select v-model="config.theme" @change="initEditor()">
|
||||
<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-option v-for="lang in Languages" :key="lang.label" :value="lang.value" :label="lang.label" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="coder-editor" v-loading="loading">
|
||||
<div id="codeBox" style="height: 60vh"></div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -25,7 +36,8 @@ import { SaveFileContent } from '@/api/modules/files';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { ref } from 'vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Languages } from '@/global/mimetype';
|
||||
|
||||
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
|
||||
|
||||
@@ -36,9 +48,33 @@ interface EditProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface EditorConfig {
|
||||
theme: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
let open = ref(false);
|
||||
let loading = ref(false);
|
||||
let language = ref('json');
|
||||
|
||||
let config = reactive<EditorConfig>({
|
||||
theme: 'vs-dark',
|
||||
language: 'plaintext',
|
||||
});
|
||||
|
||||
const themes = [
|
||||
{
|
||||
label: 'Visual Studio',
|
||||
value: 'vs',
|
||||
},
|
||||
{
|
||||
label: 'Visual Studio Dark',
|
||||
value: 'vs-dark',
|
||||
},
|
||||
{
|
||||
label: 'High Contrast Dark',
|
||||
value: 'hc-black',
|
||||
},
|
||||
];
|
||||
|
||||
let form = ref({
|
||||
content: '',
|
||||
@@ -61,13 +97,13 @@ const initEditor = () => {
|
||||
}
|
||||
const codeBox = document.getElementById('codeBox');
|
||||
editor = monaco.editor.create(codeBox as HTMLElement, {
|
||||
theme: 'vs-dark', //官方自带三种主题vs, hc-black, or vs-dark
|
||||
theme: config.theme,
|
||||
value: form.value.content,
|
||||
readOnly: false,
|
||||
automaticLayout: true,
|
||||
language: language.value,
|
||||
folding: true, //代码折叠
|
||||
roundedSelection: false, // 右侧不显示编辑器预览框
|
||||
language: config.language,
|
||||
folding: true,
|
||||
roundedSelection: false,
|
||||
});
|
||||
|
||||
editor.onDidChangeModelContent(() => {
|
||||
@@ -95,6 +131,7 @@ const saveContent = (closePage: boolean) => {
|
||||
const acceptParams = (props: EditProps) => {
|
||||
form.value.content = props.content;
|
||||
form.value.path = props.path;
|
||||
config.language = props.language;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
@@ -106,7 +143,7 @@ defineExpose({ acceptParams });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.coder-dialog {
|
||||
--el-dialog-margin-top: 10vh;
|
||||
.coder-editor {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -165,7 +165,7 @@ import CodeEditor from './code-editor/index.vue';
|
||||
import Wget from './wget/index.vue';
|
||||
import Move from './move/index.vue';
|
||||
import Download from './download/index.vue';
|
||||
import { Mimetypes } from '@/global/mimetype';
|
||||
import { Mimetypes, Languages } from '@/global/mimetype';
|
||||
import Process from './process/index.vue';
|
||||
// import Detail from './detail/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -183,7 +183,7 @@ let selects = ref<any>([]);
|
||||
let req = reactive({
|
||||
path: '/',
|
||||
expand: true,
|
||||
showHidden: false,
|
||||
showHidden: true,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
search: '',
|
||||
@@ -196,7 +196,7 @@ let pathWidth = ref(0);
|
||||
const fileCreate = reactive({ path: '/', isDir: false, mode: 0o755 });
|
||||
const fileCompress = reactive({ files: [''], name: '', dst: '', operate: 'compress' });
|
||||
const fileDeCompress = reactive({ path: '', name: '', dst: '', mimeType: '' });
|
||||
const fileEdit = reactive({ content: '', path: '', name: '' });
|
||||
const fileEdit = reactive({ content: '', path: '', name: '', language: 'plaintext' });
|
||||
const codeReq = reactive({ path: '', expand: false, page: 1, pageSize: 100 });
|
||||
const fileUpload = reactive({ path: '' });
|
||||
const fileRename = reactive({ path: '', oldName: '' });
|
||||
@@ -382,12 +382,25 @@ const openDeCompress = (item: File.File) => {
|
||||
const openCodeEditor = (row: File.File) => {
|
||||
codeReq.path = row.path;
|
||||
codeReq.expand = true;
|
||||
GetFileContent(codeReq).then((res) => {
|
||||
fileEdit.content = res.data.content;
|
||||
fileEdit.path = res.data.path;
|
||||
fileEdit.name = res.data.name;
|
||||
codeEditorRef.value.acceptParams(fileEdit);
|
||||
});
|
||||
|
||||
if (row.extension != '') {
|
||||
Languages.forEach((language) => {
|
||||
const ext = row.extension.substring(1);
|
||||
if (language.value == ext) {
|
||||
fileEdit.language = language.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
GetFileContent(codeReq)
|
||||
.then((res) => {
|
||||
fileEdit.content = res.data.content;
|
||||
fileEdit.path = res.data.path;
|
||||
fileEdit.name = res.data.name;
|
||||
|
||||
codeEditorRef.value.acceptParams(fileEdit);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const openUpload = () => {
|
||||
@@ -441,6 +454,7 @@ const openDownload = () => {
|
||||
fileDownload.name = selects.value.length > 1 ? getRandomStr(6) : selects.value[0].name;
|
||||
downloadRef.value.acceptParams(fileDownload);
|
||||
} else {
|
||||
loading.value = true;
|
||||
fileDownload.name = selects.value[0].name;
|
||||
DownloadFile(fileDownload as File.FileDownload)
|
||||
.then((res) => {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
:title="$t('file.downloadProcess')"
|
||||
>
|
||||
<div v-for="(value, index) in res" :key="index">
|
||||
<span>{{ $t('file.downloading') }} {{ value['name'] }}</span>
|
||||
<span>{{ value['percent'] === 100 ? $t('file.downlodSuccess') : $t('file.downloading') }}</span>
|
||||
<MsgInfo :info="value['name']" width="250" />
|
||||
<el-progress v-if="value['total'] == 0" :percentage="100" :indeterminate="true" :duration="1" />
|
||||
<el-progress v-else :text-inside="true" :stroke-width="15" :percentage="value['percent']"></el-progress>
|
||||
<span>
|
||||
@@ -22,6 +23,7 @@
|
||||
import { FileKeys } from '@/api/modules/files';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { onBeforeUnmount, ref, toRefs } from 'vue';
|
||||
import MsgInfo from '@/components/msg-info/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
@@ -60,7 +62,7 @@ const onClose = () => {};
|
||||
|
||||
const initProcess = () => {
|
||||
let href = window.location.href;
|
||||
let protocol = href.split('//')[0] === 'http' ? 'ws' : 'wss';
|
||||
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
|
||||
let ipLocal = href.split('//')[1].split('/')[0];
|
||||
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/files/ws`);
|
||||
processSocket.onopen = onOpenProcess;
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
drag
|
||||
:auto-upload="false"
|
||||
ref="uploadRef"
|
||||
:multiple="true"
|
||||
:on-change="fileOnChange"
|
||||
v-loading="loading"
|
||||
:limit="1"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
@@ -38,17 +39,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
|
||||
import { UploadFileData } from '@/api/modules/files';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
|
||||
import { ChunkUploadFileData } from '@/api/modules/files';
|
||||
import i18n from '@/lang';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
interface UploadProps {
|
||||
interface UploadFileProps {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
|
||||
const loading = ref(false);
|
||||
let uploadPrecent = ref(0);
|
||||
let open = ref(false);
|
||||
@@ -67,10 +69,11 @@ const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
uploaderFiles.value = uploadFiles;
|
||||
};
|
||||
|
||||
// const onProcess = (e: any) => {
|
||||
// const { loaded, total } = e;
|
||||
// uploadPrecent.value = ((loaded / total) * 100) | 0;
|
||||
// };
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
uploadRef.value!.clearFiles();
|
||||
const file = files[0] as UploadRawFile;
|
||||
uploadRef.value!.handleStart(file);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
@@ -95,7 +98,7 @@ const submit = async () => {
|
||||
formData.append('chunkCount', chunkCount.toString());
|
||||
|
||||
try {
|
||||
await UploadFileData(formData, {
|
||||
await ChunkUploadFileData(formData, {
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const progress = Math.round(
|
||||
((uploadedChunkCount + progressEvent.loaded / progressEvent.total) * 100) / chunkCount,
|
||||
@@ -109,12 +112,13 @@ const submit = async () => {
|
||||
}
|
||||
if (uploadedChunkCount == chunkCount) {
|
||||
loading.value = false;
|
||||
uploadRef.value!.clearFiles();
|
||||
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const acceptParams = (props: UploadProps) => {
|
||||
const acceptParams = (props: UploadFileProps) => {
|
||||
path.value = props.path;
|
||||
open.value = true;
|
||||
uploadPrecent.value = 0;
|
||||
|
||||
@@ -118,10 +118,12 @@ const rules = reactive({
|
||||
const loadGroups = async () => {
|
||||
const res = await GetGroupList({ type: 'host' });
|
||||
groupList.value = res.data;
|
||||
for (const item of groupList.value) {
|
||||
if (item.isDefault) {
|
||||
dialogData.value.rowData.groupID = item.id;
|
||||
break;
|
||||
if (dialogData.value.title === 'create') {
|
||||
for (const item of groupList.value) {
|
||||
if (item.isDefault) {
|
||||
dialogData.value.rowData.groupID = item.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,6 +158,11 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="demo">
|
||||
<span v-if="isDemo">
|
||||
{{ $t('commons.login.username') }}:demo {{ $t('commons.login.password') }}:1panel
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,7 +172,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { ElForm } from 'element-plus';
|
||||
import { loginApi, getCaptcha, mfaLoginApi, checkIsFirst, initUser } from '@/api/modules/auth';
|
||||
import { loginApi, getCaptcha, mfaLoginApi, checkIsFirst, initUser, checkIsDemo } from '@/api/modules/auth';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { MenuStore } from '@/store/modules/menu';
|
||||
import i18n from '@/lang';
|
||||
@@ -180,6 +185,7 @@ const menuStore = MenuStore();
|
||||
const errAuthInfo = ref(false);
|
||||
const errCaptcha = ref(false);
|
||||
const errMfaInfo = ref(false);
|
||||
const isDemo = ref(false);
|
||||
|
||||
const isFirst = ref();
|
||||
|
||||
@@ -315,6 +321,11 @@ const checkStatus = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const checkIsSystemDemo = async () => {
|
||||
const res = await checkIsDemo();
|
||||
isDemo.value = res.data;
|
||||
};
|
||||
|
||||
function checkPassword(rule: any, value: any, callback: any) {
|
||||
if (registerForm.password !== registerForm.rePassword) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.rePassword')));
|
||||
@@ -325,6 +336,7 @@ function checkPassword(rule: any, value: any, callback: any) {
|
||||
onMounted(() => {
|
||||
document.title = globalStore.themeConfig.panelName;
|
||||
checkStatus();
|
||||
checkIsSystemDemo();
|
||||
document.onkeydown = (e: any) => {
|
||||
e = window.event || e;
|
||||
if (e.keyCode === 13) {
|
||||
@@ -351,15 +363,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.login-title {
|
||||
// margin-top: 50px;
|
||||
font-size: 30px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: #646a73;
|
||||
margin-bottom: 30px;
|
||||
// @media only screen and (max-width: 1280px) {
|
||||
// margin-top: 20px;
|
||||
// }
|
||||
}
|
||||
.no-border {
|
||||
:deep(.el-input__wrapper) {
|
||||
@@ -421,5 +429,12 @@ onMounted(() => {
|
||||
height: 45px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.demo {
|
||||
text-align: center;
|
||||
span {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -55,19 +55,10 @@ const getStatus = async () => {
|
||||
statusCode.value = 1;
|
||||
};
|
||||
|
||||
// watch(
|
||||
// () => screenWidth.value,
|
||||
// (newVal) => {
|
||||
// console.log()
|
||||
// },
|
||||
// );
|
||||
|
||||
onMounted(() => {
|
||||
getStatus();
|
||||
// 屏幕适配
|
||||
screenWidth.value = document.body.clientWidth;
|
||||
window.onresize = () => {
|
||||
//屏幕尺寸变化就重新赋值
|
||||
return (() => {
|
||||
screenWidth.value = document.body.clientWidth;
|
||||
})();
|
||||
@@ -118,8 +109,6 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.login-title {
|
||||
// margin-top: 10%;
|
||||
// margin-right: 15%;
|
||||
margin-left: 10%;
|
||||
span:first-child {
|
||||
color: $primary-color;
|
||||
@@ -138,11 +127,9 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
.login-container {
|
||||
// margin-left: 15%;
|
||||
margin-top: 40px;
|
||||
padding: 40px 0;
|
||||
width: 390px;
|
||||
// height: 422px;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(255, 255, 255, 0.55);
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -8,71 +8,39 @@
|
||||
</div>
|
||||
<h3>{{ $t('setting.description') }}</h3>
|
||||
<h3>
|
||||
{{ version }}
|
||||
<el-button v-if="version !== 'Waiting'" type="primary" link @click="onLoadUpgradeInfo">
|
||||
{{ $t('setting.upgradeCheck') }}
|
||||
</el-button>
|
||||
<el-tag v-else round style="margin-left: 10px">{{ $t('setting.upgrading') }}</el-tag>
|
||||
<SystemUpgrade />
|
||||
</h3>
|
||||
<div style="margin-top: 10px">
|
||||
<el-link @click="toDoc">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>{{ $t('setting.doc') }}</span>
|
||||
</el-link>
|
||||
<el-link @click="toGithub" style="margin-left: 15px">
|
||||
<svg-icon style="font-size: 7px; margin-bottom: 3px" iconName="p-huaban88"></svg-icon>
|
||||
<span style="line-height: 20px">{{ $t('setting.project') }}</span>
|
||||
<el-link @click="toGithub" class="system-link">
|
||||
<svg-icon iconName="p-huaban88"></svg-icon>
|
||||
<span>{{ $t('setting.project') }}</span>
|
||||
</el-link>
|
||||
<el-link @click="toIssue" style="margin-left: 15px">
|
||||
<svg-icon style="font-size: 7px; margin-bottom: 3px" iconName="p-bug"></svg-icon>
|
||||
<el-link @click="toIssue" class="system-link">
|
||||
<svg-icon iconName="p-bug"></svg-icon>
|
||||
<span>{{ $t('setting.issue') }}</span>
|
||||
</el-link>
|
||||
<el-link @click="toGithubStar" style="margin-left: 15px">
|
||||
<svg-icon style="font-size: 7px; margin-bottom: 3px" iconName="p-star"></svg-icon>
|
||||
<el-link @click="toGithubStar" class="system-link">
|
||||
<svg-icon iconName="p-star"></svg-icon>
|
||||
<span>{{ $t('setting.star') }}</span>
|
||||
</el-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<el-drawer :close-on-click-modal="false" :key="refresh" v-model="drawerVisiable" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('setting.upgrade')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item :label="$t('setting.newVersion')">
|
||||
<el-tag>{{ upgradeInfo.newVersion }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.upgradeNotes')">
|
||||
<MdEditor style="height: calc(100vh - 330px)" v-model="upgradeInfo.releaseNote" previewOnly />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onUpgrade">{{ $t('setting.upgradeNow') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { getSettingInfo, loadUpgradeInfo, upgrade } from '@/api/modules/setting';
|
||||
import { getSettingInfo, getSystemAvailable } from '@/api/modules/setting';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import SystemUpgrade from '@/components/system-upgrade/index.vue';
|
||||
|
||||
const version = ref();
|
||||
const upgradeInfo = ref();
|
||||
const drawerVisiable = ref();
|
||||
const refresh = ref();
|
||||
|
||||
const loading = ref();
|
||||
const search = async () => {
|
||||
const res = await getSettingInfo();
|
||||
@@ -92,47 +60,22 @@ const toGithubStar = () => {
|
||||
window.open('https://github.com/1Panel-dev/1Panel', '_blank');
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisiable.value = false;
|
||||
};
|
||||
|
||||
const onLoadUpgradeInfo = async () => {
|
||||
loading.value = true;
|
||||
await loadUpgradeInfo()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (!res.data) {
|
||||
MsgSuccess(i18n.global.t('setting.noUpgrade'));
|
||||
return;
|
||||
}
|
||||
upgradeInfo.value = res.data;
|
||||
drawerVisiable.value = true;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const onUpgrade = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('setting.upgradeHelper', i18n.global.t('setting.upgrade')), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(() => {
|
||||
loading.value = true;
|
||||
upgrade(upgradeInfo.value.newVersion)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
drawerVisiable.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
getSystemAvailable();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.system-link {
|
||||
margin-left: 15px;
|
||||
|
||||
.svg-icon {
|
||||
font-size: 7px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
span {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { cleanMonitors, getSettingInfo, updateSetting } from '@/api/modules/setting';
|
||||
import { cleanMonitors, getSettingInfo, getSystemAvailable, updateSetting } from '@/api/modules/setting';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
@@ -110,5 +110,6 @@ const onClean = async () => {
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
getSystemAvailable();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -137,7 +137,7 @@ import { ElForm, ElMessageBox } from 'element-plus';
|
||||
import { Setting } from '@/api/interface/setting';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { updateSetting, getMFA, bindMFA, getSettingInfo, updatePort } from '@/api/modules/setting';
|
||||
import { updateSetting, getMFA, bindMFA, getSettingInfo, updatePort, getSystemAvailable } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { dateFormatSimple } from '@/utils/util';
|
||||
@@ -326,5 +326,6 @@ function loadTimeOut() {
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
getSystemAvailable();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.perserver')" prop="perserver">
|
||||
<el-input v-model.number="form.perserver" min="1" max="65535" type="number"></el-input>
|
||||
<el-input v-model.number="form.perserver" maxlength="15"></el-input>
|
||||
<span class="input-help">{{ $t('website.perserverHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.perip')" prop="perip">
|
||||
<el-input v-model.number="form.perip" type="number"></el-input>
|
||||
<el-input v-model.number="form.perip" maxlength="15"></el-input>
|
||||
<span class="input-help">{{ $t('website.peripHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.rate')" prop="rate">
|
||||
<el-input v-model.number="form.rate" type="number"></el-input>
|
||||
<el-input v-model.number="form.rate" maxlength="15"></el-input>
|
||||
<span class="input-help">{{ $t('website.rateHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -103,10 +103,10 @@ const search = (scopeReq: Website.NginxScopeReq) => {
|
||||
for (const param of res.data.params) {
|
||||
if (param.name === 'limit_conn') {
|
||||
if (param.params[0] === 'perserver') {
|
||||
form.perserver = Number(param.params[1]);
|
||||
form.perserver = Number(param.params[1].match(/\d+/g));
|
||||
}
|
||||
if (param.params[0] === 'perip') {
|
||||
form.perip = Number(param.params[1]);
|
||||
form.perip = Number(param.params[1].match(/\d+/g));
|
||||
}
|
||||
}
|
||||
if (param.name === 'limit_rate') {
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<el-switch v-model="form.enable" @change="updateEnable"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item prop="cycle" :label="$t('website.cycle')">
|
||||
<el-input v-model="form.cycle" type="number">
|
||||
<el-input v-model.number="form.cycle" maxlength="15">
|
||||
<template #append>{{ $t('website.seconds') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="frequency" :label="$t('website.frequency')">
|
||||
<el-input v-model="form.frequency" type="number">
|
||||
<el-input v-model.number="form.frequency" maxlength="15">
|
||||
<template #append>{{ $t('website.count') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -75,14 +75,12 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button link :icon="Promotion" @click="openUrl(row)"></el-button>
|
||||
<span>
|
||||
<el-link type="primary" :underline="false" @click="openConfig(row.id)">
|
||||
<span style="margin-left: 10px">{{ row.primaryDomain }}</span>
|
||||
</el-link>
|
||||
</span>
|
||||
<el-link type="primary" :underline="false" @click="openConfig(row.id)">
|
||||
<span style="margin-left: 10px">{{ row.primaryDomain }}</span>
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.type')" fix prop="type">
|
||||
<el-table-column :label="$t('commons.table.type')" fix show-overflow-tooltip prop="type">
|
||||
<template #default="{ row }">
|
||||
{{ $t('website.' + row.type) }}
|
||||
<span v-if="row.type === 'deployment'">[{{ row.appName }}]</span>
|
||||
@@ -104,12 +102,11 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('website.remark')"
|
||||
show-overflow-tooltip
|
||||
fix
|
||||
prop="remark"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('website.remark')" fix prop="remark">
|
||||
<template #default="{ row }">
|
||||
<MsgInfo :info="row.remark" width="120" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.protocol')" prop="protocol"></el-table-column>
|
||||
<el-table-column :label="$t('website.expireDate')">
|
||||
<template #default="{ row, $index }">
|
||||
@@ -187,6 +184,7 @@ import { dateFormatSimple } from '@/utils/util';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Promotion, VideoPlay, VideoPause } from '@element-plus/icons-vue';
|
||||
import MsgInfo from '@/components/msg-info/index.vue';
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
@@ -414,4 +412,10 @@ onMounted(() => {
|
||||
float: right;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
white-space: pre-wrap; /* 自动换行 */
|
||||
word-break: break-all; /* 单词内换行 */
|
||||
max-width: 200px; /* 控制最大宽度 */
|
||||
}
|
||||
</style>
|
||||
|
||||
2
go.mod
2
go.mod
@@ -70,7 +70,7 @@ require (
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -313,8 +313,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
@@ -955,7 +955,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
|
||||
Reference in New Issue
Block a user