Merge branch 'dev'

This commit is contained in:
wanghe-fit2cloud 2023-04-20 23:23:09 +08:00
commit ce3d4b16d7
348 changed files with 16583 additions and 5327 deletions

View File

@ -1,17 +0,0 @@
on:
push:
branches:
- 'pr@**'
- 'repr@**'
name: 针对特定分支名自动创建 PR
jobs:
generic_handler:
name: 自动创建 PR
runs-on: ubuntu-latest
steps:
- name: Create pull request
uses: jumpserver/action-generic-handler@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }}

View File

@ -1,16 +0,0 @@
name: Issue Close Check
on:
issues:
types: [closed]
jobs:
issue-close-remove-labels:
runs-on: ubuntu-latest
steps:
- name: Remove labels
uses: actions-cool/issues-helper@v2
if: ${{ !github.event.issue.pull_request }}
with:
actions: 'remove-labels'
labels: '状态:待处理'

View File

@ -1,38 +0,0 @@
on:
issue_comment:
types: [created]
name: Add issues workflow labels
jobs:
add-label-if-is-author:
runs-on: ubuntu-latest
if: ${{ (github.event.issue.user.id == github.event.comment.user.id) && (!github.event.issue.pull_request) }}
steps:
- name: Add require handle label
uses: actions-cool/issues-helper@v2
with:
actions: 'add-labels'
labels: '状态:待处理'
- name: Remove require reply label
uses: actions-cool/issues-helper@v2
with:
actions: 'remove-labels'
labels: '状态:待用户反馈'
add-label-if-not-author:
runs-on: ubuntu-latest
if: ${{ (github.event.issue.user.id != github.event.comment.user.id) && (!github.event.issue.pull_request) && (github.event.issue.state == 'open') }}
steps:
- name: Add require replay label
uses: actions-cool/issues-helper@v2
with:
actions: 'add-labels'
labels: '状态:待用户反馈'
- name: Remove require handle label
uses: actions-cool/issues-helper@v2
with:
actions: 'remove-labels'
labels: '状态:待处理'

View File

@ -1,16 +0,0 @@
name: Issue Open Check
on:
issues:
types: [opened]
jobs:
issue-open-add-labels:
runs-on: ubuntu-latest
steps:
- name: Add labels
uses: actions-cool/issues-helper@v2
if: ${{ !github.event.issue.pull_request }}
with:
actions: 'add-labels'
labels: '状态:待处理'

16
.github/workflows/sync2gitee.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: sync2gitee
on: [push]
jobs:
repo-sync:
runs-on: ubuntu-latest
steps:
- name: Mirror the Github organization repos to Gitee.
uses: Yikun/hub-mirror-action@master
with:
src: 'github/1Panel-dev'
dst: 'gitee/fit2cloud-xlab'
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
static_list: "1Panel"
force_update: true

View File

@ -11,15 +11,19 @@ SERVER_PATH=$(BASE_PAH)/backend
MAIN= $(BASE_PAH)/cmd/server/main.go
APP_NAME=1panel
build_web:
build_frontend:
cd $(WEB_PATH) && npm install && npm run build:dev
build_bin:
build_backend_on_linux:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags 'osusergo,netgo' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_linux_on_mac:
build_backend_on_darwin:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_web build_bin
build_backend_on_archlinux:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_frontend build_backend_on_linux

View File

