mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdf9046430 | ||
|
|
b3852db39e | ||
|
|
2f7bb6d784 | ||
|
|
e5d3c1bb86 | ||
|
|
c758c1ce87 | ||
|
|
d82086ce4a | ||
|
|
edc3540e67 | ||
|
|
13a69fa1ed | ||
|
|
948653213e | ||
|
|
a4cfb587d6 | ||
|
|
6aa9a95971 | ||
|
|
e3666d17a3 | ||
|
|
7219566273 | ||
|
|
08a45e9723 | ||
|
|
be53945f0e | ||
|
|
725ad3f6d9 | ||
|
|
42828d0a48 | ||
|
|
3804942ae4 | ||
|
|
83dda25591 | ||
|
|
f91d7c6170 | ||
|
|
2cf3b27402 | ||
|
|
0e4f82c434 | ||
|
|
51d7a84062 | ||
|
|
70863f411d | ||
|
|
5c9315ff1c | ||
|
|
3846dcef86 | ||
|
|
8b0d73b5d5 | ||
|
|
e309b9c030 | ||
|
|
a5304b66e3 | ||
|
|
10eef260d4 | ||
|
|
045dbaef91 | ||
|
|
7a45db71d5 | ||
|
|
0ee942cbef | ||
|
|
aa8429a836 | ||
|
|
9f2227cf19 | ||
|
|
338ade095a | ||
|
|
d5432c8e59 | ||
|
|
4eb34f24d1 | ||
|
|
527c8143a1 | ||
|
|
a6891711b7 | ||
|
|
36a253ce03 | ||
|
|
bbe1161dc2 | ||
|
|
e85ed7427a | ||
|
|
97f8a872c0 | ||
|
|
48187b508d | ||
|
|
ca1710cabc | ||
|
|
df2a705713 | ||
|
|
d917008f47 | ||
|
|
61078a24fd | ||
|
|
e35998a4be | ||
|
|
d1fde34a14 | ||
|
|
45394598d9 | ||
|
|
edd6b52f05 | ||
|
|
0f53de56a7 | ||
|
|
4ce50328ba | ||
|
|
115b9c58b0 | ||
|
|
64def5bebd | ||
|
|
e46f931989 | ||
|
|
1a78aecfb1 | ||
|
|
e8c94450f4 | ||
|
|
6bc4cfc8ac | ||
|
|
24579edad6 | ||
|
|
6499d7f462 | ||
|
|
1e5cd23bbe | ||
|
|
fce0ac2c7d | ||
|
|
f5bf111203 | ||
|
|
5152e7b8d5 | ||
|
|
d30be34def | ||
|
|
1a9182bfde | ||
|
|
aeb0549a9a | ||
|
|
64f3bbb13b | ||
|
|
bc0fbc4461 | ||
|
|
1e5ea024ad | ||
|
|
f89365106a | ||
|
|
4891de0a8a | ||
|
|
be6b58b0a1 | ||
|
|
33910f7993 | ||
|
|
7036ba0d5c | ||
|
|
204a9fac87 | ||
|
|
6879b92db3 | ||
|
|
54242ae220 | ||
|
|
6f0f282b7e | ||
|
|
817fd123da |
@@ -49,6 +49,17 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Refresh OneDrive token
|
||||
// @Description 刷新 OneDrive token
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/refresh/onedrive [post]
|
||||
func (b *BaseApi) RefreshOneDriveToken(c *gin.Context) {
|
||||
backupService.Run()
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary List buckets
|
||||
// @Description 获取 bucket 列表
|
||||
@@ -91,16 +102,16 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||
// @Summary Load OneDrive info
|
||||
// @Description 获取 OneDrive 信息
|
||||
// @Accept json
|
||||
// @Success 200 string clientID
|
||||
// @Success 200 {object} dto.OneDriveInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/onedrive [get]
|
||||
func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
|
||||
clientID, err := backupService.LoadOneDriveInfo()
|
||||
data, err := backupService.LoadOneDriveInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, clientID)
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
@@ -151,6 +162,32 @@ func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Page backup records by cronjob
|
||||
// @Description 通过计划任务获取备份记录列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.RecordSearchByCronjob true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/record/search/bycronjob [post]
|
||||
func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
|
||||
var req dto.RecordSearchByCronjob
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := backupService.SearchRecordsByCronjobWithPage(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Download backup record
|
||||
// @Description 下载备份记录
|
||||
@@ -265,12 +302,7 @@ func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := backupService.ListFiles(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
data := backupService.ListFiles(req)
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
@@ -281,7 +313,7 @@ func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
||||
// @Param request body dto.CommonBackup true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/ [post]
|
||||
// @Router /settings/backup/backup [post]
|
||||
// @x-panel-log {"bodyKeys":["type","name","detailName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"备份 [type] 数据 [name][detailName]","formatEN":"backup [type] data [name][detailName]"}
|
||||
func (b *BaseApi) Backup(c *gin.Context) {
|
||||
var req dto.CommonBackup
|
||||
@@ -334,14 +366,12 @@ func (b *BaseApi) Recover(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Source != "LOCAL" {
|
||||
downloadPath, err := backupService.DownloadRecord(dto.DownloadRecord{Source: req.Source, FileDir: path.Dir(req.File), FileName: path.Base(req.File)})
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("download file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
req.File = downloadPath
|
||||
downloadPath, err := backupService.DownloadRecord(dto.DownloadRecord{Source: req.Source, FileDir: path.Dir(req.File), FileName: path.Base(req.File)})
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("download file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
req.File = downloadPath
|
||||
switch req.Type {
|
||||
case "mysql", "mariadb":
|
||||
if err := backupService.MysqlRecover(req); err != nil {
|
||||
|
||||
@@ -95,7 +95,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /cronjob/record/log [post]
|
||||
// @Router /cronjobs/records/log [post]
|
||||
func (b *BaseApi) LoadRecordLog(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -422,11 +422,9 @@ func (b *BaseApi) MoveFile(c *gin.Context) {
|
||||
// @Summary Download file
|
||||
// @Description 下载文件
|
||||
// @Accept json
|
||||
// @Param request body request.FileDownload true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/download [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"}
|
||||
// @Router /files/download [get]
|
||||
func (b *BaseApi) Download(c *gin.Context) {
|
||||
filePath := c.Query("path")
|
||||
file, err := os.Open(filePath)
|
||||
@@ -689,7 +687,7 @@ func (b *BaseApi) Keys(c *gin.Context) {
|
||||
// @Param request body request.FileReadByLineReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/log/read [post]
|
||||
// @Router /files/read [post]
|
||||
func (b *BaseApi) ReadFileByLine(c *gin.Context) {
|
||||
var req request.FileReadByLineReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Description 获取 OpenResty 配置信息
|
||||
// @Success 200 {object} response.FileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty [get]
|
||||
// @Router /openresty [get]
|
||||
func (b *BaseApi) GetNginx(c *gin.Context) {
|
||||
fileInfo, err := nginxService.GetNginxConfig()
|
||||
if err != nil {
|
||||
@@ -29,7 +29,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) {
|
||||
// @Param request body request.NginxScopeReq true "request"
|
||||
// @Success 200 {array} response.NginxParam
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty/scope [post]
|
||||
// @Router /openresty/scope [post]
|
||||
func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
|
||||
var req request.NginxScopeReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -51,7 +51,7 @@ func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
|
||||
// @Param request body request.NginxConfigUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty/update [post]
|
||||
// @Router /openresty/update [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新 nginx 配置 [domain]","formatEN":"Update nginx conf [domain]"}
|
||||
func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) {
|
||||
var req request.NginxConfigUpdate
|
||||
@@ -70,7 +70,7 @@ func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) {
|
||||
// @Description 获取 OpenResty 状态信息
|
||||
// @Success 200 {object} response.NginxStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty/status [get]
|
||||
// @Router /openresty/status [get]
|
||||
func (b *BaseApi) GetNginxStatus(c *gin.Context) {
|
||||
res, err := nginxService.GetStatus()
|
||||
if err != nil {
|
||||
@@ -87,7 +87,7 @@ func (b *BaseApi) GetNginxStatus(c *gin.Context) {
|
||||
// @Param request body request.NginxConfigFileUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty/file [post]
|
||||
// @Router /openresty/file [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 nginx 配置","formatEN":"Update nginx conf"}
|
||||
func (b *BaseApi) UpdateNginxFile(c *gin.Context) {
|
||||
var req request.NginxConfigFileUpdate
|
||||
@@ -107,7 +107,7 @@ func (b *BaseApi) UpdateNginxFile(c *gin.Context) {
|
||||
// @Description 清理 OpenResty 代理缓存
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty/clear [post]
|
||||
// @Router /openresty/clear [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理 Openresty 代理缓存","formatEN":"Clear nginx proxy cache"}
|
||||
func (b *BaseApi) ClearNginxProxyCache(c *gin.Context) {
|
||||
if err := nginxService.ClearProxyCache(); err != nil {
|
||||
|
||||
@@ -55,7 +55,6 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
@@ -106,7 +105,7 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
defer killBash(redisConf.ContainerName, commands, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
@@ -174,7 +173,7 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
defer killBash(containerID, command, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ type BackupInfo struct {
|
||||
Vars string `json:"vars"`
|
||||
}
|
||||
|
||||
type OneDriveInfo struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectUri string `json:"redirect_uri"`
|
||||
}
|
||||
|
||||
type BackupSearchFile struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
@@ -45,6 +51,11 @@ type RecordSearch struct {
|
||||
DetailName string `json:"detailName"`
|
||||
}
|
||||
|
||||
type RecordSearchByCronjob struct {
|
||||
PageInfo
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupRecords struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
@@ -2,6 +2,8 @@ package dto
|
||||
|
||||
type SearchCommandWithPage struct {
|
||||
SearchWithPage
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
GroupID uint `json:"groupID"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
+31
-44
@@ -3,14 +3,9 @@ package dto
|
||||
import "time"
|
||||
|
||||
type CronjobCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
SpecType string `json:"specType" validate:"required"`
|
||||
Week int `json:"week" validate:"number,max=6,min=0"`
|
||||
Day int `json:"day" validate:"number"`
|
||||
Hour int `json:"hour" validate:"number"`
|
||||
Minute int `json:"minute" validate:"number"`
|
||||
Second int `json:"second" validate:"number"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
@@ -21,20 +16,16 @@ type CronjobCreate struct {
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
KeepLocal bool `json:"keepLocal"`
|
||||
TargetDirID int `json:"targetDirID"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
|
||||
BackupAccounts string `json:"backupAccounts"`
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
}
|
||||
|
||||
type CronjobUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
SpecType string `json:"specType" validate:"required"`
|
||||
Week int `json:"week" validate:"number,max=6,min=0"`
|
||||
Day int `json:"day" validate:"number"`
|
||||
Hour int `json:"hour" validate:"number"`
|
||||
Minute int `json:"minute" validate:"number"`
|
||||
Second int `json:"second" validate:"number"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
@@ -45,9 +36,10 @@ type CronjobUpdate struct {
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
KeepLocal bool `json:"keepLocal"`
|
||||
TargetDirID int `json:"targetDirID"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
|
||||
BackupAccounts string `json:"backupAccounts"`
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
|
||||
}
|
||||
|
||||
type CronjobUpdateStatus struct {
|
||||
@@ -61,6 +53,7 @@ type CronjobDownload struct {
|
||||
}
|
||||
|
||||
type CronjobClean struct {
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
}
|
||||
@@ -71,29 +64,23 @@ type CronjobBatchDelete struct {
|
||||
}
|
||||
|
||||
type CronjobInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
SpecType string `json:"specType"`
|
||||
Week int `json:"week"`
|
||||
Day int `json:"day"`
|
||||
Hour int `json:"hour"`
|
||||
Minute int `json:"minute"`
|
||||
Second int `json:"second"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Spec string `json:"spec"`
|
||||
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
KeepLocal bool `json:"keepLocal"`
|
||||
TargetDir string `json:"targetDir"`
|
||||
TargetDirID int `json:"targetDirID"`
|
||||
RetainCopies int `json:"retainCopies"`
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBType string `json:"dbType"`
|
||||
DBName string `json:"dbName"`
|
||||
URL string `json:"url"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
BackupAccounts string `json:"backupAccounts"`
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
RetainCopies int `json:"retainCopies"`
|
||||
|
||||
LastRecordTime string `json:"lastRecordTime"`
|
||||
Status string `json:"status"`
|
||||
|
||||
@@ -32,6 +32,7 @@ type AppContainerConfig struct {
|
||||
EditCompose bool `json:"editCompose"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
HostMode bool `json:"hostMode"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
}
|
||||
|
||||
type AppInstalledSearch struct {
|
||||
@@ -67,6 +68,7 @@ type AppInstalledOperate struct {
|
||||
DeleteBackup bool `json:"deleteBackup"`
|
||||
DeleteDB bool `json:"deleteDB"`
|
||||
Backup bool `json:"backup"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
)
|
||||
@@ -72,10 +73,11 @@ type AppInstalledDTO struct {
|
||||
}
|
||||
|
||||
type DatabaseConn struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Port int64 `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
ContainerName string `json:"containerName"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Port int64 `json:"port"`
|
||||
}
|
||||
|
||||
type AppService struct {
|
||||
|
||||
+13
-11
@@ -99,9 +99,10 @@ type SnapshotStatus struct {
|
||||
}
|
||||
|
||||
type SnapshotCreate struct {
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive WebDAV"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from" validate:"required"`
|
||||
DefaultDownload string `json:"defaultDownload" validate:"required"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
}
|
||||
type SnapshotRecover struct {
|
||||
IsNew bool `json:"isNew"`
|
||||
@@ -114,14 +115,15 @@ type SnapshotImport struct {
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
}
|
||||
type SnapshotInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
From string `json:"from"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Version string `json:"version"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
From string `json:"from"`
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Version string `json:"version"`
|
||||
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
RecoverStatus string `json:"recoverStatus"`
|
||||
|
||||
@@ -12,6 +12,8 @@ type BackupAccount struct {
|
||||
|
||||
type BackupRecord struct {
|
||||
BaseModel
|
||||
From string `gorm:"type:varchar(64)" json:"from"`
|
||||
CronjobID uint `gorm:"type:decimal" json:"cronjobID"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
DetailName string `gorm:"type:varchar(256)" json:"detailName"`
|
||||
|
||||
@@ -5,15 +5,9 @@ import "time"
|
||||
type Cronjob struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"type:varchar(64);not null;unique" json:"name"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
SpecType string `gorm:"type:varchar(64);not null" json:"specType"`
|
||||
Spec string `gorm:"type:varchar(64);not null" json:"spec"`
|
||||
Week uint64 `gorm:"type:decimal" json:"week"`
|
||||
Day uint64 `gorm:"type:decimal" json:"day"`
|
||||
Hour uint64 `gorm:"type:decimal" json:"hour"`
|
||||
Minute uint64 `gorm:"type:decimal" json:"minute"`
|
||||
Second uint64 `gorm:"type:decimal" json:"second"`
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Spec string `gorm:"type:varchar(64);not null" json:"spec"`
|
||||
|
||||
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
|
||||
Script string `gorm:"longtext" json:"script"`
|
||||
@@ -25,13 +19,17 @@ type Cronjob struct {
|
||||
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
|
||||
ExclusionRules string `gorm:"longtext" json:"exclusionRules"`
|
||||
|
||||
KeepLocal bool `gorm:"type:varchar(64)" json:"keepLocal"`
|
||||
TargetDirID uint64 `gorm:"type:decimal" json:"targetDirID"`
|
||||
RetainCopies uint64 `gorm:"type:decimal" json:"retainCopies"`
|
||||
// 已废弃
|
||||
KeepLocal bool `gorm:"type:varchar(64)" json:"keepLocal"`
|
||||
TargetDirID uint64 `gorm:"type:decimal" json:"targetDirID"`
|
||||
|
||||
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||
EntryID uint64 `gorm:"type:decimal" json:"entryID"`
|
||||
Records []JobRecords `json:"records"`
|
||||
BackupAccounts string `gorm:"type:varchar(64)" json:"backupAccounts"`
|
||||
DefaultDownload string `gorm:"type:varchar(64)" json:"defaultDownload"`
|
||||
RetainCopies uint64 `gorm:"type:decimal" json:"retainCopies"`
|
||||
|
||||
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||
EntryIDs string `gorm:"type:varchar(64)" json:"entryIDs"`
|
||||
Records []JobRecords `json:"records"`
|
||||
}
|
||||
|
||||
type JobRecords struct {
|
||||
|
||||
@@ -2,12 +2,13 @@ package model
|
||||
|
||||
type Snapshot struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null;unique"`
|
||||
Description string `json:"description" gorm:"type:varchar(256)"`
|
||||
From string `json:"from"`
|
||||
Status string `json:"status" gorm:"type:varchar(64)"`
|
||||
Message string `json:"message" gorm:"type:varchar(256)"`
|
||||
Version string `json:"version" gorm:"type:varchar(256)"`
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null;unique"`
|
||||
Description string `json:"description" gorm:"type:varchar(256)"`
|
||||
From string `json:"from"`
|
||||
DefaultDownload string `json:"defaultDownload" gorm:"type:varchar(64)"`
|
||||
Status string `json:"status" gorm:"type:varchar(64)"`
|
||||
Message string `json:"message" gorm:"type:varchar(256)"`
|
||||
Version string `json:"version" gorm:"type:varchar(256)"`
|
||||
|
||||
InterruptStep string `json:"interruptStep" gorm:"type:varchar(64)"`
|
||||
RecoverStatus string `json:"recoverStatus" gorm:"type:varchar(64)"`
|
||||
|
||||
@@ -2,6 +2,7 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"gorm.io/gorm"
|
||||
@@ -23,6 +24,7 @@ type IBackupRepo interface {
|
||||
WithByDetailName(detailName string) DBOption
|
||||
WithByFileName(fileName string) DBOption
|
||||
WithByType(backupType string) DBOption
|
||||
WithByCronID(cronjobID uint) DBOption
|
||||
}
|
||||
|
||||
func NewIBackupRepo() IBackupRepo {
|
||||
@@ -125,3 +127,9 @@ func (u *BackupRepo) Delete(opts ...DBOption) error {
|
||||
func (u *BackupRepo) DeleteRecord(ctx context.Context, opts ...DBOption) error {
|
||||
return getTx(ctx, opts...).Delete(&model.BackupRecord{}).Error
|
||||
}
|
||||
|
||||
func (u *BackupRepo) WithByCronID(cronjobID uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("cronjob_id = ?", cronjobID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ type ICronjobRepo interface {
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error)
|
||||
Create(cronjob *model.Cronjob) error
|
||||
WithByJobID(id int) DBOption
|
||||
WithByBackupID(id uint) DBOption
|
||||
WithByDbName(name string) DBOption
|
||||
WithByDefaultDownload(account string) DBOption
|
||||
WithByRecordDropID(id int) DBOption
|
||||
WithByRecordFile(file string) DBOption
|
||||
Save(id uint, cronjob model.Cronjob) error
|
||||
@@ -28,6 +29,7 @@ type ICronjobRepo interface {
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords
|
||||
UpdateRecords(id uint, vars map[string]interface{}) error
|
||||
EndRecords(record model.JobRecords, status, message, records string)
|
||||
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
|
||||
}
|
||||
@@ -116,9 +118,15 @@ func (c *CronjobRepo) WithByJobID(id int) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CronjobRepo) WithByBackupID(id uint) DBOption {
|
||||
func (c *CronjobRepo) WithByDbName(name string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("target_dir_id = ?", id)
|
||||
return g.Where("db_name = ?", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CronjobRepo) WithByDefaultDownload(account string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("default_download = ?", account)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +172,10 @@ func (u *CronjobRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Cronjob{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *CronjobRepo) UpdateRecords(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.JobRecords{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *CronjobRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -459,7 +459,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
||||
if err = runScript(appInstall, "init"); err != nil {
|
||||
return
|
||||
}
|
||||
upApp(appInstall)
|
||||
upApp(appInstall, req.PullImage)
|
||||
}()
|
||||
go updateToolApp(appInstall)
|
||||
return
|
||||
|
||||
@@ -188,6 +188,7 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
|
||||
data.Password = app.Password
|
||||
data.ServiceName = app.ServiceName
|
||||
data.Port = app.Port
|
||||
data.ContainerName = app.ContainerName
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -262,7 +263,7 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
case constant.Sync:
|
||||
return syncByID(install.ID)
|
||||
case constant.Upgrade:
|
||||
return upgradeInstall(install.ID, req.DetailId, req.Backup)
|
||||
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
|
||||
case constant.Reload:
|
||||
return opNginx(install.ContainerName, constant.NginxReload)
|
||||
default:
|
||||
|
||||
@@ -433,12 +433,12 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
|
||||
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
}
|
||||
|
||||
func upgradeInstall(installId uint, detailId uint, backup bool) error {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
|
||||
func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailId))
|
||||
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -492,23 +492,6 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
|
||||
detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
|
||||
}
|
||||
|
||||
images, err := composeV2.GetDockerComposeImages([]byte(detail.DockerCompose))
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
dockerCli, err := composeV2.NewClient()
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
for _, image := range images {
|
||||
if err = dockerCli.PullImage(image, true); err != nil {
|
||||
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
|
||||
stdout, _ := command.CombinedOutput()
|
||||
if stdout != nil {
|
||||
@@ -573,7 +556,7 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
|
||||
|
||||
install.DockerCompose = string(composeByte)
|
||||
install.Version = detail.Version
|
||||
install.AppDetailId = detailId
|
||||
install.AppDetailId = detailID
|
||||
|
||||
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
||||
if out != "" {
|
||||
@@ -592,6 +575,31 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
|
||||
return
|
||||
}
|
||||
|
||||
content, err := fileOp.GetContent(install.GetEnvPath())
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
|
||||
if pullImage {
|
||||
images, err := composeV2.GetDockerComposeImages(install.Name, content, []byte(detail.DockerCompose))
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
dockerCli, err := composeV2.NewClient()
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
for _, image := range images {
|
||||
if err = dockerCli.PullImage(image, true); err != nil {
|
||||
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil {
|
||||
return
|
||||
}
|
||||
@@ -833,14 +841,14 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func upApp(appInstall *model.AppInstall) {
|
||||
func upApp(appInstall *model.AppInstall, pullImages bool) {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
if err == nil {
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if appInstall.App.Type != "php" {
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
@@ -855,6 +863,7 @@ func upApp(appInstall *model.AppInstall) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
|
||||
+183
-89
@@ -6,12 +6,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -29,7 +30,8 @@ type BackupService struct{}
|
||||
type IBackupService interface {
|
||||
List() ([]dto.BackupInfo, error)
|
||||
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
|
||||
LoadOneDriveInfo() (string, error)
|
||||
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
|
||||
LoadOneDriveInfo() (dto.OneDriveInfo, error)
|
||||
DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
Create(backupDto dto.BackupOperate) error
|
||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||
@@ -38,7 +40,7 @@ type IBackupService interface {
|
||||
BatchDeleteRecord(ids []uint) error
|
||||
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
||||
|
||||
ListFiles(req dto.BackupSearchFile) ([]string, error)
|
||||
ListFiles(req dto.BackupSearchFile) []string
|
||||
|
||||
MysqlBackup(db dto.CommonBackup) error
|
||||
PostgresqlBackup(db dto.CommonBackup) error
|
||||
@@ -55,6 +57,8 @@ type IBackupService interface {
|
||||
|
||||
AppBackup(db dto.CommonBackup) error
|
||||
AppRecover(req dto.CommonRecover) error
|
||||
|
||||
Run()
|
||||
}
|
||||
|
||||
func NewIBackupService() IBackupService {
|
||||
@@ -84,65 +88,72 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
|
||||
commonRepo.WithByType(search.Type),
|
||||
backupRepo.WithByDetailName(search.DetailName),
|
||||
)
|
||||
|
||||
var datas []dto.BackupRecords
|
||||
clientMap := make(map[string]loadSizeHelper)
|
||||
for i := 0; i < len(records); i++ {
|
||||
var item dto.BackupRecords
|
||||
if err := copier.Copy(&item, &records[i]); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
itemPath := path.Join(records[i].FileDir, records[i].FileName)
|
||||
if records[i].Source == "LOCAL" {
|
||||
fileInfo, err := os.Stat(itemPath)
|
||||
if err == nil {
|
||||
item.Size = fileInfo.Size()
|
||||
}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
if _, ok := clientMap[records[i].Source]; !ok {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
|
||||
return total, datas, err
|
||||
}
|
||||
client, err := u.NewClient(&backup)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
|
||||
return total, datas, err
|
||||
}
|
||||
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
|
||||
datas = append(datas, item)
|
||||
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client}
|
||||
continue
|
||||
}
|
||||
item.Size, _ = clientMap[records[i].Source].client.Size(path.Join(clientMap[records[i].Source].backupPath, itemPath))
|
||||
datas = append(datas, item)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
datas, err := u.loadRecordSize(records)
|
||||
sort.Slice(datas, func(i, j int) bool {
|
||||
return datas[i].CreatedAt.After(datas[j].CreatedAt)
|
||||
})
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
||||
total, records, err := backupRepo.PageRecord(
|
||||
search.Page, search.PageSize,
|
||||
commonRepo.WithOrderBy("created_at desc"),
|
||||
backupRepo.WithByCronID(search.CronjobID),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
datas, err := u.loadRecordSize(records)
|
||||
sort.Slice(datas, func(i, j int) bool {
|
||||
return datas[i].CreatedAt.After(datas[j].CreatedAt)
|
||||
})
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
type loadSizeHelper struct {
|
||||
isOk bool
|
||||
backupPath string
|
||||
client cloud_storage.CloudStorageClient
|
||||
}
|
||||
|
||||
func (u *BackupService) LoadOneDriveInfo() (string, error) {
|
||||
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
|
||||
func (u *BackupService) LoadOneDriveInfo() (dto.OneDriveInfo, error) {
|
||||
var data dto.OneDriveInfo
|
||||
data.RedirectUri = constant.OneDriveRedirectURI
|
||||
clientID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return data, err
|
||||
}
|
||||
idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value)
|
||||
idItem, err := base64.StdEncoding.DecodeString(clientID.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return data, err
|
||||
}
|
||||
return string(idItem), err
|
||||
data.ClientID = string(idItem)
|
||||
clientSecret, err := settingRepo.Get(settingRepo.WithByKey("OneDriveSc"))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
secretItem, err := base64.StdEncoding.DecodeString(clientSecret.Value)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.ClientSecret = string(secretItem)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
|
||||
if info.Source == "LOCAL" {
|
||||
return info.FileDir + "/" + info.FileName, nil
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Join(localDir, info.FileDir, info.FileName), nil
|
||||
}
|
||||
backup, _ := backupRepo.Get(commonRepo.WithByType(info.Source))
|
||||
if backup.ID == 0 {
|
||||
@@ -205,6 +216,9 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
}
|
||||
if backup.Type == constant.OneDrive {
|
||||
StartRefreshOneDriveToken()
|
||||
}
|
||||
if err := backupRepo.Create(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -232,7 +246,14 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
|
||||
}
|
||||
|
||||
func (u *BackupService) Delete(id uint) error {
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByBackupID(id))
|
||||
backup, _ := backupRepo.Get(commonRepo.WithByID(id))
|
||||
if backup.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
if backup.Type == constant.OneDrive {
|
||||
global.Cron.Remove(global.OneDriveCronID)
|
||||
}
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDefaultDownload(backup.Type))
|
||||
if len(cronjobs) != 0 {
|
||||
return buserr.New(constant.ErrBackupInUsed)
|
||||
}
|
||||
@@ -313,27 +334,29 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
if backup.Type == "LOCAL" {
|
||||
if dir, ok := varMap["dir"]; ok {
|
||||
if dirStr, isStr := dir.(string); isStr {
|
||||
if strings.HasSuffix(dirStr, "/") {
|
||||
if strings.HasSuffix(dirStr, "/") && dirStr != "/" {
|
||||
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
|
||||
}
|
||||
if err := copyDir(oldDir, dirStr); err != nil {
|
||||
_ = backupRepo.Update(req.ID, (map[string]interface{}{"vars": oldVars}))
|
||||
return err
|
||||
}
|
||||
global.CONF.System.Backup = dirStr
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
|
||||
func (u *BackupService) ListFiles(req dto.BackupSearchFile) []string {
|
||||
var datas []string
|
||||
backup, err := backupRepo.Get(backupRepo.WithByType(req.Type))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return datas
|
||||
}
|
||||
client, err := u.NewClient(&backup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return datas
|
||||
}
|
||||
prefix := "system_snapshot"
|
||||
if len(backup.BackupPath) != 0 {
|
||||
@@ -341,15 +364,15 @@ func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
|
||||
}
|
||||
files, err := client.ListObjects(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
global.LOG.Debugf("load files from %s failed, err: %v", req.Type, err)
|
||||
return datas
|
||||
}
|
||||
var datas []string
|
||||
for _, file := range files {
|
||||
if len(file) != 0 {
|
||||
datas = append(datas, path.Base(file))
|
||||
}
|
||||
}
|
||||
return datas, nil
|
||||
return datas
|
||||
}
|
||||
|
||||
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
|
||||
@@ -357,9 +380,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if backup.Type == "LOCAL" {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
varMap["bucket"] = backup.Bucket
|
||||
switch backup.Type {
|
||||
case constant.Sftp, constant.WebDAV:
|
||||
@@ -387,6 +407,15 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
|
||||
if err := copier.Copy(&item, &account); err != nil {
|
||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||
}
|
||||
if account.Type == constant.OneDrive {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
||||
return dto.BackupInfo{Type: accountType}
|
||||
}
|
||||
delete(varMap, "refresh_token")
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
item.Vars = string(itemVars)
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -398,48 +427,66 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
}
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("client_id", global.CONF.System.OneDriveID)
|
||||
data.Set("client_secret", global.CONF.System.OneDriveSc)
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", varMap["code"].(string))
|
||||
data.Set("redirect_uri", constant.OneDriveRedirectURI)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
token, refreshToken, err := client.RefreshToken("authorization_code", varMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request for access token failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
delete(varMap, "code")
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
token := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &token); err != nil {
|
||||
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
}
|
||||
accessToken, ok := token["refresh_token"].(string)
|
||||
if !ok {
|
||||
return errors.New("no such access token in response")
|
||||
}
|
||||
|
||||
backup.Credential = token
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
varMap["refresh_token"] = refreshToken
|
||||
itemVars, err := json.Marshal(varMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshal var map failed, err: %v", err)
|
||||
}
|
||||
backup.Credential = accessToken
|
||||
backup.Vars = string(itemVars)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupRecords, error) {
|
||||
var datas []dto.BackupRecords
|
||||
clientMap := make(map[string]loadSizeHelper)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(records); i++ {
|
||||
var item dto.BackupRecords
|
||||
if err := copier.Copy(&item, &records[i]); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
itemPath := path.Join(records[i].FileDir, records[i].FileName)
|
||||
if _, ok := clientMap[records[i].Source]; !ok {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
|
||||
clientMap[records[i].Source] = loadSizeHelper{}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
client, err := u.NewClient(&backup)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
|
||||
clientMap[records[i].Source] = loadSizeHelper{}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
|
||||
datas = append(datas, item)
|
||||
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
|
||||
continue
|
||||
}
|
||||
if clientMap[records[i].Source].isOk {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
item.Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
|
||||
datas = append(datas, item)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func loadLocalDir() (string, error) {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
|
||||
if err != nil {
|
||||
@@ -521,3 +568,50 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
|
||||
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
|
||||
return client.Upload(fileItem, targetPath)
|
||||
}
|
||||
|
||||
func StartRefreshOneDriveToken() {
|
||||
service := NewIBackupService()
|
||||
oneDriveCronID, err := global.Cron.AddJob("0 * * * *", service)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("can not add OneDrive corn job: %s", err.Error())
|
||||
return
|
||||
}
|
||||
global.OneDriveCronID = oneDriveCronID
|
||||
}
|
||||
|
||||
func (u *BackupService) Run() {
|
||||
var backupItem model.BackupAccount
|
||||
_ = global.DB.Where("`type` = ?", "OneDrive").First(&backupItem)
|
||||
if backupItem.ID == 0 {
|
||||
return
|
||||
}
|
||||
if len(backupItem.Credential) == 0 {
|
||||
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("start to refresh token of OneDrive ...")
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
|
||||
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
|
||||
return
|
||||
}
|
||||
token, refreshToken, err := client.RefreshToken("refresh_token", varMap)
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
if err != nil {
|
||||
varMap["refresh_status"] = constant.StatusFailed
|
||||
varMap["refresh_msg"] = err.Error()
|
||||
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
|
||||
return
|
||||
}
|
||||
varMap["refresh_token"] = refreshToken
|
||||
|
||||
varsItem, _ := json.Marshal(varMap)
|
||||
_ = global.DB.Model(&model.BackupAccount{}).
|
||||
Where("id = ?", backupItem.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"credential": token,
|
||||
"vars": varsItem,
|
||||
}).Error
|
||||
global.LOG.Info("Successfully refreshed OneDrive token.")
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
@@ -35,10 +36,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
|
||||
return err
|
||||
}
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
|
||||
backupDir := path.Join(localDir, itemDir)
|
||||
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", req.Name, req.DetailName))
|
||||
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,7 +50,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: backupDir,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
|
||||
@@ -303,6 +304,10 @@ func reCreateDB(dbID uint, database model.Database, oldEnv string) (*model.Datab
|
||||
Password: oldPassword,
|
||||
Permission: "%",
|
||||
})
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDbName(fmt.Sprintf("%v", dbID)))
|
||||
for _, job := range cronjobs {
|
||||
_ = cronjobRepo.Update(job.ID, map[string]interface{}{"db_name": fmt.Sprintf("%v", createDB.ID)})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, envMap, err
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"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/mysql/client"
|
||||
)
|
||||
@@ -23,8 +25,9 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
targetDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName))
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||
itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName)
|
||||
targetDir := path.Join(localDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
|
||||
return err
|
||||
@@ -36,7 +39,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: targetDir,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -25,8 +26,9 @@ func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
targetDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName))
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||
itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName)
|
||||
targetDir := path.Join(localDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
if err := handlePostgresqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
|
||||
return err
|
||||
@@ -38,7 +40,7 @@ func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: targetDir,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
@@ -34,7 +35,7 @@ func (u *BackupService) RedisBackup() error {
|
||||
}
|
||||
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
timeNow := time.Now().Format("20060102150405") + common.RandStrAndNum(5)
|
||||
fileName := fmt.Sprintf("%s.rdb", timeNow)
|
||||
if appendonly == "yes" {
|
||||
if strings.HasPrefix(redisInfo.Version, "6.") {
|
||||
@@ -43,7 +44,8 @@ func (u *BackupService) RedisBackup() error {
|
||||
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
|
||||
}
|
||||
}
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
|
||||
itemDir := fmt.Sprintf("database/redis/%s", redisInfo.Name)
|
||||
backupDir := path.Join(localDir, itemDir)
|
||||
if err := handleRedisBackup(redisInfo, backupDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -51,7 +53,7 @@ func (u *BackupService) RedisBackup() error {
|
||||
Type: "redis",
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: backupDir,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
@@ -31,8 +32,9 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", req.Name))
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow)
|
||||
itemDir := fmt.Sprintf("website/%s", req.Name)
|
||||
backupDir := path.Join(localDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow+common.RandStrAndNum(5))
|
||||
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -43,7 +45,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: backupDir,
|
||||
FileDir: itemDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
|
||||
@@ -23,7 +23,7 @@ func NewICommandService() ICommandService {
|
||||
}
|
||||
|
||||
func (u *CommandService) List() ([]dto.CommandInfo, error) {
|
||||
commands, err := commandRepo.GetList()
|
||||
commands, err := commandRepo.GetList(commonRepo.WithOrderBy("name"))
|
||||
if err != nil {
|
||||
return nil, constant.ErrRecordNotFound
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (u *CommandService) List() ([]dto.CommandInfo, error) {
|
||||
}
|
||||
|
||||
func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) {
|
||||
cmdList, err := commandRepo.GetList()
|
||||
cmdList, err := commandRepo.GetList(commonRepo.WithOrderBy("name"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,11 +65,11 @@ func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) {
|
||||
}
|
||||
|
||||
func (u *CommandService) SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID))
|
||||
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
groups, _ := groupRepo.GetList(commonRepo.WithByType("command"))
|
||||
groups, _ := groupRepo.GetList(commonRepo.WithByType("command"), commonRepo.WithOrderBy("name"))
|
||||
var dtoCommands []dto.CommandInfo
|
||||
for _, command := range commands {
|
||||
var item dto.CommandInfo
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
@@ -637,6 +639,9 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
|
||||
cmd := exec.Command("bash", "-c", command)
|
||||
if !follow {
|
||||
stdout, _ := cmd.CombinedOutput()
|
||||
if !utf8.Valid(stdout) {
|
||||
return errors.New("invalid utf8")
|
||||
}
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, stdout); err != nil {
|
||||
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
|
||||
}
|
||||
@@ -678,6 +683,10 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
|
||||
global.LOG.Errorf("read bytes from log failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
// check if the bytes is valid utf8
|
||||
if !utf8.Valid(buffer[:n]) {
|
||||
continue
|
||||
}
|
||||
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
|
||||
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
|
||||
return err
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -28,7 +30,7 @@ type ICronjobService interface {
|
||||
UpdateStatus(id uint, status string) error
|
||||
Delete(req dto.CronjobBatchDelete) error
|
||||
Download(down dto.CronjobDownload) (string, error)
|
||||
StartJob(cronjob *model.Cronjob) (int, error)
|
||||
StartJob(cronjob *model.Cronjob) (string, error)
|
||||
CleanRecord(req dto.CronjobClean) error
|
||||
|
||||
LoadRecordLog(req dto.OperateByID) (string, error)
|
||||
@@ -46,14 +48,6 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
|
||||
if err := copier.Copy(&item, &cronjob); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
if hasBackup(item.Type) {
|
||||
backup, _ := backupRepo.Get(commonRepo.WithByID(uint(item.TargetDirID)))
|
||||
if len(backup.Type) != 0 {
|
||||
item.TargetDir = backup.Type
|
||||
}
|
||||
} else {
|
||||
item.TargetDir = "-"
|
||||
}
|
||||
record, _ := cronjobRepo.RecordFirst(cronjob.ID)
|
||||
if record.ID != 0 {
|
||||
item.LastRecordTime = record.StartTime.Format("2006-01-02 15:04:05")
|
||||
@@ -103,24 +97,23 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.CleanData && hasBackup(cronjob.Type) {
|
||||
cronjob.RetainCopies = 0
|
||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if backup.Type != "LOCAL" {
|
||||
localDir, err := loadLocalDir()
|
||||
if req.CleanData {
|
||||
if hasBackup(cronjob.Type) {
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
cronjob.RetainCopies = 0
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
} else {
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
|
||||
u.removeExpiredLog(cronjob)
|
||||
}
|
||||
}
|
||||
if req.IsDelete {
|
||||
records, _ := backupRepo.ListRecord(backupRepo.WithByCronID(cronjob.ID))
|
||||
for _, records := range records {
|
||||
records.CronjobID = 0
|
||||
_ = backupRepo.UpdateRecord(&records)
|
||||
}
|
||||
}
|
||||
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
|
||||
@@ -184,29 +177,40 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
cronjob.Status = constant.StatusEnable
|
||||
cronjob.Spec = loadSpec(cronjob)
|
||||
|
||||
global.LOG.Infof("create cronjob %s successful, spec: %s", cronjob.Name, cronjob.Spec)
|
||||
entryID, err := u.StartJob(&cronjob)
|
||||
spec := cronjob.Spec
|
||||
entryIDs, err := u.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cronjob.EntryID = uint64(entryID)
|
||||
cronjob.Spec = spec
|
||||
cronjob.EntryIDs = entryIDs
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) StartJob(cronjob *model.Cronjob) (int, error) {
|
||||
if cronjob.EntryID != 0 {
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
func (u *CronjobService) StartJob(cronjob *model.Cronjob) (string, error) {
|
||||
if len(cronjob.EntryIDs) != 0 {
|
||||
ids := strings.Split(cronjob.EntryIDs, ",")
|
||||
for _, id := range ids {
|
||||
idItem, _ := strconv.Atoi(id)
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
}
|
||||
entryID, err := u.AddCronJob(cronjob)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
specs := strings.Split(cronjob.Spec, ",")
|
||||
var ids []string
|
||||
for _, spec := range specs {
|
||||
cronjob.Spec = spec
|
||||
entryID, err := u.AddCronJob(cronjob)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ids = append(ids, fmt.Sprintf("%v", entryID))
|
||||
}
|
||||
return entryID, nil
|
||||
return strings.Join(ids, ","), nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
@@ -215,9 +219,13 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
if cronjob.ID == 0 {
|
||||
return errors.New("find cronjob in db failed")
|
||||
}
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData}); err != nil {
|
||||
ids := strings.Split(cronjob.EntryIDs, ",")
|
||||
for _, id := range ids {
|
||||
idItem, _ := strconv.Atoi(id)
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, IsDelete: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
@@ -238,29 +246,27 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
cronjob.EntryID = cronModel.EntryID
|
||||
cronjob.EntryIDs = cronModel.EntryIDs
|
||||
cronjob.Type = cronModel.Type
|
||||
cronjob.Spec = loadSpec(cronjob)
|
||||
spec := cronjob.Spec
|
||||
if cronModel.Status == constant.StatusEnable {
|
||||
newEntryID, err := u.StartJob(&cronjob)
|
||||
newEntryIDs, err := u.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upMap["entry_id"] = newEntryID
|
||||
upMap["entry_ids"] = newEntryIDs
|
||||
} else {
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
ids := strings.Split(cronjob.EntryIDs, ",")
|
||||
for _, id := range ids {
|
||||
idItem, _ := strconv.Atoi(id)
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
}
|
||||
|
||||
upMap["name"] = req.Name
|
||||
upMap["spec"] = cronjob.Spec
|
||||
upMap["spec"] = spec
|
||||
upMap["script"] = req.Script
|
||||
upMap["container_name"] = req.ContainerName
|
||||
upMap["spec_type"] = req.SpecType
|
||||
upMap["week"] = req.Week
|
||||
upMap["day"] = req.Day
|
||||
upMap["hour"] = req.Hour
|
||||
upMap["minute"] = req.Minute
|
||||
upMap["second"] = req.Second
|
||||
upMap["app_id"] = req.AppID
|
||||
upMap["website"] = req.Website
|
||||
upMap["exclusion_rules"] = req.ExclusionRules
|
||||
@@ -268,8 +274,9 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
upMap["db_name"] = req.DBName
|
||||
upMap["url"] = req.URL
|
||||
upMap["source_dir"] = req.SourceDir
|
||||
upMap["keep_local"] = req.KeepLocal
|
||||
upMap["target_dir_id"] = req.TargetDirID
|
||||
|
||||
upMap["backup_accounts"] = req.BackupAccounts
|
||||
upMap["default_download"] = req.DefaultDownload
|
||||
upMap["retain_copies"] = req.RetainCopies
|
||||
return cronjobRepo.Update(id, upMap)
|
||||
}
|
||||
@@ -280,19 +287,23 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
return errors.WithMessage(constant.ErrRecordNotFound, "record not found")
|
||||
}
|
||||
var (
|
||||
entryID int
|
||||
err error
|
||||
entryIDs string
|
||||
err error
|
||||
)
|
||||
if status == constant.StatusEnable {
|
||||
entryID, err = u.StartJob(&cronjob)
|
||||
entryIDs, err = u.StartJob(&cronjob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
|
||||
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
|
||||
ids := strings.Split(cronjob.EntryIDs, ",")
|
||||
for _, id := range ids {
|
||||
idItem, _ := strconv.Atoi(id)
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
|
||||
}
|
||||
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_id": entryID})
|
||||
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_ids": entryIDs})
|
||||
}
|
||||
|
||||
func (u *CronjobService) AddCronJob(cronjob *model.Cronjob) (int, error) {
|
||||
@@ -328,26 +339,3 @@ func mkdirAndWriteFile(cronjob *model.Cronjob, startTime time.Time, msg []byte)
|
||||
write.Flush()
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func loadSpec(cronjob model.Cronjob) string {
|
||||
switch cronjob.SpecType {
|
||||
case "perMonth":
|
||||
return fmt.Sprintf("%v %v %v * *", cronjob.Minute, cronjob.Hour, cronjob.Day)
|
||||
case "perWeek":
|
||||
return fmt.Sprintf("%v %v * * %v", cronjob.Minute, cronjob.Hour, cronjob.Week)
|
||||
case "perNDay":
|
||||
return fmt.Sprintf("%v %v */%v * *", cronjob.Minute, cronjob.Hour, cronjob.Day)
|
||||
case "perDay":
|
||||
return fmt.Sprintf("%v %v * * *", cronjob.Minute, cronjob.Hour)
|
||||
case "perNHour":
|
||||
return fmt.Sprintf("%v */%v * * *", cronjob.Minute, cronjob.Hour)
|
||||
case "perHour":
|
||||
return fmt.Sprintf("%v * * * *", cronjob.Minute)
|
||||
case "perNMinute":
|
||||
return fmt.Sprintf("@every %vm", cronjob.Minute)
|
||||
case "perNSecond":
|
||||
return fmt.Sprintf("@every %vs", cronjob.Second)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
)
|
||||
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
|
||||
var apps []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
apps, _ = appInstallRepo.ListBy()
|
||||
} else {
|
||||
itemID, _ := (strconv.Atoi(cronjob.AppID))
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apps = append(apps, app)
|
||||
}
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, app := range apps {
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "app"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = app.App.Key
|
||||
record.DetailName = app.Name
|
||||
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
|
||||
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
if err := handleAppBackup(&app, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time) error {
|
||||
webs := loadWebsForJob(cronjob)
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, web := range webs {
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "website"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = web.PrimaryDomain
|
||||
record.DetailName = web.Alias
|
||||
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s", web.PrimaryDomain))
|
||||
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
if err := handleWebsiteBackup(&web, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time) error {
|
||||
dbs := loadDbsForJob(cronjob)
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dbInfo := range dbs {
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = dbInfo.DBType
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = dbInfo.Database
|
||||
record.DetailName = dbInfo.Name
|
||||
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
|
||||
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
if err := handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := handlePostgresqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.Time) error {
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
|
||||
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||
return err
|
||||
}
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "directory"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = cronjob.Name
|
||||
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
record.FileName = fileName
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time) error {
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nameItem := startTime.Format("20060102150405") + common.RandStrAndNum(5)
|
||||
fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem)
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, "log", nameItem)
|
||||
if err := handleBackupLogs(backupDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "log"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = cronjob.Name
|
||||
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
|
||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(path.Dir(backupDir), fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileDir = path.Dir(downloadPath)
|
||||
record.FileName = fileName
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, startTime time.Time, logPath string) error {
|
||||
accountMap, err := loadClientMap(cronjob.BackupAccounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var record model.BackupRecord
|
||||
record.From = "cronjob"
|
||||
record.Type = "directory"
|
||||
record.CronjobID = cronjob.ID
|
||||
record.Name = cronjob.Name
|
||||
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
|
||||
record.FileDir = "system_snapshot"
|
||||
|
||||
req := dto.SnapshotCreate{
|
||||
From: record.BackupType,
|
||||
DefaultDownload: cronjob.DefaultDownload,
|
||||
}
|
||||
name, err := NewISnapshotService().HandleSnapshot(true, logPath, req, startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.FileName = name + ".tar.gz"
|
||||
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
u.removeExpiredBackup(cronjob, accountMap, record)
|
||||
return nil
|
||||
}
|
||||
|
||||
type databaseHelper struct {
|
||||
DBType string
|
||||
Database string
|
||||
Name string
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
var dbs []databaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItems, _ := mysqlRepo.List()
|
||||
for _, mysql := range mysqlItems {
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysql.MysqlName,
|
||||
Name: mysql.Name,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
pgItems, _ := postgresqlRepo.List()
|
||||
for _, pg := range pgItems {
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pg.PostgresqlName,
|
||||
Name: pg.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
itemID, _ := (strconv.Atoi(cronjob.DBName))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
|
||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
var weblist []model.Website
|
||||
if cronjob.Website == "all" {
|
||||
weblist, _ = websiteRepo.List()
|
||||
return weblist
|
||||
}
|
||||
itemID, _ := (strconv.Atoi(cronjob.Website))
|
||||
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
if webItem.ID != 0 {
|
||||
weblist = append(weblist, webItem)
|
||||
}
|
||||
return weblist
|
||||
}
|
||||
|
||||
func loadRecordPath(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper) (string, string) {
|
||||
source := accountMap[fmt.Sprintf("%v", cronjob.DefaultDownload)].backType
|
||||
targets := strings.Split(cronjob.BackupAccounts, ",")
|
||||
var itemAccounts []string
|
||||
for _, target := range targets {
|
||||
if len(target) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(accountMap[target].backType) != 0 {
|
||||
itemAccounts = append(itemAccounts, accountMap[target].backType)
|
||||
}
|
||||
}
|
||||
backupType := strings.Join(itemAccounts, ",")
|
||||
return source, backupType
|
||||
}
|
||||
|
||||
func handleBackupLogs(targetDir, fileName string) error {
|
||||
websites, err := websiteRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(websites) != 0 {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webItem := path.Join(nginxInstall.GetPath(), "www/sites")
|
||||
for _, website := range websites {
|
||||
dirItem := path.Join(targetDir, "website", website.Alias)
|
||||
if _, err := os.Stat(dirItem); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dirItem, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
itemDir := path.Join(webItem, website.Alias, "log")
|
||||
logFiles, _ := os.ReadDir(itemDir)
|
||||
if len(logFiles) != 0 {
|
||||
for i := 0; i < len(logFiles); i++ {
|
||||
if !logFiles[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(itemDir, logFiles[i].Name())}, dirItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemDir2 := path.Join(global.CONF.System.Backup, "log/website", website.Alias)
|
||||
logFiles2, _ := os.ReadDir(itemDir2)
|
||||
if len(logFiles2) != 0 {
|
||||
for i := 0; i < len(logFiles2); i++ {
|
||||
if !logFiles2[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(itemDir2, logFiles2[i].Name())}, dirItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("backup website log successful!")
|
||||
}
|
||||
|
||||
systemLogDir := path.Join(global.CONF.System.BaseDir, "1panel/log")
|
||||
systemDir := path.Join(targetDir, "system")
|
||||
if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(systemDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
systemLogFiles, _ := os.ReadDir(systemLogDir)
|
||||
if len(systemLogFiles) != 0 {
|
||||
for i := 0; i < len(systemLogFiles); i++ {
|
||||
if !systemLogFiles[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(systemLogDir, systemLogFiles[i].Name())}, systemDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("backup system log successful!")
|
||||
|
||||
loginLogFiles, _ := os.ReadDir("/var/log")
|
||||
loginDir := path.Join(targetDir, "login")
|
||||
if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(loginDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(loginLogFiles) != 0 {
|
||||
for i := 0; i < len(loginLogFiles); i++ {
|
||||
if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) {
|
||||
_ = cpBinary([]string{path.Join("/var/log", loginLogFiles[i].Name())}, loginDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("backup ssh log successful!")
|
||||
|
||||
if err := handleTar(targetDir, path.Dir(targetDir), fileName, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(targetDir)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -5,15 +5,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||
@@ -35,32 +34,25 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
if len(cronjob.Script) == 0 {
|
||||
return
|
||||
}
|
||||
record.Records = u.generateLogsPath(*cronjob, record.StartTime)
|
||||
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
|
||||
script := cronjob.Script
|
||||
if len(cronjob.ContainerName) != 0 {
|
||||
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("docker exec %s %s", cronjob.ContainerName, cronjob.Script))
|
||||
} else {
|
||||
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
|
||||
script = fmt.Sprintf("docker exec %s %s", cronjob.ContainerName, cronjob.Script)
|
||||
}
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "snapshot":
|
||||
messageItem := ""
|
||||
messageItem, record.File, err = u.handleSnapshot(cronjob, record.StartTime)
|
||||
message = []byte(messageItem)
|
||||
err = u.handleShell(cronjob.Type, cronjob.Name, script, record.Records)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "curl":
|
||||
if len(cronjob.URL) == 0 {
|
||||
return
|
||||
}
|
||||
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL))
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
record.Records = u.generateLogsPath(*cronjob, record.StartTime)
|
||||
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
|
||||
err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL), record.Records)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "ntp":
|
||||
err = u.handleNtpSync()
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "website", "database", "app":
|
||||
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
||||
case "directory":
|
||||
if len(cronjob.SourceDir) == 0 {
|
||||
return
|
||||
}
|
||||
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "cutWebsiteLog":
|
||||
var messageItem []string
|
||||
messageItem, record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime)
|
||||
@@ -69,9 +61,24 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
messageItem := ""
|
||||
messageItem, err = u.handleSystemClean()
|
||||
message = []byte(messageItem)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
case "website":
|
||||
err = u.handleWebsite(*cronjob, record.StartTime)
|
||||
case "app":
|
||||
err = u.handleApp(*cronjob, record.StartTime)
|
||||
case "database":
|
||||
err = u.handleDatabase(*cronjob, record.StartTime)
|
||||
case "directory":
|
||||
if len(cronjob.SourceDir) == 0 {
|
||||
return
|
||||
}
|
||||
err = u.handleDirectory(*cronjob, record.StartTime)
|
||||
case "log":
|
||||
record.File, err = u.handleSystemLog(*cronjob, record.StartTime)
|
||||
err = u.handleSystemLog(*cronjob, record.StartTime)
|
||||
case "snapshot":
|
||||
record.Records = u.generateLogsPath(*cronjob, record.StartTime)
|
||||
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
|
||||
err = u.handleSnapshot(*cronjob, record.StartTime, record.Records)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -88,18 +95,17 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleShell(cronType, cornName, script string) ([]byte, error) {
|
||||
func (u *CronjobService) handleShell(cronType, cornName, script, logPath string) error {
|
||||
handleDir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronType, cornName)
|
||||
if _, err := os.Stat(handleDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(handleDir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
stdout, err := cmd.ExecCronjobWithTimeOut(script, handleDir, 24*time.Hour)
|
||||
if err != nil {
|
||||
return []byte(stdout), err
|
||||
if err := cmd.ExecCronjobWithTimeOut(script, handleDir, logPath, 24*time.Hour); err != nil {
|
||||
return err
|
||||
}
|
||||
return []byte(stdout), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleNtpSync() error {
|
||||
@@ -117,98 +123,6 @@ func (u *CronjobService) handleNtpSync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
global.LOG.Infof("start to backup %s %s to %s", cronjob.Type, cronjob.Name, backup.Type)
|
||||
|
||||
switch cronjob.Type {
|
||||
case "database":
|
||||
paths, err := u.handleDatabase(*cronjob, backup, startTime)
|
||||
return strings.Join(paths, ","), err
|
||||
case "app":
|
||||
paths, err := u.handleApp(*cronjob, backup, startTime)
|
||||
return strings.Join(paths, ","), err
|
||||
case "website":
|
||||
paths, err := u.handleWebsite(*cronjob, backup, startTime)
|
||||
return strings.Join(paths, ","), err
|
||||
default:
|
||||
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
|
||||
itemFileDir := fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
|
||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
||||
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var client cloud_storage.CloudStorageClient
|
||||
if backup.Type != "LOCAL" {
|
||||
if !cronjob.KeepLocal {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
|
||||
}()
|
||||
}
|
||||
client, err = NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
|
||||
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"))
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
}
|
||||
for i := int(cronjob.RetainCopies); i < len(records); i++ {
|
||||
if len(records[i].File) != 0 {
|
||||
files := strings.Split(records[i].File, ",")
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
|
||||
if backType == "LOCAL" {
|
||||
continue
|
||||
}
|
||||
|
||||
fileItem := file
|
||||
if cronjob.KeepLocal {
|
||||
if len(backupPath) != 0 {
|
||||
fileItem = path.Join(strings.TrimPrefix(backupPath, "/") + strings.TrimPrefix(file, localDir+"/"))
|
||||
} else {
|
||||
fileItem = strings.TrimPrefix(file, localDir+"/")
|
||||
}
|
||||
}
|
||||
|
||||
if cronjob.Type == "snapshot" {
|
||||
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(path.Base(fileItem), ".tar.gz")))
|
||||
}
|
||||
_, _ = backClient.Delete(fileItem)
|
||||
}
|
||||
}
|
||||
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
|
||||
_ = os.Remove(records[i].Records)
|
||||
}
|
||||
}
|
||||
|
||||
func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
@@ -266,77 +180,6 @@ func handleUnTar(sourceFile, targetDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||
var paths []string
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
|
||||
dbs := loadDbsForJob(cronjob)
|
||||
|
||||
var client cloud_storage.CloudStorageClient
|
||||
if backup.Type != "LOCAL" {
|
||||
client, err = NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, dbInfo := range dbs {
|
||||
var record model.BackupRecord
|
||||
record.Type = dbInfo.DBType
|
||||
record.Source = "LOCAL"
|
||||
record.BackupType = backup.Type
|
||||
|
||||
record.Name = dbInfo.Database
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405"))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
if err = handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
} else {
|
||||
if err = handlePostgresqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
|
||||
record.DetailName = dbInfo.Name
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = itemFileDir
|
||||
}
|
||||
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return paths, err
|
||||
}
|
||||
if backup.Type != "LOCAL" {
|
||||
if !cronjob.KeepLocal {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
} else {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) ([]string, string, error) {
|
||||
var (
|
||||
err error
|
||||
@@ -377,7 +220,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
global.LOG.Infof(msg)
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
return msgs, strings.Join(filePaths, ","), err
|
||||
}
|
||||
|
||||
@@ -400,340 +243,129 @@ func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||
var paths []string
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
|
||||
var applist []model.AppInstall
|
||||
if cronjob.AppID == "all" {
|
||||
applist, err = appInstallRepo.ListBy()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
} else {
|
||||
itemID, _ := (strconv.Atoi(cronjob.AppID))
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
applist = append(applist, app)
|
||||
}
|
||||
|
||||
var client cloud_storage.CloudStorageClient
|
||||
if backup.Type != "LOCAL" {
|
||||
client, err = NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, app := range applist {
|
||||
var record model.BackupRecord
|
||||
record.Type = "app"
|
||||
record.Name = app.App.Key
|
||||
record.DetailName = app.Name
|
||||
record.Source = "LOCAL"
|
||||
record.BackupType = backup.Type
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
|
||||
}
|
||||
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405"))
|
||||
if err := handleAppBackup(&app, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return paths, err
|
||||
}
|
||||
if backup.Type != "LOCAL" {
|
||||
if !cronjob.KeepLocal {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
} else {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||
var paths []string
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
|
||||
weblist := loadWebsForJob(cronjob)
|
||||
var client cloud_storage.CloudStorageClient
|
||||
if backup.Type != "LOCAL" {
|
||||
client, err = NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, websiteItem := range weblist {
|
||||
var record model.BackupRecord
|
||||
record.Type = "website"
|
||||
record.Name = websiteItem.PrimaryDomain
|
||||
record.DetailName = websiteItem.Alias
|
||||
record.Source = "LOCAL"
|
||||
record.BackupType = backup.Type
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", websiteItem.PrimaryDomain))
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
|
||||
}
|
||||
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", websiteItem.PrimaryDomain, startTime.Format("20060102150405"))
|
||||
if err := handleWebsiteBackup(&websiteItem, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
return paths, err
|
||||
}
|
||||
if backup.Type != "LOCAL" {
|
||||
if !cronjob.KeepLocal {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
} else {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSnapshot(cronjob *model.Cronjob, startTime time.Time) (string, string, error) {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
req := dto.SnapshotCreate{
|
||||
From: backup.Type,
|
||||
}
|
||||
message, name, err := NewISnapshotService().HandleSnapshot(true, req, startTime.Format("20060102150405"))
|
||||
if err != nil {
|
||||
return message, "", err
|
||||
}
|
||||
|
||||
path := path.Join(strings.TrimPrefix(backup.BackupPath, "/"), "system_snapshot", name+".tar.gz")
|
||||
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, "", cronjob, client)
|
||||
return message, path, nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSystemClean() (string, error) {
|
||||
return NewIDeviceService().CleanForCronjob()
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time) (string, error) {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||
func loadClientMap(backupAccounts string) (map[string]cronjobUploadHelper, error) {
|
||||
clients := make(map[string]cronjobUploadHelper)
|
||||
accounts, err := backupRepo.List()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathItem := path.Join(global.CONF.System.BaseDir, "1panel/tmp/log", startTime.Format("20060102150405"))
|
||||
websites, err := websiteRepo.List()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(websites) != 0 {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return "", err
|
||||
targets := strings.Split(backupAccounts, ",")
|
||||
for _, target := range targets {
|
||||
if len(target) == 0 {
|
||||
continue
|
||||
}
|
||||
webItem := path.Join(nginxInstall.GetPath(), "www/sites")
|
||||
for _, website := range websites {
|
||||
dirItem := path.Join(pathItem, "website", website.Alias)
|
||||
if _, err := os.Stat(dirItem); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dirItem, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
for _, account := range accounts {
|
||||
if target == account.Type {
|
||||
client, err := NewIBackupService().NewClient(&account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
itemDir := path.Join(webItem, website.Alias, "log")
|
||||
logFiles, _ := os.ReadDir(itemDir)
|
||||
if len(logFiles) != 0 {
|
||||
for i := 0; i < len(logFiles); i++ {
|
||||
if !logFiles[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(itemDir, logFiles[i].Name())}, dirItem)
|
||||
}
|
||||
pathItem := account.BackupPath
|
||||
if account.BackupPath != "/" {
|
||||
pathItem = strings.TrimPrefix(account.BackupPath, "/")
|
||||
}
|
||||
}
|
||||
itemDir2 := path.Join(global.CONF.System.Backup, "log/website", website.Alias)
|
||||
logFiles2, _ := os.ReadDir(itemDir2)
|
||||
if len(logFiles2) != 0 {
|
||||
for i := 0; i < len(logFiles2); i++ {
|
||||
if !logFiles2[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(itemDir2, logFiles2[i].Name())}, dirItem)
|
||||
}
|
||||
clients[target] = cronjobUploadHelper{
|
||||
client: client,
|
||||
backupPath: pathItem,
|
||||
backType: account.Type,
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("backup website log successful!")
|
||||
}
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
systemLogDir := path.Join(global.CONF.System.BaseDir, "1panel/log")
|
||||
systemDir := path.Join(pathItem, "system")
|
||||
if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(systemDir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
systemLogFiles, _ := os.ReadDir(systemLogDir)
|
||||
if len(systemLogFiles) != 0 {
|
||||
for i := 0; i < len(systemLogFiles); i++ {
|
||||
if !systemLogFiles[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(systemLogDir, systemLogFiles[i].Name())}, systemDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("backup system log successful!")
|
||||
type cronjobUploadHelper struct {
|
||||
backupPath string
|
||||
backType string
|
||||
client cloud_storage.CloudStorageClient
|
||||
}
|
||||
|
||||
loginLogFiles, _ := os.ReadDir("/var/log")
|
||||
loginDir := path.Join(pathItem, "login")
|
||||
if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(loginDir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if len(loginLogFiles) != 0 {
|
||||
for i := 0; i < len(loginLogFiles); i++ {
|
||||
if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) {
|
||||
_ = cpBinary([]string{path.Join("/var/log", loginLogFiles[i].Name())}, loginDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("backup ssh log successful!")
|
||||
|
||||
fileName := fmt.Sprintf("system_log_%s.tar.gz", startTime.Format("20060102150405"))
|
||||
targetDir := path.Dir(pathItem)
|
||||
if err := handleTar(pathItem, targetDir, fileName, ""); err != nil {
|
||||
return "", err
|
||||
}
|
||||
func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, file string) (string, error) {
|
||||
defer func() {
|
||||
os.RemoveAll(pathItem)
|
||||
os.RemoveAll(path.Join(targetDir, fileName))
|
||||
_ = os.Remove(file)
|
||||
}()
|
||||
accounts := strings.Split(cronjob.BackupAccounts, ",")
|
||||
cloudSrc := strings.TrimPrefix(file, global.CONF.System.TmpDir+"/")
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
global.LOG.Debugf("start upload file to %s, dir: %s", account, path.Join(accountMap[account].backupPath, cloudSrc))
|
||||
if _, err := accountMap[account].client.Upload(file, path.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
global.LOG.Debugf("upload successful!")
|
||||
}
|
||||
}
|
||||
return cloudSrc, nil
|
||||
}
|
||||
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return "", err
|
||||
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
|
||||
opts = append(opts, commonRepo.WithByFrom("cronjob"))
|
||||
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
|
||||
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
|
||||
if record.ID != 0 {
|
||||
opts = append(opts, backupRepo.WithByType(record.Type))
|
||||
opts = append(opts, commonRepo.WithByName(record.Name))
|
||||
opts = append(opts, backupRepo.WithByDetailName(record.DetailName))
|
||||
}
|
||||
records, _ := backupRepo.ListRecord(opts...)
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
}
|
||||
for i := int(cronjob.RetainCopies); i < len(records); i++ {
|
||||
accounts := strings.Split(cronjob.BackupAccounts, ",")
|
||||
if cronjob.Type == "snapshot" {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
|
||||
}
|
||||
}
|
||||
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
|
||||
} else {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(context.Background(), commonRepo.WithByID(records[i].ID))
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
if len(records) <= int(cronjob.RetainCopies) {
|
||||
return
|
||||
}
|
||||
for i := int(cronjob.RetainCopies); i < len(records); i++ {
|
||||
if len(records[i].File) != 0 {
|
||||
files := strings.Split(records[i].File, ",")
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
}
|
||||
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
|
||||
_ = os.Remove(records[i].Records)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.Time) string {
|
||||
dir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronjob.Type, cronjob.Name)
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
|
||||
targetPath := "log/" + fileName
|
||||
if len(backup.BackupPath) != 0 {
|
||||
targetPath = strings.TrimPrefix(backup.BackupPath, "/") + "/log/" + fileName
|
||||
}
|
||||
|
||||
if _, err = client.Upload(path.Join(targetDir, fileName), targetPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, client)
|
||||
return targetPath, nil
|
||||
path := fmt.Sprintf("%s/%s.log", dir, startTime.Format("20060102150405"))
|
||||
return path
|
||||
}
|
||||
|
||||
func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log"
|
||||
}
|
||||
|
||||
type databaseHelper struct {
|
||||
DBType string
|
||||
Database string
|
||||
Name string
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
|
||||
var dbs []databaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItems, _ := mysqlRepo.List()
|
||||
for _, mysql := range mysqlItems {
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysql.MysqlName,
|
||||
Name: mysql.Name,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
pgItems, _ := postgresqlRepo.List()
|
||||
for _, pg := range pgItems {
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pg.PostgresqlName,
|
||||
Name: pg.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
itemID, _ := (strconv.Atoi(cronjob.DBName))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: mysqlItem.MysqlName,
|
||||
Name: mysqlItem.Name,
|
||||
})
|
||||
} else {
|
||||
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
|
||||
dbs = append(dbs, databaseHelper{
|
||||
DBType: cronjob.DBType,
|
||||
Database: pgItem.PostgresqlName,
|
||||
Name: pgItem.Name,
|
||||
})
|
||||
}
|
||||
return dbs
|
||||
}
|
||||
|
||||
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
|
||||
var weblist []model.Website
|
||||
if cronjob.Website == "all" {
|
||||
weblist, _ = websiteRepo.List()
|
||||
return weblist
|
||||
}
|
||||
itemID, _ := (strconv.Atoi(cronjob.Website))
|
||||
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
|
||||
if webItem.ID != 0 {
|
||||
weblist = append(weblist, webItem)
|
||||
}
|
||||
return weblist
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
|
||||
confs = append(confs, redisConfig{key: "timeout", value: req.Timeout})
|
||||
confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients})
|
||||
confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory})
|
||||
if err := confSet(redisInfo.Name, confs); err != nil {
|
||||
if err := confSet(redisInfo.Name, "", confs); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", constant.AppInstallDir, redisInfo.Name)); err != nil {
|
||||
@@ -78,7 +78,7 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
|
||||
confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly})
|
||||
confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync})
|
||||
}
|
||||
if err := confSet(redisInfo.Name, confs); err != nil {
|
||||
if err := confSet(redisInfo.Name, req.Type, confs); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", constant.AppInstallDir, redisInfo.Name)); err != nil {
|
||||
@@ -213,7 +213,7 @@ type redisConfig struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func confSet(redisName string, changeConf []redisConfig) error {
|
||||
func confSet(redisName string, updateType string, changeConf []redisConfig) error {
|
||||
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", constant.AppInstallDir, redisName)
|
||||
lineBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -221,19 +221,38 @@ func confSet(redisName string, changeConf []redisConfig) error {
|
||||
}
|
||||
files := strings.Split(string(lineBytes), "\n")
|
||||
|
||||
startIndex, endIndex := 0, 0
|
||||
startIndex, endIndex, emptyLine := 0, 0, 0
|
||||
var newFiles []string
|
||||
for i := 0; i < len(files); i++ {
|
||||
if files[i] == "# Redis configuration rewrite by 1Panel" {
|
||||
startIndex = i
|
||||
newFiles = append(newFiles, files[i])
|
||||
continue
|
||||
}
|
||||
if files[i] == "# End Redis configuration rewrite by 1Panel" {
|
||||
endIndex = i
|
||||
break
|
||||
}
|
||||
if startIndex == 0 && strings.HasPrefix(files[i], "save ") {
|
||||
newFiles = append(newFiles, "# "+files[i])
|
||||
continue
|
||||
}
|
||||
if startIndex != 0 && endIndex == 0 && (len(files[i]) == 0 || (updateType == "rbd" && strings.HasPrefix(files[i], "save "))) {
|
||||
emptyLine++
|
||||
continue
|
||||
}
|
||||
newFiles = append(newFiles, files[i])
|
||||
}
|
||||
endIndex = endIndex - emptyLine
|
||||
for _, item := range changeConf {
|
||||
if item.key == "save" {
|
||||
saveVal := strings.Split(item.value, ",")
|
||||
for i := 0; i < len(saveVal); i++ {
|
||||
newFiles = append(newFiles, "save "+saveVal[i])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
isExist := false
|
||||
for i := startIndex; i < endIndex; i++ {
|
||||
if strings.HasPrefix(newFiles[i], item.key) || strings.HasPrefix(newFiles[i], "# "+item.key) {
|
||||
|
||||
@@ -107,7 +107,7 @@ func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error {
|
||||
if req.Value == "firewallcmd-ipset" || req.Value == "ufw" {
|
||||
itemName := "ufw"
|
||||
if req.Value == "firewallcmd-ipset" {
|
||||
itemName = "firewallcmd"
|
||||
itemName = "firewalld"
|
||||
}
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
|
||||
@@ -211,6 +211,6 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
go service.saveIODataToDB(ctx, float64(intervalItem))
|
||||
go service.saveNetDataToDB(ctx, float64(intervalItem))
|
||||
|
||||
global.MonitorCronID = int(monitorID)
|
||||
global.MonitorCronID = monitorID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type ISnapshotService interface {
|
||||
UpdateDescription(req dto.UpdateDescription) error
|
||||
readFromJson(path string) (SnapshotJson, error)
|
||||
|
||||
HandleSnapshot(isCronjob bool, req dto.SnapshotCreate, timeNow string) (string, string, error)
|
||||
HandleSnapshot(isCronjob bool, logPath string, req dto.SnapshotCreate, timeNow string) (string, error)
|
||||
}
|
||||
|
||||
func NewISnapshotService() ISnapshotService {
|
||||
@@ -85,11 +85,12 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
snap = strings.ReplaceAll(snap, ".tar.gz", "")
|
||||
}
|
||||
itemSnap := model.Snapshot{
|
||||
Name: snap,
|
||||
From: req.From,
|
||||
Version: nameItems[1],
|
||||
Description: req.Description,
|
||||
Status: constant.StatusSuccess,
|
||||
Name: snap,
|
||||
From: req.From,
|
||||
DefaultDownload: req.From,
|
||||
Version: nameItems[1],
|
||||
Description: req.Description,
|
||||
Status: constant.StatusSuccess,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: formatTime,
|
||||
UpdatedAt: formatTime,
|
||||
@@ -132,7 +133,7 @@ type SnapshotJson struct {
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
if _, _, err := u.HandleSnapshot(false, req, time.Now().Format("20060102150405")); err != nil {
|
||||
if _, err := u.HandleSnapshot(false, "", req, time.Now().Format("20060102150405")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -152,7 +153,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
if len(snap.InterruptStep) != 0 && !req.IsNew {
|
||||
isReTry = true
|
||||
}
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(snap.From))
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -469,10 +470,10 @@ func (u *SnapshotService) readFromJson(path string) (SnapshotJson, error) {
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate, timeNow string) (string, string, error) {
|
||||
func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto.SnapshotCreate, timeNow string) (string, error) {
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
var (
|
||||
rootDir string
|
||||
@@ -489,11 +490,12 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate,
|
||||
rootDir = path.Join(localDir, "system", name)
|
||||
|
||||
snap = model.Snapshot{
|
||||
Name: name,
|
||||
Description: req.Description,
|
||||
From: req.From,
|
||||
Version: versionItem.Value,
|
||||
Status: constant.StatusWaiting,
|
||||
Name: name,
|
||||
Description: req.Description,
|
||||
From: req.From,
|
||||
DefaultDownload: req.DefaultDownload,
|
||||
Version: versionItem.Value,
|
||||
Status: constant.StatusWaiting,
|
||||
}
|
||||
_ = snapshotRepo.Create(&snap)
|
||||
snapStatus.SnapID = snap.ID
|
||||
@@ -501,7 +503,7 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate,
|
||||
} else {
|
||||
snap, err = snapshotRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
|
||||
if snapStatus.ID == 0 {
|
||||
@@ -523,7 +525,7 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate,
|
||||
BackupDataDir: localDir,
|
||||
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
|
||||
}
|
||||
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
if snapStatus.PanelInfo != constant.StatusDone {
|
||||
wg.Add(1)
|
||||
go snapJson(itemHelper, jsonItem, rootDir)
|
||||
@@ -569,25 +571,38 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate,
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||
}()
|
||||
return "", "", nil
|
||||
return "", nil
|
||||
}
|
||||
wg.Wait()
|
||||
if !checkIsAllDone(snap.ID) {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s backup failed", snap.Name)
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
return snap.Name, fmt.Errorf("snapshot %s backup failed", snap.Name)
|
||||
}
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
snapPanelData(itemHelper, localDir, backupPanelDir)
|
||||
if snapStatus.PanelData != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
return snap.Name, fmt.Errorf("snapshot %s 1panel data failed", snap.Name)
|
||||
}
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
snapCompress(itemHelper, rootDir)
|
||||
if snapStatus.Compress != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s compress failed", snap.Name)
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
return snap.Name, fmt.Errorf("snapshot %s compress failed", snap.Name)
|
||||
}
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
|
||||
if snapStatus.Upload != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s upload failed", snap.Name)
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
return snap.Name, fmt.Errorf("snapshot %s upload failed", snap.Name)
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||
return loadLogByStatus(snapStatus), snap.Name, nil
|
||||
loadLogByStatus(snapStatus, logPath)
|
||||
return snap.Name, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
|
||||
@@ -1055,7 +1070,7 @@ func checkIsAllDone(snapID uint) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func loadLogByStatus(status model.SnapshotStatus) string {
|
||||
func loadLogByStatus(status model.SnapshotStatus, logPath string) {
|
||||
logs := ""
|
||||
logs += fmt.Sprintf("Write 1Panel basic information: %s \n", status.PanelInfo)
|
||||
logs += fmt.Sprintf("Backup 1Panel system files: %s \n", status.Panel)
|
||||
@@ -1067,5 +1082,11 @@ func loadLogByStatus(status model.SnapshotStatus) string {
|
||||
logs += fmt.Sprintf("Snapshot size: %s \n", status.Size)
|
||||
logs += fmt.Sprintf("Upload snapshot file: %s \n", status.Upload)
|
||||
|
||||
return logs
|
||||
file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("write snapshot logs failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
_, _ = file.Write([]byte(logs))
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ func snapCompress(snap snapHelper, rootDir string) {
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
|
||||
}
|
||||
|
||||
func snapUpload(snap snapHelper, account string, file string) {
|
||||
func snapUpload(snap snapHelper, accounts string, file string) {
|
||||
source := path.Join(global.CONF.System.TmpDir, "system", path.Base(file))
|
||||
defer func() {
|
||||
global.LOG.Debugf("remove snapshot file %s", source)
|
||||
@@ -183,24 +183,22 @@ func snapUpload(snap snapHelper, account string, file string) {
|
||||
}()
|
||||
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusUploading})
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(account))
|
||||
accountMap, err := loadClientMap(accounts)
|
||||
if err != nil {
|
||||
snap.Status.Upload = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
|
||||
return
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
snap.Status.Upload = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
|
||||
return
|
||||
}
|
||||
target := path.Join(strings.TrimPrefix(backup.BackupPath, "/"), "system_snapshot", path.Base(file))
|
||||
global.LOG.Debugf("start upload snapshot to %s, dir: %s", backup.Type, target)
|
||||
if _, err := client.Upload(source, target); err != nil {
|
||||
snap.Status.Upload = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
|
||||
return
|
||||
targetAccounts := strings.Split(accounts, ",")
|
||||
for _, item := range targetAccounts {
|
||||
global.LOG.Debugf("start upload snapshot to %s, path: %s", item, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file)))
|
||||
if _, err := accountMap[item].client.Upload(source, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file))); err != nil {
|
||||
global.LOG.Debugf("upload to %s failed, err: %v", item, err)
|
||||
snap.Status.Upload = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("upload to %s successful", item)
|
||||
}
|
||||
snap.Status.Upload = constant.StatusDone
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
|
||||
|
||||
@@ -104,7 +104,7 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||
data.PubkeyAuthentication = strings.ReplaceAll(line, "PubkeyAuthentication ", "")
|
||||
}
|
||||
if strings.HasPrefix(line, "PermitRootLogin ") {
|
||||
data.PermitRootLogin = strings.ReplaceAll(line, "PermitRootLogin ", "")
|
||||
data.PermitRootLogin = strings.ReplaceAll(strings.ReplaceAll(line, "PermitRootLogin ", ""), "prohibit-password", "without-password")
|
||||
}
|
||||
if strings.HasPrefix(line, "UseDNS ") {
|
||||
data.UseDNS = strings.ReplaceAll(line, "UseDNS ", "")
|
||||
|
||||
@@ -301,7 +301,7 @@ func delNginxConfig(website model.Website, force bool) error {
|
||||
if err := fileOp.DeleteFile(configPath); err != nil {
|
||||
return err
|
||||
}
|
||||
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.PrimaryDomain)
|
||||
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.Alias)
|
||||
if fileOp.Stat(sitePath) {
|
||||
_ = fileOp.DeleteDir(sitePath)
|
||||
}
|
||||
@@ -556,7 +556,15 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
case constant.Deployment:
|
||||
server.RemoveDirective("location", []string{"/"})
|
||||
case constant.Runtime:
|
||||
server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"})
|
||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.Type == constant.RuntimePHP {
|
||||
server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"})
|
||||
} else {
|
||||
server.RemoveDirective("location", []string{"/"})
|
||||
}
|
||||
}
|
||||
server.UpdateRoot("/usr/share/nginx/html/stop")
|
||||
website.Status = constant.WebStopped
|
||||
@@ -594,10 +602,19 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
case constant.Runtime:
|
||||
server.UpdateRoot(rootIndex)
|
||||
localPath := ""
|
||||
if website.ProxyType == constant.RuntimeProxyUnix {
|
||||
localPath = path.Join(nginxInstall.Install.GetPath(), rootIndex, "index.php")
|
||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.Type == constant.RuntimePHP {
|
||||
if website.ProxyType == constant.RuntimeProxyUnix {
|
||||
localPath = path.Join(nginxInstall.Install.GetPath(), rootIndex, "index.php")
|
||||
}
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
} else {
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
}
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
}
|
||||
website.Status = constant.WebRunning
|
||||
now := time.Now()
|
||||
|
||||
@@ -12,6 +12,7 @@ const (
|
||||
Cos = "COS"
|
||||
Kodo = "KODO"
|
||||
WebDAV = "WebDAV"
|
||||
Local = "LOCAL"
|
||||
|
||||
OneDriveRedirectURI = "http://localhost/login/authorized"
|
||||
)
|
||||
|
||||
@@ -44,6 +44,12 @@ func Run() {
|
||||
if _, err := global.Cron.AddJob("@daily", job.NewAppStoreJob()); err != nil {
|
||||
global.LOG.Errorf("can not add appstore corn job: %s", err.Error())
|
||||
}
|
||||
|
||||
var backup model.BackupAccount
|
||||
_ = global.DB.Where("type = ?", "OneDrive").Find(&backup).Error
|
||||
if backup.ID != 0 {
|
||||
service.StartRefreshOneDriveToken()
|
||||
}
|
||||
global.Cron.Start()
|
||||
|
||||
var cronJobs []model.Cronjob
|
||||
@@ -60,11 +66,11 @@ func Run() {
|
||||
global.LOG.Errorf("start my cronjob failed, err: %v", err)
|
||||
}
|
||||
for i := 0; i < len(cronJobs); i++ {
|
||||
entryID, err := service.NewICronjobService().StartJob(&cronJobs[i])
|
||||
entryIDs, err := service.NewICronjobService().StartJob(&cronJobs[i])
|
||||
if err != nil {
|
||||
global.LOG.Errorf("start %s job %s failed, err: %v", cronJobs[i].Type, cronJobs[i].Name, err)
|
||||
}
|
||||
if err := repo.NewICronjobRepo().Update(cronJobs[i].ID, map[string]interface{}{"entry_id": entryID}); err != nil {
|
||||
if err := repo.NewICronjobRepo().Update(cronJobs[i].ID, map[string]interface{}{"entry_ids": entryIDs}); err != nil {
|
||||
global.LOG.Errorf("update cronjob %s %s failed, err: %v", cronJobs[i].Type, cronJobs[i].Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var (
|
||||
CACHE *badger_db.Cache
|
||||
Viper *viper.Viper
|
||||
|
||||
Cron *cron.Cron
|
||||
MonitorCronID int
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
OneDriveCronID cron.EntryID
|
||||
)
|
||||
|
||||
+75
-19
@@ -2,7 +2,10 @@ package hook
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
@@ -70,50 +73,103 @@ func Init() {
|
||||
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=true/d' /usr/local/bin/1pctl", sudo)
|
||||
}
|
||||
|
||||
handleCronjobStatus()
|
||||
handleSnapStatus()
|
||||
loadLocalDir()
|
||||
}
|
||||
|
||||
func handleSnapStatus() {
|
||||
msgFailed := "the task was interrupted due to the restart of the 1panel service"
|
||||
_ = global.DB.Model(&model.Snapshot{}).Where("status = ?", "OnSaveData").
|
||||
Updates(map[string]interface{}{"status": constant.StatusSuccess}).Error
|
||||
|
||||
_ = global.DB.Model(&model.Snapshot{}).Where("status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed,
|
||||
"message": msgFailed,
|
||||
}).Error
|
||||
|
||||
_ = global.DB.Model(&model.Snapshot{}).Where("recover_status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"recover_status": constant.StatusFailed,
|
||||
"recover_message": msgFailed,
|
||||
}).Error
|
||||
|
||||
_ = global.DB.Model(&model.Snapshot{}).Where("rollback_status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"rollback_status": constant.StatusFailed,
|
||||
"rollback_message": msgFailed,
|
||||
}).Error
|
||||
|
||||
snapRepo := repo.NewISnapshotRepo()
|
||||
snaps, _ := snapRepo.GetList()
|
||||
for _, snap := range snaps {
|
||||
if snap.Status == "OnSaveData" {
|
||||
_ = snapRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||
}
|
||||
if snap.Status == constant.StatusWaiting {
|
||||
_ = snapRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": "the task was interrupted due to the restart of the 1panel service"})
|
||||
}
|
||||
}
|
||||
|
||||
status, _ := snapRepo.GetStatusList()
|
||||
for _, statu := range status {
|
||||
for _, item := range status {
|
||||
updates := make(map[string]interface{})
|
||||
if statu.Panel == constant.StatusRunning {
|
||||
if item.Panel == constant.StatusRunning {
|
||||
updates["panel"] = constant.StatusFailed
|
||||
}
|
||||
if statu.PanelInfo == constant.StatusRunning {
|
||||
if item.PanelInfo == constant.StatusRunning {
|
||||
updates["panel_info"] = constant.StatusFailed
|
||||
}
|
||||
if statu.DaemonJson == constant.StatusRunning {
|
||||
if item.DaemonJson == constant.StatusRunning {
|
||||
updates["daemon_json"] = constant.StatusFailed
|
||||
}
|
||||
if statu.AppData == constant.StatusRunning {
|
||||
if item.AppData == constant.StatusRunning {
|
||||
updates["app_data"] = constant.StatusFailed
|
||||
}
|
||||
if statu.PanelData == constant.StatusRunning {
|
||||
if item.PanelData == constant.StatusRunning {
|
||||
updates["panel_data"] = constant.StatusFailed
|
||||
}
|
||||
if statu.BackupData == constant.StatusRunning {
|
||||
if item.BackupData == constant.StatusRunning {
|
||||
updates["backup_data"] = constant.StatusFailed
|
||||
}
|
||||
if statu.Compress == constant.StatusRunning {
|
||||
if item.Compress == constant.StatusRunning {
|
||||
updates["compress"] = constant.StatusFailed
|
||||
}
|
||||
if statu.Upload == constant.StatusUploading {
|
||||
if item.Upload == constant.StatusUploading {
|
||||
updates["upload"] = constant.StatusFailed
|
||||
}
|
||||
if len(updates) != 0 {
|
||||
_ = snapRepo.UpdateStatus(statu.ID, updates)
|
||||
_ = snapRepo.UpdateStatus(item.ID, updates)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleCronjobStatus() {
|
||||
_ = global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed,
|
||||
"message": "the task was interrupted due to the restart of the 1panel service",
|
||||
}).Error
|
||||
}
|
||||
|
||||
func loadLocalDir() {
|
||||
var backup model.BackupAccount
|
||||
_ = global.DB.Where("type = ?", "LOCAL").First(&backup).Error
|
||||
if backup.ID == 0 {
|
||||
global.LOG.Errorf("no such backup account `%s` in db", "LOCAL")
|
||||
return
|
||||
}
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
global.LOG.Errorf("json unmarshal backup.Vars: %v failed, err: %v", backup.Vars, err)
|
||||
return
|
||||
}
|
||||
if _, ok := varMap["dir"]; !ok {
|
||||
global.LOG.Error("load local backup dir failed")
|
||||
return
|
||||
}
|
||||
baseDir, ok := varMap["dir"].(string)
|
||||
if ok {
|
||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
||||
global.LOG.Errorf("mkdir %s failed, err: %v", baseDir, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
global.CONF.System.Backup = baseDir
|
||||
return
|
||||
}
|
||||
global.LOG.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ func Init() {
|
||||
migrations.AddTableDatabasePostgresql,
|
||||
migrations.AddPostgresqlSuperUser,
|
||||
migrations.UpdateCronjobWithWebsite,
|
||||
migrations.UpdateOneDriveToken,
|
||||
migrations.UpdateCronjobSpec,
|
||||
migrations.UpdateBackupRecordPath,
|
||||
migrations.UpdateSnapshotRecords,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -85,7 +85,7 @@ var AddTableSetting = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.Setting{Key: "Language", Value: "zh"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "Theme", Value: "light"}).Error; err != nil {
|
||||
if err := tx.Create(&model.Setting{Key: "Theme", Value: "auto"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -188,3 +198,247 @@ var UpdateCronjobWithWebsite = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateOneDriveToken = &gormigrate.Migration{
|
||||
ID: "20240117-update-onedrive-token",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
backup model.BackupAccount
|
||||
clientSetting model.Setting
|
||||
secretSetting model.Setting
|
||||
)
|
||||
_ = tx.Where("type = ?", "OneDrive").First(&backup).Error
|
||||
if backup.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(backup.Credential) == 0 {
|
||||
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = tx.Where("key = ?", "OneDriveID").First(&clientSetting).Error
|
||||
if clientSetting.ID == 0 {
|
||||
global.LOG.Error("system configuration lacks clientID information, please retry.")
|
||||
return nil
|
||||
}
|
||||
_ = tx.Where("key = ?", "OneDriveSc").First(&secretSetting).Error
|
||||
if secretSetting.ID == 0 {
|
||||
global.LOG.Error("system configuration lacks clientID information, please retry.")
|
||||
return nil
|
||||
}
|
||||
idItem, _ := base64.StdEncoding.DecodeString(clientSetting.Value)
|
||||
global.CONF.System.OneDriveID = string(idItem)
|
||||
scItem, _ := base64.StdEncoding.DecodeString(secretSetting.Value)
|
||||
global.CONF.System.OneDriveSc = string(scItem)
|
||||
|
||||
varMap := make(map[string]interface{})
|
||||
varMap["isCN"] = false
|
||||
varMap["client_id"] = global.CONF.System.OneDriveID
|
||||
varMap["client_secret"] = global.CONF.System.OneDriveSc
|
||||
varMap["redirect_uri"] = constant.OneDriveRedirectURI
|
||||
varMap["refresh_token"] = backup.Credential
|
||||
token, refreshToken, err := client.RefreshToken("refresh_token", varMap)
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
if err != nil {
|
||||
varMap["refresh_msg"] = err.Error()
|
||||
varMap["refresh_status"] = constant.StatusFailed
|
||||
}
|
||||
varMap["refresh_token"] = refreshToken
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
if err := tx.Model(&model.BackupAccount{}).
|
||||
Where("id = ?", backup.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"credential": token,
|
||||
"vars": string(itemVars),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
ID: "20240122-update-cronjob-spec",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Cronjob{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.AutoMigrate(&model.BackupRecord{}); err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
jobs []model.Cronjob
|
||||
backupAccounts []model.BackupAccount
|
||||
)
|
||||
mapAccount := make(map[uint]model.BackupAccount)
|
||||
if err := tx.Find(&jobs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
_ = tx.Find(&backupAccounts).Error
|
||||
for _, item := range backupAccounts {
|
||||
mapAccount[item.ID] = item
|
||||
}
|
||||
for _, job := range jobs {
|
||||
if job.KeepLocal && mapAccount[uint(job.TargetDirID)].Type != constant.Local {
|
||||
if err := tx.Model(&model.Cronjob{}).
|
||||
Where("id = ?", job.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"backup_accounts": fmt.Sprintf("%v,%v", mapAccount[uint(job.TargetDirID)].Type, constant.Local),
|
||||
"default_download": constant.Local,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
job.DefaultDownload = constant.Local
|
||||
} else {
|
||||
if err := tx.Model(&model.Cronjob{}).
|
||||
Where("id = ?", job.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"backup_accounts": mapAccount[uint(job.TargetDirID)].Type,
|
||||
"default_download": mapAccount[uint(job.TargetDirID)].Type,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
job.DefaultDownload = mapAccount[uint(job.TargetDirID)].Type
|
||||
}
|
||||
if job.Type != "directory" && job.Type != "database" && job.Type != "website" && job.Type != "app" && job.Type != "snapshot" && job.Type != "log" {
|
||||
continue
|
||||
}
|
||||
|
||||
itemPath := mapAccount[uint(job.TargetDirID)].BackupPath
|
||||
if job.DefaultDownload == constant.Local || itemPath == "/" {
|
||||
itemPath = ""
|
||||
} else {
|
||||
itemPath = strings.TrimPrefix(itemPath, "/") + "/"
|
||||
}
|
||||
|
||||
var records []model.JobRecords
|
||||
_ = tx.Where("cronjob_id = ?", job.ID).Find(&records).Error
|
||||
for _, record := range records {
|
||||
if job.Type == "snapshot" {
|
||||
var snaps []model.Snapshot
|
||||
_ = tx.Where("name like ?", "snapshot_"+"%").Find(&snaps).Error
|
||||
for _, snap := range snaps {
|
||||
item := model.BackupRecord{
|
||||
From: "cronjob",
|
||||
CronjobID: job.ID,
|
||||
Type: "snapshot",
|
||||
Name: job.Name,
|
||||
FileDir: "system_snapshot",
|
||||
FileName: snap.Name + ".tar.gz",
|
||||
Source: snap.From,
|
||||
BackupType: snap.From,
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
}
|
||||
continue
|
||||
}
|
||||
if job.Type == "log" {
|
||||
item := model.BackupRecord{
|
||||
From: "cronjob",
|
||||
CronjobID: job.ID,
|
||||
Type: "log",
|
||||
Name: job.Name,
|
||||
FileDir: path.Dir(strings.TrimPrefix(record.File, itemPath)),
|
||||
FileName: path.Base(record.File),
|
||||
Source: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
continue
|
||||
}
|
||||
if job.Type == "directory" {
|
||||
item := model.BackupRecord{
|
||||
From: "cronjob",
|
||||
CronjobID: job.ID,
|
||||
Type: "directory",
|
||||
Name: job.Name,
|
||||
FileDir: path.Dir(strings.TrimPrefix(record.File, itemPath)),
|
||||
FileName: path.Base(record.File),
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
}
|
||||
if record.FromLocal {
|
||||
item.Source = constant.Local
|
||||
} else {
|
||||
item.Source = mapAccount[uint(job.TargetDirID)].Type
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
continue
|
||||
}
|
||||
if strings.Contains(record.File, ",") {
|
||||
files := strings.Split(record.File, ",")
|
||||
for _, file := range files {
|
||||
_ = tx.Model(&model.BackupRecord{}).
|
||||
Where("file_dir = ? AND file_name = ?", path.Dir(strings.TrimPrefix(file, itemPath)), path.Base(file)).
|
||||
Updates(map[string]interface{}{"cronjob_id": job.ID, "from": "cronjob"}).Error
|
||||
}
|
||||
} else {
|
||||
_ = tx.Model(&model.BackupRecord{}).
|
||||
Where("file_dir = ? AND file_name = ?", path.Dir(strings.TrimPrefix(record.File, itemPath)), path.Base(record.File)).
|
||||
Updates(map[string]interface{}{"cronjob_id": job.ID, "from": "cronjob"}).Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN spec_type;").Error
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN week;").Error
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN day;").Error
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN hour;").Error
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN minute;").Error
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN second;").Error
|
||||
_ = tx.Exec("ALTER TABLE cronjobs DROP COLUMN entry_id;").Error
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateBackupRecordPath = &gormigrate.Migration{
|
||||
ID: "20240124-update-cronjob-spec",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
backupRecords []model.BackupRecord
|
||||
localAccount model.BackupAccount
|
||||
)
|
||||
|
||||
_ = tx.Where("type = ?", "LOCAL").First(&localAccount).Error
|
||||
if localAccount.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
varMap := make(map[string]string)
|
||||
if err := json.Unmarshal([]byte(localAccount.Vars), &varMap); err != nil {
|
||||
return err
|
||||
}
|
||||
dir, ok := varMap["dir"]
|
||||
if !ok {
|
||||
return errors.New("load local backup dir failed")
|
||||
}
|
||||
if dir != "/" {
|
||||
dir += "/"
|
||||
}
|
||||
_ = tx.Where("source = ?", "LOCAL").Find(&backupRecords).Error
|
||||
for _, record := range backupRecords {
|
||||
_ = tx.Model(&model.BackupRecord{}).
|
||||
Where("id = ?", record.ID).
|
||||
Updates(map[string]interface{}{"file_dir": strings.TrimPrefix(record.FileDir, dir)}).Error
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateSnapshotRecords = &gormigrate.Migration{
|
||||
ID: "20240124-update-snapshot-records",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Snapshot{}); err != nil {
|
||||
return err
|
||||
}
|
||||
var snaps []model.Snapshot
|
||||
_ = tx.Find(&snaps).Error
|
||||
for _, snap := range snaps {
|
||||
_ = tx.Model(&model.Snapshot{}).
|
||||
Where("id = ?", snap.ID).
|
||||
Updates(map[string]interface{}{"default_download": snap.From}).Error
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.GET("/backup/search", baseApi.ListBackup)
|
||||
settingRouter.GET("/backup/onedrive", baseApi.LoadOneDriveInfo)
|
||||
settingRouter.POST("/backup/backup", baseApi.Backup)
|
||||
settingRouter.POST("/backup/refresh/onedrive", baseApi.RefreshOneDriveToken)
|
||||
settingRouter.POST("/backup/recover", baseApi.Recover)
|
||||
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)
|
||||
settingRouter.POST("/backup/search/files", baseApi.LoadFilesFromBackup)
|
||||
@@ -53,6 +54,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.POST("/backup/del", baseApi.DeleteBackup)
|
||||
settingRouter.POST("/backup/update", baseApi.UpdateBackup)
|
||||
settingRouter.POST("/backup/record/search", baseApi.SearchBackupRecords)
|
||||
settingRouter.POST("/backup/record/search/bycronjob", baseApi.SearchBackupRecordsByCronjob)
|
||||
settingRouter.POST("/backup/record/download", baseApi.DownloadRecord)
|
||||
settingRouter.POST("/backup/record/del", baseApi.DeleteBackupRecord)
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ type cosClient struct {
|
||||
}
|
||||
|
||||
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
region := loadParamFromVars("region", true, vars)
|
||||
accessKey := loadParamFromVars("accessKey", true, vars)
|
||||
secretKey := loadParamFromVars("secretKey", true, vars)
|
||||
bucket := loadParamFromVars("bucket", true, vars)
|
||||
scType := loadParamFromVars("scType", true, vars)
|
||||
region := loadParamFromVars("region", vars)
|
||||
accessKey := loadParamFromVars("accessKey", vars)
|
||||
secretKey := loadParamFromVars("secretKey", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
scType := loadParamFromVars("scType", vars)
|
||||
if len(scType) == 0 {
|
||||
scType = "Standard"
|
||||
}
|
||||
|
||||
@@ -6,15 +6,13 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
func loadParamFromVars(key string, isString bool, vars map[string]interface{}) string {
|
||||
func loadParamFromVars(key string, vars map[string]interface{}) string {
|
||||
if _, ok := vars[key]; !ok {
|
||||
if key != "bucket" {
|
||||
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if isString {
|
||||
return vars[key].(string)
|
||||
}
|
||||
return fmt.Sprintf("%v", vars[key].(float64))
|
||||
|
||||
return fmt.Sprintf("%v", vars[key])
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ type kodoClient struct {
|
||||
}
|
||||
|
||||
func NewKodoClient(vars map[string]interface{}) (*kodoClient, error) {
|
||||
accessKey := loadParamFromVars("accessKey", true, vars)
|
||||
secretKey := loadParamFromVars("secretKey", true, vars)
|
||||
bucket := loadParamFromVars("bucket", true, vars)
|
||||
domain := loadParamFromVars("domain", true, vars)
|
||||
accessKey := loadParamFromVars("accessKey", vars)
|
||||
secretKey := loadParamFromVars("secretKey", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
domain := loadParamFromVars("domain", vars)
|
||||
|
||||
conn := auth.New(accessKey, secretKey)
|
||||
cfg := storage.Config{
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
type localClient struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
func NewLocalClient(vars map[string]interface{}) (*localClient, error) {
|
||||
dir := loadParamFromVars("dir", vars)
|
||||
return &localClient{dir: dir}, nil
|
||||
}
|
||||
|
||||
func (c localClient) ListBuckets() ([]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c localClient) Exist(file string) (bool, error) {
|
||||
_, err := os.Stat(path.Join(c.dir, file))
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (c localClient) Size(file string) (int64, error) {
|
||||
fileInfo, err := os.Stat(path.Join(c.dir, file))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fileInfo.Size(), nil
|
||||
}
|
||||
|
||||
func (c localClient) Delete(file string) (bool, error) {
|
||||
if err := os.RemoveAll(path.Join(c.dir, file)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c localClient) Upload(src, target string) (bool, error) {
|
||||
targetFilePath := path.Join(c.dir, target)
|
||||
if _, err := os.Stat(path.Dir(targetFilePath)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(targetFilePath), os.ModePerm); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("\\cp -f %s %s", src, path.Join(c.dir, target))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c localClient) Download(src, target string) (bool, error) {
|
||||
localPath := path.Join(c.dir, src)
|
||||
if _, err := os.Stat(path.Dir(target)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(target), os.ModePerm); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("\\cp -f %s %s", localPath, target)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c localClient) ListObjects(prefix string) ([]string, error) {
|
||||
var files []string
|
||||
itemPath := path.Join(c.dir, prefix)
|
||||
if _, err := os.Stat(itemPath); err != nil {
|
||||
return files, nil
|
||||
}
|
||||
if err := filepath.Walk(itemPath, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
files = append(files, info.Name())
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
@@ -19,10 +19,10 @@ type minIoClient struct {
|
||||
}
|
||||
|
||||
func NewMinIoClient(vars map[string]interface{}) (*minIoClient, error) {
|
||||
endpoint := loadParamFromVars("endpoint", true, vars)
|
||||
accessKeyID := loadParamFromVars("accessKey", true, vars)
|
||||
secretAccessKey := loadParamFromVars("secretKey", true, vars)
|
||||
bucket := loadParamFromVars("bucket", true, vars)
|
||||
endpoint := loadParamFromVars("endpoint", vars)
|
||||
accessKeyID := loadParamFromVars("accessKey", vars)
|
||||
secretAccessKey := loadParamFromVars("secretKey", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
ssl := strings.Split(endpoint, ":")[0]
|
||||
if len(ssl) == 0 || (ssl != "https" && ssl != "http") {
|
||||
return nil, constant.ErrInvalidParams
|
||||
|
||||
@@ -15,9 +15,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@@ -27,21 +25,18 @@ type oneDriveClient struct {
|
||||
}
|
||||
|
||||
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
|
||||
token := loadParamFromVars("accessToken", true, vars)
|
||||
token := loadParamFromVars("accessToken", vars)
|
||||
isCN := loadParamFromVars("isCN", vars)
|
||||
ctx := context.Background()
|
||||
|
||||
newToken, err := refreshToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = global.DB.Model(&model.Group{}).Where("type = ?", "OneDrive").Updates(map[string]interface{}{"credential": newToken}).Error
|
||||
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: newToken},
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
client := odsdk.NewClient(tc)
|
||||
if isCN == "true" {
|
||||
client.BaseURL, _ = url.Parse("https://microsoftgraph.chinacloudapi.cn/v1.0/")
|
||||
}
|
||||
return &oneDriveClient{client: *client}, nil
|
||||
}
|
||||
|
||||
@@ -108,78 +103,24 @@ func (o oneDriveClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
file, err := os.Open(src)
|
||||
folderID, err := o.loadIDByPath(path.Dir(target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
fileInfo, err := file.Stat()
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return false, errors.New("Only file is allowed to be uploaded here.")
|
||||
}
|
||||
fileName := fileInfo.Name()
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
folderID, err := o.loadIDByPath(path.Dir(target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
var isOk bool
|
||||
if fileInfo.Size() < 4*1024*1024 {
|
||||
isOk, err = o.upSmall(ctx, src, folderID, fileInfo.Size())
|
||||
} else {
|
||||
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
|
||||
}
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), fileName)
|
||||
sessionCreationRequestInside := NewUploadSessionCreationRequest{
|
||||
ConflictBehavior: "rename",
|
||||
}
|
||||
|
||||
sessionCreationRequest := struct {
|
||||
Item NewUploadSessionCreationRequest `json:"item"`
|
||||
DeferCommit bool `json:"deferCommit"`
|
||||
}{sessionCreationRequestInside, false}
|
||||
|
||||
sessionCreationReq, err := o.client.NewRequest("POST", apiURL, sessionCreationRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var sessionCreationResp *NewUploadSessionCreationResponse
|
||||
err = o.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("session creation failed %w", err)
|
||||
}
|
||||
|
||||
fileSessionUploadUrl := sessionCreationResp.UploadURL
|
||||
|
||||
sizePerSplit := int64(3200 * 1024)
|
||||
buffer := make([]byte, 3200*1024)
|
||||
splitCount := fileSize / sizePerSplit
|
||||
if fileSize%sizePerSplit != 0 {
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
var fileUploadResp *UploadSessionUploadResponse
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if int64(length) < sizePerSplit {
|
||||
bufferLast := buffer[:length]
|
||||
buffer = bufferLast
|
||||
}
|
||||
sessionFileUploadReq, err := o.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileUploadResp.Id == "" {
|
||||
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return isOk, err
|
||||
}
|
||||
|
||||
func (o oneDriveClient) Download(src, target string) (bool, error) {
|
||||
@@ -253,38 +194,52 @@ func (o *oneDriveClient) loadIDByPath(path string) (string, error) {
|
||||
return driveItem.Id, nil
|
||||
}
|
||||
|
||||
func refreshToken(oldToken string) (string, error) {
|
||||
func RefreshToken(grantType string, varMap map[string]interface{}) (string, string, error) {
|
||||
data := url.Values{}
|
||||
data.Set("client_id", global.CONF.System.OneDriveID)
|
||||
data.Set("client_secret", global.CONF.System.OneDriveSc)
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", oldToken)
|
||||
data.Set("redirect_uri", constant.OneDriveRedirectURI)
|
||||
isCN := loadParamFromVars("isCN", varMap)
|
||||
data.Set("client_id", loadParamFromVars("client_id", varMap))
|
||||
data.Set("client_secret", loadParamFromVars("client_secret", varMap))
|
||||
if grantType == "refresh_token" {
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", loadParamFromVars("refresh_token", varMap))
|
||||
} else {
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", loadParamFromVars("code", varMap))
|
||||
}
|
||||
data.Set("redirect_uri", loadParamFromVars("redirect_uri", varMap))
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
url := "https://login.microsoftonline.com/common/oauth2/v2.0/token"
|
||||
if isCN == "true" {
|
||||
url = "https://login.chinacloudapi.cn/common/oauth2/v2.0/token"
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("request for access token failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("request for access token failed, err: %v", err)
|
||||
}
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tokenMap := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
|
||||
return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
}
|
||||
accessToken, ok := tokenMap["access_token"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("no such access token in response")
|
||||
return "", "", errors.New("no such access token in response")
|
||||
}
|
||||
return accessToken, nil
|
||||
refreshToken, ok := tokenMap["refresh_token"].(string)
|
||||
if !ok {
|
||||
return "", "", errors.New("no such access token in response")
|
||||
}
|
||||
return accessToken, refreshToken, nil
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) createFolder(parent string) error {
|
||||
@@ -344,3 +299,89 @@ func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOf
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
file, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, fileSize)
|
||||
_, _ = file.Read(buffer)
|
||||
fileReader := bytes.NewReader(buffer)
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/content?@microsoft.graph.conflictBehavior=rename", url.PathEscape(folderID), path.Base(srcPath))
|
||||
|
||||
mimeType := files.GetMimeType(srcPath)
|
||||
req, err := o.client.NewFileUploadRequest(apiURL, mimeType, fileReader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var response *DriveItem
|
||||
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
|
||||
return false, fmt.Errorf("do request for list failed, err: %v", err)
|
||||
}
|
||||
fmt.Println(response)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
file, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), path.Base(srcPath))
|
||||
sessionCreationRequestInside := NewUploadSessionCreationRequest{
|
||||
ConflictBehavior: "rename",
|
||||
}
|
||||
|
||||
sessionCreationRequest := struct {
|
||||
Item NewUploadSessionCreationRequest `json:"item"`
|
||||
DeferCommit bool `json:"deferCommit"`
|
||||
}{sessionCreationRequestInside, false}
|
||||
|
||||
sessionCreationReq, err := o.client.NewRequest("POST", apiURL, sessionCreationRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var sessionCreationResp *NewUploadSessionCreationResponse
|
||||
err = o.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("session creation failed %w", err)
|
||||
}
|
||||
|
||||
fileSessionUploadUrl := sessionCreationResp.UploadURL
|
||||
|
||||
sizePerSplit := int64(3200 * 1024)
|
||||
buffer := make([]byte, 3200*1024)
|
||||
splitCount := fileSize / sizePerSplit
|
||||
if fileSize%sizePerSplit != 0 {
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
var fileUploadResp *UploadSessionUploadResponse
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if int64(length) < sizePerSplit {
|
||||
bufferLast := buffer[:length]
|
||||
buffer = bufferLast
|
||||
}
|
||||
sessionFileUploadReq, err := o.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileUploadResp.Id == "" {
|
||||
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ type ossClient struct {
|
||||
}
|
||||
|
||||
func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
|
||||
endpoint := loadParamFromVars("endpoint", true, vars)
|
||||
accessKey := loadParamFromVars("accessKey", true, vars)
|
||||
secretKey := loadParamFromVars("secretKey", true, vars)
|
||||
bucketStr := loadParamFromVars("bucket", true, vars)
|
||||
scType := loadParamFromVars("scType", true, vars)
|
||||
endpoint := loadParamFromVars("endpoint", vars)
|
||||
accessKey := loadParamFromVars("accessKey", vars)
|
||||
secretKey := loadParamFromVars("secretKey", vars)
|
||||
bucketStr := loadParamFromVars("bucket", vars)
|
||||
scType := loadParamFromVars("scType", vars)
|
||||
if len(scType) == 0 {
|
||||
scType = "Standard"
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ type s3Client struct {
|
||||
}
|
||||
|
||||
func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
accessKey := loadParamFromVars("accessKey", true, vars)
|
||||
secretKey := loadParamFromVars("secretKey", true, vars)
|
||||
endpoint := loadParamFromVars("endpoint", true, vars)
|
||||
region := loadParamFromVars("region", true, vars)
|
||||
bucket := loadParamFromVars("bucket", true, vars)
|
||||
scType := loadParamFromVars("scType", true, vars)
|
||||
accessKey := loadParamFromVars("accessKey", vars)
|
||||
secretKey := loadParamFromVars("secretKey", vars)
|
||||
endpoint := loadParamFromVars("endpoint", vars)
|
||||
region := loadParamFromVars("region", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
scType := loadParamFromVars("scType", vars)
|
||||
if len(scType) == 0 {
|
||||
scType = "Standard"
|
||||
}
|
||||
@@ -97,6 +97,10 @@ func (s s3Client) Delete(path string) (bool, error) {
|
||||
}
|
||||
|
||||
func (s s3Client) Upload(src, target string) (bool, error) {
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -104,6 +108,9 @@ func (s s3Client) Upload(src, target string) (bool, error) {
|
||||
defer file.Close()
|
||||
|
||||
uploader := s3manager.NewUploader(&s.Sess)
|
||||
if fileInfo.Size() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = fileInfo.Size() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
if _, err := uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(target),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
@@ -19,11 +19,11 @@ type sftpClient struct {
|
||||
}
|
||||
|
||||
func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
|
||||
address := loadParamFromVars("address", true, vars)
|
||||
port := loadParamFromVars("port", false, vars)
|
||||
password := loadParamFromVars("password", true, vars)
|
||||
username := loadParamFromVars("username", true, vars)
|
||||
bucket := loadParamFromVars("bucket", true, vars)
|
||||
address := loadParamFromVars("address", vars)
|
||||
port := loadParamFromVars("port", vars)
|
||||
password := loadParamFromVars("password", vars)
|
||||
username := loadParamFromVars("username", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
|
||||
auth := []ssh.AuthMethod{ssh.Password(password)}
|
||||
clientConfig := &ssh.ClientConfig{
|
||||
@@ -56,7 +56,7 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
targetFilePath := s.bucket + "/" + target
|
||||
targetFilePath := path.Join(s.bucket, target)
|
||||
targetDir, _ := path.Split(targetFilePath)
|
||||
if _, err = client.Stat(targetDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -67,22 +67,14 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
dstFile, err := client.Create(targetFilePath)
|
||||
dstFile, err := client.Create(path.Join(s.bucket, target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
reader := bufio.NewReaderSize(srcFile, 128*1024*1024)
|
||||
for {
|
||||
chunk, err := reader.Peek(8 * 1024 * 1024)
|
||||
if len(chunk) != 0 {
|
||||
_, _ = dstFile.Write(chunk)
|
||||
_, _ = reader.Discard(len(chunk))
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -103,6 +95,7 @@ func (s sftpClient) Download(src, target string) (bool, error) {
|
||||
}
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
srcFile, err := client.Open(s.bucket + "/" + src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -115,13 +108,13 @@ func (s sftpClient) Download(src, target string) (bool, error) {
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err = srcFile.WriteTo(dstFile); err != nil {
|
||||
if _, err = io.Copy(dstFile, srcFile); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (s sftpClient) Exist(path string) (bool, error) {
|
||||
func (s sftpClient) Exist(filePath string) (bool, error) {
|
||||
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -133,7 +126,7 @@ func (s sftpClient) Exist(path string) (bool, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
srcFile, err := client.Open(s.bucket + "/" + path)
|
||||
srcFile, err := client.Open(path.Join(s.bucket, filePath))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
@@ -145,7 +138,7 @@ func (s sftpClient) Exist(path string) (bool, error) {
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (s sftpClient) Size(path string) (int64, error) {
|
||||
func (s sftpClient) Size(filePath string) (int64, error) {
|
||||
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -157,7 +150,7 @@ func (s sftpClient) Size(path string) (int64, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
files, err := client.Stat(s.bucket + "/" + path)
|
||||
files, err := client.Stat(path.Join(s.bucket, filePath))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -176,8 +169,7 @@ func (s sftpClient) Delete(filePath string) (bool, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
targetFilePath := s.bucket + "/" + filePath
|
||||
if err := client.Remove(targetFilePath); err != nil {
|
||||
if err := client.Remove(path.Join(s.bucket, filePath)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
@@ -195,7 +187,7 @@ func (s sftpClient) ListObjects(prefix string) ([]string, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
files, err := client.ReadDir(s.bucket + "/" + prefix)
|
||||
files, err := client.ReadDir(path.Join(s.bucket, prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/studio-b12/gowebdav"
|
||||
)
|
||||
@@ -14,13 +17,25 @@ type webDAVClient struct {
|
||||
}
|
||||
|
||||
func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
|
||||
address := loadParamFromVars("address", true, vars)
|
||||
port := loadParamFromVars("port", false, vars)
|
||||
password := loadParamFromVars("password", true, vars)
|
||||
username := loadParamFromVars("username", true, vars)
|
||||
bucket := loadParamFromVars("bucket", true, vars)
|
||||
address := loadParamFromVars("address", vars)
|
||||
port := loadParamFromVars("port", vars)
|
||||
password := loadParamFromVars("password", vars)
|
||||
username := loadParamFromVars("username", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
|
||||
client := gowebdav.NewClient(fmt.Sprintf("%s:%s", address, port), username, password)
|
||||
url := fmt.Sprintf("%s:%s", address, port)
|
||||
if len(port) == 0 {
|
||||
url = address
|
||||
}
|
||||
client := gowebdav.NewClient(url, username, password)
|
||||
tlsConfig := &tls.Config{}
|
||||
if strings.HasPrefix(address, "https") {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
var transport http.RoundTripper = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
client.SetTransport(transport)
|
||||
if err := client.Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,22 +44,13 @@ func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
|
||||
|
||||
func (s webDAVClient) Upload(src, target string) (bool, error) {
|
||||
targetFilePath := s.Bucket + "/" + target
|
||||
fileInfo, err := os.Stat(src)
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 50M
|
||||
if fileInfo.Size() > 52428800 {
|
||||
bytes, _ := os.ReadFile(src)
|
||||
if err := s.client.Write(targetFilePath, bytes, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
file, _ := os.Open(src)
|
||||
defer file.Close()
|
||||
defer srcFile.Close()
|
||||
|
||||
if err := s.client.WriteStream(targetFilePath, file, 0644); err != nil {
|
||||
if err := s.client.WriteStream(targetFilePath, srcFile, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
@@ -72,16 +78,8 @@ func (s webDAVClient) Download(src, target string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
// 50M
|
||||
if info.Size() > 52428800 {
|
||||
reader, _ := s.client.ReadStream(srcPath)
|
||||
if _, err := io.Copy(file, reader); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
bytes, _ := s.client.Read(srcPath)
|
||||
if err := os.WriteFile(target, bytes, 0644); err != nil {
|
||||
reader, _ := s.client.ReadStream(srcPath)
|
||||
if _, err := io.Copy(file, reader); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, err
|
||||
|
||||
@@ -18,6 +18,8 @@ type CloudStorageClient interface {
|
||||
|
||||
func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
|
||||
switch backupType {
|
||||
case constant.Local:
|
||||
return client.NewLocalClient(vars)
|
||||
case constant.S3:
|
||||
return client.NewS3Client(vars)
|
||||
case constant.OSS:
|
||||
|
||||
+14
-11
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -74,24 +74,27 @@ func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExecCronjobWithTimeOut(cmdStr string, workdir string, timeout time.Duration) (string, error) {
|
||||
func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
cmd.Dir = workdir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
output := new(bytes.Buffer)
|
||||
cmd.Stdout = io.MultiWriter(output, cmd.Stdout)
|
||||
cmd.Stderr = io.MultiWriter(output, cmd.Stderr)
|
||||
cmd.Stdout = file
|
||||
cmd.Stderr = file
|
||||
|
||||
err := cmd.Run()
|
||||
err = cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
return buserr.New(constant.ErrCmdTimeout)
|
||||
}
|
||||
|
||||
return output.String(), err
|
||||
return err
|
||||
}
|
||||
|
||||
func Execf(cmdStr string, a ...interface{}) (string, error) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/joho/godotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -116,15 +115,32 @@ type Service struct {
|
||||
Image string `yaml:"image"`
|
||||
}
|
||||
|
||||
func GetDockerComposeImages(data []byte) ([]string, error) {
|
||||
var dc ComposeProject
|
||||
err := yaml.Unmarshal(data, &dc)
|
||||
func GetDockerComposeImages(projectName string, env, yml []byte) ([]string, error) {
|
||||
var (
|
||||
configFiles []types.ConfigFile
|
||||
images []string
|
||||
)
|
||||
configFiles = append(configFiles, types.ConfigFile{
|
||||
Filename: "docker-compose.yml",
|
||||
Content: yml},
|
||||
)
|
||||
envMap, err := godotenv.UnmarshalBytes(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details := types.ConfigDetails{
|
||||
ConfigFiles: configFiles,
|
||||
Environment: envMap,
|
||||
}
|
||||
|
||||
var images []string
|
||||
for _, service := range dc.Services {
|
||||
project, err := loader.LoadWithContext(context.Background(), details, func(options *loader.Options) {
|
||||
options.SetProjectName(projectName, true)
|
||||
options.ResolvePaths = true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, service := range project.AllServices() {
|
||||
images = append(images, service.Image)
|
||||
}
|
||||
return images, nil
|
||||
|
||||
@@ -319,7 +319,7 @@ func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||
}
|
||||
|
||||
func (f FileOp) Mv(oldPath, dstPath string) error {
|
||||
cmdStr := fmt.Sprintf("mv %s %s", oldPath, dstPath)
|
||||
cmdStr := fmt.Sprintf(`mv "%s" "%s"`, oldPath, dstPath)
|
||||
if err := cmd.ExecCmd(cmdStr); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -606,12 +606,12 @@ func ZipFile(files []archiver.File, dst afero.File) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Method = zip.Deflate
|
||||
hdr.Name = file.NameInArchive
|
||||
if file.IsDir() {
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
hdr.Name += "/"
|
||||
}
|
||||
hdr.Method = zip.Store
|
||||
}
|
||||
w, err := zw.CreateHeader(hdr)
|
||||
if err != nil {
|
||||
|
||||
@@ -106,6 +106,7 @@ func (f *Firewall) ListPort() ([]FireInfo, error) {
|
||||
}
|
||||
itemRule := f.loadInfo(rule)
|
||||
if len(itemRule.Port) != 0 && itemRule.Family == "ipv4" {
|
||||
itemRule.Family = ""
|
||||
datas = append(datas, itemRule)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package parser
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
|
||||
"io"
|
||||
)
|
||||
@@ -137,17 +138,22 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
|
||||
if ch == '\\' {
|
||||
if needsEscape(s.peek(), delimiter) {
|
||||
switch s.read() {
|
||||
nextch := s.read()
|
||||
switch nextch {
|
||||
case 'n':
|
||||
fmt.Println("n")
|
||||
buf.WriteRune('\n')
|
||||
case 'r':
|
||||
fmt.Println("r")
|
||||
buf.WriteRune('\r')
|
||||
case 't':
|
||||
fmt.Println("t")
|
||||
buf.WriteRune('\t')
|
||||
case '\\':
|
||||
buf.WriteRune('\\')
|
||||
case delimiter:
|
||||
buf.WriteRune(delimiter)
|
||||
default:
|
||||
buf.WriteRune('\\')
|
||||
buf.WriteRune(nextch)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/dns/namecheap"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
@@ -60,13 +61,14 @@ func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount) (*AcmeClient, error) {
|
||||
type DnsType string
|
||||
|
||||
const (
|
||||
DnsPod DnsType = "DnsPod"
|
||||
AliYun DnsType = "AliYun"
|
||||
CloudFlare DnsType = "CloudFlare"
|
||||
NameSilo DnsType = "NameSilo"
|
||||
NameCheap DnsType = "NameCheap"
|
||||
NameCom DnsType = "NameCom"
|
||||
Godaddy DnsType = "Godaddy"
|
||||
DnsPod DnsType = "DnsPod"
|
||||
AliYun DnsType = "AliYun"
|
||||
CloudFlare DnsType = "CloudFlare"
|
||||
NameSilo DnsType = "NameSilo"
|
||||
NameCheap DnsType = "NameCheap"
|
||||
NameCom DnsType = "NameCom"
|
||||
Godaddy DnsType = "Godaddy"
|
||||
TencentCloud DnsType = "TencentCloud"
|
||||
)
|
||||
|
||||
type DNSParam struct {
|
||||
@@ -78,6 +80,7 @@ type DNSParam struct {
|
||||
APIkey string `json:"apiKey"`
|
||||
APIUser string `json:"apiUser"`
|
||||
APISecret string `json:"apiSecret"`
|
||||
SecretID string `json:"secretID"`
|
||||
}
|
||||
|
||||
func (c *AcmeClient) UseDns(dnsType DnsType, params string, skipDNSCheck bool) error {
|
||||
@@ -146,6 +149,14 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, skipDNSCheck bool) e
|
||||
nameComConfig.PollingInterval = 30 * time.Second
|
||||
nameComConfig.TTL = 3600
|
||||
p, err = namedotcom.NewDNSProviderConfig(nameComConfig)
|
||||
case TencentCloud:
|
||||
tencentCloudConfig := tencentcloud.NewDefaultConfig()
|
||||
tencentCloudConfig.SecretID = param.SecretID
|
||||
tencentCloudConfig.SecretKey = param.SecretKey
|
||||
tencentCloudConfig.PropagationTimeout = 30 * time.Minute
|
||||
tencentCloudConfig.PollingInterval = 30 * time.Second
|
||||
tencentCloudConfig.TTL = 3600
|
||||
p, err = tencentcloud.NewDNSProviderConfig(tencentCloudConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -14,10 +14,11 @@ type LocalWsSession struct {
|
||||
slave *LocalCommand
|
||||
wsConn *websocket.Conn
|
||||
|
||||
allowCtrlC bool
|
||||
writeMutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand) (*LocalWsSession, error) {
|
||||
func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) {
|
||||
if err := slave.ResizeTerminal(cols, rows); err != nil {
|
||||
global.LOG.Errorf("ssh pty change windows size failed, err: %v", err)
|
||||
}
|
||||
@@ -25,6 +26,8 @@ func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalComma
|
||||
return &LocalWsSession{
|
||||
slave: slave,
|
||||
wsConn: wsConn,
|
||||
|
||||
allowCtrlC: allowCtrlC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -94,9 +97,10 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
||||
}
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||
if string(decodeBytes) != "\x03" || sws.allowCtrlC {
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||
}
|
||||
case WsMsgHeartbeat:
|
||||
// 接收到心跳包后将心跳包原样返回,可以用于网络延迟检测等情况
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, wsData)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err)
|
||||
|
||||
+195
-155
@@ -1,5 +1,5 @@
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@@ -3334,39 +3334,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjob/record/log": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取计划任务记录日志",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Cronjob"
|
||||
],
|
||||
"summary": "Load Cronjob record log",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OperateByID"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjobs": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -3614,6 +3581,39 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjobs/records/log": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取计划任务记录日志",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Cronjob"
|
||||
],
|
||||
"summary": "Load Cronjob record log",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OperateByID"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjobs/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -5972,7 +5972,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/files/download": {
|
||||
"post": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@@ -5986,30 +5986,10 @@ const docTemplate = `{
|
||||
"File"
|
||||
],
|
||||
"summary": "Download file",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FileDownload"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"formatEN": "Download file [name]",
|
||||
"formatZH": "下载文件 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6139,36 +6119,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/log/read": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "按行读取日志文件",
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "Read file by Line",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FileReadByLineReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/mode": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -6299,6 +6249,36 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/read": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "按行读取日志文件",
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "Read file by Line",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FileReadByLineReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/recycle/clear": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -8526,7 +8506,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty": {
|
||||
"/openresty": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8548,7 +8528,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/clear": {
|
||||
"/openresty/clear": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8574,7 +8554,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/file": {
|
||||
"/openresty/file": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8614,7 +8594,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/scope": {
|
||||
"/openresty/scope": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8653,7 +8633,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/status": {
|
||||
"/openresty/status": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8675,7 +8655,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/update": {
|
||||
"/openresty/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -9276,7 +9256,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/": {
|
||||
"/settings/backup/backup": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -9390,7 +9370,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/dto.OneDriveInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9523,6 +9503,39 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/record/search/bycronjob": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "通过计划任务获取备份记录列表分页",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Backup Account"
|
||||
],
|
||||
"summary": "Page backup records by cronjob",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RecordSearchByCronjob"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/recover": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -9613,6 +9626,25 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/refresh/onedrive": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "刷新 OneDrive token",
|
||||
"tags": [
|
||||
"Backup Account"
|
||||
],
|
||||
"summary": "Refresh OneDrive token",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/search": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -14892,6 +14924,9 @@ const docTemplate = `{
|
||||
},
|
||||
"cronjobID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"isDelete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -14899,18 +14934,18 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"specType",
|
||||
"spec",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
"containerName": {
|
||||
"backupAccounts": {
|
||||
"type": "string"
|
||||
},
|
||||
"day": {
|
||||
"type": "integer"
|
||||
"containerName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -14918,17 +14953,11 @@ const docTemplate = `{
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"defaultDownload": {
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keepLocal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer"
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -14940,18 +14969,12 @@ const docTemplate = `{
|
||||
"script": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sourceDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"specType": {
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetDirID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -14960,11 +14983,6 @@ const docTemplate = `{
|
||||
},
|
||||
"website": {
|
||||
"type": "string"
|
||||
},
|
||||
"week": {
|
||||
"type": "integer",
|
||||
"maximum": 6,
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -14988,17 +15006,17 @@ const docTemplate = `{
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"specType"
|
||||
"spec"
|
||||
],
|
||||
"properties": {
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
"containerName": {
|
||||
"backupAccounts": {
|
||||
"type": "string"
|
||||
},
|
||||
"day": {
|
||||
"type": "integer"
|
||||
"containerName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -15006,21 +15024,15 @@ const docTemplate = `{
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"defaultDownload": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keepLocal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15031,28 +15043,17 @@ const docTemplate = `{
|
||||
"script": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sourceDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"specType": {
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetDirID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"website": {
|
||||
"type": "string"
|
||||
},
|
||||
"week": {
|
||||
"type": "integer",
|
||||
"maximum": 6,
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16797,6 +16798,20 @@ const docTemplate = `{
|
||||
"ProxyCache"
|
||||
]
|
||||
},
|
||||
"dto.OneDriveInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string"
|
||||
},
|
||||
"redirect_uri": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.Operate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -17233,6 +17248,25 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RecordSearchByCronjob": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cronjobID",
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"cronjobID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -17841,25 +17875,19 @@ const docTemplate = `{
|
||||
"dto.SnapshotCreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"defaultDownload",
|
||||
"from"
|
||||
],
|
||||
"properties": {
|
||||
"defaultDownload": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"OSS",
|
||||
"S3",
|
||||
"SFTP",
|
||||
"MINIO",
|
||||
"COS",
|
||||
"KODO",
|
||||
"OneDrive",
|
||||
"WebDAV"
|
||||
]
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
@@ -18605,6 +18633,9 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"services": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -18676,6 +18707,9 @@ const docTemplate = `{
|
||||
},
|
||||
"operate": {
|
||||
"type": "string"
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18755,6 +18789,9 @@ const docTemplate = `{
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -19305,6 +19342,9 @@ const docTemplate = `{
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+193
-153
@@ -3327,39 +3327,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjob/record/log": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取计划任务记录日志",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Cronjob"
|
||||
],
|
||||
"summary": "Load Cronjob record log",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OperateByID"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjobs": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -3607,6 +3574,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjobs/records/log": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取计划任务记录日志",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Cronjob"
|
||||
],
|
||||
"summary": "Load Cronjob record log",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OperateByID"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cronjobs/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -5965,7 +5965,7 @@
|
||||
}
|
||||
},
|
||||
"/files/download": {
|
||||
"post": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@@ -5979,30 +5979,10 @@
|
||||
"File"
|
||||
],
|
||||
"summary": "Download file",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FileDownload"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"formatEN": "Download file [name]",
|
||||
"formatZH": "下载文件 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6132,36 +6112,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/log/read": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "按行读取日志文件",
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "Read file by Line",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FileReadByLineReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/mode": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -6292,6 +6242,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/read": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "按行读取日志文件",
|
||||
"tags": [
|
||||
"File"
|
||||
],
|
||||
"summary": "Read file by Line",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.FileReadByLineReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/recycle/clear": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -8519,7 +8499,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty": {
|
||||
"/openresty": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8541,7 +8521,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/clear": {
|
||||
"/openresty/clear": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8567,7 +8547,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/file": {
|
||||
"/openresty/file": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8607,7 +8587,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/scope": {
|
||||
"/openresty/scope": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8646,7 +8626,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/status": {
|
||||
"/openresty/status": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
@@ -8668,7 +8648,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/openResty/update": {
|
||||
"/openresty/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -9269,7 +9249,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/": {
|
||||
"/settings/backup/backup": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -9383,7 +9363,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/dto.OneDriveInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9516,6 +9496,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/record/search/bycronjob": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "通过计划任务获取备份记录列表分页",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Backup Account"
|
||||
],
|
||||
"summary": "Page backup records by cronjob",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RecordSearchByCronjob"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/recover": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -9606,6 +9619,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/refresh/onedrive": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "刷新 OneDrive token",
|
||||
"tags": [
|
||||
"Backup Account"
|
||||
],
|
||||
"summary": "Refresh OneDrive token",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/backup/search": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -14885,6 +14917,9 @@
|
||||
},
|
||||
"cronjobID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"isDelete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -14892,18 +14927,18 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"specType",
|
||||
"spec",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
"containerName": {
|
||||
"backupAccounts": {
|
||||
"type": "string"
|
||||
},
|
||||
"day": {
|
||||
"type": "integer"
|
||||
"containerName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -14911,17 +14946,11 @@
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"defaultDownload": {
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keepLocal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer"
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -14933,18 +14962,12 @@
|
||||
"script": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sourceDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"specType": {
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetDirID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -14953,11 +14976,6 @@
|
||||
},
|
||||
"website": {
|
||||
"type": "string"
|
||||
},
|
||||
"week": {
|
||||
"type": "integer",
|
||||
"maximum": 6,
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -14981,17 +14999,17 @@
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"specType"
|
||||
"spec"
|
||||
],
|
||||
"properties": {
|
||||
"appID": {
|
||||
"type": "string"
|
||||
},
|
||||
"containerName": {
|
||||
"backupAccounts": {
|
||||
"type": "string"
|
||||
},
|
||||
"day": {
|
||||
"type": "integer"
|
||||
"containerName": {
|
||||
"type": "string"
|
||||
},
|
||||
"dbName": {
|
||||
"type": "string"
|
||||
@@ -14999,21 +15017,15 @@
|
||||
"dbType": {
|
||||
"type": "string"
|
||||
},
|
||||
"defaultDownload": {
|
||||
"type": "string"
|
||||
},
|
||||
"exclusionRules": {
|
||||
"type": "string"
|
||||
},
|
||||
"hour": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keepLocal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minute": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15024,28 +15036,17 @@
|
||||
"script": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sourceDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"specType": {
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetDirID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"website": {
|
||||
"type": "string"
|
||||
},
|
||||
"week": {
|
||||
"type": "integer",
|
||||
"maximum": 6,
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16790,6 +16791,20 @@
|
||||
"ProxyCache"
|
||||
]
|
||||
},
|
||||
"dto.OneDriveInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string"
|
||||
},
|
||||
"redirect_uri": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.Operate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -17226,6 +17241,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RecordSearchByCronjob": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cronjobID",
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"cronjobID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -17834,25 +17868,19 @@
|
||||
"dto.SnapshotCreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"defaultDownload",
|
||||
"from"
|
||||
],
|
||||
"properties": {
|
||||
"defaultDownload": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"OSS",
|
||||
"S3",
|
||||
"SFTP",
|
||||
"MINIO",
|
||||
"COS",
|
||||
"KODO",
|
||||
"OneDrive",
|
||||
"WebDAV"
|
||||
]
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
@@ -18598,6 +18626,9 @@
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"services": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -18669,6 +18700,9 @@
|
||||
},
|
||||
"operate": {
|
||||
"type": "string"
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18748,6 +18782,9 @@
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -19298,6 +19335,9 @@
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pullImage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+125
-106
@@ -573,6 +573,8 @@ definitions:
|
||||
type: boolean
|
||||
cronjobID:
|
||||
type: integer
|
||||
isDelete:
|
||||
type: boolean
|
||||
required:
|
||||
- cronjobID
|
||||
type: object
|
||||
@@ -580,22 +582,18 @@ definitions:
|
||||
properties:
|
||||
appID:
|
||||
type: string
|
||||
backupAccounts:
|
||||
type: string
|
||||
containerName:
|
||||
type: string
|
||||
day:
|
||||
type: integer
|
||||
dbName:
|
||||
type: string
|
||||
dbType:
|
||||
type: string
|
||||
defaultDownload:
|
||||
type: string
|
||||
exclusionRules:
|
||||
type: string
|
||||
hour:
|
||||
type: integer
|
||||
keepLocal:
|
||||
type: boolean
|
||||
minute:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
retainCopies:
|
||||
@@ -603,27 +601,19 @@ definitions:
|
||||
type: integer
|
||||
script:
|
||||
type: string
|
||||
second:
|
||||
type: integer
|
||||
sourceDir:
|
||||
type: string
|
||||
specType:
|
||||
spec:
|
||||
type: string
|
||||
targetDirID:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
website:
|
||||
type: string
|
||||
week:
|
||||
maximum: 6
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- specType
|
||||
- spec
|
||||
- type
|
||||
type: object
|
||||
dto.CronjobDownload:
|
||||
@@ -640,24 +630,20 @@ definitions:
|
||||
properties:
|
||||
appID:
|
||||
type: string
|
||||
backupAccounts:
|
||||
type: string
|
||||
containerName:
|
||||
type: string
|
||||
day:
|
||||
type: integer
|
||||
dbName:
|
||||
type: string
|
||||
dbType:
|
||||
type: string
|
||||
defaultDownload:
|
||||
type: string
|
||||
exclusionRules:
|
||||
type: string
|
||||
hour:
|
||||
type: integer
|
||||
id:
|
||||
type: integer
|
||||
keepLocal:
|
||||
type: boolean
|
||||
minute:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
retainCopies:
|
||||
@@ -665,26 +651,18 @@ definitions:
|
||||
type: integer
|
||||
script:
|
||||
type: string
|
||||
second:
|
||||
type: integer
|
||||
sourceDir:
|
||||
type: string
|
||||
specType:
|
||||
spec:
|
||||
type: string
|
||||
targetDirID:
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
website:
|
||||
type: string
|
||||
week:
|
||||
maximum: 6
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- specType
|
||||
- spec
|
||||
type: object
|
||||
dto.CronjobUpdateStatus:
|
||||
properties:
|
||||
@@ -1865,6 +1843,15 @@ definitions:
|
||||
- CACHE
|
||||
- HttpPer
|
||||
- ProxyCache
|
||||
dto.OneDriveInfo:
|
||||
properties:
|
||||
client_id:
|
||||
type: string
|
||||
client_secret:
|
||||
type: string
|
||||
redirect_uri:
|
||||
type: string
|
||||
type: object
|
||||
dto.Operate:
|
||||
properties:
|
||||
operation:
|
||||
@@ -2162,6 +2149,19 @@ definitions:
|
||||
- pageSize
|
||||
- type
|
||||
type: object
|
||||
dto.RecordSearchByCronjob:
|
||||
properties:
|
||||
cronjobID:
|
||||
type: integer
|
||||
page:
|
||||
type: integer
|
||||
pageSize:
|
||||
type: integer
|
||||
required:
|
||||
- cronjobID
|
||||
- page
|
||||
- pageSize
|
||||
type: object
|
||||
dto.RedisConf:
|
||||
properties:
|
||||
containerName:
|
||||
@@ -2566,23 +2566,17 @@ definitions:
|
||||
type: object
|
||||
dto.SnapshotCreate:
|
||||
properties:
|
||||
defaultDownload:
|
||||
type: string
|
||||
description:
|
||||
maxLength: 256
|
||||
type: string
|
||||
from:
|
||||
enum:
|
||||
- OSS
|
||||
- S3
|
||||
- SFTP
|
||||
- MINIO
|
||||
- COS
|
||||
- KODO
|
||||
- OneDrive
|
||||
- WebDAV
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
required:
|
||||
- defaultDownload
|
||||
- from
|
||||
type: object
|
||||
dto.SnapshotImport:
|
||||
@@ -3069,6 +3063,8 @@ definitions:
|
||||
params:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
pullImage:
|
||||
type: boolean
|
||||
services:
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -3117,6 +3113,8 @@ definitions:
|
||||
type: integer
|
||||
operate:
|
||||
type: string
|
||||
pullImage:
|
||||
type: boolean
|
||||
required:
|
||||
- installId
|
||||
- operate
|
||||
@@ -3170,6 +3168,8 @@ definitions:
|
||||
params:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
pullImage:
|
||||
type: boolean
|
||||
required:
|
||||
- installId
|
||||
- params
|
||||
@@ -3540,6 +3540,8 @@ definitions:
|
||||
params:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
pullImage:
|
||||
type: boolean
|
||||
type: object
|
||||
request.NginxAntiLeechUpdate:
|
||||
properties:
|
||||
@@ -7106,26 +7108,6 @@ paths:
|
||||
summary: Page volumes
|
||||
tags:
|
||||
- Container Volume
|
||||
/cronjob/record/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取计划任务记录日志
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OperateByID'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load Cronjob record log
|
||||
tags:
|
||||
- Cronjob
|
||||
/cronjobs:
|
||||
post:
|
||||
consumes:
|
||||
@@ -7286,6 +7268,26 @@ paths:
|
||||
formatEN: clean cronjob [name] records
|
||||
formatZH: 清空计划任务记录 [name]
|
||||
paramKeys: []
|
||||
/cronjobs/records/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取计划任务记录日志
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OperateByID'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load Cronjob record log
|
||||
tags:
|
||||
- Cronjob
|
||||
/cronjobs/search:
|
||||
post:
|
||||
consumes:
|
||||
@@ -8781,17 +8783,10 @@ paths:
|
||||
formatZH: 删除文件/文件夹 [path]
|
||||
paramKeys: []
|
||||
/files/download:
|
||||
post:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 下载文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.FileDownload'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@@ -8800,13 +8795,6 @@ paths:
|
||||
summary: Download file
|
||||
tags:
|
||||
- File
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- name
|
||||
formatEN: Download file [name]
|
||||
formatZH: 下载文件 [name]
|
||||
paramKeys: []
|
||||
/files/favorite:
|
||||
post:
|
||||
consumes:
|
||||
@@ -8887,24 +8875,6 @@ paths:
|
||||
summary: List favorites
|
||||
tags:
|
||||
- File
|
||||
/files/log/read:
|
||||
post:
|
||||
description: 按行读取日志文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.FileReadByLineReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Read file by Line
|
||||
tags:
|
||||
- File
|
||||
/files/mode:
|
||||
post:
|
||||
consumes:
|
||||
@@ -8990,6 +8960,24 @@ paths:
|
||||
formatEN: Change owner [paths] => [user]/[group]
|
||||
formatZH: 修改用户/组 [paths] => [user]/[group]
|
||||
paramKeys: []
|
||||
/files/read:
|
||||
post:
|
||||
description: 按行读取日志文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.FileReadByLineReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Read file by Line
|
||||
tags:
|
||||
- File
|
||||
/files/recycle/clear:
|
||||
post:
|
||||
consumes:
|
||||
@@ -10396,7 +10384,7 @@ paths:
|
||||
summary: Load system log files
|
||||
tags:
|
||||
- Logs
|
||||
/openResty:
|
||||
/openresty:
|
||||
get:
|
||||
description: 获取 OpenResty 配置信息
|
||||
responses:
|
||||
@@ -10409,7 +10397,7 @@ paths:
|
||||
summary: Load OpenResty conf
|
||||
tags:
|
||||
- OpenResty
|
||||
/openResty/clear:
|
||||
/openresty/clear:
|
||||
post:
|
||||
description: 清理 OpenResty 代理缓存
|
||||
responses:
|
||||
@@ -10426,7 +10414,7 @@ paths:
|
||||
formatEN: Clear nginx proxy cache
|
||||
formatZH: 清理 Openresty 代理缓存
|
||||
paramKeys: []
|
||||
/openResty/file:
|
||||
/openresty/file:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -10452,7 +10440,7 @@ paths:
|
||||
formatEN: Update nginx conf
|
||||
formatZH: 更新 nginx 配置
|
||||
paramKeys: []
|
||||
/openResty/scope:
|
||||
/openresty/scope:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -10476,7 +10464,7 @@ paths:
|
||||
summary: Load partial OpenResty conf
|
||||
tags:
|
||||
- OpenResty
|
||||
/openResty/status:
|
||||
/openresty/status:
|
||||
get:
|
||||
description: 获取 OpenResty 状态信息
|
||||
responses:
|
||||
@@ -10489,7 +10477,7 @@ paths:
|
||||
summary: Load OpenResty status info
|
||||
tags:
|
||||
- OpenResty
|
||||
/openResty/update:
|
||||
/openresty/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -10865,7 +10853,7 @@ paths:
|
||||
formatEN: create backup account [type]
|
||||
formatZH: 创建备份账号 [type]
|
||||
paramKeys: []
|
||||
/settings/backup/:
|
||||
/settings/backup/backup:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -10936,7 +10924,7 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
$ref: '#/definitions/dto.OneDriveInfo'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load OneDrive info
|
||||
@@ -11023,6 +11011,26 @@ paths:
|
||||
summary: Page backup records
|
||||
tags:
|
||||
- Backup Account
|
||||
/settings/backup/record/search/bycronjob:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 通过计划任务获取备份记录列表分页
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.RecordSearchByCronjob'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Page backup records by cronjob
|
||||
tags:
|
||||
- Backup Account
|
||||
/settings/backup/recover:
|
||||
post:
|
||||
consumes:
|
||||
@@ -11083,6 +11091,17 @@ paths:
|
||||
formatEN: recover [type] data [name][detailName] from [file]
|
||||
formatZH: 从 [file] 恢复 [type] 数据 [name][detailName]
|
||||
paramKeys: []
|
||||
/settings/backup/refresh/onedrive:
|
||||
post:
|
||||
description: 刷新 OneDrive token
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Refresh OneDrive token
|
||||
tags:
|
||||
- Backup Account
|
||||
/settings/backup/search:
|
||||
get:
|
||||
description: 获取备份账号列表
|
||||
|
||||
Generated
+4
-4
@@ -69,7 +69,7 @@
|
||||
"typescript": "^4.5.4",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-vue-components": "^0.25.0",
|
||||
"vite": "^2.9.16",
|
||||
"vite": "^2.9.17",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-eslint": "^1.6.0",
|
||||
@@ -10503,9 +10503,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "2.9.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz",
|
||||
"integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==",
|
||||
"version": "2.9.17",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.17.tgz",
|
||||
"integrity": "sha512-XxcRzra6d7xrKXH66jZUgb+srThoPu+TLJc06GifUyKq9JmjHkc1Numc8ra0h56rju2jfVWw3B3fs5l3OFMvUw==",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.27",
|
||||
"postcss": "^8.4.13",
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"typescript": "^4.5.4",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-vue-components": "^0.25.0",
|
||||
"vite": "^2.9.16",
|
||||
"vite": "^2.9.17",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-eslint": "^1.6.0",
|
||||
|
||||
@@ -145,6 +145,7 @@ export namespace App {
|
||||
username: string;
|
||||
password: string;
|
||||
privilege: boolean;
|
||||
containerName: string;
|
||||
serviceName: string;
|
||||
systemIP: string;
|
||||
port: number;
|
||||
|
||||
@@ -12,6 +12,11 @@ export namespace Backup {
|
||||
varsJson: object;
|
||||
createdAt: Date;
|
||||
}
|
||||
export interface OneDriveInfo {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
redirect_uri: string;
|
||||
}
|
||||
export interface BackupOperate {
|
||||
id: number;
|
||||
type: string;
|
||||
@@ -45,6 +50,9 @@ export namespace Backup {
|
||||
name: string;
|
||||
detailName: string;
|
||||
}
|
||||
export interface SearchBackupRecordByCronjob extends ReqPage {
|
||||
cronjobID: number;
|
||||
}
|
||||
export interface Backup {
|
||||
type: string;
|
||||
name: string;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ReqPage } from '.';
|
||||
|
||||
export namespace Command {
|
||||
export interface CommandInfo {
|
||||
id: number;
|
||||
@@ -13,7 +11,4 @@ export namespace Command {
|
||||
groupID: number;
|
||||
command: string;
|
||||
}
|
||||
export interface CommandSearch extends ReqPage {
|
||||
info: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,8 @@ export namespace Cronjob {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
specType: string;
|
||||
week: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
spec: string;
|
||||
specObjs: Array<SpecObj>;
|
||||
|
||||
script: string;
|
||||
inContainer: boolean;
|
||||
@@ -22,21 +18,18 @@ export namespace Cronjob {
|
||||
dbName: string;
|
||||
url: string;
|
||||
sourceDir: string;
|
||||
keepLocal: boolean;
|
||||
targetDirID: number;
|
||||
targetDir: string;
|
||||
|
||||
backupAccounts: string;
|
||||
defaultDownload: string;
|
||||
backupAccountList: Array<string>;
|
||||
retainCopies: number;
|
||||
status: string;
|
||||
}
|
||||
export interface CronjobCreate {
|
||||
name: string;
|
||||
type: string;
|
||||
specType: string;
|
||||
week: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
spec: string;
|
||||
specObjs: Array<SpecObj>;
|
||||
|
||||
script: string;
|
||||
website: string;
|
||||
@@ -45,18 +38,22 @@ export namespace Cronjob {
|
||||
dbName: string;
|
||||
url: string;
|
||||
sourceDir: string;
|
||||
keepLocal: boolean;
|
||||
targetDirID: number;
|
||||
|
||||
backupAccounts: string;
|
||||
defaultDownload: string;
|
||||
retainCopies: number;
|
||||
}
|
||||
export interface SpecObj {
|
||||
specType: string;
|
||||
week: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
}
|
||||
export interface CronjobUpdate {
|
||||
id: number;
|
||||
specType: string;
|
||||
week: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
spec: string;
|
||||
|
||||
script: string;
|
||||
website: string;
|
||||
@@ -65,8 +62,9 @@ export namespace Cronjob {
|
||||
dbName: string;
|
||||
url: string;
|
||||
sourceDir: string;
|
||||
keepLocal: boolean;
|
||||
targetDirID: number;
|
||||
|
||||
backupAccounts: string;
|
||||
defaultDownload: string;
|
||||
retainCopies: number;
|
||||
}
|
||||
export interface CronjobDelete {
|
||||
|
||||
@@ -90,6 +90,8 @@ export namespace Setting {
|
||||
export interface SnapshotCreate {
|
||||
id: number;
|
||||
from: string;
|
||||
fromAccounts: Array<string>;
|
||||
defaultDownload: string;
|
||||
description: string;
|
||||
}
|
||||
export interface SnapshotImport {
|
||||
@@ -106,6 +108,7 @@ export namespace Setting {
|
||||
id: number;
|
||||
name: string;
|
||||
from: string;
|
||||
defaultDownload: string;
|
||||
description: string;
|
||||
status: string;
|
||||
message: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http from '@/api';
|
||||
import { ResPage } from '../interface';
|
||||
import { ResPage, SearchWithPage } from '../interface';
|
||||
import { Command } from '../interface/command';
|
||||
import { Host } from '../interface/host';
|
||||
import { Base64 } from 'js-base64';
|
||||
@@ -56,7 +56,7 @@ export const deleteHost = (params: { ids: number[] }) => {
|
||||
export const getCommandList = () => {
|
||||
return http.get<Array<Command.CommandInfo>>(`/hosts/command`, {});
|
||||
};
|
||||
export const getCommandPage = (params: Command.CommandSearch) => {
|
||||
export const getCommandPage = (params: SearchWithPage) => {
|
||||
return http.post<ResPage<Command.CommandInfo>>(`/hosts/command/search`, params);
|
||||
};
|
||||
export const getCommandTree = () => {
|
||||
|
||||
@@ -84,6 +84,9 @@ export const handleRecover = (params: Backup.Recover) => {
|
||||
export const handleRecoverByUpload = (params: Backup.Recover) => {
|
||||
return http.post(`/settings/backup/recover/byupload`, params, TimeoutEnum.T_1D);
|
||||
};
|
||||
export const refreshOneDrive = () => {
|
||||
return http.post(`/settings/backup/refresh/onedrive`, {});
|
||||
};
|
||||
export const downloadBackupRecord = (params: Backup.RecordDownload) => {
|
||||
return http.post<string>(`/settings/backup/record/download`, params, TimeoutEnum.T_10M);
|
||||
};
|
||||
@@ -91,14 +94,17 @@ export const deleteBackupRecord = (params: { ids: number[] }) => {
|
||||
return http.post(`/settings/backup/record/del`, params);
|
||||
};
|
||||
export const searchBackupRecords = (params: Backup.SearchBackupRecord) => {
|
||||
return http.post<ResPage<Backup.RecordInfo>>(`/settings/backup/record/search`, params);
|
||||
return http.post<ResPage<Backup.RecordInfo>>(`/settings/backup/record/search`, params, TimeoutEnum.T_5M);
|
||||
};
|
||||
export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => {
|
||||
return http.post<ResPage<Backup.RecordInfo>>(`/settings/backup/record/search/bycronjob`, params, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const getBackupList = () => {
|
||||
return http.get<Array<Backup.BackupInfo>>(`/settings/backup/search`);
|
||||
};
|
||||
export const getOneDriveInfo = () => {
|
||||
return http.get<string>(`/settings/backup/onedrive`);
|
||||
return http.get<Backup.OneDriveInfo>(`/settings/backup/onedrive`);
|
||||
};
|
||||
export const getFilesFromBackup = (type: string) => {
|
||||
return http.post<Array<any>>(`/settings/backup/search/files`, { type: type });
|
||||
|
||||
@@ -122,40 +122,58 @@ const search = async () => {
|
||||
};
|
||||
|
||||
const onBackup = async () => {
|
||||
let params = {
|
||||
type: type.value,
|
||||
name: name.value,
|
||||
detailName: detailName.value,
|
||||
};
|
||||
loading.value = true;
|
||||
await handleBackup(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
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 () => {
|
||||
let params = {
|
||||
type: type.value,
|
||||
name: name.value,
|
||||
detailName: detailName.value,
|
||||
};
|
||||
loading.value = true;
|
||||
await handleBackup(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onRecover = async (row: Backup.RecordInfo) => {
|
||||
let params = {
|
||||
source: row.source,
|
||||
type: type.value,
|
||||
name: name.value,
|
||||
detailName: detailName.value,
|
||||
file: row.fileDir + '/' + row.fileName,
|
||||
};
|
||||
loading.value = true;
|
||||
await handleRecover(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('commons.msg.recoverHelper', [row.fileName]),
|
||||
i18n.global.t('commons.button.recover'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
).then(async () => {
|
||||
let params = {
|
||||
source: row.source,
|
||||
type: type.value,
|
||||
name: name.value,
|
||||
detailName: detailName.value,
|
||||
file: row.fileDir + '/' + row.fileName,
|
||||
};
|
||||
loading.value = true;
|
||||
await handleRecover(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onDownload = async (row: Backup.RecordInfo) => {
|
||||
@@ -202,12 +220,18 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.recover'),
|
||||
disabled: (row: any) => {
|
||||
return row.size === 0;
|
||||
},
|
||||
click: (row: Backup.RecordInfo) => {
|
||||
onRecover(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.download'),
|
||||
disabled: (row: any) => {
|
||||
return row.size === 0;
|
||||
},
|
||||
click: (row: Backup.RecordInfo) => {
|
||||
onDownload(row);
|
||||
},
|
||||
|
||||
@@ -247,6 +247,10 @@ const init = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const clearLog = (): void => {
|
||||
content.value = '';
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
onCloseLog();
|
||||
});
|
||||
@@ -255,5 +259,5 @@ onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
defineExpose({ changeTail, onDownload });
|
||||
defineExpose({ changeTail, onDownload, clearLog });
|
||||
</script>
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
<span>{{ $t('setting.doc2') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toGithub">
|
||||
<span>{{ $t('setting.project') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
</span>
|
||||
<span class="version">{{ $t('setting.currentVersion') + version }}</span>
|
||||
<el-badge is-dot class="item" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
||||
@@ -103,6 +107,10 @@ const toForum = () => {
|
||||
window.open('https://bbs.fit2cloud.com/c/1p/7', '_blank');
|
||||
};
|
||||
|
||||
const toGithub = () => {
|
||||
window.open('https://github.com/1Panel-dev/1Panel', '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const onLoadUpgradeInfo = async () => {
|
||||
loading.value = true;
|
||||
await loadUpgradeInfo()
|
||||
|
||||
@@ -169,18 +169,21 @@ const search = async () => {
|
||||
};
|
||||
|
||||
const onRecover = async (row: File.File) => {
|
||||
let params = {
|
||||
source: 'LOCAL',
|
||||
type: type.value,
|
||||
name: name.value,
|
||||
detailName: detailName.value,
|
||||
file: baseDir.value + row.name,
|
||||
};
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.recoverHelper'), i18n.global.t('commons.button.recover'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('commons.msg.recoverHelper', [row.name]),
|
||||
i18n.global.t('commons.button.recover'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
).then(async () => {
|
||||
let params = {
|
||||
source: 'LOCAL',
|
||||
type: type.value,
|
||||
name: name.value,
|
||||
detailName: detailName.value,
|
||||
file: baseDir.value + row.name,
|
||||
};
|
||||
loading.value = true;
|
||||
await handleRecoverByUpload(params)
|
||||
.then(() => {
|
||||
@@ -319,16 +322,7 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.recover'),
|
||||
click: (row: File.File) => {
|
||||
if (type.value !== 'app') {
|
||||
onRecover(row);
|
||||
} else {
|
||||
ElMessageBox.confirm(i18n.global.t('app.restoreWarn'), i18n.global.t('commons.button.recover'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
onRecover(row);
|
||||
});
|
||||
}
|
||||
onRecover(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -478,7 +478,7 @@ const checkPHPExtensions = (rule, value, callback) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.phpExtension')));
|
||||
} else {
|
||||
const reg = /^[a-z0-9,_]{1,300}$/;
|
||||
const reg = /^[a-z0-9,_]+$/;
|
||||
if (!reg.test(value)) {
|
||||
callback(new Error(i18n.global.t('commons.rule.phpExtension')));
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,10 @@ export const Languages = [
|
||||
label: 'json',
|
||||
value: ['json'],
|
||||
},
|
||||
{
|
||||
label: 'lua',
|
||||
value: ['lua'],
|
||||
},
|
||||
{
|
||||
label: 'markdown',
|
||||
value: ['md'],
|
||||
@@ -35,6 +39,10 @@ export const Languages = [
|
||||
label: 'yaml',
|
||||
value: ['yml', 'yaml'],
|
||||
},
|
||||
{
|
||||
label: 'xml',
|
||||
value: ['xml'],
|
||||
},
|
||||
{
|
||||
label: 'php',
|
||||
value: ['php'],
|
||||
@@ -145,7 +153,11 @@ export const DNSTypes = [
|
||||
value: 'AliYun',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod',
|
||||
label: i18n.global.t('website.tencentCloud'),
|
||||
value: 'TencentCloud',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
|
||||
value: 'DnsPod',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -86,6 +86,7 @@ const message = {
|
||||
refreshRate: 'Rate',
|
||||
noRefresh: 'No Refresh',
|
||||
refreshRateUnit: '{0} Seconds/Time',
|
||||
selectColumn: 'Select column',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'System upgrade, please wait...',
|
||||
@@ -124,7 +125,8 @@ const message = {
|
||||
confirmNoNull: 'Make sure the value {0} is not empty',
|
||||
errPort: 'Incorrect port information, please confirm!',
|
||||
remove: 'Remove',
|
||||
recoverHelper: 'The current data will be overwritten. Do you want to continue?',
|
||||
backupHelper: 'The current operation will back up {0}. Do you want to proceed?',
|
||||
recoverHelper: 'Restoring from {0} file. This operation is irreversible. Do you want to continue?',
|
||||
},
|
||||
login: {
|
||||
username: 'UserName',
|
||||
@@ -399,6 +401,7 @@ const message = {
|
||||
address: 'DB address',
|
||||
version: 'DB version',
|
||||
userHelper: 'The root user or a database user with root privileges can access the remote database.',
|
||||
pgUserHelper: 'Database user with superuser privileges.',
|
||||
ssl: 'Use SSL',
|
||||
clientKey: 'Client Private Key',
|
||||
clientCert: 'Client Certificate',
|
||||
@@ -511,9 +514,12 @@ const message = {
|
||||
rdbHelper3: 'Meeting either condition triggers RDB persistence',
|
||||
rdbInfo: 'Ensure that the value in the rule list ranges from 1 to 100000',
|
||||
|
||||
containerConn: 'Container connection address',
|
||||
containerConnHelper: 'PHP runtime environment/container-installed applications use this connection address',
|
||||
remoteConn: 'External connection address',
|
||||
containerConn: 'Container Connection',
|
||||
connAddress: 'Address',
|
||||
connPort: 'Port',
|
||||
containerConnHelper:
|
||||
'This connection address is used by applications running on the PHP execution environment/container installation.',
|
||||
remoteConn: 'External Connection',
|
||||
remoteConnHelper2: 'Use this address for non-container or external connections',
|
||||
localIP: 'Local IP',
|
||||
},
|
||||
@@ -787,6 +793,7 @@ const message = {
|
||||
containerCheckBox: 'In container (no need to enter the container command)',
|
||||
containerName: 'Container name',
|
||||
ntp: 'Time synchronization',
|
||||
ntp_helper: 'You can configure the NTP server on the Quick Setup page of the Toolbox.',
|
||||
app: 'Backup app',
|
||||
website: 'Backup website',
|
||||
rulesHelper:
|
||||
@@ -811,12 +818,15 @@ const message = {
|
||||
allOptionHelper:
|
||||
'The current task plan is to back up all [{0}]. Direct download is not supported at the moment. You can check the backup list of [{0}] menu.',
|
||||
exclusionRules: 'Exclusive rule',
|
||||
default_download_path: 'Default Download Link',
|
||||
saveLocal: 'Retain local backups (the same as the number of cloud storage copies)',
|
||||
url: 'URL Address',
|
||||
target: 'Target',
|
||||
targetHelper: 'Third-party backup accounts are maintained in panel settings.',
|
||||
retainCopies: 'Retain copies',
|
||||
retainCopiesHelper: 'Retention of Execution Records',
|
||||
retainCopiesHelper: 'Number of copies to retain for execution records and logs',
|
||||
retainCopiesHelper1: 'Number of copies to retain for backup files',
|
||||
retainCopiesUnit: ' copies (View)',
|
||||
cronSpecRule: 'Please enter a correct lifecycle',
|
||||
perMonth: 'Every monthly',
|
||||
perWeek: 'Every week',
|
||||
@@ -1241,9 +1251,16 @@ const message = {
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
OneDrive: 'Microsoft OneDrive',
|
||||
isCN: 'Century Internet',
|
||||
isNotCN: 'International Version',
|
||||
client_id: 'Client ID',
|
||||
client_secret: 'Client Secret',
|
||||
redirect_uri: 'Redirect URL',
|
||||
onedrive_helper: 'Custom configuration can be referred to in the official documentation',
|
||||
refreshTime: 'Token Refresh Time',
|
||||
refreshStatus: 'Token Refresh Status',
|
||||
backupDir: 'Backup dir',
|
||||
codeWarning: 'The current authorization code format is incorrect, please confirm again!',
|
||||
isCN: 'Domestic version (not supported at the moment)',
|
||||
code: 'Auth code',
|
||||
codeHelper:
|
||||
'Please click on the "Acquire" button, then login to OneDrive and copy the content after "code" in the redirected link. Paste it into this input box. For specific instructions, please refer to the official documentation.',
|
||||
@@ -1427,6 +1444,7 @@ const message = {
|
||||
},
|
||||
clean: {
|
||||
scan: 'Start Scanning',
|
||||
scanHelper: 'Easily tidy up junk files accumulated during 1Panel runtime',
|
||||
clean: 'Clean Now',
|
||||
reScan: 'Rescan',
|
||||
cleanHelper:
|
||||
@@ -1559,8 +1577,6 @@ const message = {
|
||||
upgradeHelper: 'The abnormal application needs to be synchronized to the normal state first',
|
||||
installWarn:
|
||||
'Currently, if the port external access is not checked, it will not be able to access through the external network IP: port. Do you want to continue?',
|
||||
restoreWarn:
|
||||
'The restore operation will delete the current data of the application and restart it. This operation cannot be rolled back, continue?',
|
||||
showIgnore: 'View ignore application',
|
||||
cancelIgnore: 'Cancel ignore',
|
||||
ignoreList: 'ignore list',
|
||||
@@ -1825,6 +1841,7 @@ const message = {
|
||||
primaryDomainHelper: 'Support domain name: port',
|
||||
acmeAccountType: 'Account Type',
|
||||
keyType: 'Key algorithm',
|
||||
tencentCloud: 'Tencent Cloud',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Short tag support',
|
||||
@@ -1930,6 +1947,10 @@ const message = {
|
||||
httpHelper2: 'Unable to apply for a generic domain name certificate in HTTP mode',
|
||||
skipDNSCheck: 'Skip DNS check',
|
||||
skipDNSCheckHelper: 'If there is an application timeout problem, please check here, otherwise do not check it',
|
||||
cfHelper: 'Do not use Global API Key',
|
||||
deprecated: 'will be deprecated',
|
||||
deprecatedHelper:
|
||||
'Maintenance has been stopped and may be abandoned in a future version. Please use Tencent Cloud method for analysis',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Create rule',
|
||||
|
||||
@@ -86,11 +86,12 @@ const message = {
|
||||
refreshRate: '刷新頻率',
|
||||
noRefresh: '不刷新',
|
||||
refreshRateUnit: '{0} 秒/次',
|
||||
selectColumn: '選擇列',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: '系統升級中,請稍候...',
|
||||
Restarting: '系統重啟中,請稍候...',
|
||||
Recovering: '從快照恢復中,請稍候...',
|
||||
Recovering: '快照恢復中,請稍候...',
|
||||
Rollbacking: '快照回滾中,請稍候...',
|
||||
},
|
||||
msg: {
|
||||
@@ -125,7 +126,8 @@ const message = {
|
||||
confirmNoNull: '請確認 {0} 值不為空',
|
||||
errPort: '錯誤的端口信息,請確認!',
|
||||
remove: '移出',
|
||||
recoverHelper: '當前數據將被覆蓋,是否繼續?',
|
||||
backupHelper: '當前操作將對 {0} 進行備份,是否繼續?',
|
||||
recoverHelper: '將從 {0} 文件進行恢復,該操作不可回滾,是否繼續?',
|
||||
},
|
||||
login: {
|
||||
username: '用戶名',
|
||||
@@ -389,6 +391,7 @@ const message = {
|
||||
address: '數據庫地址',
|
||||
version: '數據庫版本',
|
||||
userHelper: 'root 用戶或者擁有 root 權限的數據庫用戶',
|
||||
pgUserHelper: '具有超級管理員權限的數據庫使用者',
|
||||
ssl: '使用 SSL',
|
||||
clientKey: '客户端私钥',
|
||||
clientCert: '客户端证书',
|
||||
@@ -498,9 +501,11 @@ const message = {
|
||||
rdbHelper3: '符合任意一個條件將會觸發RDB持久化',
|
||||
rdbInfo: '請確認規則列表中值在 1-100000 之間',
|
||||
|
||||
containerConn: '容器連接地址',
|
||||
containerConnHelper: 'PHP 運行環境/容器安裝的應用使用此連接地址',
|
||||
remoteConn: '外部連接地址',
|
||||
containerConn: '容器連接',
|
||||
connAddress: '地址',
|
||||
connPort: '端口',
|
||||
containerConnHelper: 'PHP 執行環境/容器安裝的應用程式使用此連接地址',
|
||||
remoteConn: '外部連接',
|
||||
remoteConnHelper2: '非容器或外部連接使用此地址',
|
||||
localIP: '本機 IP',
|
||||
},
|
||||
@@ -754,6 +759,7 @@ const message = {
|
||||
containerCheckBox: '在容器中執行(無需再輸入進入容器命令)',
|
||||
containerName: '容器名稱',
|
||||
ntp: '同步服務器時間',
|
||||
ntp_helper: '您可以在工具箱的快速設定頁面配置 NTP 伺服器',
|
||||
app: '備份應用',
|
||||
website: '備份網站',
|
||||
rulesHelper: '當存在多個壓縮排除規則時,需要換行顯示,例:\n*.log \n*.sql',
|
||||
@@ -772,12 +778,15 @@ const message = {
|
||||
snapshot: '系統快照',
|
||||
allOptionHelper: '當前計劃任務為備份所有【{0}】,暫不支持直接下載,可在【{0}】備份列表中查看',
|
||||
exclusionRules: '排除規則',
|
||||
default_download_path: '默認下載地址',
|
||||
saveLocal: '同時保留本地備份(和雲存儲保留份數一致)',
|
||||
url: 'URL 地址',
|
||||
target: '備份到',
|
||||
targetHelper: '第三方備份賬號在面板設置中維護',
|
||||
retainCopies: '保留份數',
|
||||
retainCopiesHelper: '執行記錄保留份數',
|
||||
retainCopiesHelper: '執行記錄及日誌保留份数',
|
||||
retainCopiesHelper1: '備份文件保留份数',
|
||||
retainCopiesUnit: ' 份 (查看)',
|
||||
cronSpecRule: '請輸入正確的執行周期',
|
||||
perMonth: '每月',
|
||||
perWeek: '每周',
|
||||
@@ -1165,9 +1174,16 @@ const message = {
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
OneDrive: '微軟 OneDrive',
|
||||
isCN: '世紀互聯',
|
||||
isNotCN: '國際版',
|
||||
client_id: '客戶端 ID',
|
||||
client_secret: '客戶端密鑰',
|
||||
redirect_uri: '重定向 URL',
|
||||
onedrive_helper: '自訂配置可參考官方文件',
|
||||
refreshTime: '令牌刷新時間',
|
||||
refreshStatus: '令牌刷新狀態',
|
||||
codeWarning: '當前授權碼格式錯誤,請重新確認!',
|
||||
backupDir: '備份路徑',
|
||||
isCN: '國內版 (暫不支持)',
|
||||
code: '授權碼',
|
||||
codeHelper:
|
||||
'請點擊獲取按鈕,然後登錄 OneDrive 復製跳轉鏈接中 code 後面的內容,粘貼到該輸入框中,具體操作可參考官方文檔。',
|
||||
@@ -1334,6 +1350,7 @@ const message = {
|
||||
},
|
||||
clean: {
|
||||
scan: '開始掃描',
|
||||
scanHelper: '輕鬆梳理 1Panel 運行期間積累的垃圾文件',
|
||||
clean: '立即清理',
|
||||
reScan: '重新掃描',
|
||||
cleanHelper: '已勾選文件及目錄清理後無法回滾(系統緩存文件清理需要重啟服務),是否繼續?',
|
||||
@@ -1459,7 +1476,6 @@ const message = {
|
||||
installHelper: '配置鏡像加速可以解決鏡像拉取失敗的問題',
|
||||
upgradeHelper: '異常應用需要先同步到正常狀態',
|
||||
installWarn: '當前未勾選端口外部訪問,將無法通過外網IP:端口訪問,是否繼續? ',
|
||||
restoreWarn: '恢復操作將刪除該應用當前數據並重啟。此操作不可回滾,是否繼續?',
|
||||
showIgnore: '查看忽略應用',
|
||||
cancelIgnore: '取消忽略',
|
||||
ignoreList: '忽略列表',
|
||||
@@ -1681,7 +1697,7 @@ const message = {
|
||||
disable: '未啟用',
|
||||
disableLeechHelper: '是否禁用防盜鏈',
|
||||
disableLeech: '禁用防盜鏈',
|
||||
ipv6: '監聽 IPV6 端口',
|
||||
ipv6: '監聽 IPV6',
|
||||
leechReturnError: '請填寫 HTTP 狀態碼',
|
||||
selectAcme: '選擇 Acme 賬號',
|
||||
imported: '手動創建',
|
||||
@@ -1710,6 +1726,7 @@ const message = {
|
||||
primaryDomainHelper: '支援網域:port',
|
||||
acmeAccountType: '賬號類型',
|
||||
keyType: '密鑰演算法',
|
||||
tencentCloud: '騰訊雲',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短標簽支持',
|
||||
@@ -1811,6 +1828,9 @@ const message = {
|
||||
httpHelper2: 'HTTP 模式無法申請泛網域憑證',
|
||||
skipDNSCheck: '跳過 DNS 校驗',
|
||||
skipDNSCheckHelper: '如果出現申請超時問題,請勾選此處,其他情況請勿勾選',
|
||||
cfHelper: '請勿使用 Global API Key',
|
||||
deprecated: '即將廢棄',
|
||||
deprecatedHelper: '已經停止維護,可能會在以後的某個版本廢棄,請使用騰訊雲方式解析',
|
||||
},
|
||||
firewall: {
|
||||
create: '創建規則',
|
||||
|
||||
@@ -86,11 +86,12 @@ const message = {
|
||||
refreshRate: '刷新频率',
|
||||
noRefresh: '不刷新',
|
||||
refreshRateUnit: '{0} 秒/次',
|
||||
selectColumn: '选择列',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: '系统升级中,请稍候...',
|
||||
Restarting: '系统重启中,请稍候...',
|
||||
Recovering: '从快照恢复中,请稍候...',
|
||||
Recovering: '快照恢复中,请稍候...',
|
||||
Rollbacking: '快照回滚中,请稍候...',
|
||||
},
|
||||
msg: {
|
||||
@@ -125,7 +126,8 @@ const message = {
|
||||
confirmNoNull: '请确认 {0} 值不为空',
|
||||
errPort: '错误的端口信息,请确认!',
|
||||
remove: '移出',
|
||||
recoverHelper: '当前数据将被覆盖,是否继续?',
|
||||
backupHelper: '当前操作将对 {0} 进行备份,是否继续?',
|
||||
recoverHelper: '将从 {0} 文件进行恢复,该操作不可回滚,是否继续?',
|
||||
},
|
||||
login: {
|
||||
username: '用户名',
|
||||
@@ -389,6 +391,7 @@ const message = {
|
||||
address: '数据库地址',
|
||||
version: '数据库版本',
|
||||
userHelper: 'root 用户或者拥有 root 权限的数据库用户',
|
||||
pgUserHelper: '拥有超级管理员权限的数据库用户',
|
||||
ssl: '使用 SSL',
|
||||
clientKey: '客户端私钥',
|
||||
clientCert: '客户端证书',
|
||||
@@ -498,9 +501,11 @@ const message = {
|
||||
rdbHelper3: '符合任意一个条件将会触发RDB持久化',
|
||||
rdbInfo: '请确认规则列表中值在 1-100000 之间',
|
||||
|
||||
containerConn: '容器连接地址',
|
||||
containerConn: '容器连接',
|
||||
connAddress: '地址',
|
||||
connPort: '端口',
|
||||
containerConnHelper: 'PHP 运行环境/容器安装的应用使用此连接地址',
|
||||
remoteConn: '外部连接地址',
|
||||
remoteConn: '外部连接',
|
||||
remoteConnHelper2: '非容器或外部连接使用此地址',
|
||||
localIP: '本机 IP',
|
||||
},
|
||||
@@ -755,6 +760,7 @@ const message = {
|
||||
containerCheckBox: '在容器中执行(无需再输入进入容器命令)',
|
||||
containerName: '容器名称',
|
||||
ntp: '同步服务器时间',
|
||||
ntp_helper: '您可以在工具箱的快速设置页面配置 NTP 服务器',
|
||||
app: '备份应用',
|
||||
website: '备份网站',
|
||||
rulesHelper: '当存在多个压缩排除规则时,需要换行显示,例:\n*.log \n*.sql',
|
||||
@@ -773,12 +779,15 @@ const message = {
|
||||
snapshot: '系统快照',
|
||||
allOptionHelper: '当前计划任务为备份所有【{0}】,暂不支持直接下载,可在【{0}】备份列表中查看',
|
||||
exclusionRules: '排除规则',
|
||||
default_download_path: '默认下载地址',
|
||||
saveLocal: '同时保留本地备份(和云存储保留份数一致)',
|
||||
url: 'URL 地址',
|
||||
target: '备份到',
|
||||
targetHelper: '第三方备份账号在面板设置中维护',
|
||||
retainCopies: '保留份数',
|
||||
retainCopiesHelper: '执行记录保留份数',
|
||||
retainCopiesHelper: '执行记录及日志保留份数',
|
||||
retainCopiesHelper1: '备份文件保留份数',
|
||||
retainCopiesUnit: ' 份 (查看)',
|
||||
cronSpecRule: '请输入正确的执行周期',
|
||||
perMonth: '每月',
|
||||
perWeek: '每周',
|
||||
@@ -1166,9 +1175,16 @@ const message = {
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
OneDrive: '微软 OneDrive',
|
||||
isCN: '世纪互联',
|
||||
isNotCN: '国际版',
|
||||
client_id: '客户端 ID',
|
||||
client_secret: '客户端密钥',
|
||||
redirect_uri: '重定向 Url',
|
||||
onedrive_helper: '自定义配置可参考官方文档',
|
||||
refreshTime: '令牌刷新时间',
|
||||
refreshStatus: '令牌刷新状态',
|
||||
codeWarning: '当前授权码格式错误,请重新确认!',
|
||||
backupDir: '备份路径',
|
||||
isCN: '国内版 (暂不支持)',
|
||||
code: '授权码',
|
||||
codeHelper:
|
||||
'请点击获取按钮,然后登录 OneDrive 复制跳转链接中 code 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。',
|
||||
@@ -1335,6 +1351,7 @@ const message = {
|
||||
},
|
||||
clean: {
|
||||
scan: '开始扫描',
|
||||
scanHelper: '轻松梳理 1Panel 运行期间积累的垃圾文件',
|
||||
clean: '立即清理',
|
||||
reScan: '重新扫描',
|
||||
cleanHelper: '已勾选文件及目录清理后无法回滚(系统缓存文件清理需要重启服务),是否继续?',
|
||||
@@ -1459,7 +1476,6 @@ const message = {
|
||||
installHelper: '配置镜像加速可以解决镜像拉取失败的问题',
|
||||
upgradeHelper: '异常应用需要先同步到正常状态',
|
||||
installWarn: '当前未勾选端口外部访问,将无法通过外网IP:端口访问,是否继续?',
|
||||
restoreWarn: '恢复操作将删除该应用当前数据并重启。此操作不可回滚,是否继续?',
|
||||
showIgnore: '查看忽略应用',
|
||||
cancelIgnore: '取消忽略',
|
||||
ignoreList: '忽略列表',
|
||||
@@ -1681,7 +1697,7 @@ const message = {
|
||||
disable: '未启用',
|
||||
disableLeechHelper: '是否禁用防盗链',
|
||||
disableLeech: '禁用防盗链',
|
||||
ipv6: '监听 IPV6 端口',
|
||||
ipv6: '监听 IPV6',
|
||||
leechReturnError: '请填写 HTTP 状态码',
|
||||
selectAcme: '选择 acme 账号',
|
||||
imported: '手动创建',
|
||||
@@ -1710,6 +1726,7 @@ const message = {
|
||||
primaryDomainHelper: '支持域名:端口',
|
||||
acmeAccountType: '账号类型',
|
||||
keyType: '密钥算法',
|
||||
tencentCloud: '腾讯云',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短标签支持',
|
||||
@@ -1811,6 +1828,9 @@ const message = {
|
||||
httpHelper2: 'HTTP 模式无法申请泛域名证书',
|
||||
skipDNSCheck: '跳过 DNS 校验',
|
||||
skipDNSCheckHelper: '如果出现申请超时问题,请勾选此处,其他情况请勿勾选',
|
||||
cfHelper: '请勿使用 Global API Key',
|
||||
deprecated: '即将废弃',
|
||||
deprecatedHelper: '已经停止维护,可能会在以后的某个版本废弃,请使用腾讯云方式解析',
|
||||
},
|
||||
firewall: {
|
||||
create: '创建规则',
|
||||
|
||||
@@ -15,10 +15,8 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
if (to.name === 'entrance' && globalStore.isLogin) {
|
||||
if (to.params.code === globalStore.entrance) {
|
||||
globalStore.setLogStatus(false);
|
||||
next({
|
||||
name: 'entrance',
|
||||
params: { code: globalStore.entrance },
|
||||
name: 'home',
|
||||
});
|
||||
NProgress.done();
|
||||
return;
|
||||
|
||||
@@ -18,7 +18,7 @@ export const GlobalStore = defineStore({
|
||||
themeConfig: {
|
||||
panelName: '',
|
||||
primary: '#005EEB',
|
||||
theme: 'bright',
|
||||
theme: 'auto',
|
||||
footer: true,
|
||||
},
|
||||
isFullScreen: false,
|
||||
|
||||
@@ -288,6 +288,13 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
.mini-border-card {
|
||||
width: 100%;
|
||||
.el-card__body {
|
||||
--el-card-padding: 12px 12px 0 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.xterm-viewport::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -307,7 +314,6 @@ html {
|
||||
|
||||
.table-link {
|
||||
color: $primary-color;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -403,4 +409,4 @@ html {
|
||||
|
||||
.p-ml-5 {
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
}
|
||||
@@ -331,4 +331,8 @@ html.dark {
|
||||
.el-collapse-item__header {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +427,8 @@ export function toLowerCase(str: string) {
|
||||
|
||||
export function downloadFile(filePath: string) {
|
||||
let url = `${import.meta.env.VITE_API_URL as string}/files/download?`;
|
||||
window.open(url + 'path=' + filePath, '_blank');
|
||||
let path = encodeURIComponent(filePath);
|
||||
window.open(url + 'path=' + path, '_blank');
|
||||
}
|
||||
|
||||
export function downloadWithContent(content: string, fileName: string) {
|
||||
@@ -439,7 +440,6 @@ export function downloadWithContent(content: string, fileName: string) {
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
}
|
||||
|
||||
export function getDateStr() {
|
||||
let now: Date = new Date();
|
||||
|
||||
|
||||
@@ -103,11 +103,14 @@
|
||||
{{ $t('container.limitHelper', [limits.memory]) }}{{ req.memoryUnit }}B
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="editCompose">
|
||||
<el-checkbox v-model="req.editCompose" :label="$t('app.editCompose')" size="large" />
|
||||
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="pullImage">
|
||||
<el-checkbox v-model="req.pullImage" :label="$t('container.forcePull')" size="large" />
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="req.editCompose">
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
@@ -193,6 +196,7 @@ const initData = () => ({
|
||||
dockerCompose: '',
|
||||
version: '',
|
||||
appID: '',
|
||||
pullImage: true,
|
||||
});
|
||||
const req = reactive(initData());
|
||||
const limits = ref<Container.ResourceLimit>({
|
||||
|
||||
@@ -69,10 +69,10 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<template #rightButton>
|
||||
<el-button @click="sync" type="primary" link v-if="mode === 'installed' && data != null">
|
||||
<el-button @click="sync" type="primary" plain v-if="mode === 'installed' && data != null">
|
||||
{{ $t('app.sync') }}
|
||||
</el-button>
|
||||
<el-button @click="openIngore" type="primary" link v-if="mode === 'upgrade'">
|
||||
<el-button @click="openIngore" type="primary" plain v-if="mode === 'upgrade'">
|
||||
{{ $t('app.showIgnore') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -157,8 +157,8 @@
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
link
|
||||
@click="openLog(installed)"
|
||||
:disabled="installed.status === 'DownloadErr'"
|
||||
>
|
||||
@@ -169,7 +169,6 @@
|
||||
|
||||
<el-button
|
||||
class="h-button"
|
||||
type="primary"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@@ -184,7 +183,6 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
class="h-button"
|
||||
type="primary"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@@ -199,7 +197,6 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
class="h-button"
|
||||
type="primary"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@@ -210,7 +207,6 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
class="h-button"
|
||||
type="primary"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@@ -259,9 +255,7 @@
|
||||
<el-button
|
||||
v-for="(button, key) in buttons"
|
||||
:key="key"
|
||||
:type="
|
||||
button.disabled && button.disabled(installed) ? 'info' : 'primary'
|
||||
"
|
||||
:type="button.disabled && button.disabled(installed) ? 'info' : ''"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
<el-checkbox v-model="operateReq.backup" :label="$t('app.backupApp')" />
|
||||
<span class="input-help">{{ $t('app.backupAppHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="pullImage">
|
||||
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" />
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="22" :offset="1">
|
||||
@@ -88,6 +92,7 @@ const operateReq = reactive({
|
||||
operate: 'upgrade',
|
||||
installId: 0,
|
||||
backup: true,
|
||||
pullImage: true,
|
||||
});
|
||||
const resourceName = ref('');
|
||||
const rules = ref<any>({
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
{{ $t('container.includeAppstore') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<fu-table-column-select
|
||||
:columns="columns"
|
||||
trigger="hover"
|
||||
:title="$t('commons.table.selectColumn')"
|
||||
popper-class="popper-class"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
@@ -86,6 +92,8 @@
|
||||
@search="search"
|
||||
:row-style="{ height: '65px' }"
|
||||
style="width: 100%"
|
||||
:columns="columns"
|
||||
localKey="contanerColumn"
|
||||
>
|
||||
<el-table-column type="selection" />
|
||||
<el-table-column
|
||||
@@ -94,6 +102,7 @@
|
||||
min-width="100"
|
||||
prop="name"
|
||||
sortable
|
||||
fix
|
||||
:fixed="mobile ? 'false' : 'left'"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
@@ -184,7 +193,12 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.ip')" :width="mobile ? 80 : 'auto'" min-width="100" fix>
|
||||
<el-table-column
|
||||
:label="$t('container.ip')"
|
||||
:width="mobile ? 80 : 'auto'"
|
||||
min-width="100"
|
||||
prop="network"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.network">
|
||||
<div v-for="(item, index) in row.network" :key="index">{{ item }}</div>
|
||||
@@ -193,41 +207,42 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.related')" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip
|
||||
v-if="row.appName != ''"
|
||||
:hide-after="20"
|
||||
:content="row.appName"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
icon="Position"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@click="router.push({ name: 'AppInstalled' })"
|
||||
<div>
|
||||
<el-tooltip
|
||||
v-if="row.appName != ''"
|
||||
:hide-after="20"
|
||||
:content="$t('app.app') + ': ' + row.appName + '[' + row.appInstallName + ']'"
|
||||
placement="top"
|
||||
>
|
||||
{{ $t('app.app') }}: {{ row.appName }} [{{ row.appInstallName }}]
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
v-if="row.websites != null"
|
||||
:hide-after="20"
|
||||
:content="row.websites.join(',')"
|
||||
placement="top"
|
||||
class="mt-1"
|
||||
>
|
||||
<el-button
|
||||
icon="Position"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@click="router.push({ name: 'Website' })"
|
||||
<el-button
|
||||
icon="Position"
|
||||
plain
|
||||
size="small"
|
||||
@click="router.push({ name: 'AppInstalled' })"
|
||||
>
|
||||
{{ $t('app.app') }}: {{ row.appName }} [{{ row.appInstallName }}]
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip
|
||||
v-if="row.websites != null"
|
||||
:hide-after="20"
|
||||
:content="row.websites.join(',')"
|
||||
placement="top"
|
||||
class="mt-1"
|
||||
>
|
||||
{{ $t('website.website') }}:
|
||||
{{ row.websites.join(',') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
icon="Position"
|
||||
plain
|
||||
size="small"
|
||||
@click="router.push({ name: 'Website' })"
|
||||
>
|
||||
{{ $t('website.website') }}:
|
||||
{{ row.websites.join(',') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -235,7 +250,6 @@
|
||||
:width="mobile ? 260 : 'auto'"
|
||||
min-width="200"
|
||||
prop="ports"
|
||||
fix
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.ports">
|
||||
@@ -247,13 +261,12 @@
|
||||
@click="goDashboard(item)"
|
||||
class="tagMargin"
|
||||
icon="Position"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
>
|
||||
{{ item.length > 25 ? item.substring(0, 25) + '...' : item }}
|
||||
</el-button>
|
||||
<el-button v-else class="tagMargin" type="primary" plain size="small">
|
||||
<el-button v-else class="tagMargin" plain size="small">
|
||||
{{ item }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
@@ -277,14 +290,14 @@
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
prop="runTime"
|
||||
fix
|
||||
/>
|
||||
<fu-table-operations
|
||||
fix
|
||||
width="180px"
|
||||
:ellipsis="2"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
:fixed="mobile ? false : 'right'"
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
@@ -357,6 +370,7 @@ const dialogUpgradeRef = ref();
|
||||
const dialogPortJumpRef = ref();
|
||||
const opRef = ref();
|
||||
const includeAppStore = ref(true);
|
||||
const columns = ref([]);
|
||||
|
||||
const dockerStatus = ref('Running');
|
||||
const loadStatus = async () => {
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:autosize="{ minRows: 2, maxRows: 10 }"
|
||||
:rows="3"
|
||||
v-model="dialogData.rowData!.labelsStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -262,7 +262,7 @@
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:autosize="{ minRows: 2, maxRows: 10 }"
|
||||
:rows="3"
|
||||
v-model="dialogData.rowData!.envStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<el-input
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:rows="3"
|
||||
v-model="form.tagStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
v-for="(item, index) of row.tags"
|
||||
:key="index"
|
||||
:title="item"
|
||||
type="info"
|
||||
>
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div v-if="!withTagAll">
|
||||
<ul v-for="(item, index) in unTagList" :key="index">
|
||||
<li>
|
||||
{{ item.tags[0] }}
|
||||
{{ item.tags[0] || '< none >' }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -66,7 +66,11 @@ const acceptParams = async (): Promise<void> => {
|
||||
unTagList.value = [];
|
||||
unUsedList.value = [];
|
||||
for (const item of list) {
|
||||
if (item.tags && item.tags.length === 1 && item.tags[0].indexOf('<none>') !== -1 && !item.isUsed) {
|
||||
if (
|
||||
!item.tags ||
|
||||
item.tags.length === 0 ||
|
||||
(item.tags.length === 1 && item.tags[0].indexOf('<none>') !== -1 && !item.isUsed)
|
||||
) {
|
||||
unTagList.value.push(item);
|
||||
}
|
||||
if (!item.isUsed) {
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:rows="3"
|
||||
v-model="form.optionStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -122,7 +122,7 @@
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:rows="3"
|
||||
v-model="form.labelStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -51,9 +51,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag effect="dark" round v-if="row.isSystem || row.name === '1panel-network'">
|
||||
system
|
||||
</el-tag>
|
||||
<el-tag round v-if="row.isSystem || row.name === '1panel-network'">system</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -68,11 +66,11 @@
|
||||
<template #default="{ row }">
|
||||
<div v-for="(item, index) in row.labels" :key="index">
|
||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||
<el-tag>{{ item }}</el-tag>
|
||||
<el-tag type="info">{{ item }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!row.expand && row.labels.length > 3">
|
||||
<el-button type="primary" link @click="row.expand = true">
|
||||
<el-button link @click="row.expand = true">
|
||||
{{ $t('commons.button.expand') }}...
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<div style="width: 100%" v-if="form.mirrors">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
:rows="5"
|
||||
disabled
|
||||
v-model="form.mirrors"
|
||||
style="width: calc(100% - 80px)"
|
||||
@@ -82,7 +82,7 @@
|
||||
<div style="width: 100%" v-if="form.registries">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
:rows="5"
|
||||
disabled
|
||||
v-model="form.registries"
|
||||
style="width: calc(100% - 80px)"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user