Compare commits

..
35 Commits
Author SHA1 Message Date
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
105 changed files with 1571 additions and 1115 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
+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
}
+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)
+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"`
}
+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
}
+4
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)
}
+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),
+2 -2
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
}
+3 -2
View File
@@ -3,6 +3,7 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os/exec"
@@ -33,7 +34,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
@@ -239,7 +240,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
}
+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
}
-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
+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"
+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")
},
}
+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
}
+134 -28
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": "OK"
}
},
"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": "OK"
}
}
},
@@ -8410,6 +8454,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": [
@@ -10652,6 +10747,9 @@ var doc = `{
"dto.UpgradeInfo": {
"type": "object",
"properties": {
"latestVersion": {
"type": "string"
},
"newVersion": {
"type": "string"
},
@@ -11441,17 +11539,6 @@ var doc = `{
}
}
},
"request.FilePath": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
}
}
},
"request.FilePathCheck": {
"type": "object",
"required": [
@@ -11987,6 +12074,21 @@ var doc = `{
}
}
},
"request.WebsiteSSLUpdate": {
"type": "object",
"required": [
"autoRenew",
"id"
],
"properties": {
"autoRenew": {
"type": "boolean"
},
"id": {
"type": "integer"
}
}
},
"request.WebsiteSearch": {
"type": "object",
"required": [
@@ -12225,10 +12327,14 @@ var doc = `{
"rule": {
"type": "string"
},
"showValue": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {}
"value": {},
"values": {}
}
},
"response.FileInfo": {
+134 -28
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": "OK"
}
},
"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": "OK"
}
}
},
@@ -8396,6 +8440,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": [
@@ -10638,6 +10733,9 @@
"dto.UpgradeInfo": {
"type": "object",
"properties": {
"latestVersion": {
"type": "string"
},
"newVersion": {
"type": "string"
},
@@ -11427,17 +11525,6 @@
}
}
},
"request.FilePath": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
}
}
},
"request.FilePathCheck": {
"type": "object",
"required": [
@@ -11973,6 +12060,21 @@
}
}
},
"request.WebsiteSSLUpdate": {
"type": "object",
"required": [
"autoRenew",
"id"
],
"properties": {
"autoRenew": {
"type": "boolean"
},
"id": {
"type": "integer"
}
}
},
"request.WebsiteSearch": {
"type": "object",
"required": [
@@ -12211,10 +12313,14 @@
"rule": {
"type": "string"
},
"showValue": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {}
"value": {},
"values": {}
}
},
"response.FileInfo": {
+87 -19
View File
@@ -1389,6 +1389,8 @@ definitions:
type: object
dto.UpgradeInfo:
properties:
latestVersion:
type: string
newVersion:
type: string
releaseNote:
@@ -1912,13 +1914,6 @@ definitions:
showHidden:
type: boolean
type: object
request.FilePath:
properties:
path:
type: string
required:
- path
type: object
request.FilePathCheck:
properties:
path:
@@ -2277,6 +2272,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 +2441,12 @@ definitions:
type: string
rule:
type: string
showValue:
type: string
type:
type: string
value: {}
values: {}
type: object
response.FileInfo:
properties:
@@ -3269,6 +3277,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: OK
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 +5181,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 +5329,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/request.FilePath'
$ref: '#/definitions/dto.FilePath'
responses:
"200":
description: ""
@@ -6968,15 +6996,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'
security:
- ApiKeyAuth: []
summary: Load upgrade info
summary: Load release notes by version
tags:
- System Setting
post:
@@ -7955,6 +7990,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;
+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;
+5 -2
View File
@@ -9,7 +9,7 @@ export const createContainer = (params: Container.ContainerCreate) => {
return http.post(`/containers`, params, 1200000);
};
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) => {
+16 -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',
@@ -226,7 +231,7 @@ const message = {
io: 'Disk IO',
totalSend: 'Total send',
totalRecv: 'Total recv',
rwPerSecond: 'RW per second',
rwPerSecond: 'IO times',
ioDelay: 'IO delay',
time: 'Times',
uptime: 'Up Time',
@@ -302,7 +307,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 +523,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',
@@ -851,7 +858,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 +967,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',
+15 -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:
'同意 FIT2CLOUD 飞致云 &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank"> 社区软件许可协议</a> &raquo;',
errorAgree: '请点击同意社区软件许可协议',
},
rule: {
username: '请输入用户名',
@@ -231,8 +235,8 @@ const message = {
io: '磁盘 IO',
totalSend: '总发送',
totalRecv: '总接收',
rwPerSecond: '每秒读写',
ioDelay: 'IO 延迟',
rwPerSecond: '读写次数',
ioDelay: '读写延迟',
time: '次',
uptime: '启动时间',
runningTime: '运行时间',
@@ -308,7 +312,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 +533,7 @@ const message = {
registrieHelper: '一行一个,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: '编排',
composeHelper: '当前内容已通过格式验证,请点击确认完成创建',
composePathHelper: '容器编排将保存在: {0}',
apps: '应用商店',
local: '本地',
@@ -836,7 +841,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 +972,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;
+20 -14
View File
@@ -1,14 +1,12 @@
<template>
<div v-loading="loading">
<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" :title="$t('container.image')" :class="{ mask: dockerStatus != 'Running' }">
<LayoutContent :title="$t('container.image')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -110,14 +108,22 @@ 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();
loadRepos();
}
loading.value = true;
await loadDockerStatus()
.then((res) => {
loading.value = false;
dockerStatus.value = res.data;
if (dockerStatus.value === 'Running') {
search();
loadRepos();
}
})
.catch(() => {
dockerStatus.value = 'Failed';
loading.value = false;
});
};
const goSetting = async () => {
router.push({ name: 'ContainerSetting' });
+20 -18
View File
@@ -1,18 +1,12 @@
<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" link class="bt" @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
<span>{{ $t('container.startIn') }}</span>
</el-card>
<LayoutContent
v-loading="loading"
:title="$t('container.network')"
:class="{ mask: dockerStatus != 'Running' }"
>
<LayoutContent :title="$t('container.network')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -124,13 +118,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' });
+20 -14
View File
@@ -1,14 +1,12 @@
<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" link class="bt" @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
<span>{{ $t('container.startIn') }}</span>
</el-card>
<LayoutContent v-loading="loading" :title="$t('container.repo')" :class="{ mask: dockerStatus != 'Running' }">
<LayoutContent :title="$t('container.repo')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -99,13 +97,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' });
+20 -18
View File
@@ -1,18 +1,12 @@
<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" link class="bt" @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
<span>{{ $t('container.startIn') }}</span>
</el-card>
<LayoutContent
v-loading="loading"
:title="$t('container.composeTemplate')"
:class="{ mask: dockerStatus != 'Running' }"
>
<LayoutContent :title="$t('container.composeTemplate')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -96,13 +90,21 @@ const searchName = ref();
const detailRef = 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' });
+20 -14
View File
@@ -1,14 +1,12 @@
<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" link class="bt" @click="goSetting">【 {{ $t('container.setting') }} 】</el-button>
<span>{{ $t('container.startIn') }}</span>
</el-card>
<LayoutContent v-loading="loading" :title="$t('container.volume')" :class="{ mask: dockerStatus != 'Running' }">
<LayoutContent :title="$t('container.volume')" :class="{ mask: dockerStatus != 'Running' }">
<template #toolbar>
<el-row>
<el-col :span="16">
@@ -104,13 +102,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' });
+29 -37
View File
@@ -84,7 +84,7 @@
<el-col :span="8">
<el-date-picker
style="width: calc(100% - 20px)"
@change="search()"
@change="search(true)"
v-model="timeRangeLoad"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
@@ -94,7 +94,7 @@
></el-date-picker>
</el-col>
<el-col :span="16">
<el-select @change="search()" v-model="searchInfo.status">
<el-select @change="search(true)" v-model="searchInfo.status">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value="" />
<el-option :label="$t('commons.status.success')" value="Success" />
@@ -301,34 +301,15 @@ interface DialogProps {
}
const recordShow = ref(false);
const dialogData = ref();
const records = ref<Array<Cronjob.Record>>();
const records = ref<Array<Cronjob.Record>>([]);
const currentRecord = ref<Cronjob.Record>();
const currentRecordDetail = ref<string>('');
const currentRecordIndex = ref();
const acceptParams = async (params: DialogProps): Promise<void> => {
dialogData.value = params;
let itemSearch = {
page: searchInfo.page,
pageSize: searchInfo.pageSize,
cronjobID: dialogData.value.rowData!.id,
startTime: new Date(new Date().setHours(0, 0, 0, 0)),
endTime: new Date(new Date().setHours(23, 59, 59, 0)),
status: searchInfo.status,
};
const res = await searchRecords(itemSearch);
records.value = res.data.items || [];
if (records.value.length === 0) {
hasRecords.value = false;
recordShow.value = true;
return;
}
hasRecords.value = true;
currentRecord.value = records.value[0];
currentRecordIndex.value = 0;
loadRecord(currentRecord.value);
searchInfo.recordTotal = res.data.total;
recordShow.value = true;
search(true);
};
const shortcuts = [
@@ -343,33 +324,36 @@ const shortcuts = [
{
text: i18n.global.t('monitor.yestoday'),
value: () => {
const yestoday = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1);
const end = new Date(yestoday.setHours(23, 59, 59, 999));
const start = new Date(yestoday.setHours(0, 0, 0, 0));
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1);
const end = new Date(itemDate.setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [3]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3);
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3);
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [7]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7);
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7);
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [30]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30);
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30);
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
@@ -384,16 +368,16 @@ const weekOptions = [
{ label: i18n.global.t('cronjob.sunday'), value: 7 },
];
const timeRangeLoad = ref<[Date, Date]>([
new Date(new Date().setHours(0, 0, 0, 0)),
new Date(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7).setHours(0, 0, 0, 0)),
new Date(new Date().setHours(23, 59, 59, 999)),
]);
const searchInfo = reactive({
page: 1,
pageSize: 8,
pageSize: 12,
recordTotal: 0,
cronjobID: 0,
startTime: new Date(new Date().setHours(0, 0, 0, 0)),
endTime: new Date(new Date().setHours(23, 59, 59, 999)),
startTime: new Date(),
endTime: new Date(),
status: '',
});
@@ -406,7 +390,7 @@ const onHandle = async (row: Cronjob.CronjobInfo) => {
searchInfo.pageSize = searchInfo.pageSize * searchInfo.page;
searchInfo.page = 1;
records.value = [];
search();
search(false);
})
.catch(() => {
loading.value = false;
@@ -425,7 +409,12 @@ const onChangeStatus = async (id: number, status: string) => {
});
};
const search = async () => {
const search = async (isInit: boolean) => {
if (isInit) {
searchInfo.page = 1;
searchInfo.pageSize = 12;
records.value = [];
}
if (timeRangeLoad.value && timeRangeLoad.value.length === 2) {
searchInfo.startTime = timeRangeLoad.value[0];
searchInfo.endTime = timeRangeLoad.value[1];
@@ -446,6 +435,9 @@ const search = async () => {
hasRecords.value = false;
return;
}
if (!res.data.items) {
return;
}
for (const item of res.data.items) {
records.value.push(item);
}
@@ -460,7 +452,7 @@ const onRefresh = () => {
records.value = [];
searchInfo.pageSize = searchInfo.pageSize * searchInfo.page;
searchInfo.page = 1;
search();
search(true);
};
const onDownload = async (record: any, backupID: number) => {
@@ -492,7 +484,7 @@ const nextPage = async () => {
return;
}
searchInfo.page = searchInfo.page + 1;
search();
search(false);
};
const forDetail = async (row: Cronjob.Record, index: number) => {
currentRecord.value = row;
+1 -1
View File
@@ -112,7 +112,7 @@
v-if="mysqlStatus != 'Running' && !isOnSetting && mysqlIsExist && !loading"
class="mask-prompt"
>
<span style="font-size: 14px">{{ $t('commons.service.serviceNotStarted', ['MySQL']) }}</span>
<span>{{ $t('commons.service.serviceNotStarted', ['MySQL']) }}</span>
</el-card>
<Setting ref="settingRef" style="margin-top: 20px" />
+1 -1
View File
@@ -24,7 +24,7 @@
</LayoutContent>
<el-card width="30%" v-if="redisStatus != 'Running' && !isOnSetting && redisIsExist" class="mask-prompt">
<span style="font-size: 14px">{{ $t('commons.service.serviceNotStarted', ['Redis']) }}</span>
<span>{{ $t('commons.service.serviceNotStarted', ['Redis']) }}</span>
</el-card>
<Setting ref="settingRef" style="margin-top: 30px" />
+1 -1
View File
@@ -104,7 +104,7 @@
<el-tag>
{{ $t('home.rwPerSecond') }}: {{ currentChartInfo.ioCount }} {{ $t('home.time') }}
</el-tag>
<el-tag>{{ $t('home.rwPerSecond') }}: {{ currentInfo.ioTime }} ms</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentInfo.ioTime }} ms</el-tag>
</div>
<div v-if="chartOption === 'io'" style="margin-top: 40px">
@@ -15,7 +15,6 @@
:auto-upload="false"
ref="uploadRef"
:on-change="fileOnChange"
v-loading="loading"
:limit="1"
:on-exceed="handleExceed"
>
@@ -24,8 +23,10 @@
{{ $t('database.dropHelper') }}
<em>{{ $t('database.clickHelper') }}</em>
</div>
<template #tip>
<el-progress v-if="loading" text-inside :stroke-width="12" :percentage="uploadPrecent"></el-progress>
</template>
</el-upload>
<el-progress v-if="loading" :text-inside="true" :stroke-width="26" :percentage="uploadPrecent"></el-progress>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
@@ -109,6 +110,7 @@ const submit = async () => {
uploadedChunkCount++;
} catch (error) {
loading.value = false;
break;
}
if (uploadedChunkCount == chunkCount) {
loading.value = false;
+5 -5
View File
@@ -21,7 +21,7 @@
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; width: 330px"
style="float: right; width: 360px"
></el-date-picker>
</template>
<div id="loadLoadChart" style="width: 100%; height: 400px"></div>
@@ -41,7 +41,7 @@
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; width: 330px"
style="float: right; width: 360px"
></el-date-picker>
</template>
<div id="loadCPUChart" style="width: 100%; height: 400px"></div>
@@ -59,7 +59,7 @@
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; width: 330px"
style="float: right; width: 360px"
></el-date-picker>
</template>
<div id="loadMemoryChart" style="width: 100%; height: 400px"></div>
@@ -79,7 +79,7 @@
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; width: 330px"
style="float: right; width: 360px"
></el-date-picker>
</template>
<div id="loadIOChart" style="width: 100%; height: 400px"></div>
@@ -114,7 +114,7 @@
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; width: 330px"
style="float: right; width: 360px"
></el-date-picker>
</template>
<div id="loadNetworkChart" style="width: 100%; height: 400px"></div>
@@ -10,12 +10,7 @@
<el-form-item :label="$t('commons.table.group')" prop="group">
<el-select filterable v-model="dialogData.groupID" clearable style="width: 100%">
<div v-for="item in groupList" :key="item.id">
<el-option
v-if="item.name === 'default'"
:label="$t('website.default')"
:value="item.id"
/>
<el-option v-else :label="item.name" :value="item.id" />
<el-option :label="item.name" :value="item.id" />
</div>
</el-select>
</el-form-item>
@@ -38,7 +33,8 @@
import { ref, reactive } from 'vue';
import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules';
import { editHostGroup, GetGroupList } from '@/api/modules/host';
import { editHostGroup } from '@/api/modules/host';
import { GetGroupList } from '@/api/modules/group';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
@@ -34,8 +34,7 @@
<template #prefix>{{ $t('terminal.group') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<div v-for="item in groupList" :key="item.name">
<el-option v-if="item.name === 'default'" :label="$t('website.default')" :value="item.id" />
<el-option v-else :value="item.id" :label="item.name" />
<el-option :value="item.id" :label="item.name" />
</div>
</el-select>
</template>
@@ -84,7 +83,8 @@ import GroupDialog from '@/components/group/index.vue';
import GroupChangeDialog from '@/views/host/terminal/host/change-group/index.vue';
import OperateDialog from '@/views/host/terminal/host/operate/index.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { deleteHost, GetGroupList, searchHosts } from '@/api/modules/host';
import { deleteHost, searchHosts } from '@/api/modules/host';
import { GetGroupList } from '@/api/modules/group';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { Host } from '@/api/interface/host';
@@ -77,7 +77,8 @@
import { ref, reactive } from 'vue';
import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules';
import { addHost, editHost, testByInfo, GetGroupList } from '@/api/modules/host';
import { addHost, editHost, testByInfo } from '@/api/modules/host';
import { GetGroupList } from '@/api/modules/group';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
@@ -1,5 +1,5 @@
<template>
<div>
<div v-loading="loading">
<div v-if="isFirst">
<div class="login-form">
<el-form ref="registerFormRef" :model="registerForm" size="default" :rules="registerRules">
@@ -70,13 +70,13 @@
</div>
<div v-else-if="mfaShow">
<div class="login-form">
<el-form>
<el-form @submit.prevent>
<div class="login-title">{{ $t('commons.login.mfaTitle') }}</div>
<el-form-item class="no-border">
<el-input
size="default"
:placeholder="$t('commons.login.captchaHelper')"
v-model="mfaLoginForm.code"
:placeholder="$t('commons.login.mfaCode')"
v-model.trim="mfaLoginForm.code"
>
<template #prefix>
<el-icon class="el-input__icon">
@@ -89,7 +89,15 @@
</span>
</el-form-item>
<el-form-item>
<el-button class="login-button" type="primary" size="default" round @click="mfaLogin()">
<el-button
@focus="mfaButtonFocused = true"
@blur="mfaButtonFocused = false"
class="login-button"
type="primary"
size="default"
round
@click="mfaLogin()"
>
{{ $t('commons.button.verify') }}
</el-button>
</el-form-item>
@@ -157,6 +165,20 @@
{{ $t('commons.button.login') }}
</el-button>
</el-form-item>
<el-form-item prop="agreeLicense">
<el-checkbox v-model="loginForm.agreeLicense">
<template #default>
<span v-html="$t('commons.login.licenseHelper')"></span>
</template>
</el-checkbox>
<span
v-if="errAgree && loginForm.agreeLicense === false"
class="input-error"
style="line-height: 14px"
>
{{ $t('commons.login.errorAgree') }}
</span>
</el-form-item>
</el-form>
<div class="demo">
<span v-if="isDemo">
@@ -186,6 +208,7 @@ const errAuthInfo = ref(false);
const errCaptcha = ref(false);
const errMfaInfo = ref(false);
const isDemo = ref(false);
const errAgree = ref(false);
const isFirst = ref();
@@ -212,11 +235,14 @@ const loginForm = reactive({
captcha: '',
captchaID: '',
authMethod: '',
agreeLicense: false,
});
const loginRules = reactive({
name: [{ required: true, message: i18n.global.t('commons.rule.username'), trigger: 'blur' }],
password: [{ required: true, message: i18n.global.t('commons.rule.password'), trigger: 'blur' }],
});
const mfaButtonFocused = ref();
const mfaLoginForm = reactive({
name: '',
password: '',
@@ -250,36 +276,43 @@ const login = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let requestLoginForm = {
name: loginForm.name,
password: loginForm.password,
captcha: loginForm.captcha,
captchaID: captcha.captchaID,
authMethod: '',
};
if (requestLoginForm.captcha == '') {
errCaptcha.value = true;
return;
}
if (loginForm.agreeLicense == false) {
errAgree.value = true;
return;
}
try {
let requestLoginForm = {
name: loginForm.name,
password: loginForm.password,
captcha: loginForm.captcha,
captchaID: captcha.captchaID,
authMethod: '',
};
loading.value = true;
const res = await loginApi(requestLoginForm);
if (res.code === 406) {
if (res.message === 'ErrCaptchaCode') {
errCaptcha.value = true;
errAuthInfo.value = false;
loginVerify();
}
if (res.message === 'ErrAuth') {
errCaptcha.value = false;
errAuthInfo.value = true;
loginVerify();
}
loginVerify();
return;
}
if (res.data.mfaStatus === 'enable') {
mfaShow.value = true;
errMfaInfo.value = false;
mfaLoginForm.secret = res.data.mfaSecret;
return;
}
globalStore.setLogStatus(true);
globalStore.setAgreeLicense(true);
menuStore.setMenuList([]);
MsgSuccess(i18n.global.t('commons.msg.loginSuccess'));
router.push({ name: 'home' });
@@ -335,17 +368,22 @@ function checkPassword(rule: any, value: any, callback: any) {
onMounted(() => {
document.title = globalStore.themeConfig.panelName;
loginForm.agreeLicense = globalStore.agreeLicense;
checkStatus();
checkIsSystemDemo();
document.onkeydown = (e: any) => {
e = window.event || e;
if (e.keyCode === 13) {
if (loading.value) return;
if (isFirst.value && !registerButtonFocused.value) {
register(registerFormRef.value);
if (!mfaShow.value) {
if (isFirst.value && !registerButtonFocused.value) {
register(registerFormRef.value);
}
if (!isFirst.value && !loginButtonFocused.value) {
login(loginFormRef.value);
}
}
if (!isFirst.value && !loginButtonFocused.value) {
login(loginFormRef.value);
if (mfaShow.value && !mfaButtonFocused.value) {
mfaLogin();
}
}
};

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