@ -1,3 +1,4 @@
[README_EN.md](README_EN.md)
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>现代化、开源的 Linux 服务器运维管理面板</b></p>
<p align="center">
@ -18,7 +19,7 @@
## UI 展示
![UI展示](https://1panel.oss-cn-hangzhou.aliyuncs.com/img/overview.png)
![UI展示](https://resource.fit2cloud.com/1panel/img/overview.png)
## 快速开始
@ -47,7 +48,7 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
**微信交流群**
<img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/wechat-group.jpg" width="156" height="156"/>
<img src="https://1panel.cn/img/wechat-group.jpg" width="156" height="156"/>
## 安全说明
@ -62,7 +63,7 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
## License
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

72
README_EN.md Normal file
View File

@ -0,0 +1,72 @@
[中文 README.md](README.md)
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>Modern and Open-Source Linux Server Operation and Management Panel</b></p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
------------------------------
1Panel is a modern and Open-Source linux server operation and management panel, the functions and advantages of 1Panel include:
- **Quick website building**: Deeply integrated with Wordpress and [Halo](https://github.com/halo-dev/halo/), with one-click solutions for domain name binding, SSL certificate configuration, and more;
- **Efficient management**: Easily manage Linux servers through the web interface, including application management, host monitoring, file management, database management, container management, and more;
- **Secure and reliable**: Minimal vulnerability exposure, with firewall and security audit functions provided;
- **One-click backup**: Support for one-click backup and restore, with backup data stored in the cloud and never lost.
## UI Display
![UI Display](https://resource.fit2cloud.com/1panel/img/overview_en.png)
## Quick Start
**Online Demo**
- Address: <https://demo.1panel.cn/>
- Username: demo
- Password: 1panel
**One-Click Installation**
Execute the following command to install 1Panel with one click:
```sh
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
```
**Learning Materials**
- [Online Documentation](https://1panel.cn/docs/)
- [Teaching Videos](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760)
## Community
If you have any questions or suggestions, please submit a GitHub Issue or join our WeChat group for communication.
**WeChat Group**
<img src="https://1panel.cn/img/wechat-group.jpg" width="156" height="156"/>
## Security Information
If you discover any security issues, please contact us through:
- Email: support@fit2cloud.com
- Phone: 400-052-0755
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date)
## License
Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
<https://www.gnu.org/licenses/gpl-3.0.html>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

19
SECURITY.md Normal file
View File

@ -0,0 +1,19 @@
# 安全说明
如果您发现安全问题,请直接联系我们:
- wanghe@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
感谢您的支持!
# Security Policy
All security bugs should be reported to the contact as below:
- wanghe@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
Thanks for your support!

View File

@ -38,8 +38,9 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
// @Router /apps/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"}
func (b *BaseApi) SyncApp(c *gin.Context) {
appService.SyncAppListFromLocal()
global.LOG.Infof("sync app list start ...")
if err := appService.SyncAppList(); err != nil {
if err := appService.SyncAppListFromRemote(); err != nil {
global.LOG.Errorf("sync app list error [%s]", err.Error())
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@ -71,14 +72,15 @@ func (b *BaseApi) GetApp(c *gin.Context) {
}
// @Tags App
// @Summary Search app detail by id
// @Description 通过 id 获取应用详情
// @Summary Search app detail by appid
// @Description 通过 appid 获取应用详情
// @Accept json
// @Param appId path integer true "app id"
// @Param version path string true "app 版本"
// @Param version path string true "app 类型"
// @Success 200 {object} response.AppDetailDTO
// @Security ApiKeyAuth
// @Router /apps/detail/:appId/:version [get]
// @Router /apps/detail/:appId/:version/:type [get]
func (b *BaseApi) GetAppDetail(c *gin.Context) {
appId, err := helper.GetIntParamByKey(c, "appId")
if err != nil {
@ -86,7 +88,30 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
return
}
version := c.Param("version")
appDetailDTO, err := appService.GetAppDetail(appId, version)
appType := c.Param("type")
appDetailDTO, err := appService.GetAppDetail(appId, version, appType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, appDetailDTO)
}
// @Tags App
// @Summary Get app detail by id
// @Description 通过 id 获取应用详情
// @Accept json
// @Param appId path integer true "id"
// @Success 200 {object} response.AppDetailDTO
// @Security ApiKeyAuth
// @Router /apps/details/:id [get]
func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
appDetailID, err := helper.GetIntParamByKey(c, "id")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
appDetailDTO, err := appService.GetAppDetailByID(appDetailID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

View File

@ -93,24 +93,24 @@ func (b *BaseApi) LoadPort(c *gin.Context) {
// @Tags App
// @Summary Search app password by key
// @Description 获取应用密码
// @Description 获取应用连接信息
// @Accept json
// @Param key path string true "request"
// @Success 200 {string} password
// @Success 200 {string} response.DatabaseConn
// @Security ApiKeyAuth
// @Router /apps/installed/loadpassword/:key [get]
func (b *BaseApi) LoadPassword(c *gin.Context) {
// @Router /apps/installed/conninfo/:key [get]
func (b *BaseApi) LoadConnInfo(c *gin.Context) {
key, ok := c.Params.Get("key")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))
return
}
password, err := appInstallService.LoadPassword(key)
conn, err := appInstallService.LoadConnInfo(key)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, password)
helper.SuccessWithData(c, conn)
}
// @Tags App
@ -144,7 +144,7 @@ func (b *BaseApi) DeleteCheck(c *gin.Context) {
// @Router /apps/installed/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"同步已安装应用列表","formatEN":"Sync the list of installed apps"}
func (b *BaseApi) SyncInstalled(c *gin.Context) {
if err := appInstallService.SyncAll(); err != nil {
if err := appInstallService.SyncAll(false); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}

View File

@ -28,9 +28,11 @@ func (b *BaseApi) Login(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
if req.AuthMethod != "jwt" {
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
}
user, err := authService.Login(c, req)

View File

@ -104,9 +104,9 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"backup_accounts","output_colume":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":true,"db":"backup_accounts","output_colume":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
func (b *BaseApi) DeleteBackup(c *gin.Context) {
var req dto.BatchDeleteReq
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -116,7 +116,7 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) {
return
}
if err := backupService.BatchDelete(req.Ids); err != nil {
if err := backupService.Delete(req.ID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@ -175,7 +175,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.File(filePath)
helper.SuccessWithData(c, filePath)
}
// @Tags Backup Account

View File

@ -70,6 +70,34 @@ func (b *BaseApi) SearchCompose(c *gin.Context) {
})
}
// @Tags Container Compose
// @Summary Test compose
// @Description 测试 compose 是否可用
// @Accept json
// @Param request body dto.ComposeCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/compose/test [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"检测 compose [name] 格式","formatEN":"check compose [name]"}
func (b *BaseApi) TestCompose(c *gin.Context) {
var req dto.ComposeCreate
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
}
isOK, err := containerService.TestCompose(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, isOK)
}
// @Tags Container Compose
// @Summary Create compose
// @Description 创建容器编排
@ -90,11 +118,12 @@ func (b *BaseApi) CreateCompose(c *gin.Context) {
return
}
if err := containerService.CreateCompose(req); err != nil {
log, err := containerService.CreateCompose(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
helper.SuccessWithData(c, log)
}
// @Tags Container Compose

View File

@ -92,17 +92,41 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
})
}
// @Tags Cronjob
// @Summary Clean job records
// @Description 清空计划任务记录
// @Accept json
// @Param request body dto.CronjobClean true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/records/clean [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"清空计划任务记录 [name]","formatEN":"clean cronjob [name] records"}
func (b *BaseApi) CleanRecord(c *gin.Context) {
var req dto.CronjobClean
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := cronjobService.CleanRecord(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Cronjob
// @Summary Delete cronjob
// @Description 删除计划任务
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Param request body dto.CronjobBatchDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"cronjobs","output_colume":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"}
func (b *BaseApi) DeleteCronjob(c *gin.Context) {
var req dto.BatchDeleteReq
var req dto.CronjobBatchDelete
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -112,7 +136,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) {
return
}
if err := cronjobService.Delete(req.Ids); err != nil {
if err := cronjobService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@ -198,7 +222,7 @@ func (b *BaseApi) TargetDownload(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.File(filePath)
helper.SuccessWithData(c, filePath)
}
// @Tags Cronjob

View File

@ -1,7 +1,6 @@
package v1
import (
"io/ioutil"
"os"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
@ -35,7 +34,7 @@ func (b *BaseApi) LoadDaemonJsonFile(c *gin.Context) {
helper.SuccessWithData(c, "daemon.json is not find in path")
return
}
content, err := ioutil.ReadFile(constant.DaemonJsonPath)
content, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

View File

@ -9,41 +9,43 @@ type ApiGroup struct {
var ApiGroupApp = new(ApiGroup)
var (
authService = service.ServiceGroupApp.AuthService
dashboardService = service.ServiceGroupApp.DashboardService
authService = service.NewIAuthService()
dashboardService = service.NewIDashboardService()
appService = service.NewIAppService()
appInstallService = service.ServiceGroupApp.AppInstallService
appInstallService = service.NewIAppInstalledService()
containerService = service.ServiceGroupApp.ContainerService
composeTemplateService = service.ServiceGroupApp.ComposeTemplateService
imageRepoService = service.ServiceGroupApp.ImageRepoService
imageService = service.ServiceGroupApp.ImageService
dockerService = service.ServiceGroupApp.DockerService
containerService = service.NewIContainerService()
composeTemplateService = service.NewIComposeTemplateService()
imageRepoService = service.NewIImageRepoService()
imageService = service.NewIImageService()
dockerService = service.NewIDockerService()
mysqlService = service.ServiceGroupApp.MysqlService
redisService = service.ServiceGroupApp.RedisService
mysqlService = service.NewIMysqlService()
redisService = service.NewIRedisService()
cronjobService = service.ServiceGroupApp.CronjobService
cronjobService = service.NewICronjobService()
hostService = service.ServiceGroupApp.HostService
groupService = service.ServiceGroupApp.GroupService
fileService = service.ServiceGroupApp.FileService
hostService = service.NewIHostService()
groupService = service.NewIGroupService()
fileService = service.NewIFileService()
firewallService = service.NewIFirewallService()
settingService = service.ServiceGroupApp.SettingService
backupService = service.ServiceGroupApp.BackupService
settingService = service.NewISettingService()
backupService = service.NewIBackupService()
commandService = service.ServiceGroupApp.CommandService
commandService = service.NewICommandService()
websiteGroupService = service.ServiceGroupApp.WebsiteGroupService
websiteService = service.ServiceGroupApp.WebsiteService
websiteDnsAccountService = service.ServiceGroupApp.WebsiteDnsAccountService
websiteSSLService = service.ServiceGroupApp.WebsiteSSLService
websiteAcmeAccountService = service.ServiceGroupApp.WebsiteAcmeAccountService
websiteService = service.NewIWebsiteService()
websiteDnsAccountService = service.NewIWebsiteDnsAccountService()
websiteSSLService = service.NewIWebsiteSSLService()
websiteAcmeAccountService = service.NewIWebsiteAcmeAccountService()
nginxService = service.ServiceGroupApp.NginxService
nginxService = service.NewINginxService()
logService = service.ServiceGroupApp.LogService
snapshotService = service.ServiceGroupApp.SnapshotService
upgradeService = service.ServiceGroupApp.UpgradeService
logService = service.NewILogService()
snapshotService = service.NewISnapshotService()
upgradeService = service.NewIUpgradeService()
runtimeService = service.NewRuntimeService()
)

View File

@ -3,7 +3,7 @@ package v1
import (
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"path"
@ -445,6 +445,28 @@ func (b *BaseApi) Download(c *gin.Context) {
c.File(filePath)
}
// @Tags File
// @Summary Download file with path
// @Description 下载指定文件
// @Accept json
// @Param request body dto.FilePath true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/download/bypath [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [path]","formatEN":"Download file [path]"}
func (b *BaseApi) DownloadFile(c *gin.Context) {
var req dto.FilePath
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
}
c.File(req.Path)
}
// @Tags File
// @Summary Load file size
// @Description 获取文件夹大小
@ -488,7 +510,7 @@ func (b *BaseApi) LoadFromFile(c *gin.Context) {
return
}
content, err := ioutil.ReadFile(req.Path)
content, err := os.ReadFile(req.Path)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@ -497,6 +519,12 @@ func (b *BaseApi) LoadFromFile(c *gin.Context) {
}
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
if _, err := os.Stat(path.Dir(dstDir)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(dstDir), os.ModePerm); err != nil {
return err
}
}
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
if err != nil {
return err
@ -505,7 +533,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int)
for i := 0; i < chunkCount; i++ {
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i))
chunkData, err := ioutil.ReadFile(chunkPath)
chunkData, err := os.ReadFile(chunkPath)
if err != nil {
return err
}
@ -525,7 +553,6 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int)
// @Success 200
// @Security ApiKeyAuth
// @Router /files/chunkupload [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"上传文件 [path]","formatEN":"Upload file [path]"}
func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
fileForm, err := c.FormFile("chunk")
if err != nil {
@ -551,13 +578,16 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
}
fileOp := files.NewFileOp()
if err := fileOp.CreateDir("uploads", 0755); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
tmpDir := path.Join(global.CONF.System.TmpDir, "upload")
if !fileOp.Stat(tmpDir) {
if err := fileOp.CreateDir(tmpDir, 0755); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
}
//fileID := uuid.New().String()
filename := c.PostForm("filename")
fileDir := filepath.Join(global.CONF.System.DataDir, "upload", filename)
fileDir := filepath.Join(tmpDir, filename)
_ = os.MkdirAll(fileDir, 0755)
filePath := filepath.Join(fileDir, filename)
@ -567,25 +597,25 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
emptyFile.Close()
defer emptyFile.Close()
chunkData, err := ioutil.ReadAll(uploadFile)
chunkData, err := io.ReadAll(uploadFile)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrFileUpload, err)
return
}
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
err = ioutil.WriteFile(chunkPath, chunkData, 0644)
err = os.WriteFile(chunkPath, chunkData, 0644)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileUpload, err)
return
}
if chunkIndex+1 == chunkCount {
err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrAppDelete, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileUpload, err)
return
}
helper.SuccessWithData(c, true)

View File

@ -0,0 +1,207 @@
package v1
import (
"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/gin-gonic/gin"
)
// @Tags Firewall
// @Summary Load firewall base info
// @Description 获取防火墙基础信息
// @Success 200 {object} dto.FirewallBaseInfo
// @Security ApiKeyAuth
// @Router /hosts/firewall/base [get]
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
data, err := firewallService.LoadBaseInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Firewall
// @Summary Page firewall rules
// @Description 获取防火墙规则列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /hosts/firewall/search [post]
func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
var req dto.RuleSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := firewallService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Firewall
// @Summary Page firewall status
// @Description 修改防火墙状态
// @Accept json
// @Param request body dto.FirewallOperation true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /hosts/firewall/operate [post]
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"}
func (b *BaseApi) OperateFirewall(c *gin.Context) {
var req dto.FirewallOperation
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 := firewallService.OperateFirewall(req.Operation); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 创建防火墙端口规则
// @Accept json
// @Param request body dto.PortRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/port [post]
// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"}
func (b *BaseApi) OperatePortRule(c *gin.Context) {
var req dto.PortRuleOperate
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 := firewallService.OperatePortRule(req, true); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 创建防火墙 IP 规则
// @Accept json
// @Param request body dto.AddrRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/ip [post]
// @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"添加 ip 规则 [strategy] [address]","formatEN":"create address rules [strategy][address]"}
func (b *BaseApi) OperateIPRule(c *gin.Context) {
var req dto.AddrRuleOperate
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 := firewallService.OperateAddressRule(req, true); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 批量删除防火墙规则
// @Accept json
// @Param request body dto.BatchRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/ip [post]
func (b *BaseApi) BatchOperateRule(c *gin.Context) {
var req dto.BatchRuleOperate
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 := firewallService.BacthOperateRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 更新端口防火墙规则
// @Accept json
// @Param request body dto.PortRuleUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/update/port [post]
func (b *BaseApi) UpdatePortRule(c *gin.Context) {
var req dto.PortRuleUpdate
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 := firewallService.UpdatePortRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 更新 ip 防火墙规则
// @Accept json
// @Param request body dto.AddrRuleUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/update/ip [post]
func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
var req dto.AddrRuleUpdate
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 := firewallService.UpdateAddrRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -15,7 +15,7 @@ import (
// @Param request body dto.GroupCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/group [post]
// @Router /groups [post]
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"}
func (b *BaseApi) CreateGroup(c *gin.Context) {
var req dto.GroupCreate
@ -41,7 +41,7 @@ func (b *BaseApi) CreateGroup(c *gin.Context) {
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/group/del [post]
// @Router /groups/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"groups","output_colume":"name","output_value":"name"},{"input_colume":"id","input_value":"id","isList":false,"db":"groups","output_colume":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"}
func (b *BaseApi) DeleteGroup(c *gin.Context) {
var req dto.OperateByID
@ -68,7 +68,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) {
// @Param request body dto.GroupUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/group/update [post]
// @Router /groups/update [post]
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"}
func (b *BaseApi) UpdateGroup(c *gin.Context) {
var req dto.GroupUpdate
@ -94,7 +94,7 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) {
// @Param request body dto.GroupSearch true "request"
// @Success 200 {anrry} dto.GroupInfo
// @Security ApiKeyAuth
// @Router /hosts/group/search [post]
// @Router /groups/search [post]
func (b *BaseApi) ListGroup(c *gin.Context) {
var req dto.GroupSearch
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -83,6 +83,15 @@ func SuccessWithData(ctx *gin.Context, data interface{}) {
ctx.Abort()
}
func SuccessWithOutData(ctx *gin.Context) {
res := dto.Response{
Code: constant.CodeSuccess,
Message: "success",
}
ctx.JSON(http.StatusOK, res)
ctx.Abort()
}
func SuccessWithMsg(ctx *gin.Context, msg string) {
res := dto.Response{
Code: constant.CodeSuccess,

View File

@ -8,7 +8,6 @@ import (
"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/ssh"
"github.com/gin-gonic/gin"
)
@ -74,33 +73,9 @@ func (b *BaseApi) TestByInfo(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)
}
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &req)
connInfo.PrivateKey = []byte(req.PrivateKey)
client, err := connInfo.NewClient()
if err != nil {
helper.SuccessWithData(c, false)
return
}
defer client.Close()
helper.SuccessWithData(c, true)
connStatus := hostService.TestByInfo(req)
helper.SuccessWithData(c, connStatus)
}
// @Tags Host
@ -270,11 +245,13 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
upMap["port"] = req.Port
upMap["user"] = req.User
upMap["auth_mode"] = req.AuthMode
upMap["remember_password"] = req.RememberPassword
if len(req.Password) != 0 {
upMap["password"] = req.Password
}
if len(req.PrivateKey) != 0 {
upMap["private_key"] = req.PrivateKey
upMap["pass_phrase"] = req.PassPhrase
}
upMap["description"] = req.Description
if err := hostService.Update(req.ID, upMap); err != nil {
@ -291,7 +268,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
// @Param request body dto.ChangeHostGroup true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/update [post]
// @Router /hosts/update/group [post]
// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"hosts","output_colume":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"}
func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
var req dto.ChangeHostGroup

View File

@ -1,6 +1,7 @@
package v1
import (
"sort"
"time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
@ -88,6 +89,7 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
for _, net := range netStat {
options = append(options, net.Name)
}
sort.Strings(options)
helper.SuccessWithData(c, options)
}
@ -98,5 +100,6 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) {
for _, net := range diskStat {
options = append(options, net.Name)
}
sort.Strings(options)
helper.SuccessWithData(c, options)
}

View File

@ -0,0 +1,123 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags Runtime
// @Summary List runtimes
// @Description 获取运行环境列表
// @Accept json
// @Param request body request.RuntimeSearch true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/search [post]
func (b *BaseApi) SearchRuntimes(c *gin.Context) {
var req request.RuntimeSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, items, err := runtimeService.Page(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: items,
})
}
// @Tags Runtime
// @Summary Create runtime
// @Description 创建运行环境
// @Accept json
// @Param request body request.RuntimeCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建运行环境 [name]","formatEN":"Create runtime [name]"}
func (b *BaseApi) CreateRuntime(c *gin.Context) {
var req request.RuntimeCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := runtimeService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Delete runtime
// @Description 删除运行环境
// @Accept json
// @Param request body request.RuntimeDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"删除网站 [name]","formatEN":"Delete website [name]"}
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
var req request.RuntimeDelete
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := runtimeService.Delete(req.ID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Runtime
// @Summary Update runtime
// @Description 更新运行环境
// @Accept json
// @Param request body request.RuntimeUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/update [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新运行环境 [name]","formatEN":"Update runtime [name]"}
func (b *BaseApi) UpdateRuntime(c *gin.Context) {
var req request.RuntimeUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := runtimeService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Runtime
// @Summary Get runtime
// @Description 获取运行环境
// @Accept json
// @Param id path string true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/:id [get]
func (b *BaseApi) GetRuntime(c *gin.Context) {
id, err := helper.GetIntParamByKey(c, "id")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
res, err := runtimeService.Get(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}

View File

@ -1,6 +1,8 @@
package v1
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strconv"
@ -42,6 +44,9 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &host)
connInfo.PrivateKey = []byte(host.PrivateKey)
if len(host.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
@ -196,7 +201,15 @@ func wshandleError(ws *websocket.Conn, err error) bool {
global.LOG.Errorf("handler ws faled:, err: %v", err)
dt := time.Now().Add(time.Second)
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
wsData, err := json.Marshal(terminal.WsMsg{
Type: terminal.WsMsgCmd,
Data: base64.StdEncoding.EncodeToString([]byte(err.Error())),
})
if err != nil {
_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}"))
} else {
_ = ws.WriteMessage(websocket.TextMessage, wsData)
}
}
return true
}

View File

@ -22,6 +22,28 @@ func (b *BaseApi) GetUpgradeInfo(c *gin.Context) {
helper.SuccessWithData(c, info)
}
// @Tags System Setting
// @Summary Load release notes by version
// @Description 获取版本 release notes
// @Accept json
// @Param request body dto.Upgrade true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/upgrade [get]
func (b *BaseApi) GetNotesByVersion(c *gin.Context) {
var req dto.Upgrade
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
notes, err := upgradeService.LoadNotes(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, notes)
}
// @Tags System Setting
// @Summary Upgrade
// @Description 系统更新

View File

@ -78,14 +78,12 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
tx, ctx := helper.GetTxAndContext()
err := websiteService.CreateWebsite(ctx, req)
err := websiteService.CreateWebsite(req)
if err != nil {
tx.Rollback()
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
tx.Commit()
helper.SuccessWithData(c, nil)
}
@ -127,14 +125,12 @@ func (b *BaseApi) DeleteWebsite(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
tx, ctx := helper.GetTxAndContext()
err := websiteService.DeleteWebsite(ctx, req)
err := websiteService.DeleteWebsite(req)
if err != nil {
tx.Rollback()
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
tx.Commit()
helper.SuccessWithData(c, nil)
}
@ -189,14 +185,16 @@ func (b *BaseApi) GetWebsite(c *gin.Context) {
// @Param id path integer true "request"
// @Success 200 {object} response.FileInfo
// @Security ApiKeyAuth
// @Router /websites/:id/nginx [get]
// @Router /websites/:id/config/:type [get]
func (b *BaseApi) GetWebsiteNginx(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
fileInfo, err := websiteService.GetWebsiteNginxConfig(id)
configType := c.Param("type")
fileInfo, err := websiteService.GetWebsiteNginxConfig(id, configType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@ -496,3 +494,157 @@ func (b *BaseApi) ChangeDefaultServer(c *gin.Context) {
}
helper.SuccessWithData(c, nil)
}
// @Tags Website
// @Summary Load websit php conf
// @Description 获取网站 php 配置
// @Accept json
// @Param id path integer true "request"
// @Success 200 {object} response.PHPConfig
// @Security ApiKeyAuth
// @Router /websites/php/config/:id [get]
func (b *BaseApi) GetWebsitePHPConfig(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
data, err := websiteService.GetPHPConfig(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Website PHP
// @Summary Update website php conf
// @Description 更新 网站 PHP 配置
// @Accept json
// @Param request body request.WebsitePHPConfigUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/php/config [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[domain] PHP 配置修改","formatEN":"[domain] PHP conf update"}
func (b *BaseApi) UpdateWebsitePHPConfig(c *gin.Context) {
var req request.WebsitePHPConfigUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdatePHPConfig(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website PHP
// @Summary Update php conf
// @Description 更新 php 配置
// @Accept json
// @Param request body request.WebsitePHPFileUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/php/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"php 配置修改 [domain]","formatEN":"Nginx conf update [domain]"}
func (b *BaseApi) UpdatePHPFile(c *gin.Context) {
var req request.WebsitePHPFileUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdatePHPConfigFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website
// @Summary Get rewrite conf
// @Description 获取伪静态配置
// @Accept json
// @Param request body request.NginxRewriteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/rewrite [post]
func (b *BaseApi) GetRewriteConfig(c *gin.Context) {
var req request.NginxRewriteReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.GetRewriteConfig(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Update rewrite conf
// @Description 更新伪静态配置
// @Accept json
// @Param request body request.NginxRewriteUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/rewrite/update [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteID","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"伪静态配置修改 [domain]","formatEN":"Nginx conf rewrite update [domain]"}
func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) {
var req request.NginxRewriteUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateRewriteConfig(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website
// @Summary Update Site Dir
// @Description 更新网站目录
// @Accept json
// @Param request body request.WebsiteUpdateDir true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dir/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录","formatEN":"Update domain [domain] dir"}
func (b *BaseApi) UpdateSiteDir(c *gin.Context) {
var req request.WebsiteUpdateDir
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateSiteDir(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Update Site Dir permission
// @Description 更新网站目录权限
// @Accept json
// @Param request body request.WebsiteUpdateDirPermission true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dir/permission [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录权限","formatEN":"Update domain [domain] dir permission"}
func (b *BaseApi) UpdateSiteDirPermission(c *gin.Context) {
var req request.WebsiteUpdateDirPermission
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateSitePermission(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -1,89 +0,0 @@
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"
"github.com/gin-gonic/gin"
)
// @Tags Website Group
// @Summary List website groups
// @Description 获取网站组
// @Success 200 {anrry} model.WebsiteGroup
// @Security ApiKeyAuth
// @Router /websites/groups [get]
func (b *BaseApi) GetWebGroups(c *gin.Context) {
list, err := websiteGroupService.GetGroups()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Website Group
// @Summary Create website group
// @Description 创建网站组
// @Accept json
// @Param request body request.WebsiteGroupCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/groups [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建网站组 [name]","formatEN":"Create website groups [name]"}
func (b *BaseApi) CreateWebGroup(c *gin.Context) {
var req request.WebsiteGroupCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteGroupService.CreateGroup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website Group
// @Summary Update website group
// @Description 更新网站组
// @Accept json
// @Param request body request.WebsiteGroupUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/groups/update [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新网站组 [name]","formatEN":"Update website groups [name]"}
func (b *BaseApi) UpdateWebGroup(c *gin.Context) {
var req request.WebsiteGroupUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteGroupService.UpdateGroup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website Group
// @Summary Delete website group
// @Description 删除网站组
// @Accept json
// @Param request body request.WebsiteResourceReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/groups/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_groups","output_colume":"name","output_value":"name"}],"formatZH":"删除网站组 [name]","formatEN":"Delete website group [name]"}
func (b *BaseApi) DeleteWebGroup(c *gin.Context) {
var req request.WebsiteResourceReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteGroupService.DeleteGroup(req.ID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -176,3 +176,25 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
}
helper.SuccessWithData(c, websiteSSL)
}
// @Tags Website SSL
// @Summary Update ssl
// @Description 更新 ssl
// @Accept json
// @Param request body request.WebsiteSSLUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ssl/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新证书设置 [domain]","formatEN":"Update ssl config [domain]"}
func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) {
var req request.WebsiteSSLUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteSSLService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -69,15 +69,22 @@ type AppForm struct {
}
type AppFormFields struct {
Type string `json:"type"`
LabelZh string `json:"labelZh"`
LabelEn string `json:"labelEn"`
Required bool `json:"required"`
Default interface{} `json:"default"`
EnvKey string `json:"envKey"`
Disabled bool `json:"disabled"`
Edit bool `json:"edit"`
Rule string `json:"rule"`
Type string `json:"type"`
LabelZh string `json:"labelZh"`
LabelEn string `json:"labelEn"`
Required bool `json:"required"`
Default interface{} `json:"default"`
EnvKey string `json:"envKey"`
Disabled bool `json:"disabled"`
Edit bool `json:"edit"`
Rule string `json:"rule"`
Multiple bool `json:"multiple"`
Values []AppFormValue `json:"values"`
}
type AppFormValue struct {
Label string `json:"label"`
Value string `json:"value"`
}
type AppResource struct {

View File

@ -9,7 +9,6 @@ type UserLoginInfo struct {
Name string `json:"name"`
Token string `json:"token"`
MfaStatus string `json:"mfaStatus"`
MfaSecret string `json:"mfaSecret"`
}
type MfaCredential struct {
@ -28,7 +27,6 @@ type Login struct {
type MFALogin struct {
Name string `json:"name"`
Password string `json:"password"`
Secret string `json:"secret"`
Code string `json:"code"`
AuthMethod string `json:"authMethod"`
}

View File

@ -59,7 +59,7 @@ type BackupRecords struct {
}
type DownloadRecord struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL"`
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO"`
FileDir string `json:"fileDir" validate:"required"`
FileName string `json:"fileName" validate:"required"`
}

View File

@ -52,6 +52,16 @@ type CronjobDownload struct {
BackupAccountID uint `json:"backupAccountID" validate:"required"`
}
type CronjobClean struct {
CleanData bool `json:"cleanData"`
CronjobID uint `json:"cronjobID" validate:"required"`
}
type CronjobBatchDelete struct {
CleanData bool `json:"cleanData"`
IDs []uint `json:"ids"`
}
type CronjobInfo struct {
ID uint `json:"id"`
Name string `json:"name"`

View File

@ -3,13 +3,6 @@ package dto
import "time"
type DashboardBase struct {
HaloID uint `json:"haloID"`
DateeaseID uint `json:"dateeaseID"`
JumpServerID uint `json:"jumpserverID"`
MeterSphereID uint `json:"metersphereID"`
KubeoperatorID uint `json:"kubeoperatorID"`
KubepiID uint `json:"kubepiID"`
WebsiteNumber int `json:"websiteNumber"`
DatabaseNumber int `json:"databaseNumber"`
CronjobNumber int `json:"cronjobNumber"`
@ -55,8 +48,21 @@ type DashboardCurrent struct {
IOReadBytes uint64 `json:"ioReadBytes"`
IOWriteBytes uint64 `json:"ioWriteBytes"`
IOCount uint64 `json:"ioCount"`
IOTime uint64 `json:"ioTime"`
IOReadTime uint64 `json:"ioReadTime"`
IOWriteTime uint64 `json:"ioWriteTime"`
DiskData []DiskInfo `json:"diskData"`
NetBytesSent uint64 `json:"netBytesSent"`
NetBytesRecv uint64 `json:"netBytesRecv"`
ShotTime time.Time `json:"shotTime"`
}
type DiskInfo struct {
Path string `json:"path"`
Type string `json:"type"`
Device string `json:"device"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
@ -66,9 +72,4 @@ type DashboardCurrent struct {
InodesUsed uint64 `json:"inodesUsed"`
InodesFree uint64 `json:"inodesFree"`
InodesUsedPercent float64 `json:"inodesUsedPercent"`
NetBytesSent uint64 `json:"netBytesSent"`
NetBytesRecv uint64 `json:"netBytesRecv"`
ShotTime time.Time `json:"shotTime"`
}

View File

@ -5,11 +5,13 @@ type DaemonJsonUpdateByFile struct {
}
type DaemonJsonConf struct {
IsSwarm bool `json:"isSwarm"`
Status string `json:"status"`
Version string `json:"version"`
Mirrors []string `json:"registryMirrors"`
Registries []string `json:"insecureRegistries"`
LiveRestore bool `json:"liveRestore"`
IPTables bool `json:"iptables"`
CgroupDriver string `json:"cgroupDriver"`
}

View File

@ -0,0 +1,47 @@
package dto
type FirewallBaseInfo struct {
Name string `json:"name"`
Status string `json:"status"`
Version string `json:"version"`
PingStatus string `json:"pingStatus"`
}
type RuleSearch struct {
PageInfo
Info string `json:"info"`
Type string `json:"type" validate:"required"`
}
type FirewallOperation struct {
Operation string `json:"operation" validate:"required,oneof=start stop disablePing enablePing"`
}
type PortRuleOperate struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Address string `json:"address"`
Port string `json:"port" validate:"required"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
}
type AddrRuleOperate struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Address string `json:"address" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
}
type PortRuleUpdate struct {
OldRule PortRuleOperate `json:"oldRule"`
NewRule PortRuleOperate `json:"newRule"`
}
type AddrRuleUpdate struct {
OldRule AddrRuleOperate `json:"oldRule"`
NewRule AddrRuleOperate `json:"newRule"`
}
type BatchRuleOperate struct {
Type string `json:"type" validate:"required"`
Rules []PortRuleOperate `json:"rules"`
}

View File

@ -13,6 +13,7 @@ type GroupSearch struct {
type GroupUpdate struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type" validate:"required"`
IsDefault bool `json:"isDefault"`
}

View File

@ -5,15 +5,17 @@ import (
)
type HostOperate struct {
ID uint `json:"id"`
GroupID uint `json:"groupID"`
Name string `json:"name"`
Addr string `json:"addr" validate:"required"`
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
User string `json:"user" validate:"required"`
AuthMode string `json:"authMode" validate:"oneof=password key"`
PrivateKey string `json:"privateKey"`
Password string `json:"password"`
ID uint `json:"id"`
GroupID uint `json:"groupID"`
Name string `json:"name"`
Addr string `json:"addr" validate:"required"`
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
User string `json:"user" validate:"required"`
AuthMode string `json:"authMode" validate:"oneof=password key"`
Password string `json:"password"`
PrivateKey string `json:"privateKey"`
PassPhrase string `json:"passPhrase"`
RememberPassword bool `json:"rememberPassword"`
Description string `json:"description"`
}
@ -23,8 +25,9 @@ type HostConnTest struct {
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
User string `json:"user" validate:"required"`
AuthMode string `json:"authMode" validate:"oneof=password key"`
PrivateKey string `json:"privateKey"`
Password string `json:"password"`
PrivateKey string `json:"privateKey"`
PassPhrase string `json:"passPhrase"`
}
type SearchHostWithPage struct {
@ -43,15 +46,19 @@ type ChangeHostGroup struct {
}
type HostInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
GroupID uint `json:"groupID"`
GroupBelong string `json:"groupBelong"`
Name string `json:"name"`
Addr string `json:"addr"`
Port uint `json:"port"`
User string `json:"user"`
AuthMode string `json:"authMode"`
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
GroupID uint `json:"groupID"`
GroupBelong string `json:"groupBelong"`
Name string `json:"name"`
Addr string `json:"addr"`
Port uint `json:"port"`
User string `json:"user"`
AuthMode string `json:"authMode"`
Password string `json:"password"`
PrivateKey string `json:"privateKey"`
PassPhrase string `json:"passPhrase"`
RememberPassword bool `json:"rememberPassword"`
Description string `json:"description"`
}

View File

@ -19,3 +19,14 @@ type NginxConfigUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Params interface{} `json:"params"`
}
type NginxRewriteReq struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Name string `json:"name" validate:"required"`
}
type NginxRewriteUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Name string `json:"name" validate:"required"`
Content string `json:"content" validate:"required"`
}

View File

@ -0,0 +1,32 @@
package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type RuntimeSearch struct {
dto.PageInfo
Type string `json:"type"`
Name string `json:"name"`
Status string `json:"status"`
}
type RuntimeCreate struct {
AppDetailID uint `json:"appDetailId"`
Name string `json:"name"`
Params map[string]interface{} `json:"params"`
Resource string `json:"resource"`
Image string `json:"image"`
Type string `json:"type"`
Version string `json:"version"`
}
type RuntimeDelete struct {
ID uint `json:"id"`
}
type RuntimeUpdate struct {
Name string `json:"name"`
ID uint `json:"id"`
Params map[string]interface{} `json:"params"`
Image string `json:"image"`
Version string `json:"version"`
}

View File

@ -23,6 +23,14 @@ type WebsiteCreate struct {
AppInstall NewAppInstall `json:"appInstall"`
AppID uint `json:"appID"`
AppInstallID uint `json:"appInstallID"`
RuntimeID uint `json:"runtimeID"`
RuntimeConfig
}
type RuntimeConfig struct {
ProxyType string `json:"proxyType"`
Port int `json:"port"`
}
type NewAppInstall struct {
@ -126,3 +134,25 @@ type WebsiteLogReq struct {
type WebsiteDefaultUpdate struct {
ID uint `json:"id" validate:"required"`
}
type WebsitePHPConfigUpdate struct {
ID uint `json:"id" validate:"required"`
Params map[string]string `json:"params" validate:"required"`
}
type WebsitePHPFileUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
Content string `json:"content" validate:"required"`
}
type WebsiteUpdateDir struct {
ID uint `json:"id" validate:"required"`
SiteDir string `json:"siteDir" validate:"required"`
}
type WebsiteUpdateDirPermission struct {
ID uint `json:"id" validate:"required"`
User string `json:"user" validate:"required"`
Group string `json:"group" validate:"required"`
}

View File

@ -44,3 +44,8 @@ type WebsiteDnsAccountUpdate struct {
type WebsiteResourceReq struct {
ID uint `json:"id" validate:"required"`
}
type WebsiteSSLUpdate struct {
ID uint `json:"id" validate:"required"`
AutoRenew bool `json:"autoRenew" validate:"required"`
}

View File

@ -1,8 +1,9 @@
package response
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type AppRes struct {
@ -43,6 +44,7 @@ type AppDetailDTO struct {
model.AppDetail
Enable bool `json:"enable"`
Params interface{} `json:"params"`
Image string `json:"image"`
}
type AppInstalledDTO struct {
@ -54,6 +56,12 @@ type AppInstalledDTO struct {
CanUpdate bool `json:"canUpdate"`
}
type DatabaseConn struct {
Password string `json:"password"`
ServiceName string `json:"serviceName"`
Port int64 `json:"port"`
}
type AppService struct {
Label string `json:"label"`
Value string `json:"value"`
@ -61,11 +69,15 @@ type AppService struct {
}
type AppParam struct {
Value interface{} `json:"value"`
Edit bool `json:"edit"`
Key string `json:"key"`
Rule string `json:"rule"`
LabelZh string `json:"labelZh"`
LabelEn string `json:"labelEn"`
Type string `json:"type"`
Value interface{} `json:"value"`
Edit bool `json:"edit"`
Key string `json:"key"`
Rule string `json:"rule"`
LabelZh string `json:"labelZh"`
LabelEn string `json:"labelEn"`
Type string `json:"type"`
Values interface{} `json:"values"`
ShowValue string `json:"showValue"`
Required bool `json:"required"`
Multiple bool `json:"multiple"`
}

View File

@ -0,0 +1,9 @@
package response
import "github.com/1Panel-dev/1Panel/backend/app/model"
type RuntimeRes struct {
model.Runtime
AppParams []AppParam `json:"appParams"`
AppID uint `json:"appId"`
}

View File

@ -10,6 +10,7 @@ type WebsiteDTO struct {
AccessLogPath string `json:"accessLogPath"`
SitePath string `json:"sitePath"`
AppName string `json:"appName"`
RuntimeName string `json:"runtimeName"`
}
type WebsitePreInstallCheck struct {
@ -42,3 +43,11 @@ type WebsiteLog struct {
Enable bool `json:"enable"`
Content string `json:"content"`
}
type PHPConfig struct {
Params map[string]string `json:"params"`
}
type NginxRewriteRes struct {
Content string `json:"content"`
}

View File

@ -49,7 +49,7 @@ type PortUpdate struct {
}
type SnapshotCreate struct {
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO"`
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO"`
Description string `json:"description"`
}
type SnapshotRecover struct {
@ -82,9 +82,11 @@ type SnapshotInfo struct {
}
type UpgradeInfo struct {
NewVersion string `json:"newVersion"`
ReleaseNote string `json:"releaseNote"`
NewVersion string `json:"newVersion"`
LatestVersion string `json:"latestVersion"`
ReleaseNote string `json:"releaseNote"`
}
type Upgrade struct {
Version string `json:"version"`
}

View File

@ -16,6 +16,7 @@ type App struct {
Github string `json:"github" gorm:"type:varchar(64);not null"`
Document string `json:"document" gorm:"type:varchar(64);not null"`
Recommend int `json:"recommend" gorm:"type:Integer;not null"`
Resource string `json:"resource" gorm:"type:varchar;not null;default:remote"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"-" gorm:"-"`
AppTags []AppTag `json:"-" gorm:"-:migration"`

View File

@ -2,6 +2,7 @@ package model
import (
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/constant"
)
@ -26,9 +27,21 @@ type AppInstall struct {
}
func (i *AppInstall) GetPath() string {
return path.Join(constant.AppInstallDir, i.App.Key, i.Name)
return path.Join(i.getAppPath(), i.Name)
}
func (i *AppInstall) GetComposePath() string {
return path.Join(constant.AppInstallDir, i.App.Key, i.Name, "docker-compose.yml")
return path.Join(i.getAppPath(), i.Name, "docker-compose.yml")
}
func (i *AppInstall) GetEnvPath() string {
return path.Join(i.getAppPath(), i.Name, ".env")
}
func (i *AppInstall) getAppPath() string {
if i.App.Resource == constant.AppResourceLocal {
return path.Join(constant.LocalAppInstallDir, strings.TrimPrefix(i.App.Key, constant.AppResourceLocal))
} else {
return path.Join(constant.AppInstallDir, i.App.Key)
}
}

View File

@ -2,14 +2,17 @@ package model
type Host struct {
BaseModel
GroupID uint `gorm:"type:decimal;not null" json:"group_id"`
Name string `gorm:"type:varchar(64);not null" json:"name"`
Addr string `gorm:"type:varchar(16);not null" json:"addr"`
Port int `gorm:"type:decimal;not null" json:"port"`
User string `gorm:"type:varchar(64);not null" json:"user"`
AuthMode string `gorm:"type:varchar(16);not null" json:"authMode"`
Password string `gorm:"type:varchar(64)" json:"password"`
PrivateKey string `gorm:"type:varchar(256)" json:"privateKey"`
GroupID uint `gorm:"type:decimal;not null" json:"group_id"`
Name string `gorm:"type:varchar(64);not null" json:"name"`
Addr string `gorm:"type:varchar(16);not null" json:"addr"`
Port int `gorm:"type:decimal;not null" json:"port"`
User string `gorm:"type:varchar(64);not null" json:"user"`
AuthMode string `gorm:"type:varchar(16);not null" json:"authMode"`
Password string `gorm:"type:varchar(64)" json:"password"`
PrivateKey string `gorm:"type:varchar(256)" json:"privateKey"`
PassPhrase string `gorm:"type:varchar(256)" json:"passPhrase"`
RememberPassword bool `json:"rememberPassword"`
Description string `gorm:"type:varchar(256)" json:"description"`
}

View File

@ -0,0 +1,17 @@
package model
type Runtime struct {
BaseModel
Name string `gorm:"type:varchar;not null" json:"name"`
AppDetailID uint `gorm:"type:integer" json:"appDetailId"`
Image string `gorm:"type:varchar" json:"image"`
WorkDir string `gorm:"type:varchar" json:"workDir"`
DockerCompose string `gorm:"type:varchar" json:"dockerCompose"`
Env string `gorm:"type:varchar" json:"env"`
Params string `gorm:"type:varchar" json:"params"`
Version string `gorm:"type:varchar;not null" json:"version"`
Type string `gorm:"type:varchar;not null" json:"type"`
Status string `gorm:"type:varchar;not null" json:"status"`
Resource string `gorm:"type:varchar;not null" json:"resource"`
Message string `gorm:"type:longtext;" json:"message"`
}

View File

@ -4,23 +4,33 @@ import "time"
type Website struct {
BaseModel
Protocol string `gorm:"type:varchar(64);not null" json:"protocol"`
PrimaryDomain string `gorm:"type:varchar(128);not null" json:"primaryDomain"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Alias string `gorm:"type:varchar(128);not null" json:"alias"`
Remark string `gorm:"type:longtext;" json:"remark"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
HttpConfig string `gorm:"type:varchar(64);not null" json:"httpConfig"`
ExpireDate time.Time `json:"expireDate"`
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"`
WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"`
Proxy string `gorm:"type:varchar(128);not null" json:"proxy"`
ErrorLog bool `json:"errorLog"`
AccessLog bool `json:"accessLog"`
DefaultServer bool `json:"defaultServer"`
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
Protocol string `gorm:"type:varchar;not null" json:"protocol"`
PrimaryDomain string `gorm:"type:varchar;not null" json:"primaryDomain"`
Type string `gorm:"type:varchar;not null" json:"type"`
Alias string `gorm:"type:varchar;not null" json:"alias"`
Remark string `gorm:"type:longtext;" json:"remark"`
Status string `gorm:"type:varchar;not null" json:"status"`
HttpConfig string `gorm:"type:varchar;not null" json:"httpConfig"`
ExpireDate time.Time `json:"expireDate"`
Proxy string `gorm:"type:varchar;" json:"proxy"`
ProxyType string `gorm:"type:varchar;" json:"proxyType"`
SiteDir string `gorm:"type:varchar;" json:"siteDir"`
ErrorLog bool `json:"errorLog"`
AccessLog bool `json:"accessLog"`
DefaultServer bool `json:"defaultServer"`
Rewrite string `gorm:"type:varchar" json:"rewrite"`
WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"`
WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"`
RuntimeID uint `gorm:"type:integer" json:"runtimeID"`
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
User string `gorm:"type:varchar;" json:"user"`
Group string `gorm:"type:varchar;" json:"group"`
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
}
func (w Website) TableName() string {

View File

@ -1,11 +0,0 @@
package model
type WebsiteGroup struct {
BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"`
Default bool `json:"default"`
}
func (w WebsiteGroup) TableName() string {
return "website_groups"
}

View File

@ -11,6 +11,25 @@ import (
type AppRepo struct {
}
type IAppRepo interface {
WithKey(key string) DBOption
WithType(typeStr string) DBOption
OrderByRecommend() DBOption
GetRecommend() DBOption
WithResource(resource string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.App, error)
GetFirst(opts ...DBOption) (model.App, error)
GetBy(opts ...DBOption) ([]model.App, error)
BatchCreate(ctx context.Context, apps []model.App) error
GetByKey(ctx context.Context, key string) (model.App, error)
Create(ctx context.Context, app *model.App) error
Save(ctx context.Context, app *model.App) error
}
func NewIAppRepo() IAppRepo {
return &AppRepo{}
}
func (a AppRepo) WithKey(key string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("key = ?", key)
@ -35,12 +54,18 @@ func (a AppRepo) GetRecommend() DBOption {
}
}
func (a AppRepo) WithResource(resource string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("resource = ?", resource)
}
}
func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) {
var apps []model.App
db := getDb(opts...).Model(&model.App{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
return count, apps, err
}

View File

@ -9,6 +9,21 @@ import (
type AppDetailRepo struct {
}
type IAppDetailRepo interface {
WithVersion(version string) DBOption
WithAppId(id uint) DBOption
GetFirst(opts ...DBOption) (model.AppDetail, error)
Update(ctx context.Context, detail model.AppDetail) error
BatchCreate(ctx context.Context, details []model.AppDetail) error
DeleteByAppIds(ctx context.Context, appIds []uint) error
GetBy(opts ...DBOption) ([]model.AppDetail, error)
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
}
func NewIAppDetailRepo() IAppDetailRepo {
return &AppDetailRepo{}
}
func (a AppDetailRepo) WithVersion(version string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("version = ?", version)

View File

@ -3,6 +3,7 @@ package repo
import (
"context"
"encoding/json"
"gorm.io/gorm/clause"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
@ -11,6 +12,31 @@ import (
type AppInstallRepo struct{}
type IAppInstallRepo interface {
WithDetailIdsIn(detailIds []uint) DBOption
WithDetailIdNotIn(detailIds []uint) DBOption
WithAppId(appId uint) DBOption
WithAppIdsIn(appIds []uint) DBOption
WithStatus(status string) DBOption
WithServiceName(serviceName string) DBOption
WithPort(port int) DBOption
WithIdNotInWebsite() DBOption
ListBy(opts ...DBOption) ([]model.AppInstall, error)
GetFirst(opts ...DBOption) (model.AppInstall, error)
Create(ctx context.Context, install *model.AppInstall) error
Save(ctx context.Context, install *model.AppInstall) error
DeleteBy(opts ...DBOption) error
Delete(ctx context.Context, install model.AppInstall) error
Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error)
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
LoadBaseInfo(key string, name string) (*RootInfo, error)
GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error)
}
func NewIAppInstallRepo() IAppInstallRepo {
return &AppInstallRepo{}
}
func (a *AppInstallRepo) WithDetailIdsIn(detailIds []uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_detail_id in (?)", detailIds)
@ -73,13 +99,20 @@ func (a *AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
return install, err
}
func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error {
db := getTx(ctx).Model(&model.AppInstall{})
return db.Create(&install).Error
func (a *AppInstallRepo) GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) {
var install model.AppInstall
db := getTx(ctx, opts...).Model(&model.AppInstall{})
err := db.Preload("App").First(&install).Error
return install, err
}
func (a *AppInstallRepo) Save(install *model.AppInstall) error {
return getDb().Save(&install).Error
func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error {
db := getTx(ctx).Model(&model.AppInstall{})
return db.Omit(clause.Associations).Create(&install).Error
}
func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error {
return getTx(ctx).Omit(clause.Associations).Save(&install).Error
}
func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error {
@ -112,8 +145,11 @@ type RootInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Port int64 `json:"port"`
HttpsPort int64 `json:"httpsPort"`
Password string `json:"password"`
UserPassword string `json:"userPassword"`
ContainerName string `json:"containerName"`
ServiceName string `json:"serviceName"`
Param string `json:"param"`
Env string `json:"env"`
Key string `json:"key"`
@ -146,8 +182,14 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
if ok {
info.Password = password
}
userPassword, ok := envMap["PANEL_DB_USER_PASSWORD"].(string)
if ok {
info.UserPassword = userPassword
}
info.Port = int64(appInstall.HttpPort)
info.HttpsPort = int64(appInstall.HttpsPort)
info.ID = appInstall.ID
info.ServiceName = appInstall.ServiceName
info.ContainerName = appInstall.ContainerName
info.Name = appInstall.Name
info.Env = appInstall.Env

View File

@ -11,6 +11,20 @@ import (
type AppInstallResourceRpo struct {
}
type IAppInstallResourceRpo interface {
WithAppInstallId(appInstallId uint) DBOption
WithLinkId(linkId uint) DBOption
WithResourceId(resourceId uint) DBOption
GetBy(opts ...DBOption) ([]model.AppInstallResource, error)
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
Create(ctx context.Context, resource *model.AppInstallResource) error
DeleteBy(ctx context.Context, opts ...DBOption) error
}
func NewIAppInstallResourceRpo() IAppInstallResourceRpo {
return &AppInstallResourceRpo{}
}
func (a AppInstallResourceRpo) WithAppInstallId(appInstallId uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallId)

View File

@ -8,6 +8,18 @@ import (
type AppTagRepo struct {
}
type IAppTagRepo interface {
BatchCreate(ctx context.Context, tags []*model.AppTag) error
DeleteByAppIds(ctx context.Context, appIds []uint) error
DeleteAll(ctx context.Context) error
GetByAppId(appId uint) ([]model.AppTag, error)
GetByTagIds(tagIds []uint) ([]model.AppTag, error)
}
func NewIAppTagRepo() IAppTagRepo {
return &AppTagRepo{}
}
func (a AppTagRepo) BatchCreate(ctx context.Context, tags []*model.AppTag) error {
return getTx(ctx).Create(&tags).Error
}

View File

@ -20,6 +20,8 @@ type IBackupRepo interface {
Delete(opts ...DBOption) error
DeleteRecord(ctx context.Context, opts ...DBOption) error
WithByDetailName(detailName string) DBOption
WithByFileName(fileName string) DBOption
WithByType(backupType string) DBOption
}
func NewIBackupRepo() IBackupRepo {

View File

@ -15,6 +15,7 @@ type ICommandRepo interface {
Create(command *model.Command) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Command, error)
}
func NewICommandRepo() ICommandRepo {

View File

@ -21,10 +21,15 @@ type ICommonRepo interface {
WithIdsIn(ids []uint) DBOption
WithByDate(startTime, endTime time.Time) DBOption
WithByStartDate(startTime time.Time) DBOption
WithByStatus(status string) DBOption
}
type CommonRepo struct{}
func NewCommonRepo() ICommonRepo {
return &CommonRepo{}
}
func (c *CommonRepo) WithByID(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id = ?", id)

View File

@ -17,6 +17,7 @@ type IComposeTemplateRepo interface {
CreateRecord(compose *model.Compose) error
DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error)
}
func NewIComposeTemplateRepo() IComposeTemplateRepo {

View File

@ -20,12 +20,15 @@ type ICronjobRepo interface {
Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error)
Create(cronjob *model.Cronjob) error
WithByJobID(id int) DBOption
WithByBackupID(id uint) DBOption
WithByRecordDropID(id int) DBOption
Save(id uint, cronjob model.Cronjob) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
DeleteRecord(opts ...DBOption) error
StartRecords(cronjobID uint, targetPath string) model.JobRecords
StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords
EndRecords(record model.JobRecords, status, message, records string)
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
}
func NewICronjobRepo() ICronjobRepo {
@ -112,10 +115,23 @@ func (c *CronjobRepo) WithByJobID(id int) DBOption {
}
}
func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath string) model.JobRecords {
func (c *CronjobRepo) WithByBackupID(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("target_dir_id = ?", id)
}
}
func (c *CronjobRepo) WithByRecordDropID(id int) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id < ?", id)
}
}
func (u *CronjobRepo) StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords {
var record model.JobRecords
record.StartTime = time.Now()
record.CronjobID = cronjobID
record.FromLocal = fromLocal
record.Status = constant.StatusWaiting
if err := global.DB.Create(&record).Error; err != nil {
global.LOG.Errorf("create record status failed, err: %v", err)

View File

@ -1,30 +0,0 @@
package repo
type RepoGroup struct {
CommonRepo
AppRepo
AppTagRepo
TagRepo
AppDetailRepo
AppInstallRepo
AppInstallResourceRpo
ImageRepoRepo
ComposeTemplateRepo
MysqlRepo
CronjobRepo
HostRepo
CommandRepo
GroupRepo
SettingRepo
BackupRepo
WebsiteRepo
WebsiteDomainRepo
WebsiteGroupRepo
WebsiteDnsAccountRepo
WebsiteSSLRepo
WebsiteAcmeAccountRepo
LogRepo
SnapshotRepo
}
var RepoGroupApp = new(RepoGroup)

View File

@ -14,7 +14,7 @@ type IGroupRepo interface {
Create(group *model.Group) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
CancelDefault() error
CancelDefault(groupType string) error
WithByIsDefault(isDefault bool) DBOption
}
@ -64,6 +64,6 @@ func (u *GroupRepo) Delete(opts ...DBOption) error {
return db.Delete(&model.Group{}).Error
}
func (w GroupRepo) CancelDefault() error {
return global.DB.Model(&model.Group{}).Where("`is_default` = 1").Updates(map[string]interface{}{"is_default": 0}).Error
func (u *GroupRepo) CancelDefault(groupType string) error {
return global.DB.Model(&model.Group{}).Where("is_default = ? AND type = ?", 1, groupType).Updates(map[string]interface{}{"is_default": 0}).Error
}

View File

@ -25,7 +25,7 @@ func NewIHostRepo() IHostRepo {
return &HostRepo{}
}
func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) {
func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) {
var host model.Host
db := global.DB
for _, opt := range opts {
@ -35,7 +35,7 @@ func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) {
return host, err
}
func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) {
func (h *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) {
var hosts []model.Host
db := global.DB.Model(&model.Host{})
for _, opt := range opts {
@ -45,7 +45,7 @@ func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) {
return hosts, err
}
func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) {
func (h *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) {
var users []model.Host
db := global.DB.Model(&model.Host{})
for _, opt := range opts {
@ -57,7 +57,7 @@ func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host,
return count, users, err
}
func (c *HostRepo) WithByInfo(info string) DBOption {
func (h *HostRepo) WithByInfo(info string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(info) == 0 {
return g
@ -67,22 +67,22 @@ func (c *HostRepo) WithByInfo(info string) DBOption {
}
}
func (u *HostRepo) WithByPort(port uint) DBOption {
func (h *HostRepo) WithByPort(port uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("port = ?", port)
}
}
func (u *HostRepo) WithByUser(user string) DBOption {
func (h *HostRepo) WithByUser(user string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("user = ?", user)
}
}
func (u *HostRepo) WithByAddr(addr string) DBOption {
func (h *HostRepo) WithByAddr(addr string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("addr = ?", addr)
}
}
func (u *HostRepo) WithByGroup(group string) DBOption {
func (h *HostRepo) WithByGroup(group string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(group) == 0 {
return g
@ -91,15 +91,15 @@ func (u *HostRepo) WithByGroup(group string) DBOption {
}
}
func (u *HostRepo) Create(host *model.Host) error {
func (h *HostRepo) Create(host *model.Host) error {
return global.DB.Create(host).Error
}
func (u *HostRepo) Update(id uint, vars map[string]interface{}) error {
func (h *HostRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error
}
func (u *HostRepo) Delete(opts ...DBOption) error {
func (h *HostRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)

View File

@ -0,0 +1,80 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm"
)
type RuntimeRepo struct {
}
type IRuntimeRepo interface {
WithName(name string) DBOption
WithImage(image string) DBOption
WithNotId(id uint) DBOption
WithStatus(status string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error)
Create(ctx context.Context, runtime *model.Runtime) error
Save(runtime *model.Runtime) error
DeleteBy(opts ...DBOption) error
GetFirst(opts ...DBOption) (*model.Runtime, error)
}
func NewIRunTimeRepo() IRuntimeRepo {
return &RuntimeRepo{}
}
func (r *RuntimeRepo) WithName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("name = ?", name)
}
}
func (r *RuntimeRepo) WithStatus(status string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status = ?", status)
}
}
func (r *RuntimeRepo) WithImage(image string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("image = ?", image)
}
}
func (r *RuntimeRepo) WithNotId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id != ?", id)
}
}
func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) {
var runtimes []model.Runtime
db := getDb(opts...).Model(&model.Runtime{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&runtimes).Error
return count, runtimes, err
}
func (r *RuntimeRepo) Create(ctx context.Context, runtime *model.Runtime) error {
db := getTx(ctx).Model(&model.Runtime{})
return db.Create(&runtime).Error
}
func (r *RuntimeRepo) Save(runtime *model.Runtime) error {
return getDb().Save(&runtime).Error
}
func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.Runtime{}).Error
}
func (r *RuntimeRepo) GetFirst(opts ...DBOption) (*model.Runtime, error) {
var runtime model.Runtime
if err := getDb(opts...).First(&runtime).Error; err != nil {
return nil, err
}
return &runtime, nil
}

View File

@ -8,6 +8,19 @@ import (
type TagRepo struct {
}
type ITagRepo interface {
BatchCreate(ctx context.Context, tags []*model.Tag) error
DeleteAll(ctx context.Context) error
All() ([]model.Tag, error)
GetByIds(ids []uint) ([]model.Tag, error)
GetByKeys(keys []string) ([]model.Tag, error)
GetByAppId(appId uint) ([]model.Tag, error)
}
func NewITagRepo() ITagRepo {
return &TagRepo{}
}
func (t TagRepo) BatchCreate(ctx context.Context, tags []*model.Tag) error {
return getTx(ctx).Create(&tags).Error
}

View File

@ -17,6 +17,7 @@ type IWebsiteRepo interface {
WithGroupID(groupId uint) DBOption
WithDefaultServer() DBOption
WithDomainLike(domain string) DBOption
WithRuntimeID(runtimeID uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
List(opts ...DBOption) ([]model.Website, error)
GetFirst(opts ...DBOption) (model.Website, error)
@ -40,6 +41,12 @@ func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption {
}
}
func (w *WebsiteRepo) WithRuntimeID(runtimeID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("runtime_id = ?", runtimeID)
}
}
func (w *WebsiteRepo) WithDomain(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("primary_domain = ?", domain)

View File

@ -7,6 +7,19 @@ import (
type WebsiteDnsAccountRepo struct {
}
type IWebsiteDnsAccountRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error)
GetFirst(opts ...DBOption) (*model.WebsiteDnsAccount, error)
List(opts ...DBOption) ([]model.WebsiteDnsAccount, error)
Create(account model.WebsiteDnsAccount) error
Save(account model.WebsiteDnsAccount) error
DeleteBy(opts ...DBOption) error
}
func NewIWebsiteDnsAccountRepo() IWebsiteDnsAccountRepo {
return &WebsiteDnsAccountRepo{}
}
func (w WebsiteDnsAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error) {
var accounts []model.WebsiteDnsAccount
db := getDb(opts...).Model(&model.WebsiteDnsAccount{})

View File

@ -10,6 +10,23 @@ import (
type WebsiteDomainRepo struct {
}
type IWebsiteDomainRepo interface {
WithWebsiteId(websiteId uint) DBOption
WithPort(port int) DBOption
WithDomain(domain string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error)
GetFirst(opts ...DBOption) (model.WebsiteDomain, error)
GetBy(opts ...DBOption) ([]model.WebsiteDomain, error)
BatchCreate(ctx context.Context, domains []model.WebsiteDomain) error
Create(ctx context.Context, app *model.WebsiteDomain) error
Save(ctx context.Context, app *model.WebsiteDomain) error
DeleteBy(ctx context.Context, opts ...DBOption) error
}
func NewIWebsiteDomainRepo() IWebsiteDomainRepo {
return &WebsiteDomainRepo{}
}
func (w WebsiteDomainRepo) WithWebsiteId(websiteId uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("website_id = ?", websiteId)

View File

@ -1,44 +0,0 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm/clause"
)
type WebsiteGroupRepo struct {
}
func (w WebsiteGroupRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteGroup, error) {
var groups []model.WebsiteGroup
db := getDb(opts...).Model(&model.WebsiteGroup{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Order("`default` desc").Find(&groups).Error
return count, groups, err
}
func (w WebsiteGroupRepo) GetBy(opts ...DBOption) ([]model.WebsiteGroup, error) {
var groups []model.WebsiteGroup
db := getDb(opts...).Model(&model.WebsiteGroup{})
if err := db.Order("`default` desc").Find(&groups).Error; err != nil {
return groups, err
}
return groups, nil
}
func (w WebsiteGroupRepo) Create(app *model.WebsiteGroup) error {
return getDb().Omit(clause.Associations).Create(app).Error
}
func (w WebsiteGroupRepo) Save(app *model.WebsiteGroup) error {
return getDb().Omit(clause.Associations).Save(app).Error
}
func (w WebsiteGroupRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.WebsiteGroup{}).Error
}
func (w WebsiteGroupRepo) CancelDefault() error {
return global.DB.Model(&model.WebsiteGroup{}).Where("`default` = 1").Updates(map[string]interface{}{"default": 0}).Error
}

View File

@ -5,13 +5,15 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"io/ioutil"
"io"
"net/http"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
@ -31,10 +33,12 @@ type IAppService interface {
PageApp(req request.AppSearch) (interface{}, error)
GetAppTags() ([]response.TagDTO, error)
GetApp(key string) (*response.AppDTO, error)
GetAppDetail(appId uint, version string) (response.AppDetailDTO, error)
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
SyncAppList() error
SyncAppListFromRemote() error
GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal()
}
func NewIAppService() IAppService {
@ -137,7 +141,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
return &appDTO, nil
}
func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetailDTO, error) {
func (a AppService) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) {
var (
appDetailDTO response.AppDetailDTO
opts []repo.DBOption
@ -147,14 +151,55 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail
if err != nil {
return appDetailDTO, err
}
paramMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(detail.Params), &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.AppDetail = detail
appDetailDTO.Params = paramMap
appDetailDTO.Enable = true
if appType == "runtime" {
app, err := appRepo.GetFirst(commonRepo.WithByID(appId))
if err != nil {
return appDetailDTO, err
}
fileOp := files.NewFileOp()
buildPath := path.Join(constant.AppResourceDir, app.Key, "versions", detail.Version, "build")
paramsPath := path.Join(buildPath, "config.json")
if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist)
}
param, err := fileOp.GetContent(paramsPath)
if err != nil {
return appDetailDTO, err
}
paramMap := make(map[string]interface{})
if err := json.Unmarshal(param, &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap
composePath := path.Join(buildPath, "docker-compose.yml")
if !fileOp.Stat(composePath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist)
}
compose, err := fileOp.GetContent(composePath)
if err != nil {
return appDetailDTO, err
}
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal(compose, &composeMap); err != nil {
return appDetailDTO, err
}
if service, ok := composeMap["services"]; ok {
servicesMap := service.(map[string]interface{})
for k := range servicesMap {
appDetailDTO.Image = k
}
}
} else {
paramMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(detail.Params), &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap
}
app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId))
if err != nil {
return appDetailDTO, err
@ -164,54 +209,75 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail
}
return appDetailDTO, nil
}
func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
res := &response.AppDetailDTO{}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return nil, err
}
res.AppDetail = appDetail
paramMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(appDetail.Params), &paramMap); err != nil {
return nil, err
}
res.Params = paramMap
return res, nil
}
func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) {
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)
return
}
if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 {
return nil, buserr.New(constant.ErrNameIsExist)
err = buserr.New(constant.ErrNameIsExist)
return
}
httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params)
var (
httpPort int
httpsPort int
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
}
appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
if err != nil {
return
}
app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return nil, err
}
httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params)
if err != nil {
return nil, err
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
if err != nil {
return nil, err
}
app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return nil, err
}
if err := checkRequiredAndLimit(app); err != nil {
return nil, err
if err = checkRequiredAndLimit(app); err != nil {
return
}
paramByte, err := json.Marshal(req.Params)
if err != nil {
return nil, err
}
appInstall := model.AppInstall{
appInstall = &model.AppInstall{
Name: req.Name,
AppId: appDetail.AppId,
AppDetailId: appDetail.ID,
Version: appDetail.Version,
Status: constant.Installing,
Env: string(paramByte),
HttpPort: httpPort,
HttpsPort: httpsPort,
App: app,
}
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil {
return nil, err
if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil {
return
}
value, ok := composeMap["services"]
if !ok {
return nil, buserr.New("")
err = buserr.New("")
return
}
servicesMap := value.(map[string]interface{})
changeKeys := make(map[string]string, len(servicesMap))
@ -232,30 +298,59 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
servicesMap[v] = servicesMap[k]
delete(servicesMap, k)
}
composeByte, err := yaml.Marshal(composeMap)
var (
composeByte []byte
paramByte []byte
)
composeByte, err = yaml.Marshal(composeMap)
if err != nil {
return nil, err
return
}
appInstall.DockerCompose = string(composeByte)
if err := copyAppData(app.Key, appDetail.Version, req.Name, req.Params); err != nil {
return nil, err
}
defer func() {
if err != nil {
hErr := handleAppInstallErr(ctx, appInstall)
if hErr != nil {
global.LOG.Errorf("delete app dir error %s", hErr.Error())
}
}
}()
if err = copyAppData(app.Key, appDetail.Version, req.Name, req.Params, app.Resource == constant.AppResourceLocal); err != nil {
return
}
fileOp := files.NewFileOp()
if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil {
return nil, err
if err = fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil {
return
}
paramByte, err = json.Marshal(req.Params)
if err != nil {
return
}
appInstall.Env = string(paramByte)
if err := appInstallRepo.Create(ctx, &appInstall); err != nil {
return nil, err
if err = appInstallRepo.Create(ctx, appInstall); err != nil {
return
}
if err := createLink(ctx, app, &appInstall, req.Params); err != nil {
return nil, err
if err = createLink(ctx, app, appInstall, req.Params); err != nil {
return
}
go upApp(appInstall.GetComposePath(), appInstall)
if err = upAppPre(app, appInstall); err != nil {
return
}
go upApp(appInstall)
go updateToolApp(appInstall)
return &appInstall, nil
ports := []int{appInstall.HttpPort}
if appInstall.HttpsPort > 0 {
ports = append(ports, appInstall.HttpsPort)
}
go func() {
_ = OperateFirewallPort(nil, ports)
}()
return
}
func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
@ -268,12 +363,11 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
}
versionUrl := fmt.Sprintf("%s/%s/%s/appstore/apps.json", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion)
versionRes, err := http.Get(versionUrl)
global.LOG.Infof("get current version from [%s]", versionUrl)
if err != nil {
return nil, err
}
defer versionRes.Body.Close()
body, err := ioutil.ReadAll(versionRes.Body)
body, err := io.ReadAll(versionRes.Body)
if err != nil {
return nil, err
}
@ -290,7 +384,155 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
return res, nil
}
func (a AppService) SyncAppList() error {
func (a AppService) SyncAppListFromLocal() {
fileOp := files.NewFileOp()
appDir := constant.LocalAppResourceDir
listFile := path.Join(appDir, "list.json")
if !fileOp.Stat(listFile) {
return
}
global.LOG.Infof("start sync local apps...")
content, err := fileOp.GetContent(listFile)
if err != nil {
global.LOG.Errorf("get list.json content failed %s", err.Error())
return
}
list := &dto.AppList{}
if err := json.Unmarshal(content, list); err != nil {
global.LOG.Errorf("unmarshal list.json failed %s", err.Error())
return
}
oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal))
appsMap := getApps(oldApps, list.Items, true)
for _, l := range list.Items {
localKey := "local" + l.Key
app := appsMap[localKey]
icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png"))
if err != nil {
global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error())
continue
}
iconStr := base64.StdEncoding.EncodeToString(icon)
app.Icon = iconStr
app.TagsKey = append(l.Tags, "Local")
app.Recommend = 9999
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
detail := detailsMap[v]
detailPath := path.Join(appDir, l.Key, "versions", v)
if _, err := os.Stat(detailPath); err != nil {
global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error())
continue
}
readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md"))
if err != nil {
global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error())
}
detail.Readme = string(readmeStr)
dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml"))
if err != nil {
global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error())
continue
}
detail.DockerCompose = string(dockerComposeStr)
paramStr, err := os.ReadFile(path.Join(detailPath, "config.json"))
if err != nil {
global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error())
}
detail.Params = string(paramStr)
detailsMap[v] = detail
}
var newDetails []model.AppDetail
for _, v := range detailsMap {
newDetails = append(newDetails, v)
}
app.Details = newDetails
appsMap[localKey] = app
}
var (
addAppArray []model.App
updateArray []model.App
appIds []uint
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
updateArray = append(updateArray, v)
appIds = append(appIds, v.ID)
}
}
tx, ctx := getTxAndContext()
if len(addAppArray) > 0 {
if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
tx.Rollback()
return
}
}
for _, update := range updateArray {
if err := appRepo.Save(ctx, &update); err != nil {
tx.Rollback()
return
}
}
if err := appTagRepo.DeleteByAppIds(ctx, appIds); err != nil {
tx.Rollback()
return
}
apps := append(addAppArray, updateArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
appTags []*model.AppTag
)
tags, _ := tagRepo.All()
tagMap := make(map[string]uint, len(tags))
for _, app := range tags {
tagMap[app.Key] = app.ID
}
for _, a := range apps {
for _, t := range a.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: a.ID,
TagId: tagId,
})
}
}
for _, d := range a.Details {
d.AppId = a.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
if len(addDetails) > 0 {
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
tx.Rollback()
return
}
}
for _, u := range updateDetails {
if err := appDetailRepo.Update(ctx, u); err != nil {
tx.Rollback()
return
}
}
if len(appTags) > 0 {
if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
tx.Rollback()
return
}
}
tx.Commit()
global.LOG.Infof("sync local apps success")
}
func (a AppService) SyncAppListFromRemote() error {
updateRes, err := a.GetAppUpdate()
if err != nil {
return err
@ -323,11 +565,11 @@ func (a AppService) SyncAppList() error {
Name: t.Name,
})
}
oldApps, err := appRepo.GetBy()
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
if err != nil {
return err
}
appsMap := getApps(oldApps, list.Items)
appsMap := getApps(oldApps, list.Items, false)
for _, l := range list.Items {
app := appsMap[l.Key]
icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png"))
@ -383,8 +625,9 @@ func (a AppService) SyncAppList() error {
var (
addAppArray []model.App
updateArray []model.App
tagMap = make(map[string]uint, len(tags))
)
tagMap := make(map[string]uint, len(tags))
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)

View File

@ -1,12 +1,9 @@
package service
import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/env"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/joho/godotenv"
"io/ioutil"
"math"
"os"
"path"
@ -14,6 +11,10 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/env"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/joho/godotenv"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/buserr"
@ -33,7 +34,28 @@ import (
type AppInstallService struct {
}
func (a AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
type IAppInstallService interface {
Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error)
CheckExist(key string) (*response.AppInstalledCheck, error)
LoadPort(key string) (int64, error)
LoadConnInfo(key string) (response.DatabaseConn, error)
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
Operate(req request.AppInstalledOperate) error
Update(req request.AppInstalledUpdate) error
SyncAll(systemInit bool) error
GetServices(key string) ([]response.AppService, error)
GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
GetParams(id uint) ([]response.AppParam, error)
ChangeAppPort(req request.PortUpdate) error
GetDefaultConfigByKey(key string) (string, error)
DeleteCheck(installId uint) ([]dto.AppResource, error)
}
func NewIAppInstalledService() IAppInstallService {
return &AppInstallService{}
}
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
var opts []repo.DBOption
if req.Name != "" {
@ -73,7 +95,7 @@ func (a AppInstallService) Page(req request.AppInstalledSearch) (int64, []respon
return total, installDTOs, nil
}
func (a AppInstallService) CheckExist(key string) (*response.AppInstalledCheck, error) {
func (a *AppInstallService) CheckExist(key string) (*response.AppInstalledCheck, error) {
res := &response.AppInstalledCheck{
IsExist: false,
}
@ -103,7 +125,7 @@ func (a AppInstallService) CheckExist(key string) (*response.AppInstalledCheck,
return res, nil
}
func (a AppInstallService) LoadPort(key string) (int64, error) {
func (a *AppInstallService) LoadPort(key string) (int64, error) {
app, err := appInstallRepo.LoadBaseInfo(key, "")
if err != nil {
return int64(0), nil
@ -111,15 +133,19 @@ func (a AppInstallService) LoadPort(key string) (int64, error) {
return app.Port, nil
}
func (a AppInstallService) LoadPassword(key string) (string, error) {
func (a *AppInstallService) LoadConnInfo(key string) (response.DatabaseConn, error) {
var data response.DatabaseConn
app, err := appInstallRepo.LoadBaseInfo(key, "")
if err != nil {
return "", nil
return data, nil
}
return app.Password, nil
data.Password = app.Password
data.ServiceName = app.ServiceName
data.Port = app.Port
return data, nil
}
func (a AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) {
func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) {
var (
installs []model.AppInstall
err error
@ -152,8 +178,8 @@ func (a AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]r
return handleInstalled(installs, false)
}
func (a AppInstallService) Operate(req request.AppInstalledOperate) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
install, err := appInstallRepo.GetFirstByCtx(context.Background(), commonRepo.WithByID(req.InstallId))
if err != nil {
return err
}
@ -180,49 +206,54 @@ func (a AppInstallService) Operate(req request.AppInstalledOperate) error {
}
return syncById(install.ID)
case constant.Delete:
tx, ctx := getTxAndContext()
if err := deleteAppInstall(ctx, install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete {
tx.Rollback()
if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete {
return err
}
tx.Commit()
return nil
case constant.Sync:
return syncById(install.ID)
case constant.Upgrade:
return updateInstall(install.ID, req.DetailId)
return upgradeInstall(install.ID, req.DetailId)
default:
return errors.New("operate not support")
}
}
func (a AppInstallService) Update(req request.AppInstalledUpdate) error {
func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
installed, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
if err != nil {
return err
}
changePort := false
var (
oldPorts []int
newPorts []int
)
port, ok := req.Params["PANEL_APP_PORT_HTTP"]
if ok {
portN := int(math.Ceil(port.(float64)))
if portN != installed.HttpPort {
oldPorts = append(oldPorts, installed.HttpPort)
changePort = true
httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params)
if err != nil {
return err
}
installed.HttpPort = httpPort
newPorts = append(newPorts, httpPort)
}
}
ports, ok := req.Params["PANEL_APP_PORT_HTTPS"]
if ok {
portN := int(math.Ceil(ports.(float64)))
if portN != installed.HttpsPort {
oldPorts = append(oldPorts, installed.HttpsPort)
httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params)
if err != nil {
return err
}
installed.HttpsPort = httpsPort
newPorts = append(newPorts, httpsPort)
}
}
@ -240,7 +271,7 @@ func (a AppInstallService) Update(req request.AppInstalledUpdate) error {
if err := env.Write(oldEnvMaps, envPath); err != nil {
return err
}
_ = appInstallRepo.Save(&installed)
_ = appInstallRepo.Save(context.Background(), &installed)
if err := rebuildApp(installed); err != nil {
return err
@ -267,16 +298,26 @@ func (a AppInstallService) Update(req request.AppInstalledUpdate) error {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
}
if changePort {
go func() {
_ = OperateFirewallPort(oldPorts, newPorts)
}()
}
return nil
}
func (a AppInstallService) SyncAll() error {
func (a *AppInstallService) SyncAll(systemInit bool) error {
allList, err := appInstallRepo.ListBy()
if err != nil {
return err
}
for _, i := range allList {
if i.Status == constant.Installing {
if systemInit {
i.Status = constant.Error
i.Message = "System restart causes application exception"
_ = appInstallRepo.Save(context.Background(), &i)
}
continue
}
if err := syncById(i.ID); err != nil {
@ -286,7 +327,7 @@ func (a AppInstallService) SyncAll() error {
return nil
}
func (a AppInstallService) GetServices(key string) ([]response.AppService, error) {
func (a *AppInstallService) GetServices(key string) ([]response.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
@ -310,7 +351,7 @@ func (a AppInstallService) GetServices(key string) ([]response.AppService, error
return res, nil
}
func (a AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, error) {
func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, error) {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
var versions []dto.AppVersion
if err != nil {
@ -335,7 +376,7 @@ func (a AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
return versions, nil
}
func (a AppInstallService) ChangeAppPort(req request.PortUpdate) error {
func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
if common.ScanPort(int(req.Port)) {
return buserr.WithDetail(constant.ErrPortInUsed, req.Port, nil)
}
@ -360,10 +401,14 @@ func (a AppInstallService) ChangeAppPort(req request.PortUpdate) error {
}
}
if err := OperateFirewallPort([]int{int(appInstall.Port)}, []int{int(req.Port)}); err != nil {
global.LOG.Errorf("allow firewall failed, err: %v", err)
}
return nil
}
func (a AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, error) {
func (a *AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, error) {
var res []dto.AppResource
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
@ -373,14 +418,12 @@ func (a AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, error
if err != nil {
return nil, err
}
if app.Type == "website" {
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID))
for _, website := range websites {
res = append(res, dto.AppResource{
Type: "website",
Name: website.PrimaryDomain,
})
}
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID))
for _, website := range websites {
res = append(res, dto.AppResource{
Type: "website",
Name: website.PrimaryDomain,
})
}
if app.Key == constant.AppOpenresty {
websites, _ := websiteRepo.GetBy()
@ -404,7 +447,7 @@ func (a AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, error
return res, nil
}
func (a AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
appInstall, err := getAppInstallByKey(key)
if err != nil {
return "", err
@ -426,7 +469,7 @@ func (a AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
return string(contentByte), nil
}
func (a AppInstallService) GetParams(id uint) ([]response.AppParam, error) {
func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) {
var (
res []response.AppParam
appForm dto.AppForm
@ -459,14 +502,20 @@ func (a AppInstallService) GetParams(id uint) ([]response.AppParam, error) {
}
appParam.LabelZh = form.LabelZh
appParam.LabelEn = form.LabelEn
appParam.Value = v
if form.Type == "service" {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
appParam.Value = appInstall.Name
res = append(res, appParam)
} else {
appParam.Value = v
res = append(res, appParam)
appParam.ShowValue = appInstall.Name
} else if form.Type == "select" {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
}
}
appParam.Values = form.Values
}
res = append(res, appParam)
}
}
return res, nil
@ -534,15 +583,15 @@ func syncById(installId uint) error {
if containerCount == 0 {
appInstall.Status = constant.Error
appInstall.Message = "container is not found"
return appInstallRepo.Save(&appInstall)
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == 0 && existedCount == 0 {
appInstall.Status = constant.Running
return appInstallRepo.Save(&appInstall)
return appInstallRepo.Save(context.Background(), &appInstall)
}
if existedCount == normalCount {
appInstall.Status = constant.Stopped
return appInstallRepo.Save(&appInstall)
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == normalCount {
appInstall.Status = constant.Error
@ -567,7 +616,7 @@ func syncById(installId uint) error {
errMsg.Write([]byte("\n"))
}
appInstall.Message = errMsg.String()
return appInstallRepo.Save(&appInstall)
return appInstallRepo.Save(context.Background(), &appInstall)
}
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
@ -579,7 +628,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
return nil
}
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, appKey, appInstall.Name)
lineBytes, err := ioutil.ReadFile(envPath)
lineBytes, err := os.ReadFile(envPath)
if err != nil {
return err
}
@ -622,7 +671,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
}, commonRepo.WithByID(appInstall.ID))
}
if param == "user-password" {
oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.Password)
oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.UserPassword)
newVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", value)
_ = appInstallRepo.BatchUpdateBy(map[string]interface{}{
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),

View File

@ -4,8 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/compose-spec/compose-go/types"
"github.com/subosito/gotenv"
"math"
"os"
"os/exec"
"path"
"reflect"
"strconv"
@ -23,6 +27,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
composeV2 "github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
)
@ -125,7 +130,23 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
return nil
}
func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBackup bool, forceDelete bool, deleteDB bool) error {
func handleAppInstallErr(ctx context.Context, install *model.AppInstall) error {
op := files.NewFileOp()
appDir := install.GetPath()
dir, _ := os.Stat(appDir)
if dir != nil {
_, _ = compose.Down(install.GetComposePath())
if err := op.DeleteDir(appDir); err != nil {
return err
}
}
if err := deleteLink(ctx, install, true, true); err != nil {
return err
}
return nil
}
func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete bool, deleteDB bool) error {
op := files.NewFileOp()
appDir := install.GetPath()
dir, _ := os.Stat(appDir)
@ -134,36 +155,34 @@ func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBacku
if err != nil && !forceDelete {
return handleErr(install, err, out)
}
if err := op.DeleteDir(appDir); err != nil && !forceDelete {
return err
}
}
tx, ctx := helper.GetTxAndContext()
defer tx.Rollback()
if err := appInstallRepo.Delete(ctx, install); err != nil {
return err
}
if err := deleteLink(ctx, &install, deleteDB, forceDelete); err != nil && !forceDelete {
return err
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key))
if install.App.Key == constant.AppMysql {
_ = mysqlRepo.DeleteAll(ctx)
}
uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name)
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
if deleteBackup {
localDir, err := loadLocalDir()
if err != nil && !forceDelete {
return err
}
localDir, _ := loadLocalDir()
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name)
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
global.LOG.Infof("delete app %s-%s backups successful", install.App.Key, install.Name)
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key))
if install.App.Key == constant.AppMysql {
_ = mysqlRepo.DeleteAll(ctx)
}
_ = op.DeleteDir(appDir)
tx.Commit()
return nil
}
@ -190,7 +209,7 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func updateInstall(installId uint, detailId uint) error {
func upgradeInstall(installId uint, detailId uint) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
return err
@ -205,7 +224,21 @@ func updateInstall(installId uint, detailId uint) error {
if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
return err
}
if _, err = compose.Down(install.GetComposePath()); err != nil {
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Key, "versions", detail.Version)
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
stdout, err := cmd.CombinedOutput()
if err != nil {
if stdout != nil {
return errors.New(string(stdout))
}
return err
}
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
}
return err
}
install.DockerCompose = detail.DockerCompose
@ -216,32 +249,51 @@ func updateInstall(installId uint, detailId uint) error {
if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil {
return err
}
if _, err = compose.Up(install.GetComposePath()); err != nil {
if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
}
return err
}
return appInstallRepo.Save(&install)
return appInstallRepo.Save(context.Background(), &install)
}
func getContainerNames(install model.AppInstall) ([]string, error) {
composeMap := install.DockerCompose
envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(install.Env), &envMap)
newEnvMap := make(map[string]string, len(envMap))
handleMap(envMap, newEnvMap)
project, err := compose.GetComposeProject([]byte(composeMap), newEnvMap)
envStr, err := coverEnvJsonToStr(install.Env)
if err != nil {
return nil, err
}
containerNames := []string{install.ContainerName}
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true)
if err != nil {
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
}
containerNames = append(containerNames, service.ContainerName)
containerMap[service.ContainerName] = struct{}{}
}
var containerNames []string
for k := range containerMap {
containerNames = append(containerNames, k)
}
return containerNames, nil
}
func coverEnvJsonToStr(envJson string) (string, error) {
envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(envJson), &envMap)
newEnvMap := make(map[string]string, len(envMap))
handleMap(envMap, newEnvMap)
envStr, err := gotenv.Marshal(newEnvMap)
if err != nil {
return "", err
}
return envStr, nil
}
func checkLimit(app model.App) error {
if app.Limit > 0 {
installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID))
@ -256,11 +308,9 @@ func checkLimit(app model.App) error {
}
func checkRequiredAndLimit(app model.App) error {
if err := checkLimit(app); err != nil {
return err
}
if app.Required != "" {
var requiredArray []string
if err := json.Unmarshal([]byte(app.Required), &requiredArray); err != nil {
@ -289,7 +339,6 @@ func checkRequiredAndLimit(app model.App) error {
}
}
}
return nil
}
@ -306,10 +355,17 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
}
}
func copyAppData(key, version, installName string, params map[string]interface{}) (err error) {
func copyAppData(key, version, installName string, params map[string]interface{}, isLocal bool) (err error) {
fileOp := files.NewFileOp()
resourceDir := path.Join(constant.AppResourceDir, key, "versions", version)
appResourceDir := constant.AppResourceDir
installAppDir := path.Join(constant.AppInstallDir, key)
appKey := key
if isLocal {
appResourceDir = constant.LocalAppResourceDir
appKey = strings.TrimPrefix(key, "local")
installAppDir = path.Join(constant.LocalAppInstallDir, appKey)
}
resourceDir := path.Join(appResourceDir, appKey, "versions", version)
if !fileOp.Stat(installAppDir) {
if err = fileOp.CreateDir(installAppDir, 0755); err != nil {
@ -339,19 +395,64 @@ func copyAppData(key, version, installName string, params map[string]interface{}
return
}
func upApp(composeFilePath string, appInstall model.AppInstall) {
out, err := compose.Up(composeFilePath)
if err != nil {
if out != "" {
appInstall.Message = out
} else {
appInstall.Message = err.Error()
// 处理文件夹权限等问题
func upAppPre(app model.App, appInstall *model.AppInstall) error {
if app.Key == "nexus" {
dataPath := path.Join(appInstall.GetPath(), "data")
if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil {
return err
}
}
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 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
}
err = composeService.ComposeUp()
if err != nil {
return err
}
return
} else {
return
}
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
_ = appInstallRepo.Save(&appInstall)
appInstall.Message = err.Error()
} else {
appInstall.Status = constant.Running
_ = appInstallRepo.Save(&appInstall)
}
exist, _ := appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID))
if exist.ID > 0 {
_ = appInstallRepo.Save(context.Background(), appInstall)
}
}
@ -390,20 +491,29 @@ func getAppDetails(details []model.AppDetail, versions []string) map[string]mode
return appDetails
}
func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
func getApps(oldApps []model.App, items []dto.AppDefine, isLocal bool) map[string]model.App {
apps := make(map[string]model.App, len(oldApps))
for _, old := range oldApps {
old.Status = constant.AppTakeDown
apps[old.Key] = old
}
for _, item := range items {
app, ok := apps[item.Key]
key := item.Key
if isLocal {
key = "local" + key
}
app, ok := apps[key]
if !ok {
app = model.App{}
}
if isLocal {
app.Resource = constant.AppResourceLocal
} else {
app.Resource = constant.AppResourceRemote
}
app.Name = item.Name
app.Limit = item.Limit
app.Key = item.Key
app.Key = key
app.ShortDescZh = item.ShortDescZh
app.ShortDescEn = item.ShortDescEn
app.Website = item.Website
@ -413,7 +523,7 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
app.CrossVersionUpdate = item.CrossVersionUpdate
app.Required = item.GetRequired()
app.Status = constant.AppNormal
apps[item.Key] = app
apps[key] = app
}
return apps
}
@ -426,7 +536,7 @@ func handleErr(install model.AppInstall, err error, out string) error {
reErr = errors.New(out)
install.Status = constant.Error
}
_ = appInstallRepo.Save(&install)
_ = appInstallRepo.Save(context.Background(), &install)
return reErr
}
@ -501,7 +611,7 @@ func getAppInstallByKey(key string) (model.AppInstall, error) {
return appInstall, nil
}
func updateToolApp(installed model.AppInstall) {
func updateToolApp(installed *model.AppInstall) {
tooKey, ok := dto.AppToolMap[installed.App.Key]
if !ok {
return
@ -537,7 +647,7 @@ func updateToolApp(installed model.AppInstall) {
return
}
toolInstall.Env = string(contentByte)
if err := appInstallRepo.Save(&toolInstall); err != nil {
if err := appInstallRepo.Save(context.Background(), &toolInstall); err != nil {
global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, err.Error())
return
}

View File

@ -26,6 +26,7 @@ type IAuthService interface {
SafeEntrance(c *gin.Context, code string) error
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLoginInfo, error)
}
func NewIAuthService() IAuthService {
@ -86,9 +87,9 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi
}
pass, err := encrypt.StringDecrypt(passwrodSetting.Value)
if err != nil {
return nil, constant.ErrAuth
return nil, err
}
if info.Password != pass && nameSetting.Value != info.Name {
if info.Password != pass || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
@ -118,7 +119,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
j := jwt.NewJWT()
claims := j.CreateClaims(jwt.BaseClaims{
Name: name,
}, lifeTime)
})
token, err := j.CreateToken(claims)
if err != nil {
return nil, err

View File

@ -9,6 +9,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/cloud_storage"
@ -26,7 +27,7 @@ type IBackupService interface {
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(ireq dto.BackupOperate) error
BatchDelete(ids []uint) error
Delete(id uint) error
BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
@ -53,36 +54,13 @@ func NewIBackupService() IBackupService {
func (u *BackupService) List() ([]dto.BackupInfo, error) {
ops, err := backupRepo.List(commonRepo.WithOrderBy("created_at desc"))
var dtobas []dto.BackupInfo
ossExist, s3Exist, sftpExist, minioExist := false, false, false, false
for _, group := range ops {
switch group.Type {
case "OSS":
ossExist = true
case "S3":
s3Exist = true
case "SFTP":
sftpExist = true
case "MINIO":
minioExist = true
}
var item dto.BackupInfo
if err := copier.Copy(&item, &group); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtobas = append(dtobas, item)
}
if !ossExist {
dtobas = append(dtobas, dto.BackupInfo{Type: "OSS"})
}
if !s3Exist {
dtobas = append(dtobas, dto.BackupInfo{Type: "S3"})
}
if !sftpExist {
dtobas = append(dtobas, dto.BackupInfo{Type: "SFTP"})
}
if !minioExist {
dtobas = append(dtobas, dto.BackupInfo{Type: "MINIO"})
}
dtobas = append(dtobas, u.loadByType("LOCAL", ops))
dtobas = append(dtobas, u.loadByType("OSS", ops))
dtobas = append(dtobas, u.loadByType("S3", ops))
dtobas = append(dtobas, u.loadByType("SFTP", ops))
dtobas = append(dtobas, u.loadByType("MINIO", ops))
dtobas = append(dtobas, u.loadByType("COS", ops))
dtobas = append(dtobas, u.loadByType("KODO", ops))
return dtobas, err
}
@ -123,7 +101,7 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
case constant.Sftp:
varMap["username"] = backup.AccessKey
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
}
@ -171,7 +149,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
case constant.Sftp:
varMap["username"] = backupDto.AccessKey
varMap["password"] = backupDto.Credential
case constant.OSS, constant.S3, constant.MinIo:
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backupDto.AccessKey
varMap["secretKey"] = backupDto.Credential
}
@ -182,8 +160,12 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
return client.ListBuckets()
}
func (u *BackupService) BatchDelete(ids []uint) error {
return backupRepo.Delete(commonRepo.WithIdsIn(ids))
func (u *BackupService) Delete(id uint) error {
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByBackupID(id))
if len(cronjobs) != 0 {
return buserr.New(constant.ErrBackupInUsed)
}
return backupRepo.Delete(commonRepo.WithByID(id))
}
func (u *BackupService) BatchDeleteRecord(ids []uint) error {
@ -277,7 +259,7 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
case constant.Sftp:
varMap["username"] = backup.AccessKey
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
}
@ -290,6 +272,19 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
return backClient, nil
}
func (u *BackupService) loadByType(accountType string, accounts []model.BackupAccount) dto.BackupInfo {
for _, account := range accounts {
if account.Type == accountType {
var item dto.BackupInfo
if err := copier.Copy(&item, &account); err != nil {
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
}
return item
}
}
return dto.BackupInfo{Type: accountType}
}
func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {

View File

@ -1,6 +1,7 @@
package service
import (
"context"
"encoding/json"
"fmt"
"io/fs"
@ -32,6 +33,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
return err
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName)
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
@ -97,7 +99,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
return err
}
appPath := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, install.App.Key, install.Name)
appPath := fmt.Sprintf("%s/%s", install.GetPath(), install.Name)
if err := handleTar(appPath, tmpDir, "app.tar.gz", ""); err != nil {
return err
}
@ -192,7 +194,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}
oldInstall.Status = constant.Running
if err := appInstallRepo.Save(install); err != nil {
if err := appInstallRepo.Save(context.Background(), install); err != nil {
global.LOG.Errorf("save db app install failed, err: %v", err)
return err
}

View File

@ -2,7 +2,6 @@ package service
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
@ -176,11 +175,11 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
if appendonly == "yes" && redisInfo.Version == "6.0.16" {
itemName = "appendonly.aof"
}
input, err := ioutil.ReadFile(recoverFile)
input, err := os.ReadFile(recoverFile)
if err != nil {
return err
}
if err = ioutil.WriteFile(composeDir+"/data/"+itemName, input, 0640); err != nil {
if err = os.WriteFile(composeDir+"/data/"+itemName, input, 0640); err != nil {
return err
}
}

View File

@ -3,8 +3,9 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"os/exec"
"sort"
"strconv"
@ -21,6 +22,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -33,7 +35,7 @@ type IContainerService interface {
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) error
CreateCompose(req dto.ComposeCreate) (string, error)
ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerCreate) error
ContainerOperation(req dto.ContainerOperation) error
@ -44,6 +46,8 @@ type IContainerService interface {
CreateNetwork(req dto.NetworkCreat) error
DeleteVolume(req dto.BatchDelete) error
CreateVolume(req dto.VolumeCreat) error
TestCompose(req dto.ComposeCreate) (bool, error)
ComposeUpdate(req dto.ComposeUpdate) error
}
func NewIContainerService() IContainerService {
@ -155,10 +159,12 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
return err
}
config := &container.Config{
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
}
hostConf := &container.HostConfig{
AutoRemove: req.AutoRemove,
@ -190,14 +196,21 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
}
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
container, err := client.ContainerCreate(context.TODO(), config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
ctx := context.Background()
if !checkImageExist(client, req.Image) {
if err := pullImages(ctx, client, req.Image); err != nil {
return err
}
}
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
if err != nil {
_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return err
}
global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name)
if err := client.ContainerStart(context.TODO(), container.ID, types.ContainerStartOptions{}); err != nil {
_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
@ -215,9 +228,9 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
case constant.ContainerOpStart:
err = client.ContainerStart(ctx, req.Name, types.ContainerStartOptions{})
case constant.ContainerOpStop:
err = client.ContainerStop(ctx, req.Name, nil)
err = client.ContainerStop(ctx, req.Name, container.StopOptions{})
case constant.ContainerOpRestart:
err = client.ContainerRestart(ctx, req.Name, nil)
err = client.ContainerRestart(ctx, req.Name, container.StopOptions{})
case constant.ContainerOpKill:
err = client.ContainerKill(ctx, req.Name, "SIGKILL")
case constant.ContainerOpPause:
@ -239,7 +252,7 @@ func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
}
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", err
return "", errors.New(string(stdout))
}
return string(stdout), nil
}
@ -255,7 +268,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
@ -320,3 +333,33 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64)
}
return rx, tx
}
func checkImageExist(client *client.Client, image string) bool {
images, err := client.ImageList(context.Background(), types.ImageListOptions{})
if err != nil {
fmt.Println(err)
return false
}
for _, img := range images {
for _, tag := range img.RepoTags {
if tag == image || tag == image+":latest" {
return true
}
}
}
return false
}
func pullImages(ctx context.Context, client *client.Client, image string) error {
out, err := client.ImagePull(ctx, image, types.ImagePullOptions{})
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(io.Discard, out)
if err != nil {
return err
}
return nil
}

View File

@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"sort"
"strings"
"time"
@ -123,41 +125,49 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
return int64(total), BackDatas, nil
}
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
if req.From == "template" {
template, err := composeRepo.Get(commonRepo.WithByID(req.Template))
if err != nil {
return err
}
req.From = "edit"
req.File = template.Content
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := u.loadPath(&req); err != nil {
return false, err
}
if req.From == "edit" {
dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name)
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return err
}
}
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
return false, errors.New(string(stdout))
}
return true, nil
}
path := fmt.Sprintf("%s/docker-compose.yml", dir)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(string(req.File))
write.Flush()
req.Path = path
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
if err := u.loadPath(&req); err != nil {
return "", err
}
global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name)
if stdout, err := compose.Up(req.Path); err != nil {
return errors.New(string(stdout))
}
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
return nil
if req.From == "path" {
req.Name = path.Base(strings.ReplaceAll(req.Path, "/"+path.Base(req.Path), ""))
}
logName := path.Dir(req.Path) + "/compose.log"
file, err := os.OpenFile(logName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
go func() {
defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
stdout, err := cmd.CombinedOutput()
_, _ = file.Write(stdout)
if err != nil {
global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err)
_, _ = compose.Down(req.Path)
_, _ = file.WriteString("docker-compose up failed!")
return
}
global.LOG.Infof("docker-compose up %s successful!", req.Name)
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
_, _ = file.WriteString("docker-compose up successful!")
}()
return logName, nil
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
@ -199,3 +209,34 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
return nil
}
func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
if req.From == "template" {
template, err := composeRepo.Get(commonRepo.WithByID(req.Template))
if err != nil {
return err
}
req.From = "edit"
req.File = template.Content
}
if req.From == "edit" {
dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name)
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return err
}
}
path := fmt.Sprintf("%s/docker-compose.yml", dir)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(string(req.File))
write.Flush()
req.Path = path
}
return nil
}

View File

@ -10,7 +10,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
)
@ -37,14 +36,14 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
}
var (
data []dto.Volume
records []*types.Volume
records []*volume.Volume
)
sort.Slice(list.Volumes, func(i, j int) bool {
return list.Volumes[i].CreatedAt > list.Volumes[j].CreatedAt
})
total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
records = make([]*types.Volume, 0)
records = make([]*volume.Volume, 0)
} else {
if end >= total {
end = total
@ -119,7 +118,7 @@ func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error {
}
}
}
options := volume.VolumeCreateBody{
options := volume.CreateOptions{
Name: req.Name,
Driver: req.Driver,
DriverOpts: stringsToMap(req.Options),

View File

@ -2,17 +2,15 @@ package service
import (
"bufio"
"encoding/json"
"fmt"
"os"
"strings"
"path"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@ -27,7 +25,10 @@ type ICronjobService interface {
HandleOnce(id uint) error
Update(id uint, req dto.CronjobUpdate) error
UpdateStatus(id uint, status string) error
Delete(ids []uint) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob) (int, error)
CleanRecord(req dto.CronjobClean) error
}
func NewICronjobService() ICronjobService {
@ -79,6 +80,44 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
cronjob, err := cronjobRepo.Get(commonRepo.WithByID(req.CronjobID))
if err != nil {
return err
}
if req.CleanData && cronjob.Type != "shell" && cronjob.Type != "curl" {
cronjob.RetainCopies = 0
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return err
}
if backup.Type != "LOCAL" {
localDir, err := loadLocalDir()
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
} else {
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
}
}
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
if err != nil {
return err
}
for _, del := range delRecords {
_ = os.RemoveAll(del.Records)
}
if err := cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(req.CronjobID))); err != nil {
return err
}
return nil
}
func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
record, _ := cronjobRepo.GetRecord(commonRepo.WithByID(down.RecordID))
if record.ID == 0 {
@ -92,69 +131,23 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
if cronjob.ID == 0 {
return "", constant.ErrRecordNotFound
}
global.LOG.Infof("start to download records %s from %s", cronjob.Type, backup.Type)
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
if backup.Type == "LOCAL" || record.FromLocal {
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
return "", constant.ErrRecordNotFound
}
return record.File, nil
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
varMap["type"] = backup.Type
if backup.Type != "LOCAL" {
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
varMap["username"] = backup.AccessKey
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
if err != nil {
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
}
global.LOG.Info("new backup client successful")
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
name := fmt.Sprintf("%s%s.tar.gz", commonDir, record.StartTime.Format("20060102150405"))
if cronjob.Type == "database" {
name = fmt.Sprintf("%s%s.gz", commonDir, record.StartTime.Format("20060102150405"))
}
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, commonDir)
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(tempPath, os.ModePerm); err != nil {
global.LOG.Errorf("mkdir %s failed, err: %v", tempPath, err)
}
}
global.LOG.Infof("download records %s from %s to %s", name, commonDir, tempPath)
targetPath := tempPath + strings.ReplaceAll(name, commonDir, "")
if _, err = os.Stat(targetPath); err != nil && os.IsNotExist(err) {
isOK, err := backClient.Download(name, targetPath)
if !isOK {
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
}
}
return targetPath, nil
}
if _, ok := varMap["dir"]; !ok {
return "", errors.New("load local backup dir failed")
}
global.LOG.Infof("record is save in local dir %s", varMap["dir"])
switch cronjob.Type {
case "website":
return fmt.Sprintf("%v/website/%s/website_%s_%s.tar.gz", varMap["dir"], cronjob.Website, cronjob.Website, record.StartTime.Format("20060102150405")), nil
case "database":
mysqlInfo, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return "", fmt.Errorf("load mysqlInfo failed, err: %v", err)
}
return fmt.Sprintf("%v/database/mysql/%s/%s/db_%s_%s.sql.gz", varMap["dir"], mysqlInfo.Name, cronjob.DBName, cronjob.DBName, record.StartTime.Format("20060102150405")), nil
case "directory":
return fmt.Sprintf("%v/%s/%s/directory%s_%s.tar.gz", varMap["dir"], cronjob.Type, cronjob.Name, strings.ReplaceAll(cronjob.SourceDir, "/", "_"), record.StartTime.Format("20060102150405")), nil
default:
return "", fmt.Errorf("not support type %s", cronjob.Type)
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, record.File)
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", constant.ErrRecordNotFound
}
return tempPath, nil
}
func (u *CronjobService) HandleOnce(id uint) error {
@ -201,21 +194,23 @@ func (u *CronjobService) StartJob(cronjob *model.Cronjob) (int, error) {
return entryID, nil
}
func (u *CronjobService) Delete(ids []uint) error {
if len(ids) == 1 {
if err := u.HandleDelete(ids[0]); err != nil {
func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
for _, id := range req.IDs {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(id))
if cronjob.ID == 0 {
return errors.New("find cronjob in db failed")
}
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData}); err != nil {
return err
}
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
return cronjobRepo.Delete(commonRepo.WithByID(ids[0]))
}
cronjobs, err := cronjobRepo.List(commonRepo.WithIdsIn(ids))
if err != nil {
return err
}
for i := range cronjobs {
_ = u.HandleDelete(ids[i])
}
return cronjobRepo.Delete(commonRepo.WithIdsIn(ids))
return nil
}
func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
@ -238,6 +233,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap := make(map[string]interface{})
upMap["entry_id"] = newEntryID
upMap["name"] = req.Name
upMap["spec"] = cronjob.Spec
upMap["script"] = req.Script
upMap["spec_type"] = req.SpecType
upMap["week"] = req.Week

View File

@ -3,18 +3,18 @@ package service
import (
"context"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
)
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
@ -22,19 +22,19 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
message []byte
err error
)
record := cronjobRepo.StartRecords(cronjob.ID, "")
record.FromLocal = cronjob.KeepLocal
record := cronjobRepo.StartRecords(cronjob.ID, cronjob.KeepLocal, "")
go func() {
switch cronjob.Type {
case "shell":
if len(cronjob.Script) == 0 {
return
}
stdout, errExec := cmd.Exec(cronjob.Script)
stdout, errExec := cmd.ExecWithTimeOut(cronjob.Script, 5*time.Minute)
if errExec != nil {
err = errExec
}
message = []byte(stdout)
u.HandleRmExpired("LOCAL", "", cronjob, nil)
case "website":
record.File, err = u.HandleBackup(cronjob, record.StartTime)
case "database":
@ -48,11 +48,12 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
if len(cronjob.URL) == 0 {
return
}
stdout, errCurl := cmd.Exec("curl " + cronjob.URL)
stdout, errCurl := cmd.ExecWithTimeOut("curl "+cronjob.URL, 5*time.Minute)
if err != nil {
err = errCurl
}
message = []byte(stdout)
u.HandleRmExpired("LOCAL", "", cronjob, nil)
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
@ -69,11 +70,6 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
}
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
var (
backupDir string
fileName string
record model.BackupRecord
)
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return "", err
@ -90,141 +86,60 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
if err != nil {
return "", err
}
fileName = fmt.Sprintf("db_%s_%s.sql.gz", cronjob.DBName, startTime.Format("20060102150405"))
backupDir = fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, cronjob.DBName)
if err = handleMysqlBackup(app, backupDir, cronjob.DBName, fileName); err != nil {
return "", err
}
record.Type = "mysql"
record.Name = app.Name
record.DetailName = cronjob.DBName
paths, err := u.handleDatabase(*cronjob, app, backup, startTime)
return strings.Join(paths, ","), err
case "website":
fileName = fmt.Sprintf("website_%s_%s.tar.gz", cronjob.Website, startTime.Format("20060102150405"))
backupDir = fmt.Sprintf("%s/website/%s", localDir, cronjob.Website)
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(cronjob.Website))
if err != nil {
return "", err
}
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
return "", err
}
record.Type = "website"
record.Name = website.PrimaryDomain
paths, err := u.handleWebsite(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
default:
fileName = fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
backupDir = fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
backupDir := fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
itemFileDir := fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
return "", err
}
}
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
if len(record.Name) != 0 {
record.FileName = fileName
record.FileDir = backupDir
record.Source = "LOCAL"
record.BackupType = backup.Type
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = itemFileDir
}
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return "", err
}
}
fullPath := fmt.Sprintf("%s/%s", record.FileDir, fileName)
if backup.Type == "LOCAL" {
u.HandleRmExpired(backup.Type, backupDir, cronjob, nil)
return fullPath, nil
}
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
}()
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return fullPath, err
}
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
return fullPath, err
}
u.HandleRmExpired(backup.Type, itemFileDir, cronjob, client)
if cronjob.KeepLocal {
u.HandleRmExpired("LOCAL", backupDir, cronjob, client)
}
return fullPath, nil
}
func (u *CronjobService) HandleDelete(id uint) error {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(id))
if cronjob.ID == 0 {
return errors.New("find cronjob in db failed")
}
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(id)))
dir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronjob.Type, cronjob.Name)
if _, err := os.Stat(dir); err == nil {
if err := os.RemoveAll(dir); err != nil {
global.LOG.Errorf("rm file %s/task/%s failed, err: %v", constant.DataDir, commonDir, err)
}
}
return nil
}
func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
if backType != "LOCAL" {
currentObjs, err := backClient.ListObjects(backupDir + "/")
if err != nil {
global.LOG.Errorf("list bucket object %s failed, err: %v", backupDir, err)
return
}
for i := 0; i < len(currentObjs)-int(cronjob.RetainCopies); i++ {
_, _ = backClient.Delete(currentObjs[i].(string))
}
return
}
files, err := ioutil.ReadDir(backupDir)
if err != nil {
global.LOG.Errorf("read dir %s failed, err: %v", backupDir, err)
return
}
if len(files) == 0 {
return
}
prefix := ""
switch cronjob.Type {
case "database":
prefix = "db_"
case "website":
prefix = "website_"
case "directory":
prefix = "directory_"
}
dbCopies := uint64(0)
for i := len(files) - 1; i >= 0; i-- {
if strings.HasPrefix(files[i].Name(), prefix) {
dbCopies++
if dbCopies > cronjob.RetainCopies {
_ = os.Remove(backupDir + "/" + files[i].Name())
_ = backupRepo.DeleteRecord(context.Background(), backupRepo.WithByFileName(files[i].Name()))
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
}()
}
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
return "", err
}
}
u.HandleRmExpired(backup.Type, 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/%s", cronjob.Type, cronjob.Name, fileName), nil
}
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)))
}
func (u *CronjobService) HandleRmExpired(backType, 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++ {
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(records[i].ID)))
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)))
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
}
}
}
@ -255,7 +170,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.Exec(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
@ -272,10 +187,144 @@ func handleUnTar(sourceFile, targetDir string) error {
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands)
stdout, err := cmd.Exec(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
}
return nil
}
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInfo, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var dblist []string
if cronjob.DBName == "all" {
mysqlService := NewIMysqlService()
dblist, err = mysqlService.ListDBName()
if err != nil {
return paths, err
}
} else {
dblist = append(dblist, cronjob.DBName)
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return paths, err
}
}
for _, dbName := range dblist {
var record model.BackupRecord
record.Type = "mysql"
record.Name = app.Name
record.Source = "LOCAL"
record.BackupType = backup.Type
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, dbName)
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
if err = handleMysqlBackup(app, backupDir, dbName, record.FileName); err != nil {
return paths, err
}
record.DetailName = dbName
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(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)
return paths, err
}
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
return paths, nil
}
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var weblist []string
if cronjob.Website == "all" {
weblist, err = NewIWebsiteService().GetWebsiteOptions()
if err != nil {
return paths, err
}
} else {
weblist = append(weblist, cronjob.Website)
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return paths, err
}
}
for _, websiteItem := range weblist {
var record model.BackupRecord
record.Type = "website"
record.Name = cronjob.Website
record.Source = "LOCAL"
record.BackupType = backup.Type
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(websiteItem))
if err != nil {
return paths, err
}
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = strings.ReplaceAll(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
}
record.Name = website.PrimaryDomain
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return paths, err
}
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
return paths, nil
}

