mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86ba53d6ec | ||
|
|
0ca559f1e1 | ||
|
|
1fd31fffd6 | ||
|
|
c62c19a49e | ||
|
|
aa8816f3ec | ||
|
|
920f1112fa | ||
|
|
4c2302dc94 | ||
|
|
efebb26b5e | ||
|
|
22fe2a6d51 | ||
|
|
e17b80cff4 | ||
|
|
1d6f1b0ef3 | ||
|
|
dac0c81d96 | ||
|
|
e555dcb903 | ||
|
|
15c391e763 | ||
|
|
3b3584714c | ||
|
|
61a0244cfe | ||
|
|
73a61933c5 | ||
|
|
72c7407b0b | ||
|
|
6538282a7b | ||
|
|
87326e3292 | ||
|
|
6eadb116c2 | ||
|
|
c9ffd2564e | ||
|
|
cd6e5f7905 | ||
|
|
b96e988f20 | ||
|
|
30f7fa6afa | ||
|
|
4605b19473 | ||
|
|
87327053dc | ||
|
|
5e7d5c0235 | ||
|
|
1ccc56100c | ||
|
|
ef948bca76 | ||
|
|
d01a964534 | ||
|
|
05004cb141 | ||
|
|
dfcef390d0 | ||
|
|
ff3b41686c | ||
|
|
7dd5082bac | ||
|
|
ec05e81286 | ||
|
|
e7a7d391e4 | ||
|
|
e6b1ef30a5 | ||
|
|
f40b053e5b | ||
|
|
1a891cb048 | ||
|
|
66007c07e2 | ||
|
|
5058a814aa | ||
|
|
37d8244414 | ||
|
|
cda5112f5e | ||
|
|
919f10cc11 | ||
|
|
66b12800e4 | ||
|
|
227856b45b | ||
|
|
44ca529027 | ||
|
|
885e3b60f0 | ||
|
|
07bbf057d0 | ||
|
|
b9227caaf8 | ||
|
|
55ed67eaed | ||
|
|
ac55385696 | ||
|
|
f9b93e8c85 | ||
|
|
ac7f33a29c | ||
|
|
4de99f66a8 | ||
|
|
c0c1d519bf | ||
|
|
f22980f4ce | ||
|
|
7b297e824c | ||
|
|
28bd0e3cc2 | ||
|
|
f90c009782 | ||
|
|
a54913f788 | ||
|
|
ea3dca79aa | ||
|
|
2b207597b9 | ||
|
|
044bb79ae9 | ||
|
|
afcebb46a0 | ||
|
|
10427ddd65 | ||
|
|
0ac2b9df7a | ||
|
|
695aacbe14 | ||
|
|
71107fa42f | ||
|
|
d36dfc5490 | ||
|
|
a463f237b1 | ||
|
|
ec105ede83 | ||
|
|
92aff95b3a | ||
|
|
1fcc345a7a | ||
|
|
7e9a09c960 | ||
|
|
8212ff117b | ||
|
|
b5a1ffe338 | ||
|
|
759382bcfb | ||
|
|
12eeb6503c | ||
|
|
9fa9772c07 | ||
|
|
35334c1650 | ||
|
|
006c27fee5 | ||
|
|
271be81557 | ||
|
|
b61d8aaabc | ||
|
|
319afd2d51 | ||
|
|
bd2facebee | ||
|
|
3cbaa052c8 | ||
|
|
a0ceb62372 | ||
|
|
dddd190911 | ||
|
|
f70b0049d9 | ||
|
|
6fea06729e | ||
|
|
24e6fe89c8 | ||
|
|
e507611cad | ||
|
|
72dcdbad1e | ||
|
|
0a55dec949 | ||
|
|
555a32c273 | ||
|
|
fdf2a9d247 | ||
|
|
30bb64d058 | ||
|
|
695d4b4a16 | ||
|
|
01c08a8ef9 | ||
|
|
dd9f2edf40 | ||
|
|
105c249d97 | ||
|
|
b780df96af | ||
|
|
46495937b1 | ||
|
|
597c9ea4c0 | ||
|
|
4662f4703c | ||
|
|
5f750e6f49 | ||
|
|
152ba81c34 | ||
|
|
4bf76aacb1 | ||
|
|
6c4c73e825 | ||
|
|
917851dcd7 | ||
|
|
2e5bf4202c | ||
|
|
d4319fa55c | ||
|
|
2ff7726442 | ||
|
|
38bf54ec3b | ||
|
|
a1c76600e2 | ||
|
|
fbcb3da422 | ||
|
|
3978fb3e46 | ||
|
|
64f80a95ab | ||
|
|
847c14ddda | ||
|
|
38c0d290e7 | ||
|
|
c403eb55b1 | ||
|
|
506d78cb00 | ||
|
|
27918e2dc5 | ||
|
|
d44231a0ff | ||
|
|
11a58fde91 | ||
|
|
3a8c3b5816 | ||
|
|
50deda27ca | ||
|
|
4482fa23e6 | ||
|
|
4c83a3bf36 | ||
|
|
514538179e | ||
|
|
4c8245045d | ||
|
|
efe185e5dc | ||
|
|
62becf819d | ||
|
|
f02f32456e | ||
|
|
57916ed6d7 | ||
|
|
efa3d06673 | ||
|
|
14f7435f82 | ||
|
|
dbeaaa49f3 | ||
|
|
7546391c17 | ||
|
|
d8d2ee0f46 | ||
|
|
3c57fa76bf | ||
|
|
3fa4a240f7 | ||
|
|
ae38239b47 | ||
|
|
6a63b2e5c4 | ||
|
|
3ead633343 | ||
|
|
e71f765f2a | ||
|
|
2b7f68f3fe | ||
|
|
c82e20efd7 | ||
|
|
aa37e3885c | ||
|
|
7918023322 | ||
|
|
80304937e1 | ||
|
|
352978b54d | ||
|
|
b7cda1d2f1 | ||
|
|
20dbd6c181 | ||
|
|
8808e1b0c3 |
@@ -130,6 +130,22 @@ func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
|
||||
helper.SuccessWithData(c, appDetailDTO)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Get Ignore App
|
||||
// @Description 获取忽略的应用版本
|
||||
// @Accept json
|
||||
// @Success 200 {object} response.IgnoredApp
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/ingored [get]
|
||||
func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
|
||||
res, err := appService.GetIgnoredApp()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Install app
|
||||
// @Description 安装应用
|
||||
|
||||
@@ -118,7 +118,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
||||
// @Description 删除前检查
|
||||
// @Accept json
|
||||
// @Param appInstallId path integer true "App install id"
|
||||
// @Success 200 {anrry} dto.AppResource
|
||||
// @Success 200 {array} dto.AppResource
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/installed/delete/check/:appInstallId [get]
|
||||
func (b *BaseApi) DeleteCheck(c *gin.Context) {
|
||||
@@ -178,7 +178,7 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) {
|
||||
// @Description 通过 key 获取应用 service
|
||||
// @Accept json
|
||||
// @Param key path string true "request"
|
||||
// @Success 200 {anrry} response.AppService
|
||||
// @Success 200 {array} response.AppService
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/services/:key [get]
|
||||
func (b *BaseApi) GetServices(c *gin.Context) {
|
||||
@@ -196,7 +196,7 @@ func (b *BaseApi) GetServices(c *gin.Context) {
|
||||
// @Description 通过 install id 获取应用更新版本
|
||||
// @Accept json
|
||||
// @Param appInstallId path integer true "request"
|
||||
// @Success 200 {anrry} dto.AppVersion
|
||||
// @Success 200 {array} dto.AppVersion
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/installed/:appInstallId/versions [get]
|
||||
func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
|
||||
@@ -305,3 +305,25 @@ func (b *BaseApi) UpdateInstalled(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary ignore App Update
|
||||
// @Description 忽略应用升级版本
|
||||
// @Accept json
|
||||
// @Param request body request.AppInstalledIgnoreUpgrade true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/installed/ignore [post]
|
||||
// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"忽略应用 [installId] 版本升级","formatEN":"Application param update [installId]"}
|
||||
func (b *BaseApi) IgnoreUpgrade(c *gin.Context) {
|
||||
var req request.AppInstalledIgnoreUpgrade
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := appInstallService.IgnoreUpgrade(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
|
||||
// @Description 获取 bucket 列表
|
||||
// @Accept json
|
||||
// @Param request body dto.ForBuckets true "request"
|
||||
// @Success 200 {anrry} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/search [post]
|
||||
func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||
@@ -98,6 +98,22 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||
helper.SuccessWithData(c, buckets)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Load OneDrive info
|
||||
// @Description 获取 OneDrive 信息
|
||||
// @Accept json
|
||||
// @Success 200 string clientID
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/onedrive [get]
|
||||
func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
|
||||
clientID, err := backupService.LoadOneDriveInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, clientID)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Delete backup account
|
||||
// @Description 删除备份账号
|
||||
@@ -253,7 +269,7 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
||||
// @Tags Backup Account
|
||||
// @Summary List backup accounts
|
||||
// @Description 获取备份账号列表
|
||||
// @Success 200 {anrry} dto.BackupInfo
|
||||
// @Success 200 {array} dto.BackupInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/search [get]
|
||||
func (b *BaseApi) ListBackup(c *gin.Context) {
|
||||
@@ -271,7 +287,7 @@ func (b *BaseApi) ListBackup(c *gin.Context) {
|
||||
// @Description 获取备份账号内文件列表
|
||||
// @Accept json
|
||||
// @Param request body dto.BackupSearchFile true "request"
|
||||
// @Success 200 {anrry} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/search/files [post]
|
||||
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
||||
|
||||
@@ -66,7 +66,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
|
||||
// @Summary List compose templates
|
||||
// @Description 获取容器编排模版列表
|
||||
// @Produce json
|
||||
// @Success 200 {anrry} dto.ComposeTemplateInfo
|
||||
// @Success 200 {array} dto.ComposeTemplateInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/template [get]
|
||||
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
|
||||
|
||||
+162
-21
@@ -40,6 +40,23 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary List containers
|
||||
// @Description 获取容器名称
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/list [post]
|
||||
func (b *BaseApi) ListContainer(c *gin.Context) {
|
||||
list, err := containerService.List()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Container Compose
|
||||
// @Summary Page composes
|
||||
// @Description 获取编排列表分页
|
||||
@@ -153,17 +170,97 @@ func (b *BaseApi) OperatorCompose(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Update container
|
||||
// @Description 更新容器
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器 [name][image]","formatEN":"update container [name][image]"}
|
||||
func (b *BaseApi) ContainerUpdate(c *gin.Context) {
|
||||
var req dto.ContainerOperate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := containerService.ContainerUpdate(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Load container info
|
||||
// @Description 获取容器表单信息
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.ContainerOperate
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/info [post]
|
||||
func (b *BaseApi) ContainerInfo(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
data, err := containerService.ContainerInfo(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Summary Load container limis
|
||||
// @Description 获取容器限制
|
||||
// @Success 200 {object} dto.ResourceLimit
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/limit [get]
|
||||
func (b *BaseApi) LoadResouceLimit(c *gin.Context) {
|
||||
data, err := containerService.LoadResouceLimit()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Summary Load container stats
|
||||
// @Description 获取容器列表资源占用
|
||||
// @Success 200 {array} dto.ContainerListStats
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/list/stats [get]
|
||||
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
||||
datas, err := containerService.ContainerListStats()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, datas)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Create container
|
||||
// @Description 创建容器
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerCreate true "request"
|
||||
// @Param request body dto.ContainerOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers [post]
|
||||
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"}
|
||||
func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
||||
var req dto.ContainerCreate
|
||||
var req dto.ContainerOperate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
@@ -179,6 +276,32 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Upgrade container
|
||||
// @Description 更新容器镜像
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerUpgrade true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/upgrade [post]
|
||||
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器镜像 [name][image]","formatEN":"upgrade container image [name][image]"}
|
||||
func (b *BaseApi) ContainerUpgrade(c *gin.Context) {
|
||||
var req dto.ContainerUpgrade
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := containerService.ContainerUpgrade(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Clean container
|
||||
// @Description 容器清理
|
||||
@@ -262,7 +385,7 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) {
|
||||
// @Summary Container stats
|
||||
// @Description 容器监控信息
|
||||
// @Param id path integer true "容器id"
|
||||
// @Success 200 {object} dto.ContainterStats
|
||||
// @Success 200 {object} dto.ContainerStats
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/stats/:id [get]
|
||||
func (b *BaseApi) ContainerStats(c *gin.Context) {
|
||||
@@ -310,27 +433,29 @@ func (b *BaseApi) Inspect(c *gin.Context) {
|
||||
// @Tags Container
|
||||
// @Summary Container logs
|
||||
// @Description 容器日志
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerLog true "request"
|
||||
// @Success 200 {string} logs
|
||||
// @Param container query string false "容器名称"
|
||||
// @Param since query string false "时间筛选"
|
||||
// @Param follow query string false "是否追踪"
|
||||
// @Param tail query string false "显示行号"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/search/log [post]
|
||||
func (b *BaseApi) ContainerLogs(c *gin.Context) {
|
||||
var req dto.ContainerLog
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
logs, err := containerService.ContainerLogs(req)
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
container := c.Query("container")
|
||||
since := c.Query("since")
|
||||
follow := c.Query("follow") == "true"
|
||||
tail := c.Query("tail")
|
||||
|
||||
if err := containerService.ContainerLogs(wsConn, container, since, tail, follow); err != nil {
|
||||
_ = wsConn.WriteMessage(1, []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, logs)
|
||||
}
|
||||
|
||||
// @Tags Container Network
|
||||
@@ -364,6 +489,23 @@ func (b *BaseApi) SearchNetwork(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Container Network
|
||||
// @Summary List networks
|
||||
// @Description 获取容器网络列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} dto.Options
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/network [get]
|
||||
func (b *BaseApi) ListNetwork(c *gin.Context) {
|
||||
list, err := containerService.ListNetwork()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Container Network
|
||||
// @Summary Delete network
|
||||
// @Description 删除容器网络
|
||||
@@ -453,11 +595,10 @@ func (b *BaseApi) SearchVolume(c *gin.Context) {
|
||||
// @Summary List volumes
|
||||
// @Description 获取容器存储卷列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Success 200 {array} dto.Options
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/volume/search [get]
|
||||
// @Router /containers/volume [get]
|
||||
func (b *BaseApi) ListVolume(c *gin.Context) {
|
||||
list, err := containerService.ListVolume()
|
||||
if err != nil {
|
||||
|
||||
@@ -216,7 +216,7 @@ func (b *BaseApi) SearchMysql(c *gin.Context) {
|
||||
// @Description 获取 mysql 数据库列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200 {anrry} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/options [get]
|
||||
func (b *BaseApi) ListDBName(c *gin.Context) {
|
||||
@@ -234,7 +234,7 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
|
||||
// @Description Mysql 数据库删除前检查
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200 {anrry} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/del/check [post]
|
||||
func (b *BaseApi) DeleteCheckMysql(c *gin.Context) {
|
||||
|
||||
@@ -49,4 +49,5 @@ var (
|
||||
upgradeService = service.NewIUpgradeService()
|
||||
|
||||
runtimeService = service.NewRuntimeService()
|
||||
processService = service.NewIProcessService()
|
||||
)
|
||||
|
||||
+10
-12
@@ -52,7 +52,7 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
|
||||
// @Description 分页获取上传文件
|
||||
// @Accept json
|
||||
// @Param request body request.SearchUploadWithPage true "request"
|
||||
// @Success 200 {anrry} response.FileInfo
|
||||
// @Success 200 {array} response.FileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/upload/search [post]
|
||||
func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
|
||||
@@ -81,7 +81,7 @@ func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
|
||||
// @Description 加载文件树
|
||||
// @Accept json
|
||||
// @Param request body request.FileOption true "request"
|
||||
// @Success 200 {anrry} response.FileTree
|
||||
// @Success 200 {array} response.FileTree
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/tree [post]
|
||||
func (b *BaseApi) GetFileTree(c *gin.Context) {
|
||||
@@ -532,7 +532,7 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
file.Seek(startPos, 0)
|
||||
_, _ = file.Seek(startPos, 0)
|
||||
reader := io.LimitReader(file, endPos-startPos+1)
|
||||
_, err = io.CopyBuffer(c.Writer, reader, buffer)
|
||||
if err != nil {
|
||||
@@ -683,7 +683,12 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
|
||||
}
|
||||
filename := c.PostForm("filename")
|
||||
fileDir := filepath.Join(tmpDir, filename)
|
||||
_ = os.MkdirAll(fileDir, 0755)
|
||||
if chunkIndex == 0 {
|
||||
if fileOp.Stat(fileDir) {
|
||||
_ = fileOp.DeleteDir(fileDir)
|
||||
}
|
||||
_ = os.MkdirAll(fileDir, 0755)
|
||||
}
|
||||
filePath := filepath.Join(fileDir, filename)
|
||||
|
||||
defer func() {
|
||||
@@ -734,19 +739,12 @@ var wsUpgrade = websocket.Upgrader{
|
||||
},
|
||||
}
|
||||
|
||||
var WsManager = websocket2.Manager{
|
||||
Group: make(map[string]*websocket2.Client),
|
||||
Register: make(chan *websocket2.Client, 128),
|
||||
UnRegister: make(chan *websocket2.Client, 128),
|
||||
ClientCount: 0,
|
||||
}
|
||||
|
||||
func (b *BaseApi) Ws(c *gin.Context) {
|
||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wsClient := websocket2.NewWsClient("wsClient", ws)
|
||||
wsClient := websocket2.NewWsClient("fileClient", ws)
|
||||
go wsClient.Read()
|
||||
go wsClient.Write()
|
||||
}
|
||||
|
||||
+23
-57
@@ -1,13 +1,11 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -30,22 +28,6 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if req.AuthMode == "password" && len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
}
|
||||
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
|
||||
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
}
|
||||
|
||||
host, err := hostService.Create(req)
|
||||
if err != nil {
|
||||
@@ -102,7 +84,7 @@ func (b *BaseApi) TestByID(c *gin.Context) {
|
||||
// @Description 加载主机树
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchForTree true "request"
|
||||
// @Success 200 {anrry} dto.HostTree
|
||||
// @Success 200 {array} dto.HostTree
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /hosts/tree [post]
|
||||
func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
@@ -126,7 +108,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
// @Description 获取主机列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchHostWithPage true "request"
|
||||
// @Success 200 {anrry} dto.HostTree
|
||||
// @Success 200 {array} dto.HostTree
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /hosts/search [post]
|
||||
func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
@@ -148,33 +130,6 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Load host info
|
||||
// @Description 加载主机信息
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} dto.HostInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /hosts/:id [get]
|
||||
func (b *BaseApi) GetHostInfo(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
host, err := hostService.GetHostInfo(id)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
var hostDto dto.HostInfo
|
||||
if err := copier.Copy(&hostDto, host); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, hostDto)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Delete host
|
||||
// @Description 删除主机
|
||||
@@ -221,21 +176,30 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if req.AuthMode == "password" && len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
var err error
|
||||
if len(req.Password) != 0 && req.AuthMode == "password" {
|
||||
req.Password, err = hostService.EncryptHost(req.Password)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.Password = string(password)
|
||||
req.PrivateKey = ""
|
||||
req.PassPhrase = ""
|
||||
}
|
||||
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
|
||||
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
|
||||
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
|
||||
req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.PrivateKey = string(privateKey)
|
||||
if len(req.PassPhrase) != 0 {
|
||||
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
req.Password = ""
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
@@ -246,10 +210,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
upMap["user"] = req.User
|
||||
upMap["auth_mode"] = req.AuthMode
|
||||
upMap["remember_password"] = req.RememberPassword
|
||||
if len(req.Password) != 0 {
|
||||
if req.AuthMode == "password" {
|
||||
upMap["password"] = req.Password
|
||||
}
|
||||
if len(req.PrivateKey) != 0 {
|
||||
upMap["private_key"] = ""
|
||||
upMap["pass_phrase"] = ""
|
||||
} else {
|
||||
upMap["password"] = ""
|
||||
upMap["private_key"] = req.PrivateKey
|
||||
upMap["pass_phrase"] = req.PassPhrase
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (b *BaseApi) SearchImage(c *gin.Context) {
|
||||
// @Summary List images
|
||||
// @Description 获取镜像列表
|
||||
// @Produce json
|
||||
// @Success 200 {anrry} dto.Options
|
||||
// @Success 200 {array} dto.Options
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/image [get]
|
||||
func (b *BaseApi) ListImage(c *gin.Context) {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
|
||||
// @Summary List image repos
|
||||
// @Description 获取镜像仓库列表
|
||||
// @Produce json
|
||||
// @Success 200 {anrry} dto.ImageRepoOption
|
||||
// @Success 200 {array} dto.ImageRepoOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/repo [get]
|
||||
func (b *BaseApi) ListRepo(c *gin.Context) {
|
||||
|
||||
@@ -27,7 +27,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) {
|
||||
// @Description 获取部分 OpenResty 配置信息
|
||||
// @Accept json
|
||||
// @Param request body request.NginxScopeReq true "request"
|
||||
// @Success 200 {anrry} response.NginxParam
|
||||
// @Success 200 {array} response.NginxParam
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /openResty/scope [post]
|
||||
func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
websocket2 "github.com/1Panel-dev/1Panel/backend/utils/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) ProcessWs(c *gin.Context) {
|
||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wsClient := websocket2.NewWsClient("processClient", ws)
|
||||
go wsClient.Read()
|
||||
go wsClient.Write()
|
||||
}
|
||||
|
||||
// @Tags Process
|
||||
// @Summary Stop Process
|
||||
// @Description 停止进程
|
||||
// @Param request body request.ProcessReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /process/stop [post]
|
||||
// @x-panel-log {"bodyKeys":["PID"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"结束进程 [PID]","formatEN":"结束进程 [PID]"}
|
||||
func (b *BaseApi) StopProcess(c *gin.Context) {
|
||||
var req request.ProcessReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := processService.StopProcess(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -260,11 +262,23 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||
// @Tags System Setting
|
||||
// @Summary Load mfa info
|
||||
// @Description 获取 mfa 信息
|
||||
// @Param interval path string true "request"
|
||||
// @Success 200 {object} mfa.Otp
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/mfa [get]
|
||||
// @Router /settings/mfa/:interval [get]
|
||||
func (b *BaseApi) GetMFA(c *gin.Context) {
|
||||
otp, err := mfa.GetOtp("admin")
|
||||
intervalStr, ok := c.Params.Get("interval")
|
||||
if !ok {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error interval in path"))
|
||||
return
|
||||
}
|
||||
interval, err := strconv.Atoi(intervalStr)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("type conversion failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
otp, err := mfa.GetOtp("admin", interval)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -288,12 +302,17 @@ func (b *BaseApi) MFABind(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
success := mfa.ValidCode(req.Code, req.Secret)
|
||||
success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
|
||||
if !success {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFAInterval", req.Interval); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFAStatus", "enable"); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
@@ -22,24 +20,27 @@ import (
|
||||
)
|
||||
|
||||
func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Query("id"))
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
id, err := strconv.Atoi(c.Query("id"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
|
||||
return
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
host, err := hostService.GetHostInfo(uint(id))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
|
||||
return
|
||||
}
|
||||
var connInfo ssh.ConnInfo
|
||||
@@ -49,13 +50,6 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
connInfo.PassPhrase = []byte(host.PassPhrase)
|
||||
}
|
||||
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
client, err := connInfo.NewClient()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
@@ -85,28 +79,25 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
redisConf, err := redisService.LoadConf()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load redis container failed, err: %v", err)
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
redisConf, err := redisService.LoadConf()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "load redis container failed")) {
|
||||
return
|
||||
}
|
||||
|
||||
defer wsConn.Close()
|
||||
commands := "redis-cli"
|
||||
if len(redisConf.Requirepass) != 0 {
|
||||
@@ -138,24 +129,6 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error param of command or containerID"))
|
||||
return
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
@@ -163,6 +136,23 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
|
||||
cmds := []string{"exec", containerID, command}
|
||||
if len(user) != 0 {
|
||||
cmds = []string{"exec", "-u", user, containerID, command}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (b *BaseApi) PageWebsite(c *gin.Context) {
|
||||
// @Tags Website
|
||||
// @Summary List websites
|
||||
// @Description 获取网站列表
|
||||
// @Success 200 {anrry} response.WebsiteDTO
|
||||
// @Success 200 {array} response.WebsiteDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/list [get]
|
||||
func (b *BaseApi) GetWebsites(c *gin.Context) {
|
||||
@@ -51,7 +51,7 @@ func (b *BaseApi) GetWebsites(c *gin.Context) {
|
||||
// @Tags Website
|
||||
// @Summary List website names
|
||||
// @Description 获取网站列表
|
||||
// @Success 200 {anrry} string
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/options [get]
|
||||
func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
|
||||
@@ -207,7 +207,7 @@ func (b *BaseApi) GetWebsiteNginx(c *gin.Context) {
|
||||
// @Description 通过网站 id 查询域名
|
||||
// @Accept json
|
||||
// @Param websiteId path integer true "request"
|
||||
// @Success 200 {anrry} model.WebsiteDomain
|
||||
// @Success 200 {array} model.WebsiteDomain
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/domains/:websiteId [get]
|
||||
func (b *BaseApi) GetWebDomains(c *gin.Context) {
|
||||
@@ -367,7 +367,7 @@ func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) {
|
||||
// @Description 网站创建前检查
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteInstallCheckReq true "request"
|
||||
// @Success 200 {anrry} request.WebsitePreInstallCheck
|
||||
// @Success 200 {array} response.WebsitePreInstallCheck
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/check [post]
|
||||
func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
|
||||
@@ -541,7 +541,7 @@ func (b *BaseApi) UpdateWebsitePHPConfig(c *gin.Context) {
|
||||
|
||||
// @Tags Website PHP
|
||||
// @Summary Update php conf
|
||||
// @Description 更新 php 配置
|
||||
// @Description 更新 php 配置文件
|
||||
// @Accept json
|
||||
// @Param request body request.WebsitePHPFileUpdate true "request"
|
||||
// @Success 200
|
||||
|
||||
@@ -35,7 +35,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
|
||||
Items: accounts,
|
||||
})
|
||||
} else {
|
||||
list, err := websiteSSLService.Search()
|
||||
list, err := websiteSSLService.Search(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -94,7 +94,7 @@ func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) {
|
||||
// @Description 解析网站 ssl
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteDNSReq true "request"
|
||||
// @Success 200 {anrry} response.WebsiteDNSRes
|
||||
// @Success 200 {array} response.WebsiteDNSRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ssl/resolve [post]
|
||||
func (b *BaseApi) GetDNSResolve(c *gin.Context) {
|
||||
|
||||
@@ -12,8 +12,9 @@ type UserLoginInfo struct {
|
||||
}
|
||||
|
||||
type MfaCredential struct {
|
||||
Secret string `json:"secret"`
|
||||
Code string `json:"code"`
|
||||
Secret string `json:"secret"`
|
||||
Code string `json:"code"`
|
||||
Interval string `json:"interval"`
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
|
||||
@@ -8,15 +8,17 @@ type BackupOperate struct {
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
Credential string `json:"credential"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Type string `json:"type"`
|
||||
Bucket string `json:"bucket"`
|
||||
Vars string `json:"vars"`
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Type string `json:"type"`
|
||||
Bucket string `json:"bucket"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars"`
|
||||
}
|
||||
|
||||
type BackupSearch struct {
|
||||
@@ -36,7 +38,7 @@ type CommonBackup struct {
|
||||
DetailName string `json:"detailName"`
|
||||
}
|
||||
type CommonRecover struct {
|
||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO"`
|
||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"`
|
||||
Type string `json:"type" validate:"required,oneof=app mysql redis website"`
|
||||
Name string `json:"name"`
|
||||
DetailName string `json:"detailName"`
|
||||
@@ -60,7 +62,7 @@ type BackupRecords struct {
|
||||
}
|
||||
|
||||
type DownloadRecord struct {
|
||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO"`
|
||||
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"`
|
||||
FileDir string `json:"fileDir" validate:"required"`
|
||||
FileName string `json:"fileName" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package dto
|
||||
|
||||
type SearchWithPage struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
Info string `json:"info"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
|
||||
type PageInfo struct {
|
||||
|
||||
@@ -5,6 +5,8 @@ import "time"
|
||||
type PageContainer struct {
|
||||
PageInfo
|
||||
Name string `json:"name"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
Filters string `json:"filters"`
|
||||
}
|
||||
|
||||
@@ -22,22 +24,29 @@ type ContainerInfo struct {
|
||||
State string `json:"state"`
|
||||
RunTime string `json:"runTime"`
|
||||
|
||||
CPUPercent float64 `json:"cpuPercent"`
|
||||
MemoryPercent float64 `json:"memoryPercent"`
|
||||
Ports []string `json:"ports"`
|
||||
Ports []string `json:"ports"`
|
||||
|
||||
IsFromApp bool `json:"isFromApp"`
|
||||
IsFromCompose bool `json:"isFromCompose"`
|
||||
}
|
||||
|
||||
type ContainerCreate struct {
|
||||
type ResourceLimit struct {
|
||||
CPU int `json:"cpu"`
|
||||
Memory int `json:"memory"`
|
||||
}
|
||||
|
||||
type ContainerOperate struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
ForcePull bool `json:"forcePull"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Network string `json:"network"`
|
||||
PublishAllPorts bool `json:"publishAllPorts"`
|
||||
ExposedPorts []PortHelper `json:"exposedPorts"`
|
||||
Cmd []string `json:"cmd"`
|
||||
NanoCPUs int64 `json:"nanoCPUs"`
|
||||
Memory int64 `json:"memory"`
|
||||
CPUShares int64 `json:"cpuShares"`
|
||||
NanoCPUs float64 `json:"nanoCPUs"`
|
||||
Memory float64 `json:"memory"`
|
||||
AutoRemove bool `json:"autoRemove"`
|
||||
Volumes []VolumeHelper `json:"volumes"`
|
||||
Labels []string `json:"labels"`
|
||||
@@ -45,7 +54,19 @@ type ContainerCreate struct {
|
||||
RestartPolicy string `json:"restartPolicy"`
|
||||
}
|
||||
|
||||
type ContainterStats struct {
|
||||
type ContainerUpgrade struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Image string `json:"image" validate:"required"`
|
||||
ForcePull bool `json:"forcePull"`
|
||||
}
|
||||
|
||||
type ContainerListStats struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
CPUPercent float64 `json:"cpuPercent"`
|
||||
MemoryPercent float64 `json:"memoryPercent"`
|
||||
}
|
||||
|
||||
type ContainerStats struct {
|
||||
CPUPercent float64 `json:"cpuPercent"`
|
||||
Memory float64 `json:"memory"`
|
||||
Cache float64 `json:"cache"`
|
||||
@@ -69,11 +90,6 @@ type PortHelper struct {
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
type ContainerLog struct {
|
||||
ContainerID string `json:"containerID" validate:"required"`
|
||||
Mode string `json:"mode" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause rename remove"`
|
||||
@@ -82,7 +98,7 @@ type ContainerOperation struct {
|
||||
|
||||
type ContainerPrune struct {
|
||||
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
|
||||
WithTagAll bool `josn:"withTagAll"`
|
||||
WithTagAll bool `json:"withTagAll"`
|
||||
}
|
||||
|
||||
type ContainerPruneReport struct {
|
||||
|
||||
@@ -13,6 +13,7 @@ type CronjobCreate struct {
|
||||
Second int `json:"second" validate:"number"`
|
||||
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBName string `json:"dbName"`
|
||||
@@ -34,6 +35,7 @@ type CronjobUpdate struct {
|
||||
Second int `json:"second" validate:"number"`
|
||||
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBName string `json:"dbName"`
|
||||
@@ -76,6 +78,7 @@ type CronjobInfo struct {
|
||||
Second int `json:"second"`
|
||||
|
||||
Script string `json:"script"`
|
||||
ContainerName string `json:"containerName"`
|
||||
Website string `json:"website"`
|
||||
ExclusionRules string `json:"exclusionRules"`
|
||||
DBName string `json:"dbName"`
|
||||
|
||||
+10
-10
@@ -10,35 +10,35 @@ type ImageInfo struct {
|
||||
}
|
||||
|
||||
type ImageLoad struct {
|
||||
Path string `josn:"path" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
}
|
||||
|
||||
type ImageBuild struct {
|
||||
From string `josn:"from" validate:"required"`
|
||||
From string `json:"from" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Dockerfile string `josn:"dockerfile" validate:"required"`
|
||||
Tags []string `josn:"tags"`
|
||||
Dockerfile string `json:"dockerfile" validate:"required"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type ImagePull struct {
|
||||
RepoID uint `josn:"repoID"`
|
||||
ImageName string `josn:"imageName" validate:"required"`
|
||||
RepoID uint `json:"repoID"`
|
||||
ImageName string `json:"imageName" validate:"required"`
|
||||
}
|
||||
|
||||
type ImageTag struct {
|
||||
RepoID uint `josn:"repoID"`
|
||||
RepoID uint `json:"repoID"`
|
||||
SourceID string `json:"sourceID" validate:"required"`
|
||||
TargetName string `josn:"targetName" validate:"required"`
|
||||
TargetName string `json:"targetName" validate:"required"`
|
||||
}
|
||||
|
||||
type ImagePush struct {
|
||||
RepoID uint `josn:"repoID" validate:"required"`
|
||||
RepoID uint `json:"repoID" validate:"required"`
|
||||
TagName string `json:"tagName" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type ImageSave struct {
|
||||
TagName string `json:"tagName" validate:"required"`
|
||||
Path string `josn:"path" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@ type AppInstalledUpdate struct {
|
||||
AppContainerConfig
|
||||
}
|
||||
|
||||
type AppInstalledIgnoreUpgrade struct {
|
||||
DetailID uint `json:"detailID" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required,oneof=cancel ignore"`
|
||||
}
|
||||
|
||||
type PortUpdate struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package request
|
||||
|
||||
type ProcessReq struct {
|
||||
PID int32 `json:"PID" validate:"required"`
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
type WebsiteSearch struct {
|
||||
dto.PageInfo
|
||||
Name string `json:"name"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
WebsiteGroupID uint `json:"websiteGroupId"`
|
||||
}
|
||||
|
||||
@@ -113,15 +115,18 @@ type WebsiteDomainDelete struct {
|
||||
}
|
||||
|
||||
type WebsiteHTTPSOp struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Enable bool `json:"enable" validate:"required"`
|
||||
WebsiteSSLID uint `json:"websiteSSLId"`
|
||||
Type string `json:"type" validate:"oneof=existed auto manual"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Certificate string `json:"certificate"`
|
||||
HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
|
||||
SSLProtocol []string `json:"SSLProtocol"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Enable bool `json:"enable" validate:"required"`
|
||||
WebsiteSSLID uint `json:"websiteSSLId"`
|
||||
Type string `json:"type" validate:"oneof=existed auto manual"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKeyPath string `json:"privateKeyPath"`
|
||||
CertificatePath string `json:"certificatePath"`
|
||||
ImportType string `json:"importType"`
|
||||
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
|
||||
SSLProtocol []string `json:"SSLProtocol"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
}
|
||||
|
||||
type WebsiteNginxUpdate struct {
|
||||
|
||||
@@ -4,6 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
type WebsiteSSLSearch struct {
|
||||
dto.PageInfo
|
||||
AcmeAccountID string `json:"acmeAccountID"`
|
||||
}
|
||||
|
||||
type WebsiteSSLCreate struct {
|
||||
|
||||
@@ -48,6 +48,13 @@ type AppDetailDTO struct {
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
type IgnoredApp struct {
|
||||
Icon string `json:"icon"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
DetailID uint `json:"detailID"`
|
||||
}
|
||||
|
||||
type AppInstalledDTO struct {
|
||||
model.AppInstall
|
||||
Total int `json:"total"`
|
||||
|
||||
@@ -5,6 +5,7 @@ import "time"
|
||||
type SettingInfo struct {
|
||||
UserName string `json:"userName"`
|
||||
Email string `json:"email"`
|
||||
SystemIP string `json:"systemIP"`
|
||||
SystemVersion string `json:"systemVersion"`
|
||||
|
||||
SessionTimeout string `json:"sessionTimeout"`
|
||||
@@ -28,6 +29,7 @@ type SettingInfo struct {
|
||||
ComplexityVerification string `json:"complexityVerification"`
|
||||
MFAStatus string `json:"mfaStatus"`
|
||||
MFASecret string `json:"mfaSecret"`
|
||||
MFAInterval string `json:"mfaInterval"`
|
||||
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
@@ -74,7 +76,7 @@ type PortUpdate struct {
|
||||
}
|
||||
|
||||
type SnapshotCreate struct {
|
||||
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO"`
|
||||
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
}
|
||||
type SnapshotRecover struct {
|
||||
|
||||
@@ -12,4 +12,5 @@ type AppDetail struct {
|
||||
DownloadUrl string `json:"downloadUrl" gorm:"type:varchar;"`
|
||||
DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"`
|
||||
Update bool `json:"update"`
|
||||
IgnoreUpgrade bool `json:"ignoreUpgrade"`
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ type BackupAccount struct {
|
||||
Bucket string `gorm:"type:varchar(256)" json:"bucket"`
|
||||
AccessKey string `gorm:"type:varchar(256)" json:"accessKey"`
|
||||
Credential string `gorm:"type:varchar(256)" json:"credential"`
|
||||
BackupPath string `gorm:"type:varchar(256)" json:"backupPath"`
|
||||
Vars string `gorm:"type:longText" json:"vars"`
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ type Cronjob struct {
|
||||
Minute uint64 `gorm:"type:decimal" json:"minute"`
|
||||
Second uint64 `gorm:"type:decimal" json:"second"`
|
||||
|
||||
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
|
||||
Script string `gorm:"longtext" json:"script"`
|
||||
Website string `gorm:"type:varchar(64)" json:"website"`
|
||||
DBName string `gorm:"type:varchar(64)" json:"dbName"`
|
||||
|
||||
@@ -13,6 +13,7 @@ type AppDetailRepo struct {
|
||||
type IAppDetailRepo interface {
|
||||
WithVersion(version string) DBOption
|
||||
WithAppId(id uint) DBOption
|
||||
WithIgnored() DBOption
|
||||
GetFirst(opts ...DBOption) (model.AppDetail, error)
|
||||
Update(ctx context.Context, detail model.AppDetail) error
|
||||
BatchCreate(ctx context.Context, details []model.AppDetail) error
|
||||
@@ -31,12 +32,19 @@ func (a AppDetailRepo) WithVersion(version string) DBOption {
|
||||
return g.Where("version = ?", version)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) WithAppId(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("app_id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) WithIgnored() DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("ignore_upgrade = 1")
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) {
|
||||
var detail model.AppDetail
|
||||
err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error
|
||||
|
||||
@@ -2,6 +2,7 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -16,6 +17,7 @@ type ICommonRepo interface {
|
||||
WithByName(name string) DBOption
|
||||
WithByType(tp string) DBOption
|
||||
WithOrderBy(orderStr string) DBOption
|
||||
WithOrderRuleBy(orderBy, order string) DBOption
|
||||
WithByGroupID(groupID uint) DBOption
|
||||
WithLikeName(name string) DBOption
|
||||
WithIdsIn(ids []uint) DBOption
|
||||
@@ -93,6 +95,21 @@ func (c *CommonRepo) WithOrderBy(orderStr string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) DBOption {
|
||||
switch order {
|
||||
case constant.OrderDesc:
|
||||
order = "desc"
|
||||
case constant.OrderAsc:
|
||||
order = "asc"
|
||||
default:
|
||||
orderBy = "created_at"
|
||||
order = "desc"
|
||||
}
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Order(fmt.Sprintf("%s %s", orderBy, order))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithIdsIn(ids []uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id in (?)", ids)
|
||||
|
||||
@@ -15,6 +15,7 @@ type IComposeTemplateRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
|
||||
GetRecord(opts ...DBOption) (model.Compose, error)
|
||||
CreateRecord(compose *model.Compose) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
ListRecord() ([]model.Compose, error)
|
||||
@@ -72,6 +73,16 @@ func (u *ComposeTemplateRepo) Delete(opts ...DBOption) error {
|
||||
return db.Delete(&model.ComposeTemplate{}).Error
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) GetRecord(opts ...DBOption) (model.Compose, error) {
|
||||
var compose model.Compose
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&compose).Error
|
||||
return compose, err
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) {
|
||||
var composes []model.Compose
|
||||
if err := global.DB.Find(&composes).Error; err != nil {
|
||||
|
||||
@@ -83,7 +83,7 @@ func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cro
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error
|
||||
return count, cronjobs, err
|
||||
}
|
||||
|
||||
|
||||
+54
-16
@@ -24,6 +24,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AppService struct {
|
||||
@@ -39,6 +40,7 @@ type IAppService interface {
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
|
||||
SyncAppListFromLocal()
|
||||
GetIgnoredApp() ([]response.IgnoredApp, error)
|
||||
}
|
||||
|
||||
func NewIAppService() IAppService {
|
||||
@@ -82,12 +84,16 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
var appDTOs []*response.AppDTO
|
||||
for _, a := range apps {
|
||||
for _, ap := range apps {
|
||||
ap.ReadMe = ""
|
||||
ap.Website = ""
|
||||
ap.Document = ""
|
||||
ap.Github = ""
|
||||
appDTO := &response.AppDTO{
|
||||
App: a,
|
||||
App: ap,
|
||||
}
|
||||
appDTOs = append(appDTOs, appDTO)
|
||||
appTags, err := appTagRepo.GetByAppId(a.ID)
|
||||
appTags, err := appTagRepo.GetByAppId(ap.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -100,7 +106,7 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
|
||||
continue
|
||||
}
|
||||
appDTO.Tags = tags
|
||||
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(a.ID))
|
||||
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID))
|
||||
appDTO.Installed = len(installs) > 0
|
||||
}
|
||||
res.Items = appDTOs
|
||||
@@ -163,7 +169,7 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response.
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
versionPath := path.Join(constant.AppResourceDir, app.Resource, app.Key, detail.Version)
|
||||
if !fileOp.Stat(versionPath) {
|
||||
if !fileOp.Stat(versionPath) || detail.Update {
|
||||
if err = downloadApp(app, detail, nil); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
@@ -232,6 +238,27 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) {
|
||||
var res []response.IgnoredApp
|
||||
details, _ := appDetailRepo.GetBy(appDetailRepo.WithIgnored())
|
||||
if len(details) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
for _, detail := range details {
|
||||
app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, response.IgnoredApp{
|
||||
Name: app.Name,
|
||||
Version: detail.Version,
|
||||
DetailID: detail.ID,
|
||||
Icon: app.Icon,
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
|
||||
if err = docker.CreateDefaultDockerNetwork(); err != nil {
|
||||
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
|
||||
@@ -247,14 +274,23 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
||||
appDetail model.AppDetail
|
||||
app model.App
|
||||
)
|
||||
httpPort, err = checkPort("PANEL_APP_PORT_HTTP", req.Params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
httpsPort, err = checkPort("PANEL_APP_PORT_HTTPS", req.Params)
|
||||
if err != nil {
|
||||
return
|
||||
for key := range req.Params {
|
||||
if !strings.Contains(key, "PANEL_APP_PORT") {
|
||||
continue
|
||||
}
|
||||
var port int
|
||||
if port, err = checkPort(key, req.Params); err == nil {
|
||||
if key == "PANEL_APP_PORT_HTTP" {
|
||||
httpPort = port
|
||||
}
|
||||
if key == "PANEL_APP_PORT_HTTPS" {
|
||||
httpsPort = port
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
|
||||
if err != nil {
|
||||
return
|
||||
@@ -470,9 +506,13 @@ func (a AppService) SyncAppListFromLocal() {
|
||||
oldDetail, exist := appDetails[version]
|
||||
if exist {
|
||||
newDetail.ID = oldDetail.ID
|
||||
delete(appDetails, version)
|
||||
}
|
||||
app.Details[i] = newDetail
|
||||
}
|
||||
for _, v := range appDetails {
|
||||
app.Details = append(app.Details, v)
|
||||
}
|
||||
}
|
||||
app.TagsKey = append(app.TagsKey, constant.AppResourceLocal)
|
||||
apps[app.Key] = app
|
||||
@@ -730,10 +770,8 @@ func (a AppService) SyncAppListFromRemote() error {
|
||||
detail.Params = string(paramByte)
|
||||
detail.DownloadUrl = v.DownloadUrl
|
||||
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
|
||||
if v.LastModified > detail.LastModified {
|
||||
detail.Update = true
|
||||
detail.LastModified = v.LastModified
|
||||
}
|
||||
detail.Update = true
|
||||
detail.LastModified = v.LastModified
|
||||
detailsMap[version] = detail
|
||||
}
|
||||
var newDetails []model.AppDetail
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
@@ -45,6 +46,7 @@ type IAppInstallService interface {
|
||||
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
|
||||
Operate(req request.AppInstalledOperate) error
|
||||
Update(req request.AppInstalledUpdate) error
|
||||
IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error
|
||||
SyncAll(systemInit bool) error
|
||||
GetServices(key string) ([]response.AppService, error)
|
||||
GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
|
||||
@@ -59,7 +61,12 @@ func NewIAppInstalledService() IAppInstallService {
|
||||
}
|
||||
|
||||
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
|
||||
var opts []repo.DBOption
|
||||
var (
|
||||
opts []repo.DBOption
|
||||
total int64
|
||||
installs []model.AppInstall
|
||||
err error
|
||||
)
|
||||
|
||||
if req.Name != "" {
|
||||
opts = append(opts, commonRepo.WithLikeName(req.Name))
|
||||
@@ -85,15 +92,25 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
|
||||
opts = append(opts, appInstallRepo.WithAppIdsIn(appIds))
|
||||
}
|
||||
|
||||
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
if req.Update {
|
||||
installs, err = appInstallRepo.ListBy(opts...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
} else {
|
||||
total, installs, err = appInstallRepo.Page(req.Page, req.PageSize, opts...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
installDTOs, err := handleInstalled(installs, req.Update)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if req.Update {
|
||||
total = int64(len(installDTOs))
|
||||
}
|
||||
|
||||
return total, installDTOs, nil
|
||||
}
|
||||
@@ -352,6 +369,15 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error {
|
||||
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.DetailID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appDetail.IgnoreUpgrade = req.Operate == "ignore"
|
||||
return appDetailRepo.Update(context.Background(), appDetail)
|
||||
}
|
||||
|
||||
func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
allList, err := appInstallRepo.ListBy()
|
||||
if err != nil {
|
||||
@@ -366,8 +392,10 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := syncById(i.ID); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
if !systemInit {
|
||||
if err := syncById(i.ID); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -412,6 +440,9 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
|
||||
return versions, err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if detail.IgnoreUpgrade {
|
||||
continue
|
||||
}
|
||||
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
|
||||
continue
|
||||
}
|
||||
@@ -577,6 +608,9 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
config := getAppCommonConfig(envs)
|
||||
config.DockerCompose = install.DockerCompose
|
||||
res.Params = params
|
||||
if config.ContainerName == "" {
|
||||
config.ContainerName = install.ContainerName
|
||||
}
|
||||
res.AppContainerConfig = config
|
||||
return &res, nil
|
||||
}
|
||||
@@ -589,12 +623,10 @@ func syncById(installId uint) error {
|
||||
if appInstall.Status == constant.Installing {
|
||||
return nil
|
||||
}
|
||||
|
||||
containerNames, err := getContainerNames(appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -620,16 +652,16 @@ func syncById(installId uint) error {
|
||||
errorContainers = append(errorContainers, n.Names[0])
|
||||
}
|
||||
}
|
||||
for _, old := range containerNames {
|
||||
for _, name := range containerNames {
|
||||
exist := false
|
||||
for _, new := range containers {
|
||||
if common.ExistWithStrArray(old, new.Names) {
|
||||
for _, container := range containers {
|
||||
if common.ExistWithStrArray(name, container.Names) {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
notFoundContainers = append(notFoundContainers, old)
|
||||
notFoundContainers = append(notFoundContainers, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,10 +674,10 @@ func syncById(installId uint) error {
|
||||
|
||||
if containerCount == 0 {
|
||||
appInstall.Status = constant.Error
|
||||
appInstall.Message = "container is not found"
|
||||
appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if errCount == 0 && existedCount == 0 {
|
||||
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
|
||||
appInstall.Status = constant.Running
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
@@ -660,22 +692,14 @@ func syncById(installId uint) error {
|
||||
appInstall.Status = constant.UnHealthy
|
||||
}
|
||||
|
||||
var errMsg strings.Builder
|
||||
var errMsg string
|
||||
if errCount > 0 {
|
||||
errMsg.Write([]byte(string(rune(errCount)) + " error containers:"))
|
||||
for _, e := range errorContainers {
|
||||
errMsg.Write([]byte(e))
|
||||
}
|
||||
errMsg.Write([]byte("\n"))
|
||||
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
|
||||
}
|
||||
if notFoundCount > 0 {
|
||||
errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:"))
|
||||
for _, e := range notFoundContainers {
|
||||
errMsg.Write([]byte(e))
|
||||
}
|
||||
errMsg.Write([]byte("\n"))
|
||||
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
|
||||
}
|
||||
appInstall.Message = errMsg.String()
|
||||
appInstall.Message = errMsg
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"math"
|
||||
@@ -49,7 +48,20 @@ var (
|
||||
func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||
port, ok := params[key]
|
||||
if ok {
|
||||
portN := int(math.Ceil(port.(float64)))
|
||||
portN := 0
|
||||
var err error
|
||||
switch p := port.(type) {
|
||||
case string:
|
||||
portN, err = strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return portN, nil
|
||||
}
|
||||
case float64:
|
||||
portN = int(math.Ceil(p))
|
||||
case int:
|
||||
portN = p
|
||||
}
|
||||
|
||||
oldInstalled, _ := appInstallRepo.ListBy(appInstallRepo.WithPort(portN))
|
||||
if len(oldInstalled) > 0 {
|
||||
var apps []string
|
||||
@@ -359,7 +371,6 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
containerMap := make(map[string]struct{})
|
||||
containerMap[install.ContainerName] = struct{}{}
|
||||
for _, service := range project.AllServices() {
|
||||
if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" {
|
||||
continue
|
||||
@@ -370,6 +381,9 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
|
||||
for k := range containerMap {
|
||||
containerNames = append(containerNames, k)
|
||||
}
|
||||
if len(containerNames) == 0 {
|
||||
containerNames = append(containerNames, install.ContainerName)
|
||||
}
|
||||
return containerNames, nil
|
||||
}
|
||||
|
||||
@@ -522,27 +536,6 @@ func upAppPre(app model.App, appInstall *model.AppInstall) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServiceFromInstall(appInstall *model.AppInstall) (service *composeV2.ComposeService, err error) {
|
||||
var (
|
||||
project *types.Project
|
||||
envStr string
|
||||
)
|
||||
envStr, err = coverEnvJsonToStr(appInstall.Env)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
project, err = composeV2.GetComposeProject(appInstall.Name, appInstall.GetPath(), []byte(appInstall.DockerCompose), []byte(envStr), true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
service, err = composeV2.NewComposeService()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
service.SetProject(project)
|
||||
return
|
||||
}
|
||||
|
||||
func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
client, err := composeV2.NewDockerClient()
|
||||
if err != nil {
|
||||
@@ -571,13 +564,30 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
func upApp(appInstall *model.AppInstall) {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
if err == nil {
|
||||
var composeService *composeV2.ComposeService
|
||||
composeService, err = getServiceFromInstall(appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = composeService.ComposeUp()
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
return
|
||||
@@ -587,7 +597,6 @@ func upApp(appInstall *model.AppInstall) {
|
||||
}
|
||||
if err := upProject(appInstall); err != nil {
|
||||
appInstall.Status = constant.Error
|
||||
appInstall.Message = err.Error()
|
||||
} else {
|
||||
appInstall.Status = constant.Running
|
||||
}
|
||||
@@ -751,7 +760,7 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) {
|
||||
var res []response.AppInstalledDTO
|
||||
for _, installed := range appInstallList {
|
||||
if updated && installed.App.Type == "php" {
|
||||
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
|
||||
continue
|
||||
}
|
||||
installDTO := response.AppInstalledDTO{
|
||||
@@ -768,12 +777,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
|
||||
}
|
||||
var versions []string
|
||||
for _, detail := range details {
|
||||
if detail.IgnoreUpgrade {
|
||||
continue
|
||||
}
|
||||
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
|
||||
continue
|
||||
}
|
||||
versions = append(versions, detail.Version)
|
||||
}
|
||||
versions = common.GetSortedVersions(versions)
|
||||
if len(versions) == 0 {
|
||||
continue
|
||||
}
|
||||
lastVersion := versions[0]
|
||||
if common.IsCrossVersion(installed.Version, lastVersion) {
|
||||
installDTO.CanUpdate = app.CrossVersionUpdate
|
||||
|
||||
@@ -76,7 +76,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
success := mfa.ValidCode(info.Code, mfaSecret.Value)
|
||||
mfaInterval, err := settingRepo.Get(settingRepo.WithByKey("MFAInterval"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value)
|
||||
if !success {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -24,6 +28,7 @@ type BackupService struct{}
|
||||
type IBackupService interface {
|
||||
List() ([]dto.BackupInfo, error)
|
||||
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
|
||||
LoadOneDriveInfo() (string, error)
|
||||
DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
Create(backupDto dto.BackupOperate) error
|
||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||
@@ -62,6 +67,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
|
||||
dtobas = append(dtobas, u.loadByType("MINIO", ops))
|
||||
dtobas = append(dtobas, u.loadByType("COS", ops))
|
||||
dtobas = append(dtobas, u.loadByType("KODO", ops))
|
||||
dtobas = append(dtobas, u.loadByType("OneDrive", ops))
|
||||
return dtobas, err
|
||||
}
|
||||
|
||||
@@ -84,6 +90,18 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
|
||||
return total, dtobas, err
|
||||
}
|
||||
|
||||
func (u *BackupService) LoadOneDriveInfo() (string, error) {
|
||||
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(idItem), err
|
||||
}
|
||||
|
||||
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
|
||||
if info.Source == "LOCAL" {
|
||||
return info.FileDir + "/" + info.FileName, nil
|
||||
@@ -96,7 +114,6 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return "", err
|
||||
}
|
||||
varMap["type"] = backup.Type
|
||||
varMap["bucket"] = backup.Bucket
|
||||
switch backup.Type {
|
||||
case constant.Sftp:
|
||||
@@ -105,8 +122,10 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||
varMap["accessKey"] = backup.AccessKey
|
||||
varMap["secretKey"] = backup.Credential
|
||||
case constant.OneDrive:
|
||||
varMap["accessToken"] = backup.Credential
|
||||
}
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
|
||||
}
|
||||
@@ -117,6 +136,11 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
}
|
||||
}
|
||||
srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName)
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
srcPath = itemPath + srcPath
|
||||
}
|
||||
if exist, _ := backClient.Exist(srcPath); exist {
|
||||
isOK, err := backClient.Download(srcPath, targetPath)
|
||||
if !isOK {
|
||||
@@ -134,6 +158,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error {
|
||||
if err := copier.Copy(&backup, &backupDto); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
|
||||
if backupDto.Type == constant.OneDrive {
|
||||
if err := u.loadAccessToken(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := backupRepo.Create(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -145,7 +175,6 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
|
||||
if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
varMap["type"] = backupDto.Type
|
||||
switch backupDto.Type {
|
||||
case constant.Sftp:
|
||||
varMap["username"] = backupDto.AccessKey
|
||||
@@ -154,7 +183,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
|
||||
varMap["accessKey"] = backupDto.AccessKey
|
||||
varMap["secretKey"] = backupDto.Credential
|
||||
}
|
||||
client, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -214,7 +243,17 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["bucket"] = req.Bucket
|
||||
upMap["credential"] = req.Credential
|
||||
upMap["backup_path"] = req.BackupPath
|
||||
upMap["vars"] = req.Vars
|
||||
backup.Vars = req.Vars
|
||||
|
||||
if req.Type == constant.OneDrive {
|
||||
if err := u.loadAccessToken(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
upMap["credential"] = backup.Credential
|
||||
upMap["vars"] = backup.Vars
|
||||
}
|
||||
if err := backupRepo.Update(req.ID, upMap); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -251,7 +290,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
varMap["type"] = backup.Type
|
||||
if backup.Type == "LOCAL" {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
@@ -263,9 +301,11 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
|
||||
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||
varMap["accessKey"] = backup.AccessKey
|
||||
varMap["secretKey"] = backup.Credential
|
||||
case constant.OneDrive:
|
||||
varMap["accessToken"] = backup.Credential
|
||||
}
|
||||
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -286,6 +326,53 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
|
||||
return dto.BackupInfo{Type: accountType}
|
||||
}
|
||||
|
||||
func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
|
||||
varMap := make(map[string]interface{})
|
||||
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()))
|
||||
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)
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
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 loadLocalDir() (string, error) {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -80,11 +81,11 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
|
||||
temPathWithName := tmpPath + "/" + website.Alias
|
||||
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
|
||||
return errors.New("the wrong recovery package does not have .conf or .web.tar.gz files")
|
||||
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
|
||||
}
|
||||
if website.Type == constant.Deployment {
|
||||
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
|
||||
return errors.New("the wrong recovery package does not have .app.tar.gz files")
|
||||
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
|
||||
}
|
||||
}
|
||||
var oldWebsite model.Website
|
||||
@@ -95,8 +96,9 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil {
|
||||
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
|
||||
}
|
||||
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type || oldWebsite.ID != website.ID {
|
||||
return errors.New("the current backup file does not match the application")
|
||||
|
||||
if err := checkValidOfWebsite(&oldWebsite, website); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isOk := false
|
||||
@@ -155,6 +157,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
||||
oldWebsite.ID = website.ID
|
||||
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
|
||||
global.LOG.Errorf("handle save website data failed, err: %v", err)
|
||||
return err
|
||||
@@ -212,3 +215,29 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
|
||||
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil)
|
||||
}
|
||||
if oldWebsite.AppInstallID != 0 {
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
if err != nil {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, "app", nil)
|
||||
}
|
||||
if app.App.Type != "website" {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("appType: %s", app.App.Type), nil)
|
||||
}
|
||||
}
|
||||
if oldWebsite.RuntimeID != 0 {
|
||||
if _, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)); err != nil {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, "runtime", nil)
|
||||
}
|
||||
}
|
||||
if oldWebsite.WebsiteSSLID != 0 {
|
||||
if _, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID)); err != nil {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, "ssl", nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -26,24 +27,34 @@ import (
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/gorilla/websocket"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
type ContainerService struct{}
|
||||
|
||||
type IContainerService interface {
|
||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||
List() ([]string, error)
|
||||
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListNetwork() ([]dto.Options, error)
|
||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListVolume() ([]dto.Options, error)
|
||||
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
ComposeOperation(req dto.ComposeOperation) error
|
||||
ContainerCreate(req dto.ContainerCreate) error
|
||||
ContainerCreate(req dto.ContainerOperate) error
|
||||
ContainerUpdate(req dto.ContainerOperate) error
|
||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
||||
LoadResouceLimit() (*dto.ResourceLimit, error)
|
||||
ContainerLogClean(req dto.OperationWithName) error
|
||||
ContainerOperation(req dto.ContainerOperation) error
|
||||
ContainerLogs(param dto.ContainerLog) (string, error)
|
||||
ContainerStats(id string) (*dto.ContainterStats, error)
|
||||
ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error
|
||||
ContainerStats(id string) (*dto.ContainerStats, error)
|
||||
Inspect(req dto.InspectReq) (string, error)
|
||||
DeleteNetwork(req dto.BatchDelete) error
|
||||
CreateNetwork(req dto.NetworkCreate) error
|
||||
@@ -60,9 +71,8 @@ func NewIContainerService() IContainerService {
|
||||
|
||||
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
||||
var (
|
||||
records []types.Container
|
||||
list []types.Container
|
||||
backDatas []dto.ContainerInfo
|
||||
records []types.Container
|
||||
list []types.Container
|
||||
)
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
@@ -88,9 +98,30 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Created > list[j].Created
|
||||
})
|
||||
switch req.OrderBy {
|
||||
case "name":
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if req.Order == constant.OrderAsc {
|
||||
return list[i].Names[0][1:] < list[j].Names[0][1:]
|
||||
}
|
||||
return list[i].Names[0][1:] > list[j].Names[0][1:]
|
||||
})
|
||||
case "state":
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if req.Order == constant.OrderAsc {
|
||||
return list[i].State < list[j].State
|
||||
}
|
||||
return list[i].State > list[j].State
|
||||
})
|
||||
default:
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if req.Order == constant.OrderAsc {
|
||||
return list[i].Created < list[j].Created
|
||||
}
|
||||
return list[i].Created > list[j].Created
|
||||
})
|
||||
}
|
||||
|
||||
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]types.Container, 0)
|
||||
@@ -101,49 +132,87 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
records = list[start:end]
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(records))
|
||||
for _, container := range records {
|
||||
go func(item types.Container) {
|
||||
IsFromCompose := false
|
||||
if _, ok := item.Labels[composeProjectLabel]; ok {
|
||||
IsFromCompose = true
|
||||
}
|
||||
IsFromApp := false
|
||||
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
|
||||
IsFromApp = true
|
||||
}
|
||||
backDatas := make([]dto.ContainerInfo, len(records))
|
||||
for i := 0; i < len(records); i++ {
|
||||
item := records[i]
|
||||
IsFromCompose := false
|
||||
if _, ok := item.Labels[composeProjectLabel]; ok {
|
||||
IsFromCompose = true
|
||||
}
|
||||
IsFromApp := false
|
||||
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
|
||||
IsFromApp = true
|
||||
}
|
||||
|
||||
var ports []string
|
||||
for _, port := range item.Ports {
|
||||
if port.IP == "::" || port.PublicPort == 0 {
|
||||
continue
|
||||
}
|
||||
ports = append(ports, fmt.Sprintf("%v:%v/%s", port.PublicPort, port.PrivatePort, port.Type))
|
||||
var ports []string
|
||||
for _, port := range item.Ports {
|
||||
itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type)
|
||||
if port.PublicPort != 0 {
|
||||
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)
|
||||
}
|
||||
cpu, mem := loadCpuAndMem(client, item.ID)
|
||||
backDatas = append(backDatas, dto.ContainerInfo{
|
||||
ContainerID: item.ID,
|
||||
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
Name: item.Names[0][1:],
|
||||
ImageId: strings.Split(item.ImageID, ":")[1],
|
||||
ImageName: item.Image,
|
||||
State: item.State,
|
||||
RunTime: item.Status,
|
||||
CPUPercent: cpu,
|
||||
MemoryPercent: mem,
|
||||
Ports: ports,
|
||||
IsFromApp: IsFromApp,
|
||||
IsFromCompose: IsFromCompose,
|
||||
})
|
||||
wg.Done()
|
||||
}(container)
|
||||
ports = append(ports, itemPortStr)
|
||||
}
|
||||
backDatas[i] = dto.ContainerInfo{
|
||||
ContainerID: item.ID,
|
||||
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
Name: item.Names[0][1:],
|
||||
ImageId: strings.Split(item.ImageID, ":")[1],
|
||||
ImageName: item.Image,
|
||||
State: item.State,
|
||||
RunTime: item.Status,
|
||||
Ports: ports,
|
||||
IsFromApp: IsFromApp,
|
||||
IsFromCompose: IsFromCompose,
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return int64(total), backDatas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) List() ([]string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []string
|
||||
for _, container := range containers {
|
||||
for _, name := range container.Names {
|
||||
if len(name) != 0 {
|
||||
datas = append(datas, strings.TrimLeft(name, "/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []dto.ContainerListStats
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(list))
|
||||
for i := 0; i < len(list); i++ {
|
||||
go func(item types.Container) {
|
||||
cpu, mem := loadCpuAndMem(client, item.ID)
|
||||
datas = append(datas, dto.ContainerListStats{CPUPercent: cpu, MemoryPercent: mem, ContainerID: item.ID})
|
||||
wg.Done()
|
||||
}(list[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
@@ -213,63 +282,52 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneRepo
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
||||
portMap, err := checkPortStats(req.ExposedPorts)
|
||||
func (u *ContainerService) LoadResouceLimit() (*dto.ResourceLimit, error) {
|
||||
cpuCounts, err := cpu.Counts(true)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("load cpu limit failed, err: %v", err)
|
||||
}
|
||||
memoryInfo, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load memory limit failed, err: %v", err)
|
||||
}
|
||||
|
||||
data := dto.ResourceLimit{
|
||||
CPU: cpuCounts,
|
||||
Memory: int(memoryInfo.Total),
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
newContainer, _ := client.ContainerInspect(ctx, req.Name)
|
||||
if newContainer.ContainerJSONBase != nil {
|
||||
return buserr.New(constant.ErrContainerName)
|
||||
}
|
||||
|
||||
exposeds := make(nat.PortSet)
|
||||
for port := range portMap {
|
||||
exposeds[port] = struct{}{}
|
||||
}
|
||||
config := &container.Config{
|
||||
Image: req.Image,
|
||||
Cmd: req.Cmd,
|
||||
Env: req.Env,
|
||||
Labels: stringsToMap(req.Labels),
|
||||
Tty: true,
|
||||
OpenStdin: true,
|
||||
ExposedPorts: exposeds,
|
||||
}
|
||||
hostConf := &container.HostConfig{
|
||||
AutoRemove: req.AutoRemove,
|
||||
PublishAllPorts: req.PublishAllPorts,
|
||||
RestartPolicy: container.RestartPolicy{Name: req.RestartPolicy},
|
||||
}
|
||||
if req.RestartPolicy == "on-failure" {
|
||||
hostConf.RestartPolicy.MaximumRetryCount = 5
|
||||
}
|
||||
if req.NanoCPUs != 0 {
|
||||
hostConf.NanoCPUs = req.NanoCPUs * 1000000000
|
||||
}
|
||||
if req.Memory != 0 {
|
||||
hostConf.Memory = req.Memory
|
||||
}
|
||||
if len(req.ExposedPorts) != 0 {
|
||||
hostConf.PortBindings = portMap
|
||||
}
|
||||
if len(req.Volumes) != 0 {
|
||||
config.Volumes = make(map[string]struct{})
|
||||
for _, volume := range req.Volumes {
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
|
||||
}
|
||||
var config container.Config
|
||||
var hostConf container.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if err := loadConfigInfo(req, &config, &hostConf, &networkConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
|
||||
|
||||
ctx := context.Background()
|
||||
if !checkImageExist(client, req.Image) {
|
||||
if !checkImageExist(client, req.Image) || req.ForcePull {
|
||||
if err := pullImages(ctx, client, req.Image); err != nil {
|
||||
return err
|
||||
if !req.ForcePull {
|
||||
return err
|
||||
}
|
||||
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
|
||||
}
|
||||
}
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
|
||||
container, err := client.ContainerCreate(ctx, &config, &hostConf, &networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
return err
|
||||
@@ -282,6 +340,159 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := context.Background()
|
||||
oldContainer, err := client.ContainerInspect(ctx, req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data dto.ContainerOperate
|
||||
data.ContainerID = oldContainer.ID
|
||||
data.Name = strings.ReplaceAll(oldContainer.Name, "/", "")
|
||||
data.Image = oldContainer.Config.Image
|
||||
if oldContainer.NetworkSettings != nil {
|
||||
for network := range oldContainer.NetworkSettings.Networks {
|
||||
data.Network = network
|
||||
break
|
||||
}
|
||||
}
|
||||
data.Cmd = oldContainer.Config.Cmd
|
||||
data.Env = oldContainer.Config.Env
|
||||
data.CPUShares = oldContainer.HostConfig.CPUShares
|
||||
for key, val := range oldContainer.Config.Labels {
|
||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
for key, val := range oldContainer.HostConfig.PortBindings {
|
||||
var itemPort dto.PortHelper
|
||||
if !strings.Contains(string(key), "/") {
|
||||
continue
|
||||
}
|
||||
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
|
||||
itemPort.Protocol = strings.Split(string(key), "/")[1]
|
||||
for _, binds := range val {
|
||||
itemPort.HostIP = binds.HostIP
|
||||
itemPort.HostPort = binds.HostPort
|
||||
data.ExposedPorts = append(data.ExposedPorts, itemPort)
|
||||
}
|
||||
}
|
||||
data.AutoRemove = oldContainer.HostConfig.AutoRemove
|
||||
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
|
||||
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
|
||||
if oldContainer.HostConfig.NanoCPUs != 0 {
|
||||
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
|
||||
}
|
||||
if oldContainer.HostConfig.Memory != 0 {
|
||||
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
|
||||
}
|
||||
for _, bind := range oldContainer.HostConfig.Binds {
|
||||
parts := strings.Split(bind, ":")
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
data.Volumes = append(data.Volumes, dto.VolumeHelper{SourceDir: parts[0], ContainerDir: parts[1], Mode: parts[2]})
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
newContainer, _ := client.ContainerInspect(ctx, req.Name)
|
||||
if newContainer.ContainerJSONBase != nil && newContainer.ID != req.ContainerID {
|
||||
return buserr.New(constant.ErrContainerName)
|
||||
}
|
||||
|
||||
oldContainer, err := client.ContainerInspect(ctx, req.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !checkImageExist(client, req.Image) || req.ForcePull {
|
||||
if err := pullImages(ctx, client, req.Image); err != nil {
|
||||
if !req.ForcePull {
|
||||
return err
|
||||
}
|
||||
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := oldContainer.Config
|
||||
hostConf := oldContainer.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recreate contianer failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
|
||||
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
|
||||
return fmt.Errorf("update successful but start failed, err: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
oldContainer, err := client.ContainerInspect(ctx, req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !checkImageExist(client, req.Image) || req.ForcePull {
|
||||
if err := pullImages(ctx, client, req.Image); err != nil {
|
||||
if !req.ForcePull {
|
||||
return err
|
||||
}
|
||||
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
|
||||
}
|
||||
}
|
||||
config := oldContainer.Config
|
||||
config.Image = req.Image
|
||||
hostConf := oldContainer.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if oldContainer.NetworkSettings != nil {
|
||||
for networkKey := range oldContainer.NetworkSettings.Networks {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recreate contianer failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
|
||||
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
|
||||
return fmt.Errorf("update successful but start failed, err: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
@@ -304,6 +515,10 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
|
||||
case constant.ContainerOpUnpause:
|
||||
err = client.ContainerUnpause(ctx, req.Name)
|
||||
case constant.ContainerOpRename:
|
||||
newContainer, _ := client.ContainerInspect(ctx, req.NewName)
|
||||
if newContainer.ContainerJSONBase != nil {
|
||||
return buserr.New(constant.ErrContainerName)
|
||||
}
|
||||
err = client.ContainerRename(ctx, req.Name, req.NewName)
|
||||
case constant.ContainerOpRemove:
|
||||
err = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
@@ -329,22 +544,57 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
|
||||
return err
|
||||
}
|
||||
_, _ = file.Seek(0, 0)
|
||||
|
||||
files, _ := filepath.Glob(fmt.Sprintf("%s.*", container.LogPath))
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
|
||||
cmd := exec.Command("docker", "logs", req.ContainerID)
|
||||
if req.Mode != "all" {
|
||||
cmd = exec.Command("docker", "logs", req.ContainerID, "--since", req.Mode)
|
||||
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error {
|
||||
if cmd.CheckIllegal(container, since, tail) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
command := fmt.Sprintf("docker logs %s", container)
|
||||
if tail != "0" {
|
||||
command += " -n " + tail
|
||||
}
|
||||
if since != "all" {
|
||||
command += " --since " + since
|
||||
}
|
||||
if follow {
|
||||
command += " -f"
|
||||
}
|
||||
command += " 2>&1"
|
||||
cmd := exec.Command("bash", "-c", command)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", errors.New(string(stdout))
|
||||
return err
|
||||
}
|
||||
return string(stdout), nil
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
n, err := stdout.Read(buffer)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
global.LOG.Errorf("read bytes from container log failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
|
||||
global.LOG.Errorf("send message with container log to ws failed, err: %v", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, error) {
|
||||
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -365,7 +615,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
|
||||
if err := json.Unmarshal(body, &stats); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data dto.ContainterStats
|
||||
var data dto.ContainerStats
|
||||
data.CPUPercent = calculateCPUPercentUnix(stats)
|
||||
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
|
||||
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
|
||||
@@ -529,3 +779,39 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
|
||||
}
|
||||
return portMap, nil
|
||||
}
|
||||
|
||||
func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig) error {
|
||||
portMap, err := checkPortStats(req.ExposedPorts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exposeds := make(nat.PortSet)
|
||||
for port := range portMap {
|
||||
exposeds[port] = struct{}{}
|
||||
}
|
||||
config.Image = req.Image
|
||||
config.Cmd = req.Cmd
|
||||
config.Env = req.Env
|
||||
config.Labels = stringsToMap(req.Labels)
|
||||
config.ExposedPorts = exposeds
|
||||
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
|
||||
|
||||
hostConf.AutoRemove = req.AutoRemove
|
||||
hostConf.CPUShares = req.CPUShares
|
||||
hostConf.PublishAllPorts = req.PublishAllPorts
|
||||
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
|
||||
if req.RestartPolicy == "on-failure" {
|
||||
hostConf.RestartPolicy.MaximumRetryCount = 5
|
||||
}
|
||||
hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000)
|
||||
hostConf.Memory = int64(req.Memory * 1024 * 1024)
|
||||
hostConf.PortBindings = portMap
|
||||
hostConf.Binds = []string{}
|
||||
config.Volumes = make(map[string]struct{})
|
||||
for _, volume := range req.Volumes {
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -92,7 +94,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
}
|
||||
for _, item := range composeCreatedByLocal {
|
||||
if err := composeRepo.DeleteRecord(commonRepo.WithByName(item.Name)); err != nil {
|
||||
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
|
||||
global.LOG.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -127,6 +129,13 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
|
||||
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
if cmd.CheckIllegal(req.Path) {
|
||||
return false, buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
|
||||
if composeItem.ID != 0 {
|
||||
return false, constant.ErrRecordExist
|
||||
}
|
||||
if err := u.loadPath(&req); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -139,6 +148,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
}
|
||||
|
||||
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
|
||||
if cmd.CheckIllegal(req.Name, req.Path) {
|
||||
return "", buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
if err := u.loadPath(&req); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -173,6 +185,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
if cmd.CheckIllegal(req.Path, req.Operation) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
@@ -191,6 +206,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
if cmd.CheckIllegal(req.Name, req.Path) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,26 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface
|
||||
|
||||
return int64(total), data, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ListNetwork() ([]dto.Options, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []dto.Options
|
||||
for _, item := range list {
|
||||
datas = append(datas, dto.Options{Option: item.Name})
|
||||
}
|
||||
sort.Slice(datas, func(i, j int) bool {
|
||||
return datas[i].Option < datas[j].Option
|
||||
})
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
|
||||
@@ -80,13 +80,16 @@ func (u *ContainerService) ListVolume() ([]dto.Options, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []dto.Options
|
||||
var datas []dto.Options
|
||||
for _, item := range list.Volumes {
|
||||
data = append(data, dto.Options{
|
||||
datas = append(datas, dto.Options{
|
||||
Option: item.Name,
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
sort.Slice(datas, func(i, j int) bool {
|
||||
return datas[i].Option < datas[j].Option
|
||||
})
|
||||
return datas, nil
|
||||
}
|
||||
func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
|
||||
@@ -36,7 +36,7 @@ func NewICronjobService() ICronjobService {
|
||||
}
|
||||
|
||||
func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info))
|
||||
total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
|
||||
var dtoCronjobs []dto.CronjobInfo
|
||||
for _, cronjob := range cronjobs {
|
||||
var item dto.CronjobInfo
|
||||
@@ -100,9 +100,9 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
} else {
|
||||
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
|
||||
}
|
||||
}
|
||||
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
|
||||
@@ -133,7 +133,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
|
||||
}
|
||||
if backup.Type == "LOCAL" || record.FromLocal {
|
||||
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
|
||||
return "", constant.ErrRecordNotFound
|
||||
return "", err
|
||||
}
|
||||
return record.File, nil
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
|
||||
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
|
||||
isOK, err := client.Download(record.File, tempPath)
|
||||
if !isOK || err != nil {
|
||||
return "", constant.ErrRecordNotFound
|
||||
return "", err
|
||||
}
|
||||
return tempPath, nil
|
||||
}
|
||||
@@ -238,6 +238,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
upMap["name"] = req.Name
|
||||
upMap["spec"] = cronjob.Spec
|
||||
upMap["script"] = req.Script
|
||||
upMap["container_name"] = req.ContainerName
|
||||
upMap["spec_type"] = req.SpecType
|
||||
upMap["week"] = req.Week
|
||||
upMap["day"] = req.Day
|
||||
|
||||
@@ -32,17 +32,21 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
if len(cronjob.Script) == 0 {
|
||||
return
|
||||
}
|
||||
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
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)
|
||||
}
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
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)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "ntp":
|
||||
err = u.handleNtpSync()
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "website":
|
||||
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
||||
case "database":
|
||||
@@ -142,39 +146,55 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
itemFileDir = itemPath + itemFileDir
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, localDir, cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", localDir, cronjob.Type, cronjob.Name, fileName), nil
|
||||
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s", cronjob.Type, cronjob.Name, fileName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
|
||||
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) {
|
||||
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 {
|
||||
if backType != "LOCAL" {
|
||||
_, _ = backClient.Delete(strings.ReplaceAll(file, localDir+"/", ""))
|
||||
_ = os.Remove(file)
|
||||
} else {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
|
||||
_ = os.Remove(records[i].Records)
|
||||
fileItem := file
|
||||
if cronjob.KeepLocal {
|
||||
if len(backupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
fileItem = itemPath + strings.TrimPrefix(file, localDir+"/")
|
||||
} else {
|
||||
fileItem = strings.TrimPrefix(file, localDir+"/")
|
||||
}
|
||||
}
|
||||
_, _ = backClient.Delete(fileItem)
|
||||
}
|
||||
}
|
||||
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
|
||||
_ = os.Remove(records[i].Records)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +227,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
|
||||
commands := fmt.Sprintf("tar zcvf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
||||
global.LOG.Debug(commands)
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
||||
return errors.New(stdout)
|
||||
@@ -224,7 +244,7 @@ func handleUnTar(sourceFile, targetDir string) error {
|
||||
|
||||
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
|
||||
global.LOG.Debug(commands)
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
|
||||
return errors.New(stdout)
|
||||
@@ -273,12 +293,11 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
||||
}
|
||||
record.DetailName = dbName
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = itemFileDir
|
||||
}
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
@@ -290,12 +309,22 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
itemFileDir = itemPath + 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, localDir, &cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
@@ -330,10 +359,10 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
websiteLogDir := path.Join(baseDir, website.PrimaryDomain, "log")
|
||||
websiteLogDir := path.Join(baseDir, website.Alias, "log")
|
||||
srcAccessLogPath := path.Join(websiteLogDir, "access.log")
|
||||
srcErrorLogPath := path.Join(websiteLogDir, "error.log")
|
||||
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.PrimaryDomain)
|
||||
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.Alias)
|
||||
if !fileOp.Stat(dstLogDir) {
|
||||
_ = os.MkdirAll(dstLogDir, 0755)
|
||||
}
|
||||
@@ -341,7 +370,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
|
||||
filePaths = append(filePaths, path.Join(dstLogDir, dstName))
|
||||
if err = fileOp.Compress([]string{srcAccessLogPath, srcErrorLogPath}, dstLogDir, dstName, files.Gz); err != nil {
|
||||
global.LOG.Errorf("There was an error in compressing the website[%s] access.log", website.PrimaryDomain)
|
||||
global.LOG.Errorf("There was an error in compressing the website[%s] access.log, err: %v", website.PrimaryDomain, err)
|
||||
} else {
|
||||
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755)
|
||||
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
|
||||
@@ -361,7 +390,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
return strings.Join(filePaths, ","), nil
|
||||
}
|
||||
|
||||
@@ -402,13 +431,12 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
|
||||
}
|
||||
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
|
||||
}
|
||||
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
@@ -423,11 +451,21 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
itemFileDir = itemPath + 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, localDir, &cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
@@ -48,7 +49,7 @@ func NewIMysqlService() IMysqlService {
|
||||
}
|
||||
|
||||
func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info))
|
||||
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
|
||||
var dtoMysqls []dto.MysqlDBInfo
|
||||
for _, mysql := range mysqls {
|
||||
var item dto.MysqlDBInfo
|
||||
@@ -77,6 +78,10 @@ var formatMap = map[string]string{
|
||||
}
|
||||
|
||||
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
|
||||
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Permission) {
|
||||
return nil, buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
if req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
}
|
||||
@@ -148,8 +153,14 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||
return err
|
||||
}
|
||||
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
if strings.HasPrefix(app.Version, "5.6") {
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
@@ -178,6 +189,10 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||
}
|
||||
|
||||
func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
||||
if cmd.CheckIllegal(info.Value) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
var (
|
||||
mysql model.DatabaseMysql
|
||||
err error
|
||||
@@ -194,7 +209,7 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
||||
}
|
||||
|
||||
passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value)
|
||||
if !strings.HasPrefix(app.Version, "5.7") {
|
||||
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
|
||||
passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value)
|
||||
}
|
||||
if info.ID != 0 {
|
||||
@@ -229,7 +244,7 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
||||
for _, host := range hosts {
|
||||
if host == "%" || host == "localhost" {
|
||||
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value)
|
||||
if !strings.HasPrefix(app.Version, "5.7") {
|
||||
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
|
||||
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value)
|
||||
}
|
||||
if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil {
|
||||
@@ -247,6 +262,9 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
||||
}
|
||||
|
||||
func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
|
||||
if cmd.CheckIllegal(info.Value) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
var (
|
||||
mysql model.DatabaseMysql
|
||||
err error
|
||||
@@ -280,8 +298,14 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
|
||||
}
|
||||
for _, user := range userlist {
|
||||
if len(user) != 0 {
|
||||
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil {
|
||||
return err
|
||||
if strings.HasPrefix(app.Version, "5.6") {
|
||||
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", mysql.Username, user)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,7 +370,7 @@ func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error
|
||||
|
||||
group := "[mysqld]"
|
||||
for _, info := range updates {
|
||||
if !strings.HasPrefix(app.Version, "5.7") {
|
||||
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
|
||||
if info.Param == "query_cache_size" {
|
||||
continue
|
||||
}
|
||||
@@ -495,7 +519,7 @@ func (u *MysqlService) createUser(container, password, version string, req dto.M
|
||||
if req.Name == "*" {
|
||||
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
|
||||
}
|
||||
if strings.HasPrefix(version, "5.7") {
|
||||
if strings.HasPrefix(version, "5.7") || strings.HasPrefix(version, "5.6") {
|
||||
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
|
||||
}
|
||||
if err := excSQL(container, password, grantStr); err != nil {
|
||||
|
||||
@@ -304,7 +304,6 @@ func OperateFirewallPort(oldPorts, newPorts []int) error {
|
||||
return err
|
||||
}
|
||||
for _, port := range newPorts {
|
||||
|
||||
if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ssh"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -23,6 +24,8 @@ type IHostService interface {
|
||||
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
|
||||
Update(id uint, upMap map[string]interface{}) error
|
||||
Delete(id []uint) error
|
||||
|
||||
EncryptHost(itemVal string) (string, error)
|
||||
}
|
||||
|
||||
func NewIHostService() IHostService {
|
||||
@@ -89,8 +92,25 @@ func (u *HostService) TestLocalConn(id uint) bool {
|
||||
if err := copier.Copy(&connInfo, &host); err != nil {
|
||||
return false
|
||||
}
|
||||
connInfo.PrivateKey = []byte(host.PrivateKey)
|
||||
if len(host.Password) != 0 {
|
||||
host.Password, err = encrypt.StringDecrypt(host.Password)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
connInfo.Password = host.Password
|
||||
}
|
||||
if len(host.PrivateKey) != 0 {
|
||||
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
connInfo.PrivateKey = []byte(host.PrivateKey)
|
||||
}
|
||||
if len(host.PassPhrase) != 0 {
|
||||
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
connInfo.PassPhrase = []byte(host.PassPhrase)
|
||||
}
|
||||
client, err := connInfo.NewClient()
|
||||
@@ -107,6 +127,25 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
|
||||
if err != nil {
|
||||
return nil, constant.ErrRecordNotFound
|
||||
}
|
||||
if len(host.Password) != 0 {
|
||||
host.Password, err = encrypt.StringDecrypt(host.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(host.PrivateKey) != 0 {
|
||||
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(host.PassPhrase) != 0 {
|
||||
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &host, err
|
||||
}
|
||||
|
||||
@@ -127,6 +166,25 @@ func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, inte
|
||||
item.Password = ""
|
||||
item.PrivateKey = ""
|
||||
item.PassPhrase = ""
|
||||
} else {
|
||||
if len(host.Password) != 0 {
|
||||
item.Password, err = encrypt.StringDecrypt(host.Password)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
if len(host.PrivateKey) != 0 {
|
||||
item.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
if len(host.PassPhrase) != 0 {
|
||||
item.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
dtoHosts = append(dtoHosts, item)
|
||||
}
|
||||
@@ -164,6 +222,28 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
|
||||
}
|
||||
|
||||
func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
|
||||
var err error
|
||||
if len(req.Password) != 0 && req.AuthMode == "password" {
|
||||
req.Password, err = u.EncryptHost(req.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.PrivateKey = ""
|
||||
req.PassPhrase = ""
|
||||
}
|
||||
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
|
||||
req.PrivateKey, err = u.EncryptHost(req.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(req.PassPhrase) != 0 {
|
||||
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
req.Password = ""
|
||||
}
|
||||
var host model.Host
|
||||
if err := copier.Copy(&host, &req); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
@@ -234,3 +314,12 @@ func (u *HostService) Delete(ids []uint) error {
|
||||
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
|
||||
return hostRepo.Update(id, upMap)
|
||||
}
|
||||
|
||||
func (u *HostService) EncryptHost(itemVal string) (string, error) {
|
||||
privateKey, err := base64.StdEncoding.DecodeString(itemVal)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
keyItem, err := encrypt.StringEncrypt(string(privateKey))
|
||||
return keyItem, err
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
|
||||
|
||||
func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
|
||||
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
|
||||
return buserr.New(constant.ErrRepoConn)
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
|
||||
if imageRepo.ID != 0 {
|
||||
@@ -148,7 +148,7 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
|
||||
return errors.New("The default value cannot be deleted !")
|
||||
}
|
||||
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
|
||||
return buserr.New(constant.ErrRepoConn)
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
|
||||
@@ -76,17 +76,31 @@ func loadDiskIO() {
|
||||
if io2.Name == io1.Name {
|
||||
var itemIO model.MonitorIO
|
||||
itemIO.Name = io1.Name
|
||||
itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
|
||||
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
|
||||
if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes {
|
||||
itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
|
||||
}
|
||||
if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes {
|
||||
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
|
||||
}
|
||||
|
||||
itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60)
|
||||
writeCount := uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
|
||||
if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount {
|
||||
itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60)
|
||||
}
|
||||
writeCount := uint64(0)
|
||||
if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount {
|
||||
writeCount = uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
|
||||
}
|
||||
if writeCount > itemIO.Count {
|
||||
itemIO.Count = writeCount
|
||||
}
|
||||
|
||||
itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
|
||||
writeTime := uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
|
||||
if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime {
|
||||
itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
|
||||
}
|
||||
writeTime := uint64(0)
|
||||
if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime {
|
||||
writeTime = uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
|
||||
}
|
||||
if writeTime > itemIO.Time {
|
||||
itemIO.Time = writeTime
|
||||
}
|
||||
@@ -113,8 +127,13 @@ func loadNetIO() {
|
||||
if net2.Name == net1.Name {
|
||||
var itemNet model.MonitorNetwork
|
||||
itemNet.Name = net1.Name
|
||||
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
|
||||
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
|
||||
|
||||
if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
|
||||
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
|
||||
}
|
||||
if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
|
||||
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
|
||||
}
|
||||
netList = append(netList, itemNet)
|
||||
break
|
||||
}
|
||||
@@ -123,14 +142,16 @@ func loadNetIO() {
|
||||
netStatAll2, _ := net.IOCounters(false)
|
||||
for _, net2 := range netStatAll2 {
|
||||
for _, net1 := range netStatAll {
|
||||
if net1.BytesSent == 0 || net1.BytesRecv == 0 {
|
||||
continue
|
||||
}
|
||||
if net2.Name == net1.Name {
|
||||
var itemNet model.MonitorNetwork
|
||||
itemNet.Name = net1.Name
|
||||
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
|
||||
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
|
||||
if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
|
||||
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
|
||||
}
|
||||
|
||||
if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
|
||||
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
|
||||
}
|
||||
netList = append(netList, itemNet)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
type ProcessService struct{}
|
||||
|
||||
type IProcessService interface {
|
||||
StopProcess(req request.ProcessReq) error
|
||||
}
|
||||
|
||||
func NewIProcessService() IProcessService {
|
||||
return &ProcessService{}
|
||||
}
|
||||
|
||||
func (p *ProcessService) StopProcess(req request.ProcessReq) error {
|
||||
proc, err := process.NewProcess(req.PID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := proc.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
appVersionDir := path.Join(constant.AppResourceDir, app.Resource, app.Key, appDetail.Version)
|
||||
if !fileOp.Stat(appVersionDir) {
|
||||
if !fileOp.Stat(appVersionDir) || appDetail.Update {
|
||||
if err := downloadApp(app, appDetail, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func handleParams(image, runtimeType, runtimeDir string, params map[string]inter
|
||||
if extendsArray, ok := extends.([]interface{}); ok {
|
||||
strArray := make([]string, len(extendsArray))
|
||||
for i, v := range extendsArray {
|
||||
strArray[i] = fmt.Sprintf("%v", v)
|
||||
strArray[i] = strings.ToLower(fmt.Sprintf("%v", v))
|
||||
}
|
||||
params["PHP_EXTENSIONS"] = strings.Join(strArray, ",")
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
if len(req.Names) == 0 {
|
||||
return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
|
||||
}
|
||||
for _, snapName := range req.Names {
|
||||
snap, _ := snapshotRepo.Get(commonRepo.WithByName(strings.ReplaceAll(snapName, ".tar.gz", "")))
|
||||
if snap.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
}
|
||||
for _, snap := range req.Names {
|
||||
nameItems := strings.Split(snap, "_")
|
||||
if !strings.HasPrefix(snap, "1panel_v") || !strings.HasSuffix(snap, ".tar.gz") || len(nameItems) != 3 {
|
||||
@@ -202,7 +208,9 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type)
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
|
||||
localPath := fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow)
|
||||
if ok, err := backupAccount.Upload(localPath, fmt.Sprintf("system_snapshot/1panel_%s_%s.tar.gz", versionItem.Value, timeNow)); err != nil || !ok {
|
||||
itemBackupPath := strings.TrimLeft(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
|
||||
if ok, err := backupAccount.Upload(localPath, fmt.Sprintf("%s/system_snapshot/1panel_%s_%s.tar.gz", itemBackupPath, versionItem.Value, timeNow)); err != nil || !ok {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err)
|
||||
return
|
||||
@@ -253,7 +261,9 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
operation = "re-recover"
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
|
||||
ok, err := client.Download(fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
|
||||
itemBackupPath := strings.TrimLeft(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
|
||||
ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
|
||||
if err != nil || !ok {
|
||||
if req.ReDownload {
|
||||
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
|
||||
@@ -535,7 +545,7 @@ func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
_, err := os.Stat(daemonJsonPath)
|
||||
if os.IsNotExist(err) {
|
||||
global.LOG.Info("no daemon.josn in snapshot and system now, nothing happened")
|
||||
global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
|
||||
}
|
||||
if err == nil {
|
||||
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
|
||||
@@ -658,7 +668,7 @@ func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation strin
|
||||
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
exclusionRules := "./tmp;./cache;"
|
||||
exclusionRules := "./tmp;./cache;./db/1Panel.db-*;"
|
||||
if strings.Contains(backupDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -146,6 +147,9 @@ func (u *SSHService) UpdateByFile(value string) error {
|
||||
}
|
||||
|
||||
func (u *SSHService) GenerateSSH(req dto.GenerateSSH) error {
|
||||
if cmd.CheckIllegal(req.EncryptionMode, req.Password) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load current user failed, err: %v", err)
|
||||
|
||||
+104
-70
@@ -8,6 +8,14 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
@@ -18,13 +26,6 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/ini.v1"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
@@ -57,7 +58,7 @@ type IWebsiteService interface {
|
||||
UpdateNginxConfigByScope(req request.NginxConfigUpdate) error
|
||||
GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error)
|
||||
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
|
||||
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error)
|
||||
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
|
||||
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
|
||||
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
|
||||
UpdateWafConfig(req request.WebsiteWafUpdate) error
|
||||
@@ -96,7 +97,7 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
}
|
||||
return 0, nil, err
|
||||
}
|
||||
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
|
||||
opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if req.Name != "" {
|
||||
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
|
||||
}
|
||||
@@ -618,10 +619,10 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) {
|
||||
func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
res response.WebsiteHTTPS
|
||||
@@ -634,7 +635,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
|
||||
website.Protocol = constant.ProtocolHTTP
|
||||
website.WebsiteSSLID = 0
|
||||
if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
|
||||
nginxParams = append(nginxParams,
|
||||
@@ -655,28 +656,64 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
|
||||
Name: "ssl_ciphers",
|
||||
},
|
||||
)
|
||||
if err := deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := websiteRepo.Save(ctx, &website); err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
if err = websiteRepo.Save(ctx, &website); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if req.Type == constant.SSLExisted {
|
||||
websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID))
|
||||
if err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
website.WebsiteSSLID = websiteSSL.ID
|
||||
res.SSL = websiteSSL
|
||||
}
|
||||
if req.Type == constant.SSLManual {
|
||||
certBlock, _ := pem.Decode([]byte(req.Certificate))
|
||||
var (
|
||||
certificate string
|
||||
privateKey string
|
||||
)
|
||||
switch req.ImportType {
|
||||
case "paste":
|
||||
certificate = req.Certificate
|
||||
privateKey = req.PrivateKey
|
||||
case "local":
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.PrivateKeyPath) {
|
||||
return nil, buserr.New("ErrSSLKeyNotFound")
|
||||
}
|
||||
if !fileOp.Stat(req.CertificatePath) {
|
||||
return nil, buserr.New("ErrSSLCertificateNotFound")
|
||||
}
|
||||
if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
privateKey = string(content)
|
||||
}
|
||||
if content, err := fileOp.GetContent(req.CertificatePath); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
certificate = string(content)
|
||||
}
|
||||
}
|
||||
|
||||
privateKeyCertBlock, _ := pem.Decode([]byte(privateKey))
|
||||
if privateKeyCertBlock == nil {
|
||||
return nil, buserr.New("ErrSSLKeyFormat")
|
||||
}
|
||||
|
||||
certBlock, _ := pem.Decode([]byte(certificate))
|
||||
if certBlock == nil {
|
||||
return nil, buserr.New("ErrSSLCertificateFormat")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
websiteSSL.ExpireDate = cert.NotAfter
|
||||
websiteSSL.StartDate = cert.NotBefore
|
||||
@@ -690,28 +727,28 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
|
||||
websiteSSL.PrimaryDomain = cert.DNSNames[0]
|
||||
websiteSSL.Domains = strings.Join(cert.DNSNames, ",")
|
||||
}
|
||||
|
||||
websiteSSL.Provider = constant.Manual
|
||||
websiteSSL.PrivateKey = req.PrivateKey
|
||||
websiteSSL.Pem = req.Certificate
|
||||
websiteSSL.PrivateKey = privateKey
|
||||
websiteSSL.Pem = certificate
|
||||
|
||||
res.SSL = websiteSSL
|
||||
}
|
||||
website.Protocol = constant.ProtocolHTTPS
|
||||
if err := applySSL(website, websiteSSL, req); err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
website.HttpConfig = req.HttpConfig
|
||||
|
||||
if websiteSSL.ID == 0 {
|
||||
if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
website.WebsiteSSLID = websiteSSL.ID
|
||||
}
|
||||
if err := websiteRepo.Save(ctx, &website); err != nil {
|
||||
return response.WebsiteHTTPS{}, err
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) {
|
||||
@@ -1058,50 +1095,41 @@ func (w WebsiteService) UpdatePHPConfig(req request.WebsitePHPConfigUpdate) (err
|
||||
return err
|
||||
}
|
||||
|
||||
if req.Scope == "params" {
|
||||
content := string(contentBytes)
|
||||
lines := strings.Split(content, "\n")
|
||||
for i, line := range lines {
|
||||
if strings.HasPrefix(line, ";") {
|
||||
continue
|
||||
}
|
||||
content := string(contentBytes)
|
||||
lines := strings.Split(content, "\n")
|
||||
for i, line := range lines {
|
||||
if strings.HasPrefix(line, ";") {
|
||||
continue
|
||||
}
|
||||
switch req.Scope {
|
||||
case "params":
|
||||
for key, value := range req.Params {
|
||||
pattern := "^" + regexp.QuoteMeta(key) + "\\s*=\\s*.*$"
|
||||
if matched, _ := regexp.MatchString(pattern, line); matched {
|
||||
lines[i] = key + " = " + value
|
||||
}
|
||||
}
|
||||
}
|
||||
updatedContent := strings.Join(lines, "\n")
|
||||
if err := fileOp.WriteFile(phpConfigPath, strings.NewReader(updatedContent), 0755); err != nil {
|
||||
return err
|
||||
case "disable_functions":
|
||||
pattern := "^" + regexp.QuoteMeta("disable_functions") + "\\s*=\\s*.*$"
|
||||
if matched, _ := regexp.MatchString(pattern, line); matched {
|
||||
lines[i] = "disable_functions" + " = " + strings.Join(req.DisableFunctions, ",")
|
||||
break
|
||||
}
|
||||
case "upload_max_filesize":
|
||||
pattern := "^" + regexp.QuoteMeta("post_max_size") + "\\s*=\\s*.*$"
|
||||
if matched, _ := regexp.MatchString(pattern, line); matched {
|
||||
lines[i] = "post_max_size" + " = " + req.UploadMaxSize
|
||||
}
|
||||
patternUpload := "^" + regexp.QuoteMeta("upload_max_filesize") + "\\s*=\\s*.*$"
|
||||
if matched, _ := regexp.MatchString(patternUpload, line); matched {
|
||||
lines[i] = "upload_max_filesize" + " = " + req.UploadMaxSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(phpConfigPath)
|
||||
if err != nil {
|
||||
updatedContent := strings.Join(lines, "\n")
|
||||
if err := fileOp.WriteFile(phpConfigPath, strings.NewReader(updatedContent), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
phpConfig, err := cfg.GetSection("PHP")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Scope == "disable_functions" {
|
||||
disable := phpConfig.Key("disable_functions")
|
||||
disable.SetValue(strings.Join(req.DisableFunctions, ","))
|
||||
if err = cfg.SaveTo(phpConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.Scope == "upload_max_filesize" {
|
||||
postMaxSize := phpConfig.Key("post_max_size")
|
||||
postMaxSize.SetValue(req.UploadMaxSize)
|
||||
uploadMaxFileSize := phpConfig.Key("upload_max_filesize")
|
||||
uploadMaxFileSize.SetValue(req.UploadMaxSize)
|
||||
if err = cfg.SaveTo(phpConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
appInstallReq := request.AppInstalledOperate{
|
||||
InstallId: appInstall.ID,
|
||||
@@ -1504,14 +1532,6 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
|
||||
if !fileOp.Stat(absoluteAuthPath) {
|
||||
_ = fileOp.CreateFile(absoluteAuthPath)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}})
|
||||
params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}})
|
||||
@@ -1519,7 +1539,9 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
authArray = strings.Split(string(authContent), "\n")
|
||||
if len(authContent) > 0 {
|
||||
authArray = strings.Split(string(authContent), "\n")
|
||||
}
|
||||
switch req.Operate {
|
||||
case "disable":
|
||||
return deleteNginxConfig(constant.NginxScopeServer, params, &website)
|
||||
@@ -1590,6 +1612,9 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
|
||||
defer passFile.Close()
|
||||
writer := bufio.NewWriter(passFile)
|
||||
for _, line := range authArray {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
_, err = writer.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
return
|
||||
@@ -1599,6 +1624,15 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
authContent, err = fileOp.GetContent(absoluteAuthPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(authContent) == 0 {
|
||||
if err = deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -21,7 +23,7 @@ type WebsiteSSLService struct {
|
||||
type IWebsiteSSLService interface {
|
||||
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
|
||||
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
|
||||
Search() ([]response.WebsiteSSLDTO, error)
|
||||
Search(req request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error)
|
||||
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
|
||||
Renew(sslId uint) error
|
||||
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
|
||||
@@ -35,17 +37,19 @@ func NewIWebsiteSSLService() IWebsiteSSLService {
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) {
|
||||
var (
|
||||
result []response.WebsiteSSLDTO
|
||||
)
|
||||
total, sslList, err := websiteSSLRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var sslDTOs []response.WebsiteSSLDTO
|
||||
for _, ssl := range sslList {
|
||||
sslDTOs = append(sslDTOs, response.WebsiteSSLDTO{
|
||||
WebsiteSSL: ssl,
|
||||
for _, sslModel := range sslList {
|
||||
result = append(result, response.WebsiteSSLDTO{
|
||||
WebsiteSSL: sslModel,
|
||||
})
|
||||
}
|
||||
return total, sslDTOs, err
|
||||
return total, result, err
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
|
||||
@@ -58,18 +62,29 @@ func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) Search() ([]response.WebsiteSSLDTO, error) {
|
||||
sslList, err := websiteSSLRepo.List()
|
||||
func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) {
|
||||
var (
|
||||
opts []repo.DBOption
|
||||
result []response.WebsiteSSLDTO
|
||||
)
|
||||
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
|
||||
if search.AcmeAccountID != "" {
|
||||
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, websiteSSLRepo.WithByAcmeAccountId(uint(acmeAccountID)))
|
||||
}
|
||||
sslList, err := websiteSSLRepo.List(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sslDTOs []response.WebsiteSSLDTO
|
||||
for _, ssl := range sslList {
|
||||
sslDTOs = append(sslDTOs, response.WebsiteSSLDTO{
|
||||
WebsiteSSL: ssl,
|
||||
for _, sslModel := range sslList {
|
||||
result = append(result, response.WebsiteSSLDTO{
|
||||
WebsiteSSL: sslModel,
|
||||
})
|
||||
}
|
||||
return sslDTOs, err
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error) {
|
||||
|
||||
@@ -378,9 +378,9 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We
|
||||
}
|
||||
config := nginxFull.SiteConfig.Config
|
||||
server := config.FindServers()[0]
|
||||
server.UpdateListen("443", website.DefaultServer, "ssl")
|
||||
server.UpdateListen("443", website.DefaultServer, "ssl", "http2")
|
||||
if website.IPV6 {
|
||||
server.UpdateListen("[::]:443", website.DefaultServer, "ssl")
|
||||
server.UpdateListen("[::]:443", website.DefaultServer, "ssl", "http2")
|
||||
}
|
||||
|
||||
switch req.HttpConfig {
|
||||
@@ -536,6 +536,10 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
if files.NewFileOp().Stat(absoluteRewritePath) {
|
||||
server.UpdateDirective("include", []string{rewriteInclude})
|
||||
}
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
if website.SiteDir != "/" {
|
||||
rootIndex = path.Join(rootIndex, website.SiteDir)
|
||||
}
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
server.RemoveDirective("root", nil)
|
||||
@@ -546,12 +550,11 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
case constant.Static:
|
||||
server.UpdateRoot(path.Join("/www/sites", website.Alias, "index"))
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdateRootLocation()
|
||||
case constant.Proxy:
|
||||
server.RemoveDirective("root", nil)
|
||||
case constant.Runtime:
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
server.UpdateRoot(rootIndex)
|
||||
localPath := ""
|
||||
if website.ProxyType == constant.RuntimeProxyUnix {
|
||||
|
||||
@@ -21,4 +21,6 @@ type System struct {
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
ChangeUserInfo bool `mapstructure:"change_user_info"`
|
||||
OneDriveID string `mapstructure:"one_drive_id"`
|
||||
OneDriveSc string `mapstructure:"one_drive_sc"`
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
DirNotFound = "DirNotFound"
|
||||
Upgrading = "Upgrading"
|
||||
UpgradeErr = "UpgradeErr"
|
||||
PullErr = "PullErr"
|
||||
|
||||
ContainerPrefix = "1Panel-"
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ const (
|
||||
S3 = "S3"
|
||||
OSS = "OSS"
|
||||
Sftp = "SFTP"
|
||||
OneDrive = "OneDrive"
|
||||
MinIo = "MINIO"
|
||||
Cos = "COS"
|
||||
Kodo = "KODO"
|
||||
|
||||
OneDriveRedirectURI = "http://localhost/login/authorized"
|
||||
)
|
||||
|
||||
@@ -42,6 +42,7 @@ var (
|
||||
ErrTypePasswordExpired = "ErrPasswordExpired"
|
||||
ErrNameIsExist = "ErrNameIsExist"
|
||||
ErrDemoEnvironment = "ErrDemoEnvironment"
|
||||
ErrCmdIllegal = "ErrCmdIllegal"
|
||||
)
|
||||
|
||||
// app
|
||||
@@ -68,6 +69,8 @@ var (
|
||||
ErrGroupIsUsed = "ErrGroupIsUsed"
|
||||
ErrUsernameIsExist = "ErrUsernameIsExist"
|
||||
ErrUsernameIsNotExist = "ErrUsernameIsNotExist"
|
||||
ErrBackupMatch = "ErrBackupMatch"
|
||||
ErrBackupExist = "ErrBackupExist"
|
||||
)
|
||||
|
||||
// ssl
|
||||
@@ -105,7 +108,6 @@ var (
|
||||
ErrInUsed = "ErrInUsed"
|
||||
ErrObjectInUsed = "ErrObjectInUsed"
|
||||
ErrPortRules = "ErrPortRules"
|
||||
ErrRepoConn = "ErrRepoConn"
|
||||
)
|
||||
|
||||
// runtime
|
||||
|
||||
@@ -10,4 +10,7 @@ const (
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
StatusNone = "None"
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
)
|
||||
|
||||
@@ -60,7 +60,7 @@ func GinI18nLocalize() gin.HandlerFunc {
|
||||
return ginI18n.Localize(
|
||||
ginI18n.WithBundle(&ginI18n.BundleCfg{
|
||||
RootPath: "./lang",
|
||||
AcceptLanguage: []language.Tag{language.Chinese, language.English},
|
||||
AcceptLanguage: []language.Tag{language.Chinese, language.English, language.TraditionalChinese},
|
||||
DefaultLanguage: language.Chinese,
|
||||
FormatBundleFile: "yaml",
|
||||
UnmarshalFunc: yaml.Unmarshal,
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
ErrInvalidParams: "Request parameter error: {{ .detail }}"
|
||||
ErrToken: "Token information is incorrect.: {{ .detail }}"
|
||||
ErrTokenParse: "Token generation error: {{ .detail }}"
|
||||
ErrTokenTimeOut: "Login information is out of date: {{ .detail }}"
|
||||
ErrInitialPassword: "Initial password error"
|
||||
ErrInternalServer: "Service internal error: {{ .detail }}"
|
||||
ErrRecordExist: "Record already exists"
|
||||
ErrRecordNotFound: "Records not found"
|
||||
ErrStructTransform: "Type conversion failure: {{ .detail }}"
|
||||
ErrNotLogin: "User is not Login: {{ .detail }}"
|
||||
ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}"
|
||||
ErrPasswordExpired: "The current password has expired: {{ .detail }}"
|
||||
ErrNotSupportType: "The system does not support the current type: {{ .detail }}"
|
||||
ErrRepoNotValid: "Remote repository verification failed!"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "Name is already exist"
|
||||
ErrDemoEnvironment: "Demo server, prohibit this operation!"
|
||||
ErrCmdTimeout: "Command execution timed out!"
|
||||
ErrCmdIllegal: "The command contains illegal characters. Please modify and try again!"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} port already in use"
|
||||
@@ -44,6 +41,9 @@ ErrHttpReqTimeOut: "Request timed out {{.err}}"
|
||||
ErrHttpReqFailed: "Request failed {{.err}}"
|
||||
ErrHttpReqNotFound: "The file does not exist"
|
||||
ErrNoSuchHost: "Network connection failed"
|
||||
ErrImagePullTimeOut: 'Image pull timeout'
|
||||
ErrContainerNotFound: '{{ .name }} container does not exist'
|
||||
ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "File can not read"
|
||||
@@ -60,12 +60,18 @@ ErrDomainIsExist: "Domain is already exist"
|
||||
ErrAliasIsExist: "Alias is already exist"
|
||||
ErrAppDelete: 'Other Website use this App'
|
||||
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
|
||||
ErrBackupMatch: 'the backup file does not match the current partial data of the website: {{ .detail}}"'
|
||||
ErrBackupExist: 'the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}"'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed"
|
||||
ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted"
|
||||
ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration!"
|
||||
ErrEmailIsExist: 'Email is already exist'
|
||||
ErrSSLKeyNotFound: 'The private key file does not exist'
|
||||
ErrSSLCertificateNotFound: 'The certificate file does not exist'
|
||||
ErrSSLKeyFormat: 'Private key file verification error'
|
||||
ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "The current user already exists. Please enter a new user"
|
||||
@@ -78,7 +84,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
|
||||
ErrObjectInUsed: "This object is in use and cannot be deleted"
|
||||
ErrRepoConn: "The repository information contains illegal characters"
|
||||
ErrPortRules: "The number of ports does not match, please re-enter!"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
ErrInvalidParams: "請求參數錯誤: {{ .detail }}"
|
||||
ErrTokenParse: "Token 產生錯誤: {{ .detail }}"
|
||||
ErrInitialPassword: "原密碼錯誤"
|
||||
ErrInternalServer: "伺服器內部錯誤: {{ .detail }}"
|
||||
ErrRecordExist: "記錄已存在"
|
||||
ErrRecordNotFound: "記錄未找到"
|
||||
ErrStructTransform: "類型轉換失敗: {{ .detail }}"
|
||||
ErrNotLogin: "用戶未登入: {{ .detail }}"
|
||||
ErrPasswordExpired: "當前密碼已過期: {{ .detail }}"
|
||||
ErrNotSupportType: "系統暫不支持當前類型: {{ .detail }}"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "名稱已存在"
|
||||
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
|
||||
ErrCmdTimeout: "指令執行超時!"
|
||||
ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
|
||||
ErrAppLimit: "應用超出安裝數量限制"
|
||||
ErrAppRequired: "請先安裝 {{ .detail }} 應用"
|
||||
ErrNotInstall: "應用未安裝"
|
||||
ErrPortInOtherApp: "{{ .port }} 端口已被 {{ .apps }}佔用!"
|
||||
ErrDbUserNotValid: "儲存資料庫,用戶名密碼不匹配!"
|
||||
ErrDockerComposeNotValid: "docker-compose 文件格式錯誤"
|
||||
ErrUpdateBuWebsite: '應用更新成功,但是網站配置文件修改失敗,請檢查配置!'
|
||||
Err1PanelNetworkFailed: '默認容器網絡創建失敗!{{ .detail }}'
|
||||
ErrFileParse: '應用 docker-compose 文件解析失敗!'
|
||||
ErrInstallDirNotFound: '安裝目錄不存在'
|
||||
AppStoreIsUpToDate: '應用商店已經是最新版本'
|
||||
LocalAppVersionNull: '{{.name}} 應用未同步到版本!無法添加到應用列表'
|
||||
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失敗!{{.err}}'
|
||||
ErrFileNotFound: '{{.name}} 文件不存在'
|
||||
ErrFileParseApp: '{{.name}} 文件解析失敗 {{.err}}'
|
||||
ErrAppDirNull: '版本資料夾不存在'
|
||||
LocalAppErr: "應用 {{.name}} 同步失敗!{{.err}}"
|
||||
ErrContainerName: "容器名稱已存在"
|
||||
ErrAppSystemRestart: "1Panel 重啟導致任務中斷"
|
||||
ErrCreateHttpClient: "創建HTTP請求失敗 {{.err}}"
|
||||
ErrHttpReqTimeOut: "請求超時 {{.err}}"
|
||||
ErrHttpReqFailed: "請求失敗 {{.err}}"
|
||||
ErrHttpReqNotFound: "文件不存在"
|
||||
ErrNoSuchHost: "網路連接失敗"
|
||||
ErrImagePullTimeOut: "鏡像拉取超時"
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
ErrFileToLarge: "文件超過10M,無法打開"
|
||||
ErrPathNotFound: "目錄不存在"
|
||||
ErrMovePathFailed: "目標路徑不能包含原路徑!"
|
||||
ErrLinkPathNotFound: "目標路徑不存在!"
|
||||
ErrFileIsExit: "文件已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下載文件夾"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代號已存在"
|
||||
ErrAppDelete: '其他網站使用此應用,無法刪除'
|
||||
ErrGroupIsUsed: '分組正在使用中,無法刪除'
|
||||
ErrBackupMatch: '該備份文件與當前網站部分數據不匹配: {{ .detail}}"'
|
||||
ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}"'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "證書正在被網站使用,無法刪除"
|
||||
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
|
||||
ErrSSLApply: "證書續簽成功,openresty reload失敗,請檢查配置!"
|
||||
ErrEmailIsExist: '郵箱已存在'
|
||||
ErrSSLKeyNotFound: '私鑰文件不存在'
|
||||
ErrSSLCertificateNotFound: '證書文件不存在'
|
||||
ErrSSLKeyFormat: '私鑰文件校驗錯誤'
|
||||
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "當前用戶已存在,請重新輸入"
|
||||
ErrDatabaseIsExist: "當前資料庫已存在,請重新輸入"
|
||||
ErrExecTimeOut: "SQL 執行超時,請檢查{{ .detail }}容器"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後重試"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
|
||||
ErrObjectInUsed: "該對象正被使用,無法刪除"
|
||||
ErrPortRules: "端口數目不匹配,請重新輸入!"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夾不存在!請檢查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!請檢查源文件完整性!"
|
||||
ErrImageBuildErr: "鏡像 build 失敗"
|
||||
ErrImageExist: "鏡像已存在!"
|
||||
ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||
|
||||
ErrOSSConn: "無法成功請求最新版本,請檢查伺服器是否能夠連接到外部網絡環境。"
|
||||
@@ -1,22 +1,19 @@
|
||||
ErrInvalidParams: "请求参数错误: {{ .detail }}"
|
||||
ErrToken: "Token 信息错误: {{ .detail }}"
|
||||
ErrTokenParse: "Token 生成错误: {{ .detail }}"
|
||||
ErrTokenTimeOut: "登陆信息已过期: {{ .detail }}"
|
||||
ErrInitialPassword: "原密码错误"
|
||||
ErrInternalServer: "服务内部错误: {{ .detail }}"
|
||||
ErrRecordExist: "记录已存在"
|
||||
ErrRecordNotFound: "记录未能找到"
|
||||
ErrStructTransform: "类型转换失败: {{ .detail }}"
|
||||
ErrNotLogin: "用户未登录: {{ .detail }}"
|
||||
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
|
||||
ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
|
||||
ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
|
||||
ErrRepoNotValid: "远程仓库校验失败!"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "名称已存在"
|
||||
ErrDemoEnvironment: "演示服务器,禁止此操作!"
|
||||
ErrCmdTimeout: "命令执行超时!"
|
||||
ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
|
||||
@@ -44,6 +41,9 @@ ErrHttpReqTimeOut: "请求超时 {{.err}}"
|
||||
ErrHttpReqFailed: "请求失败 {{.err}}"
|
||||
ErrHttpReqNotFound: "文件不存在"
|
||||
ErrNoSuchHost: "网络连接失败"
|
||||
ErrImagePullTimeOut: '镜像拉取超时'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
@@ -60,12 +60,18 @@ ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代号已存在"
|
||||
ErrAppDelete: '其他网站使用此应用,无法删除'
|
||||
ErrGroupIsUsed: '分组正在使用中,无法删除'
|
||||
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}"'
|
||||
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}"'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
|
||||
ErrAccountCannotDelete: "账号关联证书,无法删除"
|
||||
ErrSSLApply: "证书续签成功,openresty reload失败,请检查配置!"
|
||||
ErrEmailIsExist: '邮箱已存在'
|
||||
ErrSSLKeyNotFound: '私钥文件不存在'
|
||||
ErrSSLCertificateNotFound: '证书文件不存在'
|
||||
ErrSSLKeyFormat: '私钥文件校验失败'
|
||||
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
@@ -78,7 +84,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
|
||||
ErrObjectInUsed: "该对象正被使用,无法删除"
|
||||
ErrRepoConn: "仓库信息中存在不合法的字符"
|
||||
ErrPortRules: "端口数目不匹配,请重新输入!"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -15,17 +17,30 @@ func Init() {
|
||||
global.LOG.Errorf("load service port from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.Port = portSetting.Value
|
||||
enptrySetting, err := settingRepo.Get(settingRepo.WithByKey("EncryptKey"))
|
||||
encryptSetting, err := settingRepo.Get(settingRepo.WithByKey("EncryptKey"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.EncryptKey = enptrySetting.Value
|
||||
global.CONF.System.EncryptKey = encryptSetting.Value
|
||||
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.SSL = sslSetting.Value
|
||||
|
||||
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load onedrive info from setting failed, err: %v", err)
|
||||
}
|
||||
idItem, _ := base64.StdEncoding.DecodeString(OneDriveID.Value)
|
||||
global.CONF.System.OneDriveID = string(idItem)
|
||||
OneDriveSc, err := settingRepo.Get(settingRepo.WithByKey("OneDriveSc"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load onedrive info from setting failed, err: %v", err)
|
||||
}
|
||||
scItem, _ := base64.StdEncoding.DecodeString(OneDriveSc.Value)
|
||||
global.CONF.System.OneDriveSc = string(scItem)
|
||||
|
||||
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
|
||||
_ = settingRepo.Create("SystemStatus", "Free")
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ func Init() {
|
||||
migrations.AddBindAndAllowIPs,
|
||||
migrations.UpdateCronjobWithSecond,
|
||||
migrations.UpdateWebsite,
|
||||
migrations.AddBackupAccountDir,
|
||||
migrations.AddMfaInterval,
|
||||
migrations.UpdateAppDetail,
|
||||
migrations.EncryptHostPassword,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -390,3 +390,92 @@ var UpdateWebsite = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddBackupAccountDir = &gormigrate.Migration{
|
||||
ID: "20200620-add-backup-dir",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.BackupAccount{}, &model.Cronjob{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMfaInterval = &gormigrate.Migration{
|
||||
ID: "20230625-add-mfa-interval",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "MFAInterval", Value: "30"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SystemIP", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "OneDriveID", Value: "MDEwOTM1YTktMWFhOS00ODU0LWExZGMtNmU0NWZlNjI4YzZi"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "OneDriveSc", Value: "akpuOFF+YkNXOU1OLWRzS1ZSRDdOcG1LT2ZRM0RLNmdvS1RkVWNGRA=="}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateAppDetail = &gormigrate.Migration{
|
||||
ID: "20230704-update-app-detail",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.AppDetail{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.AppDetail{}).Where("1 = 1").Update("ignore_upgrade", "0").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var EncryptHostPassword = &gormigrate.Migration{
|
||||
ID: "20230703-encrypt-host-password",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var hosts []model.Host
|
||||
if err := tx.Where("1 = 1").Find(&hosts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var encryptSetting model.Setting
|
||||
if err := tx.Where("key = ?", "EncryptKey").Find(&encryptSetting).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
global.CONF.System.EncryptKey = encryptSetting.Value
|
||||
|
||||
for _, host := range hosts {
|
||||
if len(host.Password) != 0 {
|
||||
pass, err := encrypt.StringEncrypt(host.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("password", pass).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(host.PrivateKey) != 0 {
|
||||
key, err := encrypt.StringEncrypt(host.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("private_key", key).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(host.PassPhrase) != 0 {
|
||||
pass, err := encrypt.StringEncrypt(host.PassPhrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("pass_phrase", pass).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ func Routers() *gin.Engine {
|
||||
systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup)
|
||||
systemRouter.InitNginxRouter(PrivateGroup)
|
||||
systemRouter.InitRuntimeRouter(PrivateGroup)
|
||||
systemRouter.InitProcessRouter(PrivateGroup)
|
||||
}
|
||||
|
||||
return Router
|
||||
|
||||
@@ -118,8 +118,8 @@ func OperationLog() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
}
|
||||
record.DetailEN = operationDic.FormatEN
|
||||
record.DetailZH = operationDic.FormatZH
|
||||
record.DetailEN = strings.ReplaceAll(operationDic.FormatEN, "[]", "")
|
||||
record.DetailZH = strings.ReplaceAll(operationDic.FormatZH, "[]", "")
|
||||
|
||||
writer := responseBodyWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
|
||||
@@ -20,6 +20,7 @@ type RouterGroup struct {
|
||||
DatabaseRouter
|
||||
NginxRouter
|
||||
RuntimeRouter
|
||||
ProcessRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
|
||||
@@ -36,5 +36,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
||||
appRouter.GET("/installed/conf/:key", baseApi.GetDefaultConfig)
|
||||
appRouter.GET("/installed/params/:appInstallId", baseApi.GetParams)
|
||||
appRouter.POST("/installed/params/update", baseApi.UpdateInstalled)
|
||||
appRouter.POST("/installed/ignore", baseApi.IgnoreUpgrade)
|
||||
appRouter.GET("/ignored/detail", baseApi.GetIgnoredApp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,14 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||
|
||||
baRouter.POST("", baseApi.ContainerCreate)
|
||||
baRouter.POST("/update", baseApi.ContainerUpdate)
|
||||
baRouter.POST("/upgrade", baseApi.ContainerUpgrade)
|
||||
baRouter.POST("/info", baseApi.ContainerInfo)
|
||||
baRouter.POST("/search", baseApi.SearchContainer)
|
||||
baRouter.POST("/search/log", baseApi.ContainerLogs)
|
||||
baRouter.POST("/list", baseApi.ListContainer)
|
||||
baRouter.GET("/list/stats", baseApi.ContainerListStats)
|
||||
baRouter.GET("/search/log", baseApi.ContainerLogs)
|
||||
baRouter.GET("/limit", baseApi.LoadResouceLimit)
|
||||
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
|
||||
baRouter.POST("/inspect", baseApi.Inspect)
|
||||
baRouter.POST("/operate", baseApi.ContainerOperation)
|
||||
@@ -55,10 +61,11 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||
baRouter.POST("/image/tag", baseApi.ImageTag)
|
||||
baRouter.POST("/image/build", baseApi.ImageBuild)
|
||||
|
||||
baRouter.GET("/volume", baseApi.ListVolume)
|
||||
baRouter.GET("/network", baseApi.ListNetwork)
|
||||
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
||||
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
||||
baRouter.POST("/network", baseApi.CreateNetwork)
|
||||
baRouter.GET("/volume", baseApi.ListVolume)
|
||||
baRouter.POST("/volume/del", baseApi.DeleteVolume)
|
||||
baRouter.POST("/volume/search", baseApi.SearchVolume)
|
||||
baRouter.POST("/volume", baseApi.CreateVolume)
|
||||
|
||||
@@ -24,7 +24,6 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/tree", baseApi.HostTree)
|
||||
hostRouter.POST("/test/byinfo", baseApi.TestByInfo)
|
||||
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
|
||||
hostRouter.GET(":id", baseApi.GetHostInfo)
|
||||
|
||||
hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo)
|
||||
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ProcessRouter struct {
|
||||
}
|
||||
|
||||
func (f *ProcessRouter) InitProcessRouter(Router *gin.RouterGroup) {
|
||||
processRouter := Router.Group("process")
|
||||
processRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
processRouter.GET("/ws", baseApi.ProcessWs)
|
||||
processRouter.POST("/stop", baseApi.StopProcess)
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.GET("/time/option", baseApi.LoadTimeZone)
|
||||
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
||||
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||
settingRouter.GET("/mfa", baseApi.GetMFA)
|
||||
settingRouter.GET("/mfa/:interval", baseApi.GetMFA)
|
||||
settingRouter.POST("/mfa/bind", baseApi.MFABind)
|
||||
|
||||
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
|
||||
@@ -41,6 +41,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription)
|
||||
|
||||
settingRouter.GET("/backup/search", baseApi.ListBackup)
|
||||
settingRouter.GET("/backup/onedrive", baseApi.LoadOneDriveInfo)
|
||||
settingRouter.POST("/backup/backup", baseApi.Backup)
|
||||
settingRouter.POST("/backup/recover", baseApi.Recover)
|
||||
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)
|
||||
|
||||
@@ -14,6 +14,7 @@ type cosClient struct {
|
||||
region string
|
||||
accessKey string
|
||||
secretKey string
|
||||
scType string
|
||||
Vars map[string]interface{}
|
||||
client *cosSDK.Client
|
||||
}
|
||||
@@ -21,6 +22,7 @@ type cosClient struct {
|
||||
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
var accessKey string
|
||||
var secretKey string
|
||||
var scType string
|
||||
var region string
|
||||
if _, ok := vars["region"]; ok {
|
||||
region = vars["region"].(string)
|
||||
@@ -32,6 +34,11 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
} else {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
if _, ok := vars["scType"]; ok {
|
||||
scType = vars["scType"].(string)
|
||||
} else {
|
||||
scType = "Standard"
|
||||
}
|
||||
if _, ok := vars["secretKey"]; ok {
|
||||
secretKey = vars["secretKey"].(string)
|
||||
} else {
|
||||
@@ -47,7 +54,7 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
},
|
||||
})
|
||||
|
||||
return &cosClient{Vars: vars, client: client, accessKey: accessKey, secretKey: secretKey, region: region}, nil
|
||||
return &cosClient{Vars: vars, client: client, accessKey: accessKey, secretKey: secretKey, scType: scType, region: region}, nil
|
||||
}
|
||||
|
||||
func (cos cosClient) ListBuckets() ([]interface{}, error) {
|
||||
@@ -90,7 +97,12 @@ func (cos cosClient) Upload(src, target string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if _, err := client.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{}); err != nil {
|
||||
if _, err := client.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{
|
||||
ACLHeaderOptions: nil,
|
||||
ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{
|
||||
XCosStorageClass: cos.scType,
|
||||
},
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type oneDriveClient struct {
|
||||
Vars map[string]interface{}
|
||||
client odsdk.Client
|
||||
}
|
||||
|
||||
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
|
||||
token := ""
|
||||
if _, ok := vars["accessToken"]; ok {
|
||||
token = vars["accessToken"].(string)
|
||||
} else {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
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},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
client := odsdk.NewClient(tc)
|
||||
return &oneDriveClient{client: *client}, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) ListBuckets() ([]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Exist(path string) (bool, error) {
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
fileID, err := onedrive.loadIDByPath(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(fileID) != 0, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Delete(path string) (bool, error) {
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
req, err := onedrive.client.NewRequest("DELETE", fmt.Sprintf("me/drive/root:%s", path), nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("new request for delete file failed, err: %v \n", err)
|
||||
}
|
||||
if err := onedrive.client.Do(context.Background(), req, false, nil); err != nil {
|
||||
return false, fmt.Errorf("do request for delete file failed, err: %v \n", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Upload(src, target string) (bool, error) {
|
||||
target = "/" + strings.TrimPrefix(target, "/")
|
||||
if _, err := onedrive.loadIDByPath(path.Dir(target)); err != nil {
|
||||
if !strings.Contains(err.Error(), "itemNotFound") {
|
||||
return false, err
|
||||
}
|
||||
if err := onedrive.createFolder(path.Dir(target)); err != nil {
|
||||
return false, fmt.Errorf("create dir before upload failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
fileInfo, err := file.Stat()
|
||||
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 := onedrive.loadIDByPath(path.Dir(target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
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 := onedrive.client.NewRequest("POST", apiURL, sessionCreationRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var sessionCreationResp *NewUploadSessionCreationResponse
|
||||
err = onedrive.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 := onedrive.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := onedrive.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
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Download(src, target string) (bool, error) {
|
||||
src = "/" + strings.TrimPrefix(src, "/")
|
||||
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", src), nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("new request for file id failed, err: %v", err)
|
||||
}
|
||||
var driveItem *odsdk.DriveItem
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
|
||||
return false, fmt.Errorf("do request for file id failed, err: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.Get(driveItem.DownloadURL)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
out, err := os.Create(target)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer out.Close()
|
||||
buffer := make([]byte, 2*1024*1024)
|
||||
|
||||
_, err = io.CopyBuffer(out, resp.Body, buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
prefix = "/" + strings.TrimPrefix(prefix, "/")
|
||||
folderID, err := onedrive.loadIDByPath(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/items/%s/children", folderID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new request for list failed, err: %v", err)
|
||||
}
|
||||
var driveItems *odsdk.OneDriveDriveItemsResponse
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItems); err != nil {
|
||||
return nil, fmt.Errorf("do request for list failed, err: %v", err)
|
||||
}
|
||||
for _, item := range driveItems.DriveItems {
|
||||
return nil, fmt.Errorf("id: %v, name: %s \n", item.Id, item.Name)
|
||||
}
|
||||
|
||||
var itemList []interface{}
|
||||
for _, item := range driveItems.DriveItems {
|
||||
itemList = append(itemList, item.Name)
|
||||
}
|
||||
return itemList, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) loadIDByPath(path string) (string, error) {
|
||||
pathItem := "root:" + path
|
||||
if path == "/" {
|
||||
pathItem = "root"
|
||||
}
|
||||
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/%s", pathItem), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new request for file id failed, err: %v", err)
|
||||
}
|
||||
var driveItem *odsdk.DriveItem
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
|
||||
return "", fmt.Errorf("do request for file id failed, err: %v", err)
|
||||
}
|
||||
return driveItem.Id, nil
|
||||
}
|
||||
|
||||
func refreshToken(oldToken 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)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
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)
|
||||
}
|
||||
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()
|
||||
|
||||
tokenMap := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
|
||||
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 accessToken, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) createFolder(parent string) error {
|
||||
if _, err := onedrive.loadIDByPath(path.Dir(parent)); err != nil {
|
||||
if !strings.Contains(err.Error(), "itemNotFound") {
|
||||
return err
|
||||
}
|
||||
_ = onedrive.createFolder(path.Dir(parent))
|
||||
}
|
||||
item2, err := onedrive.loadIDByPath(path.Dir(parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := onedrive.client.DriveItems.CreateNewFolder(context.Background(), "", item2, path.Base(parent)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NewUploadSessionCreationRequest struct {
|
||||
ConflictBehavior string `json:"@microsoft.graph.conflictBehavior,omitempty"`
|
||||
}
|
||||
type NewUploadSessionCreationResponse struct {
|
||||
UploadURL string `json:"uploadUrl"`
|
||||
ExpirationDateTime string `json:"expirationDateTime"`
|
||||
}
|
||||
type UploadSessionUploadResponse struct {
|
||||
ExpirationDateTime string `json:"expirationDateTime"`
|
||||
NextExpectedRanges []string `json:"nextExpectedRanges"`
|
||||
DriveItem
|
||||
}
|
||||
type DriveItem struct {
|
||||
Name string `json:"name"`
|
||||
Id string `json:"id"`
|
||||
DownloadURL string `json:"@microsoft.graph.downloadUrl"`
|
||||
Description string `json:"description"`
|
||||
Size int64 `json:"size"`
|
||||
WebURL string `json:"webUrl"`
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOffset, grandTotalSize int64, byteReader *bytes.Reader) (*http.Request, error) {
|
||||
apiUrl, err := onedrive.client.BaseURL.Parse(absoluteUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
absoluteUrl = apiUrl.String()
|
||||
contentLength := byteReader.Size()
|
||||
req, err := http.NewRequest("PUT", absoluteUrl, byteReader)
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
preliminaryLength := grandOffset
|
||||
preliminaryRange := grandOffset + contentLength - 1
|
||||
if preliminaryRange >= grandTotalSize {
|
||||
preliminaryRange = grandTotalSize - 1
|
||||
preliminaryLength = preliminaryRange - grandOffset + 1
|
||||
}
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", preliminaryLength, preliminaryRange, grandTotalSize))
|
||||
|
||||
return req, err
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type ossClient struct {
|
||||
scType string
|
||||
Vars map[string]interface{}
|
||||
client osssdk.Client
|
||||
}
|
||||
@@ -14,6 +15,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
|
||||
var endpoint string
|
||||
var accessKey string
|
||||
var secretKey string
|
||||
var scType string
|
||||
if _, ok := vars["endpoint"]; ok {
|
||||
endpoint = vars["endpoint"].(string)
|
||||
} else {
|
||||
@@ -24,6 +26,11 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
|
||||
} else {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
if _, ok := vars["scType"]; ok {
|
||||
scType = vars["scType"].(string)
|
||||
} else {
|
||||
scType = "Standard"
|
||||
}
|
||||
if _, ok := vars["secretKey"]; ok {
|
||||
secretKey = vars["secretKey"].(string)
|
||||
} else {
|
||||
@@ -34,6 +41,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &ossClient{
|
||||
scType: scType,
|
||||
Vars: vars,
|
||||
client: *client,
|
||||
}, nil
|
||||
@@ -77,7 +85,7 @@ func (oss ossClient) Upload(src, target string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""))
|
||||
err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""), osssdk.ObjectStorageClass(osssdk.StorageClassType(oss.scType)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@ import (
|
||||
)
|
||||
|
||||
type s3Client struct {
|
||||
Vars map[string]interface{}
|
||||
Sess session.Session
|
||||
scType string
|
||||
Vars map[string]interface{}
|
||||
Sess session.Session
|
||||
}
|
||||
|
||||
func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
var accessKey string
|
||||
var secretKey string
|
||||
var endpoint string
|
||||
var scType string
|
||||
var region string
|
||||
if _, ok := vars["accessKey"]; ok {
|
||||
accessKey = vars["accessKey"].(string)
|
||||
@@ -32,6 +34,11 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
} else {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
if _, ok := vars["scType"]; ok {
|
||||
scType = vars["scType"].(string)
|
||||
} else {
|
||||
scType = "Standard"
|
||||
}
|
||||
if _, ok := vars["endpoint"]; ok {
|
||||
endpoint = vars["endpoint"].(string)
|
||||
} else {
|
||||
@@ -53,8 +60,9 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &s3Client{
|
||||
Vars: vars,
|
||||
Sess: *sess,
|
||||
scType: scType,
|
||||
Vars: vars,
|
||||
Sess: *sess,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -126,9 +134,10 @@ func (s3C s3Client) Upload(src, target string) (bool, error) {
|
||||
|
||||
uploader := s3manager.NewUploader(&s3C.Sess)
|
||||
_, err = uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(target),
|
||||
Body: file,
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(target),
|
||||
Body: file,
|
||||
StorageClass: &s3C.scType,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -14,24 +14,23 @@ type CloudStorageClient interface {
|
||||
Download(src, target string) (bool, error)
|
||||
}
|
||||
|
||||
func NewCloudStorageClient(vars map[string]interface{}) (CloudStorageClient, error) {
|
||||
if vars["type"] == constant.S3 {
|
||||
func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
|
||||
switch backupType {
|
||||
case constant.S3:
|
||||
return client.NewS3Client(vars)
|
||||
}
|
||||
if vars["type"] == constant.OSS {
|
||||
case constant.OSS:
|
||||
return client.NewOssClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.Sftp {
|
||||
case constant.Sftp:
|
||||
return client.NewSftpClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.MinIo {
|
||||
case constant.MinIo:
|
||||
return client.NewMinIoClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.Cos {
|
||||
case constant.Cos:
|
||||
return client.NewCosClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.Kodo {
|
||||
case constant.Kodo:
|
||||
return client.NewKodoClient(vars)
|
||||
case constant.OneDrive:
|
||||
return client.NewOneDriveClient(vars)
|
||||
default:
|
||||
return nil, constant.ErrNotSupportType
|
||||
}
|
||||
return nil, constant.ErrNotSupportType
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
func Pull(filePath string) (string, error) {
|
||||
stdout, err := cmd.Execf("docker-compose -f %s pull", filePath)
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
func Up(filePath string) (string, error) {
|
||||
stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
|
||||
return stdout, err
|
||||
|
||||
@@ -89,6 +89,10 @@ func (s *ComposeService) ComposeBuild() error {
|
||||
return s.Build(context.Background(), s.project, api.BuildOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposePull() error {
|
||||
return s.Pull(context.Background(), s.project, api.PullOptions{})
|
||||
}
|
||||
|
||||
func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) {
|
||||
var configFiles []types.ConfigFile
|
||||
configFiles = append(configFiles, types.ConfigFile{
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@@ -15,6 +16,9 @@ import (
|
||||
)
|
||||
|
||||
func StringEncrypt(text string) (string, error) {
|
||||
if len(text) == 0 {
|
||||
return "", errors.New("it is not possible to encrypt an empty string.")
|
||||
}
|
||||
key := global.CONF.System.EncryptKey
|
||||
pass := []byte(text)
|
||||
xpass, err := aesEncryptWithSalt([]byte(key), pass)
|
||||
@@ -26,6 +30,9 @@ func StringEncrypt(text string) (string, error) {
|
||||
}
|
||||
|
||||
func StringDecrypt(text string) (string, error) {
|
||||
if len(text) == 0 {
|
||||
return "", errors.New("it is not possible to decrypt an empty string.")
|
||||
}
|
||||
key := global.CONF.System.EncryptKey
|
||||
bytesPass, err := base64.StdEncoding.DecodeString(text)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -114,6 +116,10 @@ func (f *Firewall) ListAddress() ([]FireInfo, error) {
|
||||
}
|
||||
|
||||
func (f *Firewall) Port(port FireInfo, operation string) error {
|
||||
if cmd.CheckIllegal(operation, port.Protocol, port.Port) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s port failed, err: %s", operation, stdout)
|
||||
@@ -122,6 +128,9 @@ func (f *Firewall) Port(port FireInfo, operation string) error {
|
||||
}
|
||||
|
||||
func (f *Firewall) RichRules(rule FireInfo, operation string) error {
|
||||
if cmd.CheckIllegal(operation, rule.Address, rule.Protocol, rule.Port, rule.Strategy) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
ruleStr := ""
|
||||
if strings.Contains(rule.Address, "-") {
|
||||
std, err := cmd.Execf("firewall-cmd --permanent --new-ipset=%s --type=hash:ip", rule.Address)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -131,6 +133,9 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
|
||||
default:
|
||||
return fmt.Errorf("unsupport strategy %s", port.Strategy)
|
||||
}
|
||||
if cmd.CheckIllegal(port.Protocol, port.Port) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("%s %s %s", f.CmdStr, port.Strategy, port.Port)
|
||||
if operation == "remove" {
|
||||
@@ -156,6 +161,10 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
|
||||
return fmt.Errorf("unsupport strategy %s", rule.Strategy)
|
||||
}
|
||||
|
||||
if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
ruleStr := fmt.Sprintf("%s %s ", f.CmdStr, rule.Strategy)
|
||||
if operation == "remove" {
|
||||
ruleStr = fmt.Sprintf("%s delete %s ", f.CmdStr, rule.Strategy)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
@@ -17,10 +18,10 @@ type Otp struct {
|
||||
QrImage string `json:"qrImage"`
|
||||
}
|
||||
|
||||
func GetOtp(username string) (otp Otp, err error) {
|
||||
func GetOtp(username string, interval int) (otp Otp, err error) {
|
||||
secret := gotp.RandomSecret(secretLength)
|
||||
otp.Secret = secret
|
||||
totp := gotp.NewDefaultTOTP(secret)
|
||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||
uri := totp.ProvisioningUri(username, "1Panel")
|
||||
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
|
||||
dist := make([]byte, 3000)
|
||||
@@ -31,8 +32,13 @@ func GetOtp(username string) (otp Otp, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ValidCode(code string, secret string) bool {
|
||||
totp := gotp.NewDefaultTOTP(secret)
|
||||
func ValidCode(code, intervalStr, secret string) bool {
|
||||
interval, err := strconv.Atoi(intervalStr)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("type conversion failed, err: %v", err)
|
||||
return false
|
||||
}
|
||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||
now := time.Now().Unix()
|
||||
strInt64 := strconv.FormatInt(now, 10)
|
||||
id16, _ := strconv.Atoi(strInt64)
|
||||
|
||||
@@ -127,13 +127,27 @@ func (s *Server) RemoveDirective(key string, params []string) {
|
||||
var newDirectives []IDirective
|
||||
for _, dir := range directives {
|
||||
if dir.GetName() == key {
|
||||
if len(params) > 0 {
|
||||
oldParams := dir.GetParameters()
|
||||
if len(params) == 0 {
|
||||
continue
|
||||
}
|
||||
oldParams := dir.GetParameters()
|
||||
if key == "location" {
|
||||
if len(params) == len(oldParams) {
|
||||
exist := true
|
||||
for i := range params {
|
||||
if params[i] != oldParams[i] {
|
||||
exist = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if oldParams[0] == params[0] {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
newDirectives = append(newDirectives, dir)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPs(t *testing.T) {
|
||||
processes, err := process.Processes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, pro := range processes {
|
||||
var (
|
||||
name string
|
||||
parentID int32
|
||||
userName string
|
||||
status string
|
||||
startTime string
|
||||
numThreads int32
|
||||
numConnections int
|
||||
cpuPercent float64
|
||||
//mem string
|
||||
rss string
|
||||
ioRead string
|
||||
ioWrite string
|
||||
)
|
||||
name, _ = pro.Name()
|
||||
parentID, _ = pro.Ppid()
|
||||
userName, _ = pro.Username()
|
||||
array, err := pro.Status()
|
||||
if err == nil {
|
||||
status = array[0]
|
||||
}
|
||||
createTime, err := pro.CreateTime()
|
||||
if err == nil {
|
||||
t := time.Unix(createTime/1000, 0)
|
||||
startTime = t.Format("2006-1-2 15:04:05")
|
||||
}
|
||||
numThreads, _ = pro.NumThreads()
|
||||
connections, err := pro.Connections()
|
||||
if err == nil && len(connections) > 0 {
|
||||
numConnections = len(connections)
|
||||
}
|
||||
cpuPercent, _ = pro.CPUPercent()
|
||||
menInfo, err := pro.MemoryInfo()
|
||||
if err == nil {
|
||||
rssF := float64(menInfo.RSS) / 1048576
|
||||
rss = fmt.Sprintf("%.2f", rssF)
|
||||
}
|
||||
ioStat, err := pro.IOCounters()
|
||||
if err == nil {
|
||||
ioWrite = strconv.FormatUint(ioStat.WriteBytes, 10)
|
||||
ioRead = strconv.FormatUint(ioStat.ReadBytes, 10)
|
||||
}
|
||||
|
||||
cmdLine, err := pro.Cmdline()
|
||||
if err == nil {
|
||||
fmt.Println(cmdLine)
|
||||
}
|
||||
ss, err := pro.Terminal()
|
||||
if err == nil {
|
||||
fmt.Println(ss)
|
||||
}
|
||||
|
||||
fmt.Println(fmt.Sprintf("Name: %s PId: %v ParentID: %v Username: %v status:%s startTime: %s numThreads: %v numConnections:%v cpuPercent:%v rss:%s MB IORead: %s IOWrite: %s",
|
||||
name, pro.Pid, parentID, userName, status, startTime, numThreads, numConnections, cpuPercent, rss, ioRead, ioWrite))
|
||||
}
|
||||
users, err := host.Users()
|
||||
if err == nil {
|
||||
fmt.Println(users)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WsMsg struct {
|
||||
Type string
|
||||
Keys []string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
ID string
|
||||
Socket *websocket.Conn
|
||||
@@ -35,9 +27,7 @@ func (c *Client) Read() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msg := &WsMsg{}
|
||||
_ = json.Unmarshal(message, msg)
|
||||
ProcessData(c, msg)
|
||||
ProcessData(c, message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,21 +43,3 @@ func (c *Client) Write() {
|
||||
_ = c.Socket.WriteMessage(websocket.TextMessage, message)
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessData(c *Client, msg *WsMsg) {
|
||||
if msg.Type == "wget" {
|
||||
var res []files.Process
|
||||
for _, k := range msg.Keys {
|
||||
value, err := global.CACHE.Get(k)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get cache error,err %s", err.Error())
|
||||
return
|
||||
}
|
||||
process := &files.Process{}
|
||||
_ = json.Unmarshal(value, process)
|
||||
res = append(res, *process)
|
||||
}
|
||||
reByte, _ := json.Marshal(res)
|
||||
c.Msg <- reByte
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package websocket
|
||||
|
||||
import "sync"
|
||||
|
||||
type Manager struct {
|
||||
Group map[string]*Client
|
||||
Lock sync.Mutex
|
||||
Register, UnRegister chan *Client
|
||||
ClientCount uint
|
||||
}
|
||||
|
||||
func (m *Manager) Start() {
|
||||
for {
|
||||
select {
|
||||
case client := <-m.Register:
|
||||
m.Lock.Lock()
|
||||
m.Group[client.ID] = client
|
||||
m.ClientCount++
|
||||
m.Lock.Unlock()
|
||||
case client := <-m.UnRegister:
|
||||
m.Lock.Lock()
|
||||
if _, ok := m.Group[client.ID]; ok {
|
||||
close(client.Msg)
|
||||
delete(m.Group, client.ID)
|
||||
m.ClientCount--
|
||||
}
|
||||
m.Lock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) RegisterClient(client *Client) {
|
||||
m.Register <- client
|
||||
}
|
||||
|
||||
func (m *Manager) UnRegisterClient(client *Client) {
|
||||
m.UnRegister <- client
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WsInput struct {
|
||||
Type string `json:"type"`
|
||||
DownloadProgress
|
||||
PsProcessConfig
|
||||
SSHSessionConfig
|
||||
NetConfig
|
||||
}
|
||||
|
||||
type DownloadProgress struct {
|
||||
Keys []string `json:"keys"`
|
||||
}
|
||||
|
||||
type PsProcessConfig struct {
|
||||
Pid int32 `json:"pid"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type SSHSessionConfig struct {
|
||||
LoginUser string `json:"loginUser"`
|
||||
LoginIP string `json:"loginIP"`
|
||||
}
|
||||
|
||||
type NetConfig struct {
|
||||
Port uint32 `json:"port"`
|
||||
ProcessName string `json:"processName"`
|
||||
ProcessID int32 `json:"processID"`
|
||||
}
|
||||
|
||||
type PsProcessData struct {
|
||||
PID int32 `json:"PID"`
|
||||
Name string `json:"name"`
|
||||
PPID int32 `json:"PPID"`
|
||||
Username string `json:"username"`
|
||||
Status string `json:"status"`
|
||||
StartTime string `json:"startTime"`
|
||||
NumThreads int32 `json:"numThreads"`
|
||||
NumConnections int `json:"numConnections"`
|
||||
CpuPercent string `json:"cpuPercent"`
|
||||
|
||||
DiskRead string `json:"diskRead"`
|
||||
DiskWrite string `json:"diskWrite"`
|
||||
CmdLine string `json:"cmdLine"`
|
||||
|
||||
Rss string `json:"rss"`
|
||||
VMS string `json:"vms"`
|
||||
HWM string `json:"hwm"`
|
||||
Data string `json:"data"`
|
||||
Stack string `json:"stack"`
|
||||
Locked string `json:"locked"`
|
||||
Swap string `json:"swap"`
|
||||
|
||||
CpuValue float64 `json:"cpuValue"`
|
||||
RssValue uint64 `json:"rssValue"`
|
||||
|
||||
Envs []string `json:"envs"`
|
||||
|
||||
OpenFiles []process.OpenFilesStat `json:"openFiles"`
|
||||
Connects []processConnect `json:"connects"`
|
||||
}
|
||||
|
||||
type processConnect struct {
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Laddr net.Addr `json:"localaddr"`
|
||||
Raddr net.Addr `json:"remoteaddr"`
|
||||
PID int32 `json:"PID"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ProcessConnects []processConnect
|
||||
|
||||
func (p ProcessConnects) Len() int {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func (p ProcessConnects) Less(i, j int) bool {
|
||||
return p[i].PID < p[j].PID
|
||||
}
|
||||
|
||||
func (p ProcessConnects) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
type sshSession struct {
|
||||
Username string `json:"username"`
|
||||
PID int32 `json:"PID"`
|
||||
Terminal string `json:"terminal"`
|
||||
Host string `json:"host"`
|
||||
LoginTime string `json:"loginTime"`
|
||||
}
|
||||
|
||||
func ProcessData(c *Client, inputMsg []byte) {
|
||||
wsInput := &WsInput{}
|
||||
err := json.Unmarshal(inputMsg, wsInput)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("unmarshal wsInput error,err %s", err.Error())
|
||||
return
|
||||
}
|
||||
switch wsInput.Type {
|
||||
case "wget":
|
||||
res, err := getDownloadProcess(wsInput.DownloadProgress)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Msg <- res
|
||||
case "ps":
|
||||
res, err := getProcessData(wsInput.PsProcessConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Msg <- res
|
||||
case "ssh":
|
||||
res, err := getSSHSessions(wsInput.SSHSessionConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Msg <- res
|
||||
case "net":
|
||||
res, err := getNetConnections(wsInput.NetConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Msg <- res
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getDownloadProcess(progress DownloadProgress) (res []byte, err error) {
|
||||
var result []files.Process
|
||||
for _, k := range progress.Keys {
|
||||
value, err := global.CACHE.Get(k)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get cache error,err %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
downloadProcess := &files.Process{}
|
||||
_ = json.Unmarshal(value, downloadProcess)
|
||||
result = append(result, *downloadProcess)
|
||||
}
|
||||
res, err = json.Marshal(result)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
b = uint64(1)
|
||||
kb = 1024 * b
|
||||
mb = 1024 * kb
|
||||
gb = 1024 * mb
|
||||
)
|
||||
|
||||
func formatBytes(bytes uint64) string {
|
||||
switch {
|
||||
case bytes < kb:
|
||||
return fmt.Sprintf("%dB", bytes)
|
||||
case bytes < mb:
|
||||
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
|
||||
case bytes < gb:
|
||||
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
|
||||
default:
|
||||
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
|
||||
}
|
||||
}
|
||||
|
||||
func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
|
||||
var processes []*process.Process
|
||||
processes, err = process.Processes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
result []PsProcessData
|
||||
resultMutex sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
numWorkers = 4
|
||||
)
|
||||
|
||||
handleData := func(proc *process.Process) {
|
||||
procData := PsProcessData{
|
||||
PID: proc.Pid,
|
||||
}
|
||||
if processConfig.Pid > 0 && processConfig.Pid != proc.Pid {
|
||||
return
|
||||
}
|
||||
if procName, err := proc.Name(); err == nil {
|
||||
procData.Name = procName
|
||||
} else {
|
||||
procData.Name = "<UNKNOWN>"
|
||||
}
|
||||
if processConfig.Name != "" && !strings.Contains(procData.Name, processConfig.Name) {
|
||||
return
|
||||
}
|
||||
if username, err := proc.Username(); err == nil {
|
||||
procData.Username = username
|
||||
}
|
||||
if processConfig.Username != "" && !strings.Contains(procData.Username, processConfig.Username) {
|
||||
return
|
||||
}
|
||||
procData.PPID, _ = proc.Ppid()
|
||||
statusArray, _ := proc.Status()
|
||||
if len(statusArray) > 0 {
|
||||
procData.Status = strings.Join(statusArray, ",")
|
||||
}
|
||||
createTime, procErr := proc.CreateTime()
|
||||
if procErr == nil {
|
||||
t := time.Unix(createTime/1000, 0)
|
||||
procData.StartTime = t.Format("2006-1-2 15:04:05")
|
||||
}
|
||||
procData.NumThreads, _ = proc.NumThreads()
|
||||
connections, procErr := proc.Connections()
|
||||
if procErr == nil {
|
||||
procData.NumConnections = len(connections)
|
||||
for _, conn := range connections {
|
||||
if conn.Laddr.IP != "" || conn.Raddr.IP != "" {
|
||||
procData.Connects = append(procData.Connects, processConnect{
|
||||
Status: conn.Status,
|
||||
Laddr: conn.Laddr,
|
||||
Raddr: conn.Raddr,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
procData.CpuValue, _ = proc.CPUPercent()
|
||||
procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
|
||||
menInfo, procErr := proc.MemoryInfo()
|
||||
if procErr == nil {
|
||||
procData.Rss = formatBytes(menInfo.RSS)
|
||||
procData.RssValue = menInfo.RSS
|
||||
procData.Data = formatBytes(menInfo.Data)
|
||||
procData.VMS = formatBytes(menInfo.VMS)
|
||||
procData.HWM = formatBytes(menInfo.HWM)
|
||||
procData.Stack = formatBytes(menInfo.Stack)
|
||||
procData.Locked = formatBytes(menInfo.Locked)
|
||||
procData.Swap = formatBytes(menInfo.Swap)
|
||||
} else {
|
||||
procData.Rss = "--"
|
||||
procData.Data = "--"
|
||||
procData.VMS = "--"
|
||||
procData.HWM = "--"
|
||||
procData.Stack = "--"
|
||||
procData.Locked = "--"
|
||||
procData.Swap = "--"
|
||||
|
||||
procData.RssValue = 0
|
||||
}
|
||||
ioStat, procErr := proc.IOCounters()
|
||||
if procErr == nil {
|
||||
procData.DiskWrite = formatBytes(ioStat.WriteBytes)
|
||||
procData.DiskRead = formatBytes(ioStat.ReadBytes)
|
||||
} else {
|
||||
procData.DiskWrite = "--"
|
||||
procData.DiskRead = "--"
|
||||
}
|
||||
procData.CmdLine, _ = proc.Cmdline()
|
||||
procData.OpenFiles, _ = proc.OpenFiles()
|
||||
procData.Envs, _ = proc.Environ()
|
||||
|
||||
resultMutex.Lock()
|
||||
result = append(result, procData)
|
||||
resultMutex.Unlock()
|
||||
}
|
||||
|
||||
chunkSize := (len(processes) + numWorkers - 1) / numWorkers
|
||||
for i := 0; i < numWorkers; i++ {
|
||||
wg.Add(1)
|
||||
start := i * chunkSize
|
||||
end := (i + 1) * chunkSize
|
||||
if end > len(processes) {
|
||||
end = len(processes)
|
||||
}
|
||||
|
||||
go func(start, end int) {
|
||||
defer wg.Done()
|
||||
for j := start; j < end; j++ {
|
||||
handleData(processes[j])
|
||||
}
|
||||
}(start, end)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].PID < result[j].PID
|
||||
})
|
||||
res, err = json.Marshal(result)
|
||||
return
|
||||
}
|
||||
|
||||
func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
|
||||
var (
|
||||
result []sshSession
|
||||
users []host.UserStat
|
||||
processes []*process.Process
|
||||
)
|
||||
processes, err = process.Processes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
users, err = host.Users()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, proc := range processes {
|
||||
name, _ := proc.Name()
|
||||
if name != "sshd" || proc.Pid == 0 {
|
||||
continue
|
||||
}
|
||||
connections, _ := proc.Connections()
|
||||
for _, conn := range connections {
|
||||
for _, user := range users {
|
||||
if user.Host == "" {
|
||||
continue
|
||||
}
|
||||
if conn.Raddr.IP == user.Host {
|
||||
if config.LoginUser != "" && !strings.Contains(user.User, config.LoginUser) {
|
||||
continue
|
||||
}
|
||||
if config.LoginIP != "" && !strings.Contains(user.Host, config.LoginIP) {
|
||||
continue
|
||||
}
|
||||
if terminal, err := proc.Cmdline(); err == nil {
|
||||
if strings.Contains(terminal, user.Terminal) {
|
||||
session := sshSession{
|
||||
Username: user.User,
|
||||
Host: user.Host,
|
||||
Terminal: user.Terminal,
|
||||
PID: proc.Pid,
|
||||
}
|
||||
t := time.Unix(int64(user.Started), 0)
|
||||
session.LoginTime = t.Format("2006-1-2 15:04:05")
|
||||
result = append(result, session)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res, err = json.Marshal(result)
|
||||
return
|
||||
}
|
||||
|
||||
var netTypes = [...]string{"tcp", "udp"}
|
||||
|
||||
func getNetConnections(config NetConfig) (res []byte, err error) {
|
||||
var (
|
||||
result []processConnect
|
||||
proc *process.Process
|
||||
)
|
||||
for _, netType := range netTypes {
|
||||
connections, _ := net.Connections(netType)
|
||||
if err == nil {
|
||||
for _, conn := range connections {
|
||||
if config.ProcessID > 0 && config.ProcessID != conn.Pid {
|
||||
continue
|
||||
}
|
||||
proc, err = process.NewProcess(conn.Pid)
|
||||
if err == nil {
|
||||
name, _ := proc.Name()
|
||||
if name != "" && config.ProcessName != "" && !strings.Contains(name, config.ProcessName) {
|
||||
continue
|
||||
}
|
||||
if config.Port > 0 && config.Port != conn.Laddr.Port && config.Port != conn.Raddr.Port {
|
||||
continue
|
||||
}
|
||||
result = append(result, processConnect{
|
||||
Type: netType,
|
||||
Status: conn.Status,
|
||||
Laddr: conn.Laddr,
|
||||
Raddr: conn.Raddr,
|
||||
PID: conn.Pid,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
res, err = json.Marshal(result)
|
||||
return
|
||||
}
|
||||
@@ -25,11 +25,11 @@ var userinfoCmd = &cobra.Command{
|
||||
port := getSettingByKey(db, "ServerPort")
|
||||
ssl := getSettingByKey(db, "SSL")
|
||||
entrance := getSettingByKey(db, "SecurityEntrance")
|
||||
enptrySetting := getSettingByKey(db, "EncryptKey")
|
||||
encryptSetting := getSettingByKey(db, "EncryptKey")
|
||||
|
||||
p := ""
|
||||
if len(enptrySetting) == 16 {
|
||||
global.CONF.System.EncryptKey = enptrySetting
|
||||
if len(encryptSetting) == 16 {
|
||||
global.CONF.System.EncryptKey = encryptSetting
|
||||
p, _ = encrypt.StringDecrypt(password)
|
||||
} else {
|
||||
p = password
|
||||
|
||||
+1048
-416
File diff suppressed because it is too large
Load Diff
+1035
-358
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user