Compare commits

...
83 Commits
Author SHA1 Message Date
ssongliu fdf9046430 fix: 全局备份目录变量给定初始值 (#3787) 2024-02-02 10:47:24 +08:00
ssongliu b3852db39e fix: 解决网站日志切割备份路径错误的问题 (#3785) 2024-02-01 23:54:59 +08:00
ssongliu 2f7bb6d784 fix: 解决本地备份账号无法切换到根目录的问题 (#3782) 2024-02-01 22:36:01 +08:00
ssongliu e5d3c1bb86 feat: 计划任务备份增加随机后缀 (#3778) 2024-02-01 18:35:57 +08:00
ssongliu c758c1ce87 fix: 解决计划任务记录删除时文件被误删问题 (#3777) 2024-02-01 18:05:40 +08:00
ssongliu d82086ce4a fix: 解决 Redis rbd 规则设置报错的问题 (#3776) 2024-02-01 18:04:02 +08:00
ssongliu edc3540e67 pref: 优化容器列表选项列选项 (#3775) 2024-02-01 07:01:41 +00:00
ssongliu 13a69fa1ed fix: 解决 Redis 终端输入 Ctrl + C 时,浏览器崩溃的问题 (#3774)
Refs #3754
2024-02-01 06:33:41 +00:00
ssongliu 948653213e style: 修改计划任务报告页面样式 (#3772) 2024-02-01 06:29:40 +00:00
ssongliu a4cfb587d6 pref: 优化同步快照返回信息 (#3773) 2024-02-01 06:23:40 +00:00
zhengkunwang 6aa9a95971 fix: 解决切换运行环境之后 PHP 高级设置还存在的问题 (#3771)
Refs https://github.com/1Panel-dev/1Panel/issues/3762
2024-02-01 06:21:40 +00:00
ssongliu e3666d17a3 fix: 解决应用恢复计划任务执行失败的问题 (#3770) 2024-01-31 20:38:01 +08:00
zhengkunwang 7219566273 fix: 解决文件下载路径包含 + 下载失败的问题 (#3768) 2024-01-31 12:23:59 +00:00
ssongliu 08a45e9723 fix: 解决应用恢复导致计划任务执行失败的问题 (#3769) 2024-01-31 10:47:21 +00:00
ssongliu be53945f0e fix: 解决升级导致计划任务数据丢失的问题 (#3766) 2024-01-31 16:07:19 +08:00
ssongliu 725ad3f6d9 fix: 暂时移除网站备份规则功能 (#3764) 2024-01-31 15:08:34 +08:00
ssongliu 42828d0a48 feat: 适配世纪互联版本 OneDrive (#3763) 2024-01-31 15:08:14 +08:00
ssongliu 3804942ae4 pref: 优化计划任务备份账号切换逻辑 (#3758) 2024-01-31 15:08:03 +08:00
ssongliu 83dda25591 fix: 解决镜像清理列表显示不全的问题 (#3759) 2024-01-31 14:50:47 +08:00
ssongliu f91d7c6170 fix: 解决数据升级失败的问题 (#3755) 2024-01-30 23:25:29 +08:00
ssongliu 2cf3b27402 style: 修改同步服务器时间计划任务提示信息 (#3751) 2024-01-30 16:11:58 +08:00
ssongliu 0e4f82c434 fix: 解决快照恢复失败的问题 (#3750) 2024-01-30 16:11:49 +08:00
zhengkunwang 51d7a84062 feat: 安装升级应用增加强制拉取镜像选项 (#3746) 2024-01-30 16:11:41 +08:00
zhengkunwang 70863f411d fix: 解决扩展模版过长无法保存的问题 (#3749)
Refs https://github.com/1Panel-dev/1Panel/issues/3731
2024-01-30 06:54:11 +00:00
ssongliu 5c9315ff1c fix: 解决计划任务升级导致数据异常问题 (#3744) 2024-01-30 14:39:13 +08:00
ssongliu 3846dcef86 style: 快照状态界面样式调整 (#3748) 2024-01-30 14:39:00 +08:00
ssongliu 8b0d73b5d5 fix: 解决计划任务备份下载路径错误的问题 (#3743) 2024-01-29 10:52:38 +00:00
ssongliu e309b9c030 fix: 解决计划任务备份上传路径错误的问题 (#3742) 2024-01-29 16:35:06 +08:00
ssongliu a5304b66e3 fix: 解决服务重启后计划任务状态错误的问题 (#3741) 2024-01-29 16:34:57 +08:00
ssongliu 10eef260d4 perf: 优化计划任务备份列表打开速度 (#3740) 2024-01-29 16:34:49 +08:00
ssongliu 045dbaef91 fix: 数据库连接信息更改为容器名 (#3735) 2024-01-29 16:34:40 +08:00
zhengkunwang 7a45db71d5 fix: 解决 Node.js 运行环境网站停止失败的问题 (#3734) 2024-01-29 16:34:32 +08:00
ssongliu 0ee942cbef fix: 优化缓存清理界面样式 (#3733)
![image](https://github.com/1Panel-dev/1Panel/assets/73214554/30426b21-0bf8-4e3f-b59b-2016ae4b55b9)
2024-01-29 02:28:49 +00:00
ssongliu aa8429a836 fix: 计划任务列表样式调整 (#3718) 2024-01-26 14:44:11 +00:00
ssongliu 9f2227cf19 fix: 解决备份列表异常时,备份列表无法打开的问题 (#3717) 2024-01-26 17:08:19 +08:00
Leif 338ade095a fix: 解决日志中出现非UTF-8编码字符导致日志错误 (#3715) (#3716) 2024-01-26 16:07:37 +08:00
ssongliu d5432c8e59 fix: 界面表格样式调整 (#3714) 2024-01-26 15:08:36 +08:00
ssongliu 4eb34f24d1 feat: 页脚增加项目跳转地址 (#3712) 2024-01-26 13:46:13 +08:00
ssongliu 527c8143a1 feat: 增加备份恢复确认框 (#3711) 2024-01-26 13:46:04 +08:00
ssongliu a6891711b7 fix: 计划任务名称禁用空格 (#3710) 2024-01-26 13:45:56 +08:00
ssongliu 36a253ce03 feat: 主机快速命令增加排序 (#3707) 2024-01-26 13:45:41 +08:00
ssongliu bbe1161dc2 fix: 解决 Fail2ban 禁用方式切换失败的问题 (#3704) 2024-01-25 18:18:47 +08:00
ssongliu e85ed7427a fix: 解决接口文档描述不一致的问题 (#3703) 2024-01-25 18:18:26 +08:00
ssongliu 97f8a872c0 feat: PostgreSQL 远程数据库增加用户绑定提示信息 (#3701) 2024-01-25 18:18:10 +08:00
ssongliu 48187b508d fix: 解决登陆状态下跳转安全入口的问题 (#3702) 2024-01-25 18:17:26 +08:00
ssongliu ca1710cabc fix: WebDav 支持 https (#3700) 2024-01-25 18:17:13 +08:00
ssongliu df2a705713 fix: 调整快照页面备份路径样式 (#3696) 2024-01-25 17:24:09 +08:00
zhengkunwang d917008f47 style: 修改部分页面样式 (#3692) 2024-01-25 15:18:03 +08:00
ssongliu 61078a24fd feat: 快照支持本地目录及多选备份账号 (#3693) 2024-01-25 13:55:11 +08:00
ssongliu e35998a4be fix: 统一多行文本输入样式 (#3694) 2024-01-25 13:54:49 +08:00
ssongliu d1fde34a14 feat: 增加计划任务服务器时间同步提示信息 (#3695) 2024-01-25 13:54:32 +08:00
ssongliu 45394598d9 fix: 调整计划任务备份账号字段名 (#3691) 2024-01-25 11:20:42 +08:00
ssongliu edd6b52f05 feat: 计划任务支持备份到多个备份账号 (#3689) 2024-01-24 17:41:56 +08:00
zhengkunwang 0f53de56a7 feat: 容器列表增加选择列功能 (#3684) 2024-01-23 18:48:58 +08:00
zhengkunwang 4ce50328ba feat: 优化应用升级逻辑 支持 docker-compose.yml 文件的 image 值中包含参数 (#3681) 2024-01-23 15:24:13 +08:00
ssongliu 115b9c58b0 feat: 计划任务支持多周期执行 (#3676) 2024-01-22 22:29:19 +08:00
zhengkunwang 64def5bebd feat:证书 DNS 账号增加腾讯云类型 (#3673) 2024-01-22 22:28:52 +08:00
zhengkunwang e46f931989 style: 修改文件上传页面部分样式 (#3672) 2024-01-22 22:28:40 +08:00
dependabot[bot] 1a78aecfb1 build(deps-dev): bump vite from 2.9.16 to 2.9.17 in /frontend (#3669)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 2.9.16 to 2.9.17.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v2.9.17/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v2.9.17/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 10:41:44 +08:00
igophper e8c94450f4 fix: 删除带特殊符号文件的报错问题 (#3657) (#3658)
Refs #3657
2024-01-20 22:17:37 +08:00
ssongliu 6bc4cfc8ac feat: OneDrive 支持世纪互联 (#3655) 2024-01-19 23:06:41 +08:00
zhengkunwang 24579edad6 feat: 文件编辑器增加 lua xml 语言支持 (#3652) 2024-01-19 16:48:55 +08:00
ssongliu 6499d7f462 feat: OneDrive 增加 Token 刷新机制 (#3637) 2024-01-19 16:48:41 +08:00
zhengkunwang 1e5cd23bbe style: CloudFlare DNS 账号增加提示 (#3648) 2024-01-18 22:46:16 +08:00
zhengkunwang fce0ac2c7d fix: 反向代理的文本替换不兼容 " 符号的问题 (#3647) 2024-01-18 22:45:51 +08:00
zhengkunwang f5bf111203 fix: 解决网站日志清空页面没有刷新的问题 (#3645) 2024-01-18 17:05:22 +08:00
ssongliu 5152e7b8d5 feat: OneDrive 增加 Token 刷新机制 (#3638) 2024-01-18 10:38:25 +08:00
zhengkunwang d30be34def fix: 解决网站删除目录错误的问题 (#3632) 2024-01-17 18:15:59 +08:00
真心 1a9182bfde faet: 设置默认主题为 auto (#3634)
* faet: set default theme to `auto`
2024-01-17 18:12:52 +08:00
igophper aeb0549a9a fix: 解决Redis配置修改报错的问题 (#3627) (#3629)
Refs #3627
2024-01-17 10:14:40 +08:00
igophper 64f3bbb13b fix: sshd_config兼容prohibit-password (#3609) (#3626) 2024-01-16 21:10:37 +08:00
ssongliu bc0fbc4461 fix: 解决容器未启动时无法编辑计划任务的问题 (#3622) 2024-01-16 16:03:56 +08:00
ssongliu 1e5ea024ad fix: 解决部分 Onedrive 备份账号添加失败的问题 (#3621) 2024-01-16 16:03:45 +08:00
zhengkunwang f89365106a style: 容器列表页面适配手机端 (#3618) 2024-01-15 15:57:12 +08:00
ssongliu 4891de0a8a fix: 解决类型断言错误的问题 (#3619) 2024-01-15 15:57:02 +08:00
ssongliu be6b58b0a1 fix: WebDav 端口去掉必填校验 (#3617) 2024-01-15 15:07:00 +08:00
zhengkunwang 33910f7993 style: 容器列表页面适配手机端 (#3616) 2024-01-15 14:16:15 +08:00
ssongliu 7036ba0d5c fix: 移除备份账号保存份数最大限制 (#3615) 2024-01-15 14:16:05 +08:00
ssongliu 204a9fac87 fix: 删除 hosts 错误提示信息 (#3614) 2024-01-15 14:15:55 +08:00
zhengkunwang 6879b92db3 fix: 解决切换 PHP 版本扩展模版没办法继续选择的问题 (#3613) 2024-01-15 14:15:45 +08:00
ssongliu 54242ae220 fix: 解决本地备份路径无法编辑的问题 (#3612) 2024-01-15 14:15:36 +08:00
zhengkunwang 6f0f282b7e fix: 解决 zip 压缩前后大小不变的问题 (#3610) 2024-01-15 14:15:26 +08:00
ssongliu 817fd123da fix: 解决 WebDav 备份账号校验错误的问题 (#3587)
Refs #3585
2024-01-12 09:13:08 +00:00
154 changed files with 4357 additions and 2778 deletions
+47 -17
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+2 -4
View File
@@ -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 {
+6 -6
View File
@@ -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 {
+2 -3
View File
@@ -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
}
+11
View File
@@ -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
View File
@@ -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
View File
@@ -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"`
+2
View File
@@ -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 {
+7 -5
View File
@@ -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
View File
@@ -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"`
+2
View File
@@ -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"`
+13 -15
View File
@@ -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 {
+7 -6
View File
@@ -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)"`
+8
View File
@@ -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)
}
}
+15 -3
View File
@@ -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 {
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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:
+32 -23
View File
@@ -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
View File
@@ -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.")
}
+9 -4
View File
@@ -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
}
+7 -4
View File
@@ -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 {
+5 -3
View File
@@ -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 {
+5 -3
View File
@@ -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 {
+5 -3
View File
@@ -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 {
+4 -4
View File
@@ -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
+9
View File
@@ -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
+67 -79
View File
@@ -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 ""
}
}
+386
View File
@@ -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
}
+136 -504
View File
@@ -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
}
+23 -4
View File
@@ -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) {
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+45 -24
View File
@@ -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))
}
+12 -14
View File
@@ -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})
+1 -1
View File
@@ -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 ", "")
+22 -5
View File
@@ -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()
+1
View File
@@ -12,6 +12,7 @@ const (
Cos = "COS"
Kodo = "KODO"
WebDAV = "WebDAV"
Local = "LOCAL"
OneDriveRedirectURI = "http://localhost/login/authorized"
)
+8 -2
View File
@@ -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)
}
}
+3 -2
View File
@@ -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
View File
@@ -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"])
}
+4
View File
@@ -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)
+1 -1
View File
@@ -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
}
+254
View File
@@ -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
},
}
+2
View File
@@ -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)
+5 -5
View File
@@ -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"
}
+3 -5
View File
@@ -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])
}
+4 -4
View File
@@ -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
}
+4 -4
View File
@@ -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
+128 -87
View File
@@ -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
}
+5 -5
View File
@@ -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"
}
+13 -6
View File
@@ -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),
+18 -26
View File
@@ -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
}
+26 -28
View File
@@ -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
View File
@@ -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) {
+22 -6
View File
@@ -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
+2 -2
View File
@@ -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)
}
}
+9 -3
View File
@@ -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
}
+18 -7
View File
@@ -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
+7 -3
View File
@@ -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
View File
@@ -1,5 +1,5 @@
// Code generated by swaggo/swag. DO NOT EDIT.
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
@@ -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
View File
@@ -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
View File
@@ -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: 获取备份账号列表
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -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",
+1
View File
@@ -145,6 +145,7 @@ export namespace App {
username: string;
password: string;
privilege: boolean;
containerName: string;
serviceName: string;
systemIP: string;
port: number;
+8
View File
@@ -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;
-5
View File
@@ -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;
}
}
+23 -25
View File
@@ -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 {
+3
View File
@@ -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;
+2 -2
View File
@@ -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 = () => {
+8 -2
View File
@@ -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 });
+55 -31
View File
@@ -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);
},
+5 -1
View File
@@ -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()
+16 -22
View File
@@ -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);
},
},
{
+1 -1
View File
@@ -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 {
+13 -1
View File
@@ -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',
},
{
+29 -8
View File
@@ -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',
+29 -9
View File
@@ -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: '創建規則',
+28 -8
View File
@@ -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: '创建规则',
+1 -3
View File
@@ -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;
+1 -1
View File
@@ -18,7 +18,7 @@ export const GlobalStore = defineStore({
themeConfig: {
panelName: '',
primary: '#005EEB',
theme: 'bright',
theme: 'auto',
footer: true,
},
isFullScreen: false,
+8 -2
View File
@@ -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;
}
}
+4
View File
@@ -331,4 +331,8 @@ html.dark {
.el-collapse-item__header {
color: #ffffff;
}
.file-item:hover {
background-color: #4f4f4f;
}
}
+2 -2
View File
@@ -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