View File

@ -2,9 +2,11 @@ package service
import (
"encoding/json"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
@ -39,27 +41,6 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
ss, _ := json.Marshal(hostInfo)
baseInfo.VirtualizationSystem = string(ss)
apps, err := appRepo.GetBy()
if err != nil {
return nil, err
}
for _, app := range apps {
switch app.Key {
case "dateease":
baseInfo.DateeaseID = app.ID
case "halo":
baseInfo.HaloID = app.ID
case "metersphere":
baseInfo.MeterSphereID = app.ID
case "jumpserver":
baseInfo.JumpServerID = app.ID
case "kubeoperator":
baseInfo.KubeoperatorID = app.ID
case "kubepi":
baseInfo.KubepiID = app.ID
}
}
appInstall, err := appInstallRepo.ListBy()
if err != nil {
return nil, err
@ -120,15 +101,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
state, _ := disk.Usage("/")
currentInfo.Total = state.Total
currentInfo.Free = state.Free
currentInfo.Used = state.Used
currentInfo.UsedPercent = state.UsedPercent
currentInfo.InodesTotal = state.InodesTotal
currentInfo.InodesUsed = state.InodesUsed
currentInfo.InodesFree = state.InodesFree
currentInfo.InodesUsedPercent = state.InodesUsedPercent
currentInfo.DiskData = loadDiskInfo()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
@ -136,20 +109,17 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOTime += state.ReadTime / 1000 / 1000
if state.WriteTime > state.ReadTime {
currentInfo.IOTime += state.WriteTime / 1000 / 1000
}
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
} else {
diskInfo, _ := disk.IOCounters(ioOption)
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOTime += state.ReadTime / 1000 / 1000
if state.WriteTime > state.ReadTime {
currentInfo.IOTime += state.WriteTime / 1000 / 1000
}
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
}
@ -172,3 +142,67 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.ShotTime = time.Now()
return &currentInfo
}
type diskInfo struct {
Type string
Mount string
Device string
}
func loadDiskInfo() []dto.DiskInfo {
var datas []dto.DiskInfo
stdout, err := cmd.Exec("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")
if err != nil {
return datas
}
lines := strings.Split(stdout, "\n")
var mounts []diskInfo
var excludes = []string{"/mnt/cdrom", "/boot", "/boot/efi", "/dev", "/dev/shm", "/run/lock", "/run", "/run/shm", "/run/user"}
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 7 {
continue
}
if fields[1] == "tmpfs" {
continue
}
if strings.Contains(fields[2], "M") || strings.Contains(fields[2], "K") {
continue
}
if strings.Contains(fields[6], "docker") {
continue
}
isExclude := false
for _, exclude := range excludes {
if exclude == fields[6] {
isExclude = true
}
}
if isExclude {
continue
}
mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: fields[6]})
}
for i := 0; i < len(mounts); i++ {
state, err := disk.Usage(mounts[i].Mount)
if err != nil {
continue
}
var itemData dto.DiskInfo
itemData.Path = mounts[i].Mount
itemData.Type = mounts[i].Type
itemData.Device = mounts[i].Device
itemData.Total = state.Total
itemData.Free = state.Free
itemData.Used = state.Used
itemData.UsedPercent = state.UsedPercent
itemData.InodesTotal = state.InodesTotal
itemData.InodesUsed = state.InodesUsed
itemData.InodesFree = state.InodesFree
itemData.InodesUsedPercent = state.InodesUsedPercent
datas = append(datas, itemData)
}
return datas
}

