Compare commits

...
53 Commits
Author SHA1 Message Date
ssongliu 1d5797fe68 fix: 修复了执行周期更新失败的问题 2023-03-31 17:23:30 +08:00
zhengkunwang223 bbe08ed218 style: 修改编辑器行尾符提示 (#465) 2023-03-31 07:18:15 +00:00
ssongliu 6e12eba356 fix: 修复了重启后计划任务执行周期错误的问题 (#462) 2023-03-31 04:34:14 +00:00
zhengkunwang223 3457b99df6 fix: 解决 nexus 安装失败的问题 (#461) 2023-03-31 04:16:14 +00:00
ssongliu 4ad3b82c84 fix: 解决 opencontainers/runc 系统漏洞 (#459) 2023-03-31 03:22:13 +00:00
ssongliu 85fc07c900 fix: 概览页磁盘显示过滤规则修改 (#458) 2023-03-31 03:20:19 +00:00
gengxin d71e2a74b4 feat: xterm-Support for Ctrl+MouseWheel to scaling fonts (#448)
#### What this PR does / why we need it?
#284 
Support for scaling fonts using Ctrl+mouse wheel

#446 
support Editor config the eol.
Currently, 1Panel only supports Linux, so directly set the default EOL to LF and add a new configuration item 

#### Summary of your change

#### Please indicate you've done the following:

- [x] Made sure tests are passing and test coverage is added if needed.
- [x] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-03-31 03:16:15 +00:00
zhengkunwang223 a18105349b fix: 解决某些版本的 ubuntu 启动不了的 BUG (#451) 2023-03-30 07:08:13 +00:00
wangdan-fit2cloud 6472227b6b fix: 修复登录页面从大屏切换小屏填写信息消失问题 (#450)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-03-30 06:42:13 +00:00
ssongliu c927132aa6 fix: 优化了概览页、监控页选项排序 2023-03-29 23:00:32 +08:00
ssongliu b06058ec18 fix: 修复了概览页未显示挂载磁盘信息的问题 (#442) 2023-03-29 13:28:13 +00:00
ssongliu 57329a26c8 fix: 修复了概览页 io 延迟数据错误的问题 (#441) 2023-03-29 12:56:13 +00:00
ssongliu 4a1aa84fa8 fix: 修改 jwt 超时时间为 1 小时 (#437) 2023-03-29 07:34:13 +00:00
ssongliu cbe9c83515 fix: 修复了 jwt 无法登录的问题 (#436) 2023-03-29 03:46:14 +00:00
ssongliu 67479e7060 fix: 修复了创建容器自动拉取镜像失败的问题 (#433) 2023-03-28 11:02:11 +00:00
ssongliu b454c959b4 fix: 修复了部分容器创建完直接退出的问题 (#432) 2023-03-28 09:50:11 +00:00
wangdan-fit2cloud e2d39b9ed0 fix: 登录页面优化适配 (#425) 2023-03-27 23:07:32 +08:00
maninhill b9fbcb0e73 chore: 简化社区软件协议文案 2023-03-26 10:14:25 +08:00
ssongliu 30cb471629 fix: 修改默认升级版本 (#390) 2023-03-24 03:52:10 +00:00
ssongliu d70c22dde8 fix: 解决设置默认分组的问题 (#385) 2023-03-23 12:58:33 +00:00
ssongliu 01bb6b7c01 fix: 解决 mfa 验证回车时触发页面刷新的问题 (#383) 2023-03-23 12:34:33 +00:00
ssongliu 4f4879759e fix: 修改主机默认分组名称 (#381) 2023-03-23 08:26:33 +00:00
ssongliu 92a410fcea fix: 修改构建失败判断条件 (#380) 2023-03-23 08:24:36 +00:00
zhengkunwang223 5d1fced8e9 feat: 网站分组删除校验 (#377) 2023-03-23 04:14:31 +00:00
liqiang-fit2cloud a56e5a8abe Pr@dev@docs add readme en.md (#378)
* docs: add README_EN.md.

* docs: add README_EN.md.

* docs: add README_EN.md.

* docs: add README_EN.md.
2023-03-23 11:43:43 +08:00
liqiang-fit2cloud 28ae7f3a0c docs: add README_EN.md. (#375) 2023-03-23 11:08:11 +08:00
zhengkunwang223 8d675c81c5 feat: 优化网站分组 (#372) 2023-03-22 13:42:30 +00:00
ssongliu 66a345364f fix: 优化分组显示 (#371) 2023-03-22 10:48:29 +00:00
zhengkunwang223 a3cb8be08f feat: 修改文件上传错误处理 (#370) 2023-03-22 10:34:29 +00:00
zhengkunwang223 0861b30a7b fix: 解决应用创建没有默认网络创建失败的BUG (#369) 2023-03-22 10:22:29 +00:00
ssongliu 36f2a3eb4b fix: 优化计划任务记录查询返回 (#364) 2023-03-22 10:16:29 +00:00
zhengkunwang223 092cbbf8da feat: 应用编辑增加 select 类型处理 (#363) 2023-03-22 07:38:30 +00:00
ssongliu 39e3e8f214 fix: 优化容器菜单页面状态加载样式 (#356) 2023-03-22 07:32:29 +00:00
ssongliu fa983bdcc9 fix: 优化导入备份限制 (#361) 2023-03-22 07:20:30 +00:00
zhengkunwang223 67bb30c10c fix: 解决文件编辑,快捷提示不显示的 BUG (#355) 2023-03-22 11:56:09 +08:00
ssongliu e85340ca5d fix: 数据库启用 WAL 模式,增加连接数与超时设置 (#349)
fix: 数据库启用 WAL 模式,增加连接数与超时设置
2023-03-21 11:00:28 +00:00
ssongliu a3a1e17849 fix: 解决监控时间控件显示不全的问题 (#348)
fix: 解决监控时间控件显示不全的问题
2023-03-21 10:48:27 +00:00
ssongliu 2601135225 fix: 解决快照同步恢复失败的问题 (#345)
fix: 解决快照同步恢复失败的问题
2023-03-21 10:46:31 +00:00
ssongliu c556affc91 fix: 解决数据库密码修改未同步到应用的问题 (#343)
fix: 解决数据库密码修改未同步到应用的问题
2023-03-21 10:44:35 +00:00
ssongliu 6ee9789a2f fix: 修改镜像构建和编排创建路径限制,增加 config 校验 (#342)
fix: 修改镜像构建和编排创建路径限制,增加 config 校验
2023-03-21 10:42:37 +00:00
ssongliu 68a457ae89 fix: 升级逻辑调整 (#341)
fix: 升级逻辑调整
2023-03-21 07:16:28 +00:00
zhengkunwang223 72237596f3 feat: 证书列表增加设置自动续签功能 (#337) 2023-03-21 14:42:50 +08:00
zhengkunwang223 f516333682 删除 issue 处理相关 workflow (#339) 2023-03-21 06:40:31 +00:00
zhengkunwang223 873af25684 fix: 解决网站定时任务没有执行的BUG (#333) 2023-03-21 05:10:27 +00:00
zhengkunwang223 8b058a873e feat: 登录增加社区软件许可协议同意选项 (#322) 2023-03-20 11:26:27 +00:00
ssongliu 0c5a5a6454 fix: 创建编排改为异步操作 (#318)
fix:  创建编排改为异步操作
2023-03-20 10:16:26 +00:00
zhengkunwang223 2096049708 feat: 删除自动创建 pr 的 workflow (#313) 2023-03-20 14:07:11 +08:00
1Panel-bot 1095aa2b65 fix: 解决镜像构建输出部分丢失的问题 (#310)
fix: 解决镜像构建输出部分丢失的问题
2023-03-20 03:34:09 +00:00
Ikko Eltociear Ashimine 13679ff301 Fix typo in image.go (#306)
lenth -> length
2023-03-20 10:41:54 +08:00
1Panel-botandzhengkunwang223 89b7a06662 feat: 系统启动增加同步已安装应用步骤 (#295)
Co-authored-by: zhengkunwang223 <zhengkun@fit2cloud.com>
2023-03-20 10:38:39 +08:00
maninhill e8582bea75 Update README.md 2023-03-19 15:59:11 +08:00
wanghe 9af7926eb9 Create SECURITY.md 2023-03-18 23:08:08 +08:00
BugKing bdcdf7e181 ci: add workflow to sync to Gitee repo 2023-03-18 22:48:17 +08:00
127 changed files with 1988 additions and 1925 deletions
-17
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 }}
-16
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: '状态:待处理'
-38
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: '状态:待处理'
-16
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
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
+1 -1
View File
@@ -16,7 +16,7 @@ build_web:
build_bin:
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:
cd $(SERVER_PATH) \
+5 -4
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,13 +48,13 @@ 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"/>
## 安全说明
如果您在使用过程中发现任何安全问题,请通过以下方式直接联系我们:
- 邮箱:support@fit2cloud.com
- 邮箱:support@fit2cloud.com
- 电话:400-052-0755
## Star History
@@ -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
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
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!
+1 -1
View File
@@ -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
}
+5 -3
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)
+31 -2
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
-1
View File
@@ -35,7 +35,6 @@ var (
commandService = service.ServiceGroupApp.CommandService
websiteGroupService = service.ServiceGroupApp.WebsiteGroupService
websiteService = service.ServiceGroupApp.WebsiteService
websiteDnsAccountService = service.ServiceGroupApp.WebsiteDnsAccountService
websiteSSLService = service.ServiceGroupApp.WebsiteSSLService
+19 -11
View File
@@ -449,7 +449,7 @@ func (b *BaseApi) Download(c *gin.Context) {
// @Summary Download file with path
// @Description 下载指定文件
// @Accept json
// @Param request body request.FilePath true "request"
// @Param request body dto.FilePath true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/download/bypath [post]
@@ -519,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
@@ -547,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 {
@@ -573,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)
@@ -589,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)
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)
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)
+3
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)
}
+22
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 系统更新
-89
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)
}
+22
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)
}
+15 -9
View File
@@ -69,15 +69,21 @@ 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"`
Values []AppFormValue `json:"values"`
}
type AppFormValue struct {
Label string `json:"label"`
Value string `json:"value"`
}
type AppResource struct {
-2
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"`
}
+14 -13
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"`
}
+1
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"`
}
+5
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"`
}
+9 -7
View File
@@ -61,11 +61,13 @@ 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"`
}
+4 -2
View File
@@ -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"`
}
-11
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"
}
+5
View File
@@ -113,6 +113,7 @@ type RootInfo struct {
Name string `json:"name"`
Port int64 `json:"port"`
Password string `json:"password"`
UserPassword string `json:"userPassword"`
ContainerName string `json:"containerName"`
Param string `json:"param"`
Env string `json:"env"`
@@ -146,6 +147,10 @@ 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.ID = appInstall.ID
info.ContainerName = appInstall.ContainerName
-1
View File
@@ -19,7 +19,6 @@ type RepoGroup struct {
BackupRepo
WebsiteRepo
WebsiteDomainRepo
WebsiteGroupRepo
WebsiteDnsAccountRepo
WebsiteSSLRepo
WebsiteAcmeAccountRepo
+3 -3
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
}
-44
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
}
+7
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"io/ioutil"
"net/http"
"os"
@@ -166,6 +167,9 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail
}
func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) {
if err := docker.CreateDefaultDockerNetwork(); err != nil {
return nil, buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
}
if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 {
return nil, buserr.New(constant.ErrNameIsExist)
}
@@ -253,6 +257,9 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
if err := createLink(ctx, app, &appInstall, req.Params); err != nil {
return nil, err
}
if err := upAppPre(app, appInstall); err != nil {
return nil, err
}
go upApp(appInstall.GetComposePath(), appInstall)
go updateToolApp(appInstall)
return &appInstall, nil
+56 -23
View File
@@ -3,9 +3,6 @@ package service
import (
"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"
@@ -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)
LoadPassword(key string) (string, 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,7 +133,7 @@ func (a AppInstallService) LoadPort(key string) (int64, error) {
return app.Port, nil
}
func (a AppInstallService) LoadPassword(key string) (string, error) {
func (a *AppInstallService) LoadPassword(key string) (string, error) {
app, err := appInstallRepo.LoadBaseInfo(key, "")
if err != nil {
return "", nil
@@ -119,7 +141,7 @@ func (a AppInstallService) LoadPassword(key string) (string, error) {
return app.Password, 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,7 +174,7 @@ func (a AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]r
return handleInstalled(installs, false)
}
func (a AppInstallService) Operate(req request.AppInstalledOperate) error {
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
if err != nil {
return err
@@ -196,7 +218,7 @@ func (a AppInstallService) Operate(req request.AppInstalledOperate) error {
}
}
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
@@ -270,13 +292,18 @@ func (a AppInstallService) Update(req request.AppInstalledUpdate) error {
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(&i)
}
continue
}
if err := syncById(i.ID); err != nil {
@@ -286,7 +313,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 +337,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 +362,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)
}
@@ -363,7 +390,7 @@ func (a AppInstallService) ChangeAppPort(req request.PortUpdate) error {
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 {
@@ -404,7 +431,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 +453,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 +486,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
@@ -622,7 +655,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),
+11
View File
@@ -339,6 +339,17 @@ func copyAppData(key, version, installName string, params map[string]interface{}
return
}
// 处理文件夹权限等问题
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 upApp(composeFilePath string, appInstall model.AppInstall) {
out, err := compose.Up(composeFilePath)
if err != nil {
+3 -3
View File
@@ -86,9 +86,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 +118,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
+33 -10
View File
@@ -3,7 +3,9 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os/exec"
"sort"
@@ -21,6 +23,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 +36,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
@@ -155,10 +158,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 +195,19 @@ 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 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
@@ -239,7 +249,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
}
@@ -320,3 +330,16 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64)
}
return rx, tx
}
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(ioutil.Discard, out)
if err != nil {
return err
}
return nil
}
+66 -31
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"sort"
"strings"
@@ -124,46 +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 req.From == "path" {
req.Name = path.Base(strings.ReplaceAll(req.Path, "/docker-compose.yml", ""))
req.Name = path.Base(strings.ReplaceAll(req.Path, "/"+path.Base(req.Path), ""))
}
if stdout, err := compose.Up(req.Path); err != nil {
_, _ = compose.Down(req.Path)
return errors.New(stdout)
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
}
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
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 nil
return logName, nil
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
@@ -205,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
}
+1
View File
@@ -194,6 +194,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
+69 -38
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,64 @@ 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, _ := disk.Usage(mounts[i].Mount)
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
}
-2
View File
@@ -28,7 +28,6 @@ type ServiceGroup struct {
SettingService
BackupService
WebsiteGroupService
WebsiteService
WebsiteDnsAccountService
WebsiteSSLService
@@ -68,7 +67,6 @@ var (
backupRepo = repo.RepoGroupApp.BackupRepo
websiteRepo = repo.NewIWebsiteRepo()
websiteGroupRepo = repo.RepoGroupApp.WebsiteGroupRepo
websiteDomainRepo = repo.RepoGroupApp.WebsiteDomainRepo
websiteDnsRepo = repo.RepoGroupApp.WebsiteDnsAccountRepo
websiteSSLRepo = repo.NewISSLRepo()
+4 -4
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
}
}
+9 -7
View File
@@ -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),
@@ -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!")
}()
+3
View File
@@ -66,6 +66,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,
+79 -18
View File
@@ -1,6 +1,8 @@
package service
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@@ -20,6 +22,7 @@ type UpgradeService struct{}
type IUpgradeService interface {
Upgrade(req dto.Upgrade) error
LoadNotes(req dto.Upgrade) (string, error)
SearchUpgrade() (*dto.UpgradeInfo, error)
}
@@ -34,36 +37,50 @@ func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
return nil, err
}
versionRes, err := http.Get(fmt.Sprintf("%s/%s/latest", global.CONF.System.RepoUrl, global.CONF.System.Mode))
latestVersion, err := u.loadVersion(true, currentVersion.Value)
if err != nil {
global.LOG.Infof("load latest version failed, err: %v", err)
return nil, err
}
defer versionRes.Body.Close()
version, err := ioutil.ReadAll(versionRes.Body)
if err != nil {
if !common.CompareVersion(string(latestVersion), currentVersion.Value) {
return nil, err
}
isNew := common.CompareVersion(string(version), currentVersion.Value)
if !isNew {
return nil, err
upgrade.LatestVersion = latestVersion
if latestVersion[0:4] == currentVersion.Value[0:4] {
upgrade.NewVersion = ""
} else {
newerVersion, err := u.loadVersion(false, currentVersion.Value)
if err != nil {
global.LOG.Infof("load newer version failed, err: %v", err)
return nil, err
}
if newerVersion == currentVersion.Value {
upgrade.NewVersion = ""
} else {
upgrade.NewVersion = newerVersion
}
}
itemVersion := upgrade.LatestVersion
if upgrade.NewVersion != "" {
itemVersion = upgrade.NewVersion
}
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, global.CONF.System.Mode, itemVersion, itemVersion))
upgrade.NewVersion = string(version)
releaseNotes, err := http.Get(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, global.CONF.System.Mode, upgrade.NewVersion, upgrade.NewVersion))
if err != nil {
return nil, err
return nil, fmt.Errorf("load relase-notes of version %s failed, err: %v", latestVersion, err)
}
defer releaseNotes.Body.Close()
release, err := ioutil.ReadAll(releaseNotes.Body)
if err != nil {
return nil, err
}
upgrade.ReleaseNote = string(release)
upgrade.ReleaseNote = notes
return &upgrade, nil
}
func (u *UpgradeService) LoadNotes(req dto.Upgrade) (string, error) {
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, global.CONF.System.Mode, req.Version, req.Version))
if err != nil {
return "", fmt.Errorf("load relase-notes of version %s failed, err: %v", req.Version, err)
}
return notes, nil
}
func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
global.LOG.Info("start to upgrade now...")
fileOp := files.NewFileOp()
@@ -174,5 +191,49 @@ func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string,
if err := cpBinary(originalDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil {
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
}
}
func (u *UpgradeService) loadVersion(isLatest bool, currentVersion string) (string, error) {
path := fmt.Sprintf("%s/%s/latest", global.CONF.System.RepoUrl, global.CONF.System.Mode)
if !isLatest {
path = fmt.Sprintf("%s/%s/latest.current", global.CONF.System.RepoUrl, global.CONF.System.Mode)
}
latestVersionRes, err := http.Get(path)
if err != nil {
return "", err
}
defer latestVersionRes.Body.Close()
version, err := ioutil.ReadAll(latestVersionRes.Body)
if err != nil {
return "", err
}
if isLatest {
return string(version), nil
}
versionMap := make(map[string]string)
if err := json.Unmarshal(version, &versionMap); err != nil {
return "", fmt.Errorf("load version map failed, err: %v", err)
}
if len(currentVersion) < 4 {
return "", fmt.Errorf("current version is error format: %s", currentVersion)
}
if version, ok := versionMap[currentVersion[0:4]]; ok {
return version, nil
}
return "", errors.New("load version failed in latest.current")
}
func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
releaseNotes, err := http.Get(path)
if err != nil {
return "", err
}
defer releaseNotes.Body.Close()
release, err := ioutil.ReadAll(releaseNotes.Body)
if err != nil {
return "", err
}
return string(release), nil
}
-61
View File
@@ -1,61 +0,0 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
)
type WebsiteGroupService struct {
}
func (w WebsiteGroupService) CreateGroup(create request.WebsiteGroupCreate) error {
groups, _ := websiteGroupRepo.GetBy(commonRepo.WithByName(create.Name))
if len(groups) > 0 {
return buserr.New(constant.ErrNameIsExist)
}
return websiteGroupRepo.Create(&model.WebsiteGroup{
Name: create.Name,
})
}
func (w WebsiteGroupService) GetGroups() ([]model.WebsiteGroup, error) {
return websiteGroupRepo.GetBy()
}
func (w WebsiteGroupService) UpdateGroup(update request.WebsiteGroupUpdate) error {
if update.Default {
if err := websiteGroupRepo.CancelDefault(); err != nil {
return err
}
return websiteGroupRepo.Save(&model.WebsiteGroup{
BaseModel: model.BaseModel{
ID: update.ID,
},
Name: update.Name,
Default: true,
})
} else {
exists, _ := websiteGroupRepo.GetBy(commonRepo.WithByName(update.Name))
for _, exist := range exists {
if exist.ID != update.ID {
return buserr.New(constant.ErrNameIsExist)
}
}
return websiteGroupRepo.Save(&model.WebsiteGroup{
BaseModel: model.BaseModel{
ID: update.ID,
},
Name: update.Name,
})
}
}
func (w WebsiteGroupService) DeleteGroup(id uint) error {
websites, _ := websiteRepo.GetBy(websiteRepo.WithGroupID(id))
if len(websites) > 0 {
return buserr.New(constant.ErrGroupIsUsed)
}
return websiteGroupRepo.DeleteBy(commonRepo.WithByID(id))
}
+11
View File
@@ -27,6 +27,7 @@ type IWebsiteSSLService interface {
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error)
Delete(id uint) error
Update(update request.WebsiteSSLUpdate) error
}
func NewIWebsiteSSLService() IWebsiteSSLService {
@@ -132,6 +133,7 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
websiteSSL.StartDate = cert.NotBefore
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.AutoRenew = create.AutoRenew
if err := websiteSSLRepo.Create(context.TODO(), &websiteSSL); err != nil {
return res, err
@@ -258,3 +260,12 @@ func (w WebsiteSSLService) Delete(id uint) error {
}
return websiteSSLRepo.DeleteBy(commonRepo.WithByID(id))
}
func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error {
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(update.ID))
if err != nil {
return err
}
websiteSSL.AutoRenew = update.AutoRenew
return websiteSSLRepo.Save(websiteSSL)
}
+11 -9
View File
@@ -50,15 +50,16 @@ var (
// app
var (
ErrPortInUsed = "ErrPortInUsed"
ErrAppLimit = "ErrAppLimit"
ErrAppRequired = "ErrAppRequired"
ErrFileCanNotRead = "ErrFileCanNotRead"
ErrFileToLarge = "ErrFileToLarge"
ErrNotInstall = "ErrNotInstall"
ErrPortInOtherApp = "ErrPortInOtherApp"
ErrDbUserNotValid = "ErrDbUserNotValid"
ErrUpdateBuWebsite = "ErrUpdateBuWebsite"
ErrPortInUsed = "ErrPortInUsed"
ErrAppLimit = "ErrAppLimit"
ErrAppRequired = "ErrAppRequired"
ErrFileCanNotRead = "ErrFileCanNotRead"
ErrFileToLarge = "ErrFileToLarge"
ErrNotInstall = "ErrNotInstall"
ErrPortInOtherApp = "ErrPortInOtherApp"
ErrDbUserNotValid = "ErrDbUserNotValid"
ErrUpdateBuWebsite = "ErrUpdateBuWebsite"
Err1PanelNetworkFailed = "Err1PanelNetworkFailed"
)
//website
@@ -83,6 +84,7 @@ var (
ErrMovePathFailed = "ErrMovePathFailed"
ErrLinkPathNotFound = "ErrLinkPathNotFound"
ErrFileIsExit = "ErrFileIsExit"
ErrFileUpload = "ErrFileUpload"
)
//mysql
+1 -1
View File
@@ -6,7 +6,7 @@ const (
AuthMethodJWT = "jwt"
JWTHeaderName = "Authorization"
JWTBufferTime = 86400
JWTBufferTime = 3600
JWTIssuer = "1Panel"
PasswordExpiredName = "expired"
+3 -2
View File
@@ -4,8 +4,9 @@ const (
WebRunning = "Running"
WebStopped = "Stopped"
DateLayout = "2006-01-02"
DefaultDate = "1970-01-01"
DateLayout = "2006-01-02"
DateTimeLayout = "2006-01-02 15:04:05"
DefaultDate = "1970-01-01"
ProtocolHTTP = "HTTP"
ProtocolHTTPS = "HTTPS"
+5 -5
View File
@@ -44,13 +44,13 @@ func Run() {
}).Error; err != nil {
global.LOG.Errorf("start my cronjob failed, err: %v", err)
}
for _, cronjob := range cronJobs {
entryID, err := service.ServiceGroupApp.StartJob(&cronjob)
for i := 0; i < len(cronJobs); i++ {
entryID, err := service.ServiceGroupApp.StartJob(&cronJobs[i])
if err != nil {
global.LOG.Errorf("start %s job %s failed, err: %v", cronjob.Type, cronjob.Name, err)
global.LOG.Errorf("start %s job %s failed, err: %v", &cronJobs[i].Type, &cronJobs[i].Name, err)
}
if err := repo.NewICronjobRepo().Update(cronjob.ID, map[string]interface{}{"entry_id": entryID}); err != nil {
global.LOG.Errorf("update cronjob %s %s failed, err: %v", cronjob.Type, cronjob.Name, err)
if err := repo.NewICronjobRepo().Update(cronJobs[i].ID, map[string]interface{}{"entry_id": entryID}); err != nil {
global.LOG.Errorf("update cronjob %s %s failed, err: %v", cronJobs[i].Type, cronJobs[i].Name, err)
}
}
}
+3
View File
@@ -118,6 +118,9 @@ func loadNetIO() {
netStatAll2, _ := net.IOCounters(false)
for _, net2 := range netStatAll2 {
for _, net1 := range netStatAll {
if net1.BytesSent == 0 || net1.BytesRecv == 0 {
continue
}
if net2.Name == net1.Name {
var itemNet model.MonitorNetwork
itemNet.Name = net1.Name
+5 -4
View File
@@ -3,6 +3,7 @@ package job
import (
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"time"
)
@@ -19,18 +20,18 @@ func (ssl *ssl) Run() {
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
global.LOG.Info("ssl renew cron job start...")
now := time.Now()
now := time.Now().Add(10 * time.Second)
for _, s := range sslList {
if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" {
continue
}
sum := s.ExpireDate.Sub(now)
if sum.Hours() < 168 {
expireDate, _ := time.ParseInLocation(constant.DateTimeLayout, s.ExpireDate.String(), time.Now().Location())
sum := expireDate.Sub(now)
if sum.Hours() < 720 {
if err := sslService.Renew(s.ID); err != nil {
global.LOG.Errorf("renew doamin [%s] ssl failed err:%s", s.PrimaryDomain, err.Error())
}
}
}
global.LOG.Info("ssl renew cron job end...")
}
+3 -2
View File
@@ -20,7 +20,7 @@ func NewWebsiteJob() *website {
func (w *website) Run() {
websites, _ := repo.NewIWebsiteRepo().List()
global.LOG.Info("website cron job start...")
now := time.Now()
now := time.Now().Add(10 * time.Second)
if len(websites) > 0 {
neverExpireDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
var wg sync.WaitGroup
@@ -28,7 +28,8 @@ func (w *website) Run() {
if site.Status != constant.WebRunning || neverExpireDate.Equal(site.ExpireDate) {
continue
}
if site.ExpireDate.Before(now) {
expireDate, _ := time.ParseInLocation(constant.DateTimeLayout, site.ExpireDate.String(), time.Now().Location())
if expireDate.Before(now) {
wg.Add(1)
go func(ws model.Website) {
stopWebsite(ws.ID, &wg)
+7 -7
View File
@@ -11,35 +11,35 @@ import (
"gopkg.in/yaml.v3"
)
func GetMsgWithMap(msg string, maps map[string]interface{}) string {
func GetMsgWithMap(key string, maps map[string]interface{}) string {
content := ""
if maps == nil {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
MessageID: msg,
MessageID: key,
})
} else {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
MessageID: msg,
MessageID: key,
TemplateData: maps,
})
}
content = strings.ReplaceAll(content, ": <no value>", "")
if content == "" {
return msg
return key
} else {
return content
}
}
func GetErrMsg(msg string, maps map[string]interface{}) string {
func GetErrMsg(key string, maps map[string]interface{}) string {
content := ""
if maps == nil {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
MessageID: msg,
MessageID: key,
})
} else {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
MessageID: msg,
MessageID: key,
TemplateData: maps,
})
}
+2
View File
@@ -26,6 +26,7 @@ ErrPortInOtherApp: "{{ .port }} port already in use by {{ .apps }}"
ErrDbUserNotValid: "Stock database, username and password do not match!"
ErrDockerComposeNotValid: "docker-compose file format error!"
ErrUpdateBuWebsite: 'The application was updated successfully, but the modification of the website configuration file failed, please check the configuration!'
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
#file
ErrFileCanNotRead: "File can not read"
@@ -34,6 +35,7 @@ ErrPathNotFound: "Path is not found"
ErrMovePathFailed: "The target path cannot contain the original path!"
ErrLinkPathNotFound: "Target path does not exist!"
ErrFileIsExit: "File already exists!"
ErrFileUpload: "Failed to upload file"
#website
ErrDomainIsExist: "Domain is already exist"
+2
View File
@@ -26,6 +26,7 @@ ErrPortInOtherApp: "{{ .port }} 端口已被 {{ .apps }}占用!"
ErrDbUserNotValid: "存量数据库,用户名密码不匹配!"
ErrDockerComposeNotValid: "docker-compose 文件格式错误"
ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!'
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
#file
ErrFileCanNotRead: "此文件不支持预览"
@@ -34,6 +35,7 @@ ErrPathNotFound: "目录不存在"
ErrMovePathFailed: "目标路径不能包含原路径!"
ErrLinkPathNotFound: "目标路径不存在!"
ErrFileIsExit: "文件已存在!"
ErrFileUpload: "上传文件失败"
#website
ErrDomainIsExist: "域名已存在"
+2 -16
View File
@@ -1,11 +1,11 @@
package app
import (
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"path"
"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"
)
@@ -22,7 +22,7 @@ func Init() {
createDir(fileOp, dir)
}
createDefaultDockerNetwork()
_ = docker.CreateDefaultDockerNetwork()
}
func createDir(fileOp files.FileOp, dirPath string) {
@@ -30,17 +30,3 @@ func createDir(fileOp files.FileOp, dirPath string) {
_ = fileOp.CreateDir(dirPath, 0755)
}
}
func createDefaultDockerNetwork() {
cli, err := docker.NewClient()
if err != nil {
global.LOG.Errorf("init docker client error", err.Error())
return
}
if !cli.NetworkExist("1panel-network") {
if err := cli.CreateNetwork("1panel-network"); err != nil {
global.LOG.Errorf("init docker client error", err.Error())
return
}
}
}
+14 -2
View File
@@ -6,18 +6,30 @@ import (
)
func Init() {
syncApp()
syncInstalledApp()
}
func syncApp() {
setting, err := service.NewISettingService().GetSettingInfo()
if err != nil {
global.LOG.Errorf("sync app error: %s", err.Error())
return
}
if setting.AppStoreVersion != "" {
global.LOG.Info("do not sync")
global.LOG.Info("no need to sync")
return
}
global.LOG.Info("sync app start...")
if err := service.NewIAppService().SyncAppList(); err != nil {
global.LOG.Errorf("sync app error: %s", err.Error())
return
}
global.LOG.Info("sync app successful")
}
func syncInstalledApp() {
if err := service.NewIAppInstalledService().SyncAll(true); err != nil {
global.LOG.Errorf("sync instaled app error: %s", err.Error())
}
global.LOG.Info("sync app success")
}
+12 -1
View File
@@ -2,9 +2,11 @@ package db
import (
"fmt"
"os"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"os"
"github.com/1Panel-dev/1Panel/backend/global"
)
@@ -28,6 +30,15 @@ func Init() {
if err != nil {
panic(err)
}
_ = db.Exec("PRAGMA journal_mode = WAL;")
sqlDB, dbError := db.DB()
if dbError != nil {
panic(err)
}
sqlDB.SetConnMaxIdleTime(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
global.DB = db
global.LOG.Info("init db successfully")
}
+1
View File
@@ -20,6 +20,7 @@ func Init() {
migrations.AddTableWebsite,
migrations.AddTableDatabaseMysql,
migrations.AddTableSnap,
migrations.AddDefaultGroup,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+22 -9
View File
@@ -140,7 +140,6 @@ var AddTableSetting = &gormigrate.Migration{
return err
}
if err := tx.Create(&model.Setting{Key: "SystemStatus", Value: "Free"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&model.Setting{Key: "AppStoreVersion", Value: ""}).Error; err != nil {
@@ -211,14 +210,7 @@ var AddTableDatabaseMysql = &gormigrate.Migration{
var AddTableWebsite = &gormigrate.Migration{
ID: "20201009-add-table-website",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Website{}, &model.WebsiteDomain{}, &model.WebsiteGroup{}, &model.WebsiteDnsAccount{}, &model.WebsiteSSL{}, &model.WebsiteAcmeAccount{}); err != nil {
return err
}
item := &model.WebsiteGroup{
Name: "默认",
Default: true,
}
if err := tx.Create(item).Error; err != nil {
if err := tx.AutoMigrate(&model.Website{}, &model.WebsiteDomain{}, &model.WebsiteDnsAccount{}, &model.WebsiteSSL{}, &model.WebsiteAcmeAccount{}); err != nil {
return err
}
return nil
@@ -234,3 +226,24 @@ var AddTableSnap = &gormigrate.Migration{
return nil
},
}
var AddDefaultGroup = &gormigrate.Migration{
ID: "2023022-change-default-group",
Migrate: func(tx *gorm.DB) error {
defaultGroup := &model.Group{
Name: "默认",
IsDefault: true,
Type: "website",
}
if err := tx.Create(defaultGroup).Error; err != nil {
return err
}
if err := tx.Model(&model.Group{}).Where("name = ? AND type = ?", "default", "host").Update("name", "默认").Error; err != nil {
return err
}
if err := tx.Model(&model.Website{}).Where("1 = 1").Update("website_group_id", defaultGroup.ID).Error; err != nil {
return err
}
return tx.Migrator().DropTable("website_groups")
},
}
+4
View File
@@ -63,6 +63,10 @@ func Routers() *gin.Engine {
systemRouter := rou.RouterGroupApp
swaggerRouter := Router.Group("1panel")
docs.SwaggerInfo.BasePath = "/api/v1"
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
PublicGroup := Router.Group("")
{
PublicGroup.GET("/health", func(c *gin.Context) {
-16
View File
@@ -1,22 +1,15 @@
package middleware
import (
"strconv"
"time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
jwtUtils "github.com/1Panel-dev/1Panel/backend/utils/jwt"
"github.com/golang-jwt/jwt/v4"
"github.com/gin-gonic/gin"
)
func JwtAuth() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("authMethod", "")
token := c.Request.Header.Get(constant.JWTHeaderName)
if token == "" {
c.Next()
@@ -28,15 +21,6 @@ func JwtAuth() gin.HandlerFunc {
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeInternalServer, err)
return
}
if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(settingRepo.WithByKey("SessionTimeout"))
if err != nil {
global.LOG.Errorf("create operation record failed, err: %v", err)
}
lifeTime, _ := strconv.Atoi(setting.Value)
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(lifeTime)))
}
c.Set("claims", claims)
c.Set("authMethod", constant.AuthMethodJWT)
c.Next()
+1
View File
@@ -14,6 +14,7 @@ func SessionAuth() gin.HandlerFunc {
return func(c *gin.Context) {
if method, exist := c.Get("authMethod"); exist && method == constant.AuthMethodJWT {
c.Next()
return
}
sId, err := c.Cookie(constant.SessionName)
if err != nil {
+1
View File
@@ -33,6 +33,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
baRouter.POST("/compose/search", baseApi.SearchCompose)
baRouter.POST("/compose", baseApi.CreateCompose)
baRouter.POST("/compose/test", baseApi.TestCompose)
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
baRouter.POST("/compose/update", baseApi.ComposeUpdate)
@@ -10,14 +10,14 @@ type WebsiteGroupRouter struct {
}
func (a *WebsiteGroupRouter) InitWebsiteGroupRouter(Router *gin.RouterGroup) {
groupRouter := Router.Group("websites/groups")
groupRouter := Router.Group("groups")
groupRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.GET("", baseApi.GetWebGroups)
groupRouter.POST("", baseApi.CreateWebGroup)
groupRouter.POST("/update", baseApi.UpdateWebGroup)
groupRouter.POST("/del", baseApi.DeleteWebGroup)
groupRouter.POST("", baseApi.CreateGroup)
groupRouter.POST("/del", baseApi.DeleteGroup)
groupRouter.POST("/update", baseApi.UpdateGroup)
groupRouter.POST("/search", baseApi.ListGroup)
}
}
-5
View File
@@ -26,11 +26,6 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
hostRouter.GET(":id", baseApi.GetHostInfo)
hostRouter.POST("/group", baseApi.CreateGroup)
hostRouter.POST("/group/del", baseApi.DeleteGroup)
hostRouter.POST("/group/update", baseApi.UpdateGroup)
hostRouter.POST("/group/search", baseApi.ListGroup)
hostRouter.GET("/command", baseApi.ListCommand)
hostRouter.POST("/command", baseApi.CreateCommand)
hostRouter.POST("/command/del", baseApi.DeleteCommand)
+1
View File
@@ -51,6 +51,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.POST("/backup/record/del", baseApi.DeleteBackupRecord)
settingRouter.POST("/upgrade", baseApi.Upgrade)
settingRouter.POST("/upgrade/notes", baseApi.GetNotesByVersion)
settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo)
settingRouter.GET("/basedir", baseApi.LoadBaseDir)
}
+1
View File
@@ -22,5 +22,6 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) {
groupRouter.POST("/del", baseApi.DeleteWebsiteSSL)
groupRouter.GET("/website/:websiteId", baseApi.GetWebsiteSSLByWebsiteId)
groupRouter.GET("/:id", baseApi.GetWebsiteSSLById)
groupRouter.POST("/update", baseApi.UpdateWebsiteSSL)
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func Up(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s up -d --quiet-pull", filePath)
stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
return stdout, err
}
+16
View File
@@ -2,6 +2,7 @@ package docker
import (
"context"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
@@ -73,3 +74,18 @@ func (c Client) NetworkExist(name string) bool {
}
return len(networks) > 0
}
func CreateDefaultDockerNetwork() error {
cli, err := NewClient()
if err != nil {
global.LOG.Errorf("init docker client error %s", err.Error())
return err
}
if !cli.NetworkExist("1panel-network") {
if err := cli.CreateNetwork("1panel-network"); err != nil {
global.LOG.Errorf("create default docker network error %s", err.Error())
return err
}
}
return nil
}
+4
View File
@@ -106,6 +106,10 @@ func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
return f.Fs.Chmod(dst, mode)
}
func (f FileOp) Chown(dst string, uid int, gid int) error {
return f.Fs.Chown(dst, uid, gid)
}
func (f FileOp) Rename(oldName string, newName string) error {
return f.Fs.Rename(oldName, newName)
}
+2 -2
View File
@@ -38,12 +38,12 @@ func NewJWT() *JWT {
}
}
func (j *JWT) CreateClaims(baseClaims BaseClaims, ttl int) CustomClaims {
func (j *JWT) CreateClaims(baseClaims BaseClaims) CustomClaims {
claims := CustomClaims{
BaseClaims: baseClaims,
BufferTime: constant.JWTBufferTime,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(ttl))),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(constant.JWTBufferTime))),
Issuer: constant.JWTIssuer,
},
}
+180 -252
View File
@@ -1045,6 +1045,48 @@ var doc = `{
}
}
},
"/containers/compose/test": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "测试 compose 是否可用",
"consumes": [
"application/json"
],
"tags": [
"Container Compose"
],
"summary": "Test compose",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ComposeCreate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "check compose [name]",
"formatZH": "检测 compose [name] 格式",
"paramKeys": []
}
}
},
"/containers/compose/update": {
"post": {
"security": [
@@ -3999,15 +4041,6 @@ var doc = `{
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"path"
],
"formatEN": "Upload file [path]",
"formatZH": "上传文件 [path]",
"paramKeys": []
}
}
},
@@ -4246,7 +4279,7 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.FilePath"
"$ref": "#/definitions/dto.FilePath"
}
}
],
@@ -6860,17 +6893,28 @@ var doc = `{
"ApiKeyAuth": []
}
],
"description": "系统更新信息",
"description": "获取版本 release notes",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Load upgrade info",
"summary": "Load release notes by version",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.Upgrade"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.UpgradeInfo"
}
"description": ""
}
}
},
@@ -7770,161 +7814,6 @@ var doc = `{
}
}
},
"/websites/groups": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取网站组",
"tags": [
"Website Group"
],
"summary": "List website groups",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
},
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Create website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupCreate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Create website groups [name]",
"formatZH": "创建网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "删除网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Delete website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "website_groups",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Delete website group [name]",
"formatZH": "删除网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Update website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Update website groups [name]",
"formatZH": "更新网站组 [name]",
"paramKeys": []
}
}
},
"/websites/list": {
"get": {
"security": [
@@ -8410,6 +8299,57 @@ var doc = `{
}
}
},
"/websites/ssl/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新 ssl",
"consumes": [
"application/json"
],
"tags": [
"Website SSL"
],
"summary": "Update ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteSSLUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "website_ssls",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update ssl config [domain]",
"formatZH": "更新证书设置 [domain]",
"paramKeys": []
}
}
},
"/websites/ssl/website/:websiteId": {
"get": {
"security": [
@@ -9231,33 +9171,15 @@ var doc = `{
"databaseNumber": {
"type": "integer"
},
"dateeaseID": {
"type": "integer"
},
"haloID": {
"type": "integer"
},
"hostname": {
"type": "string"
},
"jumpserverID": {
"type": "integer"
},
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"kubeoperatorID": {
"type": "integer"
},
"kubepiID": {
"type": "integer"
},
"metersphereID": {
"type": "integer"
},
"os": {
"type": "string"
},
@@ -9299,20 +9221,11 @@ var doc = `{
"cpuUsedPercent": {
"type": "number"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
"diskData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.DiskInfo"
}
},
"ioCount": {
"type": "integer"
@@ -9320,12 +9233,15 @@ var doc = `{
"ioReadBytes": {
"type": "integer"
},
"ioTime": {
"ioReadTime": {
"type": "integer"
},
"ioWriteBytes": {
"type": "integer"
},
"ioWriteTime": {
"type": "integer"
},
"load1": {
"type": "number"
},
@@ -9362,11 +9278,40 @@ var doc = `{
"timeSinceUptime": {
"type": "string"
},
"uptime": {
"type": "integer"
}
}
},
"dto.DiskInfo": {
"type": "object",
"properties": {
"device": {
"type": "string"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
},
"path": {
"type": "string"
},
"total": {
"type": "integer"
},
"uptime": {
"type": "integer"
"type": {
"type": "string"
},
"used": {
"type": "integer"
@@ -9488,6 +9433,9 @@ var doc = `{
},
"dto.GroupUpdate": {
"type": "object",
"required": [
"type"
],
"properties": {
"id": {
"type": "integer"
@@ -9497,6 +9445,9 @@ var doc = `{
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
@@ -9818,9 +9769,6 @@ var doc = `{
},
"password": {
"type": "string"
},
"secret": {
"type": "string"
}
}
},
@@ -10652,6 +10600,9 @@ var doc = `{
"dto.UpgradeInfo": {
"type": "object",
"properties": {
"latestVersion": {
"type": "string"
},
"newVersion": {
"type": "string"
},
@@ -10663,9 +10614,6 @@ var doc = `{
"dto.UserLoginInfo": {
"type": "object",
"properties": {
"mfaSecret": {
"type": "string"
},
"mfaStatus": {
"type": "string"
},
@@ -11441,17 +11389,6 @@ var doc = `{
}
}
},
"request.FilePath": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
}
}
},
"request.FilePathCheck": {
"type": "object",
"required": [
@@ -11782,34 +11719,6 @@ var doc = `{
}
}
},
"request.WebsiteGroupCreate": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"request.WebsiteGroupUpdate": {
"type": "object",
"required": [
"id"
],
"properties": {
"default": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"request.WebsiteHTTPSOp": {
"type": "object",
"required": [
@@ -11987,6 +11896,21 @@ var doc = `{
}
}
},
"request.WebsiteSSLUpdate": {
"type": "object",
"required": [
"autoRenew",
"id"
],
"properties": {
"autoRenew": {
"type": "boolean"
},
"id": {
"type": "integer"
}
}
},
"request.WebsiteSearch": {
"type": "object",
"required": [
@@ -12225,10 +12149,14 @@ var doc = `{
"rule": {
"type": "string"
},
"showValue": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {}
"value": {},
"values": {}
}
},
"response.FileInfo": {
+180 -252
View File
@@ -1031,6 +1031,48 @@
}
}
},
"/containers/compose/test": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "测试 compose 是否可用",
"consumes": [
"application/json"
],
"tags": [
"Container Compose"
],
"summary": "Test compose",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ComposeCreate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "check compose [name]",
"formatZH": "检测 compose [name] 格式",
"paramKeys": []
}
}
},
"/containers/compose/update": {
"post": {
"security": [
@@ -3985,15 +4027,6 @@
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"path"
],
"formatEN": "Upload file [path]",
"formatZH": "上传文件 [path]",
"paramKeys": []
}
}
},
@@ -4232,7 +4265,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.FilePath"
"$ref": "#/definitions/dto.FilePath"
}
}
],
@@ -6846,17 +6879,28 @@
"ApiKeyAuth": []
}
],
"description": "系统更新信息",
"description": "获取版本 release notes",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Load upgrade info",
"summary": "Load release notes by version",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.Upgrade"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.UpgradeInfo"
}
"description": ""
}
}
},
@@ -7756,161 +7800,6 @@
}
}
},
"/websites/groups": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取网站组",
"tags": [
"Website Group"
],
"summary": "List website groups",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
},
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Create website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupCreate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Create website groups [name]",
"formatZH": "创建网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "删除网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Delete website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "website_groups",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Delete website group [name]",
"formatZH": "删除网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Update website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Update website groups [name]",
"formatZH": "更新网站组 [name]",
"paramKeys": []
}
}
},
"/websites/list": {
"get": {
"security": [
@@ -8396,6 +8285,57 @@
}
}
},
"/websites/ssl/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新 ssl",
"consumes": [
"application/json"
],
"tags": [
"Website SSL"
],
"summary": "Update ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteSSLUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "website_ssls",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update ssl config [domain]",
"formatZH": "更新证书设置 [domain]",
"paramKeys": []
}
}
},
"/websites/ssl/website/:websiteId": {
"get": {
"security": [
@@ -9217,33 +9157,15 @@
"databaseNumber": {
"type": "integer"
},
"dateeaseID": {
"type": "integer"
},
"haloID": {
"type": "integer"
},
"hostname": {
"type": "string"
},
"jumpserverID": {
"type": "integer"
},
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"kubeoperatorID": {
"type": "integer"
},
"kubepiID": {
"type": "integer"
},
"metersphereID": {
"type": "integer"
},
"os": {
"type": "string"
},
@@ -9285,20 +9207,11 @@
"cpuUsedPercent": {
"type": "number"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
"diskData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.DiskInfo"
}
},
"ioCount": {
"type": "integer"
@@ -9306,12 +9219,15 @@
"ioReadBytes": {
"type": "integer"
},
"ioTime": {
"ioReadTime": {
"type": "integer"
},
"ioWriteBytes": {
"type": "integer"
},
"ioWriteTime": {
"type": "integer"
},
"load1": {
"type": "number"
},
@@ -9348,11 +9264,40 @@
"timeSinceUptime": {
"type": "string"
},
"uptime": {
"type": "integer"
}
}
},
"dto.DiskInfo": {
"type": "object",
"properties": {
"device": {
"type": "string"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
},
"path": {
"type": "string"
},
"total": {
"type": "integer"
},
"uptime": {
"type": "integer"
"type": {
"type": "string"
},
"used": {
"type": "integer"
@@ -9474,6 +9419,9 @@
},
"dto.GroupUpdate": {
"type": "object",
"required": [
"type"
],
"properties": {
"id": {
"type": "integer"
@@ -9483,6 +9431,9 @@
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
@@ -9804,9 +9755,6 @@
},
"password": {
"type": "string"
},
"secret": {
"type": "string"
}
}
},
@@ -10638,6 +10586,9 @@
"dto.UpgradeInfo": {
"type": "object",
"properties": {
"latestVersion": {
"type": "string"
},
"newVersion": {
"type": "string"
},
@@ -10649,9 +10600,6 @@
"dto.UserLoginInfo": {
"type": "object",
"properties": {
"mfaSecret": {
"type": "string"
},
"mfaStatus": {
"type": "string"
},
@@ -11427,17 +11375,6 @@
}
}
},
"request.FilePath": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
}
}
},
"request.FilePathCheck": {
"type": "object",
"required": [
@@ -11768,34 +11705,6 @@
}
}
},
"request.WebsiteGroupCreate": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"request.WebsiteGroupUpdate": {
"type": "object",
"required": [
"id"
],
"properties": {
"default": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"request.WebsiteHTTPSOp": {
"type": "object",
"required": [
@@ -11973,6 +11882,21 @@
}
}
},
"request.WebsiteSSLUpdate": {
"type": "object",
"required": [
"autoRenew",
"id"
],
"properties": {
"autoRenew": {
"type": "boolean"
},
"id": {
"type": "integer"
}
}
},
"request.WebsiteSearch": {
"type": "object",
"required": [
@@ -12211,10 +12135,14 @@
"rule": {
"type": "string"
},
"showValue": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {}
"value": {},
"values": {}
}
},
"response.FileInfo": {
+120 -166
View File
@@ -443,24 +443,12 @@ definitions:
$ref: '#/definitions/dto.DashboardCurrent'
databaseNumber:
type: integer
dateeaseID:
type: integer
haloID:
type: integer
hostname:
type: string
jumpserverID:
type: integer
kernelArch:
type: string
kernelVersion:
type: string
kubeoperatorID:
type: integer
kubepiID:
type: integer
metersphereID:
type: integer
os:
type: string
platform:
@@ -488,24 +476,20 @@ definitions:
type: number
cpuUsedPercent:
type: number
free:
type: integer
inodesFree:
type: integer
inodesTotal:
type: integer
inodesUsed:
type: integer
inodesUsedPercent:
type: number
diskData:
items:
$ref: '#/definitions/dto.DiskInfo'
type: array
ioCount:
type: integer
ioReadBytes:
type: integer
ioTime:
ioReadTime:
type: integer
ioWriteBytes:
type: integer
ioWriteTime:
type: integer
load1:
type: number
load5:
@@ -530,10 +514,29 @@ definitions:
type: string
timeSinceUptime:
type: string
total:
type: integer
uptime:
type: integer
type: object
dto.DiskInfo:
properties:
device:
type: string
free:
type: integer
inodesFree:
type: integer
inodesTotal:
type: integer
inodesUsed:
type: integer
inodesUsedPercent:
type: number
path:
type: string
total:
type: integer
type:
type: string
used:
type: integer
usedPercent:
@@ -622,6 +625,10 @@ definitions:
type: boolean
name:
type: string
type:
type: string
required:
- type
type: object
dto.HostConnTest:
properties:
@@ -837,8 +844,6 @@ definitions:
type: string
password:
type: string
secret:
type: string
type: object
dto.MfaCredential:
properties:
@@ -1389,6 +1394,8 @@ definitions:
type: object
dto.UpgradeInfo:
properties:
latestVersion:
type: string
newVersion:
type: string
releaseNote:
@@ -1396,8 +1403,6 @@ definitions:
type: object
dto.UserLoginInfo:
properties:
mfaSecret:
type: string
mfaStatus:
type: string
name:
@@ -1912,13 +1917,6 @@ definitions:
showHidden:
type: boolean
type: object
request.FilePath:
properties:
path:
type: string
required:
- path
type: object
request.FilePathCheck:
properties:
path:
@@ -2140,24 +2138,6 @@ definitions:
required:
- id
type: object
request.WebsiteGroupCreate:
properties:
name:
type: string
required:
- name
type: object
request.WebsiteGroupUpdate:
properties:
default:
type: boolean
id:
type: integer
name:
type: string
required:
- id
type: object
request.WebsiteHTTPSOp:
properties:
HttpConfig:
@@ -2277,6 +2257,16 @@ definitions:
- page
- pageSize
type: object
request.WebsiteSSLUpdate:
properties:
autoRenew:
type: boolean
id:
type: integer
required:
- autoRenew
- id
type: object
request.WebsiteSearch:
properties:
name:
@@ -2436,9 +2426,12 @@ definitions:
type: string
rule:
type: string
showValue:
type: string
type:
type: string
value: {}
values: {}
type: object
response.FileInfo:
properties:
@@ -3269,6 +3262,33 @@ paths:
summary: Page composes
tags:
- Container Compose
/containers/compose/test:
post:
consumes:
- application/json
description: 测试 compose 是否可用
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ComposeCreate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Test compose
tags:
- Container Compose
x-panel-log:
BeforeFuntions: []
bodyKeys:
- name
formatEN: check compose [name]
formatZH: 检测 compose [name] 格式
paramKeys: []
/containers/compose/update:
post:
consumes:
@@ -5146,13 +5166,6 @@ paths:
summary: ChunkUpload file
tags:
- File
x-panel-log:
BeforeFuntions: []
bodyKeys:
- path
formatEN: Upload file [path]
formatZH: 上传文件 [path]
paramKeys: []
/files/compress:
post:
consumes:
@@ -5301,7 +5314,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/request.FilePath'
$ref: '#/definitions/dto.FilePath'
responses:
"200":
description: ""
@@ -6968,15 +6981,22 @@ paths:
paramKeys: []
/settings/upgrade:
get:
description: 系统更新信息
consumes:
- application/json
description: 获取版本 release notes
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.Upgrade'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.UpgradeInfo'
description: ""
security:
- ApiKeyAuth: []
summary: Load upgrade info
summary: Load release notes by version
tags:
- System Setting
post:
@@ -7549,105 +7569,6 @@ paths:
formatEN: Delete domain [domain]
formatZH: 删除域名 [domain]
paramKeys: []
/websites/groups:
get:
description: 获取网站组
responses:
"200":
description: OK
schema:
type: anrry
security:
- ApiKeyAuth: []
summary: List website groups
tags:
- Website Group
post:
consumes:
- application/json
description: 创建网站组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteGroupCreate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Create website group
tags:
- Website Group
x-panel-log:
BeforeFuntions: []
bodyKeys:
- name
formatEN: Create website groups [name]
formatZH: 创建网站组 [name]
paramKeys: []
/websites/groups/del:
post:
consumes:
- application/json
description: 删除网站组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteResourceReq'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Delete website group
tags:
- Website Group
x-panel-log:
BeforeFuntions:
- db: website_groups
input_colume: id
input_value: id
isList: false
output_colume: name
output_value: name
bodyKeys:
- id
formatEN: Delete website group [name]
formatZH: 删除网站组 [name]
paramKeys: []
/websites/groups/update:
post:
consumes:
- application/json
description: 更新网站组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteGroupUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update website group
tags:
- Website Group
x-panel-log:
BeforeFuntions: []
bodyKeys:
- name
formatEN: Update website groups [name]
formatZH: 更新网站组 [name]
paramKeys: []
/websites/list:
get:
description: 获取网站列表
@@ -7955,6 +7876,39 @@ paths:
summary: Page website ssl
tags:
- Website SSL
/websites/ssl/update:
post:
consumes:
- application/json
description: 更新 ssl
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteSSLUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update ssl
tags:
- Website SSL
x-panel-log:
BeforeFuntions:
- db: website_ssls
input_colume: id
input_value: id
isList: false
output_colume: primary_domain
output_value: domain
bodyKeys:
- id
formatEN: Update ssl config [domain]
formatZH: 更新证书设置 [domain]
paramKeys: []
/websites/ssl/website/:websiteId:
get:
consumes:
+2
View File
@@ -163,5 +163,7 @@ export namespace App {
key: string;
rule: string;
type: string;
values?: any;
showValue?: string;
}
}
-2
View File
@@ -9,7 +9,6 @@ export namespace Login {
export interface MFALoginForm {
name: string;
password: string;
secret: string;
code: string;
authMethod: string;
}
@@ -17,7 +16,6 @@ export namespace Login {
name: string;
token: string;
mfaStatus: string;
mfaSecret: string;
}
export interface InitUser {
name: string;
+13 -13
View File
@@ -1,12 +1,5 @@
export namespace Dashboard {
export interface BaseInfo {
haloID: number;
dateeaseID: number;
jumpserverID: number;
metersphereID: number;
kubeoperatorID: number;
kubepiID: number;
websiteNumber: number;
databaseNumber: number;
cronjobNumber: number;
@@ -49,9 +42,21 @@ export namespace Dashboard {
ioReadBytes: number;
ioWriteBytes: number;
ioTime: number;
ioCount: number;
ioReadTime: number;
ioWriteTime: number;
diskData: Array<DiskInfo>;
netBytesSent: number;
netBytesRecv: number;
shotTime: Date;
}
export interface DiskInfo {
path: string;
type: string;
device: string;
total: number;
free: number;
used: number;
@@ -61,10 +66,5 @@ export namespace Dashboard {
inodesUsed: number;
inodesFree: number;
inodesUsedPercent: number;
netBytesSent: number;
netBytesRecv: number;
shotTime: Date;
}
}
+1
View File
@@ -82,6 +82,7 @@ export namespace Setting {
}
export interface UpgradeInfo {
newVersion: string;
latestVersion: string;
releaseNote: string;
}
}
+6 -11
View File
@@ -79,17 +79,6 @@ export namespace Website {
content: string;
}
export interface Group extends CommonModel {
name: string;
default: boolean;
}
export interface GroupOp {
name: string;
id?: number;
default: boolean;
}
export interface Domain {
websiteId: number;
port: number;
@@ -160,6 +149,7 @@ export namespace Website {
startDate: string;
provider: string;
websites?: Website.Website[];
autoRenew: boolean;
}
export interface SSLCreate {
@@ -179,6 +169,11 @@ export namespace Website {
SSLId: number;
}
export interface SSLUpdate {
id: number;
autoRenew: boolean;
}
export interface AcmeAccount extends CommonModel {
email: string;
url: string;
+6 -3
View File
@@ -6,10 +6,10 @@ export const searchContainer = (params: Container.ContainerSearch) => {
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
};
export const createContainer = (params: Container.ContainerCreate) => {
return http.post(`/containers`, params, 1200000);
return http.post(`/containers`, params, 3000000);
};
export const logContainer = (params: Container.ContainerLogSearch) => {
return http.post<string>(`/containers/search/log`, params);
return http.post<string>(`/containers/search/log`, params, 400000);
};
export const ContainerStats = (id: string) => {
return http.get<Container.ContainerStats>(`/containers/stats/${id}`);
@@ -117,7 +117,10 @@ export const searchCompose = (params: SearchWithPage) => {
return http.post<ResPage<Container.ComposeInfo>>(`/containers/compose/search`, params);
};
export const upCompose = (params: Container.ComposeCreate) => {
return http.post(`/containers/compose`, params, 600000);
return http.post<string>(`/containers/compose`, params);
};
export const testCompose = (params: Container.ComposeCreate) => {
return http.post<boolean>(`/containers/compose/test`, params);
};
export const composeOperator = (params: Container.ComposeOpration) => {
return http.post(`/containers/compose/operate`, params);
+15
View File
@@ -0,0 +1,15 @@
import { Group } from '../interface/group';
import http from '@/api';
export const GetGroupList = (params: Group.GroupSearch) => {
return http.post<Array<Group.GroupInfo>>(`/groups/search`, params);
};
export const CreateGroup = (params: Group.GroupCreate) => {
return http.post<Group.GroupCreate>(`/groups`, params);
};
export const UpdateGroup = (params: Group.GroupUpdate) => {
return http.post(`/groups/update`, params);
};
export const DeleteGroup = (id: number) => {
return http.post(`/groups/del`, { id: id });
};
-15
View File
@@ -1,7 +1,6 @@
import http from '@/api';
import { ResPage } from '../interface';
import { Command } from '../interface/command';
import { Group } from '../interface/group';
import { Host } from '../interface/host';
import { Base64 } from 'js-base64';
import { deepCopy } from '@/utils/util';
@@ -55,20 +54,6 @@ export const deleteHost = (params: { ids: number[] }) => {
return http.post(`/hosts/del`, params);
};
// group
export const GetGroupList = (params: Group.GroupSearch) => {
return http.post<Array<Group.GroupInfo>>(`/hosts/group/search`, params);
};
export const CreateGroup = (params: Group.GroupCreate) => {
return http.post<Group.GroupCreate>(`/hosts/group`, params);
};
export const UpdateGroup = (params: Group.GroupUpdate) => {
return http.post(`/hosts/group/update`, params);
};
export const DeleteGroup = (id: number) => {
return http.post(`/hosts/group/del`, { id: id });
};
// command
export const getCommandList = () => {
return http.get<Array<Command.CommandInfo>>(`/hosts/command`, {});
+3
View File
@@ -139,6 +139,9 @@ export const searchSnapshotPage = (param: SearchWithPage) => {
export const loadUpgradeInfo = () => {
return http.get<Setting.UpgradeInfo>(`/settings/upgrade`);
};
export const loadReleaseNotes = (version: string) => {
return http.post<string>(`/settings/upgrade/notes`, { version: version });
};
export const upgrade = (version: string) => {
return http.post(`/settings/upgrade`, { version: version });
};
+4 -16
View File
@@ -43,22 +43,6 @@ export const DeleteWebsite = (req: Website.WebSiteDel) => {
return http.post<any>(`/websites/del`, req);
};
export const ListGroups = () => {
return http.get<Website.Group[]>(`/websites/groups`);
};
export const CreateGroup = (req: Website.GroupOp) => {
return http.post<any>(`/websites/groups`, req);
};
export const UpdateGroup = (req: Website.GroupOp) => {
return http.post<any>(`/websites/groups/update`, req);
};
export const DeleteGroup = (req: Website.DelReq) => {
return http.post<any>(`/websites/groups/del`, req);
};
export const ListDomains = (id: number) => {
return http.get<Website.Domain[]>(`/websites/domains/${id}`);
};
@@ -139,6 +123,10 @@ export const RenewSSL = (req: Website.SSLRenew) => {
return http.post<any>(`/websites/ssl/renew`, req);
};
export const UpdateSSL = (req: Website.SSLUpdate) => {
return http.post<any>(`/websites/ssl/update`, req);
};
export const GetDnsResolve = (req: Website.DNSResolveReq) => {
return http.post<Website.DNSResolve[]>(`/websites/ssl/resolve`, req);
};
+4 -3
View File
@@ -20,7 +20,7 @@
<el-form ref="groupForm" v-if="row.edit" :model="row">
<el-form-item prop="name" v-if="row.edit" :rules="Rules.name">
<el-input v-model="row.name"></el-input>
<div style="margin-top: 20px; width: 100%"><el-input v-model="row.name" /></div>
</el-form-item>
</el-form>
</template>
@@ -59,7 +59,7 @@
import { ref } from 'vue';
import i18n from '@/lang';
import ComplexTable from '@/components/complex-table/index.vue';
import { CreateGroup, DeleteGroup, GetGroupList, UpdateGroup } from '@/api/modules/host';
import { CreateGroup, DeleteGroup, GetGroupList, UpdateGroup } from '@/api/modules/group';
import Header from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message';
import { Group } from '@/api/interface/group';
@@ -124,6 +124,7 @@ const saveGroup = async (formEl: FormInstance, group: Group.GroupInfo) => {
const setDefault = (group: Group.GroupInfo) => {
group.isDefault = true;
group.type = type.value;
UpdateGroup(group).then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
search();
@@ -145,7 +146,7 @@ const openCreate = () => {
isDefault: false,
edit: true,
};
data.value.push(g);
data.value.unshift(g);
};
const deleteGroup = (index: number) => {
@@ -1,7 +1,7 @@
<template>
<div>
<span class="version">{{ version }}</span>
<el-button v-if="version !== 'Waiting'" type="primary" link @click="onLoadUpgradeInfo">
<el-button v-if="version !== 'Waiting'" style="margin-top: -2px" type="primary" link @click="onLoadUpgradeInfo">
{{ $t('setting.upgradeCheck') }}
</el-button>
<el-tag v-else round style="margin-left: 10px">{{ $t('setting.upgrading') }}</el-tag>
@@ -12,10 +12,22 @@
<DrawerHeader :header="$t('setting.upgrade')" :back="handleClose" />
</template>
<div class="panel-MdEditor">
<el-alert :closable="false">
{{ $t('setting.versionHelper') }}
<li>{{ $t('setting.versionHelper1') }}</li>
<li>{{ $t('setting.versionHelper2') }}</li>
</el-alert>
<div class="default-theme">
<h2 class="inline-block">{{ $t('setting.newVersion') }}</h2>
<el-tag class="inline-block tag">{{ upgradeInfo.newVersion }}</el-tag>
<h2 class="inline-block">{{ $t('app.version') }}</h2>
</div>
<el-radio-group class="inline-block tag" v-model="upgradeVersion" @change="changeOption">
<el-radio v-if="upgradeInfo.newVersion" :label="upgradeInfo.newVersion">
{{ upgradeInfo.newVersion }} {{ $t('setting.newVersion') }}
</el-radio>
<el-radio :label="upgradeInfo.latestVersion">
{{ upgradeInfo.latestVersion }} {{ $t('setting.latestVersion') }}
</el-radio>
</el-radio-group>
<MdEditor v-model="upgradeInfo.releaseNote" previewOnly />
</div>
<template #footer>
@@ -27,13 +39,14 @@
</el-drawer>
</template>
<script setup lang="ts">
import { getSettingInfo, loadUpgradeInfo, upgrade } from '@/api/modules/setting';
import { getSettingInfo, loadReleaseNotes, loadUpgradeInfo, upgrade } from '@/api/modules/setting';
import MdEditor from 'md-editor-v3';
import i18n from '@/lang';
import 'md-editor-v3/lib/style.css';
import { MsgSuccess } from '@/utils/message';
import { onMounted, ref } from 'vue';
import { GlobalStore } from '@/store';
import { ElMessageBox } from 'element-plus';
const globalStore = GlobalStore();
const version = ref();
@@ -42,6 +55,8 @@ const drawerVisiable = ref(false);
const upgradeInfo = ref();
const refresh = ref();
const upgradeVersion = ref();
const search = async () => {
const res = await getSettingInfo();
version.value = res.data.systemVersion;
@@ -56,12 +71,12 @@ const onLoadUpgradeInfo = async () => {
await loadUpgradeInfo()
.then((res) => {
loading.value = false;
if (!res.data) {
MsgSuccess(i18n.global.t('setting.noUpgrade'));
return;
}
upgradeInfo.value = res.data;
upgradeVersion.value = upgradeInfo.value.newVersion || upgradeInfo.value.latestVersion;
drawerVisiable.value = true;
})
.catch(() => {
@@ -69,6 +84,11 @@ const onLoadUpgradeInfo = async () => {
});
};
const changeOption = async () => {
const res = await loadReleaseNotes(upgradeVersion.value);
upgradeInfo.value.releaseNote = res.data;
};
const onUpgrade = async () => {
ElMessageBox.confirm(i18n.global.t('setting.upgradeHelper', i18n.global.t('setting.upgrade')), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
@@ -76,7 +96,7 @@ const onUpgrade = async () => {
type: 'info',
}).then(async () => {
globalStore.isLoading = true;
await upgrade(upgradeInfo.value.newVersion);
await upgrade(upgradeVersion.value);
drawerVisiable.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
@@ -99,7 +119,6 @@ onMounted(() => {
height: calc(100vh - 330px);
margin-left: 70px;
.tag {
margin-left: 20px;
margin-top: -6px;
vertical-align: middle;
}
+56 -25
View File
@@ -12,6 +12,12 @@
<em>{{ $t('database.clickHelper') }}</em>
</div>
<template #tip>
<el-progress
v-if="isUpload"
text-inside
:stroke-width="12"
:percentage="uploadPrecent"
></el-progress>
<div v-if="type === 'mysql'" class="el-upload__tip">
<span class="input-help">{{ $t('database.supportUpType') }}</span>
<span class="input-help">
@@ -26,7 +32,7 @@
</div>
</template>
</el-upload>
<el-button v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
<el-button :disabled="isUpload" v-if="uploaderFiles.length === 1" icon="Upload" @click="onSubmit">
{{ $t('commons.button.upload') }}
</el-button>
@@ -82,11 +88,13 @@ import i18n from '@/lang';
import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
import { File } from '@/api/interface/file';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { BatchDeleteFile, CheckFile, GetUploadList, UploadFileData } from '@/api/modules/files';
import { BatchDeleteFile, CheckFile, ChunkUploadFileData, GetUploadList } from '@/api/modules/files';
import { loadBaseDir } from '@/api/modules/setting';
import { MsgError, MsgSuccess } from '@/utils/message';
const loading = ref();
const isUpload = ref();
const uploadPrecent = ref<number>(0);
const selects = ref<any>([]);
const baseDir = ref();
@@ -166,20 +174,12 @@ const beforeAvatarUpload = (rawFile) => {
MsgError(i18n.global.t('commons.msg.unSupportType'));
return false;
}
if (rawFile.size / 1024 / 1024 > 50) {
MsgError(i18n.global.t('commons.msg.unSupportSize', [50]));
return false;
}
return true;
}
if (!rawFile.name.endsWith('.sql') && !rawFile.name.endsWith('.tar.gz') && !rawFile.name.endsWith('.sql.gz')) {
MsgError(i18n.global.t('commons.msg.unSupportType'));
return false;
}
if (rawFile.size / 1024 / 1024 > 10) {
MsgError(i18n.global.t('commons.msg.unSupportSize', [10]));
return false;
}
return true;
};
@@ -194,42 +194,73 @@ const handleClose = () => {
};
const onSubmit = async () => {
const formData = new FormData();
if (uploaderFiles.value.length !== 1) {
return;
}
if (!uploaderFiles.value[0]!.raw.name) {
const file = uploaderFiles.value[0];
if (!file.raw.name) {
MsgError(i18n.global.t('commons.msg.fileNameErr'));
return;
}
let reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-z:A-Z0-9_.\u4e00-\u9fa5-]{0,256}$/;
if (!reg.test(uploaderFiles.value[0]!.raw.name)) {
if (!reg.test(file.raw.name)) {
MsgError(i18n.global.t('commons.msg.fileNameErr'));
return;
}
const res = await CheckFile(baseDir.value + uploaderFiles.value[0]!.raw.name);
const res = await CheckFile(baseDir.value + file.raw.name);
if (!res.data) {
MsgError(i18n.global.t('commons.msg.fileExist'));
return;
}
formData.append('file', uploaderFiles.value[0]!.raw);
let isOk = beforeAvatarUpload(uploaderFiles.value[0]!.raw);
let isOk = beforeAvatarUpload(file.raw);
if (!isOk) {
return;
}
formData.append('path', baseDir.value);
loading.value = true;
UploadFileData(formData, {})
.then(() => {
loading.value = false;
submitUpload(file);
};
const submitUpload = async (file: any) => {
isUpload.value = true;
const CHUNK_SIZE = 1024 * 1024;
const fileSize = file.size;
const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
let uploadedChunkCount = 0;
for (let i = 0; i < chunkCount; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, fileSize);
const chunk = file.raw.slice(start, end);
const formData = new FormData();
formData.append('filename', file.name);
formData.append('path', baseDir.value);
formData.append('chunk', chunk);
formData.append('chunkIndex', i.toString());
formData.append('chunkCount', chunkCount.toString());
try {
await ChunkUploadFileData(formData, {
onUploadProgress: (progressEvent) => {
const progress = Math.round(
((uploadedChunkCount + progressEvent.loaded / progressEvent.total) * 100) / chunkCount,
);
uploadPrecent.value = progress;
},
});
uploadedChunkCount++;
} catch (error) {
isUpload.value = false;
break;
}
if (uploadedChunkCount == chunkCount) {
isUpload.value = false;
uploadRef.value?.clearFiles();
uploaderFiles.value = [];
MsgSuccess(i18n.global.t('file.uploadSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
}
}
};
const onBatchDelete = async (row: File.File | null) => {
+19 -4
View File
@@ -120,7 +120,12 @@ const message = {
warnning:
'Note: [Closing the security entrance] will make your panel login address directly exposed to the Internet, very dangerous, please exercise caution',
codeInput: 'Please enter the 6-digit verification code of the MFA validator',
mfaTitle: 'MFA Certification',
mfaCode: 'MFA verification code',
title: 'Linux Server Management Panel',
licenseHelper:
'Agree to FIT2CLOUD &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank">Community Software License Agreement</a> &raquo;',
errorAgree: 'Please click to agree to the Community Software License Agreement',
},
rule: {
username: 'Please enter a username',
@@ -224,9 +229,10 @@ const message = {
kernelArch: 'Kernel arch',
network: 'Network',
io: 'Disk IO',
baseInfo: 'Base info',
totalSend: 'Total send',
totalRecv: 'Total recv',
rwPerSecond: 'RW per second',
rwPerSecond: 'IO times',
ioDelay: 'IO delay',
time: 'Times',
uptime: 'Up Time',
@@ -246,6 +252,7 @@ const message = {
loadAverage: 'Average load in the last {0} minutes',
load: 'Load',
mount: 'Mount point',
fileSystem: 'File system',
total: 'Total',
used: 'Used',
free: 'Free',
@@ -302,7 +309,7 @@ const message = {
'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
selectFile: 'Select file',
supportUpType: 'Only sql, sql.gz, and tar.gz files within 10 MB are supported',
supportUpType: 'Only sql, sql.gz, and tar.gz files are supported',
zipFormat: 'tar.gz compressed package structure: test.tar.gz compressed package must contain test.sql',
currentStatus: 'Current state',
@@ -518,6 +525,8 @@ const message = {
registrieHelper: 'One in a row, for example:\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: 'Compose',
composeHelper:
'The current content has passed the format verification. Please click Submit to complete the creation',
apps: 'Apps',
local: 'Local',
createCompose: 'Create compose',
@@ -754,6 +763,7 @@ const message = {
downlodSuccess: 'Download Success',
theme: 'Theme',
language: 'Language',
eol: 'End Of Line',
},
setting: {
all: 'All',
@@ -851,7 +861,12 @@ const message = {
upgrading: 'Please wait while the upgrade is underway...',
upgradeHelper: 'The upgrade requires a service restart. Do you want to continue?',
noUpgrade: 'It is currently the latest version',
newVersion: 'Latest version',
versionHelper:
'Name rules: [major version].[functional version].[Bug fix version], as shown in the following example:',
versionHelper1: 'v1.0.1 is a Bug fix after v1.0.0',
versionHelper2: 'v1.1.0 is a feature release after v1.0.0',
newVersion: '(Bug fix version)',
latestVersion: '(Functional version)',
upgradeCheck: 'Check for updates',
upgradeNotes: 'Release note',
upgradeNow: 'Upgrade now',
@@ -955,7 +970,7 @@ const message = {
type: 'Type',
static: 'Static',
deployment: 'Deployment',
supportUpType: 'Only .tar.gz files within 50 MB are supported',
supportUpType: 'Only .tar.gz files are supported',
zipFormat: '.tar.gz compressed package structure: test.tar.gz compressed package must contain {0} file',
proxy: 'Reverse Proxy',
alias: 'Path Name',
+18 -6
View File
@@ -123,8 +123,12 @@ const message = {
solutionHelper: '在 SSH 终端输入以下一种命令来解决 1.查看面板入口:/etc/init.d/bt default',
warnning: '注意:【关闭安全入口】将使您的面板登录地址被直接暴露在互联网上,非常危险,请谨慎操作',
codeInput: '请输入 MFA 验证器的 6 位验证码',
mfaTitle: 'MFA认证',
mfaTitle: 'MFA 认证',
mfaCode: 'MFA 验证码',
title: 'Linux 服务器运维管理面板',
licenseHelper:
'同意 &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank"> 飞致云社区软件许可协议</a> &raquo;',
errorAgree: '请点击同意社区软件许可协议',
},
rule: {
username: '请输入用户名',
@@ -229,10 +233,11 @@ const message = {
kernelArch: '系统类型',
network: '流量',
io: '磁盘 IO',
baseInfo: '基本信息',
totalSend: '总发送',
totalRecv: '总接收',
rwPerSecond: '每秒读写',
ioDelay: 'IO 延迟',
rwPerSecond: '读写次数',
ioDelay: '读写延迟',
time: '次',
uptime: '启动时间',
runningTime: '运行时间',
@@ -251,6 +256,7 @@ const message = {
loadAverage: '最近 {0} 分钟平均负载',
load: '负载',
mount: '挂载点',
fileSystem: '文件系统',
total: '总数',
used: '已用',
free: '可用',
@@ -308,7 +314,7 @@ const message = {
selectFile: '选择文件',
dropHelper: '将上传文件拖拽到此处,或者',
clickHelper: '点击上传',
supportUpType: '仅支持 10M 以内 sql、sql.gz、tar.gz 文件',
supportUpType: '仅支持 sql、sql.gz、tar.gz 文件',
zipFormat: 'tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 test.sql',
currentStatus: '当前状态',
@@ -529,6 +535,7 @@ const message = {
registrieHelper: '一行一个,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: '编排',
composeHelper: '当前内容已通过格式验证,请点击确认完成创建',
composePathHelper: '容器编排将保存在: {0}',
apps: '应用商店',
local: '本地',
@@ -760,6 +767,7 @@ const message = {
downlodSuccess: '下载完成',
theme: '主题',
language: '语言',
eol: '行尾符',
},
setting: {
all: '全部',
@@ -836,7 +844,11 @@ const message = {
upgrading: '正在升级中,请稍候...',
upgradeHelper: '升级操作需要重启服务,是否继续?',
noUpgrade: '当前已经是最新版本',
newVersion: '最新版本',
versionHelper: '1Panel 版本号命名规则为: [大版本].[功能版本].[Bug 修复版本],示例如下:',
versionHelper1: 'v1.0.1 是 v1.0.0 之后的 Bug 修复版本',
versionHelper2: 'v1.1.0 是 v1.0.0 之后的功能版本',
newVersion: '(Bug 修复版本)',
latestVersion: '(功能版本)',
upgradeCheck: '检查更新',
upgradeNotes: '更新内容',
upgradeNow: '立即更新',
@@ -963,7 +975,7 @@ const message = {
type: '类型',
static: '静态网站',
deployment: '一键部署',
supportUpType: '仅支持 50M 以内 .tar.gz 文件',
supportUpType: '仅支持 .tar.gz 文件',
zipFormat: '.tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 {0} 文件',
proxy: '反向代理',
alias: '代号',
+5 -5
View File
@@ -54,7 +54,7 @@
<div v-if="slots.prompt" class="prompt">
<slot name="prompt"></slot>
</div>
<div class="main">
<div class="main-content">
<slot name="main"></slot>
</div>
</el-card>
@@ -120,10 +120,6 @@ const showBack = computed(() => {
margin-top: 20px;
}
.main {
margin-top: 20px;
}
.prompt {
margin-top: 10px;
}
@@ -137,4 +133,8 @@ const showBack = computed(() => {
.main-box {
position: relative;
}
.main-content {
margin-top: 20px;
}
</style>
+4
View File
@@ -20,6 +20,7 @@ export const GlobalStore = defineStore({
footer: true,
},
isFullScreen: false,
agreeLicense: false,
}),
getters: {},
actions: {
@@ -45,6 +46,9 @@ export const GlobalStore = defineStore({
setThemeConfig(themeConfig: ThemeConfigProp) {
this.themeConfig = themeConfig;
},
setAgreeLicense(agree: boolean) {
this.agreeLicense = agree;
},
},
persist: piniaPersistConfig('GlobalState'),
});
+1
View File
@@ -16,6 +16,7 @@ export interface GlobalState {
// assemblySize: string; // small | default | large
themeConfig: ThemeConfigProp;
isFullScreen: boolean;
agreeLicense: boolean;
}
export interface MenuState {
+4 -1
View File
@@ -179,9 +179,12 @@
top: 220px;
left: 45%;
transform: translate(-50%, -50%);
width: 300px;
width: 400px;
text-align: center;
font-size: 14px;
.bt {
margin-top: -2px;
}
}
.table-button {
@@ -42,7 +42,14 @@
{{ $t('app.toInstall') }}
</el-link>
</span>
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'">
<el-option
v-for="service in p.values"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
<el-row :gutter="10" v-if="p.type == 'apps'">
<el-col :span="12">
<el-form-item :prop="p.prop">
@@ -11,7 +11,7 @@
</template>
<el-descriptions border :column="1" v-if="!edit">
<el-descriptions-item v-for="(param, key) in params" :label="getLabel(param)" :key="key">
{{ param.value }}
<span>{{ param.showValue && param.showValue != '' ? param.showValue : param.value }}</span>
</el-descriptions-item>
</el-descriptions>
<el-row v-else v-loading="loading">
@@ -26,6 +26,15 @@
v-model.number="paramModel[p.key]"
:disabled="!p.edit"
></el-input>
<el-select v-model="paramModel[p.key]" v-else-if="p.type == 'select'">
<el-option
v-for="value in p.values"
:key="value.label"
:value="value.value"
:label="value.label"
:disabled="!p.edit"
></el-option>
</el-select>
<el-input v-else v-model.trim="paramModel[p.key]" :disabled="!p.edit"></el-input>
</el-form-item>
</div>
@@ -116,6 +125,8 @@ const get = async () => {
edit: d.edit,
key: d.key,
type: d.type,
values: d.values,
showValue: d.showValue,
});
rules[d.key] = [Rules.requiredInput];
if (d.rule) {
@@ -1,14 +1,20 @@
<template>
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<el-drawer
v-model="drawerVisiable"
@close="handleClose"
:destroy-on-close="true"
:close-on-click-modal="false"
size="50%"
>
<template #header>
<DrawerHeader :header="$t('container.compose')" :back="handleClose" />
</template>
<div v-loading="loading">
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" label-width="80px">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" label-width="80px">
<el-form-item :label="$t('container.from')">
<el-radio-group v-model="form.from">
<el-radio-group v-model="form.from" @change="hasChecked = false">
<el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio label="path">{{ $t('container.pathSelect') }}</el-radio>
<el-radio label="template">{{ $t('container.composeTemplate') }}</el-radio>
@@ -31,7 +37,7 @@
<span class="input-help">{{ $t('container.composePathHelper', [composeFile]) }}</span>
</el-form-item>
<el-form-item v-if="form.from === 'template'" prop="template">
<el-select v-model="form.template">
<el-select v-model="form.template" @change="hasChecked = false">
<el-option
v-for="item in templateOptions"
:key="item.id"
@@ -46,25 +52,61 @@
placeholder="#Define or paste the content of your docker-compose file here"
:indent-with-tab="true"
:tabSize="4"
style="width: 100%; height: calc(100vh - 340px)"
style="width: 100%; height: 250px"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
@change="hasChecked = false"
:styleActiveLine="true"
:extensions="extensions"
v-model="form.file"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-form>
<codemirror
v-if="logVisiable && form.from !== 'edit'"
:autofocus="true"
placeholder="Waiting for docker-compose up output..."
:indent-with-tab="true"
:tabSize="4"
style="height: calc(100vh - 370px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
@ready="handleReady"
v-model="logInfo"
:readOnly="true"
/>
<codemirror
v-if="logVisiable && form.from === 'edit'"
:autofocus="true"
placeholder="Waiting for docker-compose up output..."
:indent-with-tab="true"
:tabSize="4"
style="height: calc(100vh - 590px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
@ready="handleReady"
v-model="logInfo"
:readOnly="true"
/>
</el-col>
</el-row>
</div>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="drawerVisiable = false">
<el-button @click="drawerVisiable = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" :disabled="loading" @click="onSubmit(formRef)">
<el-button :disabled="buttonDisabled" @click="onTest(formRef)">
{{ $t('commons.button.verify') }}
</el-button>
<el-button type="primary" :disabled="buttonDisabled || !hasChecked" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -73,7 +115,7 @@
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { nextTick, reactive, ref, shallowRef } from 'vue';
import FileList from '@/components/file-list/index.vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
@@ -82,24 +124,33 @@ import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { listComposeTemplate, upCompose } from '@/api/modules/container';
import { MsgSuccess } from '@/utils/message';
import { listComposeTemplate, testCompose, upCompose } from '@/api/modules/container';
import { loadBaseDir } from '@/api/modules/setting';
import { LoadFile } from '@/api/modules/files';
import { formatImageStdout } from '@/utils/docker';
import { MsgSuccess } from '@/utils/message';
const loading = ref();
const extensions = [javascript(), oneDark];
const view = shallowRef();
const handleReady = (payload) => {
view.value = payload.view;
};
const logVisiable = ref();
const logInfo = ref();
const drawerVisiable = ref(false);
const templateOptions = ref();
const buttonDisabled = ref(false);
const loading = ref(false);
const baseDir = ref();
const composeFile = ref();
const varifyPath = (rule: any, value: any, callback: any) => {
if (value.indexOf('docker-compose.yml') === -1) {
callback(new Error(i18n.global.t('commons.rule.selectHelper', ['docker-compose.yml'])));
}
callback();
};
const hasChecked = ref();
let timer: NodeJS.Timer | null = null;
const form = reactive({
name: '',
from: 'edit',
@@ -109,7 +160,7 @@ const form = reactive({
});
const rules = reactive({
name: [Rules.requiredInput, Rules.imageName],
path: [Rules.requiredSelect, { validator: varifyPath, trigger: 'change', required: true }],
path: [Rules.requiredSelect],
});
const loadTemplates = async () => {
@@ -126,12 +177,18 @@ const acceptParams = (): void => {
form.from = 'edit';
form.path = '';
form.file = '';
logVisiable.value = false;
hasChecked.value = false;
logInfo.value = '';
loadTemplates();
loadPath();
};
const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => {
emit('search');
clearInterval(Number(timer));
timer = null;
drawerVisiable.value = false;
};
@@ -148,26 +205,64 @@ const changePath = async () => {
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const onSubmit = async (formEl: FormInstance | undefined) => {
const onTest = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
upCompose(form)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
await testCompose(form)
.then((res) => {
loading.value = false;
emit('search');
drawerVisiable.value = false;
if (res.data) {
MsgSuccess(i18n.global.t('container.composeHelper'));
hasChecked.value = true;
}
})
.finally(() => {
.catch(() => {
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
const res = await upCompose(form);
logInfo.value = '';
buttonDisabled.value = true;
logVisiable.value = true;
loadLogs(res.data);
});
};
const loadLogs = async (path: string) => {
timer = setInterval(async () => {
if (logVisiable.value) {
const res = await LoadFile({ path: path });
logInfo.value = formatImageStdout(res.data);
nextTick(() => {
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
});
});
if (
logInfo.value.endsWith('docker-compose up failed!') ||
logInfo.value.endsWith('docker-compose up successful!')
) {
clearInterval(Number(timer));
timer = null;
buttonDisabled.value = false;
}
}
}, 1000 * 3);
};
const loadDir = async (path: string) => {
form.path = path;
hasChecked.value = false;
};
defineExpose({
+19 -18
View File
@@ -3,20 +3,13 @@
<div v-show="isOnDetail">
<ComposeDetial @back="backList" ref="composeDetailRef" />
</div>
<el-card width="30%" v-if="dockerStatus != 'Running'" class="mask-prompt">
<span style="font-size: 14px">{{ $t('container.serviceUnavailable') }}</span>
<el-button type="primary" link style="font-size: 14px; margin-bottom: 5px" @click="goSetting">
【 {{ $t('container.setting') }} 】
</el-button>
<span style="font-size: 14px">{{ $t('container.startIn') }}</span>
<el-card v-if="dockerStatus != 'Running'" class="mask-prompt">
<span>{{ $t('container.serviceUnavailable') }}</span>
<el-button type="primary" link class="bt" @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
<span>{{ $t('container.startIn') }}</span>
</el-card>
<LayoutContent
v-loading="loading"
v-if="!isOnDetail"
:title="$t('container.compose')"
:class="{ mask: dockerStatus != 'Running' }"
>
<LayoutContent v-if="!isOnDetail" :title="$t('container.compose')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -111,13 +104,21 @@ const paginationConfig = reactive({
});
const searchName = ref();
const dockerStatus = ref();
const dockerStatus = ref('Running');
const loadStatus = async () => {
const res = await loadDockerStatus();
dockerStatus.value = res.data;
if (dockerStatus.value === 'Running') {
search();
}
loading.value = true;
await loadDockerStatus()
.then((res) => {
loading.value = false;
dockerStatus.value = res.data;
if (dockerStatus.value === 'Running') {
search();
}
})
.catch(() => {
dockerStatus.value = 'Failed';
loading.value = false;
});
};
const goSetting = async () => {
router.push({ name: 'ContainerSetting' });
@@ -1,17 +1,11 @@
<template>
<div>
<el-card width="30%" v-if="dockerStatus != 'Running'" class="mask-prompt">
<span style="font-size: 14px">{{ $t('container.serviceUnavailable') }}</span>
<el-button type="primary" link style="font-size: 14px; margin-bottom: 5px" @click="goSetting">
【 {{ $t('container.setting') }} 】
</el-button>
<span style="font-size: 14px">{{ $t('container.startIn') }}</span>
<div v-loading="loading">
<el-card v-if="dockerStatus != 'Running'" class="mask-prompt">
<span>{{ $t('container.serviceUnavailable') }}</span>
<el-button type="primary" class="bt" link @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
<span>{{ $t('container.startIn') }}</span>
</el-card>
<LayoutContent
v-loading="loading"
:title="$t('container.container')"
:class="{ mask: dockerStatus != 'Running' }"
>
<LayoutContent :title="$t('container.container')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -141,13 +135,21 @@ const paginationConfig = reactive({
});
const searchName = ref();
const dockerStatus = ref();
const dockerStatus = ref('Running');
const loadStatus = async () => {
const res = await loadDockerStatus();
dockerStatus.value = res.data;
if (dockerStatus.value === 'Running') {
search();
}
loading.value = true;
await loadDockerStatus()
.then((res) => {
loading.value = false;
dockerStatus.value = res.data;
if (dockerStatus.value === 'Running') {
search();
}
})
.catch(() => {
dockerStatus.value = 'Failed';
loading.value = false;
});
};
const goSetting = async () => {
router.push({ name: 'ContainerSetting' });
@@ -2,7 +2,7 @@
<el-drawer
v-model="drawerVisiable"
:destroy-on-close="true"
@close="onCloseLog"
@close="handleClose"
:close-on-click-modal="false"
size="50%"
>
@@ -116,16 +116,11 @@ const form = reactive({
tagStr: '',
tags: [] as Array<string>,
});
const varifyPath = (rule: any, value: any, callback: any) => {
if (value.indexOf('Dockerfile') === -1) {
callback(new Error(i18n.global.t('commons.rule.selectHelper', ['Dockerfile'])));
}
callback();
};
const rules = reactive({
name: [Rules.requiredInput, Rules.imageName],
from: [Rules.requiredSelect],
dockerfile: [Rules.requiredInput, { validator: varifyPath, trigger: 'change', required: true }],
dockerfile: [Rules.requiredInput],
});
const acceptParams = async () => {
logVisiable.value = false;
@@ -141,6 +136,9 @@ const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => {
drawerVisiable.value = false;
emit('search');
clearInterval(Number(timer));
timer = null;
};
type FormInstance = InstanceType<typeof ElForm>;
@@ -181,11 +179,6 @@ const loadLogs = async (path: string) => {
}
}, 1000 * 3);
};
const onCloseLog = async () => {
emit('search');
clearInterval(Number(timer));
timer = null;
};
const loadBuildDir = async (path: string) => {
form.dockerfile = path;

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