Compare commits

...
92 Commits
Author SHA1 Message Date
zhengkunwang223 8c5c2440fe fix: 解决定时任务停止网站失败的问题 (#933) 2023-05-08 09:03:41 +00:00
ssongliu f22caed20c fix: 提示信息国际化修改 (#931) 2023-05-08 08:49:41 +00:00
zhengkunwang223 ed3e7e7c26 fix: 解决停止一键部署网站失败的问题 (#930) 2023-05-08 08:39:41 +00:00
zhengkunwang223 54bc33a1a2 fix: 解决删除 Openresty 之后网站列表页面报错的问题 (#926) 2023-05-08 07:11:39 +00:00
ssongliu 900e141297 feat: 增加 iptables 设置修改提示信息 (#925) 2023-05-08 06:57:39 +00:00
zhengkunwang223 ca1eca476c feat: 解决时区获取不到导致的网站自动停止的问题 (#921) 2023-05-08 05:01:39 +00:00
ssongliu c4d2593a42 feat: 更新 ip 地址库 (#922) 2023-05-08 04:55:39 +00:00
zhengkunwang223 932811c167 feat: 文件搜索框样式调整 (#919) 2023-05-08 03:23:37 +00:00
zhengkunwang223 95ad101a08 fix: 解决同名反向代理失败导致文件被删除的问题 (#910) 2023-05-06 11:13:34 +00:00
zhengkunwang223 a0c329019f fix: 解决应用列表页面搜索之后点击安装没响应的问题 (#909) 2023-05-06 09:57:34 +00:00
ssongliu 8a92913230 fix: 解决计划任务部分脚本执行没有输出的问题 (#908) 2023-05-06 09:23:34 +00:00
ssongliu 5a8deddc63 feat: ssl 设置增加重启提示信息 (#905) 2023-05-06 08:51:34 +00:00
ssongliu 2a1e60da44 fix: 创建编排时,默认显示编辑模式 (#904) 2023-05-06 08:49:38 +00:00
ssongliu 6ca3deeac1 fix: 优化编排删除提示信息 (#902) 2023-05-06 08:09:33 +00:00
zhengkunwang223 1b2edbb79a feat: 非静态网站和运行环境网站隐藏网站目录权限设置 (#901) 2023-05-06 08:07:38 +00:00
zhengkunwang223 49c9929a53 feat: 网站域名跳转增加端口 (#900) 2023-05-06 07:49:33 +00:00
zhengkunwang223 8ce73a38dd feat: 网站新增域名,自动放开相应端口 (#899) 2023-05-06 07:27:34 +00:00
zhengkunwang223 28ebf7a0cc fix: 解决文件搜索不存在的资源后,点击下面存在的目录也会提示资源不存在的问题 (#896) 2023-05-06 06:49:33 +00:00
ssongliu 4f168656fc fix: mfa 启用时,增加时间同步提示信息 (#895) 2023-05-06 06:27:33 +00:00
zhengkunwang223 7f1f758e60 feat: 网站列表增加网站目录跳转功能 (#892) 2023-05-06 04:05:33 +00:00
maninhill f8ab71953d chore: remove console.log 2023-05-06 11:22:24 +08:00
ssongliu eb50ee1c03 fix: 数据库创建重名用户问题修改 (#887) 2023-05-06 02:15:32 +00:00
ssongliu a184cb9bc4 fix: 证书创建校验修改 (#884) 2023-05-05 10:31:30 +00:00
zhengkunwang223 0b50a10fb8 feat: 搜索子目录增加提示 (#883) 2023-05-05 09:35:30 +00:00
zhengkunwang223 9bafdc1b0b 增加安全问题联系人 (#882) 2023-05-05 09:33:34 +00:00
ssongliu 63af54353b feat: 监控设置改到【主机-监控】菜单内 (#881) 2023-05-05 09:31:31 +00:00
ssongliu b67739af13 feat: 面板设置统一改为抽屉实现 (#880) 2023-05-05 07:54:52 +00:00
zhengkunwang223 40633619ca fix: 解决解压 mac 压缩文件失败的问题 (#879) 2023-05-05 07:50:52 +00:00
zhengkunwang223 e672d1d896 fix: 解决反向代理网站停止不生效的问题 (#878) 2023-05-05 07:02:54 +00:00
zhengkunwang223 c69d162f5a fix: 修改用户名长度限制 (#877) 2023-05-05 06:42:52 +00:00
ssongliu 2fd97b1753 fix: 解决列表查询输入框刷新的问题 (#876) 2023-05-05 06:16:52 +00:00
zhengkunwang223 5f893929da fix: 解决反向代理停用无法删除的问题 (#875) 2023-05-05 06:14:56 +00:00
ssongliu 1c06b7b608 fix: 删除初始化用户界面及接口 (#874) 2023-05-05 03:56:52 +00:00
ssongliu 3ac64d65b6 fix: 修改初始化错误返回 (#873) 2023-05-05 03:40:51 +00:00
ssongliu f4f83283d3 fix: 容器创建端口校验修改 (#872) 2023-05-05 10:17:28 +07:00
zhengkunwang223 aed4a55655 feat: 升级 gin 版本到 v1.9.0 (#871) 2023-05-05 02:56:56 +00:00
ssongliu 5f55a56a9e fix: 指定 IP 输入增加提示信息 (#867) 2023-05-04 11:12:52 +00:00
ssongliu e95e79b572 fix: 限制描述长度 (#866) 2023-05-04 09:44:41 +00:00
ssongliu 15c0d0074b fix: 安全入口逻辑调整 (#863) 2023-05-04 08:40:37 +00:00
zhengkunwang223 f4716cb62f feat: 创建网站支持 ipv6 (#861) 2023-05-04 06:28:38 +00:00
ssongliu 1f2dfce7d1 fix: 初始化密码时,对密码进行加密操作 (#860) 2023-05-04 06:14:38 +00:00
ssongliu c679631440 fix: 时区问题修改 (#855) 2023-05-04 04:00:37 +00:00
zhengkunwang223 c62fd4841a feat: 增加已安装应用操作的执行时间 (#854) 2023-05-04 03:58:41 +00:00
ssongliu a073113817 fix: redis 按钮位置调整 (#856) 2023-05-04 03:56:37 +00:00
ssongliu 09d462b829 feat: 验证码输入判断逻辑修改 (#842) 2023-04-30 15:58:24 +00:00
ssongliu 379b171f0a fix: 解决容器创建批量端口失败的问题 (#830) 2023-04-28 14:46:20 +00:00
ssongliu c424e22924 fix: 两步验证样式调整 (#827) 2023-04-28 07:58:19 +00:00
zhengkunwang223 18e171446e feat: 解决文件上传成功后再次点击“确认”会重新上传一遍的问题 (#826) 2023-04-28 07:50:18 +00:00
zhengkunwang223 c0a1555c73 style: 网站列表增加 WAF 入口 (#825) 2023-04-28 07:18:19 +00:00
ssongliu 3b0c844f33 fix: 解决回车导致页面刷新的问题 (#824) 2023-04-28 07:16:22 +00:00
ssongliu 566a4f3568 fix: 面板设置证书改为抽屉实现 (#823) 2023-04-28 06:48:19 +00:00
ssongliu 2c8b19bff2 feat: 两步验证增加手动输入选项 (#822) 2023-04-28 03:42:16 +00:00
ssongliu 292dca6419 fix: 安全入口逻辑调整 (#821) 2023-04-27 14:44:16 +00:00
zhengkunwang223 ebe0f98209 feat: 文件搜索增加搜索子目录功能 (#819) 2023-04-27 21:31:52 +08:00
zhengkunwang223 eba1e5495f style: 运行环境页面增加提示 (#820) 2023-04-27 12:52:20 +00:00
ssongliu 0ebd04f012 feat: 1pctl 支持 mfa、ssl 和安全入口关闭 (#818) 2023-04-27 11:06:15 +00:00
maninhill c5e8a3fa04 Update README.md (#817)
#### What this PR does / why we need it?

#### Summary of your change

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

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-04-27 09:36:15 +00:00
maninhill 42b76fd82d Update README.md 2023-04-27 17:21:58 +08:00
zhengkunwang223 bbf610569d fix: 解决本地应用升级失败的问题 (#813) 2023-04-27 07:30:15 +00:00
ssongliu eeb5fa81a1 feat: 安全登录界面样式优化 (#814) 2023-04-27 07:18:16 +00:00
zhengkunwang223 f65ca6678f style: 网站-密码访问 名称 改为 用户名 (#812) 2023-04-27 06:02:15 +00:00
zhengkunwang223 680f48dcba feat: 文件权限增加修改子文件功能 (#811) 2023-04-27 05:54:15 +00:00
ssongliu 8947e00302 fix: 面板设置样式调整 (#810) 2023-04-27 04:46:14 +00:00
ssongliu b42cf32326 feat: 编排删除增加删除文件勾选 (#808) 2023-04-27 04:44:18 +00:00
ssongliu 5b68332b9a fix: compose 验证、提交按钮合并 (#806) 2023-04-27 02:24:14 +00:00
zhengkunwang223 d7f53862e9 fix: 解决文件编辑之后大小没有改变的 BUG (#804) 2023-04-26 14:30:14 +00:00
zhengkunwang223 7887bf96de feat: 文件管理增加用户/用户组设置 (#803) 2023-04-26 14:18:16 +00:00
zhengkunwang223 db2aa35b2f fix: 解决文件复制到原路径导致文件内容清空的 BUG (#798) 2023-04-26 12:30:14 +00:00
ssongliu 936b0e59ab fix: 修复概览页流量显示问题 (#797) 2023-04-26 10:56:14 +00:00
zhengkunwang223 6e3923d0da feat: 优化文件复制粘贴逻辑 (#796) 2023-04-26 09:58:12 +00:00
ssongliu 4377575206 feat: 存储卷创建支持 nfs (#795) 2023-04-26 09:26:12 +00:00
ssongliu 0c09b12680 fix: 编排创建样式调整、日志改为异步加载 (#794) 2023-04-26 09:00:20 +00:00
zhengkunwang223 8a32d8032f feat: 修改反向代理网站创建逻辑 (#793) 2023-04-26 08:06:12 +00:00
zhengkunwang223 9f12a91f4a feat: 页脚增加论坛、文档入口 (#792) 2023-04-26 07:36:11 +00:00
ssongliu dd0ca4bcaf feat: 创建容器时,端口支持多种形式 (#790) 2023-04-26 07:18:13 +00:00
ssongliu 14cc97eb44 fix: 证书校验规则修改 (#788) 2023-04-26 00:04:10 +00:00
ssongliu 2d31c5b005 feat: 容器增加资源使用、端口显示 (#786) 2023-04-25 14:06:17 +00:00
zhengkunwang223 05e7506f61 feat: 1pctl version 增加 mode 参数 (#784) 2023-04-25 13:28:17 +00:00
zhengkunwang223 37806f1113 feat: 网站增加密码访问功能 (#782) 2023-04-25 10:10:17 +00:00
ssongliu 79622f324b feat: 1pctl user-info 返回增加 protocol 信息 (#781) 2023-04-25 10:02:17 +00:00
ssongliu 34e84081e3 feat: 增加系统 ssl 设置功能 (#780) 2023-04-25 06:34:16 +00:00
ssongliu edf07be281 fix: 解决容器终端断开后未退出进程的问题 (#779) 2023-04-25 06:32:16 +00:00
zhengkunwang223 1208514f37 style: 文件列表样式修改 (#775) 2023-04-24 14:10:14 +00:00
你的明明呐丶andblank bfd857ec4b feat: 增加复制路径功能 (#749)
Co-authored-by: blank <admin@zym88.cn>
2023-04-24 18:21:44 +08:00
zhengkunwang223 9435290bb6 feat: 修改 api 文档 2023-04-24 17:53:05 +08:00
zhengkunwang223 acf64dcf25 feat: 反向代理增加编辑源文功能 2023-04-24 17:53:05 +08:00
zhengkunwang223 2dd88364f8 feat: 反向代理增加编辑功能 2023-04-24 17:53:05 +08:00
zhengkunwang223 d900b52a50 feat: 网站增加创建反向代理功能 2023-04-24 17:53:05 +08:00
ssongliu bd8d96be4d fix: 安全入口长度限制改为 6-10 位 2023-04-24 14:30:54 +08:00
ssongliu 17dd07fe32 feat: 概览页增加未启用安全入口提示信息 2023-04-24 14:30:54 +08:00
ssongliu a06e5f28b3 feat: 增加单独的安全入口接口,防止泄漏 2023-04-24 14:30:54 +08:00
ssongliu d5f400670c feat: 增加系统安全入口功能 2023-04-24 14:30:54 +08:00
177 changed files with 10255 additions and 4313 deletions
+2 -5
View File
@@ -41,12 +41,9 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
- [在线文档](https://1panel.cn/docs/)
- [教学视频](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760)
- [社区论坛](https://bbs.fit2cloud.com/c/1p/7)
## 社区
如果您在使用过程中有任何疑问或对建议,欢迎提交 GitHub Issue 或加入到我们微信交流群进行交流沟通。
**微信交流群**
**加入微信交流群**
<img src="https://1panel.cn/img/wechat-group.jpg" width="156" height="156"/>
+2
View File
@@ -3,6 +3,7 @@
如果您发现安全问题,请直接联系我们:
- wanghe@fit2cloud.com
- zhengkun@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
@@ -13,6 +14,7 @@
All security bugs should be reported to the contact as below:
- wanghe@fit2cloud.com
- zhengkun@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
+5 -68
View File
@@ -1,8 +1,6 @@
package v1
import (
"errors"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
@@ -28,7 +26,7 @@ func (b *BaseApi) Login(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMethod != "jwt" {
if req.AuthMethod != "jwt" && !req.IgnoreCaptcha {
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -102,71 +100,10 @@ func (b *BaseApi) Captcha(c *gin.Context) {
// @Summary Load safety status
// @Description 获取系统安全登录状态
// @Success 200
// @Failure 402
// @Router /auth/status [get]
func (b *BaseApi) GetSafetyStatus(c *gin.Context) {
if err := authService.SafetyStatus(c); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) SafeEntrance(c *gin.Context) {
code, exist := c.Params.Get("code")
if !exist {
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
return
}
ok, err := authService.VerifyCode(code)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
return
}
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
return
}
if err := authService.SafeEntrance(c, code); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Auth
// @Summary Check is First login
// @Description 判断是否为首次登录
// @Success 200
// @Router /auth/status [get]
func (b *BaseApi) CheckIsFirstLogin(c *gin.Context) {
helper.SuccessWithData(c, authService.CheckIsFirst())
}
// @Tags Auth
// @Summary Init user
// @Description 初始化用户
// @Accept json
// @Param request body dto.InitUser true "request"
// @Success 200
// @Router /auth/init [post]
func (b *BaseApi) InitUserInfo(c *gin.Context) {
var req dto.InitUser
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := authService.InitUser(c, req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
// @Router /auth/issafety [get]
func (b *BaseApi) CheckIsSafety(c *gin.Context) {
code := c.DefaultQuery("code", "")
helper.SuccessWithData(c, authService.CheckIsSafety(code))
}
// @Tags Auth
+4 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
)
@@ -77,8 +78,9 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.StartTime = req.StartTime.Add(8 * time.Hour)
req.EndTime = req.EndTime.Add(8 * time.Hour)
loc, _ := time.LoadLocation(common.LoadTimeZone())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
total, list, err := cronjobService.SearchRecords(req)
if err != nil {
+23 -2
View File
@@ -186,7 +186,29 @@ func (b *BaseApi) ChangeFileMode(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
helper.SuccessWithOutData(c)
}
// @Tags File
// @Summary Change file owner
// @Description 修改文件用户/组
// @Accept json
// @Param request body request.FileRoleUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/owner [post]
// @x-panel-log {"bodyKeys":["path","user","group"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改用户/组 [paths] => [user]/[group]","formatEN":"Change owner [paths] => [user]/[group]"}
func (b *BaseApi) ChangeFileOwner(c *gin.Context) {
var req request.FileRoleUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := fileService.ChangeOwner(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags File
@@ -498,7 +520,6 @@ func (b *BaseApi) Size(c *gin.Context) {
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Router /files/loadfile [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"读取文件 [path]","formatEN":"Read file [path]"}
func (b *BaseApi) LoadFromFile(c *gin.Context) {
var req dto.FilePath
if err := c.ShouldBindJSON(&req); err != nil {
+4 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/net"
@@ -24,8 +25,9 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.StartTime = req.StartTime.Add(8 * time.Hour)
req.EndTime = req.EndTime.Add(8 * time.Hour)
loc, _ := time.LoadLocation(common.LoadTimeZone())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
var backdatas []dto.MonitorData
if req.Param == "all" || req.Param == "cpu" || req.Param == "memory" || req.Param == "load" {
+42
View File
@@ -92,6 +92,48 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Update system ssl
// @Description 修改系统 ssl 登录
// @Accept json
// @Param request body dto.SSLUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/ssl/update [post]
// @x-panel-log {"bodyKeys":["ssl"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"}
func (b *BaseApi) UpdateSSL(c *gin.Context) {
var req dto.SSLUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := settingService.UpdateSSL(c, req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Load system cert info
// @Description 获取证书信息
// @Success 200 {object} dto.SettingInfo
// @Security ApiKeyAuth
// @Router /settings/ssl/info [get]
func (b *BaseApi) LoadFromCert(c *gin.Context) {
info, err := settingService.LoadFromCert()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, info)
}
// @Tags System Setting
// @Summary Update system port
// @Description 更新系统端口
+15 -3
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"os/exec"
"strconv"
"time"
@@ -107,14 +108,15 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
return
}
defer wsConn.Close()
commands := fmt.Sprintf("docker exec -it %s redis-cli", redisConf.ContainerName)
commands := "redis-cli"
if len(redisConf.Requirepass) != 0 {
commands = fmt.Sprintf("docker exec -it %s redis-cli -a %s --no-auth-warning", redisConf.ContainerName, redisConf.Requirepass)
commands = fmt.Sprintf("redis-cli -a %s --no-auth-warning", redisConf.Requirepass)
}
slave, err := terminal.NewCommand(commands)
slave, err := terminal.NewCommand(fmt.Sprintf("docker exec -it %s %s", redisConf.ContainerName, commands))
if wshandleError(wsConn, err) {
return
}
defer killBash(redisConf.ContainerName, commands)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
@@ -177,6 +179,7 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
if wshandleError(wsConn, err) {
return
}
defer killBash(containerID, command)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
@@ -216,6 +219,15 @@ func wshandleError(ws *websocket.Conn, err error) bool {
return false
}
func killBash(containerID, comm string) {
sudo := ""
if cmd.HasNoPasswordSudo() {
sudo = "sudo"
}
command := exec.Command("sh", "-c", fmt.Sprintf("%s kill -9 $(docker top %s -eo pid,command | grep '%s' | awk '{print $1}')", sudo, containerID, comm))
_, _ = command.CombinedOutput()
}
var upGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
+110
View File
@@ -648,3 +648,113 @@ func (b *BaseApi) UpdateSiteDirPermission(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get proxy conf
// @Description 获取反向代理配置
// @Accept json
// @Param request body request.WebsiteProxyReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/proxies [post]
func (b *BaseApi) GetProxyConfig(c *gin.Context) {
var req request.WebsiteProxyReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.GetProxies(req.ID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Update proxy conf
// @Description 修改反向代理配置
// @Accept json
// @Param request body request.WebsiteProxyConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/proxies/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 ","formatEN":"Update domain [domain] proxy config"}
func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
var req request.WebsiteProxyConfig
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := websiteService.OperateProxy(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Update proxy file
// @Description 更新反向代理文件
// @Accept json
// @Param request body request.NginxProxyUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/proxy/file [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteID","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"}
func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) {
var req request.NginxProxyUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateProxyFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get AuthBasic conf
// @Description 获取密码访问配置
// @Accept json
// @Param request body request.NginxAuthReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/auths [post]
func (b *BaseApi) GetAuthConfig(c *gin.Context) {
var req request.NginxAuthReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.GetAuthBasics(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Get AuthBasic conf
// @Description 更新密码访问配置
// @Accept json
// @Param request body request.NginxAuthUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/auths/update [post]
func (b *BaseApi) UpdateAuthConfig(c *gin.Context) {
var req request.NginxAuthUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateAuthBasic(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+6 -10
View File
@@ -17,11 +17,12 @@ type MfaCredential struct {
}
type Login struct {
Name string `json:"name"`
Password string `json:"password"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod"`
Name string `json:"name"`
Password string `json:"password"`
IgnoreCaptcha bool `json:"ignoreCaptcha"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod"`
}
type MFALogin struct {
@@ -30,8 +31,3 @@ type MFALogin struct {
Code string `json:"code"`
AuthMethod string `json:"authMethod"`
}
type InitUser struct {
Name string `json:"name" validate:"required"`
Password string `json:"password" validate:"required"`
}
+1 -1
View File
@@ -12,7 +12,7 @@ type PageInfo struct {
type UpdateDescription struct {
ID uint `json:"id" validate:"required"`
Description string `json:"description"`
Description string `json:"description" validate:"max=256"`
}
type OperationWithName struct {
+9 -2
View File
@@ -22,6 +22,10 @@ type ContainerInfo struct {
State string `json:"state"`
RunTime string `json:"runTime"`
CPUPercent float64 `json:"cpuPercent"`
MemoryPercent float64 `json:"memoryPercent"`
Ports []string `json:"ports"`
IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"`
}
@@ -59,8 +63,10 @@ type VolumeHelper struct {
Mode string `json:"mode"`
}
type PortHelper struct {
ContainerPort int `json:"containerPort"`
HostPort int `json:"hostPort"`
HostIP string `json:"hostIP"`
HostPort string `json:"hostPort"`
ContainerPort string `json:"containerPort"`
Protocol string `json:"protocol"`
}
type ContainerLog struct {
@@ -140,6 +146,7 @@ type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop down"`
WithFile bool `json:"withFile"`
}
type ComposeUpdate struct {
Name string `json:"name" validate:"required"`
+14 -5
View File
@@ -28,13 +28,20 @@ type NginxParam struct {
Params []string
}
type NginxAuth struct {
Username string `json:"username"`
Remark string `json:"remark"`
}
type NginxKey string
const (
Index NginxKey = "index"
LimitConn NginxKey = "limit-conn"
SSL NginxKey = "ssl"
HttpPer NginxKey = "http-per"
Index NginxKey = "index"
LimitConn NginxKey = "limit-conn"
SSL NginxKey = "ssl"
CACHE NginxKey = "cache"
HttpPer NginxKey = "http-per"
ProxyCache NginxKey = "proxy-cache"
)
var ScopeKeyMap = map[NginxKey][]string{
@@ -46,5 +53,7 @@ var ScopeKeyMap = map[NginxKey][]string{
var StaticFileKeyMap = map[NginxKey]struct {
}{
SSL: {},
SSL: {},
CACHE: {},
ProxyCache: {},
}
+8
View File
@@ -22,6 +22,7 @@ type FileCreate struct {
IsLink bool `json:"isLink"`
IsSymlink bool `json:"isSymlink"`
LinkPath string `json:"linkPath"`
Sub bool `json:"sub"`
}
type FileDelete struct {
@@ -88,3 +89,10 @@ type DirSizeReq struct {
type FileProcessReq struct {
Key string `json:"key"`
}
type FileRoleUpdate struct {
Path string `json:"path" validate:"required"`
User string `json:"user" validate:"required"`
Group string `json:"group" validate:"required"`
Sub bool `json:"sub" validate:"required"`
}
+18
View File
@@ -30,3 +30,21 @@ type NginxRewriteUpdate struct {
Name string `json:"name" validate:"required"`
Content string `json:"content" validate:"required"`
}
type NginxProxyUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Content string `json:"content" validate:"required"`
Name string `json:"name" validate:"required"`
}
type NginxAuthUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Operate string `json:"operate" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Remark string `json:"remark"`
}
type NginxAuthReq struct {
WebsiteID uint `json:"websiteID" validate:"required"`
}
+21
View File
@@ -156,3 +156,24 @@ type WebsiteUpdateDirPermission struct {
User string `json:"user" validate:"required"`
Group string `json:"group" validate:"required"`
}
type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" validate:"required"`
Cache bool `json:"cache" validate:"required"`
CacheTime int `json:"cacheTime" validate:"required"`
CacheUnit string `json:"cacheUnit" validate:"required"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier" validate:"required"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
}
type WebsiteProxyReq struct {
ID uint `json:"id" validate:"required"`
}
+7
View File
@@ -1,5 +1,7 @@
package response
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type NginxStatus struct {
Active string `json:"active"`
Accepts string `json:"accepts"`
@@ -14,3 +16,8 @@ type NginxParam struct {
Name string `json:"name"`
Params []string `json:"params"`
}
type NginxAuthRes struct {
Enable bool `json:"enable"`
Items []dto.NginxAuth `json:"items"`
}
+22 -3
View File
@@ -16,6 +16,8 @@ type SettingInfo struct {
Language string `json:"language"`
ServerPort string `json:"serverPort"`
SSL string `json:"ssl"`
SSLType string `json:"sslType"`
SecurityEntrance string `json:"securityEntrance"`
ExpirationDays string `json:"expirationDays"`
ExpirationTime string `json:"expirationTime"`
@@ -39,6 +41,23 @@ type SettingUpdate struct {
Value string `json:"value"`
}
type SSLUpdate struct {
SSLType string `json:"sslType"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`
}
type SSLInfo struct {
Domain string `json:"domain"`
Timeout string `json:"timeout"`
RootPath string `json:"rootPath"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`
}
type PasswordUpdate struct {
OldPassword string `json:"oldPassword" validate:"required"`
NewPassword string `json:"newPassword" validate:"required"`
@@ -50,7 +69,7 @@ type PortUpdate struct {
type SnapshotCreate struct {
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO"`
Description string `json:"description"`
Description string `json:"description" validate:"max=256"`
}
type SnapshotRecover struct {
IsNew bool `json:"isNew"`
@@ -60,12 +79,12 @@ type SnapshotRecover struct {
type SnapshotImport struct {
From string `json:"from"`
Names []string `json:"names"`
Description string `json:"description"`
Description string `json:"description" validate:"max=256"`
}
type SnapshotInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Description string `json:"description" validate:"max=256"`
From string `json:"from"`
Status string `json:"status"`
Message string `json:"message"`
-7
View File
@@ -343,13 +343,6 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
go upApp(appInstall)
go updateToolApp(appInstall)
ports := []int{appInstall.HttpPort}
if appInstall.HttpsPort > 0 {
ports = append(ports, appInstall.HttpsPort)
}
go func() {
_ = OperateFirewallPort(nil, ports)
}()
return
}
+4
View File
@@ -226,6 +226,10 @@ func upgradeInstall(installId uint, detailId uint) error {
}
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Key, "versions", detail.Version)
if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(constant.ResourceDir, "localApps", strings.TrimPrefix(install.App.Key, "local"), "versions", detail.Version)
}
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
stdout, err := cmd.CombinedOutput()
if err != nil {
+7 -72
View File
@@ -1,9 +1,7 @@
package service
import (
"fmt"
"strconv"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -19,11 +17,8 @@ import (
type AuthService struct{}
type IAuthService interface {
SafetyStatus(c *gin.Context) error
CheckIsFirst() bool
InitUser(c *gin.Context, req dto.InitUser) error
CheckIsSafety(code string) bool
VerifyCode(code string) (bool, error)
SafeEntrance(c *gin.Context, code string) error
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLoginInfo, error)
@@ -33,22 +28,6 @@ func NewIAuthService() IAuthService {
return &AuthService{}
}
func (u *AuthService) SafeEntrance(c *gin.Context, code string) error {
codeWithMD5 := encrypt.Md5(code)
cookieValue, _ := encrypt.StringEncrypt(codeWithMD5)
c.SetCookie(codeWithMD5, cookieValue, 604800, "", "", false, false)
expiredSetting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
if err != nil {
return err
}
timeout, _ := strconv.Atoi(expiredSetting.Value)
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil {
return err
}
return nil
}
func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) {
nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName"))
if err != nil {
@@ -164,57 +143,13 @@ func (u *AuthService) VerifyCode(code string) (bool, error) {
return setting.Value == code, nil
}
func (u *AuthService) SafetyStatus(c *gin.Context) error {
setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
func (u *AuthService) CheckIsSafety(code string) bool {
status, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
return err
return false
}
codeWithEcrypt, err := c.Cookie(encrypt.Md5(setting.Value))
if err != nil {
return err
if len(status.Value) == 0 {
return true
}
code, err := encrypt.StringDecrypt(codeWithEcrypt)
if err != nil {
return err
}
if code != encrypt.Md5(setting.Value) {
return errors.New("code not match")
}
return nil
}
func (u *AuthService) CheckIsFirst() bool {
user, _ := settingRepo.Get(settingRepo.WithByKey("UserName"))
pass, _ := settingRepo.Get(settingRepo.WithByKey("Password"))
return len(user.Value) == 0 || len(pass.Value) == 0
}
func (u *AuthService) InitUser(c *gin.Context, req dto.InitUser) error {
user, _ := settingRepo.Get(settingRepo.WithByKey("UserName"))
pass, _ := settingRepo.Get(settingRepo.WithByKey("Password"))
if len(user.Value) == 0 || len(pass.Value) == 0 {
newPass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return err
}
if err := settingRepo.Update("UserName", req.Name); err != nil {
return err
}
if err := settingRepo.Update("Password", newPass); err != nil {
return err
}
expiredSetting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
if err != nil {
return err
}
timeout, _ := strconv.Atoi(expiredSetting.Value)
if timeout != 0 {
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil {
return err
}
}
return nil
}
return fmt.Errorf("can't init user because user %s is in system", user.Value)
return status.Value == code
}
+142 -48
View File
@@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -97,27 +98,45 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
records = list[start:end]
}
var wg sync.WaitGroup
wg.Add(len(records))
for _, container := range records {
IsFromCompose := false
if _, ok := container.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := container.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: container.ID,
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
Name: container.Names[0][1:],
ImageId: strings.Split(container.ImageID, ":")[1],
ImageName: container.Image,
State: container.State,
RunTime: container.Status,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
})
go func(item types.Container) {
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
var ports []string
for _, port := range item.Ports {
if port.IP == "::" || port.PublicPort == 0 {
continue
}
ports = append(ports, fmt.Sprintf("%v:%v/%s", port.PublicPort, port.PrivatePort, port.Type))
}
cpu, mem := loadCpuAndMem(client, item.ID)
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
CPUPercent: cpu,
MemoryPercent: mem,
Ports: ports,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
})
wg.Done()
}(container)
}
wg.Wait()
return int64(total), backDatas, nil
}
@@ -147,24 +166,27 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
}
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
if len(req.ExposedPorts) != 0 {
for _, port := range req.ExposedPorts {
if common.ScanPort(port.HostPort) {
return buserr.WithDetail(constant.ErrPortInUsed, port.HostPort, nil)
}
}
portMap, err := checkPortStats(req.ExposedPorts)
if err != nil {
return err
}
client, err := docker.NewDockerClient()
if err != nil {
return err
}
exposeds := make(nat.PortSet)
for port := range portMap {
exposeds[port] = struct{}{}
}
config := &container.Config{
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
ExposedPorts: exposeds,
}
hostConf := &container.HostConfig{
AutoRemove: req.AutoRemove,
@@ -181,11 +203,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
hostConf.Memory = req.Memory
}
if len(req.ExposedPorts) != 0 {
hostConf.PortBindings = make(nat.PortMap)
for _, port := range req.ExposedPorts {
bindItem := nat.PortBinding{HostPort: strconv.Itoa(port.HostPort)}
hostConf.PortBindings[nat.Port(fmt.Sprintf("%d/tcp", port.ContainerPort))] = []nat.PortBinding{bindItem}
}
hostConf.PortBindings = portMap
}
if len(req.Volumes) != 0 {
config.Volumes = make(map[string]struct{})
@@ -270,16 +288,16 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
body, err := io.ReadAll(res.Body)
if err != nil {
res.Body.Close()
return nil, err
}
res.Body.Close()
var stats *types.StatsJSON
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
}
var data dto.ContainterStats
previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage
previousSystem := stats.PreCPUStats.SystemUsage
data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, stats)
data.CPUPercent = calculateCPUPercentUnix(stats)
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
@@ -294,25 +312,33 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
func stringsToMap(list []string) map[string]string {
var lableMap = make(map[string]string)
for _, label := range list {
sps := strings.Split(label, "=")
if len(sps) > 1 {
if strings.Contains(label, "=") {
sps := strings.SplitN(label, "=", 2)
lableMap[sps[0]] = sps[1]
}
}
return lableMap
}
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
var (
cpuPercent = 0.0
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
)
func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
cpuPercent := 0.0
cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
cpuPercent = (cpuDelta / systemDelta) * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) * 100.0
}
return cpuPercent
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
memLimit := float64(memStats.Limit)
if memUsage > 0.0 && memLimit > 0.0 {
memPercent = (memUsage / memLimit) * 100.0
}
return memPercent
}
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
for _, bioEntry := range blkio.IoServiceBytesRecursive {
switch strings.ToLower(bioEntry.Op) {
@@ -363,3 +389,71 @@ func pullImages(ctx context.Context, client *client.Client, image string) error
}
return nil
}
func loadCpuAndMem(client *client.Client, container string) (float64, float64) {
res, err := client.ContainerStats(context.Background(), container, false)
if err != nil {
return 0, 0
}
body, err := io.ReadAll(res.Body)
if err != nil {
res.Body.Close()
return 0, 0
}
res.Body.Close()
var stats *types.StatsJSON
if err := json.Unmarshal(body, &stats); err != nil {
return 0, 0
}
CPUPercent := calculateCPUPercentUnix(stats)
MemPercent := calculateMemPercentUnix(stats.MemoryStats)
return CPUPercent, MemPercent
}
func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
portMap := make(nat.PortMap)
if len(ports) == 0 {
return portMap, nil
}
for _, port := range ports {
if strings.Contains(port.ContainerPort, "-") {
if !strings.Contains(port.HostPort, "-") {
return portMap, buserr.New(constant.ErrPortRules)
}
hostStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0])
hostEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1])
containerStart, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[0])
containerEnd, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[1])
if (hostEnd-hostStart) <= 0 || (containerEnd-containerStart) <= 0 {
return portMap, buserr.New(constant.ErrPortRules)
}
if (containerEnd - containerStart) != (hostEnd - hostStart) {
return portMap, buserr.New(constant.ErrPortRules)
}
for i := 0; i <= hostEnd-hostStart; i++ {
bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP}
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
}
for i := hostStart; i <= hostEnd; i++ {
if common.ScanPort(i) {
return portMap, buserr.WithDetail(constant.ErrPortInUsed, i, nil)
}
}
} else {
portItem := 0
if strings.Contains(port.HostPort, "-") {
portItem, _ = strconv.Atoi(strings.Split(port.HostPort, "-")[0])
} else {
portItem, _ = strconv.Atoi(port.HostPort)
}
if common.ScanPort(portItem) {
return portMap, buserr.WithDetail(constant.ErrPortInUsed, portItem, nil)
}
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem}
}
}
return portMap, nil
}
+9 -13
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path"
@@ -154,9 +155,10 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
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 {
multiWriter := io.MultiWriter(os.Stdout, file)
cmd.Stdout = multiWriter
cmd.Stderr = multiWriter
if err := cmd.Run(); 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!")
@@ -180,7 +182,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
if req.Operation == "down" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
_ = os.RemoveAll(strings.ReplaceAll(req.Path, "/docker-compose.yml", ""))
if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path))
}
}
return nil
@@ -211,15 +215,7 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
}
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" {
if req.From == "template" || 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 {
+1 -1
View File
@@ -29,7 +29,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
if len(cronjob.Script) == 0 {
return
}
stdout, errExec := cmd.ExecWithTimeOut(cronjob.Script, 5*time.Minute)
stdout, errExec := cmd.ExecCronjobWithTimeOut(cronjob.Script, 5*time.Minute)
if errExec != nil {
err = errExec
}
+3 -3
View File
@@ -518,12 +518,12 @@ func excSQL(containerName, password, command string) error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
err := cmd.Run()
stdout, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return buserr.WithDetail(constant.ErrExecTimeOut, containerName, nil)
}
if err != nil {
stdStr := strings.ReplaceAll(err.Error(), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
+11 -1
View File
@@ -39,6 +39,7 @@ type IFileService interface {
ChangeName(req request.FileRename) error
Wget(w request.FileWget) (string, error)
MvFile(m request.FileMove) error
ChangeOwner(req request.FileRoleUpdate) error
}
func NewIFileService() IFileService {
@@ -159,7 +160,16 @@ func (f *FileService) BatchDelete(op request.FileBatchDelete) error {
func (f *FileService) ChangeMode(op request.FileCreate) error {
fo := files.NewFileOp()
return fo.Chmod(op.Path, fs.FileMode(op.Mode))
if op.Sub {
return fo.ChmodR(op.Path, op.Mode)
} else {
return fo.Chmod(op.Path, fs.FileMode(op.Mode))
}
}
func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error {
fo := files.NewFileOp()
return fo.ChownR(req.Path, req.User, req.Group, req.Sub)
}
func (f *FileService) Compress(c request.FileCompress) error {
+4
View File
@@ -161,6 +161,10 @@ func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam
switch scope {
case dto.SSL:
newConfig = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
case dto.CACHE:
newConfig = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
case dto.ProxyCache:
newConfig = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
}
for _, dir := range newConfig.GetDirectives() {
addParam := dto.NginxParam{
+196
View File
@@ -1,8 +1,14 @@
package service
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -12,6 +18,8 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
"github.com/gin-gonic/gin"
)
@@ -22,6 +30,8 @@ type ISettingService interface {
Update(key, value string) error
UpdatePassword(c *gin.Context, old, new string) error
UpdatePort(port uint) error
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
LoadFromCert() (*dto.SSLInfo, error)
HandlePasswordExpired(c *gin.Context, old, new string) error
}
@@ -90,6 +100,135 @@ func (u *SettingService) UpdatePort(port uint) error {
return nil
}
func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
secretDir := global.CONF.System.BaseDir + "/1panel/secret/"
if req.SSL == "disable" {
if err := settingRepo.Update("SSL", "disable"); err != nil {
return err
}
if err := settingRepo.Update("SSLType", "self"); err != nil {
return err
}
_ = os.Remove(secretDir + "server.crt")
_ = os.Remove(secretDir + "server.key")
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
global.LOG.Errorf("restart system failed, err: %v", err)
}
}()
return nil
}
if _, err := os.Stat(secretDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(secretDir, os.ModePerm); err != nil {
return err
}
}
if err := settingRepo.Update("SSLType", req.SSLType); err != nil {
return err
}
if req.SSLType == "self" {
if len(req.Domain) == 0 {
return fmt.Errorf("load domain failed")
}
if err := ssl.GenerateSSL(req.Domain); err != nil {
return err
}
}
if req.SSLType == "select" {
sslInfo, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
if err != nil {
return err
}
req.Cert = sslInfo.Pem
req.Key = sslInfo.PrivateKey
req.SSLType = "import"
if err := settingRepo.Update("SSLID", strconv.Itoa(int(req.SSLID))); err != nil {
return err
}
}
if req.SSLType == "import" {
cert, err := os.OpenFile(secretDir+"server.crt.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer cert.Close()
if _, err := cert.WriteString(req.Cert); err != nil {
return err
}
key, err := os.OpenFile(secretDir+"server.key.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
if _, err := key.WriteString(req.Key); err != nil {
return err
}
defer key.Close()
}
if err := checkCertValid(req.Domain); err != nil {
return err
}
fileOp := files.NewFileOp()
if err := fileOp.Rename(secretDir+"server.crt.tmp", secretDir+"server.crt"); err != nil {
return err
}
if err := fileOp.Rename(secretDir+"server.key.tmp", secretDir+"server.key"); err != nil {
return err
}
if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err
}
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
global.LOG.Errorf("restart system failed, err: %v", err)
}
}()
return nil
}
func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
ssl, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return nil, err
}
if ssl.Value == "disable" {
return &dto.SSLInfo{}, nil
}
sslType, err := settingRepo.Get(settingRepo.WithByKey("SSLType"))
if err != nil {
return nil, err
}
data, err := loadInfoFromCert()
if err != nil {
return nil, err
}
switch sslType.Value {
case "import":
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.crt"); err != nil {
return nil, fmt.Errorf("load server.crt file failed, err: %v", err)
}
certFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
data.Cert = string(certFile)
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.key"); err != nil {
return nil, fmt.Errorf("load server.key file failed, err: %v", err)
}
keyFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
data.Key = string(keyFile)
case "select":
sslID, err := settingRepo.Get(settingRepo.WithByKey("SSLID"))
if err != nil {
return nil, err
}
id, _ := strconv.Atoi(sslID.Value)
data.SSLID = uint(id)
}
return data, nil
}
func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error {
setting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
@@ -128,3 +267,60 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
_ = global.SESSION.Clean()
return nil
}
func loadInfoFromCert() (*dto.SSLInfo, error) {
var info dto.SSLInfo
certFile := global.CONF.System.BaseDir + "/1panel/secret/server.crt"
if _, err := os.Stat(certFile); err != nil {
return &info, err
}
certData, err := os.ReadFile(certFile)
if err != nil {
return &info, err
}
certBlock, _ := pem.Decode(certData)
if certBlock == nil {
return &info, err
}
certObj, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return &info, err
}
var domains []string
if len(certObj.IPAddresses) != 0 {
for _, ip := range certObj.IPAddresses {
domains = append(domains, ip.String())
}
}
if len(certObj.DNSNames) != 0 {
domains = append(domains, certObj.DNSNames...)
}
return &dto.SSLInfo{
Domain: strings.Join(domains, ","),
Timeout: certObj.NotAfter.Format("2006-01-02 15:04:05"),
RootPath: global.CONF.System.BaseDir + "/1panel/secret/server.crt",
}, nil
}
func checkCertValid(domain string) error {
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt.tmp")
if err != nil {
return err
}
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key.tmp")
if err != nil {
return err
}
if _, err = tls.X509KeyPair(certificate, key); err != nil {
return err
}
certBlock, _ := pem.Decode(certificate)
if certBlock == nil {
return err
}
if _, err := x509.ParseCertificate(certBlock.Bytes); err != nil {
return err
}
return nil
}
+443 -10
View File
@@ -10,12 +10,18 @@ import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"os"
"path"
"reflect"
"regexp"
"strconv"
"strings"
"time"
@@ -64,6 +70,11 @@ type IWebsiteService interface {
UpdateRewriteConfig(req request.NginxRewriteUpdate) error
UpdateSiteDir(req request.WebsiteUpdateDir) error
UpdateSitePermission(req request.WebsiteUpdateDirPermission) error
OperateProxy(req request.WebsiteProxyConfig) (err error)
GetProxies(id uint) (res []request.WebsiteProxyConfig, err error)
UpdateProxyFile(req request.NginxProxyUpdate) (err error)
GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error)
UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
}
func NewIWebsiteService() IWebsiteService {
@@ -75,6 +86,13 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
websiteDTOs []response.WebsiteDTO
opts []repo.DBOption
)
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, nil, nil
}
return 0, nil, err
}
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
if req.Name != "" {
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
@@ -105,10 +123,12 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
}
runtimeName = runtime.Name
}
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
websiteDTOs = append(websiteDTOs, response.WebsiteDTO{
Website: web,
AppName: appName,
RuntimeName: runtimeName,
SitePath: sitePath,
})
}
return total, websiteDTOs, nil
@@ -393,10 +413,16 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
}
func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) (model.WebsiteDomain, error) {
var domainModel model.WebsiteDomain
var ports []int
var domains []string
var (
domainModel model.WebsiteDomain
ports []int
domains []string
)
if create.Port != 80 {
if common.ScanPort(create.Port) {
return domainModel, buserr.WithDetail(constant.ErrPortInUsed, create.Port, nil)
}
}
website, err := websiteRepo.GetFirst(commonRepo.WithByID(create.WebsiteID))
if err != nil {
return domainModel, err
@@ -413,6 +439,11 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
Port: create.Port,
WebsiteID: create.WebsiteID,
}
if create.Port != 80 {
go func() {
_ = OperateFirewallPort(nil, []int{create.Port})
}()
}
return domainModel, websiteDomainRepo.Create(context.TODO(), &domainModel)
}
@@ -443,7 +474,11 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
domains = append(domains, webSiteDomain.Domain)
}
if len(ports) > 0 || len(domains) > 0 {
if err := deleteListenAndServerName(website, ports, domains); err != nil {
stringBinds := make([]string, len(ports))
for i := 0; i < len(ports); i++ {
stringBinds[i] = strconv.Itoa(ports[i])
}
if err := deleteListenAndServerName(website, stringBinds, domains); err != nil {
return err
}
}
@@ -590,14 +625,28 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
if !req.Enable {
website.Protocol = constant.ProtocolHTTP
website.WebsiteSSLID = 0
if err := deleteListenAndServerName(website, []int{443}, []string{}); err != nil {
if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil {
return response.WebsiteHTTPS{}, err
}
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
nginxParams = append(nginxParams, dto.NginxParam{
Name: "if",
Params: []string{"($scheme", "=", "http)"},
})
nginxParams = append(nginxParams,
dto.NginxParam{
Name: "if",
Params: []string{"($scheme", "=", "http)"},
},
dto.NginxParam{
Name: "ssl_certificate",
},
dto.NginxParam{
Name: "ssl_certificate_key",
},
dto.NginxParam{
Name: "ssl_protocols",
},
dto.NginxParam{
Name: "ssl_ciphers",
},
)
if err := deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
return response.WebsiteHTTPS{}, err
}
@@ -1105,3 +1154,387 @@ func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermiss
website.Group = req.Group
return websiteRepo.Save(context.Background(), &website)
}
func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
var (
website model.Website
params []response.NginxParam
nginxInstall model.AppInstall
par *parser.Parser
oldContent []byte
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return
}
params, err = getNginxParamsByKeys(constant.NginxScopeHttp, []string{"proxy_cache"}, &website)
if err != nil {
return
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
fileOp := files.NewFileOp()
if len(params) == 0 || len(params[0].Params) == 0 {
commonDir := path.Join(nginxInstall.GetPath(), "www", "common", "proxy")
proxyTempPath := path.Join(commonDir, "proxy_temp_dir")
if !fileOp.Stat(proxyTempPath) {
_ = fileOp.CreateDir(proxyTempPath, 0755)
}
proxyCacheDir := path.Join(commonDir, "proxy_temp_dir")
if !fileOp.Stat(proxyCacheDir) {
_ = fileOp.CreateDir(proxyCacheDir, 0755)
}
nginxParams := getNginxParamsFromStaticFile(dto.CACHE, nil)
if err = updateNginxConfig(constant.NginxScopeHttp, nginxParams, &website); err != nil {
return
}
}
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
if !fileOp.Stat(includeDir) {
_ = fileOp.CreateDir(includeDir, 0755)
}
fileName := fmt.Sprintf("%s.conf", req.Name)
includePath := path.Join(includeDir, fileName)
backName := fmt.Sprintf("%s.bak", req.Name)
backPath := path.Join(includeDir, backName)
if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) {
err = buserr.New(constant.ErrNameIsExist)
return
}
defer func() {
if err != nil {
switch req.Operate {
case "create":
_ = fileOp.DeleteFile(includePath)
case "edit":
_ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), 0755)
}
}
}()
var config *components.Config
switch req.Operate {
case "create":
config = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
case "edit":
par, err = parser.NewParser(includePath)
if err != nil {
return
}
config = par.Parse()
oldContent, err = fileOp.GetContent(includePath)
if err != nil {
return
}
case "delete":
_ = fileOp.DeleteFile(includePath)
_ = fileOp.DeleteFile(backPath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
case "disable":
_ = fileOp.Rename(includePath, backPath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
case "enable":
_ = fileOp.Rename(backPath, includePath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
}
config.FilePath = includePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
if !ok {
err = errors.New("error")
return
}
location.UpdateDirective("proxy_pass", []string{req.ProxyPass})
location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
location.ChangePath(req.Modifier, req.Match)
if req.Cache {
location.AddCache(req.CacheTime, req.CacheUnit)
} else {
location.RemoveCache()
}
if len(req.Replaces) > 0 {
location.AddSubFilter(req.Replaces)
} else {
location.RemoveSubFilter()
}
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
return
}
return
}
func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) {
var (
website model.Website
nginxInstall model.AppInstall
fileList response.FileInfo
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
fileOp := files.NewFileOp()
if !fileOp.Stat(includeDir) {
return
}
fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
if len(fileList.Items) == 0 {
return
}
var (
content []byte
config *components.Config
)
for _, configFile := range fileList.Items {
proxyConfig := request.WebsiteProxyConfig{
ID: website.ID,
}
parts := strings.Split(configFile.Name, ".")
proxyConfig.Name = parts[0]
if parts[1] == "conf" {
proxyConfig.Enable = true
} else {
proxyConfig.Enable = false
}
proxyConfig.FilePath = configFile.Path
content, err = fileOp.GetContent(configFile.Path)
if err != nil {
return
}
proxyConfig.Content = string(content)
config = parser.NewStringParser(string(content)).Parse()
directives := config.GetDirectives()
location, ok := directives[0].(*components.Location)
if !ok {
err = errors.New("error")
return
}
proxyConfig.ProxyPass = location.ProxyPass
proxyConfig.Cache = location.Cache
if location.CacheTime > 0 {
proxyConfig.CacheTime = location.CacheTime
proxyConfig.CacheUnit = location.CacheUint
}
proxyConfig.Match = location.Match
proxyConfig.Modifier = location.Modifier
proxyConfig.ProxyHost = location.Host
proxyConfig.Replaces = location.Replaces
res = append(res, proxyConfig)
}
return
}
func (w WebsiteService) UpdateProxyFile(req request.NginxProxyUpdate) (err error) {
var (
website model.Website
nginxFull dto.NginxFull
oldRewriteContent []byte
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
nginxFull, err = getNginxFull(&website)
if err != nil {
return err
}
includePath := fmt.Sprintf("/www/sites/%s/proxy/%s.conf", website.Alias, req.Name)
absolutePath := path.Join(nginxFull.Install.GetPath(), includePath)
fileOp := files.NewFileOp()
oldRewriteContent, err = fileOp.GetContent(absolutePath)
if err != nil {
return err
}
if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), 0755); err != nil {
return err
}
defer func() {
if err != nil {
_ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), 0755)
}
}()
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
}
func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error) {
var (
website model.Website
nginxInstall model.AppInstall
params []dto.NginxParam
authContent []byte
authArray []string
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias)
absoluteAuthPath := path.Join(nginxInstall.GetPath(), authPath)
fileOp := files.NewFileOp()
if !fileOp.Stat(path.Dir(absoluteAuthPath)) {
_ = fileOp.CreateDir(path.Dir(absoluteAuthPath), 0755)
}
if !fileOp.Stat(absoluteAuthPath) {
_ = fileOp.CreateFile(absoluteAuthPath)
}
defer func() {
if err != nil {
switch req.Operate {
case "create":
}
}
}()
params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}})
params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}})
authContent, err = fileOp.GetContent(absoluteAuthPath)
if err != nil {
return
}
authArray = strings.Split(string(authContent), "\n")
switch req.Operate {
case "disable":
return deleteNginxConfig(constant.NginxScopeServer, params, &website)
case "enable":
return updateNginxConfig(constant.NginxScopeServer, params, &website)
case "create":
for _, line := range authArray {
authParams := strings.Split(line, ":")
username := authParams[0]
if username == req.Username {
err = buserr.New(constant.ErrUsernameIsExist)
return
}
}
var passwdHash []byte
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return
}
line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
if req.Remark != "" {
line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
}
authArray = append(authArray, line)
case "edit":
userExist := false
for index, line := range authArray {
authParams := strings.Split(line, ":")
username := authParams[0]
if username == req.Username {
userExist = true
var passwdHash []byte
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return
}
userPasswd := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
if req.Remark != "" {
userPasswd = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
}
authArray[index] = userPasswd
}
}
if !userExist {
err = buserr.New(constant.ErrUsernameIsNotExist)
return
}
case "delete":
deleteIndex := -1
for index, line := range authArray {
authParams := strings.Split(line, ":")
username := authParams[0]
if username == req.Username {
deleteIndex = index
}
}
if deleteIndex < 0 {
return
}
authArray = append(authArray[:deleteIndex], authArray[deleteIndex+1:]...)
}
var passFile *os.File
passFile, err = os.Create(absoluteAuthPath)
if err != nil {
return
}
defer passFile.Close()
writer := bufio.NewWriter(passFile)
for _, line := range authArray {
_, err = writer.WriteString(line + "\n")
if err != nil {
return
}
}
err = writer.Flush()
if err != nil {
return
}
return
}
func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) {
var (
website model.Website
nginxInstall model.AppInstall
authContent []byte
nginxParams []response.NginxParam
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias)
absoluteAuthPath := path.Join(nginxInstall.GetPath(), authPath)
fileOp := files.NewFileOp()
if !fileOp.Stat(absoluteAuthPath) {
return
}
nginxParams, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"auth_basic"}, &website)
if err != nil {
return
}
res.Enable = len(nginxParams[0].Params) > 0
authContent, err = fileOp.GetContent(absoluteAuthPath)
authArray := strings.Split(string(authContent), "\n")
for _, line := range authArray {
if line == "" {
continue
}
params := strings.Split(line, ":")
auth := dto.NginxAuth{
Username: params[0],
}
if len(params) == 3 {
auth.Remark = params[2]
}
res.Items = append(res.Items, auth)
}
return
}
+70 -11
View File
@@ -2,7 +2,9 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"path"
"strconv"
"strings"
@@ -84,6 +86,40 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
return nil
}
func createProxyFile(website *model.Website, runtime *model.Runtime) error {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
proxyFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.Alias, "proxy")
filePath := path.Join(proxyFolder, "root.conf")
fileOp := files.NewFileOp()
if !fileOp.Stat(proxyFolder) {
if err := fileOp.CreateDir(proxyFolder, 0755); err != nil {
return err
}
}
if !fileOp.Stat(filePath) {
if err := fileOp.CreateFile(filePath); err != nil {
return err
}
}
config := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
config.FilePath = filePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
if !ok {
return errors.New("error")
}
location.ChangePath("^~", "/")
location.UpdateDirective("proxy_pass", []string{website.Proxy})
location.UpdateDirective("proxy_set_header", []string{"Host", "$host"})
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
return nil
}
func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website, runtime *model.Runtime) error {
nginxFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name)
siteFolder := path.Join(nginxFolder, "www", "sites", website.Alias)
@@ -123,6 +159,11 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website,
return err
}
}
if website.Type == constant.Proxy {
if err := createProxyFile(website, runtime); err != nil {
return err
}
}
}
return fileOp.CopyDir(path.Join(nginxFolder, "www", "common", "waf", "rules"), path.Join(siteFolder, "waf"))
}
@@ -167,9 +208,9 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
server.UpdateRootProxy([]string{proxy})
case constant.Static:
server.UpdateRoot(rootIndex)
//server.UpdateRootLocation()
case constant.Proxy:
server.UpdateRootProxy([]string{website.Proxy})
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
server.UpdateDirective("include", []string{nginxInclude})
case constant.Runtime:
if runtime.Resource == constant.ResourceLocal {
switch runtime.Type {
@@ -192,7 +233,6 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
if err := opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
_ = deleteWebsiteFolder(nginxInstall, website)
return err
@@ -261,7 +301,7 @@ func addListenAndServerName(website model.Website, ports []int, domains []string
return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName)
}
func deleteListenAndServerName(website model.Website, ports []int, domains []string) error {
func deleteListenAndServerName(website model.Website, binds []string, domains []string) error {
nginxFull, err := getNginxFull(&website)
if err != nil {
return nil
@@ -269,8 +309,8 @@ func deleteListenAndServerName(website model.Website, ports []int, domains []str
nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
for _, port := range ports {
server.DeleteListen(strconv.Itoa(port))
for _, bind := range binds {
server.DeleteListen(bind)
}
for _, domain := range domains {
server.DeleteServerName(domain)
@@ -332,16 +372,20 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
server.UpdateListen("443", false, "ssl")
server.UpdateListen("[::]:443", false, "ssl")
switch req.HttpConfig {
case constant.HTTPSOnly:
server.RemoveListenByBind("80")
server.RemoveListenByBind("[::]:80")
server.RemoveDirective("if", []string{"($scheme"})
case constant.HTTPToHTTPS:
server.UpdateListen("80", website.DefaultServer)
server.UpdateListen("[::]:80", false)
server.AddHTTP2HTTPS()
case constant.HTTPAlso:
server.UpdateListen("80", website.DefaultServer)
server.UpdateListen("[::]:80", false)
server.RemoveDirective("if", []string{"($scheme"})
}
@@ -453,16 +497,32 @@ func opWebsite(website *model.Website, operate string) error {
}
server := servers[0]
if operate == constant.StopWeb {
if website.Type == constant.Deployment || website.Type == constant.Static || website.Type == constant.Proxy {
server.RemoveDirective("location", []string{"", "/"})
}
if website.Type == constant.Runtime {
proxyInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
server.RemoveDirective("include", []string{proxyInclude})
rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
server.RemoveDirective("include", []string{rewriteInclude})
switch website.Type {
case constant.Deployment:
server.RemoveDirective("location", []string{"/"})
case constant.Runtime:
server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"})
}
server.UpdateRoot("/usr/share/nginx/html/stop")
website.Status = constant.WebStopped
}
if operate == constant.StartWeb {
proxyInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
absoluteIncludeDir := path.Join(nginxInstall.Install.GetPath(), fmt.Sprintf("/www/sites/%s/proxy", website.Alias))
if files.NewFileOp().Stat(absoluteIncludeDir) {
server.UpdateDirective("include", []string{proxyInclude})
}
server.UpdateDirective("include", []string{proxyInclude})
rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
absoluteRewritePath := path.Join(nginxInstall.Install.GetPath(), rewriteInclude)
if files.NewFileOp().Stat(absoluteRewritePath) {
server.UpdateDirective("include", []string{rewriteInclude})
}
switch website.Type {
case constant.Deployment:
server.RemoveDirective("root", nil)
@@ -477,7 +537,6 @@ func opWebsite(website *model.Website, operate string) error {
server.UpdateRootLocation()
case constant.Proxy:
server.RemoveDirective("root", nil)
server.UpdateRootProxy([]string{website.Proxy})
case constant.Runtime:
rootIndex := path.Join("/www/sites", website.Alias, "index")
server.UpdateRoot(rootIndex)
+4
View File
@@ -2,6 +2,7 @@ package configs
type System struct {
Port string `mapstructure:"port"`
SSL string `mapstructure:"ssl"`
DbFile string `mapstructure:"db_file"`
DbPath string `mapstructure:"db_path"`
LogPath string `mapstructure:"log_path"`
@@ -14,5 +15,8 @@ type System struct {
Mode string `mapstructure:"mode"`
RepoUrl string `mapstructure:"repo_url"`
Version string `mapstructure:"version"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Entrance string `mapstructure:"entrance"`
IsDemo bool `mapstructure:"is_demo"`
}
+8 -4
View File
@@ -46,6 +46,7 @@ var (
ErrTypeNotSafety = "ErrNotSafety"
ErrNameIsExist = "ErrNameIsExist"
ErrDemoEnvironment = "ErrDemoEnvironment"
ErrInitUser = "ErrInitUser"
)
// app
@@ -65,10 +66,12 @@ var (
// website
var (
ErrDomainIsExist = "ErrDomainIsExist"
ErrAliasIsExist = "ErrAliasIsExist"
ErrAppDelete = "ErrAppDelete"
ErrGroupIsUsed = "ErrGroupIsUsed"
ErrDomainIsExist = "ErrDomainIsExist"
ErrAliasIsExist = "ErrAliasIsExist"
ErrAppDelete = "ErrAppDelete"
ErrGroupIsUsed = "ErrGroupIsUsed"
ErrUsernameIsExist = "ErrUsernameIsExist"
ErrUsernameIsNotExist = "ErrUsernameIsNotExist"
)
// ssl
@@ -104,6 +107,7 @@ var (
var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules"
)
// runtime
+3 -2
View File
@@ -5,8 +5,9 @@ const (
NginxScopeHttp = "http"
NginxScopeOut = "out"
NginxReload = "reload"
NginxCheck = "check"
NginxReload = "reload"
NginxCheck = "check"
NginxRestart = "restart"
ConfigNew = "add"
ConfigUpdate = "update"
+5 -7
View File
@@ -9,22 +9,20 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/cron/job"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/robfig/cron/v3"
)
func Run() {
nyc, _ := time.LoadLocation("Asia/Shanghai")
nyc, _ := time.LoadLocation(common.LoadTimeZone())
Cron := cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
_, err := Cron.AddJob("@every 5m", job.NewMonitorJob())
if err != nil {
if _, err := Cron.AddJob("@every 5m", job.NewMonitorJob()); err != nil {
global.LOG.Errorf("can not add monitor corn job: %s", err.Error())
}
_, err = Cron.AddJob("@daily", job.NewWebsiteJob())
if err != nil {
if _, err := Cron.AddJob("@daily", job.NewWebsiteJob()); err != nil {
global.LOG.Errorf("can not add website corn job: %s", err.Error())
}
_, err = Cron.AddJob("@daily", job.NewSSLJob())
if err != nil {
if _, err := Cron.AddJob("@daily", job.NewSSLJob()); err != nil {
global.LOG.Errorf("can not add ssl corn job: %s", err.Error())
}
Cron.Start()
+3 -1
View File
@@ -4,6 +4,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"time"
)
@@ -18,13 +19,14 @@ func (ssl *ssl) Run() {
sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
nyc, _ := time.LoadLocation(common.LoadTimeZone())
global.LOG.Info("The scheduled certificate update task is currently in progress ...")
now := time.Now().Add(10 * time.Second)
for _, s := range sslList {
if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" {
continue
}
expireDate := s.ExpireDate.In(time.Now().Location())
expireDate := s.ExpireDate.In(nyc)
sub := expireDate.Sub(now)
if sub.Hours() < 720 {
global.LOG.Errorf("Update the SSL certificate for the [%s] domain", s.PrimaryDomain)
+15 -7
View File
@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"sync"
"time"
)
@@ -18,9 +19,10 @@ func NewWebsiteJob() *website {
}
func (w *website) Run() {
nyc, _ := time.LoadLocation(common.LoadTimeZone())
websites, _ := repo.NewIWebsiteRepo().List()
global.LOG.Info("website cron job start...")
now := time.Now().Add(10 * time.Second)
global.LOG.Info("Website scheduled task in progress ...")
now := time.Now().Add(10 * time.Minute)
if len(websites) > 0 {
neverExpireDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
var wg sync.WaitGroup
@@ -28,27 +30,33 @@ func (w *website) Run() {
if site.Status != constant.WebRunning || neverExpireDate.Equal(site.ExpireDate) {
continue
}
expireDate, _ := time.ParseInLocation(constant.DateTimeLayout, site.ExpireDate.String(), time.Now().Location())
expireDate, err := time.ParseInLocation(constant.DateLayout, site.ExpireDate.Format(constant.DateLayout), nyc)
if err != nil {
global.LOG.Errorf("time parse err %v", err)
continue
}
if expireDate.Before(now) {
wg.Add(1)
go func(ws model.Website) {
stopWebsite(ws.ID, &wg)
stopWebsite(ws.ID, ws.PrimaryDomain, &wg)
}(site)
}
}
wg.Wait()
}
global.LOG.Info("website cron job end...")
global.LOG.Info("Website scheduled task has completed")
}
func stopWebsite(websiteId uint, wg *sync.WaitGroup) {
func stopWebsite(websiteId uint, websiteName string, wg *sync.WaitGroup) {
websiteService := service.NewIWebsiteService()
req := request.WebsiteOp{
ID: websiteId,
Operate: constant.StopWeb,
}
if err := websiteService.OpWebsite(req); err != nil {
global.LOG.Errorf("stop website err: %s", err.Error())
global.LOG.Errorf("Website [%s] seop failed err %v", websiteName, err)
} else {
global.LOG.Infof("Website [%s] stopped successfully", websiteName)
}
wg.Done()
}
+4
View File
@@ -17,6 +17,7 @@ ErrRepoNotValid: "Remote repository verification failed!"
ErrNameIsExist: "Name is already exist"
ErrDemoEnvironment: "Demo server, prohibit this operation!"
ErrCmdTimeout: "Command execution timed out!"
ErrInitUser: "The user initialization has been completed by the system. Please do not repeat the operation!"
#app
ErrPortInUsed: "{{ .detail }} port already in use"
@@ -43,6 +44,8 @@ ErrDomainIsExist: "Domain is already exist"
ErrAliasIsExist: "Alias is already exist"
ErrAppDelete: 'Other Website use this App'
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
ErrUsernameIsExist: 'Username is already exist'
ErrUsernameIsNotExist: 'Username is not exist'
#ssl
ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed"
@@ -61,6 +64,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrPortRules: "Invalid port specification"
#runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
+4
View File
@@ -17,6 +17,7 @@ ErrRepoNotValid: "远程仓库校验失败!"
ErrNameIsExist: "名称已存在"
ErrDemoEnvironment: "演示服务器,禁止此操作!"
ErrCmdTimeout: "命令执行超时!"
ErrInitUser: "系统已完成用户初始化,请勿重复操作!"
#app
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
@@ -43,6 +44,8 @@ ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代号已存在"
ErrAppDelete: '其他网站使用此应用,无法删除'
ErrGroupIsUsed: '分组正在使用中,无法删除'
ErrUsernameIsExist: '用户名已存在'
ErrUsernameIsNotExist: '用户不存在'
#ssl
ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
@@ -61,6 +64,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrPortRules: "无效的端口规格"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
+5
View File
@@ -17,6 +17,11 @@ func Init() {
global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err)
}
global.CONF.System.EncryptKey = enptrySetting.Value
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
}
global.CONF.System.SSL = sslSetting.Value
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
_ = settingRepo.Create("SystemStatus", "Free")
+1
View File
@@ -25,6 +25,7 @@ func Init() {
migrations.UpdateTableApp,
migrations.UpdateTableHost,
migrations.UpdateTableWebsite,
migrations.AddEntranceAndSSL,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+29 -4
View File
@@ -8,6 +8,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
@@ -61,10 +62,13 @@ var AddTableSetting = &gormigrate.Migration{
if err := tx.AutoMigrate(&model.Setting{}); err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "UserName", Value: ""}).Error; err != nil {
encryptKey := common.RandStr(16)
if err := tx.Create(&model.Setting{Key: "UserName", Value: global.CONF.System.Username}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Password", Value: ""}).Error; err != nil {
global.CONF.System.EncryptKey = encryptKey
pass, _ := encrypt.StringEncrypt(global.CONF.System.Password)
if err := tx.Create(&model.Setting{Key: "Password", Value: pass}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Email", Value: ""}).Error; err != nil {
@@ -91,13 +95,13 @@ var AddTableSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "ServerPort", Value: global.CONF.System.Port}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "onepanel"}).Error; err != nil {
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: global.CONF.System.Entrance}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "JWTSigningKey", Value: common.RandStr(16)}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: common.RandStr(16)}).Error; err != nil {
if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: encryptKey}).Error; err != nil {
return err
}
@@ -287,3 +291,24 @@ var UpdateTableWebsite = &gormigrate.Migration{
return nil
},
}
var AddEntranceAndSSL = &gormigrate.Migration{
ID: "20230414-add-entrance-and-ssl",
Migrate: func(tx *gorm.DB) error {
if err := tx.Model(&model.Setting{}).
Where("key = ? AND value = ?", "SecurityEntrance", "onepanel").
Updates(map[string]interface{}{"value": ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SSLType", Value: "self"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SSLID", Value: "0"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SSL", Value: "disable"}).Error; err != nil {
return err
}
return tx.AutoMigrate(&model.Website{})
},
}
-13
View File
@@ -6,7 +6,6 @@ import (
"github.com/gin-contrib/gzip"
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/middleware"
@@ -40,43 +39,31 @@ func setWebStatic(rootRouter *gin.Engine) {
func Routers() *gin.Engine {
Router := gin.Default()
Router.Use(middleware.OperationLog())
// Router.Use(middleware.CSRF())
// Router.Use(middleware.LoadCsrfToken())
if global.CONF.System.IsDemo {
Router.Use(middleware.DemoHandle())
}
Router.Use(gzip.Gzip(gzip.DefaultCompression))
setWebStatic(Router)
Router.Use(i18n.GinI18nLocalize())
Router.GET("/api/v1/info", v1.ApiGroupApp.BaseApi.GetSafetyStatus)
Router.GET("/api/v1/:code", v1.ApiGroupApp.BaseApi.SafeEntrance)
Router.SetFuncMap(template.FuncMap{
"Localize": ginI18n.GetMessage,
})
Router.Use(middleware.JwtAuth())
systemRouter := rou.RouterGroupApp
swaggerRouter := Router.Group("1panel")
docs.SwaggerInfo.BasePath = "/api/v1"
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
PublicGroup := Router.Group("")
{
PublicGroup.GET("/health", func(c *gin.Context) {
c.JSON(200, "ok")
})
}
PrivateGroup := Router.Group("/api/v1")
PrivateGroup.Use(middleware.GlobalLoading())
//PrivateGroup.Use(middleware.SafetyAuth())
{
systemRouter.InitBaseRouter(PrivateGroup)
systemRouter.InitDashboardRouter(PrivateGroup)
+31 -13
View File
@@ -21,6 +21,7 @@ func Init() {
port := "9999"
mode := ""
version := "v1.0.0"
username, password, entrance := "", "", ""
fileOp := files.NewFileOp()
v := viper.NewWithOptions()
v.SetConfigType("yaml")
@@ -40,8 +41,11 @@ func Init() {
}
} else {
baseDir = loadParams("BASE_DIR")
port = loadParams("PANEL_PORT")
version = loadParams("ORIGINAL_INSTALLED_VERSION")
port = loadParams("ORIGINAL_PORT")
version = loadParams("ORIGINAL_VERSION")
username = loadParams("ORIGINAL_USERNAME")
password = loadParams("ORIGINAL_PASSWORD")
entrance = loadParams("ORIGINAL_ENTRANCE")
if strings.HasSuffix(baseDir, "/") {
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
@@ -60,14 +64,25 @@ func Init() {
if err := v.Unmarshal(&serverConfig); err != nil {
panic(err)
}
if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") && serverConfig.System.BaseDir != "" {
baseDir = serverConfig.System.BaseDir
}
if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") && serverConfig.System.Port != "" {
port = serverConfig.System.Port
}
if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") && serverConfig.System.Version != "" {
version = serverConfig.System.Version
if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") {
if serverConfig.System.BaseDir != "" {
baseDir = serverConfig.System.BaseDir
}
if serverConfig.System.Port != "" {
port = serverConfig.System.Port
}
if serverConfig.System.Version != "" {
version = serverConfig.System.Version
}
if serverConfig.System.Username != "" {
version = serverConfig.System.Username
}
if serverConfig.System.Password != "" {
version = serverConfig.System.Password
}
if serverConfig.System.Entrance != "" {
version = serverConfig.System.Entrance
}
}
global.CONF = serverConfig
@@ -81,6 +96,9 @@ func Init() {
global.CONF.System.TmpDir = global.CONF.System.DataDir + "/tmp"
global.CONF.System.Port = port
global.CONF.System.Version = version
global.CONF.System.Username = username
global.CONF.System.Password = password
global.CONF.System.Entrance = entrance
global.Viper = v
}
@@ -89,9 +107,9 @@ func loadParams(param string) string {
if err != nil {
panic(err)
}
baseDir := strings.ReplaceAll(stdout, "\n", "")
if len(baseDir) == 0 {
info := strings.ReplaceAll(stdout, "\n", "")
if len(info) == 0 || info == `""` {
panic(fmt.Sprintf("error `%s` find in /usr/bin/1pctl", param))
}
return baseDir
return info
}
+3 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
)
@@ -29,7 +30,8 @@ func PasswordExpired() gin.HandlerFunc {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
return
}
expiredTime, err := time.Parse("2006-01-02 15:04:05", extime.Value)
loc, _ := time.LoadLocation(common.LoadTimeZone())
expiredTime, err := time.ParseInLocation("2006-01-02 15:04:05", extime.Value, loc)
if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
return
-18
View File
@@ -1,18 +0,0 @@
package middleware
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
func SafetyAuth() gin.HandlerFunc {
return func(c *gin.Context) {
if err := service.NewIAuthService().SafetyStatus(c); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, nil)
return
}
c.Next()
}
}
+1 -2
View File
@@ -14,8 +14,7 @@ func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) {
baseRouter.GET("/captcha", baseApi.Captcha)
baseRouter.POST("/mfalogin", baseApi.MFALogin)
baseRouter.POST("/login", baseApi.Login)
baseRouter.GET("/status", baseApi.CheckIsFirstLogin)
baseRouter.POST("/init", baseApi.InitUserInfo)
baseRouter.GET("/issafety", baseApi.CheckIsSafety)
baseRouter.POST("/logout", baseApi.LogOut)
baseRouter.GET("/demo", baseApi.CheckIsDemo)
}
+1
View File
@@ -21,6 +21,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
fileRouter.POST("/del", baseApi.DeleteFile)
fileRouter.POST("/batch/del", baseApi.BatchDeleteFile)
fileRouter.POST("/mode", baseApi.ChangeFileMode)
fileRouter.POST("/owner", baseApi.ChangeFileOwner)
fileRouter.POST("/compress", baseApi.CompressFile)
fileRouter.POST("/decompress", baseApi.DeCompressFile)
fileRouter.POST("/content", baseApi.GetContent)
+2
View File
@@ -23,6 +23,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
settingRouter.POST("/update", baseApi.UpdateSetting)
settingRouter.POST("/port/update", baseApi.UpdatePort)
settingRouter.POST("/ssl/update", baseApi.UpdateSSL)
settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
settingRouter.POST("/password/update", baseApi.UpdatePassword)
settingRouter.POST("/time/sync", baseApi.SyncTime)
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
+7
View File
@@ -51,5 +51,12 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
groupRouter.POST("/dir/update", baseApi.UpdateSiteDir)
groupRouter.POST("/dir/permission", baseApi.UpdateSiteDirPermission)
groupRouter.POST("/proxies", baseApi.GetProxyConfig)
groupRouter.POST("/proxies/update", baseApi.UpdateProxyConfig)
groupRouter.POST("/proxies/file", baseApi.UpdateProxyConfigFile)
groupRouter.POST("/auths", baseApi.GetAuthConfig)
groupRouter.POST("/auths/update", baseApi.UpdateAuthConfig)
}
}
+38 -15
View File
@@ -1,8 +1,11 @@
package server
import (
"crypto/tls"
"encoding/gob"
"fmt"
"net/http"
"os"
"time"
"github.com/1Panel-dev/1Panel/backend/init/app"
@@ -43,22 +46,42 @@ func Start() {
rootRouter := router.Routers()
address := fmt.Sprintf(":%s", global.CONF.System.Port)
s := initServer(address, rootRouter)
global.LOG.Infof("server run success on %s", global.CONF.System.Port)
if err := s.ListenAndServe(); err != nil {
global.LOG.Error(err)
panic(err)
}
}
type server interface {
ListenAndServe() error
}
func initServer(address string, router *gin.Engine) server {
s := endless.NewServer(address, router)
s := endless.NewServer(address, rootRouter)
s.ReadHeaderTimeout = 20 * time.Second
s.WriteTimeout = 60 * time.Second
s.MaxHeaderBytes = 1 << 20
return s
if global.CONF.System.SSL == "disable" {
global.LOG.Infof("server run success on %s with http", global.CONF.System.Port)
if err := s.ListenAndServe(); err != nil {
global.LOG.Error(err)
panic(err)
}
} else {
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
if err != nil {
panic(err)
}
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
if err != nil {
panic(err)
}
cert, err := tls.X509KeyPair(certificate, key)
if err != nil {
panic(err)
}
s := &http.Server{
Addr: address,
Handler: rootRouter,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
}
global.LOG.Infof("server run success on %s with https", global.CONF.System.Port)
if err := s.ListenAndServeTLS("", ""); err != nil {
global.LOG.Error(err)
panic(err)
}
}
}
+26 -5
View File
@@ -67,6 +67,32 @@ func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
return stdout.String(), nil
}
func ExecCronjobWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.Command("bash", "-c", cmdStr)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return "", buserr.New(constant.ErrCmdTimeout)
}
errMsg := ""
if len(stderr.String()) != 0 {
errMsg = fmt.Sprintf("stderr:\n %s", stderr.String())
}
if len(stdout.String()) != 0 {
if len(errMsg) != 0 {
errMsg = fmt.Sprintf("%s \n\n; stdout:\n %s", errMsg, stdout.String())
} else {
errMsg = fmt.Sprintf("stdout\n: %s", stdout.String())
}
}
return errMsg, err
}
func Execf(cmdStr string, a ...interface{}) (string, error) {
cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdStr, a...))
var stdout, stderr bytes.Buffer
@@ -91,11 +117,6 @@ func Execf(cmdStr string, a ...interface{}) (string, error) {
}
func HasNoPasswordSudo() bool {
cmd := exec.Command("sudo", "-v")
err := cmd.Run()
if err != nil {
return false
}
cmd2 := exec.Command("sudo", "-n", "ls")
err2 := cmd2.Run()
return err2 == nil
+9
View File
@@ -11,6 +11,7 @@ import (
"sort"
"strconv"
"strings"
"time"
)
func CompareVersion(version1 string, version2 string) bool {
@@ -134,3 +135,11 @@ func LoadSizeUnit(value float64) string {
}
return fmt.Sprintf("%v", value)
}
func LoadTimeZone() string {
loc := time.Now().Location()
if _, err := time.LoadLocation(loc.String()); err != nil {
return "Asia/Shanghai"
}
return loc.String()
}
+46 -7
View File
@@ -6,6 +6,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"io"
"io/fs"
"net/http"
@@ -106,7 +107,7 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(string(content))
_, _ = write.WriteString(content)
write.Flush()
return nil
}
@@ -119,6 +120,37 @@ func (f FileOp) Chown(dst string, uid int, gid int) error {
return f.Fs.Chown(dst, uid, gid)
}
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
cmdStr := fmt.Sprintf("chown %s:%s %s", uid, gid, dst)
if sub {
cmdStr = fmt.Sprintf("chown -R %s:%s %s", uid, gid, dst)
}
if cmd.HasNoPasswordSudo() {
cmdStr = fmt.Sprintf("sudo %s", cmdStr)
}
if msg, err := cmd.ExecWithTimeOut(cmdStr, 2*time.Second); err != nil {
if msg != "" {
return errors.New(msg)
}
return err
}
return nil
}
func (f FileOp) ChmodR(dst string, mode int64) error {
cmdStr := fmt.Sprintf("chmod -R %v %s", fmt.Sprintf("%04o", mode), dst)
if cmd.HasNoPasswordSudo() {
cmdStr = fmt.Sprintf("sudo %s", cmdStr)
}
if msg, err := cmd.ExecWithTimeOut(cmdStr, 2*time.Second); err != nil {
if msg != "" {
return errors.New(msg)
}
return err
}
return nil
}
func (f FileOp) Rename(oldName string, newName string) error {
return f.Fs.Rename(oldName, newName)
}
@@ -322,17 +354,21 @@ func (f FileOp) CopyFile(src, dst string) error {
}
defer srcFile.Close()
srcInfo, err := f.Fs.Stat(src)
if err != nil {
return err
}
dstPath := path.Join(dst, srcInfo.Name())
if src == dstPath {
return nil
}
err = f.Fs.MkdirAll(filepath.Dir(dst), 0666)
if err != nil {
return err
}
srcInfo, err := f.Fs.Stat(src)
if err != nil {
return err
}
dstFile, err := f.Fs.OpenFile(path.Join(dst, srcInfo.Name()), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
dstFile, err := f.Fs.OpenFile(dstPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return err
}
@@ -446,6 +482,9 @@ func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error
handler := func(ctx context.Context, archFile archiver.File) error {
info := archFile.FileInfo
if strings.HasPrefix(archFile.NameInArchive, "__MACOSX") {
return nil
}
filePath := filepath.Join(dst, archFile.NameInArchive)
if archFile.FileInfo.IsDir() {
if err := f.Fs.MkdirAll(filePath, info.Mode()); err != nil {
+45 -36
View File
@@ -1,14 +1,17 @@
package files
import (
"bufio"
"fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"io/fs"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/spf13/afero"
@@ -73,6 +76,8 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
Extension: filepath.Ext(info.Name()),
IsHidden: IsHidden(op.Path),
Mode: fmt.Sprintf("%04o", info.Mode().Perm()),
User: GetUsername(info.Sys().(*syscall.Stat_t).Uid),
Group: GetGroup(info.Sys().(*syscall.Stat_t).Gid),
MimeType: GetMimeType(op.Path),
}
if file.IsSymlink {
@@ -93,35 +98,39 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
return file, nil
}
func (f *FileInfo) search(dir, showHidden bool, af afero.Afero, search string, count int) ([]FileSearchInfo, error) {
var files []FileSearchInfo
if err := afero.Walk(af, f.Path, func(path string, info fs.FileInfo, err error) error {
if info != nil {
if dir && !info.IsDir() {
return nil
}
if !showHidden && IsHidden(info.Name()) {
return nil
}
lowerName := strings.ToLower(info.Name())
lowerSearch := strings.ToLower(search)
if strings.Contains(lowerName, lowerSearch) {
files = append(files, FileSearchInfo{
Path: path,
FileInfo: info,
})
if len(files) > count {
return nil
}
}
}
return nil
}); err != nil {
return nil, err
func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, total int, err error) {
cmd := exec.Command("find", f.Path, "-name", search)
output, err := cmd.StdoutPipe()
if err != nil {
return
}
return files, nil
if err = cmd.Start(); err != nil {
return
}
defer cmd.Wait()
defer cmd.Process.Kill()
scanner := bufio.NewScanner(output)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
info, err := os.Stat(line)
if err != nil {
continue
}
total++
if total > count {
continue
}
files = append(files, FileSearchInfo{
Path: line,
FileInfo: info,
})
}
if err = scanner.Err(); err != nil {
return
}
return
}
func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string, page, pageSize int) error {
@@ -129,10 +138,11 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
var (
files []FileSearchInfo
err error
total int
)
if search != "" && containSub {
files, err = f.search(dir, showHidden, *afs, search, page*pageSize)
files, total, err = f.search(search, page*pageSize)
if err != nil {
return err
}
@@ -156,10 +166,8 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
if dir && !df.IsDir() {
continue
}
name := df.Name()
fPath := path.Join(df.Path, df.Name())
if search != "" {
if containSub {
fPath = df.Path
@@ -172,12 +180,10 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
}
}
}
if !showHidden && IsHidden(name) {
continue
}
f.ItemTotal++
isSymlink, isInvalidLink := false, false
if IsSymlink(df.Mode()) {
isSymlink = true
@@ -201,19 +207,22 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
Extension: filepath.Ext(name),
Path: fPath,
Mode: fmt.Sprintf("%04o", df.Mode().Perm()),
MimeType: GetMimeType(fPath),
User: GetUsername(df.Sys().(*syscall.Stat_t).Uid),
Group: GetGroup(df.Sys().(*syscall.Stat_t).Gid),
}
if isSymlink {
file.LinkPath = GetSymlink(fPath)
}
if df.Size() > 0 {
file.MimeType = GetMimeType(fPath)
}
if isInvalidLink {
file.Type = "invalid_link"
}
items = append(items, file)
}
f.ItemTotal = total
start := (page - 1) * pageSize
end := pageSize + start
var result []*FileInfo
+29 -3
View File
@@ -1,10 +1,12 @@
package files
import (
"github.com/gabriel-vasile/mimetype"
"github.com/spf13/afero"
"net/http"
"os"
"os/user"
"path/filepath"
"strconv"
"sync"
)
@@ -13,11 +15,19 @@ func IsSymlink(mode os.FileMode) bool {
}
func GetMimeType(path string) string {
mime, err := mimetype.DetectFile(path)
file, err := os.Open(path)
if err != nil {
return ""
}
return mime.String()
defer file.Close()
buffer := make([]byte, 512)
_, err = file.Read(buffer)
if err != nil {
return ""
}
mimeType := http.DetectContentType(buffer)
return mimeType
}
func GetSymlink(path string) string {
@@ -28,6 +38,22 @@ func GetSymlink(path string) string {
return linkPath
}
func GetUsername(uid uint32) string {
usr, err := user.LookupId(strconv.Itoa(int(uid)))
if err != nil {
return ""
}
return usr.Username
}
func GetGroup(gid uint32) string {
usr, err := user.LookupGroupId(strconv.Itoa(int(gid)))
if err != nil {
return ""
}
return usr.Name
}
func ScanDir(fs afero.Fs, path string, dirMap *sync.Map, wg *sync.WaitGroup) {
afs := &afero.Afero{Fs: fs}
files, _ := afs.ReadDir(path)
-35
View File
@@ -62,41 +62,6 @@ func (b *Block) UpdateDirective(key string, params []string) {
b.Directives = directives
}
//func (b *Block) UpdateDirectiveBySecondKey(name string, key string, directive Directive) {
//
// directives := b.GetDirectives()
//
// index := -1
// for i, dir := range directives {
// if dir.GetName() == name && dir.GetParameters()[0] == key {
// index = i
// break
// }
// }
// if index > -1 {
// directives[index] = &directive
// } else {
// directives = append(directives, &directive)
// }
// b.Directives = directives
//}
//func (b *Block) RemoveDirectives(names []string) {
// nameMaps := make(map[string]struct{}, len(names))
// for _, name := range names {
// nameMaps[name] = struct{}{}
// }
// directives := b.GetDirectives()
// var newDirectives []IDirective
// for _, dir := range directives {
// if _, ok := nameMaps[dir.GetName()]; ok {
// continue
// }
// newDirectives = append(newDirectives, dir)
// }
// b.Directives = newDirectives
//}
func (b *Block) RemoveDirective(key string, params []string) {
directives := b.GetDirectives()
var newDirectives []IDirective
+1
View File
@@ -46,6 +46,7 @@ var repeatKeys = map[string]struct {
"proxy_set_header": {},
"location": {},
"include": {},
"sub_filter": {},
}
func IsRepeatKey(key string) bool {
+229 -21
View File
@@ -1,32 +1,240 @@
package components
import (
"fmt"
"regexp"
"strconv"
"strings"
)
type Location struct {
*Directive
Modifier string
Match string
Modifier string
Match string
Cache bool
ProxyPass string
Host string
CacheTime int
CacheUint string
Comment string
Directives []IDirective
Line int
Parameters []string
Replaces map[string]string
}
func NewLocation(directive *Directive) *Location {
func NewLocation(directive IDirective) *Location {
location := &Location{
Modifier: "",
Match: "",
Directive: directive,
Modifier: "",
Match: "",
}
if directive.GetBlock() != nil {
directive.Comment = directive.GetBlock().GetComment()
}
if len(directive.Parameters) == 0 {
directives := make([]IDirective, 0)
if len(directive.GetParameters()) == 0 {
panic("no enough parameter for location")
}
if len(directive.Parameters) == 1 {
location.Match = directive.Parameters[0]
return location
} else if len(directive.Parameters) == 2 {
location.Match = directive.Parameters[1]
location.Modifier = directive.Parameters[0]
return location
for _, dir := range directive.GetBlock().GetDirectives() {
directives = append(directives, dir)
params := dir.GetParameters()
switch dir.GetName() {
case "proxy_pass":
location.ProxyPass = params[0]
case "proxy_set_header":
if params[0] == "Host" {
location.Host = params[1]
}
case "proxy_cache":
location.Cache = true
case "if":
if params[0] == "(" && params[1] == "$uri" && params[2] == "~*" {
dirs := dir.GetBlock().GetDirectives()
for _, di := range dirs {
if di.GetName() == "expires" {
re := regexp.MustCompile(`^(\d+)(\w+)$`)
matches := re.FindStringSubmatch(di.GetParameters()[0])
if matches == nil {
continue
}
cacheTime, err := strconv.Atoi(matches[1])
if err != nil {
continue
}
unit := matches[2]
location.CacheUint = unit
location.CacheTime = cacheTime
}
}
}
case "sub_filter":
if location.Replaces == nil {
location.Replaces = make(map[string]string, 0)
}
location.Replaces[strings.Trim(params[0], "\"")] = strings.Trim(params[1], "\"")
}
}
return nil
params := directive.GetParameters()
if len(params) == 1 {
location.Match = params[0]
} else if len(params) == 2 {
location.Match = params[1]
location.Modifier = params[0]
}
location.Parameters = directive.GetParameters()
location.Line = directive.GetLine()
location.Comment = directive.GetComment()
location.Directives = directives
return location
}
func (l *Location) GetName() string {
return "location"
}
func (l *Location) GetParameters() []string {
return l.Parameters
}
func (l *Location) GetBlock() IBlock {
return l
}
func (l *Location) GetComment() string {
return l.Comment
}
func (l *Location) GetLine() int {
return l.Line
}
func (l *Location) GetDirectives() []IDirective {
return l.Directives
}
func (l *Location) FindDirectives(directiveName string) []IDirective {
directives := make([]IDirective, 0)
for _, directive := range l.Directives {
if directive.GetName() == directiveName {
directives = append(directives, directive)
}
if directive.GetBlock() != nil {
directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...)
}
}
return directives
}
func (l *Location) UpdateDirective(key string, params []string) {
if key == "" || len(params) == 0 {
return
}
directives := l.Directives
index := -1
for i, dir := range directives {
if dir.GetName() == key {
if IsRepeatKey(key) {
oldParams := dir.GetParameters()
if !(len(oldParams) > 0 && oldParams[0] == params[0]) {
continue
}
}
index = i
break
}
}
newDirective := &Directive{
Name: key,
Parameters: params,
}
if index > -1 {
directives[index] = newDirective
} else {
directives = append(directives, newDirective)
}
l.Directives = directives
}
func (l *Location) RemoveDirective(key string, params []string) {
directives := l.Directives
var newDirectives []IDirective
for _, dir := range directives {
if dir.GetName() == key {
if len(params) > 0 {
oldParams := dir.GetParameters()
if oldParams[0] == params[0] {
continue
}
} else {
continue
}
}
newDirectives = append(newDirectives, dir)
}
l.Directives = newDirectives
}
func (l *Location) ChangePath(Modifier string, Match string) {
if Match != "" && Modifier != "" {
l.Parameters = []string{Modifier, Match}
}
if Match != "" && Modifier == "" {
l.Parameters = []string{Match}
}
l.Modifier = Modifier
l.Match = Match
}
func (l *Location) AddCache(cacheTime int, cacheUint string) {
l.RemoveDirective("add_header", []string{"Cache-Control", "no-cache"})
l.RemoveDirective("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"})
directives := l.GetDirectives()
newDir := &Directive{
Name: "if",
Parameters: []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"},
Block: &Block{},
}
block := &Block{}
block.Directives = append(block.Directives, &Directive{
Name: "expires",
Parameters: []string{strconv.Itoa(cacheTime) + cacheUint},
})
newDir.Block = block
directives = append(directives, newDir)
l.Directives = directives
l.UpdateDirective("proxy_ignore_headers", []string{"Set-Cookie", "Cache-Control", "expires"})
l.UpdateDirective("proxy_cache", []string{"proxy_cache_panel"})
l.UpdateDirective("proxy_cache_key", []string{"$host$uri$is_args$args"})
l.UpdateDirective("proxy_cache_valid", []string{"200", "304", "301", "302", "10m"})
l.Cache = true
l.CacheTime = cacheTime
l.CacheUint = cacheUint
}
func (l *Location) RemoveCache() {
l.RemoveDirective("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"})
l.RemoveDirective("proxy_ignore_headers", []string{"Set-Cookie"})
l.RemoveDirective("proxy_cache", []string{"proxy_cache_panel"})
l.RemoveDirective("proxy_cache_key", []string{"$host$uri$is_args$args"})
l.RemoveDirective("proxy_cache_valid", []string{"200"})
l.UpdateDirective("add_header", []string{"Cache-Control", "no-cache"})
l.CacheTime = 0
l.CacheUint = ""
l.Cache = false
}
func (l *Location) AddSubFilter(subFilters map[string]string) {
l.RemoveDirective("sub_filter", []string{})
l.Replaces = subFilters
for k, v := range subFilters {
l.UpdateDirective("sub_filter", []string{fmt.Sprintf(`"%s"`, k), fmt.Sprintf(`"%s"`, v)})
}
l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.UpdateDirective("sub_filter_once", []string{"off"})
}
func (l *Location) RemoveSubFilter() {
l.RemoveDirective("sub_filter", []string{})
l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.RemoveDirective("sub_filter_once", []string{"off"})
l.Replaces = nil
}
+6 -15
View File
@@ -116,22 +116,14 @@ func (s *Server) RemoveDirective(key string, params []string) {
directives := s.Directives
var newDirectives []IDirective
for _, dir := range directives {
if key == "location" {
if location, ok := dir.(*Location); ok {
if len(params) == 2 && location.Match == params[1] && location.Modifier == params[0] {
continue
}
}
} else {
if dir.GetName() == key {
if len(params) > 0 {
oldParams := dir.GetParameters()
if oldParams[0] == params[0] {
continue
}
} else {
if dir.GetName() == key {
if len(params) > 0 {
oldParams := dir.GetParameters()
if oldParams[0] == params[0] {
continue
}
} else {
continue
}
}
newDirectives = append(newDirectives, dir)
@@ -319,5 +311,4 @@ func (s *Server) AddHTTP2HTTPS() {
})
newDir.Block = block
s.UpdateDirectiveBySecondKey("if", "($scheme", newDir)
}
-1
View File
@@ -74,7 +74,6 @@ func DumpBlock(b components.IBlock, style *Style, startLine int) string {
}
directives := b.GetDirectives()
for i, directive := range directives {
if directive.GetLine() > line {
-1
View File
@@ -84,7 +84,6 @@ func (p *Parser) Parse() *components.Config {
}
func (p *Parser) parseBlock() *components.Block {
context := &components.Block{
Comment: "",
Directives: make([]components.IDirective, 0),
+112
View File
@@ -0,0 +1,112 @@
package ssl
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
"github.com/1Panel-dev/1Panel/backend/global"
)
func GenerateSSL(domain string) error {
rootPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
ipItem := net.ParseIP(domain)
isIP := false
if len(ipItem) != 0 {
isIP = true
}
rootTemplate := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "1Panel Root CA"},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
BasicConstraintsValid: true,
IsCA: true,
KeyUsage: x509.KeyUsageCertSign,
}
if isIP {
rootTemplate.IPAddresses = []net.IP{ipItem}
} else {
rootTemplate.DNSNames = []string{domain}
}
rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey)
rootCertBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: rootCertBytes,
}
interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
interTemplate := x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{CommonName: "1Panel Intermediate CA"},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
BasicConstraintsValid: true,
IsCA: true,
KeyUsage: x509.KeyUsageCertSign,
}
if isIP {
interTemplate.IPAddresses = []net.IP{ipItem}
} else {
interTemplate.DNSNames = []string{domain}
}
interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey)
interCertBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: interCertBytes,
}
clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
clientTemplate := x509.Certificate{
SerialNumber: big.NewInt(3),
Subject: pkix.Name{CommonName: domain},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
if isIP {
clientTemplate.IPAddresses = []net.IP{ipItem}
} else {
clientTemplate.DNSNames = []string{domain}
}
clientCertBytes, _ := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey)
clientCertBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: clientCertBytes,
}
pemBytes := []byte{}
pemBytes = append(pemBytes, pem.EncodeToMemory(clientCertBlock)...)
pemBytes = append(pemBytes, pem.EncodeToMemory(interCertBlock)...)
pemBytes = append(pemBytes, pem.EncodeToMemory(rootCertBlock)...)
certOut, err := os.OpenFile(global.CONF.System.BaseDir+"/1panel/secret/server.crt.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer certOut.Close()
if _, err := certOut.Write(pemBytes); err != nil {
return err
}
keyOut, err := os.OpenFile(global.CONF.System.BaseDir+"/1panel/secret/server.key.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer keyOut.Close()
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)}); err != nil {
return err
}
return nil
}
+7 -24
View File
@@ -21,9 +21,8 @@ type LocalCommand struct {
closeSignal syscall.Signal
closeTimeout time.Duration
cmd *exec.Cmd
pty *os.File
ptyClosed chan struct{}
cmd *exec.Cmd
pty *os.File
}
func NewCommand(commands string) (*LocalCommand, error) {
@@ -33,15 +32,13 @@ func NewCommand(commands string) (*LocalCommand, error) {
if err != nil {
return nil, errors.Wrapf(err, "failed to start command")
}
ptyClosed := make(chan struct{})
lcmd := &LocalCommand{
closeSignal: DefaultCloseSignal,
closeTimeout: DefaultCloseTimeout,
cmd: cmd,
pty: pty,
ptyClosed: ptyClosed,
cmd: cmd,
pty: pty,
}
return lcmd, nil
@@ -57,16 +54,10 @@ func (lcmd *LocalCommand) Write(p []byte) (n int, err error) {
func (lcmd *LocalCommand) Close() error {
if lcmd.cmd != nil && lcmd.cmd.Process != nil {
_ = lcmd.cmd.Process.Signal(lcmd.closeSignal)
}
for {
select {
case <-lcmd.ptyClosed:
return nil
case <-lcmd.closeTimeoutC():
_ = lcmd.cmd.Process.Signal(syscall.SIGKILL)
}
_ = lcmd.cmd.Process.Kill()
}
_ = lcmd.pty.Close()
return nil
}
func (lcmd *LocalCommand) ResizeTerminal(width int, height int) error {
@@ -100,11 +91,3 @@ func (lcmd *LocalCommand) Wait(quitChan chan bool) {
setQuit(quitChan)
}
}
func (lcmd *LocalCommand) closeTimeoutC() <-chan time.Time {
if lcmd.closeTimeout >= 0 {
return time.After(lcmd.closeTimeout)
}
return make(chan time.Time)
}
+49
View File
@@ -0,0 +1,49 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
RootCmd.AddCommand(resetMFACmd)
RootCmd.AddCommand(resetSSLCmd)
RootCmd.AddCommand(resetEntranceCmd)
}
var resetMFACmd = &cobra.Command{
Use: "reset-mfa",
Short: "关闭 1Panel 两步验证",
RunE: func(cmd *cobra.Command, args []string) error {
db, err := loadDBConn()
if err != nil {
return err
}
return setSettingByKey(db, "MFAStatus", "disable")
},
}
var resetSSLCmd = &cobra.Command{
Use: "reset-https",
Short: "取消 1Panel https 方式登录",
RunE: func(cmd *cobra.Command, args []string) error {
db, err := loadDBConn()
if err != nil {
return err
}
return setSettingByKey(db, "SSL", "disable")
},
}
var resetEntranceCmd = &cobra.Command{
Use: "reset-entrance",
Short: "取消 1Panel 安全入口",
RunE: func(cmd *cobra.Command, args []string) error {
db, err := loadDBConn()
if err != nil {
return err
}
return setSettingByKey(db, "SecurityEntrance", "")
},
}
+28
View File
@@ -1,10 +1,14 @@
package cmd
import (
"fmt"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/server"
cmdUtils "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/spf13/cobra"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
@@ -28,8 +32,32 @@ type setting struct {
About string `json:"about" gorm:"type:longText"`
}
func loadDBConn() (*gorm.DB, error) {
stdout, err := cmdUtils.Exec("grep '^BASE_DIR=' /usr/bin/1pctl | cut -d'=' -f2")
if err != nil {
return nil, fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err)
}
baseDir := strings.ReplaceAll(stdout, "\n", "")
if len(baseDir) == 0 {
return nil, fmt.Errorf("error `BASE_DIR` find in /usr/bin/1pctl \n")
}
if strings.HasSuffix(baseDir, "/") {
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
}
db, err := gorm.Open(sqlite.Open(baseDir+"/1panel/db/1Panel.db"), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("init my db conn failed, err: %v \n", err)
}
return db, nil
}
func getSettingByKey(db *gorm.DB, key string) string {
var setting setting
_ = db.Where("key = ?", key).First(&setting).Error
return setting.Value
}
func setSettingByKey(db *gorm.DB, key, value string) error {
return db.Model(&setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error
}
+6 -18
View File
@@ -2,14 +2,10 @@ package cmd
import (
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/backend/global"
cmdUtils "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/spf13/cobra"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func init() {
@@ -20,25 +16,15 @@ var userinfoCmd = &cobra.Command{
Use: "user-info",
Short: "获取用户信息",
RunE: func(cmd *cobra.Command, args []string) error {
stdout, err := cmdUtils.Exec("grep '^BASE_DIR=' /usr/bin/1pctl | cut -d'=' -f2")
db, err := loadDBConn()
if err != nil {
panic(err)
}
baseDir := strings.ReplaceAll(stdout, "\n", "")
if len(baseDir) == 0 {
fmt.Printf("error `BASE_DIR` find in /usr/bin/1pctl \n")
}
if strings.HasSuffix(baseDir, "/") {
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
}
db, err := gorm.Open(sqlite.Open(baseDir+"/1panel/db/1Panel.db"), &gorm.Config{})
if err != nil {
fmt.Printf("init my db conn failed, err: %v \n", err)
return fmt.Errorf("init my db conn failed, err: %v \n", err)
}
user := getSettingByKey(db, "UserName")
password := getSettingByKey(db, "Password")
port := getSettingByKey(db, "ServerPort")
ssl := getSettingByKey(db, "SSL")
entrance := getSettingByKey(db, "SecurityEntrance")
enptrySetting := getSettingByKey(db, "EncryptKey")
p := ""
@@ -52,6 +38,8 @@ var userinfoCmd = &cobra.Command{
fmt.Printf("username: %s\n", user)
fmt.Printf("password: %s\n", p)
fmt.Printf("port: %s\n", port)
fmt.Printf("ssl: %s\n", ssl)
fmt.Printf("entrance: %s\n", entrance)
return nil
},
}
+12 -18
View File
@@ -2,12 +2,12 @@ package cmd
import (
"fmt"
"strings"
cmdUtils "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/configs"
"github.com/1Panel-dev/1Panel/cmd/server/conf"
"gopkg.in/yaml.v3"
"github.com/spf13/cobra"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func init() {
@@ -18,27 +18,21 @@ var versionCmd = &cobra.Command{
Use: "version",
Short: "获取系统版本信息",
RunE: func(cmd *cobra.Command, args []string) error {
stdout, err := cmdUtils.Exec("grep '^BASE_DIR=' /usr/bin/1pctl | cut -d'=' -f2")
db, err := loadDBConn()
if err != nil {
panic(err)
}
baseDir := strings.ReplaceAll(stdout, "\n", "")
if len(baseDir) == 0 {
fmt.Printf("error `BASE_DIR` find in /usr/bin/1pctl \n")
}
if strings.HasSuffix(baseDir, "/") {
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
}
db, err := gorm.Open(sqlite.Open(baseDir+"/1panel/db/1Panel.db"), &gorm.Config{})
if err != nil {
fmt.Printf("init my db conn failed, err: %v \n", err)
return err
}
version := getSettingByKey(db, "SystemVersion")
appStoreVersion := getSettingByKey(db, "AppStoreVersion")
fmt.Printf("1panel version: %s\n", version)
fmt.Printf("appstore version: %s\n", appStoreVersion)
config := configs.ServerConfig{}
if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil {
return fmt.Errorf("unmarshal conf.App.Yaml failed, errL %v", err)
} else {
fmt.Printf("mode: %s\n", config.System.Mode)
}
return nil
},
}
+608 -53
View File
@@ -799,27 +799,13 @@ var doc = `{
}
}
},
"/auth/init": {
"post": {
"description": "初始化用户",
"consumes": [
"application/json"
],
"/auth/issafety": {
"get": {
"description": "获取系统安全登录状态",
"tags": [
"Auth"
],
"summary": "Init user",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.InitUser"
}
}
],
"summary": "Load safety status",
"responses": {
"200": {
"description": ""
@@ -908,20 +894,6 @@ var doc = `{
}
}
},
"/auth/status": {
"get": {
"description": "判断是否为首次登录",
"tags": [
"Auth"
],
"summary": "Check is First login",
"responses": {
"200": {
"description": ""
}
}
}
},
"/containers": {
"post": {
"security": [
@@ -4522,6 +4494,50 @@ var doc = `{
}
}
},
"/files/owner": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改文件用户/组",
"consumes": [
"application/json"
],
"tags": [
"File"
],
"summary": "Change file owner",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.FileRoleUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"path",
"user",
"group"
],
"formatEN": "Change owner [paths] =\u003e [user]/[group]",
"formatZH": "修改用户/组 [paths] =\u003e [user]/[group]",
"paramKeys": []
}
}
},
"/files/rename": {
"post": {
"security": [
@@ -7384,6 +7400,70 @@ var doc = `{
}
}
},
"/settings/ssl/info": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取证书信息",
"tags": [
"System Setting"
],
"summary": "Load system cert info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SettingInfo"
}
}
}
}
},
"/settings/ssl/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统 ssl 登录",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Update system ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SSLUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"ssl"
],
"formatEN": "update system ssl =\u003e [ssl]",
"formatZH": "修改系统 ssl =\u003e [ssl]",
"paramKeys": []
}
}
},
"/settings/time/sync": {
"post": {
"security": [
@@ -7857,6 +7937,72 @@ var doc = `{
}
}
},
"/websites/auths": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取密码访问配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get AuthBasic conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxAuthReq"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/auths/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新密码访问配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get AuthBasic conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxAuthUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/check": {
"post": {
"security": [
@@ -8083,6 +8229,57 @@ var doc = `{
}
}
},
"/websites/dir/permission": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站目录权限",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update Site Dir permission",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteUpdateDirPermission"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update domain [domain] dir permission",
"formatZH": "更新网站 [domain] 目录权限",
"paramKeys": []
}
}
},
"/websites/dir/update": {
"post": {
"security": [
@@ -8111,7 +8308,7 @@ var doc = `{
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
},
"x-panel-log": {
@@ -8773,6 +8970,141 @@ var doc = `{
}
}
},
"/websites/proxies": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取反向代理配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get proxy conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteProxyReq"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/proxies/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改反向代理配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update proxy conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteProxyConfig"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update domain [domain] proxy config",
"formatZH": "修改网站 [domain] 反向代理配置 ",
"paramKeys": []
}
}
},
"/websites/proxy/file": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新反向代理文件",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update proxy file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxProxyUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "websiteID",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"websiteID"
],
"formatEN": "Nginx conf proxy file update [domain]",
"formatZH": "更新反向代理文件 [domain]",
"paramKeys": []
}
}
},
"/websites/rewrite": {
"post": {
"security": [
@@ -9659,6 +9991,9 @@ var doc = `{
},
"path": {
"type": "string"
},
"withFile": {
"type": "boolean"
}
}
},
@@ -10696,21 +11031,6 @@ var doc = `{
}
}
},
"dto.InitUser": {
"type": "object",
"required": [
"name",
"password"
],
"properties": {
"name": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"dto.InspectReq": {
"type": "object",
"properties": {
@@ -10734,6 +11054,9 @@ var doc = `{
"captchaID": {
"type": "string"
},
"ignoreCaptcha": {
"type": "boolean"
},
"name": {
"type": "string"
},
@@ -11122,10 +11445,16 @@ var doc = `{
"type": "object",
"properties": {
"containerPort": {
"type": "integer"
"type": "string"
},
"hostIP": {
"type": "string"
},
"hostPort": {
"type": "integer"
"type": "string"
},
"protocol": {
"type": "string"
}
}
},
@@ -11351,6 +11680,36 @@ var doc = `{
}
}
},
"dto.SSLUpdate": {
"type": "object",
"required": [
"ssl"
],
"properties": {
"cert": {
"type": "string"
},
"domain": {
"type": "string"
},
"key": {
"type": "string"
},
"ssl": {
"type": "string",
"enum": [
"enable",
"disable"
]
},
"sslID": {
"type": "integer"
},
"sslType": {
"type": "string"
}
}
},
"dto.SearchForTree": {
"type": "object",
"properties": {
@@ -11530,6 +11889,12 @@ var doc = `{
"sessionTimeout": {
"type": "string"
},
"ssl": {
"type": "string"
},
"sslType": {
"type": "string"
},
"systemVersion": {
"type": "string"
},
@@ -11565,7 +11930,8 @@ var doc = `{
],
"properties": {
"description": {
"type": "string"
"type": "string",
"maxLength": 256
},
"from": {
"type": "string",
@@ -11584,7 +11950,8 @@ var doc = `{
"type": "object",
"properties": {
"description": {
"type": "string"
"type": "string",
"maxLength": 256
},
"from": {
"type": "string"
@@ -11621,7 +11988,8 @@ var doc = `{
],
"properties": {
"description": {
"type": "string"
"type": "string",
"maxLength": 256
},
"id": {
"type": "integer"
@@ -11942,6 +12310,9 @@ var doc = `{
"expireDate": {
"type": "string"
},
"group": {
"type": "string"
},
"httpConfig": {
"type": "string"
},
@@ -11981,6 +12352,9 @@ var doc = `{
"updatedAt": {
"type": "string"
},
"user": {
"type": "string"
},
"webSiteGroupId": {
"type": "integer"
},
@@ -12315,6 +12689,9 @@ var doc = `{
},
"path": {
"type": "string"
},
"sub": {
"type": "boolean"
}
}
},
@@ -12469,6 +12846,29 @@ var doc = `{
}
}
},
"request.FileRoleUpdate": {
"type": "object",
"required": [
"group",
"path",
"sub",
"user"
],
"properties": {
"group": {
"type": "string"
},
"path": {
"type": "string"
},
"sub": {
"type": "boolean"
},
"user": {
"type": "string"
}
}
},
"request.FileWget": {
"type": "object",
"required": [
@@ -12503,6 +12903,43 @@ var doc = `{
}
}
},
"request.NginxAuthReq": {
"type": "object",
"required": [
"websiteID"
],
"properties": {
"websiteID": {
"type": "integer"
}
}
},
"request.NginxAuthUpdate": {
"type": "object",
"required": [
"operate",
"password",
"username",
"websiteID"
],
"properties": {
"operate": {
"type": "string"
},
"password": {
"type": "string"
},
"remark": {
"type": "string"
},
"username": {
"type": "string"
},
"websiteID": {
"type": "integer"
}
}
},
"request.NginxConfigFileUpdate": {
"type": "object",
"required": [
@@ -12540,6 +12977,25 @@ var doc = `{
}
}
},
"request.NginxProxyUpdate": {
"type": "object",
"required": [
"content",
"name",
"websiteID"
],
"properties": {
"content": {
"type": "string"
},
"name": {
"type": "string"
},
"websiteID": {
"type": "integer"
}
}
},
"request.NginxRewriteReq": {
"type": "object",
"required": [
@@ -13044,6 +13500,80 @@ var doc = `{
}
}
},
"request.WebsiteProxyConfig": {
"type": "object",
"required": [
"cache",
"cacheTime",
"cacheUnit",
"enable",
"id",
"match",
"modifier",
"name",
"operate",
"proxyHost",
"proxyPass"
],
"properties": {
"cache": {
"type": "boolean"
},
"cacheTime": {
"type": "integer"
},
"cacheUnit": {
"type": "string"
},
"content": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"filePath": {
"type": "string"
},
"id": {
"type": "integer"
},
"match": {
"type": "string"
},
"modifier": {
"type": "string"
},
"name": {
"type": "string"
},
"operate": {
"type": "string"
},
"proxyHost": {
"type": "string"
},
"proxyPass": {
"type": "string"
},
"replaces": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"request.WebsiteProxyReq": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer"
}
}
},
"request.WebsiteResourceReq": {
"type": "object",
"required": [
@@ -13186,6 +13716,25 @@ var doc = `{
}
}
},
"request.WebsiteUpdateDirPermission": {
"type": "object",
"required": [
"group",
"id",
"user"
],
"properties": {
"group": {
"type": "string"
},
"id": {
"type": "integer"
},
"user": {
"type": "string"
}
}
},
"request.WebsiteWafReq": {
"type": "object",
"required": [
@@ -13572,6 +14121,9 @@ var doc = `{
"expireDate": {
"type": "string"
},
"group": {
"type": "string"
},
"httpConfig": {
"type": "string"
},
@@ -13617,6 +14169,9 @@ var doc = `{
"updatedAt": {
"type": "string"
},
"user": {
"type": "string"
},
"webSiteGroupId": {
"type": "integer"
},
+608 -53
View File
@@ -785,27 +785,13 @@
}
}
},
"/auth/init": {
"post": {
"description": "初始化用户",
"consumes": [
"application/json"
],
"/auth/issafety": {
"get": {
"description": "获取系统安全登录状态",
"tags": [
"Auth"
],
"summary": "Init user",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.InitUser"
}
}
],
"summary": "Load safety status",
"responses": {
"200": {
"description": ""
@@ -894,20 +880,6 @@
}
}
},
"/auth/status": {
"get": {
"description": "判断是否为首次登录",
"tags": [
"Auth"
],
"summary": "Check is First login",
"responses": {
"200": {
"description": ""
}
}
}
},
"/containers": {
"post": {
"security": [
@@ -4508,6 +4480,50 @@
}
}
},
"/files/owner": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改文件用户/组",
"consumes": [
"application/json"
],
"tags": [
"File"
],
"summary": "Change file owner",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.FileRoleUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"path",
"user",
"group"
],
"formatEN": "Change owner [paths] =\u003e [user]/[group]",
"formatZH": "修改用户/组 [paths] =\u003e [user]/[group]",
"paramKeys": []
}
}
},
"/files/rename": {
"post": {
"security": [
@@ -7370,6 +7386,70 @@
}
}
},
"/settings/ssl/info": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取证书信息",
"tags": [
"System Setting"
],
"summary": "Load system cert info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SettingInfo"
}
}
}
}
},
"/settings/ssl/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统 ssl 登录",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Update system ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SSLUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"ssl"
],
"formatEN": "update system ssl =\u003e [ssl]",
"formatZH": "修改系统 ssl =\u003e [ssl]",
"paramKeys": []
}
}
},
"/settings/time/sync": {
"post": {
"security": [
@@ -7843,6 +7923,72 @@
}
}
},
"/websites/auths": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取密码访问配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get AuthBasic conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxAuthReq"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/auths/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新密码访问配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get AuthBasic conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxAuthUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/check": {
"post": {
"security": [
@@ -8069,6 +8215,57 @@
}
}
},
"/websites/dir/permission": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站目录权限",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update Site Dir permission",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteUpdateDirPermission"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update domain [domain] dir permission",
"formatZH": "更新网站 [domain] 目录权限",
"paramKeys": []
}
}
},
"/websites/dir/update": {
"post": {
"security": [
@@ -8097,7 +8294,7 @@
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
},
"x-panel-log": {
@@ -8759,6 +8956,141 @@
}
}
},
"/websites/proxies": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取反向代理配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get proxy conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteProxyReq"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/proxies/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改反向代理配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update proxy conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteProxyConfig"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update domain [domain] proxy config",
"formatZH": "修改网站 [domain] 反向代理配置 ",
"paramKeys": []
}
}
},
"/websites/proxy/file": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新反向代理文件",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update proxy file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxProxyUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "websiteID",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"websiteID"
],
"formatEN": "Nginx conf proxy file update [domain]",
"formatZH": "更新反向代理文件 [domain]",
"paramKeys": []
}
}
},
"/websites/rewrite": {
"post": {
"security": [
@@ -9645,6 +9977,9 @@
},
"path": {
"type": "string"
},
"withFile": {
"type": "boolean"
}
}
},
@@ -10682,21 +11017,6 @@
}
}
},
"dto.InitUser": {
"type": "object",
"required": [
"name",
"password"
],
"properties": {
"name": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"dto.InspectReq": {
"type": "object",
"properties": {
@@ -10720,6 +11040,9 @@
"captchaID": {
"type": "string"
},
"ignoreCaptcha": {
"type": "boolean"
},
"name": {
"type": "string"
},
@@ -11108,10 +11431,16 @@
"type": "object",
"properties": {
"containerPort": {
"type": "integer"
"type": "string"
},
"hostIP": {
"type": "string"
},
"hostPort": {
"type": "integer"
"type": "string"
},
"protocol": {
"type": "string"
}
}
},
@@ -11337,6 +11666,36 @@
}
}
},
"dto.SSLUpdate": {
"type": "object",
"required": [
"ssl"
],
"properties": {
"cert": {
"type": "string"
},
"domain": {
"type": "string"
},
"key": {
"type": "string"
},
"ssl": {
"type": "string",
"enum": [
"enable",
"disable"
]
},
"sslID": {
"type": "integer"
},
"sslType": {
"type": "string"
}
}
},
"dto.SearchForTree": {
"type": "object",
"properties": {
@@ -11516,6 +11875,12 @@
"sessionTimeout": {
"type": "string"
},
"ssl": {
"type": "string"
},
"sslType": {
"type": "string"
},
"systemVersion": {
"type": "string"
},
@@ -11551,7 +11916,8 @@
],
"properties": {
"description": {
"type": "string"
"type": "string",
"maxLength": 256
},
"from": {
"type": "string",
@@ -11570,7 +11936,8 @@
"type": "object",
"properties": {
"description": {
"type": "string"
"type": "string",
"maxLength": 256
},
"from": {
"type": "string"
@@ -11607,7 +11974,8 @@
],
"properties": {
"description": {
"type": "string"
"type": "string",
"maxLength": 256
},
"id": {
"type": "integer"
@@ -11928,6 +12296,9 @@
"expireDate": {
"type": "string"
},
"group": {
"type": "string"
},
"httpConfig": {
"type": "string"
},
@@ -11967,6 +12338,9 @@
"updatedAt": {
"type": "string"
},
"user": {
"type": "string"
},
"webSiteGroupId": {
"type": "integer"
},
@@ -12301,6 +12675,9 @@
},
"path": {
"type": "string"
},
"sub": {
"type": "boolean"
}
}
},
@@ -12455,6 +12832,29 @@
}
}
},
"request.FileRoleUpdate": {
"type": "object",
"required": [
"group",
"path",
"sub",
"user"
],
"properties": {
"group": {
"type": "string"
},
"path": {
"type": "string"
},
"sub": {
"type": "boolean"
},
"user": {
"type": "string"
}
}
},
"request.FileWget": {
"type": "object",
"required": [
@@ -12489,6 +12889,43 @@
}
}
},
"request.NginxAuthReq": {
"type": "object",
"required": [
"websiteID"
],
"properties": {
"websiteID": {
"type": "integer"
}
}
},
"request.NginxAuthUpdate": {
"type": "object",
"required": [
"operate",
"password",
"username",
"websiteID"
],
"properties": {
"operate": {
"type": "string"
},
"password": {
"type": "string"
},
"remark": {
"type": "string"
},
"username": {
"type": "string"
},
"websiteID": {
"type": "integer"
}
}
},
"request.NginxConfigFileUpdate": {
"type": "object",
"required": [
@@ -12526,6 +12963,25 @@
}
}
},
"request.NginxProxyUpdate": {
"type": "object",
"required": [
"content",
"name",
"websiteID"
],
"properties": {
"content": {
"type": "string"
},
"name": {
"type": "string"
},
"websiteID": {
"type": "integer"
}
}
},
"request.NginxRewriteReq": {
"type": "object",
"required": [
@@ -13030,6 +13486,80 @@
}
}
},
"request.WebsiteProxyConfig": {
"type": "object",
"required": [
"cache",
"cacheTime",
"cacheUnit",
"enable",
"id",
"match",
"modifier",
"name",
"operate",
"proxyHost",
"proxyPass"
],
"properties": {
"cache": {
"type": "boolean"
},
"cacheTime": {
"type": "integer"
},
"cacheUnit": {
"type": "string"
},
"content": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"filePath": {
"type": "string"
},
"id": {
"type": "integer"
},
"match": {
"type": "string"
},
"modifier": {
"type": "string"
},
"name": {
"type": "string"
},
"operate": {
"type": "string"
},
"proxyHost": {
"type": "string"
},
"proxyPass": {
"type": "string"
},
"replaces": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"request.WebsiteProxyReq": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer"
}
}
},
"request.WebsiteResourceReq": {
"type": "object",
"required": [
@@ -13172,6 +13702,25 @@
}
}
},
"request.WebsiteUpdateDirPermission": {
"type": "object",
"required": [
"group",
"id",
"user"
],
"properties": {
"group": {
"type": "string"
},
"id": {
"type": "integer"
},
"user": {
"type": "string"
}
}
},
"request.WebsiteWafReq": {
"type": "object",
"required": [
@@ -13558,6 +14107,9 @@
"expireDate": {
"type": "string"
},
"group": {
"type": "string"
},
"httpConfig": {
"type": "string"
},
@@ -13603,6 +14155,9 @@
"updatedAt": {
"type": "string"
},
"user": {
"type": "string"
},
"webSiteGroupId": {
"type": "integer"
},
+399 -35
View File
@@ -202,6 +202,8 @@ definitions:
type: string
path:
type: string
withFile:
type: boolean
required:
- name
- operation
@@ -902,16 +904,6 @@ definitions:
- sourceID
- targetName
type: object
dto.InitUser:
properties:
name:
type: string
password:
type: string
required:
- name
- password
type: object
dto.InspectReq:
properties:
id:
@@ -927,6 +919,8 @@ definitions:
type: string
captchaID:
type: string
ignoreCaptcha:
type: boolean
name:
type: string
password:
@@ -1185,9 +1179,13 @@ definitions:
dto.PortHelper:
properties:
containerPort:
type: integer
type: string
hostIP:
type: string
hostPort:
type: integer
type: string
protocol:
type: string
type: object
dto.PortRuleOperate:
properties:
@@ -1338,6 +1336,26 @@ definitions:
used_memory_rss:
type: string
type: object
dto.SSLUpdate:
properties:
cert:
type: string
domain:
type: string
key:
type: string
ssl:
enum:
- enable
- disable
type: string
sslID:
type: integer
sslType:
type: string
required:
- ssl
type: object
dto.SearchForTree:
properties:
info:
@@ -1457,6 +1475,10 @@ definitions:
type: string
sessionTimeout:
type: string
ssl:
type: string
sslType:
type: string
systemVersion:
type: string
theme:
@@ -1478,6 +1500,7 @@ definitions:
dto.SnapshotCreate:
properties:
description:
maxLength: 256
type: string
from:
enum:
@@ -1494,6 +1517,7 @@ definitions:
dto.SnapshotImport:
properties:
description:
maxLength: 256
type: string
from:
type: string
@@ -1516,6 +1540,7 @@ definitions:
dto.UpdateDescription:
properties:
description:
maxLength: 256
type: string
id:
type: integer
@@ -1728,6 +1753,8 @@ definitions:
type: boolean
expireDate:
type: string
group:
type: string
httpConfig:
type: string
id:
@@ -1754,6 +1781,8 @@ definitions:
type: string
updatedAt:
type: string
user:
type: string
webSiteGroupId:
type: integer
webSiteSSL:
@@ -1974,6 +2003,8 @@ definitions:
type: integer
path:
type: string
sub:
type: boolean
required:
- mode
- path
@@ -2079,6 +2110,22 @@ definitions:
- newName
- oldName
type: object
request.FileRoleUpdate:
properties:
group:
type: string
path:
type: string
sub:
type: boolean
user:
type: string
required:
- group
- path
- sub
- user
type: object
request.FileWget:
properties:
name:
@@ -2102,6 +2149,31 @@ definitions:
additionalProperties: true
type: object
type: object
request.NginxAuthReq:
properties:
websiteID:
type: integer
required:
- websiteID
type: object
request.NginxAuthUpdate:
properties:
operate:
type: string
password:
type: string
remark:
type: string
username:
type: string
websiteID:
type: integer
required:
- operate
- password
- username
- websiteID
type: object
request.NginxConfigFileUpdate:
properties:
backup:
@@ -2127,6 +2199,19 @@ definitions:
required:
- websiteId
type: object
request.NginxProxyUpdate:
properties:
content:
type: string
name:
type: string
websiteID:
type: integer
required:
- content
- name
- websiteID
type: object
request.NginxRewriteReq:
properties:
name:
@@ -2465,6 +2550,58 @@ definitions:
- id
- type
type: object
request.WebsiteProxyConfig:
properties:
cache:
type: boolean
cacheTime:
type: integer
cacheUnit:
type: string
content:
type: string
enable:
type: boolean
filePath:
type: string
id:
type: integer
match:
type: string
modifier:
type: string
name:
type: string
operate:
type: string
proxyHost:
type: string
proxyPass:
type: string
replaces:
additionalProperties:
type: string
type: object
required:
- cache
- cacheTime
- cacheUnit
- enable
- id
- match
- modifier
- name
- operate
- proxyHost
- proxyPass
type: object
request.WebsiteProxyReq:
properties:
id:
type: integer
required:
- id
type: object
request.WebsiteResourceReq:
properties:
id:
@@ -2560,6 +2697,19 @@ definitions:
- id
- siteDir
type: object
request.WebsiteUpdateDirPermission:
properties:
group:
type: string
id:
type: integer
user:
type: string
required:
- group
- id
- user
type: object
request.WebsiteWafReq:
properties:
key:
@@ -2816,6 +2966,8 @@ definitions:
type: string
expireDate:
type: string
group:
type: string
httpConfig:
type: string
id:
@@ -2846,6 +2998,8 @@ definitions:
type: string
updatedAt:
type: string
user:
type: string
webSiteGroupId:
type: integer
webSiteSSL:
@@ -3392,22 +3546,13 @@ paths:
summary: Check System isDemo
tags:
- Auth
/auth/init:
post:
consumes:
- application/json
description: 初始化用户
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.InitUser'
/auth/issafety:
get:
description: 获取系统安全登录状态
responses:
"200":
description: ""
summary: Init user
summary: Load safety status
tags:
- Auth
/auth/login:
@@ -3461,15 +3606,6 @@ paths:
summary: User login with mfa
tags:
- Auth
/auth/status:
get:
description: 判断是否为首次登录
responses:
"200":
description: ""
summary: Check is First login
tags:
- Auth
/containers:
post:
consumes:
@@ -5761,6 +5897,35 @@ paths:
formatEN: Move [oldPaths] => [newPath]
formatZH: 移动文件 [oldPaths] => [newPath]
paramKeys: []
/files/owner:
post:
consumes:
- application/json
description: 修改文件用户/组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.FileRoleUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Change file owner
tags:
- File
x-panel-log:
BeforeFuntions: []
bodyKeys:
- path
- user
- group
formatEN: Change owner [paths] => [user]/[group]
formatZH: 修改用户/组 [paths] => [user]/[group]
paramKeys: []
/files/rename:
post:
consumes:
@@ -7578,6 +7743,46 @@ paths:
summary: Page system snapshot
tags:
- System Setting
/settings/ssl/info:
get:
description: 获取证书信息
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SettingInfo'
security:
- ApiKeyAuth: []
summary: Load system cert info
tags:
- System Setting
/settings/ssl/update:
post:
consumes:
- application/json
description: 修改系统 ssl 登录
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SSLUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update system ssl
tags:
- System Setting
x-panel-log:
BeforeFuntions: []
bodyKeys:
- ssl
formatEN: update system ssl => [ssl]
formatZH: 修改系统 ssl => [ssl]
paramKeys: []
/settings/time/sync:
post:
description: 系统时间同步
@@ -7879,6 +8084,46 @@ paths:
summary: Page website acme accounts
tags:
- Website Acme
/websites/auths:
post:
consumes:
- application/json
description: 获取密码访问配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.NginxAuthReq'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Get AuthBasic conf
tags:
- Website
/websites/auths/update:
post:
consumes:
- application/json
description: 更新密码访问配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.NginxAuthUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Get AuthBasic conf
tags:
- Website
/websites/check:
post:
consumes:
@@ -8023,6 +8268,39 @@ paths:
formatEN: Delete website [domain]
formatZH: 删除网站 [domain]
paramKeys: []
/websites/dir/permission:
post:
consumes:
- application/json
description: 更新网站目录权限
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteUpdateDirPermission'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update Site Dir permission
tags:
- Website
x-panel-log:
BeforeFuntions:
- db: websites
input_colume: id
input_value: id
isList: false
output_colume: primary_domain
output_value: domain
bodyKeys:
- id
formatEN: Update domain [domain] dir permission
formatZH: 更新网站 [domain] 目录权限
paramKeys: []
/websites/dir/update:
post:
consumes:
@@ -8037,7 +8315,7 @@ paths:
$ref: '#/definitions/request.WebsiteUpdateDir'
responses:
"200":
description: OK
description: ""
security:
- ApiKeyAuth: []
summary: Update Site Dir
@@ -8464,6 +8742,92 @@ paths:
formatEN: Nginx conf update [domain]
formatZH: php 配置修改 [domain]
paramKeys: []
/websites/proxies:
post:
consumes:
- application/json
description: 获取反向代理配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteProxyReq'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Get proxy conf
tags:
- Website
/websites/proxies/update:
post:
consumes:
- application/json
description: 修改反向代理配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteProxyConfig'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update proxy conf
tags:
- Website
x-panel-log:
BeforeFuntions:
- db: websites
input_colume: id
input_value: id
isList: false
output_colume: primary_domain
output_value: domain
bodyKeys:
- id
formatEN: Update domain [domain] proxy config
formatZH: '修改网站 [domain] 反向代理配置 '
paramKeys: []
/websites/proxy/file:
post:
consumes:
- application/json
description: 更新反向代理文件
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.NginxProxyUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update proxy file
tags:
- Website
x-panel-log:
BeforeFuntions:
- db: websites
input_colume: id
input_value: websiteID
isList: false
output_colume: primary_domain
output_value: domain
bodyKeys:
- websiteID
formatEN: Nginx conf proxy file update [domain]
formatZH: 更新反向代理文件 [domain]
paramKeys: []
/websites/rewrite:
post:
consumes:
+12
View File
@@ -0,0 +1,12 @@
proxy_temp_path /www/common/proxy/proxy_temp_dir;
proxy_cache_path /www/common/proxy/proxy_cache_dir levels=1:2 keys_zone=proxy_cache_panel:20m inactive=1d max_size=5g;
client_body_buffer_size 512k;
proxy_connect_timeout 60;
proxy_read_timeout 60;
proxy_send_timeout 60;
proxy_buffer_size 32k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;
proxy_cache proxy_cache_panel;
+9
View File
@@ -19,3 +19,12 @@ var IndexPHP []byte
//go:embed rewrite/*
var Rewrites embed.FS
//go:embed cache.conf
var Cache []byte
//go:embed proxy.conf
var Proxy []byte
//go:embed proxy_cache.conf
var ProxyCache []byte
+12
View File
@@ -0,0 +1,12 @@
location ^~ /test {
proxy_pass http://1panel.cloud/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
}
+4
View File
@@ -0,0 +1,4 @@
proxy_ignore_headers Set-Cookie Cache-Control expires;
proxy_cache cache_one;
proxy_cache_key $host$uri$is_args$args;
proxy_cache_valid 200 304 301 302 10m;
+10
View File
@@ -0,0 +1,10 @@
set $static_fileg 0;
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
{
set $static_fileg 1;
expires 1m;
}
if ( $static_fileg = 0 )
{
add_header Cache-Control no-cache;
}
@@ -1,5 +1,7 @@
server {
listen 80;
listen [::]:80;
server_name ko.wp-1.com;
index index.php index.html index.htm default.php default.htm default.html;
Binary file not shown.
+1
View File
@@ -90,6 +90,7 @@ declare module 'vue' {
TableSetting: typeof import('./src/components/table-setting/index.vue')['default']
Terminal: typeof import('./src/components/terminal/index.vue')['default']
Tooltip: typeof import('./src/components/tooltip/index.vue')['default']
Unsafe: typeof import('./src/components/error-message/unsafe.vue')['default']
Upload: typeof import('./src/components/upload/index.vue')['default']
VCharts: typeof import('./src/components/v-charts/index.vue')['default']
}
+2567 -2090
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -29,8 +29,8 @@
"axios": "^0.27.2",
"echarts": "^5.3.0",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.2.32",
"fit2cloud-ui-plus": "^1.0.4",
"element-plus": "^2.3.4",
"fit2cloud-ui-plus": "^1.0.7",
"js-base64": "^3.7.2",
"js-md5": "^0.7.3",
"md-editor-v3": "^2.7.2",
+3 -1
View File
@@ -1,6 +1,8 @@
import i18n from '@/lang';
import router from '@/routers';
import { MsgError } from '@/utils/message';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
export const checkStatus = (status: number, msg: string): void => {
switch (status) {
@@ -11,7 +13,7 @@ export const checkStatus = (status: number, msg: string): void => {
MsgError(msg ? msg : i18n.global.t('commons.res.notFound'));
break;
case 403:
router.replace({ path: '/login' });
router.replace({ name: 'entrance', params: { code: globalStore.entrance } });
MsgError(msg ? msg : i18n.global.t('commons.res.forbidden'));
break;
case 500:
+3 -9
View File
@@ -26,7 +26,6 @@ class RequestHttp {
...config.headers,
};
}
return {
...config,
};
@@ -43,17 +42,12 @@ class RequestHttp {
globalStore.setCsrfToken(response.headers['x-csrf-token']);
}
if (data.code == ResultEnum.OVERDUE || data.code == ResultEnum.FORBIDDEN) {
router.replace({
path: '/login',
router.push({
name: 'entrance',
params: { code: globalStore.entrance },
});
return Promise.reject(data);
}
if (data.code == ResultEnum.UNSAFETY) {
router.replace({
path: '/login',
});
return data;
}
if (data.code == ResultEnum.EXPIRED) {
router.push({ name: 'Expired' });
return data;
+1 -4
View File
@@ -2,6 +2,7 @@ export namespace Login {
export interface ReqLoginForm {
name: string;
password: string;
ignoreCaptcha: boolean;
captcha: string;
captchaID: string;
authMethod: string;
@@ -17,10 +18,6 @@ export namespace Login {
token: string;
mfaStatus: string;
}
export interface InitUser {
name: string;
password: string;
}
export interface ResCaptcha {
imagePath: string;
captchaID: string;
+9 -3
View File
@@ -27,8 +27,11 @@ export namespace Container {
restartPolicy: string;
}
export interface Port {
containerPort: number;
hostPort: number;
host: string;
hostIP: string;
containerPort: string;
hostPort: string;
protocol: string;
}
export interface Volume {
sourceDir: string;
@@ -42,7 +45,9 @@ export namespace Container {
createTime: string;
state: string;
runTime: string;
cpuPercent: number;
memoryPercent: number;
ports: Array<string>;
isFromApp: boolean;
isFromCompose: boolean;
}
@@ -203,6 +208,7 @@ export namespace Container {
name: string;
operation: string;
path: string;
withFile: boolean;
}
export interface ComposeUpdate {
name: string;
+8
View File
@@ -54,6 +54,7 @@ export namespace File {
isLink?: boolean;
isSymlink?: boolean;
linkPath?: boolean;
sub?: boolean;
}
export interface FileDelete {
@@ -90,6 +91,13 @@ export namespace File {
newName: string;
}
export interface FileOwner {
path: string;
user: string;
group: string;
sub: boolean;
}
export interface FileWget {
path: string;
name: string;
+18
View File
@@ -15,6 +15,8 @@ export namespace Setting {
language: string;
serverPort: number;
ssl: string;
sslType: string;
securityEntrance: string;
expirationDays: number;
expirationTime: string;
@@ -34,6 +36,22 @@ export namespace Setting {
key: string;
value: string;
}
export interface SSLUpdate {
ssl: string;
domain: string;
sslType: string;
cert: string;
key: string;
sslID: number;
}
export interface SSLInfo {
domain: string;
timeout: string;
rootPath: string;
cert: string;
key: string;
sslID: number;
}
export interface PasswordUpdate {
oldPassword: string;
newPassword: string;
+53
View File
@@ -307,4 +307,57 @@ export namespace Website {
user: string;
group: string;
}
export interface ProxyReq {
id: number;
}
export interface ProxyConfig {
id: number;
operate: string;
enable: boolean;
cache: boolean;
cacheTime: number;
cacheUnit: string;
name: string;
modifier: string;
match: string;
proxyPass: string;
proxyHost: string;
filePath?: string;
replaces?: ProxReplace;
content?: string;
}
export interface ProxReplace {
[key: string]: string;
}
export interface ProxyFileUpdate {
websiteID: number;
name: string;
content: string;
}
export interface AuthReq {
websiteID: number;
}
export interface NginxAuth {
username: string;
remark: string;
}
export interface AuthConfig {
enable: boolean;
items: NginxAuth[];
}
export interface NginxAuthConfig {
websiteID: number;
operate: string;
username: string;
password: string;
remark: string;
}
}
+1 -1
View File
@@ -63,7 +63,7 @@ export const GetAppInstalled = (search: App.AppInstalledSearch) => {
};
export const InstalledOp = (op: App.AppInstalledOp) => {
return http.post<any>('apps/installed/op', op);
return http.post<any>('apps/installed/op', op, 40000);
};
export const SyncInstalledApp = () => {
+2 -6
View File
@@ -25,12 +25,8 @@ export const loginStatus = () => {
return http.get<any>('/info');
};
export const checkIsFirst = () => {
return http.get<boolean>('/auth/status');
};
export const initUser = (params: Login.InitUser) => {
return http.post(`/auth/init`, params);
export const checkIsSafety = (code: string) => {
return http.get<boolean>(`/auth/issafety?code=${code}`);
};
export const checkIsDemo = () => {
+1 -1
View File
@@ -3,7 +3,7 @@ import { ResPage, SearchWithPage } from '../interface';
import { Container } from '../interface/container';
export const searchContainer = (params: Container.ContainerSearch) => {
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params, 400000);
};
export const createContainer = (params: Container.ContainerCreate) => {
return http.post(`/containers`, params, 3000000);
+4
View File
@@ -67,6 +67,10 @@ export const RenameRile = (params: File.FileRename) => {
return http.post<File.File>('files/rename', params);
};
export const ChangeOwner = (params: File.FileOwner) => {
return http.post<File.File>('files/owner', params);
};
export const WgetFile = (params: File.FileWget) => {
return http.post<File.FileWgetRes>('files/wget', params);
};
+7
View File
@@ -24,6 +24,13 @@ export const updatePort = (param: Setting.PortUpdate) => {
return http.post(`/settings/port/update`, param);
};
export const updateSSL = (param: Setting.SSLUpdate) => {
return http.post(`/settings/ssl/update`, param);
};
export const loadSSLInfo = () => {
return http.get<Setting.SSLInfo>(`/settings/ssl/info`);
};
export const handleExpired = (param: Setting.PasswordUpdate) => {
return http.post(`/settings/expired/handle`, param);
};
+21 -1
View File
@@ -1,6 +1,6 @@
import http from '@/api';
import { ReqPage, ResPage } from '../interface';
import { Website } from '../interface/Website';
import { Website } from '../interface/website';
import { File } from '../interface/file';
export const SearchWebsites = (req: Website.WebSiteSearch) => {
@@ -186,3 +186,23 @@ export const UpdateWebsiteDir = (req: Website.DirUpdate) => {
export const UpdateWebsiteDirPermission = (req: Website.DirPermissionUpdate) => {
return http.post<any>(`/websites/dir/permission`, req);
};
export const GetProxyConfig = (req: Website.ProxyReq) => {
return http.post<Website.ProxyConfig[]>(`/websites/proxies`, req);
};
export const OperateProxyConfig = (req: Website.ProxyReq) => {
return http.post<any>(`/websites/proxies/update`, req);
};
export const UpdateProxyConfigFile = (req: Website.ProxyFileUpdate) => {
return http.post<any>(`/websites/proxies/file`, req);
};
export const GetAuthConfig = (req: Website.AuthReq) => {
return http.post<Website.AuthConfig>(`/websites/auths`, req);
};
export const OperateAuthConfig = (req: Website.NginxAuthConfig) => {
return http.post<any>(`/websites/auths/update`, req);
};
+20
View File
@@ -0,0 +1,20 @@
<svg width="250" height="250" viewBox="0 0 250 250" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_17_668)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M137.1 72.5238C137.1 49.0386 118.062 30 94.5763 30H79.7382C56.2529 30 37.2144 49.0386 37.2144 72.5238V127.805H137.1V72.5238ZM79.7381 46.2857H94.5762L95.1468 46.2918C109.374 46.5953 120.814 58.2236 120.814 72.5238L120.812 111.517H53.5V72.5238L53.5061 71.9532C53.8096 57.7258 65.4379 46.2857 79.7381 46.2857Z" fill="#B5BEC8"/>
<rect x="6" y="98.6719" width="162.314" height="120.695" rx="3" fill="#D6DAE1"/>
<path d="M89.3762 198.557C108.914 198.557 124.752 182.719 124.752 163.181C124.752 143.643 108.914 127.805 89.3762 127.805C69.8385 127.805 54 143.643 54 163.181C54 182.719 69.8385 198.557 89.3762 198.557Z" fill="white"/>
<path d="M94.3999 170.798C95.4168 170.279 96.3558 169.6 97.1781 168.778C99.241 166.715 100.4 163.917 100.4 161C100.4 158.083 99.241 155.285 97.1781 153.222C95.1152 151.159 92.3173 150 89.3999 150C86.4825 150 83.6846 151.159 81.6217 153.222C79.5588 155.285 78.3999 158.083 78.3999 161C78.3999 163.917 79.5588 166.715 81.6217 168.778C82.444 169.6 83.3831 170.279 84.3999 170.798V178C84.3999 179.105 85.2953 180 86.3999 180H92.3999C93.5045 180 94.3999 179.105 94.3999 178V170.798Z" fill="#B5BEC8"/>
<rect x="187.043" y="77.8623" width="12.4857" height="60.3476" rx="3.45" fill="#B5BEC8"/>
<path d="M193.286 87C182.243 87 173.291 95.9522 173.291 106.995C173.291 118.038 182.243 126.99 193.286 126.99C204.329 126.99 213.281 118.038 213.281 106.995C213.281 95.9522 204.329 87 193.286 87Z" fill="#B5BEC8"/>
<path d="M193.286 97.8574C198.332 97.8574 202.424 101.949 202.424 106.996C202.424 112.042 198.332 116.134 193.286 116.134C188.239 116.134 184.147 112.042 184.147 106.996C184.147 101.949 188.239 97.8574 193.286 97.8574Z" fill="white"/>
<path d="M137.552 134.048H215.724C221.72 134.048 226.581 138.909 226.581 144.905V208.51C226.581 214.506 221.72 219.367 215.724 219.367H126.695V219.367V144.905C126.695 138.909 131.556 134.048 137.552 134.048Z" fill="#D6DAE1"/>
<rect x="143.343" y="134.048" width="99.8857" height="85.319" rx="3" fill="#E7EBED"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M167.41 179.828C167.41 165.537 178.995 153.952 193.286 153.952C207.577 153.952 219.162 165.537 219.162 179.828C219.162 194.119 207.577 205.705 193.286 205.705C178.995 205.705 167.41 194.119 167.41 179.828ZM217.353 179.828C217.353 166.537 206.578 155.762 193.286 155.762C179.994 155.762 169.219 166.537 169.219 179.828C169.219 193.12 179.994 203.895 193.286 203.895C206.578 203.895 217.353 193.12 217.353 179.828Z" fill="#050B3C"/>
<path d="M177.234 162.5L213.003 193.794L211.812 195.156L176.042 163.862L177.234 162.5Z" fill="#2E344D"/>
</g>
<defs>
<clipPath id="clip0_17_668">
<rect width="250" height="250" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -7,7 +7,7 @@
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2023 FIT2CLOUD 飞致云</a>
</div>
<div class="footer-right">
<SystemUpgrade />
<SystemUpgrade :footer="true" />
</div>
</div>
</template>
@@ -80,7 +80,7 @@ const logout = () => {
})
.then(() => {
systemLogOut();
router.push({ name: 'login' });
router.push({ name: 'entrance', params: { code: globalStore.entrance } });
globalStore.setLogStatus(false);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
@@ -22,7 +22,7 @@
</template>
<div class="complex-table__body">
<fu-table v-bind="$attrs" @selection-change="handleSelectionChange">
<fu-table v-bind="$attrs" ref="tableRef" @selection-change="handleSelectionChange">
<slot></slot>
</fu-table>
</div>
@@ -52,6 +52,7 @@ defineProps({
});
const emit = defineEmits(['search', 'update:selects']);
const condition = ref({});
const tableRef = ref();
function search(conditions: any, e: any) {
if (conditions) {
condition.value = conditions;
@@ -62,6 +63,13 @@ function search(conditions: any, e: any) {
function handleSelectionChange(row: any) {
emit('update:selects', row);
}
function clearSelects() {
tableRef.value.refElTable.clearSelection();
}
defineExpose({
clearSelects,
});
</script>
<style lang="scss">
@@ -0,0 +1,66 @@
<template>
<div class="not-container">
<img src="@/assets/images/unsafe.svg" class="not-img" alt="404" />
<div class="not-detail">
<h2>{{ $t('commons.login.notSafe') }}</h2>
<h4>{{ $t('commons.login.safeEntrance1') }}</h4>
<div>
<h4>{{ $t('commons.login.safeEntrance2') }}</h4>
<div style="cursor: pointer; float: left">
<el-icon color="#409EFC" style="margin-left: 5px; margin-top: 33px" :size="18" @click="onCopy()">
<DocumentCopy />
</el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="404">
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
const onCopy = () => {
let input = document.createElement('input');
input.value = '1pctl user-info';
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
};
</script>
<style scoped lang="scss">
.not-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.not-img {
margin-top: 300px;
}
.not-detail {
margin-top: 300px;
display: flex;
flex-direction: column;
h2,
h4 {
padding: 0;
margin: 0;
}
h2 {
font-size: 60px;
color: #434e59;
}
h4 {
margin: 30px 0 20px;
float: left;
font-size: 19px;
font-weight: normal;
color: #848587;
}
}
}
</style>
@@ -1,9 +1,24 @@
<template>
<div>
<span class="version">{{ version }}</span>
<el-badge is-dot class="item" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
<el-button style="margin-top: -8px" type="primary" link @click="onLoadUpgradeInfo">
{{ $t('setting.upgradeCheck') }}
<span v-if="props.footer">
<el-button type="primary" link @click="toForum">
<span>{{ $t('setting.forum') }}</span>
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="toDoc">
<span>{{ $t('setting.doc2') }}</span>
</el-button>
<el-divider direction="vertical" />
</span>
<span class="version">{{ $t('setting.currentVersion') + version }}</span>
<el-badge
is-dot
class="item"
v-if="version !== 'Waiting' && globalStore.hasNewVersion"
style="margin-top: -6px"
>
<el-button type="primary" link @click="onLoadUpgradeInfo">
<span style="font-size: 14px">({{ $t('setting.hasNewVersion') }})</span>
</el-button>
</el-badge>
<el-button
@@ -13,11 +28,10 @@
link
@click="onLoadUpgradeInfo"
>
{{ $t('setting.upgradeCheck') }}
({{ $t('setting.upgradeCheck') }})
</el-button>
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">{{ $t('setting.upgrading') }}</el-tag>
</div>
<el-drawer :close-on-click-modal="false" :key="refresh" v-model="drawerVisiable" size="50%" append-to-body>
<template #header>
<DrawerHeader :header="$t('setting.upgrade')" :back="handleClose" />
@@ -50,6 +64,7 @@
</el-drawer>
</template>
<script setup lang="ts">
import DrawerHeader from '@/components/drawer-header/index.vue';
import { getSettingInfo, loadReleaseNotes, loadUpgradeInfo, upgrade } from '@/api/modules/setting';
import MdEditor from 'md-editor-v3';
import i18n from '@/lang';
@@ -61,12 +76,17 @@ import { ElMessageBox } from 'element-plus';
const globalStore = GlobalStore();
const version = ref();
let loading = ref(false);
const loading = ref(false);
const drawerVisiable = ref(false);
const upgradeInfo = ref();
const refresh = ref();
const upgradeVersion = ref();
const props = defineProps({
footer: {
type: Boolean,
default: false,
},
});
const search = async () => {
const res = await getSettingInfo();
@@ -77,6 +97,14 @@ const handleClose = () => {
drawerVisiable.value = false;
};
const toDoc = () => {
window.open('https://1panel.cn/docs/', '_blank');
};
const toForum = () => {
window.open('https://bbs.fit2cloud.com/c/1p/7', '_blank');
};
const onLoadUpgradeInfo = async () => {
loading.value = true;
await loadUpgradeInfo()
+1 -1
View File
@@ -47,7 +47,7 @@ const checkName = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.commonName')));
} else {
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{0,30}$/;
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{1,29}$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.commonName')));
} else {

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