View File

@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
@ -94,25 +93,28 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
}
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", req.Name, req.Format, formatMap[req.Format])
if err := excuteSql(app.ContainerName, app.Password, createSql); err != nil {
if err := excSQL(app.ContainerName, app.Password, createSql); err != nil {
if strings.Contains(err.Error(), "ERROR 1007") {
return nil, buserr.New(constant.ErrDatabaseIsExist)
}
return nil, err
}
tmpPermission := req.Permission
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user '%s'@'%s' identified by '%s';", req.Username, tmpPermission, req.Password)); err != nil {
_ = excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", req.Name))
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("create user '%s'@'%s' identified by '%s';", req.Username, tmpPermission, req.Password)); err != nil {
_ = excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", req.Name))
if strings.Contains(err.Error(), "ERROR 1396") {
return nil, buserr.New(constant.ErrUserIsExist)
}
return nil, err
}
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to '%s'@'%s'", req.Name, req.Username, tmpPermission)
if req.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to '%s'@'%s'", mysql.Username, tmpPermission)
}
if app.Version == "5.7.39" {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
}
if err := excuteSql(app.ContainerName, app.Password, grantStr); err != nil {
if err := excSQL(app.ContainerName, app.Password, grantStr); err != nil {
return nil, err
}
@ -161,10 +163,10 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
return err
}
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
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 := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
return err
}
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
@ -292,7 +294,10 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user if not exists '%s'@'%s' identified by '%s';", mysql.Username, info.Value, mysql.Password)); err != nil {
return err
}
grantStr := fmt.Sprintf("grant all privileges on %s.* to '%s'@'%s'", mysql.Name, mysql.Username, info.Value)
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to '%s'@'%s'", mysql.Name, mysql.Username, info.Value)
if mysql.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to '%s'@'%s'", mysql.Username, info.Value)
}
if app.Version == "5.7.39" {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, mysql.Password)
}
@ -339,7 +344,7 @@ func (u *MysqlService) UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
var files []string
path := fmt.Sprintf("%s/mysql/%s/conf/my.cnf", constant.AppInstallDir, app.Name)
lineBytes, err := ioutil.ReadFile(path)
lineBytes, err := os.ReadFile(path)
if err != nil {
return err
}
@ -509,6 +514,21 @@ func excuteSql(containerName, password, command string) error {
return nil
}
func excSQL(containerName, password, command string) error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return buserr.WithDetail(constant.ErrExecTimeOut, containerName, nil)
}
if err != nil {
stdStr := strings.ReplaceAll(err.Error(), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
return errors.New(stdStr)
}
return nil
}
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
isOn := false
hasGroup := false

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@ -215,7 +214,7 @@ type redisConfig struct {
func confSet(redisName string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", constant.AppInstallDir, redisName)
lineBytes, err := ioutil.ReadFile(path)
lineBytes, err := os.ReadFile(path)
if err != nil {
return err
}

View File

@ -4,7 +4,6 @@ import (
"bufio"
"context"
"encoding/json"
"io/ioutil"
"os"
"path"
"strings"
@ -35,6 +34,7 @@ type daemonJsonItem struct {
Mirrors []string `json:"registry-mirrors"`
Registries []string `json:"insecure-registries"`
LiveRestore bool `json:"live-restore"`
IPTables bool `json:"iptables"`
ExecOpts []string `json:"exec-opts"`
}
@ -49,55 +49,60 @@ func (u *DockerService) LoadDockerStatus() string {
}
func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
status := constant.StatusRunning
var data dto.DaemonJsonConf
data.IPTables = true
data.Status = constant.StatusRunning
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) != "active\n" || err != nil {
status = constant.Stopped
data.Status = constant.Stopped
}
version := "-"
data.IsSwarm = false
stdout2, _ := cmd.Exec("docker info | grep Swarm")
if string(stdout2) == " Swarm: active\n" {
data.IsSwarm = true
}
data.Version = "-"
client, err := docker.NewDockerClient()
if err == nil {
ctx := context.Background()
itemVersion, err := client.ServerVersion(ctx)
if err == nil {
version = itemVersion.Version
data.Version = itemVersion.Version
}
}
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
return &dto.DaemonJsonConf{Status: status, Version: version}
return &data
}
file, err := ioutil.ReadFile(constant.DaemonJsonPath)
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return &dto.DaemonJsonConf{Status: status, Version: version}
return &data
}
var conf daemonJsonItem
deamonMap := make(map[string]interface{})
if err := json.Unmarshal(file, &deamonMap); err != nil {
return &dto.DaemonJsonConf{Status: status, Version: version}
return &data
}
arr, err := json.Marshal(deamonMap)
if err != nil {
return &dto.DaemonJsonConf{Status: status, Version: version}
return &data
}
if err := json.Unmarshal(arr, &conf); err != nil {
return &dto.DaemonJsonConf{Status: status, Version: version}
return &data
}
driver := "cgroupfs"
if _, ok := deamonMap["iptables"]; !ok {
conf.IPTables = true
}
data.CgroupDriver = "cgroupfs"
for _, opt := range conf.ExecOpts {
if strings.HasPrefix(opt, "native.cgroupdriver=") {
driver = strings.ReplaceAll(opt, "native.cgroupdriver=", "")
data.CgroupDriver = strings.ReplaceAll(opt, "native.cgroupdriver=", "")
break
}
}
data := dto.DaemonJsonConf{
Status: status,
Version: version,
Mirrors: conf.Mirrors,
Registries: conf.Registries,
LiveRestore: conf.LiveRestore,
CgroupDriver: driver,
}
data.Mirrors = conf.Mirrors
data.Registries = conf.Registries
data.IPTables = conf.IPTables
data.LiveRestore = conf.LiveRestore
return &data
}
@ -109,7 +114,7 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
_, _ = os.Create(constant.DaemonJsonPath)
}
file, err := ioutil.ReadFile(constant.DaemonJsonPath)
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
}
@ -131,6 +136,11 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
} else {
deamonMap["live-restore"] = req.LiveRestore
}
if req.IPTables {
delete(deamonMap, "iptables")
} else {
deamonMap["iptables"] = false
}
if opts, ok := deamonMap["exec-opts"]; ok {
if optsValue, isArray := opts.([]interface{}); isArray {
for i := 0; i < len(optsValue); i++ {
@ -147,11 +157,15 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
deamonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"}
}
}
if len(deamonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
return nil
}
newJson, err := json.MarshalIndent(deamonMap, "", "\t")
if err != nil {
return err
}
if err := ioutil.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
@ -163,6 +177,16 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
}
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
if len(req.File) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
return nil
}
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
}
file, err := os.OpenFile(constant.DaemonJsonPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err

View File

@ -2,78 +2,38 @@ package service
import "github.com/1Panel-dev/1Panel/backend/app/repo"
type ServiceGroup struct {
AuthService
DashboardService
AppService
AppInstallService
ContainerService
ImageService
ImageRepoService
ComposeTemplateService
DockerService
MysqlService
RedisService
CronjobService
HostService
GroupService
CommandService
FileService
SettingService
BackupService
WebsiteGroupService
WebsiteService
WebsiteDnsAccountService
WebsiteSSLService
WebsiteAcmeAccountService
NginxService
LogService
SnapshotService
UpgradeService
}
var ServiceGroupApp = new(ServiceGroup)
var (
commonRepo = repo.RepoGroupApp.CommonRepo
commonRepo = repo.NewCommonRepo()
appRepo = repo.RepoGroupApp.AppRepo
appTagRepo = repo.RepoGroupApp.AppTagRepo
appDetailRepo = repo.RepoGroupApp.AppDetailRepo
tagRepo = repo.RepoGroupApp.TagRepo
appInstallRepo = repo.RepoGroupApp.AppInstallRepo
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
appRepo = repo.NewIAppRepo()
appTagRepo = repo.NewIAppTagRepo()
appDetailRepo = repo.NewIAppDetailRepo()
tagRepo = repo.NewITagRepo()
appInstallRepo = repo.NewIAppInstallRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
mysqlRepo = repo.RepoGroupApp.MysqlRepo
mysqlRepo = repo.NewIMysqlRepo()
imageRepoRepo = repo.RepoGroupApp.ImageRepoRepo
composeRepo = repo.RepoGroupApp.ComposeTemplateRepo
imageRepoRepo = repo.NewIImageRepoRepo()
composeRepo = repo.NewIComposeTemplateRepo()
cronjobRepo = repo.RepoGroupApp.CronjobRepo
cronjobRepo = repo.NewICronjobRepo()
hostRepo = repo.RepoGroupApp.HostRepo
groupRepo = repo.RepoGroupApp.GroupRepo
commandRepo = repo.RepoGroupApp.CommandRepo
hostRepo = repo.NewIHostRepo()
groupRepo = repo.NewIGroupRepo()
commandRepo = repo.NewICommandRepo()
settingRepo = repo.RepoGroupApp.SettingRepo
backupRepo = repo.RepoGroupApp.BackupRepo
settingRepo = repo.NewISettingRepo()
backupRepo = repo.NewIBackupRepo()
websiteRepo = repo.NewIWebsiteRepo()
websiteGroupRepo = repo.RepoGroupApp.WebsiteGroupRepo
websiteDomainRepo = repo.RepoGroupApp.WebsiteDomainRepo
websiteDnsRepo = repo.RepoGroupApp.WebsiteDnsAccountRepo
websiteDomainRepo = repo.NewIWebsiteDomainRepo()
websiteDnsRepo = repo.NewIWebsiteDnsAccountRepo()
websiteSSLRepo = repo.NewISSLRepo()
websiteAcmeRepo = repo.NewIAcmeAccountRepo()
logRepo = repo.RepoGroupApp.LogRepo
logRepo = repo.NewILogRepo()
snapshotRepo = repo.NewISnapshotRepo()
runtimeRepo = repo.NewIRunTimeRepo()
)

View File

@ -22,7 +22,30 @@ import (
type FileService struct {
}
func (f FileService) GetFileList(op request.FileOption) (response.FileInfo, error) {
type IFileService interface {
GetFileList(op request.FileOption) (response.FileInfo, error)
SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error)
GetFileTree(op request.FileOption) ([]response.FileTree, error)
Create(op request.FileCreate) error
Delete(op request.FileDelete) error
BatchDelete(op request.FileBatchDelete) error
ChangeMode(op request.FileCreate) error
Compress(c request.FileCompress) error
DeCompress(c request.FileDeCompress) error
GetContent(op request.FileOption) (response.FileInfo, error)
SaveContent(edit request.FileEdit) error
FileDownload(d request.FileDownload) (string, error)
DirSize(req request.DirSizeReq) (response.DirSizeRes, error)
ChangeName(req request.FileRename) error
Wget(w request.FileWget) (string, error)
MvFile(m request.FileMove) error
}
func NewIFileService() IFileService {
return &FileService{}
}
func (f *FileService) GetFileList(op request.FileOption) (response.FileInfo, error) {
var fileInfo response.FileInfo
if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
return fileInfo, nil
@ -35,7 +58,7 @@ func (f FileService) GetFileList(op request.FileOption) (response.FileInfo, erro
return fileInfo, nil
}
func (f FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error) {
func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error) {
var (
files []response.UploadInfo
backData []response.UploadInfo
@ -65,7 +88,7 @@ func (f FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (int
return int64(total), backData, nil
}
func (f FileService) GetFileTree(op request.FileOption) ([]response.FileTree, error) {
func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, error) {
var treeArray []response.FileTree
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
@ -88,7 +111,7 @@ func (f FileService) GetFileTree(op request.FileOption) ([]response.FileTree, er
return append(treeArray, node), nil
}
func (f FileService) Create(op request.FileCreate) error {
func (f *FileService) Create(op request.FileCreate) error {
fo := files.NewFileOp()
if fo.Stat(op.Path) {
return buserr.New(constant.ErrFileIsExit)
@ -107,7 +130,7 @@ func (f FileService) Create(op request.FileCreate) error {
}
}
func (f FileService) Delete(op request.FileDelete) error {
func (f *FileService) Delete(op request.FileDelete) error {
fo := files.NewFileOp()
if op.IsDir {
return fo.DeleteDir(op.Path)
@ -116,7 +139,7 @@ func (f FileService) Delete(op request.FileDelete) error {
}
}
func (f FileService) BatchDelete(op request.FileBatchDelete) error {
func (f *FileService) BatchDelete(op request.FileBatchDelete) error {
fo := files.NewFileOp()
if op.IsDir {
for _, file := range op.Paths {
@ -134,12 +157,12 @@ func (f FileService) BatchDelete(op request.FileBatchDelete) error {
return nil
}
func (f FileService) ChangeMode(op request.FileCreate) error {
func (f *FileService) ChangeMode(op request.FileCreate) error {
fo := files.NewFileOp()
return fo.Chmod(op.Path, fs.FileMode(op.Mode))
}
func (f FileService) Compress(c request.FileCompress) error {
func (f *FileService) Compress(c request.FileCompress) error {
fo := files.NewFileOp()
if !c.Replace && fo.Stat(filepath.Join(c.Dst, c.Name)) {
return buserr.New(constant.ErrFileIsExit)
@ -147,12 +170,12 @@ func (f FileService) Compress(c request.FileCompress) error {
return fo.Compress(c.Files, c.Dst, c.Name, files.CompressType(c.Type))
}
func (f FileService) DeCompress(c request.FileDeCompress) error {
func (f *FileService) DeCompress(c request.FileDeCompress) error {
fo := files.NewFileOp()
return fo.Decompress(c.Path, c.Dst, files.CompressType(c.Type))
}
func (f FileService) GetContent(op request.FileOption) (response.FileInfo, error) {
func (f *FileService) GetContent(op request.FileOption) (response.FileInfo, error) {
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
return response.FileInfo{}, err
@ -160,7 +183,7 @@ func (f FileService) GetContent(op request.FileOption) (response.FileInfo, error
return response.FileInfo{FileInfo: *info}, nil
}
func (f FileService) SaveContent(edit request.FileEdit) error {
func (f *FileService) SaveContent(edit request.FileEdit) error {
info, err := files.NewFileInfo(files.FileOption{
Path: edit.Path,
Expand: false,
@ -173,18 +196,18 @@ func (f FileService) SaveContent(edit request.FileEdit) error {
return fo.WriteFile(edit.Path, strings.NewReader(edit.Content), info.FileMode)
}
func (f FileService) ChangeName(req request.FileRename) error {
func (f *FileService) ChangeName(req request.FileRename) error {
fo := files.NewFileOp()
return fo.Rename(req.OldName, req.NewName)
}
func (f FileService) Wget(w request.FileWget) (string, error) {
func (f *FileService) Wget(w request.FileWget) (string, error) {
fo := files.NewFileOp()
key := "file-wget-" + common.GetUuid()
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key)
}
func (f FileService) MvFile(m request.FileMove) error {
func (f *FileService) MvFile(m request.FileMove) error {
fo := files.NewFileOp()
if !fo.Stat(m.NewPath) {
return buserr.New(constant.ErrPathNotFound)
@ -217,7 +240,7 @@ func (f FileService) MvFile(m request.FileMove) error {
return nil
}
func (f FileService) FileDownload(d request.FileDownload) (string, error) {
func (f *FileService) FileDownload(d request.FileDownload) (string, error) {
filePath := d.Paths[0]
if d.Compress {
tempPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano()))
@ -233,7 +256,7 @@ func (f FileService) FileDownload(d request.FileDownload) (string, error) {
return filePath, nil
}
func (f FileService) DirSize(req request.DirSizeReq) (response.DirSizeRes, error) {
func (f *FileService) DirSize(req request.DirSizeReq) (response.DirSizeRes, error) {
fo := files.NewFileOp()
size, err := fo.GetDirSize(req.Path)
if err != nil {

View File

@ -0,0 +1,448 @@
package service
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/firewall"
fireClient "github.com/1Panel-dev/1Panel/backend/utils/firewall/client"
"github.com/jinzhu/copier"
)
const confPath = "/etc/sysctl.conf"
type FirewallService struct{}
type IFirewallService interface {
LoadBaseInfo() (dto.FirewallBaseInfo, error)
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
OperateFirewall(operation string) error
OperatePortRule(req dto.PortRuleOperate, reload bool) error
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
UpdatePortRule(req dto.PortRuleUpdate) error
UpdateAddrRule(req dto.AddrRuleUpdate) error
BacthOperateRule(req dto.BatchRuleOperate) error
}
func NewIFirewallService() IFirewallService {
return &FirewallService{}
}
func (u *FirewallService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
var baseInfo dto.FirewallBaseInfo
baseInfo.PingStatus = u.pingStatus()
baseInfo.Status = "not running"
baseInfo.Version = "-"
baseInfo.Name = "-"
client, err := firewall.NewFirewallClient()
if err != nil {
if err.Error() == "no such type" {
return baseInfo, nil
}
return baseInfo, err
}
baseInfo.Name = client.Name()
baseInfo.Status, err = client.Status()
if err != nil {
return baseInfo, err
}
if baseInfo.Status == "not running" {
return baseInfo, err
}
baseInfo.Version, err = client.Version()
if err != nil {
return baseInfo, err
}
return baseInfo, nil
}
func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}, error) {
var (
datas []fireClient.FireInfo
backDatas []fireClient.FireInfo
)
client, err := firewall.NewFirewallClient()
if err != nil {
return 0, nil, err
}
if req.Type == "port" {
ports, err := client.ListPort()
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, port := range ports {
if strings.Contains(port.Port, req.Info) {
datas = append(datas, port)
}
}
} else {
datas = ports
}
} else {
addrs, err := client.ListAddress()
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, addr := range addrs {
if strings.Contains(addr.Address, req.Info) {
datas = append(datas, addr)
}
}
} else {
datas = addrs
}
}
total, start, end := len(datas), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]fireClient.FireInfo, 0)
} else {
if end >= total {
end = total
}
backDatas = datas[start:end]
}
if req.Type == "port" {
apps := u.loadPortByApp()
for i := 0; i < len(backDatas); i++ {
port, _ := strconv.Atoi(backDatas[i].Port)
backDatas[i].IsUsed = common.ScanPort(port)
if backDatas[i].Protocol == "udp" {
backDatas[i].IsUsed = common.ScanUDPPort(port)
continue
}
for _, app := range apps {
if app.HttpPort == backDatas[i].Port || app.HttpsPort == backDatas[i].Port {
backDatas[i].APPName = app.AppName
break
}
}
}
}
return int64(total), backDatas, nil
}
func (u *FirewallService) OperateFirewall(operation string) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
switch operation {
case "start":
if err := client.Start(); err != nil {
return err
}
if err := u.addPortsBeforeStart(client); err != nil {
_ = client.Stop()
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "stop":
if err := client.Stop(); err != nil {
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "disablePing":
return u.updatePingStatus("0")
case "enablePing":
return u.updatePingStatus("1")
}
return fmt.Errorf("not support such operation: %s", operation)
}
func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
if client.Name() == "ufw" {
req.Port = strings.ReplaceAll(req.Port, "-", ":")
if req.Operation == "remove" && req.Protocol == "tcp/udp" {
req.Protocol = ""
return u.operatePort(client, req)
}
}
if req.Protocol == "tcp/udp" {
if client.Name() == "firewalld" && strings.Contains(req.Port, ",") {
ports := strings.Split(req.Port, ",")
for _, port := range ports {
if len(port) == 0 {
continue
}
req.Port = port
req.Protocol = "tcp"
if err := u.operatePort(client, req); err != nil {
return err
}
req.Protocol = "udp"
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
req.Protocol = "tcp"
if err := u.operatePort(client, req); err != nil {
return err
}
req.Protocol = "udp"
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
if strings.Contains(req.Port, ",") {
ports := strings.Split(req.Port, ",")
for _, port := range ports {
req.Port = port
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
if err := u.operatePort(client, req); err != nil {
return err
}
}
}
if reload {
return client.Reload()
}
return nil
}
func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
var fireInfo fireClient.FireInfo
if err := copier.Copy(&fireInfo, &req); err != nil {
return err
}
addressList := strings.Split(req.Address, ",")
for _, addr := range addressList {
if len(addr) == 0 {
continue
}
fireInfo.Address = addr
if err := client.RichRules(fireInfo, req.Operation); err != nil {
return err
}
}
if reload {
return client.Reload()
}
return nil
}
func (u *FirewallService) UpdatePortRule(req dto.PortRuleUpdate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
if err := u.OperatePortRule(req.OldRule, false); err != nil {
return err
}
if err := u.OperatePortRule(req.NewRule, false); err != nil {
return err
}
return client.Reload()
}
func (u *FirewallService) UpdateAddrRule(req dto.AddrRuleUpdate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
if err := u.OperateAddressRule(req.OldRule, false); err != nil {
return err
}
if err := u.OperateAddressRule(req.NewRule, false); err != nil {
return err
}
return client.Reload()
}
func (u *FirewallService) BacthOperateRule(req dto.BatchRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
if req.Type == "port" {
for _, rule := range req.Rules {
if err := u.OperatePortRule(rule, false); err != nil {
return err
}
}
return client.Reload()
}
for _, rule := range req.Rules {
itemRule := dto.AddrRuleOperate{Operation: rule.Operation, Address: rule.Address, Strategy: rule.Strategy}
if err := u.OperateAddressRule(itemRule, false); err != nil {
return err
}
}
return client.Reload()
}
func OperateFirewallPort(oldPorts, newPorts []int) error {
client, err := firewall.NewFirewallClient()
if err != nil {
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
}
}
for _, port := range oldPorts {
if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "remove"); err != nil {
return err
}
}
return client.Reload()
}
func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.PortRuleOperate) error {
var fireInfo fireClient.FireInfo
if err := copier.Copy(&fireInfo, &req); err != nil {
return err
}
if client.Name() == "ufw" {
if len(fireInfo.Address) != 0 && fireInfo.Address != "Anywhere" {
return client.RichRules(fireInfo, req.Operation)
}
return client.Port(fireInfo, req.Operation)
}
if len(fireInfo.Address) != 0 || fireInfo.Strategy == "drop" {
return client.RichRules(fireInfo, req.Operation)
}
return client.Port(fireInfo, req.Operation)
}
type portOfApp struct {
AppName string
HttpPort string
HttpsPort string
}
func (u *FirewallService) loadPortByApp() []portOfApp {
var datas []portOfApp
apps, err := appInstallRepo.ListBy()
if err != nil {
return datas
}
for i := 0; i < len(apps); i++ {
datas = append(datas, portOfApp{
AppName: apps[i].App.Key,
HttpPort: strconv.Itoa(apps[i].HttpPort),
HttpsPort: strconv.Itoa(apps[i].HttpsPort),
})
}
systemPort, err := settingRepo.Get(settingRepo.WithByKey("ServerPort"))
if err != nil {
return datas
}
datas = append(datas, portOfApp{AppName: "1panel", HttpPort: systemPort.Value})
return datas
}
func (u *FirewallService) pingStatus() string {
if _, err := os.Stat("/etc/sysctl.conf"); err != nil {
return constant.StatusNone
}
commond := "cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= "
if cmd.HasNoPasswordSudo() {
commond = "sudo cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= "
}
stdout, _ := cmd.Exec(commond)
if stdout == "net/ipv4/icmp_echo_ignore_all=1\n" {
return constant.StatusEnable
}
return constant.StatusDisable
}
func (u *FirewallService) updatePingStatus(enabel string) error {
lineBytes, err := os.ReadFile(confPath)
if err != nil {
return err
}
files := strings.Split(string(lineBytes), "\n")
var newFiles []string
hasLine := false
for _, line := range files {
if strings.Contains(line, "net/ipv4/icmp_echo_ignore_all") || strings.HasPrefix(line, "net/ipv4/icmp_echo_ignore_all") {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enabel)
hasLine = true
} else {
newFiles = append(newFiles, line)
}
}
if !hasLine {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enabel)
}
file, err := os.OpenFile(confPath, os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(strings.Join(newFiles, "\n"))
if err != nil {
return err
}
commond := "sysctl -p"
if cmd.HasNoPasswordSudo() {
commond = "sudo sysctl -p"
}
stdout, err := cmd.Exec(commond)
if err != nil {
return fmt.Errorf("update ping status failed, err: %v", stdout)
}
return nil
}
func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) error {
serverPort, err := settingRepo.Get(settingRepo.WithByKey("ServerPort"))
if err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: serverPort.Value, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "22", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "80", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
apps := u.loadPortByApp()
for _, app := range apps {
if err := client.Port(fireClient.FireInfo{Port: app.HttpPort, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
}
return client.Reload()
}

View File

@ -22,7 +22,7 @@ func NewIGroupService() IGroupService {
}
func (u *GroupService) List(req dto.GroupSearch) ([]dto.GroupInfo, error) {
groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type))
groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type), commonRepo.WithOrderBy("is_default desc"), commonRepo.WithOrderBy("created_at desc"))
if err != nil {
return nil, constant.ErrRecordNotFound
}
@ -38,7 +38,7 @@ func (u *GroupService) List(req dto.GroupSearch) ([]dto.GroupInfo, error) {
}
func (u *GroupService) Create(req dto.GroupCreate) error {
group, _ := groupRepo.Get(commonRepo.WithByName(req.Name), commonRepo.WithByName(req.Name))
group, _ := groupRepo.Get(commonRepo.WithByName(req.Name), commonRepo.WithByType(req.Type))
if group.ID != 0 {
return constant.ErrRecordExist
}
@ -58,7 +58,7 @@ func (u *GroupService) Delete(id uint) error {
}
switch group.Type {
case "website":
websites, _ := websiteRepo.GetBy(commonRepo.WithByGroupID(id))
websites, _ := websiteRepo.GetBy(websiteRepo.WithGroupID(id))
if len(websites) > 0 {
return buserr.New(constant.ErrGroupIsUsed)
}
@ -73,7 +73,7 @@ func (u *GroupService) Delete(id uint) error {
func (u *GroupService) Update(req dto.GroupUpdate) error {
if req.IsDefault {
if err := groupRepo.CancelDefault(); err != nil {
if err := groupRepo.CancelDefault(req.Type); err != nil {
return err
}
}

View File

@ -1,6 +1,7 @@
package service
import (
"encoding/base64"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@ -15,6 +16,7 @@ type HostService struct{}
type IHostService interface {
TestLocalConn(id uint) bool
TestByInfo(req dto.HostConnTest) bool
GetHostInfo(id uint) (*model.Host, error)
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error)
@ -27,6 +29,46 @@ func NewIHostService() IHostService {
return &HostService{}
}
func (u *HostService) TestByInfo(req dto.HostConnTest) bool {
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
return false
}
req.Password = string(password)
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return false
}
req.PrivateKey = string(privateKey)
}
if len(req.Password) == 0 && len(req.PrivateKey) == 0 {
host, err := hostRepo.Get(hostRepo.WithByAddr(req.Addr))
if err != nil {
return false
}
req.Password = host.Password
req.AuthMode = host.AuthMode
req.PrivateKey = host.PrivateKey
req.PassPhrase = host.PassPhrase
}
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &req)
connInfo.PrivateKey = []byte(req.PrivateKey)
if len(req.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(req.PassPhrase)
}
client, err := connInfo.NewClient()
if err != nil {
return false
}
defer client.Close()
return true
}
func (u *HostService) TestLocalConn(id uint) bool {
var (
host model.Host
@ -47,6 +89,10 @@ 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.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
client, err := connInfo.NewClient()
if err != nil {
return false
@ -77,6 +123,11 @@ func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, inte
}
group, _ := groupRepo.Get(commonRepo.WithByID(host.GroupID))
item.GroupBelong = group.Name
if !item.RememberPassword {
item.Password = ""
item.PrivateKey = ""
item.PassPhrase = ""
}
dtoHosts = append(dtoHosts, item)
}
return total, dtoHosts, err
@ -144,6 +195,8 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
upMap["auth_mode"] = req.AuthMode
upMap["password"] = req.Password
upMap["private_key"] = req.PrivateKey
upMap["pass_phrase"] = req.PassPhrase
upMap["remember_password"] = req.RememberPassword
upMap["description"] = req.Description
if err := hostRepo.Update(sameHostID, upMap); err != nil {
return nil, err

View File

@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
@ -34,6 +33,7 @@ type IImageService interface {
ImageSave(req dto.ImageSave) error
ImagePush(req dto.ImagePush) (string, error)
ImageRemove(req dto.BatchDelete) error
ImageTag(req dto.ImageTag) error
}
func NewIImageService() IImageService {
@ -54,8 +54,8 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
return 0, nil, err
}
if len(req.Info) != 0 {
lenth, count := len(list), 0
for count < lenth {
length, count := len(list), 0
for count < length {
hasTag := false
for _, tag := range list[count].RepoTags {
if strings.Contains(tag, req.Info) {
@ -65,7 +65,7 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
}
if !hasTag {
list = append(list[:count], list[(count+1):]...)
lenth--
length--
} else {
count++
}
@ -122,6 +122,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
if err != nil {
return "", err
}
fileName := "Dockerfile"
if req.From == "edit" {
dir := fmt.Sprintf("%s/docker/build/%s", constant.DataDir, strings.ReplaceAll(req.Name, ":", "_"))
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
@ -141,7 +142,8 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
write.Flush()
req.Dockerfile = dir
} else {
req.Dockerfile = strings.ReplaceAll(req.Dockerfile, "/Dockerfile", "")
fileName = path.Base(req.Dockerfile)
req.Dockerfile = path.Dir(req.Dockerfile)
}
tar, err := archive.TarWithOptions(req.Dockerfile+"/", &archive.TarOptions{})
if err != nil {
@ -149,7 +151,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
}
opts := types.ImageBuildOptions{
Dockerfile: "Dockerfile",
Dockerfile: fileName,
Tags: []string{req.Name},
Remove: true,
Labels: stringsToMap(req.Tags),
@ -171,7 +173,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
body, err := io.ReadAll(res.Body)
if err != nil {
global.LOG.Errorf("build image %s failed, err: %v", req.Name, err)
_, _ = file.WriteString(fmt.Sprintf("build image %s failed, err: %v", req.Name, err))
@ -179,14 +181,14 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
return
}
if strings.Contains(string(body), "error") && strings.Contains(string(body), "failed:") {
if strings.Contains(string(body), "errorDetail") || strings.Contains(string(body), "error:") {
global.LOG.Errorf("build image %s failed", req.Name)
_, _ = file.Write(body)
_, _ = file.WriteString("image build failed!")
return
}
global.LOG.Infof("build image %s successful!", req.Name)
_, _ = io.Copy(file, res.Body)
_, _ = file.Write(body)
_, _ = file.WriteString("image build successful!")
}()
@ -272,7 +274,7 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
if err != nil {
return err
}
content, err := ioutil.ReadAll(res.Body)
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}

View File

@ -3,7 +3,6 @@ package service
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path"
"strings"
@ -190,7 +189,7 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
}
deamonMap := make(map[string]interface{})
file, err := ioutil.ReadFile(constant.DaemonJsonPath)
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
}
@ -226,7 +225,7 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
if err != nil {
return err
}
if err := ioutil.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
return nil

View File

@ -1,15 +1,16 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"io/ioutil"
"io"
"net/http"
"os"
"path"
"strings"
"time"
"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/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/files"
@ -18,6 +19,18 @@ import (
type NginxService struct {
}
type INginxService interface {
GetNginxConfig() (response.FileInfo, error)
GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error)
UpdateConfigByScope(req request.NginxConfigUpdate) error
GetStatus() (response.NginxStatus, error)
UpdateConfigFile(req request.NginxConfigFileUpdate) error
}
func NewINginxService() INginxService {
return &NginxService{}
}
func (n NginxService) GetNginxConfig() (response.FileInfo, error) {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
@ -55,7 +68,7 @@ func (n NginxService) GetStatus() (response.NginxStatus, error) {
if err != nil {
return response.NginxStatus{}, err
}
content, err := ioutil.ReadAll(res.Body)
content, err := io.ReadAll(res.Body)
if err != nil {
return response.NginxStatus{}, err
}

View File

@ -16,6 +16,7 @@ import (
"os"
"path"
"strings"
"time"
)
func getNginxFull(website *model.Website) (dto.NginxFull, error) {
@ -191,7 +192,7 @@ func opNginx(containerName, operate string) error {
if operate == constant.NginxCheck {
nginxCmd = fmt.Sprintf("docker exec -i %s %s", containerName, "nginx -t")
}
if out, err := cmd.Exec(nginxCmd); err != nil {
if out, err := cmd.ExecWithTimeOut(nginxCmd, 2*time.Second); err != nil {
return errors.New(out)
}
return nil

View File

@ -0,0 +1,276 @@
package service
import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"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/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/subosito/gotenv"
"path"
"path/filepath"
"strings"
"time"
)
type RuntimeService struct {
}
type IRuntimeService interface {
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
Create(create request.RuntimeCreate) error
Delete(id uint) error
Update(req request.RuntimeUpdate) error
Get(id uint) (res *response.RuntimeRes, err error)
}
func NewRuntimeService() IRuntimeService {
return &RuntimeService{}
}
func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithName(create.Name))
if exist != nil {
return buserr.New(constant.ErrNameIsExist)
}
if create.Resource == constant.ResourceLocal {
runtime := &model.Runtime{
Name: create.Name,
Resource: create.Resource,
Type: create.Type,
Version: create.Version,
Status: constant.RuntimeNormal,
}
return runtimeRepo.Create(context.Background(), runtime)
}
exist, _ = runtimeRepo.GetFirst(runtimeRepo.WithImage(create.Image))
if exist != nil {
return buserr.New(constant.ErrImageExist)
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID))
if err != nil {
return err
}
app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return err
}
fileOp := files.NewFileOp()
buildDir := path.Join(constant.AppResourceDir, app.Key, "versions", appDetail.Version, "build")
if !fileOp.Stat(buildDir) {
return buserr.New(constant.ErrDirNotFound)
}
runtimeDir := path.Join(constant.RuntimeDir, create.Type)
tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano()))
if err = fileOp.CopyDir(buildDir, tempDir); err != nil {
return
}
oldDir := path.Join(tempDir, "build")
newNameDir := path.Join(runtimeDir, create.Name)
defer func() {
if err != nil {
_ = fileOp.DeleteDir(newNameDir)
}
}()
if oldDir != newNameDir {
if err = fileOp.Rename(oldDir, newNameDir); err != nil {
return
}
if err = fileOp.DeleteDir(tempDir); err != nil {
return
}
}
composeContent, envContent, forms, err := handleParams(create.Image, create.Type, newNameDir, create.Params)
if err != nil {
return
}
composeService, err := getComposeService(create.Name, newNameDir, composeContent, envContent, false)
if err != nil {
return
}
runtime := &model.Runtime{
Name: create.Name,
DockerCompose: string(composeContent),
Env: string(envContent),
AppDetailID: create.AppDetailID,
Type: create.Type,
Image: create.Image,
Resource: create.Resource,
Status: constant.RuntimeBuildIng,
Version: create.Version,
Params: string(forms),
}
if err = runtimeRepo.Create(context.Background(), runtime); err != nil {
return
}
go buildRuntime(runtime, composeService, "")
return
}
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) {
var (
opts []repo.DBOption
res []response.RuntimeRes
)
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
}
if req.Status != "" {
opts = append(opts, runtimeRepo.WithStatus(req.Status))
}
total, runtimes, err := runtimeRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}
for _, runtime := range runtimes {
res = append(res, response.RuntimeRes{
Runtime: runtime,
})
}
return total, res, nil
}
func (r *RuntimeService) Delete(id uint) error {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return err
}
website, _ := websiteRepo.GetFirst(websiteRepo.WithRuntimeID(id))
if website.ID > 0 {
return buserr.New(constant.ErrDelWithWebsite)
}
if runtime.Resource == constant.ResourceAppstore {
client, err := docker.NewClient()
if err != nil {
return err
}
imageID, err := client.GetImageIDByName(runtime.Image)
if err != nil {
return err
}
if imageID != "" {
if err := client.DeleteImage(imageID); err != nil {
global.LOG.Errorf("delete image id [%s] error %v", imageID, err)
}
}
runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
if err := files.NewFileOp().DeleteDir(runtimeDir); err != nil {
return err
}
}
return runtimeRepo.DeleteBy(commonRepo.WithByID(id))
}
func (r *RuntimeService) Get(id uint) (*response.RuntimeRes, error) {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return nil, err
}
res := &response.RuntimeRes{}
res.Runtime = *runtime
if runtime.Resource == constant.ResourceLocal {
return res, nil
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID))
if err != nil {
return nil, err
}
res.AppID = appDetail.AppId
var (
appForm dto.AppForm
appParams []response.AppParam
)
if err := json.Unmarshal([]byte(runtime.Params), &appForm); err != nil {
return nil, err
}
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return nil, err
}
for _, form := range appForm.FormFields {
if v, ok := envs[form.EnvKey]; ok {
appParam := response.AppParam{
Edit: false,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
Required: form.Required,
}
if form.Edit {
appParam.Edit = true
}
appParam.LabelZh = form.LabelZh
appParam.LabelEn = form.LabelEn
appParam.Multiple = form.Multiple
appParam.Value = v
if form.Type == "select" {
if form.Multiple {
if v == "" {
appParam.Value = []string{}
} else {
appParam.Value = strings.Split(v, ",")
}
} else {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
}
}
}
appParam.Values = form.Values
}
appParams = append(appParams, appParam)
}
}
res.AppParams = appParams
return res, nil
}
func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
oldImage := runtime.Image
if runtime.Resource == constant.ResourceLocal {
runtime.Version = req.Version
return runtimeRepo.Save(runtime)
}
exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID))
if exist != nil {
return buserr.New(constant.ErrImageExist)
}
runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
composeContent, envContent, _, err := handleParams(req.Image, runtime.Type, runtimeDir, req.Params)
if err != nil {
return err
}
composeService, err := getComposeService(runtime.Name, runtimeDir, composeContent, envContent, false)
if err != nil {
return err
}
runtime.Image = req.Image
runtime.Env = string(envContent)
runtime.DockerCompose = string(composeContent)
runtime.Status = constant.RuntimeBuildIng
_ = runtimeRepo.Save(runtime)
client, err := docker.NewClient()
if err != nil {
return err
}
imageID, err := client.GetImageIDByName(oldImage)
if err != nil {
return err
}
go buildRuntime(runtime, composeService, imageID)
return nil
}

View File

@ -0,0 +1,107 @@
package service
import (
"fmt"
"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/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/docker/cli/cli/command"
"github.com/subosito/gotenv"
"os"
"path"
"strings"
)
func buildRuntime(runtime *model.Runtime, service *docker.ComposeService, oldImageID string) {
err := service.ComposeBuild()
if err != nil {
runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
} else {
runtime.Status = constant.RuntimeNormal
if oldImageID != "" {
client, err := docker.NewClient()
if err == nil {
newImageID, err := client.GetImageIDByName(runtime.Image)
if err == nil && newImageID != oldImageID {
global.LOG.Infof("delete imageID [%s] ", oldImageID)
if err := client.DeleteImage(oldImageID); err != nil {
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
} else {
global.LOG.Infof("delete old image success")
}
}
} else {
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
}
}
}
_ = runtimeRepo.Save(runtime)
}
func handleParams(image, runtimeType, runtimeDir string, params map[string]interface{}) (composeContent []byte, envContent []byte, forms []byte, err error) {
fileOp := files.NewFileOp()
composeContent, err = fileOp.GetContent(path.Join(runtimeDir, "docker-compose.yml"))
if err != nil {
return
}
env, err := gotenv.Read(path.Join(runtimeDir, ".env"))
if err != nil {
return
}
forms, err = fileOp.GetContent(path.Join(runtimeDir, "config.json"))
if err != nil {
return
}
params["IMAGE_NAME"] = image
if runtimeType == constant.RuntimePHP {
if extends, ok := params["PHP_EXTENSIONS"]; ok {
if extendsArray, ok := extends.([]interface{}); ok {
strArray := make([]string, len(extendsArray))
for i, v := range extendsArray {
strArray[i] = fmt.Sprintf("%v", v)
}
params["PHP_EXTENSIONS"] = strings.Join(strArray, ",")
}
}
}
newMap := make(map[string]string)
handleMap(params, newMap)
for k, v := range newMap {
env[k] = v
}
envStr, err := gotenv.Marshal(env)
if err != nil {
return
}
if err = gotenv.Write(env, path.Join(runtimeDir, ".env")); err != nil {
return
}
envContent = []byte(envStr)
return
}
func getComposeService(name, runtimeDir string, composeFile, env []byte, skipNormalization bool) (*docker.ComposeService, error) {
project, err := docker.GetComposeProject(name, runtimeDir, composeFile, env, skipNormalization)
if err != nil {
return nil, err
}
logPath := path.Join(runtimeDir, "build.log")
fileOp := files.NewFileOp()
if fileOp.Stat(logPath) {
_ = fileOp.DeleteFile(logPath)
}
file, err := os.Create(logPath)
if err != nil {
return nil, err
}
composeService, err := docker.NewComposeService(command.WithOutputStream(file))
if err != nil {
return nil, err
}
composeService.SetProject(project)
return composeService, nil
}

View File

@ -70,7 +70,14 @@ func (u *SettingService) UpdatePort(port uint) error {
if common.ScanPort(int(port)) {
return buserr.WithDetail(constant.ErrPortInUsed, port, nil)
}
serverPort, err := settingRepo.Get(settingRepo.WithByKey("ServerPort"))
if err != nil {
return err
}
portValue, _ := strconv.Atoi(serverPort.Value)
if err := OperateFirewallPort([]int{portValue}, []int{int(port)}); err != nil {
global.LOG.Errorf("set system firewall ports failed, err: %v", err)
}
if err := settingRepo.Update("ServerPort", strconv.Itoa(int(port))); err != nil {
return err
}

View File

@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@ -66,6 +65,9 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
if err != nil {
return fmt.Errorf("incorrect snapshot name format of %s", snap)
}
if strings.HasSuffix(snap, ".tar.gz") {
snap = strings.ReplaceAll(snap, ".tar.gz", "")
}
itemSnap := model.Snapshot{
Name: snap,
From: req.From,
@ -481,7 +483,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
func (u *SnapshotService) saveJson(snapJson SnapshotJson, path string) error {
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
if err := ioutil.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
return err
}
return nil
@ -790,7 +792,7 @@ func (u *SnapshotService) updateLiveRestore(enabled bool) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
return fmt.Errorf("load docker daemon.json conf failed, err: %v", err)
}
file, err := ioutil.ReadFile(constant.DaemonJsonPath)
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
}
@ -806,7 +808,7 @@ func (u *SnapshotService) updateLiveRestore(enabled bool) error {
if err != nil {
return err
}
if err := ioutil.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
@ -852,7 +854,7 @@ func (u *SnapshotService) handleTar(sourceDir, targetDir, name, exclusionRules s
commands := fmt.Sprintf("tar --warning=no-file-changed -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
global.LOG.Debug(commands)
stdout, err := cmd.Exec(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
@ -869,7 +871,7 @@ func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
commands := fmt.Sprintf("tar -zxf %s -C %s .", sourceDir, targetDir)
global.LOG.Debug(commands)
stdout, err := cmd.Exec(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)

Some files were not shown because too many files have changed in this diff Show More