mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 16:00:33 +00:00
Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b00286e65 | ||
|
|
6597caaf05 | ||
|
|
5851124dee | ||
|
|
b18689a0d6 | ||
|
|
a5e7f78a1f | ||
|
|
a1d394fbc2 | ||
|
|
5e82bda666 | ||
|
|
3d5580ad9d | ||
|
|
c83cfbe8b0 | ||
|
|
00cc1b62ef | ||
|
|
e782f75863 | ||
|
|
f5f379465f | ||
|
|
70a8fda449 | ||
|
|
ca14302191 | ||
|
|
7297c2626f | ||
|
|
92a3982f21 | ||
|
|
626f5efdcd | ||
|
|
24e780b984 | ||
|
|
596d4aceb6 | ||
|
|
e770254b65 | ||
|
|
7901c26d32 | ||
|
|
fc65f8f689 | ||
|
|
e0a5e14978 | ||
|
|
07562c203a | ||
|
|
8187a19e6e | ||
|
|
32cd684307 | ||
|
|
3a72fc95d9 | ||
|
|
64405fba3d | ||
|
|
cc7981229c | ||
|
|
3ec208dc85 | ||
|
|
1b92b6cbfa | ||
|
|
779addfcb4 | ||
|
|
7da5f7a884 | ||
|
|
34e14636d4 | ||
|
|
aa222aac31 | ||
|
|
5134caa103 | ||
|
|
dca3c92d72 | ||
|
|
a85d5bad46 | ||
|
|
716613a4db | ||
|
|
12eaa66bc5 | ||
|
|
a6af97e3c1 | ||
|
|
d561c86c68 | ||
|
|
f243d1c9b4 | ||
|
|
1cc2cc6400 | ||
|
|
15f4c05772 | ||
|
|
c9a204b7a7 | ||
|
|
3d0018c0bd | ||
|
|
d639e46bc7 | ||
|
|
fa633bf419 | ||
|
|
40f7ef90b3 | ||
|
|
50efe607d4 | ||
|
|
3b057f2c27 | ||
|
|
d3d018bd44 | ||
|
|
1378609aa2 | ||
|
|
001c896eed | ||
|
|
1cf3a0143c | ||
|
|
f04be521e5 | ||
|
|
2873632a40 | ||
|
|
075879b5b5 | ||
|
|
9b12930376 | ||
|
|
3339ba9bad | ||
|
|
68922676fc | ||
|
|
dcf2afb4a6 | ||
|
|
16a35141dd | ||
|
|
f6c334bdad | ||
|
|
cbb119117b | ||
|
|
cae0a6118f |
+2
-3
@@ -29,6 +29,7 @@ build/1panel
|
|||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directories
|
||||||
/pkg/
|
/pkg/
|
||||||
backend/__debug_bin
|
backend/__debug_bin
|
||||||
@@ -40,9 +41,7 @@ frontend/auto-imports.d.ts
|
|||||||
frontend/components.d.ts
|
frontend/components.d.ts
|
||||||
frontend/src/xpack
|
frontend/src/xpack
|
||||||
backend/xpack
|
backend/xpack
|
||||||
backend/router/entry_xpack.go
|
*_xpack.go
|
||||||
backend/server/init_xpack.go
|
|
||||||
backend/utils/xpack/xpack_xpack.go
|
|
||||||
|
|
||||||
.history/
|
.history/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ archives:
|
|||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ incpatch .Version }}-next"
|
version_template: "{{ incpatch .Version }}-next"
|
||||||
release:
|
release:
|
||||||
draft: true
|
draft: true
|
||||||
mode: append
|
mode: append
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
|
|||||||
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
|
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
|
||||||
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
|
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
|
||||||
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
|
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
|
||||||
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源自动化测试平台
|
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源持续测试工具
|
||||||
|
- [KubePi](https://github.com/1Panel-dev/KubePi/) - 现代化、开源的 K8s 面板
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
|
|||||||
// @Success 200 {object} model.AppInstall
|
// @Success 200 {object} model.AppInstall
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /apps/install [post]
|
// @Router /apps/install [post]
|
||||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"name","input_value":"name","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"}
|
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"安装应用 [name]","formatEN":"Install app [name]"}
|
||||||
func (b *BaseApi) InstallApp(c *gin.Context) {
|
func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||||
var req request.AppInstallCreate
|
var req request.AppInstallCreate
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
req.StartTime = req.StartTime.In(loc)
|
req.StartTime = req.StartTime.In(loc)
|
||||||
req.EndTime = req.EndTime.In(loc)
|
req.EndTime = req.EndTime.In(loc)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
@@ -336,8 +337,12 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
mode := info.Mode()
|
mode := info.Mode()
|
||||||
|
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
|
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||||
|
uid, gid := -1, -1
|
||||||
|
if ok {
|
||||||
|
uid, gid = int(stat.Uid), int(stat.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
success := 0
|
success := 0
|
||||||
failures := make(buserr.MultiErr)
|
failures := make(buserr.MultiErr)
|
||||||
@@ -351,6 +356,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
|||||||
global.LOG.Error(e)
|
global.LOG.Error(e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
_ = os.Chown(dstDir, uid, gid)
|
||||||
}
|
}
|
||||||
tmpFilename := dstFilename + ".tmp"
|
tmpFilename := dstFilename + ".tmp"
|
||||||
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
|
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
|
||||||
@@ -378,6 +384,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
_ = os.Chmod(dstFilename, mode)
|
_ = os.Chmod(dstFilename, mode)
|
||||||
}
|
}
|
||||||
|
if uid != -1 && gid != -1 {
|
||||||
|
_ = os.Chown(dstFilename, uid, gid)
|
||||||
|
}
|
||||||
success++
|
success++
|
||||||
}
|
}
|
||||||
if success == 0 {
|
if success == 0 {
|
||||||
@@ -603,9 +612,17 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
|
|||||||
if mode == 0 {
|
if mode == 0 {
|
||||||
mode = 0755
|
mode = 0755
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
|
uid, gid := -1, -1
|
||||||
if err = op.CreateDir(dstDir, mode); err != nil {
|
if info, err := os.Stat(dstDir); err != nil {
|
||||||
return err
|
if os.IsNotExist(err) {
|
||||||
|
if err = op.CreateDir(dstDir, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||||
|
if ok {
|
||||||
|
uid, gid = int(stat.Uid), int(stat.Gid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dstFileName := filepath.Join(dstDir, fileName)
|
dstFileName := filepath.Join(dstDir, fileName)
|
||||||
@@ -615,6 +632,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
|
|||||||
} else {
|
} else {
|
||||||
mode = 0644
|
mode = 0644
|
||||||
}
|
}
|
||||||
|
|
||||||
if overwrite {
|
if overwrite {
|
||||||
_ = os.Remove(dstFileName)
|
_ = os.Remove(dstFileName)
|
||||||
}
|
}
|
||||||
@@ -635,6 +653,9 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
|
|||||||
}
|
}
|
||||||
_ = os.Remove(chunkPath)
|
_ = os.Remove(chunkPath)
|
||||||
}
|
}
|
||||||
|
if uid != -1 && gid != -1 {
|
||||||
|
_ = os.Chown(dstFileName, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
req.StartTime = req.StartTime.In(loc)
|
req.StartTime = req.StartTime.In(loc)
|
||||||
req.EndTime = req.EndTime.In(loc)
|
req.EndTime = req.EndTime.In(loc)
|
||||||
|
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ func (b *BaseApi) UpdateSnapDescription(c *gin.Context) {
|
|||||||
// @Summary Page system snapshot
|
// @Summary Page system snapshot
|
||||||
// @Description 获取系统快照列表分页
|
// @Description 获取系统快照列表分页
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.SearchWithPage true "request"
|
// @Param request body dto.PageSnapshot true "request"
|
||||||
// @Success 200 {object} dto.PageResult
|
// @Success 200 {object} dto.PageResult
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /settings/snapshot/search [post]
|
// @Router /settings/snapshot/search [post]
|
||||||
func (b *BaseApi) SearchSnapshot(c *gin.Context) {
|
func (b *BaseApi) SearchSnapshot(c *gin.Context) {
|
||||||
var req dto.SearchWithPage
|
var req dto.PageSnapshot
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type CommonBackup struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DetailName string `json:"detailName"`
|
DetailName string `json:"detailName"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
|
FileName string `json:"fileName"`
|
||||||
}
|
}
|
||||||
type CommonRecover struct {
|
type CommonRecover struct {
|
||||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
|
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ type DashboardBase struct {
|
|||||||
KernelArch string `json:"kernelArch"`
|
KernelArch string `json:"kernelArch"`
|
||||||
KernelVersion string `json:"kernelVersion"`
|
KernelVersion string `json:"kernelVersion"`
|
||||||
VirtualizationSystem string `json:"virtualizationSystem"`
|
VirtualizationSystem string `json:"virtualizationSystem"`
|
||||||
|
IpV4Addr string `json:"ipv4Addr"`
|
||||||
CPUCores int `json:"cpuCores"`
|
SystemProxy string `json:"SystemProxy"`
|
||||||
CPULogicalCores int `json:"cpuLogicalCores"`
|
CPUCores int `json:"cpuCores"`
|
||||||
CPUModelName string `json:"cpuModelName"`
|
CPULogicalCores int `json:"cpuLogicalCores"`
|
||||||
|
CPUModelName string `json:"cpuModelName"`
|
||||||
|
|
||||||
CurrentInfo DashboardCurrent `json:"currentInfo"`
|
CurrentInfo DashboardCurrent `json:"currentInfo"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ type FileWgetRes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FileLineContent struct {
|
type FileLineContent struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
End bool `json:"end"`
|
End bool `json:"end"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
|
Lines []string `json:"lines"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileExist struct {
|
type FileExist struct {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ type WebsiteRes struct {
|
|||||||
RuntimeName string `json:"runtimeName"`
|
RuntimeName string `json:"runtimeName"`
|
||||||
SSLExpireDate time.Time `json:"sslExpireDate"`
|
SSLExpireDate time.Time `json:"sslExpireDate"`
|
||||||
SSLStatus string `json:"sslStatus"`
|
SSLStatus string `json:"sslStatus"`
|
||||||
|
AppInstallID uint `json:"appInstallId"`
|
||||||
|
RuntimeType string `json:"runtimeType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsiteOption struct {
|
type WebsiteOption struct {
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ type PortUpdate struct {
|
|||||||
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
|
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PageSnapshot struct {
|
||||||
|
PageInfo
|
||||||
|
Info string `json:"info"`
|
||||||
|
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
|
||||||
|
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||||
|
}
|
||||||
type SnapshotStatus struct {
|
type SnapshotStatus struct {
|
||||||
Panel string `json:"panel"`
|
Panel string `json:"panel"`
|
||||||
PanelInfo string `json:"panelInfo"`
|
PanelInfo string `json:"panelInfo"`
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type IBackupRepo interface {
|
|||||||
WithByFileName(fileName string) DBOption
|
WithByFileName(fileName string) DBOption
|
||||||
WithByType(backupType string) DBOption
|
WithByType(backupType string) DBOption
|
||||||
WithByCronID(cronjobID uint) DBOption
|
WithByCronID(cronjobID uint) DBOption
|
||||||
|
WithFileNameStartWith(filePrefix string) DBOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIBackupRepo() IBackupRepo {
|
func NewIBackupRepo() IBackupRepo {
|
||||||
@@ -81,6 +82,12 @@ func (u *BackupRepo) WithByFileName(fileName string) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *BackupRepo) WithFileNameStartWith(filePrefix string) DBOption {
|
||||||
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
|
return g.Where("file_name LIKE ?", filePrefix+"%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (u *BackupRepo) WithByType(backupType string) DBOption {
|
func (u *BackupRepo) WithByType(backupType string) DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
if len(backupType) == 0 {
|
if len(backupType) == 0 {
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
|
|||||||
return db.Where("ca_id = ?", caID)
|
return db.Where("ca_id = ?", caID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
|
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
|
||||||
var sslList []model.WebsiteSSL
|
var sslList []model.WebsiteSSL
|
||||||
db := getDb(opts...).Model(&model.WebsiteSSL{})
|
db := getDb(opts...).Model(&model.WebsiteSSL{})
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
var appDTOs []*response.AppDto
|
var appDTOs []*response.AppDto
|
||||||
for _, ap := range apps {
|
for _, ap := range apps {
|
||||||
ap.ReadMe = ""
|
// ap.ReadMe = ""
|
||||||
ap.Website = ""
|
// ap.Website = ""
|
||||||
ap.Document = ""
|
// ap.Document = ""
|
||||||
ap.Github = ""
|
// ap.Github = ""
|
||||||
appDTO := &response.AppDto{
|
appDTO := &response.AppDto{
|
||||||
ID: ap.ID,
|
ID: ap.ID,
|
||||||
Name: ap.Name,
|
Name: ap.Name,
|
||||||
@@ -145,6 +145,9 @@ func (a AppService) GetAppTags() ([]response.TagDTO, error) {
|
|||||||
|
|
||||||
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||||
var appDTO response.AppDTO
|
var appDTO response.AppDTO
|
||||||
|
if key == "postgres" {
|
||||||
|
key = "postgresql"
|
||||||
|
}
|
||||||
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -185,7 +188,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
|
|||||||
|
|
||||||
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
|
||||||
if !fileOp.Stat(versionPath) || detail.Update {
|
if !fileOp.Stat(versionPath) || detail.Update {
|
||||||
if err = downloadApp(app, detail, nil); err != nil {
|
if err = downloadApp(app, detail, nil); err != nil && !fileOp.Stat(versionPath) {
|
||||||
return appDetailDTO, err
|
return appDetailDTO, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -535,6 +535,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, err
|
return versions, err
|
||||||
}
|
}
|
||||||
|
if app.Key == constant.AppMysql {
|
||||||
|
majorVersion := getMajorVersion(install.Version)
|
||||||
|
if !strings.HasPrefix(detail.Version, majorVersion) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
versions = append(versions, dto.AppVersion{
|
versions = append(versions, dto.AppVersion{
|
||||||
Version: detail.Version,
|
Version: detail.Version,
|
||||||
DetailId: detail.ID,
|
DetailId: detail.ID,
|
||||||
@@ -703,6 +709,22 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
appParam.Values = form.Values
|
appParam.Values = form.Values
|
||||||
|
} else if form.Type == "apps" {
|
||||||
|
if m, ok := form.Child.(map[string]interface{}); ok {
|
||||||
|
result := make(map[string]string)
|
||||||
|
for key, value := range m {
|
||||||
|
if strVal, ok := value.(string); ok {
|
||||||
|
result[key] = strVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if envKey, ok := result["envKey"]; ok {
|
||||||
|
serviceName := envs[envKey]
|
||||||
|
if serviceName != nil {
|
||||||
|
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string)))
|
||||||
|
appParam.ShowValue = appInstall.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
params = append(params, appParam)
|
params = append(params, appParam)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -186,17 +186,18 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
|||||||
}
|
}
|
||||||
case constant.AppRedis:
|
case constant.AppRedis:
|
||||||
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
||||||
if password != "" {
|
authParam := dto.RedisAuthParam{
|
||||||
authParam := dto.RedisAuthParam{
|
RootPassword: "",
|
||||||
RootPassword: password.(string),
|
|
||||||
}
|
|
||||||
authByte, err := json.Marshal(authParam)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appInstall.Param = string(authByte)
|
|
||||||
}
|
}
|
||||||
database.Password = password.(string)
|
if password != "" {
|
||||||
|
authParam.RootPassword = password.(string)
|
||||||
|
database.Password = password.(string)
|
||||||
|
}
|
||||||
|
authByte, err := json.Marshal(authParam)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
appInstall.Param = string(authByte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := databaseRepo.Create(ctx, database); err != nil {
|
if err := databaseRepo.Create(ctx, database); err != nil {
|
||||||
@@ -516,8 +517,19 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
)
|
)
|
||||||
global.LOG.Infof(i18n.GetMsgWithName("UpgradeAppStart", install.Name, nil))
|
global.LOG.Infof(i18n.GetMsgWithName("UpgradeAppStart", install.Name, nil))
|
||||||
if req.Backup {
|
if req.Backup {
|
||||||
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name})
|
iBackUpService := NewIBackupService()
|
||||||
|
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||||
|
backupRecord, err := iBackUpService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
backups, _ := iBackUpService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
|
||||||
|
if len(backups) > 3 {
|
||||||
|
backupsToDelete := backups[:len(backups)-3]
|
||||||
|
var deleteIDs []uint
|
||||||
|
for _, backup := range backupsToDelete {
|
||||||
|
deleteIDs = append(deleteIDs, backup.ID)
|
||||||
|
}
|
||||||
|
_ = iBackUpService.BatchDeleteRecord(deleteIDs)
|
||||||
|
}
|
||||||
localDir, err := loadLocalDir()
|
localDir, err := loadLocalDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
backupFile = path.Join(localDir, backupRecord.FileDir, backupRecord.FileName)
|
backupFile = path.Join(localDir, backupRecord.FileDir, backupRecord.FileName)
|
||||||
@@ -1224,6 +1236,31 @@ func synAppInstall(containers map[string]types.Container, appInstall *model.AppI
|
|||||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMajorVersion(version string) string {
|
||||||
|
parts := strings.Split(version, ".")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
return parts[0] + "." + parts[1]
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignoreUpdate(installed model.AppInstall) bool {
|
||||||
|
if installed.App.Type == "php" || installed.Status == constant.Installing {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if installed.App.Key == constant.AppMysql {
|
||||||
|
majorVersion := getMajorVersion(installed.Version)
|
||||||
|
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||||
|
for _, appDetail := range appDetails {
|
||||||
|
if strings.HasPrefix(appDetail.Version, majorVersion) && common.CompareVersion(appDetail.Version, installed.Version) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
|
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
|
||||||
var (
|
var (
|
||||||
res []response.AppInstallDTO
|
res []response.AppInstallDTO
|
||||||
@@ -1246,7 +1283,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, installed := range appInstallList {
|
for _, installed := range appInstallList {
|
||||||
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
|
if updated && ignoreUpdate(installed) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sync && !doNotNeedSync(installed) {
|
if sync && !doNotNeedSync(installed) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type IBackupService interface {
|
|||||||
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
||||||
BatchDeleteRecord(ids []uint) error
|
BatchDeleteRecord(ids []uint) error
|
||||||
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
||||||
|
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
|
||||||
|
|
||||||
ListFiles(req dto.BackupSearchFile) []string
|
ListFiles(req dto.BackupSearchFile) []string
|
||||||
|
|
||||||
@@ -100,6 +101,20 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
|
|||||||
return total, datas, err
|
return total, datas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
|
||||||
|
records, err := backupRepo.ListRecord(
|
||||||
|
commonRepo.WithOrderBy("created_at asc"),
|
||||||
|
commonRepo.WithByName(name),
|
||||||
|
commonRepo.WithByType("app"),
|
||||||
|
backupRepo.WithFileNameStartWith(fileName),
|
||||||
|
backupRepo.WithByDetailName(detailName),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return records, err
|
||||||
|
}
|
||||||
|
|
||||||
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
||||||
total, records, err := backupRepo.PageRecord(
|
total, records, err := backupRepo.PageRecord(
|
||||||
search.Page, search.PageSize,
|
search.Page, search.PageSize,
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
|||||||
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
|
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
|
||||||
backupDir := path.Join(localDir, itemDir)
|
backupDir := path.Join(localDir, itemDir)
|
||||||
|
|
||||||
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
fileName := req.FileName
|
||||||
|
if req.FileName == "" {
|
||||||
|
fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||||
|
}
|
||||||
if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil {
|
if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
|||||||
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
_ = StopAllCronJob(false)
|
||||||
}
|
}
|
||||||
if baseInfo.FreshIsActive {
|
if baseInfo.FreshIsActive {
|
||||||
version, err := cmd.Exec("freshclam --version")
|
version, err := cmd.Exec("freshclam --version")
|
||||||
@@ -139,7 +141,7 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
|
|||||||
item.LastHandleDate = "-"
|
item.LastHandleDate = "-"
|
||||||
datas = append(datas, item)
|
datas = append(datas, item)
|
||||||
}
|
}
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
for i := 0; i < len(datas); i++ {
|
for i := 0; i < len(datas); i++ {
|
||||||
logPaths := loadFileByName(datas[i].Name)
|
logPaths := loadFileByName(datas[i].Name)
|
||||||
sort.Slice(logPaths, func(i, j int) bool {
|
sort.Slice(logPaths, func(i, j int) bool {
|
||||||
@@ -268,7 +270,7 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
|
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||||
if cmd.Which("clamdscan") == false {
|
if cleaned := StopAllCronJob(true); cleaned {
|
||||||
return buserr.New("ErrClamdscanNotFound")
|
return buserr.New("ErrClamdscanNotFound")
|
||||||
}
|
}
|
||||||
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
|
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
|
||||||
@@ -321,7 +323,7 @@ func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
var filterFiles []string
|
var filterFiles []string
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
for _, item := range logPaths {
|
for _, item := range logPaths {
|
||||||
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
|
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -473,6 +475,29 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StopAllCronJob(withCheck bool) bool {
|
||||||
|
if withCheck {
|
||||||
|
isActive := false
|
||||||
|
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||||
|
if exist1 {
|
||||||
|
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||||
|
}
|
||||||
|
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||||
|
if exist2 {
|
||||||
|
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||||
|
}
|
||||||
|
if isActive {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clams, _ := clamRepo.List(commonRepo.WithByStatus(constant.StatusEnable))
|
||||||
|
for i := 0; i < len(clams); i++ {
|
||||||
|
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
|
||||||
|
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func loadFileByName(name string) []string {
|
func loadFileByName(name string) []string {
|
||||||
var logPaths []string
|
var logPaths []string
|
||||||
pathItem := path.Join(global.CONF.System.DataDir, resultDir, name)
|
pathItem := path.Join(global.CONF.System.DataDir, resultDir, name)
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.ExposedPorts, _ = loadContainerPortForInfo(req.Name, client)
|
||||||
networkSettings := oldContainer.NetworkSettings
|
networkSettings := oldContainer.NetworkSettings
|
||||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||||
@@ -465,19 +466,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
|||||||
for key, val := range oldContainer.Config.Labels {
|
for key, val := range oldContainer.Config.Labels {
|
||||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||||
}
|
}
|
||||||
for key, val := range oldContainer.HostConfig.PortBindings {
|
|
||||||
var itemPort dto.PortHelper
|
|
||||||
if !strings.Contains(string(key), "/") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
|
|
||||||
itemPort.Protocol = strings.Split(string(key), "/")[1]
|
|
||||||
for _, binds := range val {
|
|
||||||
itemPort.HostIP = binds.HostIP
|
|
||||||
itemPort.HostPort = binds.HostPort
|
|
||||||
data.ExposedPorts = append(data.ExposedPorts, itemPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.AutoRemove = oldContainer.HostConfig.AutoRemove
|
data.AutoRemove = oldContainer.HostConfig.AutoRemove
|
||||||
data.Privileged = oldContainer.HostConfig.Privileged
|
data.Privileged = oldContainer.HostConfig.Privileged
|
||||||
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
|
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
|
||||||
@@ -1201,7 +1190,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
|
|||||||
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
|
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
|
||||||
global.LOG.Errorf("restart after container update failed, err: %v", err)
|
global.LOG.Errorf("restart after container update failed, err: %v", err)
|
||||||
}
|
}
|
||||||
global.LOG.Errorf("recreate after container update successful")
|
global.LOG.Info("recreate after container update successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
|
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
|
||||||
@@ -1310,3 +1299,47 @@ func simplifyPort(ports []types.Port) []string {
|
|||||||
}
|
}
|
||||||
return datas
|
return datas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadContainerPortForInfo(id string, client *client.Client) ([]dto.PortHelper, error) {
|
||||||
|
var exposedPorts []dto.PortHelper
|
||||||
|
containers, err := client.ContainerList(context.Background(), container.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
samePortMap := make(map[string]dto.PortHelper)
|
||||||
|
for _, container := range containers {
|
||||||
|
if container.ID == id {
|
||||||
|
ports := loadContainerPort(container.Ports)
|
||||||
|
var itemPort dto.PortHelper
|
||||||
|
for _, item := range ports {
|
||||||
|
itemStr := strings.Split(item, "->")
|
||||||
|
if len(itemStr) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastIndex := strings.LastIndex(itemStr[0], ":")
|
||||||
|
if lastIndex == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemContainer := strings.Split(itemStr[1], "/")
|
||||||
|
if len(itemContainer) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemPort.HostIP = itemStr[0][0:lastIndex]
|
||||||
|
itemPort.HostPort = itemStr[0][lastIndex+1:]
|
||||||
|
itemPort.ContainerPort = itemContainer[0]
|
||||||
|
itemPort.Protocol = itemContainer[1]
|
||||||
|
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
||||||
|
if val, ok := samePortMap[keyItem]; ok {
|
||||||
|
val.HostIP = ""
|
||||||
|
samePortMap[keyItem] = val
|
||||||
|
} else {
|
||||||
|
samePortMap[keyItem] = itemPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, val := range samePortMap {
|
||||||
|
exposedPorts = append(exposedPorts, val)
|
||||||
|
}
|
||||||
|
return exposedPorts, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
|
|||||||
records = list.Volumes[start:end]
|
records = list.Volumes[start:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
for _, item := range records {
|
for _, item := range records {
|
||||||
tag := make([]string, 0)
|
tag := make([]string, 0)
|
||||||
for _, val := range item.Labels {
|
for _, val := range item.Labels {
|
||||||
|
|||||||
@@ -17,15 +17,20 @@ import (
|
|||||||
|
|
||||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
|
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
|
||||||
var apps []model.AppInstall
|
var apps []model.AppInstall
|
||||||
if cronjob.AppID == "all" {
|
if strings.Contains(cronjob.AppID, "all") {
|
||||||
apps, _ = appInstallRepo.ListBy()
|
apps, _ = appInstallRepo.ListBy()
|
||||||
} else {
|
} else {
|
||||||
itemID, _ := strconv.Atoi(cronjob.AppID)
|
appIds := strings.Split(cronjob.AppID, ",")
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
var idItems []uint
|
||||||
|
for i := 0; i < len(appIds); i++ {
|
||||||
|
itemID, _ := strconv.Atoi(appIds[i])
|
||||||
|
idItems = append(idItems, uint(itemID))
|
||||||
|
}
|
||||||
|
appItems, err := appInstallRepo.ListBy(commonRepo.WithIdsIn(idItems))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
apps = append(apps, app)
|
apps = appItems
|
||||||
}
|
}
|
||||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -232,7 +237,7 @@ type databaseHelper struct {
|
|||||||
|
|
||||||
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||||
var dbs []databaseHelper
|
var dbs []databaseHelper
|
||||||
if cronjob.DBName == "all" {
|
if strings.Contains(cronjob.DBName, "all") {
|
||||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||||
mysqlItems, _ := mysqlRepo.List()
|
mysqlItems, _ := mysqlRepo.List()
|
||||||
for _, mysql := range mysqlItems {
|
for _, mysql := range mysqlItems {
|
||||||
@@ -254,36 +259,42 @@ func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
|||||||
}
|
}
|
||||||
return dbs
|
return dbs
|
||||||
}
|
}
|
||||||
itemID, _ := strconv.Atoi(cronjob.DBName)
|
|
||||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
dbNames := strings.Split(cronjob.DBName, ",")
|
||||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
for _, name := range dbNames {
|
||||||
dbs = append(dbs, databaseHelper{
|
itemID, _ := strconv.Atoi(name)
|
||||||
DBType: cronjob.DBType,
|
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||||
Database: mysqlItem.MysqlName,
|
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||||
Name: mysqlItem.Name,
|
dbs = append(dbs, databaseHelper{
|
||||||
})
|
DBType: cronjob.DBType,
|
||||||
} else {
|
Database: mysqlItem.MysqlName,
|
||||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
Name: mysqlItem.Name,
|
||||||
dbs = append(dbs, databaseHelper{
|
})
|
||||||
DBType: cronjob.DBType,
|
} else {
|
||||||
Database: pgItem.PostgresqlName,
|
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||||
Name: pgItem.Name,
|
dbs = append(dbs, databaseHelper{
|
||||||
})
|
DBType: cronjob.DBType,
|
||||||
|
Database: pgItem.PostgresqlName,
|
||||||
|
Name: pgItem.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return dbs
|
return dbs
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||||
var weblist []model.Website
|
var weblist []model.Website
|
||||||
if cronjob.Website == "all" {
|
if strings.Contains(cronjob.Website, "all") {
|
||||||
weblist, _ = websiteRepo.List()
|
weblist, _ = websiteRepo.List()
|
||||||
return weblist
|
return weblist
|
||||||
}
|
}
|
||||||
itemID, _ := strconv.Atoi(cronjob.Website)
|
websites := strings.Split(cronjob.Website, ",")
|
||||||
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
var idItems []uint
|
||||||
if webItem.ID != 0 {
|
for i := 0; i < len(websites); i++ {
|
||||||
weblist = append(weblist, webItem)
|
itemID, _ := strconv.Atoi(websites[i])
|
||||||
|
idItems = append(idItems, uint(itemID))
|
||||||
}
|
}
|
||||||
|
weblist, _ = websiteRepo.List(commonRepo.WithIdsIn(idItems))
|
||||||
return weblist
|
return weblist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
pathUtils "path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -139,7 +140,6 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
|
|||||||
|
|
||||||
excludes := strings.Split(exclusionRules, ",")
|
excludes := strings.Split(exclusionRules, ",")
|
||||||
excludeRules := ""
|
excludeRules := ""
|
||||||
excludes = append(excludes, "*.sock")
|
|
||||||
for _, exclude := range excludes {
|
for _, exclude := range excludes {
|
||||||
if len(exclude) == 0 {
|
if len(exclude) == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -162,10 +162,14 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
|
|||||||
|
|
||||||
if len(secret) != 0 {
|
if len(secret) != 0 {
|
||||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
|
||||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||||
} else {
|
} else {
|
||||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
itemPrefix := pathUtils.Base(sourceDir)
|
||||||
|
if itemPrefix == "/" {
|
||||||
|
itemPrefix = ""
|
||||||
|
}
|
||||||
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|%s/|') -zcf %s %s %s", sourceDir, "%P\n", itemPrefix, targetDir+"/"+name, excludeRules, path)
|
||||||
global.LOG.Debug(commands)
|
global.LOG.Debug(commands)
|
||||||
}
|
}
|
||||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||||
@@ -239,7 +243,6 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
|||||||
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
|
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
|
||||||
}
|
}
|
||||||
msg := i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath})
|
msg := i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath})
|
||||||
global.LOG.Infof(msg)
|
|
||||||
msgs = append(msgs, msg)
|
msgs = append(msgs, msg)
|
||||||
}
|
}
|
||||||
u.removeExpiredLog(*cronjob)
|
u.removeExpiredLog(*cronjob)
|
||||||
@@ -326,7 +329,6 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, record model.BackupRecord) {
|
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, record model.BackupRecord) {
|
||||||
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
|
||||||
var opts []repo.DBOption
|
var opts []repo.DBOption
|
||||||
opts = append(opts, commonRepo.WithByFrom("cronjob"))
|
opts = append(opts, commonRepo.WithByFrom("cronjob"))
|
||||||
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
|
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
|
||||||
@@ -361,7 +363,6 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
|
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
|
||||||
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
|
||||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
|
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
|
||||||
if len(records) <= int(cronjob.RetainCopies) {
|
if len(records) <= int(cronjob.RetainCopies) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
network "net"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -94,11 +96,20 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
|
|||||||
baseInfo.KernelVersion = hostInfo.KernelVersion
|
baseInfo.KernelVersion = hostInfo.KernelVersion
|
||||||
ss, _ := json.Marshal(hostInfo)
|
ss, _ := json.Marshal(hostInfo)
|
||||||
baseInfo.VirtualizationSystem = string(ss)
|
baseInfo.VirtualizationSystem = string(ss)
|
||||||
|
baseInfo.IpV4Addr = GetOutboundIP()
|
||||||
|
httpProxy := os.Getenv("http_proxy")
|
||||||
|
if httpProxy == "" {
|
||||||
|
httpProxy = os.Getenv("HTTP_PROXY")
|
||||||
|
}
|
||||||
|
if httpProxy != "" {
|
||||||
|
baseInfo.SystemProxy = httpProxy
|
||||||
|
}
|
||||||
|
baseInfo.SystemProxy = "noProxy"
|
||||||
appInstall, err := appInstallRepo.ListBy()
|
appInstall, err := appInstallRepo.ListBy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseInfo.AppInstalledNumber = len(appInstall)
|
baseInfo.AppInstalledNumber = len(appInstall)
|
||||||
postgresqlDbs, err := postgresqlRepo.List()
|
postgresqlDbs, err := postgresqlRepo.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,6 +219,18 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
|||||||
return ¤tInfo
|
return ¤tInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOutboundIP() string {
|
||||||
|
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "IPNotFound"
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
localAddr := conn.LocalAddr().(*network.UDPAddr)
|
||||||
|
return localAddr.IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
type diskInfo struct {
|
type diskInfo struct {
|
||||||
Type string
|
Type string
|
||||||
Mount string
|
Mount string
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *MysqlService) BindUser(req dto.BindUser) error {
|
func (u *MysqlService) BindUser(req dto.BindUser) error {
|
||||||
|
if cmd.CheckIllegal(req.Username, req.Password, req.Permission) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
|
|
||||||
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
|
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -533,7 +537,10 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
|
|||||||
info.Position = "OFF"
|
info.Position = "OFF"
|
||||||
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "show master status;")
|
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "show master status;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
rows, err = executeSqlForRows(app.ContainerName, app.Key, app.Password, "SHOW BINARY LOG STATUS;")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(rows) > 2 {
|
if len(rows) > 2 {
|
||||||
itemValue := strings.Split(rows[1], "\t")
|
itemValue := strings.Split(rows[1], "\t")
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
|||||||
}
|
}
|
||||||
if len(daemonMap) == 0 {
|
if len(daemonMap) == 0 {
|
||||||
_ = os.Remove(constant.DaemonJsonPath)
|
_ = os.Remove(constant.DaemonJsonPath)
|
||||||
|
stdout, err := cmd.Exec("systemctl restart docker")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(string(stdout))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
|
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
|
||||||
@@ -304,6 +308,10 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
|
|||||||
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||||
if len(req.File) == 0 {
|
if len(req.File) == 0 {
|
||||||
_ = os.Remove(constant.DaemonJsonPath)
|
_ = os.Remove(constant.DaemonJsonPath)
|
||||||
|
stdout, err := cmd.Exec("systemctl restart docker")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(string(stdout))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := createIfNotExistDaemonJsonFile()
|
err := createIfNotExistDaemonJsonFile()
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func shouldFilterPath(path string) bool {
|
|||||||
func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error {
|
func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error {
|
||||||
for _, v := range items {
|
for _, v := range items {
|
||||||
if shouldFilterPath(v.Path) {
|
if shouldFilterPath(v.Path) {
|
||||||
global.LOG.Info("File Tree: Skipping %s due to filter\n", v.Path)
|
global.LOG.Infof("File Tree: Skipping %s due to filter\n", v.Path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
childNode := response.FileTree{
|
childNode := response.FileTree{
|
||||||
@@ -167,7 +167,7 @@ func (f *FileService) buildChildNode(childNode *response.FileTree, fileInfo *fil
|
|||||||
subInfo, err := files.NewFileInfo(op.FileOption)
|
subInfo, err := files.NewFileInfo(op.FileOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsPermission(err) || errors.Is(err, unix.EACCES) {
|
if os.IsPermission(err) || errors.Is(err, unix.EACCES) {
|
||||||
global.LOG.Info("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
|
global.LOG.Infof("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error())
|
global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error())
|
||||||
@@ -474,11 +474,20 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
|
||||||
|
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lines = append(preLines, lines...)
|
||||||
|
}
|
||||||
|
|
||||||
res := &response.FileLineContent{
|
res := &response.FileLineContent{
|
||||||
Content: strings.Join(lines, "\n"),
|
Content: strings.Join(lines, "\n"),
|
||||||
End: isEndOfFile,
|
End: isEndOfFile,
|
||||||
Path: logFilePath,
|
Path: logFilePath,
|
||||||
Total: total,
|
Total: total,
|
||||||
|
Lines: lines,
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -588,14 +588,6 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
|
|||||||
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
apps := u.loadPortByApp()
|
|
||||||
for _, app := range apps {
|
|
||||||
if len(app.HttpPort) != 0 && app.HttpPort != "0" {
|
|
||||||
if err := client.Port(fireClient.FireInfo{Port: app.HttpPort, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.Reload()
|
return client.Reload()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
authStr := base64.StdEncoding.EncodeToString(encodedJSON)
|
||||||
options.RegistryAuth = authStr
|
options.RegistryAuth = authStr
|
||||||
}
|
}
|
||||||
image := repo.DownloadUrl + "/" + req.ImageName
|
image := repo.DownloadUrl + "/" + req.ImageName
|
||||||
@@ -295,7 +295,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
|||||||
out, err := client.ImagePull(context.TODO(), image, options)
|
out, err := client.ImagePull(context.TODO(), image, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = file.WriteString("image pull failed!")
|
_, _ = file.WriteString("image pull failed!")
|
||||||
global.LOG.Errorf("image %s pull failed, err: %v", image, err)
|
_, _ = file.WriteString(fmt.Sprintf("image %s pull failed, err: %v", image, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type SnapshotService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ISnapshotService interface {
|
type ISnapshotService interface {
|
||||||
SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error)
|
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
|
||||||
SnapshotCreate(req dto.SnapshotCreate) error
|
SnapshotCreate(req dto.SnapshotCreate) error
|
||||||
SnapshotRecover(req dto.SnapshotRecover) error
|
SnapshotRecover(req dto.SnapshotRecover) error
|
||||||
SnapshotRollback(req dto.SnapshotRecover) error
|
SnapshotRollback(req dto.SnapshotRecover) error
|
||||||
@@ -46,8 +46,11 @@ func NewISnapshotService() ISnapshotService {
|
|||||||
return &SnapshotService{}
|
return &SnapshotService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) {
|
||||||
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
|
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
dtoSnap, err := loadSnapSize(systemBackups)
|
dtoSnap, err := loadSnapSize(systemBackups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
|||||||
exMap := make(map[string]struct{})
|
exMap := make(map[string]struct{})
|
||||||
exStr := ""
|
exStr := ""
|
||||||
excludes := strings.Split(exclusionRules, ";")
|
excludes := strings.Split(exclusionRules, ";")
|
||||||
excludes = append(excludes, "*.sock")
|
|
||||||
for _, exclude := range excludes {
|
for _, exclude := range excludes {
|
||||||
if len(exclude) == 0 {
|
if len(exclude) == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -257,10 +256,10 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
|||||||
commands := ""
|
commands := ""
|
||||||
if len(secret) != 0 {
|
if len(secret) != 0 {
|
||||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
||||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||||
} else {
|
} else {
|
||||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
|
||||||
global.LOG.Debug(commands)
|
global.LOG.Debug(commands)
|
||||||
}
|
}
|
||||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
|||||||
req.IsNew = true
|
req.IsNew = true
|
||||||
}
|
}
|
||||||
if req.IsNew || snap.InterruptStep == "Backup" {
|
if req.IsNew || snap.InterruptStep == "Backup" {
|
||||||
if err := backupBeforeRecover(snap, ""); err != nil {
|
if err := backupBeforeRecover(snap); err != nil {
|
||||||
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
|
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
|||||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||||
}
|
}
|
||||||
|
|
||||||
func backupBeforeRecover(snap model.Snapshot, secret string) error {
|
func backupBeforeRecover(snap model.Snapshot) error {
|
||||||
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var status model.SnapshotStatus
|
var status model.SnapshotStatus
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
|||||||
|
|
||||||
showCountFrom := (req.Page - 1) * req.PageSize
|
showCountFrom := (req.Page - 1) * req.PageSize
|
||||||
showCountTo := req.Page * req.PageSize
|
showCountTo := req.Page * req.PageSize
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
qqWry, err := qqwry.NewQQwry()
|
qqWry, err := qqwry.NewQQwry()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("load qqwry datas failed: %s", err)
|
global.LOG.Errorf("load qqwry datas failed: %s", err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -139,8 +140,10 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
|||||||
}
|
}
|
||||||
for _, web := range websites {
|
for _, web := range websites {
|
||||||
var (
|
var (
|
||||||
appName string
|
appName string
|
||||||
runtimeName string
|
runtimeName string
|
||||||
|
runtimeType string
|
||||||
|
appInstallID uint
|
||||||
)
|
)
|
||||||
switch web.Type {
|
switch web.Type {
|
||||||
case constant.Deployment:
|
case constant.Deployment:
|
||||||
@@ -149,12 +152,15 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
|||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
appName = appInstall.Name
|
appName = appInstall.Name
|
||||||
|
appInstallID = appInstall.ID
|
||||||
case constant.Runtime:
|
case constant.Runtime:
|
||||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID))
|
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
runtimeName = runtime.Name
|
runtimeName = runtime.Name
|
||||||
|
runtimeType = runtime.Type
|
||||||
|
appInstallID = runtime.ID
|
||||||
}
|
}
|
||||||
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
|
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
|
||||||
|
|
||||||
@@ -173,6 +179,8 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
|||||||
SSLStatus: checkSSLStatus(web.WebsiteSSL.ExpireDate),
|
SSLStatus: checkSSLStatus(web.WebsiteSSL.ExpireDate),
|
||||||
RuntimeName: runtimeName,
|
RuntimeName: runtimeName,
|
||||||
SitePath: sitePath,
|
SitePath: sitePath,
|
||||||
|
AppInstallID: appInstallID,
|
||||||
|
RuntimeType: runtimeType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return total, websiteDTOs, nil
|
return total, websiteDTOs, nil
|
||||||
@@ -305,6 +313,14 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
|||||||
switch runtime.Type {
|
switch runtime.Type {
|
||||||
case constant.RuntimePHP:
|
case constant.RuntimePHP:
|
||||||
if runtime.Resource == constant.ResourceAppstore {
|
if runtime.Resource == constant.ResourceAppstore {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
if !checkImageExist(client, runtime.Image) {
|
||||||
|
return buserr.WithName("ErrImageNotExist", runtime.Name)
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
req request.AppInstallCreate
|
req request.AppInstallCreate
|
||||||
install *model.AppInstall
|
install *model.AppInstall
|
||||||
@@ -471,7 +487,9 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
|||||||
tx, ctx := helper.GetTxAndContext()
|
tx, ctx := helper.GetTxAndContext()
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
go NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
|
go func() {
|
||||||
|
_ = NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
|
||||||
|
}()
|
||||||
if err := websiteRepo.DeleteBy(ctx, commonRepo.WithByID(req.ID)); err != nil {
|
if err := websiteRepo.DeleteBy(ctx, commonRepo.WithByID(req.ID)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -910,6 +928,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
|
|||||||
websiteSSL.Provider = constant.Manual
|
websiteSSL.Provider = constant.Manual
|
||||||
websiteSSL.PrivateKey = privateKey
|
websiteSSL.PrivateKey = privateKey
|
||||||
websiteSSL.Pem = certificate
|
websiteSSL.Pem = certificate
|
||||||
|
websiteSSL.Status = constant.SSLReady
|
||||||
|
|
||||||
res.SSL = websiteSSL
|
res.SSL = websiteSSL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlertEnable = "Enable"
|
||||||
|
AlertDisable = "Disable"
|
||||||
|
AlertSuccess = "Success"
|
||||||
|
AlertError = "Error"
|
||||||
|
AlertSyncError = "SyncError"
|
||||||
|
AlertPushError = "PushError"
|
||||||
|
AlertPushSuccess = "PushSuccess"
|
||||||
|
)
|
||||||
@@ -95,7 +95,7 @@ var (
|
|||||||
ErrPathNotFound = "ErrPathNotFound"
|
ErrPathNotFound = "ErrPathNotFound"
|
||||||
ErrMovePathFailed = "ErrMovePathFailed"
|
ErrMovePathFailed = "ErrMovePathFailed"
|
||||||
ErrLinkPathNotFound = "ErrLinkPathNotFound"
|
ErrLinkPathNotFound = "ErrLinkPathNotFound"
|
||||||
ErrFileIsExist = "ErrFileIsExist"
|
ErrFileIsExist = "ErrFileIsExist"
|
||||||
ErrFileUpload = "ErrFileUpload"
|
ErrFileUpload = "ErrFileUpload"
|
||||||
ErrFileDownloadDir = "ErrFileDownloadDir"
|
ErrFileDownloadDir = "ErrFileDownloadDir"
|
||||||
ErrCmdNotFound = "ErrCmdNotFound"
|
ErrCmdNotFound = "ErrCmdNotFound"
|
||||||
@@ -162,3 +162,12 @@ var (
|
|||||||
ErrLicenseSave = "ErrLicenseSave"
|
ErrLicenseSave = "ErrLicenseSave"
|
||||||
ErrLicenseSync = "ErrLicenseSync"
|
ErrLicenseSync = "ErrLicenseSync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// alert
|
||||||
|
var (
|
||||||
|
ErrAlert = "ErrAlert"
|
||||||
|
ErrAlertPush = "ErrAlertPush"
|
||||||
|
ErrAlertSave = "ErrAlertSave"
|
||||||
|
ErrAlertSync = "ErrAlertSync"
|
||||||
|
ErrAlertRemote = "ErrAlertRemote"
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
|
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
interval model.Setting
|
interval model.Setting
|
||||||
status model.Setting
|
status model.Setting
|
||||||
)
|
)
|
||||||
syncBeforeStart()
|
go syncBeforeStart()
|
||||||
if err := global.DB.Where("key = ?", "MonitorStatus").Find(&status).Error; err != nil {
|
if err := global.DB.Where("key = ?", "MonitorStatus").Find(&status).Error; err != nil {
|
||||||
global.LOG.Errorf("load monitor status from db failed, err: %v", err)
|
global.LOG.Errorf("load monitor status from db failed, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||||
@@ -8,9 +12,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ssl struct {
|
type ssl struct {
|
||||||
@@ -25,7 +26,7 @@ func (ssl *ssl) Run() {
|
|||||||
sslRepo := repo.NewISSLRepo()
|
sslRepo := repo.NewISSLRepo()
|
||||||
sslService := service.NewIWebsiteSSLService()
|
sslService := service.NewIWebsiteSSLService()
|
||||||
sslList, _ := sslRepo.List()
|
sslList, _ := sslRepo.List()
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
global.LOG.Info("The scheduled certificate update task is currently in progress ...")
|
global.LOG.Info("The scheduled certificate update task is currently in progress ...")
|
||||||
now := time.Now().Add(10 * time.Second)
|
now := time.Now().Add(10 * time.Second)
|
||||||
for _, s := range sslList {
|
for _, s := range sslList {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
@@ -8,8 +11,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type website struct{}
|
type website struct{}
|
||||||
@@ -19,7 +20,7 @@ func NewWebsiteJob() *website {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *website) Run() {
|
func (w *website) Run() {
|
||||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
websites, _ := repo.NewIWebsiteRepo().List()
|
websites, _ := repo.NewIWebsiteRepo().List()
|
||||||
global.LOG.Info("Website scheduled task in progress ...")
|
global.LOG.Info("Website scheduled task in progress ...")
|
||||||
now := time.Now().Add(10 * time.Minute)
|
now := time.Now().Add(10 * time.Minute)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the
|
|||||||
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
|
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
|
||||||
ErrDomainFormat: "{{ .name }} domain format error"
|
ErrDomainFormat: "{{ .name }} domain format error"
|
||||||
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
||||||
|
ErrImageNotExist: "Running environment {{.name}} image does not exist, please re-edit the running environment"
|
||||||
|
|
||||||
#ssl
|
#ssl
|
||||||
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
||||||
@@ -196,3 +197,11 @@ ErrLicenseSync: "Failed to sync license information, no license information dete
|
|||||||
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
|
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
|
||||||
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
|
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
|
||||||
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
||||||
|
|
||||||
|
|
||||||
|
#license
|
||||||
|
ErrAlert: "Alert information format error, please check and try again!"
|
||||||
|
ErrAlertPush: "Alert push error, please check and try again!"
|
||||||
|
ErrAlertSave: "Alert save error, please check and try again!"
|
||||||
|
ErrAlertSync: "Alert sync error, please check and try again!"
|
||||||
|
ErrAlertRemote: "Remote alert error, please check and try again!"
|
||||||
@@ -94,6 +94,7 @@ ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可
|
|||||||
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
||||||
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
||||||
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
||||||
|
ErrImageNotExist: "執行環境 {{.name}} 鏡像不存在,請重新編輯執行環境"
|
||||||
|
|
||||||
#ssl
|
#ssl
|
||||||
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
||||||
@@ -198,3 +199,10 @@ ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證
|
|||||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
||||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
||||||
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
||||||
|
|
||||||
|
#license
|
||||||
|
ErrAlert: "告警資訊格式錯誤,請檢查後重試!"
|
||||||
|
ErrAlertPush: "告警資訊推送錯誤,請檢查後重試!"
|
||||||
|
ErrAlertSave: "告警資訊保存錯誤,請檢查後重試!"
|
||||||
|
ErrAlertSync: "告警資訊同步錯誤,請檢查後重試!"
|
||||||
|
ErrAlertRemote: "告警資訊遠端錯誤,請檢查後重試!"
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可
|
|||||||
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
||||||
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
||||||
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
||||||
|
ErrImageNotExist: "运行环境 {{.name}} 镜像不存在,请重新编辑运行环境"
|
||||||
|
|
||||||
#ssl
|
#ssl
|
||||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||||
@@ -200,3 +201,10 @@ ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可
|
|||||||
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
||||||
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
||||||
|
|
||||||
|
#license
|
||||||
|
ErrAlert: "告警信息格式错误,请检查后重试!"
|
||||||
|
ErrAlertPush: "告警信息推送错误,请检查后重试!"
|
||||||
|
ErrAlertSave: "告警信息保存错误,请检查后重试!"
|
||||||
|
ErrAlertSync: "告警信息同步错误,请检查后重试!"
|
||||||
|
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ func Init() {
|
|||||||
migrations.AddShellColumn,
|
migrations.AddShellColumn,
|
||||||
migrations.AddClam,
|
migrations.AddClam,
|
||||||
migrations.AddClamStatus,
|
migrations.AddClamStatus,
|
||||||
|
migrations.AddAlertMenu,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
|||||||
@@ -288,3 +288,29 @@ var AddClamStatus = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddAlertMenu = &gormigrate.Migration{
|
||||||
|
ID: "20240706-update-xpack-hide-menu",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
var (
|
||||||
|
setting model.Setting
|
||||||
|
menu dto.XpackHideMenu
|
||||||
|
)
|
||||||
|
tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").First(&setting)
|
||||||
|
if err := json.Unmarshal([]byte(setting.Value), &menu); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
menu.Children = append(menu.Children, dto.XpackHideMenu{
|
||||||
|
ID: "7",
|
||||||
|
Title: "xpack.alert.alert",
|
||||||
|
Path: "/xpack/alert/dashboard",
|
||||||
|
Label: "XAlertDashboard",
|
||||||
|
IsCheck: true,
|
||||||
|
})
|
||||||
|
data, err := json.Marshal(menu)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -96,10 +97,11 @@ func OperationLog() gin.HandlerFunc {
|
|||||||
if funcs.InputValue == key {
|
if funcs.InputValue == key {
|
||||||
var names []string
|
var names []string
|
||||||
if funcs.IsList {
|
if funcs.IsList {
|
||||||
sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s in (?)", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||||
_ = global.DB.Raw(sql, value).Scan(&names)
|
_ = global.DB.Raw(query, value).Scan(&names)
|
||||||
} else {
|
} else {
|
||||||
_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
|
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||||
|
_ = global.DB.Raw(query, value).Scan(&names)
|
||||||
}
|
}
|
||||||
formatMap[funcs.OutputValue] = strings.Join(names, ",")
|
formatMap[funcs.OutputValue] = strings.Join(names, ",")
|
||||||
break
|
break
|
||||||
@@ -109,12 +111,21 @@ func OperationLog() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
for key, value := range formatMap {
|
for key, value := range formatMap {
|
||||||
if strings.Contains(operationDic.FormatEN, "["+key+"]") {
|
if strings.Contains(operationDic.FormatEN, "["+key+"]") {
|
||||||
if arrays, ok := value.([]string); ok {
|
t := reflect.TypeOf(value)
|
||||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
|
if t.Kind() != reflect.Array && t.Kind() != reflect.Slice {
|
||||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
|
|
||||||
} else {
|
|
||||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value))
|
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value))
|
||||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value))
|
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value))
|
||||||
|
} else {
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
length := val.Len()
|
||||||
|
|
||||||
|
var elements []string
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
element := val.Index(i).Interface().(string)
|
||||||
|
elements = append(elements, element)
|
||||||
|
}
|
||||||
|
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ",")))
|
||||||
|
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ",")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func PasswordExpired() gin.HandlerFunc {
|
|||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, extime.Value, loc)
|
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, extime.Value, loc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
|
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
|
||||||
|
|||||||
@@ -256,13 +256,6 @@ func LoadSizeUnit2F(value float64) string {
|
|||||||
return fmt.Sprintf("%.2f", value)
|
return fmt.Sprintf("%.2f", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadTimeZone() string {
|
|
||||||
loc := time.Now().Location()
|
|
||||||
if _, err := time.LoadLocation(loc.String()); err != nil {
|
|
||||||
return "Asia/Shanghai"
|
|
||||||
}
|
|
||||||
return loc.String()
|
|
||||||
}
|
|
||||||
func LoadTimeZoneByCmd() string {
|
func LoadTimeZoneByCmd() string {
|
||||||
loc := time.Now().Location().String()
|
loc := time.Now().Location().String()
|
||||||
if _, err := time.LoadLocation(loc); err != nil {
|
if _, err := time.LoadLocation(loc); err != nil {
|
||||||
@@ -325,3 +318,27 @@ func SplitStr(str string, spi ...string) []string {
|
|||||||
func IsValidIP(ip string) bool {
|
func IsValidIP(ip string) bool {
|
||||||
return net.ParseIP(ip) != nil
|
return net.ParseIP(ip) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
b = uint64(1)
|
||||||
|
kb = 1024 * b
|
||||||
|
mb = 1024 * kb
|
||||||
|
gb = 1024 * mb
|
||||||
|
)
|
||||||
|
|
||||||
|
func FormatBytes(bytes uint64) string {
|
||||||
|
switch {
|
||||||
|
case bytes < kb:
|
||||||
|
return fmt.Sprintf("%dB", bytes)
|
||||||
|
case bytes < mb:
|
||||||
|
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
|
||||||
|
case bytes < gb:
|
||||||
|
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatPercent(percent float64) string {
|
||||||
|
return fmt.Sprintf("%.2f%%", percent)
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,16 +72,21 @@ func countLines(path string) (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
scanner := bufio.NewScanner(file)
|
count := 0
|
||||||
lineCount := 0
|
for {
|
||||||
for scanner.Scan() {
|
_, err := reader.ReadString('\n')
|
||||||
lineCount++
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if count > 0 {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return lineCount, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadFileByLine(filename string, page, pageSize int, latest bool) (lines []string, isEndOfFile bool, total int, err error) {
|
func ReadFileByLine(filename string, page, pageSize int, latest bool) (lines []string, isEndOfFile bool, total int, err error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -35,6 +36,9 @@ func HandleGetWithTransport(url, method string, transport *http.Transport, timeo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return 0, nil, errors.New(resp.Status)
|
||||||
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ func ValidCode(code, intervalStr, secret string) bool {
|
|||||||
}
|
}
|
||||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
strInt64 := strconv.FormatInt(now, 10)
|
prevTime := now - int64(interval)
|
||||||
id16, _ := strconv.Atoi(strInt64)
|
return totp.Verify(code, now) || totp.Verify(code, prevTime)
|
||||||
return totp.Verify(code, int64(id16))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
package helper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {}
|
|
||||||
|
|
||||||
type dumpOption struct {
|
|
||||||
isData bool
|
|
||||||
|
|
||||||
tables []string
|
|
||||||
isAllTable bool
|
|
||||||
isDropTable bool
|
|
||||||
writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
type DumpOption func(*dumpOption)
|
|
||||||
|
|
||||||
func WithDropTable() DumpOption {
|
|
||||||
return func(option *dumpOption) {
|
|
||||||
option.isDropTable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithData() DumpOption {
|
|
||||||
return func(option *dumpOption) {
|
|
||||||
option.isData = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithWriter(writer io.Writer) DumpOption {
|
|
||||||
return func(option *dumpOption) {
|
|
||||||
option.writer = writer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dump(dns string, opts ...DumpOption) error {
|
|
||||||
start := time.Now()
|
|
||||||
global.LOG.Infof("dump start at %s\n", start.Format(constant.DateTimeLayout))
|
|
||||||
defer func() {
|
|
||||||
end := time.Now()
|
|
||||||
global.LOG.Infof("dump end at %s, cost %s\n", end.Format(constant.DateTimeLayout), end.Sub(start))
|
|
||||||
}()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
var o dumpOption
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&o)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(o.tables) == 0 {
|
|
||||||
o.isAllTable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.writer == nil {
|
|
||||||
o.writer = os.Stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bufio.NewWriter(o.writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
|
|
||||||
itemFile, lineNumber := "", 0
|
|
||||||
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
itemFile += "-- MySQL Database Dump\n"
|
|
||||||
itemFile += "-- Start Time: " + start.Format(constant.DateTimeLayout) + "\n"
|
|
||||||
itemFile += "-- ----------------------------\n\n\n"
|
|
||||||
|
|
||||||
db, err := sql.Open("mysql", dns)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("open mysql db failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
dbName, err := getDBNameFromDNS(dns)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get db name from dns failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = db.Exec(fmt.Sprintf("USE `%s`", dbName))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec `use %s` failed, err: %v", dbName, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tables []string
|
|
||||||
if o.isAllTable {
|
|
||||||
tmp, err := getAllTables(db)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get all tables failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tables = tmp
|
|
||||||
} else {
|
|
||||||
tables = o.tables
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, table := range tables {
|
|
||||||
if o.isDropTable {
|
|
||||||
itemFile += fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table)
|
|
||||||
}
|
|
||||||
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
itemFile += fmt.Sprintf("-- Table structure for %s\n", table)
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
|
|
||||||
createTableSQL, err := getCreateTableSQL(db, table)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get create table sql failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
itemFile += createTableSQL
|
|
||||||
itemFile += ";\n\n\n\n"
|
|
||||||
|
|
||||||
if o.isData {
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
itemFile += fmt.Sprintf("-- Records of %s\n", table)
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
|
|
||||||
lineRows, err := db.Query(fmt.Sprintf("SELECT * FROM `%s`", table))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec `select * from %s` failed, err: %v", table, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer lineRows.Close()
|
|
||||||
|
|
||||||
var columns []string
|
|
||||||
columns, err = lineRows.Columns()
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get columes failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
columnTypes, err := lineRows.ColumnTypes()
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get colume types failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for lineRows.Next() {
|
|
||||||
row := make([]interface{}, len(columns))
|
|
||||||
rowPointers := make([]interface{}, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
rowPointers[i] = &row[i]
|
|
||||||
}
|
|
||||||
if err = lineRows.Scan(rowPointers...); err != nil {
|
|
||||||
global.LOG.Errorf("scan row data failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ssql := loadDataSql(row, columnTypes, table)
|
|
||||||
if len(ssql) != 0 {
|
|
||||||
itemFile += ssql
|
|
||||||
lineNumber++
|
|
||||||
}
|
|
||||||
if lineNumber > 500 {
|
|
||||||
_, _ = buf.WriteString(itemFile)
|
|
||||||
itemFile = ""
|
|
||||||
lineNumber = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemFile += "\n\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
itemFile += "-- Dumped by mysqldump\n"
|
|
||||||
itemFile += "-- Cost Time: " + time.Since(start).String() + "\n"
|
|
||||||
itemFile += "-- ----------------------------\n"
|
|
||||||
|
|
||||||
_, _ = buf.WriteString(itemFile)
|
|
||||||
_ = buf.Flush()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCreateTableSQL(db *sql.DB, table string) (string, error) {
|
|
||||||
var createTableSQL string
|
|
||||||
err := db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`", table)).Scan(&table, &createTableSQL)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
createTableSQL = strings.Replace(createTableSQL, "CREATE TABLE", "CREATE TABLE IF NOT EXISTS", 1)
|
|
||||||
return createTableSQL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllTables(db *sql.DB) ([]string, error) {
|
|
||||||
var tables []string
|
|
||||||
rows, err := db.Query("SHOW TABLES")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var table string
|
|
||||||
err = rows.Scan(&table)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tables = append(tables, table)
|
|
||||||
}
|
|
||||||
return tables, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadDataSql(row []interface{}, columnTypes []*sql.ColumnType, table string) string {
|
|
||||||
ssql := "INSERT INTO `" + table + "` VALUES ("
|
|
||||||
for i, col := range row {
|
|
||||||
if col == nil {
|
|
||||||
ssql += "NULL"
|
|
||||||
} else {
|
|
||||||
Type := columnTypes[i].DatabaseTypeName()
|
|
||||||
Type = strings.Replace(Type, "UNSIGNED", "", -1)
|
|
||||||
Type = strings.Replace(Type, " ", "", -1)
|
|
||||||
switch Type {
|
|
||||||
case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER", "BIGINT":
|
|
||||||
if bs, ok := col.([]byte); ok {
|
|
||||||
ssql += string(bs)
|
|
||||||
} else {
|
|
||||||
ssql += fmt.Sprintf("%d", col)
|
|
||||||
}
|
|
||||||
case "FLOAT", "DOUBLE":
|
|
||||||
if bs, ok := col.([]byte); ok {
|
|
||||||
ssql += string(bs)
|
|
||||||
} else {
|
|
||||||
ssql += fmt.Sprintf("%f", col)
|
|
||||||
}
|
|
||||||
case "DECIMAL", "DEC":
|
|
||||||
ssql += fmt.Sprintf("%s", col)
|
|
||||||
|
|
||||||
case "DATE":
|
|
||||||
t, ok := col.(time.Time)
|
|
||||||
if !ok {
|
|
||||||
global.LOG.Errorf("the DATE type conversion failed, err value: %v", col)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02"))
|
|
||||||
case "DATETIME":
|
|
||||||
t, ok := col.(time.Time)
|
|
||||||
if !ok {
|
|
||||||
global.LOG.Errorf("the DATETIME type conversion failed, err value: %v", col)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ssql += fmt.Sprintf("'%s'", t.Format(constant.DateTimeLayout))
|
|
||||||
case "TIMESTAMP":
|
|
||||||
t, ok := col.(time.Time)
|
|
||||||
if !ok {
|
|
||||||
global.LOG.Errorf("the TIMESTAMP type conversion failed, err value: %v", col)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ssql += fmt.Sprintf("'%s'", t.Format(constant.DateTimeLayout))
|
|
||||||
case "TIME":
|
|
||||||
t, ok := col.([]byte)
|
|
||||||
if !ok {
|
|
||||||
global.LOG.Errorf("the TIME type conversion failed, err value: %v", col)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ssql += fmt.Sprintf("'%s'", string(t))
|
|
||||||
case "YEAR":
|
|
||||||
t, ok := col.([]byte)
|
|
||||||
if !ok {
|
|
||||||
global.LOG.Errorf("the YEAR type conversion failed, err value: %v", col)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ssql += string(t)
|
|
||||||
case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT":
|
|
||||||
ssql += fmt.Sprintf("'%s'", strings.Replace(fmt.Sprintf("%s", col), "'", "''", -1))
|
|
||||||
case "BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB":
|
|
||||||
ssql += fmt.Sprintf("0x%X", col)
|
|
||||||
case "ENUM", "SET":
|
|
||||||
ssql += fmt.Sprintf("'%s'", col)
|
|
||||||
case "BOOL", "BOOLEAN":
|
|
||||||
if col.(bool) {
|
|
||||||
ssql += "true"
|
|
||||||
} else {
|
|
||||||
ssql += "false"
|
|
||||||
}
|
|
||||||
case "JSON":
|
|
||||||
ssql += fmt.Sprintf("'%s'", col)
|
|
||||||
default:
|
|
||||||
global.LOG.Errorf("unsupported colume type: %s", Type)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i < len(row)-1 {
|
|
||||||
ssql += ","
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ssql += ");\n"
|
|
||||||
return ssql
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
package helper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sourceOption struct {
|
|
||||||
dryRun bool
|
|
||||||
mergeInsert int
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
type SourceOption func(*sourceOption)
|
|
||||||
|
|
||||||
func WithMergeInsert(size int) SourceOption {
|
|
||||||
return func(o *sourceOption) {
|
|
||||||
o.mergeInsert = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type dbWrapper struct {
|
|
||||||
DB *sql.DB
|
|
||||||
debug bool
|
|
||||||
dryRun bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDBWrapper(db *sql.DB, dryRun, debug bool) *dbWrapper {
|
|
||||||
|
|
||||||
return &dbWrapper{
|
|
||||||
DB: db,
|
|
||||||
dryRun: dryRun,
|
|
||||||
debug: debug,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *dbWrapper) Exec(query string, args ...interface{}) (sql.Result, error) {
|
|
||||||
if db.debug {
|
|
||||||
global.LOG.Debugf("query %s", query)
|
|
||||||
}
|
|
||||||
|
|
||||||
if db.dryRun {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return db.DB.Exec(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Source(dns string, reader io.Reader, opts ...SourceOption) error {
|
|
||||||
start := time.Now()
|
|
||||||
global.LOG.Infof("source start at %s", start.Format(constant.DateTimeLayout))
|
|
||||||
defer func() {
|
|
||||||
end := time.Now()
|
|
||||||
global.LOG.Infof("source end at %s, cost %s", end.Format(constant.DateTimeLayout), end.Sub(start))
|
|
||||||
}()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var db *sql.DB
|
|
||||||
var o sourceOption
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&o)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbName, err := getDBNameFromDNS(dns)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("get db name from dns failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err = sql.Open("mysql", dns)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("open mysql db failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
dbWrapper := newDBWrapper(db, o.dryRun, o.debug)
|
|
||||||
|
|
||||||
_, err = dbWrapper.Exec(fmt.Sprintf("USE `%s`;", dbName))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec `use %s` failed, err: %v", dbName, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
db.SetConnMaxLifetime(3600)
|
|
||||||
|
|
||||||
r := bufio.NewReader(reader)
|
|
||||||
_, err = dbWrapper.Exec("SET autocommit=0;")
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec `set autocommit=0` failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
line, err := readLine(r)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
global.LOG.Errorf("read sql failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ssql, err := trim(line)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("trim sql failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
afterInsertSql := ""
|
|
||||||
if o.mergeInsert > 1 && strings.HasPrefix(ssql, "INSERT INTO") {
|
|
||||||
var insertSQLs []string
|
|
||||||
insertSQLs = append(insertSQLs, ssql)
|
|
||||||
for i := 0; i < o.mergeInsert-1; i++ {
|
|
||||||
line, err := readLine(r)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ssql2, err := trim(line)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("trim merge insert sql failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ssql2, "INSERT INTO") {
|
|
||||||
insertSQLs = append(insertSQLs, ssql2)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
afterInsertSql = ssql2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ssql, err = mergeInsert(insertSQLs)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("do merge insert failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dbWrapper.Exec(ssql)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec sql failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(afterInsertSql) != 0 {
|
|
||||||
_, err = dbWrapper.Exec(afterInsertSql)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec sql failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dbWrapper.Exec("COMMIT;")
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec `commit` failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dbWrapper.Exec("SET autocommit=1;")
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("exec `autocommit=1` failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeInsert(insertSQLs []string) (string, error) {
|
|
||||||
if len(insertSQLs) == 0 {
|
|
||||||
return "", errors.New("no input provided")
|
|
||||||
}
|
|
||||||
builder := strings.Builder{}
|
|
||||||
sql1 := insertSQLs[0]
|
|
||||||
sql1 = strings.TrimSuffix(sql1, ";")
|
|
||||||
builder.WriteString(sql1)
|
|
||||||
for i, insertSQL := range insertSQLs[1:] {
|
|
||||||
if i < len(insertSQLs)-1 {
|
|
||||||
builder.WriteString(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
valuesIdx := strings.Index(insertSQL, "VALUES")
|
|
||||||
if valuesIdx == -1 {
|
|
||||||
return "", errors.New("invalid SQL: missing VALUES keyword")
|
|
||||||
}
|
|
||||||
sqln := insertSQL[valuesIdx:]
|
|
||||||
sqln = strings.TrimPrefix(sqln, "VALUES")
|
|
||||||
sqln = strings.TrimSuffix(sqln, ";")
|
|
||||||
builder.WriteString(sqln)
|
|
||||||
|
|
||||||
}
|
|
||||||
builder.WriteString(";")
|
|
||||||
|
|
||||||
return builder.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func trim(s string) (string, error) {
|
|
||||||
s = strings.TrimLeft(s, "\n")
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDBNameFromDNS(dns string) (string, error) {
|
|
||||||
ss1 := strings.Split(dns, "/")
|
|
||||||
if len(ss1) == 2 {
|
|
||||||
ss2 := strings.Split(ss1[1], "?")
|
|
||||||
if len(ss2) == 2 {
|
|
||||||
return ss2[0], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("dns error: %s", dns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readLine(r *bufio.Reader) (string, error) {
|
|
||||||
lineItem, err := r.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return lineItem, err
|
|
||||||
}
|
|
||||||
global.LOG.Errorf("read merge insert sql failed, err: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(lineItem, ";\n") {
|
|
||||||
return lineItem, nil
|
|
||||||
}
|
|
||||||
lineAppend, err := readLine(r)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return lineItem, err
|
|
||||||
}
|
|
||||||
global.LOG.Errorf("read merge insert sql failed, err: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return lineItem + lineAppend, nil
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
@@ -292,8 +293,12 @@ func (c *AcmeClient) GetDNSResolve(domains []string) (map[string]Resolve, error)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
|
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
fqdn := challengeInfo.FQDN
|
||||||
|
if strings.HasPrefix(domain, "*.") && strings.Contains(fqdn, "*.") {
|
||||||
|
fqdn = strings.Replace(fqdn, "*.", "", 1)
|
||||||
|
}
|
||||||
resolves[domain] = Resolve{
|
resolves[domain] = Resolve{
|
||||||
Key: challengeInfo.FQDN,
|
Key: fqdn,
|
||||||
Value: challengeInfo.Value,
|
Value: challengeInfo.Value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
"github.com/shirou/gopsutil/v3/host"
|
"github.com/shirou/gopsutil/v3/host"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
@@ -147,26 +148,6 @@ func getDownloadProcess(progress DownloadProgress) (res []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
b = uint64(1)
|
|
||||||
kb = 1024 * b
|
|
||||||
mb = 1024 * kb
|
|
||||||
gb = 1024 * mb
|
|
||||||
)
|
|
||||||
|
|
||||||
func formatBytes(bytes uint64) string {
|
|
||||||
switch {
|
|
||||||
case bytes < kb:
|
|
||||||
return fmt.Sprintf("%dB", bytes)
|
|
||||||
case bytes < mb:
|
|
||||||
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
|
|
||||||
case bytes < gb:
|
|
||||||
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
|
func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
|
||||||
var processes []*process.Process
|
var processes []*process.Process
|
||||||
processes, err = process.Processes()
|
processes, err = process.Processes()
|
||||||
@@ -230,14 +211,14 @@ func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
|
|||||||
procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
|
procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
|
||||||
menInfo, procErr := proc.MemoryInfo()
|
menInfo, procErr := proc.MemoryInfo()
|
||||||
if procErr == nil {
|
if procErr == nil {
|
||||||
procData.Rss = formatBytes(menInfo.RSS)
|
procData.Rss = common.FormatBytes(menInfo.RSS)
|
||||||
procData.RssValue = menInfo.RSS
|
procData.RssValue = menInfo.RSS
|
||||||
procData.Data = formatBytes(menInfo.Data)
|
procData.Data = common.FormatBytes(menInfo.Data)
|
||||||
procData.VMS = formatBytes(menInfo.VMS)
|
procData.VMS = common.FormatBytes(menInfo.VMS)
|
||||||
procData.HWM = formatBytes(menInfo.HWM)
|
procData.HWM = common.FormatBytes(menInfo.HWM)
|
||||||
procData.Stack = formatBytes(menInfo.Stack)
|
procData.Stack = common.FormatBytes(menInfo.Stack)
|
||||||
procData.Locked = formatBytes(menInfo.Locked)
|
procData.Locked = common.FormatBytes(menInfo.Locked)
|
||||||
procData.Swap = formatBytes(menInfo.Swap)
|
procData.Swap = common.FormatBytes(menInfo.Swap)
|
||||||
} else {
|
} else {
|
||||||
procData.Rss = "--"
|
procData.Rss = "--"
|
||||||
procData.Data = "--"
|
procData.Data = "--"
|
||||||
@@ -251,8 +232,8 @@ func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
|
|||||||
}
|
}
|
||||||
ioStat, procErr := proc.IOCounters()
|
ioStat, procErr := proc.IOCounters()
|
||||||
if procErr == nil {
|
if procErr == nil {
|
||||||
procData.DiskWrite = formatBytes(ioStat.WriteBytes)
|
procData.DiskWrite = common.FormatBytes(ioStat.WriteBytes)
|
||||||
procData.DiskRead = formatBytes(ioStat.ReadBytes)
|
procData.DiskRead = common.FormatBytes(ioStat.ReadBytes)
|
||||||
} else {
|
} else {
|
||||||
procData.DiskWrite = "--"
|
procData.DiskWrite = "--"
|
||||||
procData.DiskRead = "--"
|
procData.DiskRead = "--"
|
||||||
|
|||||||
+43
-22
@@ -1,5 +1,4 @@
|
|||||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import "github.com/swaggo/swag"
|
import "github.com/swaggo/swag"
|
||||||
@@ -216,28 +215,12 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [
|
"BeforeFunctions": [],
|
||||||
{
|
|
||||||
"db": "app_installs",
|
|
||||||
"input_column": "name",
|
|
||||||
"input_value": "name",
|
|
||||||
"isList": false,
|
|
||||||
"output_column": "app_id",
|
|
||||||
"output_value": "appId"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"db": "apps",
|
|
||||||
"info": "appId",
|
|
||||||
"isList": false,
|
|
||||||
"output_column": "key",
|
|
||||||
"output_value": "appKey"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"formatEN": "Install app [appKey]-[name]",
|
"formatEN": "Install app [name]",
|
||||||
"formatZH": "安装应用 [appKey]-[name]",
|
"formatZH": "安装应用 [name]",
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11486,7 +11469,7 @@ const docTemplate = `{
|
|||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/dto.SearchWithPage"
|
"$ref": "#/definitions/dto.SearchClamWithPage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -19321,6 +19304,42 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SearchClamWithPage": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"order",
|
||||||
|
"orderBy",
|
||||||
|
"page",
|
||||||
|
"pageSize"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"info": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"null",
|
||||||
|
"ascending",
|
||||||
|
"descending"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"orderBy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"name",
|
||||||
|
"status",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pageSize": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SearchForTree": {
|
"dto.SearchForTree": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -23457,6 +23476,8 @@ var SwaggerInfo = &swag.Spec{
|
|||||||
Description: "开源Linux面板",
|
Description: "开源Linux面板",
|
||||||
InfoInstanceName: "swagger",
|
InfoInstanceName: "swagger",
|
||||||
SwaggerTemplate: docTemplate,
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -209,28 +209,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [
|
"BeforeFunctions": [],
|
||||||
{
|
|
||||||
"db": "app_installs",
|
|
||||||
"input_column": "name",
|
|
||||||
"input_value": "name",
|
|
||||||
"isList": false,
|
|
||||||
"output_column": "app_id",
|
|
||||||
"output_value": "appId"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"db": "apps",
|
|
||||||
"info": "appId",
|
|
||||||
"isList": false,
|
|
||||||
"output_column": "key",
|
|
||||||
"output_value": "appKey"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"formatEN": "Install app [appKey]-[name]",
|
"formatEN": "Install app [name]",
|
||||||
"formatZH": "安装应用 [appKey]-[name]",
|
"formatZH": "安装应用 [name]",
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11479,7 +11463,7 @@
|
|||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/dto.SearchWithPage"
|
"$ref": "#/definitions/dto.SearchClamWithPage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -19314,6 +19298,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SearchClamWithPage": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"order",
|
||||||
|
"orderBy",
|
||||||
|
"page",
|
||||||
|
"pageSize"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"info": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"null",
|
||||||
|
"ascending",
|
||||||
|
"descending"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"orderBy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"name",
|
||||||
|
"status",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pageSize": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SearchForTree": {
|
"dto.SearchForTree": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -2748,6 +2748,32 @@ definitions:
|
|||||||
- ssl
|
- ssl
|
||||||
- sslType
|
- sslType
|
||||||
type: object
|
type: object
|
||||||
|
dto.SearchClamWithPage:
|
||||||
|
properties:
|
||||||
|
info:
|
||||||
|
type: string
|
||||||
|
order:
|
||||||
|
enum:
|
||||||
|
- "null"
|
||||||
|
- ascending
|
||||||
|
- descending
|
||||||
|
type: string
|
||||||
|
orderBy:
|
||||||
|
enum:
|
||||||
|
- name
|
||||||
|
- status
|
||||||
|
- created_at
|
||||||
|
type: string
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
pageSize:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- order
|
||||||
|
- orderBy
|
||||||
|
- page
|
||||||
|
- pageSize
|
||||||
|
type: object
|
||||||
dto.SearchForTree:
|
dto.SearchForTree:
|
||||||
properties:
|
properties:
|
||||||
info:
|
info:
|
||||||
@@ -5633,22 +5659,11 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- App
|
- App
|
||||||
x-panel-log:
|
x-panel-log:
|
||||||
BeforeFunctions:
|
BeforeFunctions: []
|
||||||
- db: app_installs
|
|
||||||
input_column: name
|
|
||||||
input_value: name
|
|
||||||
isList: false
|
|
||||||
output_column: app_id
|
|
||||||
output_value: appId
|
|
||||||
- db: apps
|
|
||||||
info: appId
|
|
||||||
isList: false
|
|
||||||
output_column: key
|
|
||||||
output_value: appKey
|
|
||||||
bodyKeys:
|
bodyKeys:
|
||||||
- name
|
- name
|
||||||
formatEN: Install app [appKey]-[name]
|
formatEN: Install app [name]
|
||||||
formatZH: 安装应用 [appKey]-[name]
|
formatZH: 安装应用 [name]
|
||||||
paramKeys: []
|
paramKeys: []
|
||||||
/apps/installed/check:
|
/apps/installed/check:
|
||||||
post:
|
post:
|
||||||
@@ -12769,7 +12784,7 @@ paths:
|
|||||||
name: request
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/dto.SearchWithPage'
|
$ref: '#/definitions/dto.SearchClamWithPage'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ location ^~ /test {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header REMOTE-HOST $remote_addr;
|
proxy_set_header REMOTE-HOST $remote_addr;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection $http_connection;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ server {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection $http_connection;
|
||||||
|
|
||||||
|
|
||||||
access_log /www/sites/domain/log/access.log main;
|
access_log /www/sites/domain/log/access.log main;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"element-plus": "^2.7.5",
|
"element-plus": "^2.7.5",
|
||||||
"fit2cloud-ui-plus": "^1.1.4",
|
"fit2cloud-ui-plus": "^1.1.6",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"md-editor-v3": "^2.11.3",
|
"md-editor-v3": "^2.11.3",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"postcss-html": "^1.4.1",
|
"postcss-html": "^1.4.1",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"rollup-plugin-visualizer": "^5.5.4",
|
"rollup-plugin-visualizer": "^5.5.4",
|
||||||
"sass": "^1.49.7",
|
"sass": "^1.77.8",
|
||||||
"standard-version": "^9.5.0",
|
"standard-version": "^9.5.0",
|
||||||
"stylelint": "^15.10.1",
|
"stylelint": "^15.10.1",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export namespace Cronjob {
|
|||||||
backupAccounts: string;
|
backupAccounts: string;
|
||||||
defaultDownload: string;
|
defaultDownload: string;
|
||||||
backupAccountList: Array<string>;
|
backupAccountList: Array<string>;
|
||||||
|
appIdList: Array<string>;
|
||||||
|
websiteList: Array<string>;
|
||||||
|
dbNameList: Array<string>;
|
||||||
retainCopies: number;
|
retainCopies: number;
|
||||||
status: string;
|
status: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export namespace Dashboard {
|
|||||||
kernelArch: string;
|
kernelArch: string;
|
||||||
kernelVersion: string;
|
kernelVersion: string;
|
||||||
virtualizationSystem: string;
|
virtualizationSystem: string;
|
||||||
|
ipv4Addr: string;
|
||||||
|
systemProxy: string;
|
||||||
|
|
||||||
cpuCores: number;
|
cpuCores: number;
|
||||||
cpuLogicalCores: number;
|
cpuLogicalCores: number;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export namespace Website {
|
|||||||
sitePath: string;
|
sitePath: string;
|
||||||
appName: string;
|
appName: string;
|
||||||
runtimeName: string;
|
runtimeName: string;
|
||||||
|
runtimeType: string;
|
||||||
}
|
}
|
||||||
export interface WebsiteRes extends CommonModel {
|
export interface WebsiteRes extends CommonModel {
|
||||||
protocol: string;
|
protocol: string;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "panel"; /* Project id 3575356 */
|
font-family: "panel"; /* Project id 3575356 */
|
||||||
src: url('iconfont.woff2?t=1717570629440') format('woff2'),
|
src: url('iconfont.woff2?t=1724290117056') format('woff2'),
|
||||||
url('iconfont.woff?t=1717570629440') format('woff'),
|
url('iconfont.woff?t=1724290117056') format('woff'),
|
||||||
url('iconfont.ttf?t=1717570629440') format('truetype'),
|
url('iconfont.ttf?t=1724290117056') format('truetype'),
|
||||||
url('iconfont.svg?t=1717570629440#panel') format('svg');
|
url('iconfont.svg?t=1724290117056#panel') format('svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
@@ -14,6 +14,50 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-alert-1:before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-alert-2:before {
|
||||||
|
content: "\e701";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-aws:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tengxunyun1:before {
|
||||||
|
content: "\e651";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-qiniuyun:before {
|
||||||
|
content: "\e62c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-minio:before {
|
||||||
|
content: "\e63c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-onedrive:before {
|
||||||
|
content: "\e601";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-oss:before {
|
||||||
|
content: "\e607";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-SFTP:before {
|
||||||
|
content: "\e647";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-webdav:before {
|
||||||
|
content: "\e622";
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-zhongduan:before {
|
||||||
|
content: "\e731";
|
||||||
|
}
|
||||||
|
|
||||||
.p-docker1:before {
|
.p-docker1:before {
|
||||||
content: "\e76a";
|
content: "\e76a";
|
||||||
}
|
}
|
||||||
@@ -178,18 +222,10 @@
|
|||||||
content: "\e856";
|
content: "\e856";
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-webdav:before {
|
|
||||||
content: "\e622";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-xiangqing:before {
|
.p-xiangqing:before {
|
||||||
content: "\e677";
|
content: "\e677";
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-onedrive:before {
|
|
||||||
content: "\e601";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-caidan:before {
|
.p-caidan:before {
|
||||||
content: "\e61d";
|
content: "\e61d";
|
||||||
}
|
}
|
||||||
@@ -198,14 +234,6 @@
|
|||||||
content: "\e744";
|
content: "\e744";
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-tengxunyun1:before {
|
|
||||||
content: "\e651";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-qiniuyun:before {
|
|
||||||
content: "\e62c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-file-png:before {
|
.p-file-png:before {
|
||||||
content: "\e7ae";
|
content: "\e7ae";
|
||||||
}
|
}
|
||||||
@@ -250,10 +278,6 @@
|
|||||||
content: "\e60f";
|
content: "\e60f";
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-aws:before {
|
|
||||||
content: "\e600";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-taolun:before {
|
.p-taolun:before {
|
||||||
content: "\e602";
|
content: "\e602";
|
||||||
}
|
}
|
||||||
@@ -262,22 +286,10 @@
|
|||||||
content: "\e616";
|
content: "\e616";
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-SFTP:before {
|
|
||||||
content: "\e647";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-huaban88:before {
|
.p-huaban88:before {
|
||||||
content: "\e67c";
|
content: "\e67c";
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-oss:before {
|
|
||||||
content: "\e607";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-minio:before {
|
|
||||||
content: "\e63c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-logout:before {
|
.p-logout:before {
|
||||||
content: "\e8fe";
|
content: "\e8fe";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,83 @@
|
|||||||
"css_prefix_text": "p-",
|
"css_prefix_text": "p-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "29851742",
|
||||||
|
"name": "短信告警",
|
||||||
|
"font_class": "alert-1",
|
||||||
|
"unicode": "e611",
|
||||||
|
"unicode_decimal": 58897
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "33427786",
|
||||||
|
"name": "告警日志",
|
||||||
|
"font_class": "alert-2",
|
||||||
|
"unicode": "e701",
|
||||||
|
"unicode_decimal": 59137
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "32101973",
|
||||||
|
"name": "Amazon_Web_Services_Logo",
|
||||||
|
"font_class": "aws",
|
||||||
|
"unicode": "e600",
|
||||||
|
"unicode_decimal": 58880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "12959160",
|
||||||
|
"name": "腾讯云",
|
||||||
|
"font_class": "tengxunyun1",
|
||||||
|
"unicode": "e651",
|
||||||
|
"unicode_decimal": 58961
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "24877229",
|
||||||
|
"name": "七牛云",
|
||||||
|
"font_class": "qiniuyun",
|
||||||
|
"unicode": "e62c",
|
||||||
|
"unicode_decimal": 58924
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "20290513",
|
||||||
|
"name": "minio",
|
||||||
|
"font_class": "minio",
|
||||||
|
"unicode": "e63c",
|
||||||
|
"unicode_decimal": 58940
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "13015332",
|
||||||
|
"name": "onedrive",
|
||||||
|
"font_class": "onedrive",
|
||||||
|
"unicode": "e601",
|
||||||
|
"unicode_decimal": 58881
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "16268521",
|
||||||
|
"name": "oss",
|
||||||
|
"font_class": "oss",
|
||||||
|
"unicode": "e607",
|
||||||
|
"unicode_decimal": 58887
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "13532955",
|
||||||
|
"name": "SFTP",
|
||||||
|
"font_class": "SFTP",
|
||||||
|
"unicode": "e647",
|
||||||
|
"unicode_decimal": 58951
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "23044673",
|
||||||
|
"name": "webdav",
|
||||||
|
"font_class": "webdav",
|
||||||
|
"unicode": "e622",
|
||||||
|
"unicode_decimal": 58914
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "30053592",
|
||||||
|
"name": "终端",
|
||||||
|
"font_class": "zhongduan",
|
||||||
|
"unicode": "e731",
|
||||||
|
"unicode_decimal": 59185
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "1064806",
|
"icon_id": "1064806",
|
||||||
"name": "docker",
|
"name": "docker",
|
||||||
@@ -292,13 +369,6 @@
|
|||||||
"unicode": "e856",
|
"unicode": "e856",
|
||||||
"unicode_decimal": 59478
|
"unicode_decimal": 59478
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "23044673",
|
|
||||||
"name": "webdav",
|
|
||||||
"font_class": "webdav",
|
|
||||||
"unicode": "e622",
|
|
||||||
"unicode_decimal": 58914
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "10293150",
|
"icon_id": "10293150",
|
||||||
"name": "详情",
|
"name": "详情",
|
||||||
@@ -306,13 +376,6 @@
|
|||||||
"unicode": "e677",
|
"unicode": "e677",
|
||||||
"unicode_decimal": 58999
|
"unicode_decimal": 58999
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "13015332",
|
|
||||||
"name": "onedrive",
|
|
||||||
"font_class": "onedrive",
|
|
||||||
"unicode": "e601",
|
|
||||||
"unicode_decimal": 58881
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "7708032",
|
"icon_id": "7708032",
|
||||||
"name": "菜单",
|
"name": "菜单",
|
||||||
@@ -327,20 +390,6 @@
|
|||||||
"unicode": "e744",
|
"unicode": "e744",
|
||||||
"unicode_decimal": 59204
|
"unicode_decimal": 59204
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "12959160",
|
|
||||||
"name": "腾讯云",
|
|
||||||
"font_class": "tengxunyun1",
|
|
||||||
"unicode": "e651",
|
|
||||||
"unicode_decimal": 58961
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon_id": "24877229",
|
|
||||||
"name": "七牛云",
|
|
||||||
"font_class": "qiniuyun",
|
|
||||||
"unicode": "e62c",
|
|
||||||
"unicode_decimal": 58924
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "19671162",
|
"icon_id": "19671162",
|
||||||
"name": "png-1",
|
"name": "png-1",
|
||||||
@@ -418,13 +467,6 @@
|
|||||||
"unicode": "e60f",
|
"unicode": "e60f",
|
||||||
"unicode_decimal": 58895
|
"unicode_decimal": 58895
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "32101973",
|
|
||||||
"name": "Amazon_Web_Services_Logo",
|
|
||||||
"font_class": "aws",
|
|
||||||
"unicode": "e600",
|
|
||||||
"unicode_decimal": 58880
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "1760690",
|
"icon_id": "1760690",
|
||||||
"name": "讨论",
|
"name": "讨论",
|
||||||
@@ -439,13 +481,6 @@
|
|||||||
"unicode": "e616",
|
"unicode": "e616",
|
||||||
"unicode_decimal": 58902
|
"unicode_decimal": 58902
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "13532955",
|
|
||||||
"name": "SFTP",
|
|
||||||
"font_class": "SFTP",
|
|
||||||
"unicode": "e647",
|
|
||||||
"unicode_decimal": 58951
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "15337722",
|
"icon_id": "15337722",
|
||||||
"name": "Logo GitHub",
|
"name": "Logo GitHub",
|
||||||
@@ -453,20 +488,6 @@
|
|||||||
"unicode": "e67c",
|
"unicode": "e67c",
|
||||||
"unicode_decimal": 59004
|
"unicode_decimal": 59004
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "16268521",
|
|
||||||
"name": "oss",
|
|
||||||
"font_class": "oss",
|
|
||||||
"unicode": "e607",
|
|
||||||
"unicode_decimal": 58887
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon_id": "20290513",
|
|
||||||
"name": "minio",
|
|
||||||
"font_class": "minio",
|
|
||||||
"unicode": "e63c",
|
|
||||||
"unicode_decimal": 58940
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "924436",
|
"icon_id": "924436",
|
||||||
"name": "logout",
|
"name": "logout",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 174 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -72,30 +72,52 @@
|
|||||||
</ComplexTable>
|
</ComplexTable>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="open"
|
||||||
|
:title="isBackup ? $t('commons.button.backup') : $t('commons.button.recover') + ' - ' + name"
|
||||||
|
width="40%"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<el-form ref="backupForm" label-position="left" v-loading="loading">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.compressPassword')"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
v-if="type === 'app' || type === 'website'"
|
||||||
|
>
|
||||||
|
<el-input v-model="secret" :placeholder="$t('setting.backupRecoverMessage')" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose" :disabled="loading">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="onSubmit" :disabled="loading">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<OpDialog ref="opRef" @search="search" />
|
<OpDialog ref="opRef" @search="search" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppBackUp ref="backupRef" @close="search" />
|
|
||||||
<AppRecover ref="recoverRef" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
|
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
|
||||||
import { getBackupList } from '@/api/modules/setting';
|
import { getBackupList, handleBackup, handleRecover } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
|
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
|
||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import AppBackUp from '@/views/app-store/installed/backup/index.vue';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import AppRecover from '@/views/app-store/installed/recover/index.vue';
|
|
||||||
|
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const backupRef = ref();
|
|
||||||
const recoverRef = ref();
|
|
||||||
|
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
@@ -113,6 +135,10 @@ const backupPath = ref();
|
|||||||
const status = ref();
|
const status = ref();
|
||||||
const secret = ref();
|
const secret = ref();
|
||||||
|
|
||||||
|
const open = ref();
|
||||||
|
const isBackup = ref();
|
||||||
|
const currentRow = ref();
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -133,6 +159,9 @@ const acceptParams = (params: DialogProps): void => {
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
backupVisible.value = false;
|
backupVisible.value = false;
|
||||||
};
|
};
|
||||||
|
const handleBackupClose = () => {
|
||||||
|
open.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const loadBackupDir = () => {
|
const loadBackupDir = () => {
|
||||||
getBackupList().then((res) => {
|
getBackupList().then((res) => {
|
||||||
@@ -175,25 +204,84 @@ const search = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onBackup = async () => {
|
const onBackup = async () => {
|
||||||
|
isBackup.value = true;
|
||||||
|
if (type.value !== 'app' && type.value !== 'website') {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']),
|
||||||
|
i18n.global.t('commons.button.backup'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
onSubmit();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
if (isBackup.value) {
|
||||||
|
let params = {
|
||||||
|
type: type.value,
|
||||||
|
name: name.value,
|
||||||
|
detailName: detailName.value,
|
||||||
|
secret: secret.value,
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
await handleBackup(params)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
handleClose();
|
||||||
|
handleBackupClose();
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
let params = {
|
let params = {
|
||||||
|
source: currentRow.value.source,
|
||||||
type: type.value,
|
type: type.value,
|
||||||
name: name.value,
|
name: name.value,
|
||||||
detailName: detailName.value,
|
detailName: detailName.value,
|
||||||
|
file: currentRow.value.fileDir + '/' + currentRow.value.fileName,
|
||||||
secret: secret.value,
|
secret: secret.value,
|
||||||
};
|
};
|
||||||
backupRef.value.acceptParams(params);
|
loading.value = true;
|
||||||
|
await handleRecover(params)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
handleClose();
|
||||||
|
handleBackupClose();
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRecover = async (row: Backup.RecordInfo) => {
|
const onRecover = async (row: Backup.RecordInfo) => {
|
||||||
let params = {
|
isBackup.value = false;
|
||||||
source: row.source,
|
currentRow.value = row;
|
||||||
type: type.value,
|
if (type.value !== 'app' && type.value !== 'website') {
|
||||||
name: name.value,
|
ElMessageBox.confirm(
|
||||||
detailName: detailName.value,
|
i18n.global.t('commons.msg.recoverHelper', [name.value + '( ' + detailName.value + ' )']),
|
||||||
file: row.fileDir + '/' + row.fileName,
|
i18n.global.t('commons.button.recover'),
|
||||||
secret: secret.value,
|
{
|
||||||
};
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
recoverRef.value.acceptParams(params);
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
onSubmit();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownload = async (row: Backup.RecordInfo) => {
|
const onDownload = async (row: Backup.RecordInfo) => {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { ReadByLine } from '@/api/modules/files';
|
|||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
|
|
||||||
const editorRef = ref();
|
const editorRef = ref();
|
||||||
|
|
||||||
interface LogProps {
|
interface LogProps {
|
||||||
id?: number;
|
id?: number;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -74,10 +73,11 @@ let timer: NodeJS.Timer | null = null;
|
|||||||
const tailLog = ref(false);
|
const tailLog = ref(false);
|
||||||
const content = ref('');
|
const content = ref('');
|
||||||
const end = ref(false);
|
const end = ref(false);
|
||||||
const lastContent = ref('');
|
|
||||||
const scrollerElement = ref<HTMLElement | null>(null);
|
const scrollerElement = ref<HTMLElement | null>(null);
|
||||||
const minPage = ref(1);
|
const minPage = ref(1);
|
||||||
const maxPage = ref(1);
|
const maxPage = ref(1);
|
||||||
|
const logs = ref([]);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
const readReq = reactive({
|
const readReq = reactive({
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -114,7 +114,12 @@ const stopSignals = [
|
|||||||
'image push successful!',
|
'image push successful!',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const lastLogs = ref([]);
|
||||||
|
|
||||||
const getContent = (pre: boolean) => {
|
const getContent = (pre: boolean) => {
|
||||||
|
if (isLoading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
emit('update:isReading', true);
|
emit('update:isReading', true);
|
||||||
readReq.id = props.config.id;
|
readReq.id = props.config.id;
|
||||||
readReq.type = props.config.type;
|
readReq.type = props.config.type;
|
||||||
@@ -122,38 +127,55 @@ const getContent = (pre: boolean) => {
|
|||||||
if (readReq.page < 1) {
|
if (readReq.page < 1) {
|
||||||
readReq.page = 1;
|
readReq.page = 1;
|
||||||
}
|
}
|
||||||
|
isLoading.value = true;
|
||||||
ReadByLine(readReq).then((res) => {
|
ReadByLine(readReq).then((res) => {
|
||||||
if (!end.value && res.data.end) {
|
if (!end.value && res.data.end) {
|
||||||
lastContent.value = content.value;
|
lastLogs.value = [...logs.value];
|
||||||
}
|
}
|
||||||
|
|
||||||
res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) {
|
|
||||||
return String.fromCharCode(parseInt(grp, 16));
|
|
||||||
});
|
|
||||||
data.value = res.data;
|
data.value = res.data;
|
||||||
if (res.data.content != '') {
|
if (res.data.lines && res.data.lines.length > 0) {
|
||||||
if (stopSignals.some((signal) => res.data.content.endsWith(signal))) {
|
res.data.lines = res.data.lines.map((line) =>
|
||||||
|
line.replace(/\\u(\w{4})/g, function (match, grp) {
|
||||||
|
return String.fromCharCode(parseInt(grp, 16));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const newLogs = res.data.lines;
|
||||||
|
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
|
||||||
|
readReq.page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
readReq.type == 'php' &&
|
||||||
|
logs.value.length > 0 &&
|
||||||
|
newLogs.length > 0 &&
|
||||||
|
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
|
||||||
|
) {
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
|
||||||
onCloseLog();
|
onCloseLog();
|
||||||
}
|
}
|
||||||
if (end.value) {
|
if (end.value) {
|
||||||
if (lastContent.value == '') {
|
if ((logs.value.length = 0)) {
|
||||||
content.value = res.data.content;
|
logs.value = newLogs;
|
||||||
} else {
|
} else {
|
||||||
content.value = pre
|
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
|
||||||
? res.data.content + '\n' + lastContent.value
|
|
||||||
: lastContent.value + '\n' + res.data.content;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (content.value == '') {
|
if ((logs.value.length = 0)) {
|
||||||
content.value = res.data.content;
|
logs.value = newLogs;
|
||||||
} else {
|
} else {
|
||||||
content.value = pre
|
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
|
||||||
? res.data.content + '\n' + content.value
|
|
||||||
: content.value + '\n' + res.data.content;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
end.value = res.data.end;
|
end.value = res.data.end;
|
||||||
|
content.value = logs.value.join('\n');
|
||||||
|
|
||||||
emit('update:hasContent', content.value !== '');
|
emit('update:hasContent', content.value !== '');
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (pre) {
|
if (pre) {
|
||||||
@@ -171,16 +193,52 @@ const getContent = (pre: boolean) => {
|
|||||||
maxPage.value = res.data.total;
|
maxPage.value = res.data.total;
|
||||||
minPage.value = res.data.total;
|
minPage.value = res.data.total;
|
||||||
}
|
}
|
||||||
|
if (logs.value && logs.value.length > 3000) {
|
||||||
|
logs.value.splice(0, readReq.pageSize);
|
||||||
|
if (minPage.value > 1) {
|
||||||
|
minPage.value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
|
||||||
|
let inThrottle: boolean;
|
||||||
|
let lastFunc: ReturnType<typeof setTimeout>;
|
||||||
|
let lastRan: number;
|
||||||
|
return function (this: any, ...args: Parameters<T>) {
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(this, args);
|
||||||
|
lastRan = Date.now();
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => (inThrottle = false), limit);
|
||||||
|
} else {
|
||||||
|
clearTimeout(lastFunc);
|
||||||
|
lastFunc = setTimeout(() => {
|
||||||
|
if (Date.now() - lastRan >= limit) {
|
||||||
|
func.apply(this, args);
|
||||||
|
lastRan = Date.now();
|
||||||
|
}
|
||||||
|
}, limit - (Date.now() - lastRan));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const throttledGetContent = throttle(getContent, 3000);
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
throttledGetContent(false);
|
||||||
|
};
|
||||||
|
|
||||||
const changeTail = (fromOutSide: boolean) => {
|
const changeTail = (fromOutSide: boolean) => {
|
||||||
if (fromOutSide) {
|
if (fromOutSide) {
|
||||||
tailLog.value = !tailLog.value;
|
tailLog.value = !tailLog.value;
|
||||||
}
|
}
|
||||||
if (tailLog.value) {
|
if (tailLog.value) {
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
getContent(false);
|
search();
|
||||||
}, 1000 * 3);
|
}, 1000 * 3);
|
||||||
} else {
|
} else {
|
||||||
onCloseLog();
|
onCloseLog();
|
||||||
@@ -198,6 +256,7 @@ const onCloseLog = async () => {
|
|||||||
tailLog.value = false;
|
tailLog.value = false;
|
||||||
clearInterval(Number(timer));
|
clearInterval(Number(timer));
|
||||||
timer = null;
|
timer = null;
|
||||||
|
isLoading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isScrolledToBottom(element: HTMLElement): boolean {
|
function isScrolledToBottom(element: HTMLElement): boolean {
|
||||||
@@ -218,7 +277,7 @@ const init = () => {
|
|||||||
changeTail(false);
|
changeTail(false);
|
||||||
}
|
}
|
||||||
readReq.latest = true;
|
readReq.latest = true;
|
||||||
getContent(false);
|
search();
|
||||||
|
|
||||||
nextTick(() => {});
|
nextTick(() => {});
|
||||||
};
|
};
|
||||||
@@ -232,9 +291,14 @@ const initCodemirror = () => {
|
|||||||
if (editorRef.value) {
|
if (editorRef.value) {
|
||||||
scrollerElement.value = editorRef.value.$el as HTMLElement;
|
scrollerElement.value = editorRef.value.$el as HTMLElement;
|
||||||
scrollerElement.value.addEventListener('scroll', function () {
|
scrollerElement.value.addEventListener('scroll', function () {
|
||||||
|
if (tailLog.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isScrolledToBottom(scrollerElement.value)) {
|
if (isScrolledToBottom(scrollerElement.value)) {
|
||||||
readReq.page = maxPage.value;
|
if (maxPage.value > 1) {
|
||||||
getContent(false);
|
readReq.page = maxPage.value;
|
||||||
|
}
|
||||||
|
search();
|
||||||
}
|
}
|
||||||
if (isScrolledToTop(scrollerElement.value)) {
|
if (isScrolledToTop(scrollerElement.value)) {
|
||||||
readReq.page = minPage.value - 1;
|
readReq.page = minPage.value - 1;
|
||||||
@@ -242,7 +306,7 @@ const initCodemirror = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
minPage.value = readReq.page;
|
minPage.value = readReq.page;
|
||||||
getContent(true);
|
throttledGetContent(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
|
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ onMounted(() => {
|
|||||||
background-color: var(--panel-button-active) !important;
|
background-color: var(--panel-button-active) !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border: 2px solid transparent !important;
|
border: 2px solid transparent !important;
|
||||||
|
color: var(--el-text-color-regular) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<el-button type="primary" link @click="toHalo">
|
<el-button type="primary" link @click="toHalo">
|
||||||
<span class="font-normal">{{ isProductPro ? $t('license.pro') : $t('license.community') }}</span>
|
<span class="font-normal">{{ isProductPro ? $t('license.pro') : $t('license.community') }}</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<span class="version">{{ version }}</span>
|
<span class="version" @click="copyText(version)">{{ version }}</span>
|
||||||
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
||||||
<el-button type="primary" link @click="onLoadUpgradeInfo">
|
<el-button type="primary" link @click="onLoadUpgradeInfo">
|
||||||
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
|
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
|
||||||
@@ -81,6 +81,7 @@ import MdEditor from 'md-editor-v3';
|
|||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import 'md-editor-v3/lib/style.css';
|
import 'md-editor-v3/lib/style.css';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { copyText } from '@/utils/util';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
@@ -190,6 +191,7 @@ onMounted(() => {
|
|||||||
color: var(--dark-gold-base-color);
|
color: var(--dark-gold-base-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.line-height {
|
.line-height {
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
|
|||||||
@@ -92,9 +92,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="open"
|
||||||
|
:title="$t('commons.button.recover') + ' - ' + name"
|
||||||
|
width="40%"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<el-form ref="backupForm" label-position="left" v-loading="loading">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('setting.compressPassword')"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
v-if="type === 'app' || type === 'website'"
|
||||||
|
>
|
||||||
|
<el-input v-model="secret" :placeholder="$t('setting.backupRecoverMessage')" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose" :disabled="loading">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="onHandleRecover" :disabled="loading">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<OpDialog ref="opRef" @search="search" />
|
<OpDialog ref="opRef" @search="search" />
|
||||||
</div>
|
</div>
|
||||||
<AppRecover ref="recoverRef" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -105,9 +132,8 @@ import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
|
|||||||
import { File } from '@/api/interface/file';
|
import { File } from '@/api/interface/file';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { BatchDeleteFile, CheckFile, ChunkUploadFileData, GetUploadList } from '@/api/modules/files';
|
import { BatchDeleteFile, CheckFile, ChunkUploadFileData, GetUploadList } from '@/api/modules/files';
|
||||||
import { loadBaseDir } from '@/api/modules/setting';
|
import { handleRecoverByUpload, loadBaseDir } from '@/api/modules/setting';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import AppRecover from '@/views/app-store/installed/recover/index.vue';
|
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const isUpload = ref();
|
const isUpload = ref();
|
||||||
@@ -115,7 +141,9 @@ const uploadPercent = ref<number>(0);
|
|||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const baseDir = ref();
|
const baseDir = ref();
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const recoverRef = ref();
|
|
||||||
|
const open = ref();
|
||||||
|
const currentRow = ref();
|
||||||
|
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const title = ref();
|
const title = ref();
|
||||||
@@ -180,16 +208,48 @@ const search = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onRecover = async (row: File.File) => {
|
const onRecover = async (row: File.File) => {
|
||||||
|
currentRow.value = row;
|
||||||
|
if (type.value !== 'app' && type.value !== 'website') {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']),
|
||||||
|
i18n.global.t('commons.button.backup'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
onHandleRecover();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackupClose = () => {
|
||||||
|
open.value = false;
|
||||||
|
};
|
||||||
|
const onHandleRecover = async () => {
|
||||||
let params = {
|
let params = {
|
||||||
source: 'LOCAL',
|
source: 'LOCAL',
|
||||||
type: type.value,
|
type: type.value,
|
||||||
name: name.value,
|
name: name.value,
|
||||||
detailName: detailName.value,
|
detailName: detailName.value,
|
||||||
file: baseDir.value + row.name,
|
file: baseDir.value + currentRow.value.name,
|
||||||
secret: secret.value,
|
secret: secret.value,
|
||||||
recoverType: 'upload',
|
recoverType: 'upload',
|
||||||
};
|
};
|
||||||
recoverRef.value.acceptParams(params);
|
loading.value = true;
|
||||||
|
await handleRecoverByUpload(params)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
handleClose();
|
||||||
|
handleBackupClose();
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploaderFiles = ref<UploadFiles>([]);
|
const uploaderFiles = ref<UploadFiles>([]);
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ const checkImageName = (rule: any, value: any, callback: any) => {
|
|||||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||||
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||||
} else {
|
} else {
|
||||||
const reg = /^[a-zA-Z0-9]{1}[a-z:A-Z0-9_/.-]{0,149}$/;
|
const reg = /^[a-zA-Z0-9]{1}[a-z:@A-Z0-9_/.-]{0,256}$/;
|
||||||
if (!reg.test(value) && value !== '') {
|
if (!reg.test(value) && value !== '') {
|
||||||
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const message = {
|
|||||||
createNewFolder: 'Create new folder',
|
createNewFolder: 'Create new folder',
|
||||||
createNewFile: 'Create new file',
|
createNewFile: 'Create new file',
|
||||||
helpDoc: 'Help Document',
|
helpDoc: 'Help Document',
|
||||||
|
unbind: 'UnBind',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
timeStart: 'Time start',
|
timeStart: 'Time start',
|
||||||
@@ -190,7 +191,7 @@ const message = {
|
|||||||
simpleName: 'Supports non-underscore starting, English, numbers, _, length 3-30',
|
simpleName: 'Supports non-underscore starting, English, numbers, _, length 3-30',
|
||||||
simplePassword: 'Supports non-underscore starting, English, numbers, _, length 1-30',
|
simplePassword: 'Supports non-underscore starting, English, numbers, _, length 1-30',
|
||||||
dbName: 'Supports non-special character starting, including English, Chinese, numbers, .-_, with a length of 1-64',
|
dbName: 'Supports non-special character starting, including English, Chinese, numbers, .-_, with a length of 1-64',
|
||||||
imageName: 'Support English, numbers, :/.-_, length 1-150',
|
imageName: 'Support English, numbers, :@/.-_, length 1-256',
|
||||||
volumeName: 'Support English, numbers, .-_, length 2-30',
|
volumeName: 'Support English, numbers, .-_, length 2-30',
|
||||||
supervisorName: 'Supports non-special characters starting with English, numbers, - and _, length 1-128',
|
supervisorName: 'Supports non-special characters starting with English, numbers, - and _, length 1-128',
|
||||||
complexityPassword:
|
complexityPassword:
|
||||||
@@ -1523,6 +1524,7 @@ const message = {
|
|||||||
allowIPEgs:
|
allowIPEgs:
|
||||||
'If multiple ip authorizations exist, newlines need to be displayed. For example, \n172.16.10.111 \n172.16.10.0/24',
|
'If multiple ip authorizations exist, newlines need to be displayed. For example, \n172.16.10.111 \n172.16.10.0/24',
|
||||||
mfa: 'MFA',
|
mfa: 'MFA',
|
||||||
|
mfaClose: 'Disabling MFA will reduce the security of the service. Do you want to continue?',
|
||||||
secret: 'Secret',
|
secret: 'Secret',
|
||||||
mfaInterval: 'Refresh interval (s)',
|
mfaInterval: 'Refresh interval (s)',
|
||||||
mfaTitleHelper:
|
mfaTitleHelper:
|
||||||
@@ -1571,7 +1573,7 @@ const message = {
|
|||||||
status: 'Snapshot status',
|
status: 'Snapshot status',
|
||||||
ignoreRule: 'Ignore Rule',
|
ignoreRule: 'Ignore Rule',
|
||||||
ignoreHelper:
|
ignoreHelper:
|
||||||
'This rule will be used to compress and backup the 1Panel data directory during snapshots, please modify with caution.',
|
'This rule will be used to compress and backup the 1Panel data directory during snapshots, with socket files ignored by default.',
|
||||||
ignoreHelper1: 'One item per line, e.g.: \n*.log\n/opt/1panel/cache',
|
ignoreHelper1: 'One item per line, e.g.: \n*.log\n/opt/1panel/cache',
|
||||||
panelInfo: 'Write 1Panel basic information',
|
panelInfo: 'Write 1Panel basic information',
|
||||||
panelBin: 'Backup 1Panel system files',
|
panelBin: 'Backup 1Panel system files',
|
||||||
@@ -1679,6 +1681,7 @@ const message = {
|
|||||||
'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.',
|
'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.',
|
||||||
monitor:
|
monitor:
|
||||||
'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ',
|
'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ',
|
||||||
|
alert: 'Upgrade to the professional version to receive alarm information via SMS and view alarm logs, fully control various key events, and ensure worry-free system operation',
|
||||||
},
|
},
|
||||||
clean: {
|
clean: {
|
||||||
scan: 'Start Scanning',
|
scan: 'Start Scanning',
|
||||||
@@ -1828,7 +1831,7 @@ const message = {
|
|||||||
appHelper: 'Please view the installation instructions of some applications on the application details page',
|
appHelper: 'Please view the installation instructions of some applications on the application details page',
|
||||||
backupApp: 'Backup application before upgrade',
|
backupApp: 'Backup application before upgrade',
|
||||||
backupAppHelper:
|
backupAppHelper:
|
||||||
'If the upgrade fails, the backup will be automatically rolled back. Please check the failure reason in the log audit-system log',
|
'If the upgrade fails, the backup will be automatically rolled back. Please check the failure reason in the log audit-system log. The backup will retain the latest 3 copies by default',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
openrestyDeleteHelper:
|
openrestyDeleteHelper:
|
||||||
'Forcibly deleting OpenResty will delete all websites, please confirm the risk before operation',
|
'Forcibly deleting OpenResty will delete all websites, please confirm the risk before operation',
|
||||||
@@ -1851,6 +1854,10 @@ const message = {
|
|||||||
useCustom: 'Customize docker-compose.yml',
|
useCustom: 'Customize docker-compose.yml',
|
||||||
useCustomHelper:
|
useCustomHelper:
|
||||||
'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it',
|
'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it',
|
||||||
|
diffHelper:
|
||||||
|
'The left side is the old version, the right side is the new version. After editing, click to save the custom version',
|
||||||
|
pullImage: 'Pull Image',
|
||||||
|
pullImageHelper: 'Execute docker pull to pull the image before the application starts',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
website: 'Website',
|
website: 'Website',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const message = {
|
|||||||
delete: '刪除',
|
delete: '刪除',
|
||||||
edit: '編輯',
|
edit: '編輯',
|
||||||
enable: '啟用',
|
enable: '啟用',
|
||||||
disable: '禁用',
|
disable: '停用',
|
||||||
confirm: '確認',
|
confirm: '確認',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
@@ -68,6 +68,7 @@ const message = {
|
|||||||
createNewFolder: '新建資料夾',
|
createNewFolder: '新建資料夾',
|
||||||
createNewFile: '新建檔案',
|
createNewFile: '新建檔案',
|
||||||
helpDoc: '幫助文档',
|
helpDoc: '幫助文档',
|
||||||
|
unbind: '解綁',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
timeStart: '開始時間',
|
timeStart: '開始時間',
|
||||||
@@ -189,7 +190,7 @@ const message = {
|
|||||||
simpleName: '支持非底線開頭,英文、數字、_,長度3-30',
|
simpleName: '支持非底線開頭,英文、數字、_,長度3-30',
|
||||||
simplePassword: '支持非底線開頭,英文、數字、_,長度1-30',
|
simplePassword: '支持非底線開頭,英文、數字、_,長度1-30',
|
||||||
dbName: '支持非特殊字符開頭,英文、中文、數字、.-_,長度1-64',
|
dbName: '支持非特殊字符開頭,英文、中文、數字、.-_,長度1-64',
|
||||||
imageName: '支持英文、數字、:/.-_,長度1-150',
|
imageName: '支持英文、數字、:@/.-_,長度1-256',
|
||||||
volumeName: '支持英文、數字、.-和_,長度2-30',
|
volumeName: '支持英文、數字、.-和_,長度2-30',
|
||||||
supervisorName: '支援非特殊字元開頭,英文、數字、-和_,長度1-128',
|
supervisorName: '支援非特殊字元開頭,英文、數字、-和_,長度1-128',
|
||||||
complexityPassword: '請輸入長度為 8-30 位,並包含字母、數字、至少兩種特殊字符的密碼組合',
|
complexityPassword: '請輸入長度為 8-30 位,並包含字母、數字、至少兩種特殊字符的密碼組合',
|
||||||
@@ -1387,7 +1388,7 @@ const message = {
|
|||||||
deleteHelper: '將刪除該快照的所有備份文件,包括第三方備份賬號中的文件。',
|
deleteHelper: '將刪除該快照的所有備份文件,包括第三方備份賬號中的文件。',
|
||||||
status: '快照狀態',
|
status: '快照狀態',
|
||||||
ignoreRule: '排除規則',
|
ignoreRule: '排除規則',
|
||||||
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,請謹慎修改。',
|
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,預設忽略套接字檔案。',
|
||||||
ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache',
|
ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache',
|
||||||
panelInfo: '寫入 1Panel 基礎信息',
|
panelInfo: '寫入 1Panel 基礎信息',
|
||||||
panelBin: '備份 1Panel 系統文件',
|
panelBin: '備份 1Panel 系統文件',
|
||||||
@@ -1466,6 +1467,7 @@ const message = {
|
|||||||
allowIPsHelper1: '授權 IP 為空時,則取消授權 IP',
|
allowIPsHelper1: '授權 IP 為空時,則取消授權 IP',
|
||||||
allowIPEgs: '當存在多個授權 IP 時,需要換行顯示,例: \n172.16.10.111 \n172.16.10.0/24',
|
allowIPEgs: '當存在多個授權 IP 時,需要換行顯示,例: \n172.16.10.111 \n172.16.10.0/24',
|
||||||
mfa: '兩步驗證',
|
mfa: '兩步驗證',
|
||||||
|
mfaClose: '關閉兩步驗證將導致服務安全性降低,是否繼續?',
|
||||||
secret: '密鑰',
|
secret: '密鑰',
|
||||||
mfaAlert: '兩步驗證密碼是基於當前時間生成,請確保服務器時間已同步',
|
mfaAlert: '兩步驗證密碼是基於當前時間生成,請確保服務器時間已同步',
|
||||||
mfaHelper: '開啟後會驗證手機應用驗證碼',
|
mfaHelper: '開啟後會驗證手機應用驗證碼',
|
||||||
@@ -1560,6 +1562,7 @@ const message = {
|
|||||||
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。',
|
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。',
|
||||||
setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。',
|
setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。',
|
||||||
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
|
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
|
||||||
|
alert: '陞級專業版可通過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統運行無憂。',
|
||||||
},
|
},
|
||||||
clean: {
|
clean: {
|
||||||
scan: '開始掃描',
|
scan: '開始掃描',
|
||||||
@@ -1700,7 +1703,7 @@ const message = {
|
|||||||
ignoreList: '忽略列表',
|
ignoreList: '忽略列表',
|
||||||
appHelper: '部分應用的安裝使用說明請在應用詳情頁查看',
|
appHelper: '部分應用的安裝使用說明請在應用詳情頁查看',
|
||||||
backupApp: '升級前備份應用',
|
backupApp: '升級前備份應用',
|
||||||
backupAppHelper: '升級失敗會使用備份自動回滾,請在日誌審計-系統日誌中查看失敗原因',
|
backupAppHelper: '升級失敗會使用備份自動回滾,請在日誌審計-系統日誌中查看失敗原因,備份預設保留最新的3份',
|
||||||
delete: '刪除',
|
delete: '刪除',
|
||||||
openrestyDeleteHelper: '強制刪除 OpenResty 會刪除所有的網站,請確認風險後操作',
|
openrestyDeleteHelper: '強制刪除 OpenResty 會刪除所有的網站,請確認風險後操作',
|
||||||
downloadLogHelper1: '即將下載 {0} 套用所有日誌,是否繼續? ',
|
downloadLogHelper1: '即將下載 {0} 套用所有日誌,是否繼續? ',
|
||||||
@@ -1719,6 +1722,8 @@ const message = {
|
|||||||
useCustom: '自訂 docker-compose.yml',
|
useCustom: '自訂 docker-compose.yml',
|
||||||
useCustomHelper: '使用自訂 docker-compose.yml 文件,可能會導致應用程式升級失敗,如無必要,請勿勾選',
|
useCustomHelper: '使用自訂 docker-compose.yml 文件,可能會導致應用程式升級失敗,如無必要,請勿勾選',
|
||||||
diffHelper: '左側為舊版本,右側為新版,編輯之後點選使用自訂版本儲存',
|
diffHelper: '左側為舊版本,右側為新版,編輯之後點選使用自訂版本儲存',
|
||||||
|
pullImage: '拉取鏡像',
|
||||||
|
pullImageHelper: '在應用啟動之前執行 docker pull 來拉取鏡像',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
website: '網站',
|
website: '網站',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const message = {
|
|||||||
delete: '删除',
|
delete: '删除',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
enable: '启用',
|
enable: '启用',
|
||||||
disable: '禁用',
|
disable: '停用',
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
@@ -68,6 +68,7 @@ const message = {
|
|||||||
createNewFolder: '新建文件夹',
|
createNewFolder: '新建文件夹',
|
||||||
createNewFile: '新建文件',
|
createNewFile: '新建文件',
|
||||||
helpDoc: '帮助文档',
|
helpDoc: '帮助文档',
|
||||||
|
unbind: '解绑',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
timeStart: '开始时间',
|
timeStart: '开始时间',
|
||||||
@@ -189,7 +190,7 @@ const message = {
|
|||||||
simpleName: '支持非下划线开头,英文、数字、_,长度3-30',
|
simpleName: '支持非下划线开头,英文、数字、_,长度3-30',
|
||||||
simplePassword: '支持非下划线开头,英文、数字、_,长度1-30',
|
simplePassword: '支持非下划线开头,英文、数字、_,长度1-30',
|
||||||
dbName: '支持非特殊字符开头,英文、中文、数字、.-_,长度1-64',
|
dbName: '支持非特殊字符开头,英文、中文、数字、.-_,长度1-64',
|
||||||
imageName: '支持英文、数字、:/.-_,长度1-150',
|
imageName: '支持英文、数字、:@/.-_,长度1-256',
|
||||||
volumeName: '支持英文、数字、.-和_,长度2-30',
|
volumeName: '支持英文、数字、.-和_,长度2-30',
|
||||||
supervisorName: '支持非特殊字符开头,英文、数字、-和_,长度1-128',
|
supervisorName: '支持非特殊字符开头,英文、数字、-和_,长度1-128',
|
||||||
complexityPassword: '请输入长度为 8-30 位且包含字母、数字、特殊字符至少两项的密码组合',
|
complexityPassword: '请输入长度为 8-30 位且包含字母、数字、特殊字符至少两项的密码组合',
|
||||||
@@ -336,6 +337,8 @@ const message = {
|
|||||||
kernelArch: '系统类型',
|
kernelArch: '系统类型',
|
||||||
network: '流量',
|
network: '流量',
|
||||||
io: '磁盘 IO',
|
io: '磁盘 IO',
|
||||||
|
ip: '主机地址',
|
||||||
|
proxy: '系统代理',
|
||||||
baseInfo: '基本信息',
|
baseInfo: '基本信息',
|
||||||
totalSend: '总发送',
|
totalSend: '总发送',
|
||||||
totalRecv: '总接收',
|
totalRecv: '总接收',
|
||||||
@@ -1388,7 +1391,7 @@ const message = {
|
|||||||
snapshot: '快照',
|
snapshot: '快照',
|
||||||
deleteHelper: '将删除该快照的所有备份文件,包括第三方备份账号中的文件。',
|
deleteHelper: '将删除该快照的所有备份文件,包括第三方备份账号中的文件。',
|
||||||
ignoreRule: '排除规则',
|
ignoreRule: '排除规则',
|
||||||
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,请谨慎修改。',
|
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,默认忽略套接字文件。',
|
||||||
ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache',
|
ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache',
|
||||||
status: '快照状态',
|
status: '快照状态',
|
||||||
panelInfo: '写入 1Panel 基础信息',
|
panelInfo: '写入 1Panel 基础信息',
|
||||||
@@ -1468,6 +1471,7 @@ const message = {
|
|||||||
allowIPsHelper1: '授权 IP 为空时,则取消授权 IP',
|
allowIPsHelper1: '授权 IP 为空时,则取消授权 IP',
|
||||||
allowIPEgs: '当存在多个授权 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24',
|
allowIPEgs: '当存在多个授权 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24',
|
||||||
mfa: '两步验证',
|
mfa: '两步验证',
|
||||||
|
mfaClose: '关闭两步验证将导致服务安全性降低,是否继续?',
|
||||||
secret: '密钥',
|
secret: '密钥',
|
||||||
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
||||||
mfaHelper: '开启后会验证手机应用验证码',
|
mfaHelper: '开启后会验证手机应用验证码',
|
||||||
@@ -1562,6 +1566,7 @@ const message = {
|
|||||||
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。',
|
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。',
|
||||||
setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。',
|
setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。',
|
||||||
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
|
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
|
||||||
|
alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。',
|
||||||
},
|
},
|
||||||
clean: {
|
clean: {
|
||||||
scan: '开始扫描',
|
scan: '开始扫描',
|
||||||
@@ -1701,7 +1706,7 @@ const message = {
|
|||||||
ignoreList: '忽略列表',
|
ignoreList: '忽略列表',
|
||||||
appHelper: '部分应用的安装使用说明请在应用详情页查看',
|
appHelper: '部分应用的安装使用说明请在应用详情页查看',
|
||||||
backupApp: '升级前备份应用',
|
backupApp: '升级前备份应用',
|
||||||
backupAppHelper: '升级失败会使用备份自动回滚,请在日志审计-系统日志中查看失败原因',
|
backupAppHelper: '升级失败会使用备份自动回滚,请在日志审计-系统日志中查看失败原因,备份默认保留最新的3份',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
openrestyDeleteHelper: '强制删除 OpenResty 会删除所有的网站,请确认风险之后操作',
|
openrestyDeleteHelper: '强制删除 OpenResty 会删除所有的网站,请确认风险之后操作',
|
||||||
downloadLogHelper1: '即将下载 {0} 应用所有日志,是否继续?',
|
downloadLogHelper1: '即将下载 {0} 应用所有日志,是否继续?',
|
||||||
@@ -1720,6 +1725,8 @@ const message = {
|
|||||||
useCustom: '自定义 docker-compose.yml',
|
useCustom: '自定义 docker-compose.yml',
|
||||||
useCustomHelper: '使用自定义 docker-compose.yml 文件,可能会导致应用升级失败,如无必要,请勿勾选',
|
useCustomHelper: '使用自定义 docker-compose.yml 文件,可能会导致应用升级失败,如无必要,请勿勾选',
|
||||||
diffHelper: '左侧为旧版本,右侧为新版,编辑之后点击使用自定义版本保存',
|
diffHelper: '左侧为旧版本,右侧为新版,编辑之后点击使用自定义版本保存',
|
||||||
|
pullImage: '拉取镜像',
|
||||||
|
pullImageHelper: '在应用启动之前执行 docker pull 来拉取镜像',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
website: '网站',
|
website: '网站',
|
||||||
|
|||||||
@@ -361,15 +361,16 @@ html {
|
|||||||
|
|
||||||
.el-input-group__append {
|
.el-input-group__append {
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
&:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
background-color: var(--el-color-primary-light-9) !important;
|
|
||||||
}
|
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
|
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
|
||||||
-1px 0 0 0 var(--el-input-border-color) inset;
|
-1px 0 0 0 var(--el-input-border-color) inset;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-button {
|
.tag-button {
|
||||||
|
|||||||
@@ -80,10 +80,6 @@ html.dark {
|
|||||||
border: 1px solid var(--panel-border-color);
|
border: 1px solid var(--panel-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// * wangEditor
|
|
||||||
--w-e-textarea-bg-color: #1b1b1b;
|
|
||||||
--w-e-textarea-color: #eeeeee;
|
|
||||||
|
|
||||||
.md-editor-dark {
|
.md-editor-dark {
|
||||||
--md-bk-color: #111417;
|
--md-bk-color: #111417;
|
||||||
}
|
}
|
||||||
@@ -251,14 +247,16 @@ html.dark {
|
|||||||
color: rgb(174, 166, 153);
|
color: rgb(174, 166, 153);
|
||||||
}
|
}
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
|
border: 1px solid var(--panel-border-color);
|
||||||
|
|
||||||
.el-dialog__header {
|
.el-dialog__header {
|
||||||
border-bottom: var(--panel-border);
|
border-bottom: var(--panel-border);
|
||||||
color: #999999;
|
color: #999999;
|
||||||
|
|
||||||
.el-dialog__title {
|
.el-dialog__title {
|
||||||
color: var(--el-menu-text-color);
|
color: var(--el-menu-text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
border: 1px solid var(--panel-border-color);
|
|
||||||
}
|
}
|
||||||
.el-tabs__item {
|
.el-tabs__item {
|
||||||
color: #999999;
|
color: #999999;
|
||||||
@@ -330,15 +328,16 @@ html.dark {
|
|||||||
|
|
||||||
.el-input-group__append {
|
.el-input-group__append {
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
&:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
background-color: var(--el-color-primary-light-9) !important;
|
|
||||||
}
|
|
||||||
background-color: #212426 !important;
|
background-color: #212426 !important;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
|
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
|
||||||
-1px 0 0 0 var(--el-input-border-color) inset;
|
-1px 0 0 0 var(--el-input-border-color) inset;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background-color: var(--el-color-primary-light-9) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-collapse-item__header {
|
.el-collapse-item__header {
|
||||||
|
|||||||
@@ -173,12 +173,13 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
.el-dialog__header {
|
.el-dialog__header {
|
||||||
.el-dialog__title {
|
.el-dialog__title {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
border-radius: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-box {
|
.row-box {
|
||||||
|
|||||||
@@ -10,3 +10,16 @@ body,
|
|||||||
:-webkit-any(article, aside, nav, section) h1 {
|
:-webkit-any(article, aside, nav, section) h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-switch--small .el-switch__core {
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-switch--small .el-switch__core::after {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-switch--small,.is-checked .el-switch__core::after {
|
||||||
|
margin-left: 12px !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,11 +56,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #rightButton>
|
<template #rightButton>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<div class="mr-10">
|
|
||||||
<el-checkbox v-model="req.resource" true-value="all" false-value="remote" @change="search(req)">
|
|
||||||
{{ $t('app.showLocal') }}
|
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
<fu-table-pagination
|
<fu-table-pagination
|
||||||
v-model:current-page="paginationConfig.currentPage"
|
v-model:current-page="paginationConfig.currentPage"
|
||||||
v-model:page-size="paginationConfig.pageSize"
|
v-model:page-size="paginationConfig.pageSize"
|
||||||
@@ -68,11 +63,11 @@
|
|||||||
@change="search(req)"
|
@change="search(req)"
|
||||||
:layout="mobile ? ' prev, pager, next' : ' prev, pager, next'"
|
:layout="mobile ? ' prev, pager, next' : ' prev, pager, next'"
|
||||||
/>
|
/>
|
||||||
<el-badge is-dot :hidden="!canUpdate" class="ml-5">
|
<div>
|
||||||
<el-button @click="sync" type="primary" plain :disabled="syncing">
|
<el-checkbox v-model="req.resource" true-value="all" false-value="remote" @change="search(req)">
|
||||||
{{ $t('app.syncAppList') }}
|
{{ $t('app.showLocal') }}
|
||||||
</el-button>
|
</el-checkbox>
|
||||||
</el-badge>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
@@ -168,12 +163,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import { onMounted, reactive, ref, computed } from 'vue';
|
import { onMounted, reactive, ref, computed } from 'vue';
|
||||||
import { GetAppTags, SearchApp, SyncApp } from '@/api/modules/app';
|
import { GetAppTags, SearchApp } from '@/api/modules/app';
|
||||||
import i18n from '@/lang';
|
|
||||||
import Detail from '../detail/index.vue';
|
import Detail from '../detail/index.vue';
|
||||||
import Install from '../detail/install/index.vue';
|
import Install from '../detail/install/index.vue';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
import { getLanguage } from '@/utils/util';
|
import { getLanguage } from '@/utils/util';
|
||||||
|
|
||||||
@@ -205,8 +198,6 @@ const tags = ref<App.Tag[]>([]);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const activeTag = ref('all');
|
const activeTag = ref('all');
|
||||||
const showDetail = ref(false);
|
const showDetail = ref(false);
|
||||||
const canUpdate = ref(false);
|
|
||||||
const syncing = ref(false);
|
|
||||||
const detailRef = ref();
|
const detailRef = ref();
|
||||||
const installRef = ref();
|
const installRef = ref();
|
||||||
const installKey = ref('');
|
const installKey = ref('');
|
||||||
@@ -256,23 +247,6 @@ const openDetail = (key: string) => {
|
|||||||
detailRef.value.acceptParams(key, 'install');
|
detailRef.value.acceptParams(key, 'install');
|
||||||
};
|
};
|
||||||
|
|
||||||
const sync = () => {
|
|
||||||
syncing.value = true;
|
|
||||||
SyncApp()
|
|
||||||
.then((res) => {
|
|
||||||
if (res.message != '') {
|
|
||||||
MsgSuccess(res.message);
|
|
||||||
} else {
|
|
||||||
MsgSuccess(i18n.global.t('app.syncStart'));
|
|
||||||
}
|
|
||||||
canUpdate.value = false;
|
|
||||||
search(req);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
syncing.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeTag = (key: string) => {
|
const changeTag = (key: string) => {
|
||||||
req.tags = [];
|
req.tags = [];
|
||||||
activeTag.value = key;
|
activeTag.value = key;
|
||||||
|
|||||||
@@ -147,6 +147,12 @@ const openInstall = () => {
|
|||||||
case 'node':
|
case 'node':
|
||||||
router.push({ path: '/websites/runtimes/node' });
|
router.push({ path: '/websites/runtimes/node' });
|
||||||
break;
|
break;
|
||||||
|
case 'java':
|
||||||
|
router.push({ path: '/websites/runtimes/java' });
|
||||||
|
break;
|
||||||
|
case 'go':
|
||||||
|
router.push({ path: '/websites/runtimes/go' });
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
const params = {
|
const params = {
|
||||||
app: app.value,
|
app: app.value,
|
||||||
|
|||||||
@@ -109,8 +109,8 @@
|
|||||||
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item pro="pullImage">
|
<el-form-item pro="pullImage">
|
||||||
<el-checkbox v-model="req.pullImage" :label="$t('container.forcePull')" size="large" />
|
<el-checkbox v-model="req.pullImage" :label="$t('app.pullImage')" size="large" />
|
||||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
<span class="input-help">{{ $t('app.pullImageHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="req.editCompose">
|
<div v-if="req.editCompose">
|
||||||
<codemirror
|
<codemirror
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="showButton">
|
<div v-if="showButton">
|
||||||
<RouterButton :buttons="buttons" />
|
<RouterButton :buttons="buttons">
|
||||||
|
<template #route-button>
|
||||||
|
<el-badge is-dot :hidden="!canUpdate" class="pr-5">
|
||||||
|
<el-button @click="sync" type="primary" plain :disabled="syncing">
|
||||||
|
{{ $t('app.syncAppList') }}
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
</template>
|
||||||
|
</RouterButton>
|
||||||
</div>
|
</div>
|
||||||
<LayoutContent>
|
<LayoutContent>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
@@ -12,9 +20,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { SearchAppInstalled } from '@/api/modules/app';
|
import { SearchAppInstalled, SyncApp } from '@/api/modules/app';
|
||||||
import bus from './bus';
|
import bus from './bus';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
let showButton = ref(false);
|
let showButton = ref(false);
|
||||||
|
const syncing = ref(false);
|
||||||
|
const canUpdate = ref(false);
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
label: i18n.global.t('app.all'),
|
label: i18n.global.t('app.all'),
|
||||||
@@ -31,6 +43,22 @@ const buttons = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const sync = () => {
|
||||||
|
syncing.value = true;
|
||||||
|
SyncApp()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.message != '') {
|
||||||
|
MsgSuccess(res.message);
|
||||||
|
} else {
|
||||||
|
MsgSuccess(i18n.global.t('app.syncStart'));
|
||||||
|
}
|
||||||
|
canUpdate.value = false;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
syncing.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const search = () => {
|
const search = () => {
|
||||||
SearchAppInstalled({ update: true, page: 1, pageSize: 100 })
|
SearchAppInstalled({ update: true, page: 1, pageSize: 100 })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
v-model="open"
|
|
||||||
:title="$t('commons.button.backup') + ' - ' + appInstallName"
|
|
||||||
width="40%"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:before-close="handleClose"
|
|
||||||
>
|
|
||||||
<el-form ref="backupForm" label-position="left" v-loading="loading">
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('setting.compressPassword')"
|
|
||||||
style="margin-top: 10px"
|
|
||||||
v-if="backupReq.type === 'app' || backupReq.type === 'website'"
|
|
||||||
>
|
|
||||||
<el-input v-model="backupReq.secret" :placeholder="$t('setting.backupRecoverMessage')" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="handleClose" :disabled="loading">
|
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="submit" :disabled="loading">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { FormInstance } from 'element-plus';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { handleBackup } from '@/api/modules/setting';
|
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
|
|
||||||
let appInstallName = ref('');
|
|
||||||
let loading = ref(false);
|
|
||||||
let open = ref(false);
|
|
||||||
const backupForm = ref<FormInstance>();
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
|
||||||
|
|
||||||
interface DialogProps {
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
detailName: string;
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let backupReq = ref({
|
|
||||||
type: '',
|
|
||||||
name: '',
|
|
||||||
detailName: '',
|
|
||||||
secret: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
open.value = false;
|
|
||||||
};
|
|
||||||
const acceptParams = (params: DialogProps): void => {
|
|
||||||
appInstallName.value = params.name;
|
|
||||||
backupReq.value = {
|
|
||||||
type: params.type,
|
|
||||||
name: params.name,
|
|
||||||
detailName: params.detailName,
|
|
||||||
secret: '',
|
|
||||||
};
|
|
||||||
open.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async () => {
|
|
||||||
let params = {
|
|
||||||
type: backupReq.value.type,
|
|
||||||
name: backupReq.value.name,
|
|
||||||
detailName: backupReq.value.detailName,
|
|
||||||
secret: backupReq.value.secret,
|
|
||||||
};
|
|
||||||
loading.value = true;
|
|
||||||
await handleBackup(params)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
handleClose();
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
emit('search');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@@ -23,7 +23,11 @@
|
|||||||
<el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" />
|
<el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" />
|
||||||
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
|
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
|
||||||
<div v-for="(p, index) in params" :key="index">
|
<div v-for="(p, index) in params" :key="index">
|
||||||
<el-form-item :prop="p.key" :label="getLabel(p)">
|
<el-form-item
|
||||||
|
:prop="p.key"
|
||||||
|
:label="getLabel(p)"
|
||||||
|
v-if="p.showValue == undefined || p.showValue == ''"
|
||||||
|
>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="p.type == 'number'"
|
v-if="p.type == 'number'"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -45,6 +49,9 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item :prop="p.key" :label="getLabel(p)" v-else>
|
||||||
|
<el-input v-model.trim="p.showValue" :disabled="!p.edit"></el-input>
|
||||||
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<el-form-item prop="advanced">
|
<el-form-item prop="advanced">
|
||||||
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
|
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
v-model="open"
|
|
||||||
:title="$t('commons.button.recover') + ' - ' + appInstallName"
|
|
||||||
width="40%"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:before-close="handleClose"
|
|
||||||
>
|
|
||||||
<el-form ref="recoverForm" label-position="left" v-loading="loading">
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('setting.compressPassword')"
|
|
||||||
style="margin-top: 10px"
|
|
||||||
v-if="recoverReq.type === 'app' || recoverReq.type === 'website'"
|
|
||||||
>
|
|
||||||
<el-input v-model="recoverReq.secret" :placeholder="$t('setting.backupRecoverMessage')" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="handleClose" :disabled="loading">
|
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="submit" :disabled="loading">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { FormInstance } from 'element-plus';
|
|
||||||
import i18n from '@/lang';
|
|
||||||
import { MsgSuccess } from '@/utils/message';
|
|
||||||
import { handleRecover, handleRecoverByUpload } from '@/api/modules/setting';
|
|
||||||
|
|
||||||
let appInstallName = ref('');
|
|
||||||
let loading = ref(false);
|
|
||||||
let open = ref(false);
|
|
||||||
const recoverForm = ref<FormInstance>();
|
|
||||||
|
|
||||||
interface DialogProps {
|
|
||||||
source: string;
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
detailName: string;
|
|
||||||
file: string;
|
|
||||||
recoverType: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let recoverReq = ref({
|
|
||||||
source: '',
|
|
||||||
type: '',
|
|
||||||
name: '',
|
|
||||||
detailName: '',
|
|
||||||
file: '',
|
|
||||||
secret: '',
|
|
||||||
recoverType: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
open.value = false;
|
|
||||||
};
|
|
||||||
const acceptParams = (params: DialogProps): void => {
|
|
||||||
appInstallName.value = params.name;
|
|
||||||
recoverReq.value = {
|
|
||||||
source: params.source,
|
|
||||||
type: params.type,
|
|
||||||
name: params.name,
|
|
||||||
detailName: params.detailName,
|
|
||||||
file: params.file,
|
|
||||||
secret: '',
|
|
||||||
recoverType: params.recoverType,
|
|
||||||
};
|
|
||||||
open.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async () => {
|
|
||||||
let params = {
|
|
||||||
source: recoverReq.value.source,
|
|
||||||
type: recoverReq.value.type,
|
|
||||||
name: recoverReq.value.name,
|
|
||||||
detailName: recoverReq.value.detailName,
|
|
||||||
file: recoverReq.value.file,
|
|
||||||
secret: recoverReq.value.secret,
|
|
||||||
};
|
|
||||||
loading.value = true;
|
|
||||||
if (recoverReq.value.recoverType === 'upload') {
|
|
||||||
await handleRecoverByUpload(params)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
handleClose();
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await handleRecover(params)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
handleClose();
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
|
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
|
||||||
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" />
|
<el-checkbox v-model="operateReq.pullImage" :label="$t('app.pullImage')" size="large" />
|
||||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
<span class="input-help">{{ $t('app.pullImageHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|||||||
@@ -201,7 +201,11 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Command" prop="cmdStr">
|
<el-form-item label="Command" prop="cmdStr">
|
||||||
<el-input v-model="dialogData.rowData!.cmdStr" :placeholder="$t('container.cmdHelper')" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="dialogData.rowData!.cmdStr"
|
||||||
|
:placeholder="$t('container.cmdHelper')"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Entrypoint" prop="entrypointStr">
|
<el-form-item label="Entrypoint" prop="entrypointStr">
|
||||||
<el-input
|
<el-input
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
icon="VideoPlay"
|
icon="VideoPlay"
|
||||||
type="success"
|
type="success"
|
||||||
>
|
>
|
||||||
{{ $t('commons.status.enabled') }}
|
{{ $t('commons.button.enable') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-else
|
v-else
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
type="danger"
|
type="danger"
|
||||||
@click="onChangeStatus(row.id, 'enable')"
|
@click="onChangeStatus(row.id, 'enable')"
|
||||||
>
|
>
|
||||||
{{ $t('commons.status.disabled') }}
|
{{ $t('commons.button.disable') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|||||||
@@ -186,9 +186,9 @@
|
|||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'"
|
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'"
|
||||||
:label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('website.website')"
|
:label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('website.website')"
|
||||||
prop="website"
|
prop="websiteList"
|
||||||
>
|
>
|
||||||
<el-select class="selectClass" v-model="dialogData.rowData!.website">
|
<el-select class="selectClass" multiple v-model="dialogData.rowData!.websiteList">
|
||||||
<el-option
|
<el-option
|
||||||
:disabled="websiteOptions.length === 0"
|
:disabled="websiteOptions.length === 0"
|
||||||
:label="$t('commons.table.all')"
|
:label="$t('commons.table.all')"
|
||||||
@@ -212,8 +212,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div v-if="dialogData.rowData!.type === 'app'">
|
<div v-if="dialogData.rowData!.type === 'app'">
|
||||||
<el-form-item :label="$t('cronjob.app')" prop="appID">
|
<el-form-item :label="$t('cronjob.app')" prop="appIdList">
|
||||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.appID">
|
<el-select class="selectClass" multiple clearable v-model="dialogData.rowData!.appIdList">
|
||||||
<el-option
|
<el-option
|
||||||
:disabled="appOptions.length === 0"
|
:disabled="appOptions.length === 0"
|
||||||
:label="$t('commons.table.all')"
|
:label="$t('commons.table.all')"
|
||||||
@@ -239,8 +239,8 @@
|
|||||||
<el-radio value="postgresql">PostgreSQL</el-radio>
|
<el-radio value="postgresql">PostgreSQL</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
<el-form-item :label="$t('cronjob.database')" prop="dbNameList">
|
||||||
<el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
|
<el-select class="selectClass" multiple clearable v-model="dialogData.rowData!.dbNameList">
|
||||||
<el-option
|
<el-option
|
||||||
:disabled="dbInfo.dbs.length === 0"
|
:disabled="dbInfo.dbs.length === 0"
|
||||||
:label="$t('commons.table.all')"
|
:label="$t('commons.table.all')"
|
||||||
@@ -378,6 +378,7 @@ import { listContainer } from '@/api/modules/container';
|
|||||||
import { Database } from '@/api/interface/database';
|
import { Database } from '@/api/interface/database';
|
||||||
import { ListAppInstalled } from '@/api/modules/app';
|
import { ListAppInstalled } from '@/api/modules/app';
|
||||||
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
|
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
@@ -385,6 +386,7 @@ interface DialogProps {
|
|||||||
rowData?: Cronjob.CronjobInfo;
|
rowData?: Cronjob.CronjobInfo;
|
||||||
getTableList?: () => Promise<any>;
|
getTableList?: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = ref<string>('');
|
const title = ref<string>('');
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
const dialogData = ref<DialogProps>({
|
const dialogData = ref<DialogProps>({
|
||||||
@@ -408,6 +410,15 @@ const acceptParams = (params: DialogProps): void => {
|
|||||||
if (dialogData.value.rowData.backupAccounts) {
|
if (dialogData.value.rowData.backupAccounts) {
|
||||||
dialogData.value.rowData.backupAccountList = dialogData.value.rowData.backupAccounts.split(',');
|
dialogData.value.rowData.backupAccountList = dialogData.value.rowData.backupAccounts.split(',');
|
||||||
}
|
}
|
||||||
|
if (dialogData.value.rowData.appID) {
|
||||||
|
dialogData.value.rowData.appIdList = dialogData.value.rowData.appID.split(',');
|
||||||
|
}
|
||||||
|
if (dialogData.value.rowData.website) {
|
||||||
|
dialogData.value.rowData.websiteList = dialogData.value.rowData.website.split(',');
|
||||||
|
}
|
||||||
|
if (dialogData.value.rowData.dbName) {
|
||||||
|
dialogData.value.rowData.dbNameList = dialogData.value.rowData.dbName.split(',');
|
||||||
|
}
|
||||||
dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
|
dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
|
||||||
dialogData.value.rowData!.isCustom =
|
dialogData.value.rowData!.isCustom =
|
||||||
dialogData.value.rowData!.command !== 'sh' &&
|
dialogData.value.rowData!.command !== 'sh' &&
|
||||||
@@ -554,8 +565,10 @@ const rules = reactive({
|
|||||||
],
|
],
|
||||||
|
|
||||||
script: [Rules.requiredInput],
|
script: [Rules.requiredInput],
|
||||||
website: [Rules.requiredSelect],
|
websiteList: [Rules.requiredSelect],
|
||||||
dbName: [Rules.requiredSelect],
|
appIdList: [Rules.requiredSelect],
|
||||||
|
dbNameList: [Rules.requiredSelect],
|
||||||
|
backupAccountList: [Rules.requiredSelect],
|
||||||
url: [Rules.requiredInput],
|
url: [Rules.requiredInput],
|
||||||
sourceDir: [Rules.requiredInput],
|
sourceDir: [Rules.requiredInput],
|
||||||
backupAccounts: [Rules.requiredSelect],
|
backupAccounts: [Rules.requiredSelect],
|
||||||
@@ -718,6 +731,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
|||||||
specs.push(itemSpec);
|
specs.push(itemSpec);
|
||||||
}
|
}
|
||||||
dialogData.value.rowData.backupAccounts = dialogData.value.rowData.backupAccountList.join(',');
|
dialogData.value.rowData.backupAccounts = dialogData.value.rowData.backupAccountList.join(',');
|
||||||
|
|
||||||
|
if (dialogData.value.rowData.appIdList) {
|
||||||
|
dialogData.value.rowData.appID = dialogData.value.rowData.appIdList.join(',');
|
||||||
|
}
|
||||||
|
if (dialogData.value.rowData.websiteList) {
|
||||||
|
dialogData.value.rowData.website = dialogData.value.rowData.websiteList.join(',');
|
||||||
|
}
|
||||||
|
if (dialogData.value.rowData.dbNameList) {
|
||||||
|
dialogData.value.rowData.dbName = dialogData.value.rowData.dbNameList.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
dialogData.value.rowData.spec = specs.join(',');
|
dialogData.value.rowData.spec = specs.join(',');
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
@@ -750,40 +774,49 @@ defineExpose({
|
|||||||
.specClass {
|
.specClass {
|
||||||
width: 20% !important;
|
width: 20% !important;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
|
|
||||||
.append {
|
.append {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1000px) {
|
@media only screen and (max-width: 1000px) {
|
||||||
.specClass {
|
.specClass {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
||||||
.append {
|
.append {
|
||||||
width: 43px;
|
width: 43px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.specTypeClass {
|
.specTypeClass {
|
||||||
width: 22% !important;
|
width: 22% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1000px) {
|
@media only screen and (max-width: 1000px) {
|
||||||
.specTypeClass {
|
.specTypeClass {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectClass {
|
.selectClass {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagClass {
|
.tagClass {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logText {
|
.logText {
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const submit = async () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
emit('search');
|
emit('search');
|
||||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const buttons = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('commons.button.delete'),
|
label: i18n.global.t('commons.button.unbind'),
|
||||||
click: (row: Database.DatabaseInfo) => {
|
click: (row: Database.DatabaseInfo) => {
|
||||||
onDelete(row);
|
onDelete(row);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
<CopyButton :content="loadPgInfo(true)" type="icon" />
|
<CopyButton :content="loadPgInfo(true)" type="icon" />
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item :label="$t('database.connPort')">
|
<el-descriptions-item :label="$t('database.connPort')">
|
||||||
{{ form.port }}
|
5432
|
||||||
<CopyButton :content="form.port + ''" type="icon" />
|
<CopyButton content="5432" type="icon" />
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const submit = async () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
emit('search');
|
emit('search');
|
||||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user