Compare commits

...
108 Commits
Author SHA1 Message Date
zhengkunwang 1dfbca3d5b feat: 删除多余代码 (#6463) 2024-09-11 23:10:15 +08:00
zhengkunwang d2ca265cc2 perf: 优化首页加载速度 (#6461) 2024-09-11 14:12:16 +00:00
ssongliu fc70345fcd fix: 优化移动端运行时样式 (#6460) 2024-09-11 22:02:11 +08:00
ssongliu 6ae9b982a9 fix: 优化移动端样式 (#6455) 2024-09-11 10:22:15 +00:00
蘭 0122b4c6ce fix: 解决主机文件压缩解压特殊字符问题 (#6451) 2024-09-11 08:20:15 +00:00
ssongliu 08c0faa20e feat: 优化概览页内存单位 (#6450) 2024-09-11 07:38:15 +00:00
ssongliu ab7efea8c5 style: 优化移动端样式 (#6448) 2024-09-11 07:24:15 +00:00
zhengkunwang f4623664d5 feat: 日志页面增加加载状态 (#6446) 2024-09-11 03:48:14 +00:00
ssongliu df147435c5 fix: 移除重复样式 (#6445) 2024-09-11 03:16:14 +00:00
see-more 71c1c5ae83 style:优化设置移动端界面 (#6443)
* style:优化设置移动端界面

* style:设置-安全界面样式

* style:修复设置-备份账号服务器磁盘移动端无法剧中

* style:优化移动端设置界面
2024-09-11 10:30:46 +08:00
see-more 0ea142d6db style:优化日志审计页面样式 (#6441) 2024-09-11 10:16:02 +08:00
ssongliu f88a3059be fix: 修改移动端样式 (#6440) 2024-09-10 14:04:12 +00:00
蘭 48281ddf86 fix: 主机文件解压文件 mimetype 类型增加 (#6437) 2024-09-10 14:02:11 +00:00
zhengkunwang 7cdcf9b757 feat: 增加 Intel Arc 显卡 适配 (#6435) 2024-09-10 18:55:29 +08:00
ssongliu ef0bb12fbd fix: 处理代码格式问题 (#6433) 2024-09-10 07:40:09 +00:00
蘭 a0857f86b2 fix: 主机文件打开软链接优化 (#6432) 2024-09-10 07:26:10 +00:00
wangdan-fit2cloud 2219b3f47b perf: 优化工具栏样式 (#6431)
#### 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.
2024-09-10 07:18:10 +00:00
蘭 b6c873f22d fix: 主机文件的路径栏路径 Bug 解决 (#6428)
Refs #6310
2024-09-10 03:46:08 +00:00
蘭 64bc92a7fd fix: 限制二进制文件解压的类型 (#6425)
Refs #6113
2024-09-10 02:14:08 +00:00
ssongliu 6c4c0f7595 fix: 解决服务器重启按钮被遮住的样式问题 (#6424) 2024-09-09 22:56:14 +08:00
蘭 2d9c977805 fix: 解决文件编辑器行号问题 (#6423)
Refs #5918

升级了编辑器版本,调整了编辑器的主题,需要整体测试一下编辑器的样式!
2024-09-09 10:08:04 +00:00
ssongliu dff0375848 fix: 解决容器端口的显示问题 (#6417) 2024-09-09 07:26:04 +00:00
ssongliu ab825a1a70 feat: 防火墙操作支持 ipv6 网段 (#6415) 2024-09-09 11:19:29 +08:00
Anye 466617847f fix: 创建CA机构生成的应为证书而非证书签名请求 (#6404) 2024-09-07 21:56:38 +08:00
蘭 a53fff2416 fix: 解决软连接被识别为文件问题 (#6400)
Refs #5879
2024-09-06 09:37:52 +00:00
zhengkunwang dd1430dbbf feat: 反向代理配置文件统一 (#6396)
Refs https://github.com/1Panel-dev/1Panel/issues/6395
2024-09-06 06:33:50 +00:00
蘭 5aad16968b perf: 优化文件管理器终端命令 (#6393)
Refs #5891
2024-09-06 03:51:50 +00:00
see-more f2d3acb650 style:更新所有移动端container路径下样式 (#6387)
* style:更新所有移动端container路径下样式

* style:修复el-button的默认margin
2024-09-06 10:00:53 +08:00
see-more 666f8c813d style:移动端文件页面样式和防火墙页面样式 (#6386)
* style:移动端端文件页面样式

* style:修改防火墙页面样式

* style:手机端ssl界面和数据库界面调整
2024-09-06 10:00:39 +08:00
ssongliu e87fb7b50e fix: 备份账号 bucket 支持手动输入 (#6381)
Refs #6378
2024-09-05 13:53:48 +00:00
蘭 75612879c7 fix: 解决部分情况下缓存目录导致启动失败的问题 (#6382) 2024-09-05 09:23:47 +00:00
蘭 94e0c80d47 refactor: 调整主机监控任务时间为整分钟执行 (#6379) 2024-09-05 08:41:48 +00:00
蘭 feb6f4cbe0 fix: 概览页获取 CPU 精度问题 (#6380)
Refs #6309
2024-09-05 08:39:48 +00:00
ssongliu 1173bbf628 fix: 解决备份恢复弹窗打开后无法关闭的问题 (#6376)
Refs #6355
2024-09-05 07:11:48 +00:00
ssongliu 1cdc2e93ad fix: 解决 FreshClam 操作失败的问题 (#6374)
Refs #6372
2024-09-05 07:09:46 +00:00
see-more 3356bd8825 fix:移动端网站页面和数据库页面样式 (#6360)
* fix:移动端网站页面和数据库页面样式

* style:修改database页面样式

* style:修改databse页面手机端搜索框与按钮间距
2024-09-05 11:46:57 +08:00
ssongliu decc7a4a1e fix: 解决停止状态容器端口获取不正确的问题 (#6370)
Refs #6333
2024-09-05 03:45:46 +00:00
zhengkunwang a397cab61c fix: 解决部分情况下缓存目录导致启动失败的问题 (#6364)
Refs https://github.com/1Panel-dev/1Panel/issues/6362
2024-09-04 09:21:43 +00:00
trom 228e3d9ae3 style:优化container界面的布局 (#6348) 2024-09-03 13:46:37 +08:00
zhengkunwang 051f670453 fix: 修复域名删除导致网站访问被 WAF 拦截的问题 (#6337)
Refs https://github.com/1Panel-dev/1Panel/issues/6336
2024-09-02 10:23:37 +00:00
trom cf8aa86a67 fix:修复文件路径bug (#6313)
* fix:修复文件路径bug

* fix:修改文件路径逻辑
2024-09-02 10:35:32 +08:00
ssongliu 5b00286e65 fix: 修改 PostgreSQL 容器内部端口 (#6275)
Refs #6274
2024-08-28 08:45:22 +00:00
ssongliu 6597caaf05 fix: 解决快照套接字忽略失败的问题 (#6273) 2024-08-28 06:41:22 +00:00
ssongliu 5851124dee fix: 解决备份套接字忽略不完的问题 (#6272) 2024-08-28 05:45:22 +00:00
trom b18689a0d6 feat:显示当前ip,系统网络代理地址 (#6251) 2024-08-27 13:39:36 +08:00
蘭 a5e7f78a1f pref: 完善告警通知任务 (#6263) 2024-08-27 13:39:18 +08:00
ssongliu a1d394fbc2 fix: 解决容器端口范围太大时,编辑打开失败的问题 (#6249)
Refs #6245
2024-08-26 08:07:13 +00:00
wanghe-fit2cloud 5e82bda666 Revert commit 3d5580a: feat:显示当前ip,系统网络代理地址 2024-08-26 15:55:07 +08:00
trom 3d5580ad9d feat:显示当前ip,系统网络代理地址 (#6235) 2024-08-26 11:25:03 +08:00
ssongliu c83cfbe8b0 fix: 解决备份导入失败的问题 (#6228) 2024-08-23 14:58:59 +00:00
蘭 00cc1b62ef pref: 完善告警通知 (#6227) 2024-08-23 13:53:00 +00:00
ssongliu e782f75863 fix: 解决容器配置修改后未重启的问题 (#6225)
Refs #6226
2024-08-23 10:24:58 +00:00
zhengkunwang f5f379465f feat: 创建运行环境网站,校验 PHP 镜像是否存在 (#6220) 2024-08-23 09:37:00 +00:00
ssongliu 70a8fda449 feat: 优化数据库备份页面样式 (#6221) 2024-08-23 17:36:21 +08:00
ssongliu ca14302191 fix: 优化部分频繁打印的日志操作 (#6219) 2024-08-23 06:40:58 +00:00
ssongliu 7297c2626f fix: 优化防火墙启动时自动添加端口功能 (#6216)
Refs #6215
2024-08-23 03:54:57 +00:00
蘭 92a3982f21 feat: 告警通知功能完善 (#6211) 2024-08-22 14:22:56 +00:00
蘭 626f5efdcd style: 优化 switch 组件 small 尺寸样式 (#6210) 2024-08-22 14:20:55 +00:00
ssongliu 24e780b984 fix: 压缩增加 *.socket 排除规则 (#6196) 2024-08-21 07:12:50 +00:00
zhengkunwang 596d4aceb6 fix: 解决删除 PHP 运行环境之后,删除其他网站提示错误的问题 (#6192) 2024-08-21 02:50:50 +00:00
蘭 e770254b65 feat: 增加短信告警功能 (#6184) 2024-08-20 22:11:13 +08:00
wanghe-fit2cloud 7901c26d32 feat: 修改 snapshot 参数 2024-08-20 18:47:49 +08:00
zhengkunwang fc65f8f689 feat: 修复代码警告 (#6190) 2024-08-20 10:36:47 +00:00
zhengkunwang e0a5e14978 feat: 修改非 PHP 运行环境网站的删除提示 (#6189) 2024-08-20 09:42:47 +00:00
zhengkunwang 07562c203a feat: 优化已安装应用的参数显示 (#6188) 2024-08-20 09:40:48 +00:00
ssongliu 8187a19e6e fix: 修改版本号复制方式 (#6187) 2024-08-20 09:38:47 +00:00
zhengkunwang 32cd684307 fix: 解决路由菜单文字丢失的问题 (#6185) 2024-08-20 06:46:46 +00:00
ssongliu 3a72fc95d9 style: 处理代码警告 (#6183) 2024-08-20 03:38:47 +00:00
ssongliu 64405fba3d fix: 统一时区获取方法 (#6175) 2024-08-19 10:30:42 +00:00
zhengkunwang cc7981229c fix: 解决 redis 没有密码情况下安装应用没有关联的问题 (#6173)
Refs #6174
2024-08-19 10:06:42 +00:00
zhengkunwang 3ec208dc85 perf: 优化运行环境日志卡顿情况 (#6172) 2024-08-19 10:04:43 +00:00
ssongliu 1b92b6cbfa fix: mysql 绑定用户增加输入校验 (#6149) 2024-08-16 06:00:31 +00:00
trom 779addfcb4 fix:mysql升级到8.4后查询会出错,容器的command回显使用 text area (#6147)
* style:容器的command参数回显应该使用 text area而不是text input,且不应该过滤'\n'

* chore:prettier格式化

* fix:mysql升级到8.4后,数据库管理 - 当前状态 查询会出错

* chore:修改逻辑判断
2024-08-16 11:23:44 +08:00
ssongliu 7da5f7a884 fix: 修改镜像拉取校验规则 (#6145)
Refs #5740
2024-08-15 15:18:32 +00:00
ssongliu 34e14636d4 fix: 解决 ClamAV 卸载后定时任务无法停止的问题 (#6143)
Refs #6100
2024-08-15 15:16:30 +00:00
ssongliu aa222aac31 fix: 解决计划任务备份内容允许为空的校验问题 (#6142)
Refs #6099
2024-08-15 09:42:28 +00:00
zhengkunwang 5134caa103 fix: 解决部分应用参数数据库显示错误的问题 (#6136) 2024-08-15 07:12:28 +00:00
zhengkunwang dca3c92d72 fix: 解决 PHP 运行环境版本过期之后下载安装包失败的问题 (#6125) 2024-08-14 10:29:59 +00:00
zhengkunwang a85d5bad46 feat: 优化 mysql 版本升级 (#6119) 2024-08-13 10:48:56 +00:00
zhengkunwang 716613a4db feat: 应用升级备份保留 3 份 (#6116)
Refs https://github.com/1Panel-dev/1Panel/issues/6107
2024-08-13 07:28:55 +00:00
zhengkunwang 12eaa66bc5 style: 去掉应用列表中的更新应用列表按钮 (#6105) 2024-08-12 18:40:10 +08:00
zhengkunwang a6af97e3c1 style: 优化文件属性页面显示 (#6104)
Refs https://github.com/1Panel-dev/1Panel/issues/5855
2024-08-12 10:02:53 +00:00
zhengkunwang d561c86c68 feat: 优化网站日志在每行数据量大情况下的读取 (#6103)
Refs https://github.com/1Panel-dev/1Panel/issues/6102
2024-08-12 09:58:52 +00:00
dependabot[bot] f243d1c9b4 chore(deps): bump github.com/docker/docker (#6093) 2024-08-12 13:43:47 +08:00
trom 1cc2cc6400 feat:更新应用列表上移 #6035 (#6084) 2024-08-12 10:26:16 +08:00
trom 15f4c05772 chore:点击复制版本号(#5894) (#6077) 2024-08-09 18:25:53 +08:00
trom c9a204b7a7 fix: 搜索框加长(#6063) 回收站时间排序(#5957) hoer状态撑高界面(#5929) (#6070) 2024-08-09 10:22:29 +08:00
trom 3d0018c0bd chore:新建文件夹支持添加空格,登录页鼠标悬浮样式 (#6058) 2024-08-08 10:27:06 +08:00
zhengkunwang d639e46bc7 fix: 解决 PHP 运行环境网站删除之后没有删除应用的问题 (#6036) 2024-08-06 03:34:29 +00:00
dependabot[bot] fa633bf419 chore(deps): bump github.com/docker/docker (#5989)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.2+incompatible to 26.1.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.2...v26.1.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-31 19:05:05 +08:00
zhengkunwang 40f7ef90b3 feat: 修改反向代理默认 Connection 请求头 (#5951)
Refs https://github.com/1Panel-dev/1Panel/issues/5937
2024-07-26 07:01:40 +00:00
zhengkunwang 50efe607d4 feat: 优化泛域名申请证书手动解析值 (#5950)
Refs https://github.com/1Panel-dev/1Panel/issues/4609
2024-07-26 06:59:40 +00:00
igophper 3b057f2c27 feat: 2FA支持前一个验证码 (#5942)
Refs #5940
2024-07-26 11:15:53 +08:00
maninhill d3d018bd44 chore: Update README.md 2024-07-24 17:01:38 +08:00
maninhill 1378609aa2 chore: Update README.md 2024-07-24 16:58:59 +08:00
zhengkunwang 001c896eed fix: 解决上传文件夹权限没改的问题 (#5919) 2024-07-23 17:52:30 +08:00
ssongliu 1cf3a0143c fix: 解决系统日志解析错误的问题 (#5917) 2024-07-23 07:57:32 +00:00
zhengkunwang f04be521e5 feat: 回滚代码 (#5914) 2024-07-23 14:42:06 +08:00
ssongliu 2873632a40 pref: 移除部分废弃代码 (#5910) 2024-07-23 02:31:32 +00:00
zhengkunwang 075879b5b5 fix: 解决应用商店 Go 和 Java 安装跳转错误的问题 (#5909)
Refs https://github.com/1Panel-dev/1Panel/issues/5900
2024-07-23 02:29:32 +00:00
zhengkunwang 9b12930376 fix: 解决安装 gitea 跳转到 postgres 报错的问题 (#5908)
Refs https://github.com/1Panel-dev/1Panel/issues/5902
2024-07-23 02:27:31 +00:00
zhengkunwang 3339ba9bad fix: 解决潜在的 SQL 注入漏洞 (#5906)
影响范围:操作日志
2024-07-22 14:41:31 +00:00
zhengkunwang 68922676fc fix: 解决从网站上传的证书状态显示异常的问题 (#5869)
Refs https://github.com/1Panel-dev/1Panel/issues/5867
2024-07-19 07:41:28 +00:00
zhengkunwang dcf2afb4a6 feat: 上传文件使用父目录用户和用户组 (#5868)
Refs https://github.com/1Panel-dev/1Panel/issues/5866
2024-07-19 07:33:28 +00:00
ssongliu 16a35141dd fix: 解决病毒定时扫描校验国际化的问题 (#5856) 2024-07-18 03:27:28 +00:00
John Bro f6c334bdad feat: 计划任务备份应用/网站/数据库支持多选 (#5849) 2024-07-17 13:35:27 +00:00
蘭 cbb119117b fix: 多个文件同时计算大小,计算状态独自显示 (#5851) 2024-07-17 11:09:30 +00:00
ssongliu cae0a6118f pref: 优化远程数据库解绑提示信息 (#5850) 2024-07-17 09:53:28 +00:00
191 changed files with 3153 additions and 2539 deletions
+2 -3
View File
@@ -29,6 +29,7 @@ build/1panel
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories
/pkg/
backend/__debug_bin
@@ -40,9 +41,7 @@ frontend/auto-imports.d.ts
frontend/components.d.ts
frontend/src/xpack
backend/xpack
backend/router/entry_xpack.go
backend/server/init_xpack.go
backend/utils/xpack/xpack_xpack.go
*_xpack.go
.history/
dist/
+1 -1
View File
@@ -44,7 +44,7 @@ archives:
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
version_template: "{{ incpatch .Version }}-next"
release:
draft: true
mode: append
+2 -1
View File
@@ -51,7 +51,8 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源自动化测试平台
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源持续测试工具
- [KubePi](https://github.com/1Panel-dev/KubePi/) - 现代化、开源的 K8s 面板
## License
+1 -1
View File
@@ -155,7 +155,7 @@ func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
// @Success 200 {object} model.AppInstall
// @Security ApiKeyAuth
// @Router /apps/install [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"name","input_value":"name","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"}
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"安装应用 [name]","formatEN":"Install app [name]"}
func (b *BaseApi) InstallApp(c *gin.Context) {
var req request.AppInstallCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+1 -1
View File
@@ -72,7 +72,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
return
}
loc, _ := time.LoadLocation(common.LoadTimeZone())
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
+7 -14
View File
@@ -2,6 +2,7 @@ package v1
import (
"errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -55,25 +56,17 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
// @Tags Dashboard
// @Summary Load dashboard current info
// @Description 获取首页实时数据
// @Accept json
// @Param ioOption path string true "request"
// @Param netOption path string true "request"
// @Accept json、
// @Param request body dto.DashboardReq true "request"
// @Success 200 {object} dto.DashboardCurrent
// @Security ApiKeyAuth
// @Router /dashboard/current/:ioOption/:netOption [get]
// @Router /dashboard/current [post]
func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
ioOption, ok := c.Params.Get("ioOption")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error ioOption in path"))
var req dto.DashboardReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
netOption, ok := c.Params.Get("netOption")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error netOption in path"))
return
}
data := dashboardService.LoadCurrentInfo(ioOption, netOption)
data := dashboardService.LoadCurrentInfo(req)
helper.SuccessWithData(c, data)
}
+25 -4
View File
@@ -11,6 +11,7 @@ import (
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -336,8 +337,12 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
return
}
mode := info.Mode()
fileOp := files.NewFileOp()
stat, ok := info.Sys().(*syscall.Stat_t)
uid, gid := -1, -1
if ok {
uid, gid = int(stat.Uid), int(stat.Gid)
}
success := 0
failures := make(buserr.MultiErr)
@@ -351,6 +356,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
global.LOG.Error(e)
continue
}
_ = os.Chown(dstDir, uid, gid)
}
tmpFilename := dstFilename + ".tmp"
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
@@ -378,6 +384,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
} else {
_ = os.Chmod(dstFilename, mode)
}
if uid != -1 && gid != -1 {
_ = os.Chown(dstFilename, uid, gid)
}
success++
}
if success == 0 {
@@ -603,9 +612,17 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
if mode == 0 {
mode = 0755
}
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = op.CreateDir(dstDir, mode); err != nil {
return err
uid, gid := -1, -1
if info, err := os.Stat(dstDir); err != nil {
if os.IsNotExist(err) {
if err = op.CreateDir(dstDir, mode); err != nil {
return err
}
}
} else {
stat, ok := info.Sys().(*syscall.Stat_t)
if ok {
uid, gid = int(stat.Uid), int(stat.Gid)
}
}
dstFileName := filepath.Join(dstDir, fileName)
@@ -615,6 +632,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
} else {
mode = 0644
}
if overwrite {
_ = os.Remove(dstFileName)
}
@@ -635,6 +653,9 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int,
}
_ = os.Remove(chunkPath)
}
if uid != -1 && gid != -1 {
_ = os.Chown(dstFileName, uid, gid)
}
return nil
}
+1 -1
View File
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
return
}
loc, _ := time.LoadLocation(common.LoadTimeZone())
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
+2 -2
View File
@@ -99,12 +99,12 @@ func (b *BaseApi) UpdateSnapDescription(c *gin.Context) {
// @Summary Page system snapshot
// @Description 获取系统快照列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Param request body dto.PageSnapshot true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /settings/snapshot/search [post]
func (b *BaseApi) SearchSnapshot(c *gin.Context) {
var req dto.SearchWithPage
var req dto.PageSnapshot
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
+1
View File
@@ -36,6 +36,7 @@ type CommonBackup struct {
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
FileName string `json:"fileName"`
}
type CommonRecover struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
+22 -4
View File
@@ -16,10 +16,11 @@ type DashboardBase struct {
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
VirtualizationSystem string `json:"virtualizationSystem"`
CPUCores int `json:"cpuCores"`
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
IpV4Addr string `json:"ipv4Addr"`
SystemProxy string `json:"SystemProxy"`
CPUCores int `json:"cpuCores"`
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
@@ -34,6 +35,12 @@ type OsInfo struct {
DiskSize int64 `json:"diskSize"`
}
type DashboardReq struct {
Scope string `json:"scope"`
IoOption string `json:"ioOption"`
NetOption string `json:"netOption"`
}
type DashboardCurrent struct {
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`
@@ -72,6 +79,7 @@ type DashboardCurrent struct {
NetBytesRecv uint64 `json:"netBytesRecv"`
GPUData []GPUInfo `json:"gpuData"`
XPUData []XPUInfo `json:"xpuData"`
ShotTime time.Time `json:"shotTime"`
}
@@ -105,3 +113,13 @@ type GPUInfo struct {
MemTotal string `json:"memTotal"`
FanSpeed string `json:"fanSpeed"`
}
type XPUInfo struct {
DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"`
Memory string `json:"memory"`
Temperature string `json:"temperature"`
MemoryUsed string `json:"memoryUsed"`
Power string `json:"power"`
MemoryUtil string `json:"memoryUtil"`
}
+5 -4
View File
@@ -36,10 +36,11 @@ type FileWgetRes struct {
}
type FileLineContent struct {
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
Lines []string `json:"lines"`
}
type FileExist struct {
+2
View File
@@ -30,6 +30,8 @@ type WebsiteRes struct {
RuntimeName string `json:"runtimeName"`
SSLExpireDate time.Time `json:"sslExpireDate"`
SSLStatus string `json:"sslStatus"`
AppInstallID uint `json:"appInstallId"`
RuntimeType string `json:"runtimeType"`
}
type WebsiteOption struct {
+6
View File
@@ -98,6 +98,12 @@ type PortUpdate struct {
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
}
type PageSnapshot struct {
PageInfo
Info string `json:"info"`
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type SnapshotStatus struct {
Panel string `json:"panel"`
PanelInfo string `json:"panelInfo"`
+7
View File
@@ -25,6 +25,7 @@ type IBackupRepo interface {
WithByFileName(fileName string) DBOption
WithByType(backupType string) DBOption
WithByCronID(cronjobID uint) DBOption
WithFileNameStartWith(filePrefix string) DBOption
}
func NewIBackupRepo() IBackupRepo {
@@ -81,6 +82,12 @@ func (u *BackupRepo) WithByFileName(fileName string) DBOption {
}
}
func (u *BackupRepo) WithFileNameStartWith(filePrefix string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("file_name LIKE ?", filePrefix+"%")
}
}
func (u *BackupRepo) WithByType(backupType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(backupType) == 0 {
-1
View File
@@ -50,7 +50,6 @@ func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
return db.Where("ca_id = ?", caID)
}
}
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
var sslList []model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
+8 -5
View File
@@ -91,10 +91,10 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
}
var appDTOs []*response.AppDto
for _, ap := range apps {
ap.ReadMe = ""
ap.Website = ""
ap.Document = ""
ap.Github = ""
// ap.ReadMe = ""
// ap.Website = ""
// ap.Document = ""
// ap.Github = ""
appDTO := &response.AppDto{
ID: ap.ID,
Name: ap.Name,
@@ -145,6 +145,9 @@ func (a AppService) GetAppTags() ([]response.TagDTO, error) {
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
var appDTO response.AppDTO
if key == "postgres" {
key = "postgresql"
}
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
@@ -185,7 +188,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
if !fileOp.Stat(versionPath) || detail.Update {
if err = downloadApp(app, detail, nil); err != nil {
if err = downloadApp(app, detail, nil); err != nil && !fileOp.Stat(versionPath) {
return appDetailDTO, err
}
}
+22
View File
@@ -535,6 +535,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
if err != nil {
return versions, err
}
if app.Key == constant.AppMysql {
majorVersion := getMajorVersion(install.Version)
if !strings.HasPrefix(detail.Version, majorVersion) {
continue
}
}
versions = append(versions, dto.AppVersion{
Version: detail.Version,
DetailId: detail.ID,
@@ -703,6 +709,22 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
}
}
appParam.Values = form.Values
} else if form.Type == "apps" {
if m, ok := form.Child.(map[string]interface{}); ok {
result := make(map[string]string)
for key, value := range m {
if strVal, ok := value.(string); ok {
result[key] = strVal
}
}
if envKey, ok := result["envKey"]; ok {
serviceName := envs[envKey]
if serviceName != nil {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string)))
appParam.ShowValue = appInstall.Name
}
}
}
}
params = append(params, appParam)
} else {
+49 -12
View File
@@ -186,17 +186,18 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
}
case constant.AppRedis:
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.RedisAuthParam{
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
authParam := dto.RedisAuthParam{
RootPassword: "",
}
database.Password = password.(string)
if password != "" {
authParam.RootPassword = password.(string)
database.Password = password.(string)
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
}
}
if err := databaseRepo.Create(ctx, database); err != nil {
@@ -516,8 +517,19 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
)
global.LOG.Infof(i18n.GetMsgWithName("UpgradeAppStart", install.Name, nil))
if req.Backup {
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name})
iBackUpService := NewIBackupService()
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupRecord, err := iBackUpService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
if err == nil {
backups, _ := iBackUpService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
if len(backups) > 3 {
backupsToDelete := backups[:len(backups)-3]
var deleteIDs []uint
for _, backup := range backupsToDelete {
deleteIDs = append(deleteIDs, backup.ID)
}
_ = iBackUpService.BatchDeleteRecord(deleteIDs)
}
localDir, err := loadLocalDir()
if err == nil {
backupFile = path.Join(localDir, backupRecord.FileDir, backupRecord.FileName)
@@ -1224,6 +1236,31 @@ func synAppInstall(containers map[string]types.Container, appInstall *model.AppI
_ = appInstallRepo.Save(context.Background(), appInstall)
}
func getMajorVersion(version string) string {
parts := strings.Split(version, ".")
if len(parts) >= 2 {
return parts[0] + "." + parts[1]
}
return version
}
func ignoreUpdate(installed model.AppInstall) bool {
if installed.App.Type == "php" || installed.Status == constant.Installing {
return true
}
if installed.App.Key == constant.AppMysql {
majorVersion := getMajorVersion(installed.Version)
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
for _, appDetail := range appDetails {
if strings.HasPrefix(appDetail.Version, majorVersion) && common.CompareVersion(appDetail.Version, installed.Version) {
return false
}
}
return true
}
return false
}
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
var (
res []response.AppInstallDTO
@@ -1246,7 +1283,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
}
for _, installed := range appInstallList {
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
if updated && ignoreUpdate(installed) {
continue
}
if sync && !doNotNeedSync(installed) {
+15
View File
@@ -40,6 +40,7 @@ type IBackupService interface {
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
ListFiles(req dto.BackupSearchFile) []string
@@ -100,6 +101,20 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
return total, datas, err
}
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
records, err := backupRepo.ListRecord(
commonRepo.WithOrderBy("created_at asc"),
commonRepo.WithByName(name),
commonRepo.WithByType("app"),
backupRepo.WithFileNameStartWith(fileName),
backupRepo.WithByDetailName(detailName),
)
if err != nil {
return nil, err
}
return records, err
}
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
total, records, err := backupRepo.PageRecord(
search.Page, search.PageSize,
+4 -1
View File
@@ -39,7 +39,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
backupDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
fileName := req.FileName
if req.FileName == "" {
fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
}
if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil {
return nil, err
}
+28 -3
View File
@@ -92,6 +92,8 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
}
}
} else {
_ = StopAllCronJob(false)
}
if baseInfo.FreshIsActive {
version, err := cmd.Exec("freshclam --version")
@@ -139,7 +141,7 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
item.LastHandleDate = "-"
datas = append(datas, item)
}
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for i := 0; i < len(datas); i++ {
logPaths := loadFileByName(datas[i].Name)
sort.Slice(logPaths, func(i, j int) bool {
@@ -268,7 +270,7 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
}
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
if cmd.Which("clamdscan") == false {
if cleaned := StopAllCronJob(true); cleaned {
return buserr.New("ErrClamdscanNotFound")
}
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
@@ -321,7 +323,7 @@ func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, er
}
var filterFiles []string
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, item := range logPaths {
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
if err != nil {
@@ -473,6 +475,29 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
return nil
}
func StopAllCronJob(withCheck bool) bool {
if withCheck {
isActive := false
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
}
if isActive {
return false
}
}
clams, _ := clamRepo.List(commonRepo.WithByStatus(constant.StatusEnable))
for i := 0; i < len(clams); i++ {
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
}
return true
}
func loadFileByName(name string) []string {
var logPaths []string
pathItem := path.Join(global.CONF.System.DataDir, resultDir, name)
+65 -17
View File
@@ -187,7 +187,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
IsFromApp = true
}
ports := loadContainerPort(item.Ports)
exposePorts := transPortToStr(records[i].Ports)
info := dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
@@ -196,7 +196,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
Ports: ports,
Ports: exposePorts,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
@@ -445,6 +445,8 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
}
}
exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
networkSettings := oldContainer.NetworkSettings
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
@@ -465,19 +467,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
for key, val := range oldContainer.Config.Labels {
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
}
for key, val := range oldContainer.HostConfig.PortBindings {
var itemPort dto.PortHelper
if !strings.Contains(string(key), "/") {
continue
}
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
itemPort.Protocol = strings.Split(string(key), "/")[1]
for _, binds := range val {
itemPort.HostIP = binds.HostIP
itemPort.HostPort = binds.HostPort
data.ExposedPorts = append(data.ExposedPorts, itemPort)
}
}
data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.Privileged = oldContainer.HostConfig.Privileged
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
@@ -1201,7 +1191,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
global.LOG.Errorf("restart after container update failed, err: %v", err)
}
global.LOG.Errorf("recreate after container update successful")
global.LOG.Info("recreate after container update successful")
}
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
@@ -1224,7 +1214,65 @@ func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
return datas
}
func loadContainerPort(ports []types.Port) []string {
func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) {
container, err := client.ContainerInspect(context.Background(), id)
if err != nil {
return nil, err
}
var itemPorts []types.Port
for key, val := range container.ContainerJSONBase.HostConfig.PortBindings {
if !strings.Contains(string(key), "/") {
continue
}
item := strings.Split(string(key), "/")
itemPort, _ := strconv.ParseUint(item[0], 10, 16)
for _, itemVal := range val {
publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16)
itemPorts = append(itemPorts, types.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
}
}
return itemPorts, nil
}
func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
var exposedPorts []dto.PortHelper
samePortMap := make(map[string]dto.PortHelper)
ports := transPortToStr(itemPorts)
var itemPort dto.PortHelper
for _, item := range ports {
itemStr := strings.Split(item, "->")
if len(itemStr) < 2 {
continue
}
lastIndex := strings.LastIndex(itemStr[0], ":")
if lastIndex == -1 {
itemPort.HostPort = itemStr[0]
} else {
itemPort.HostIP = itemStr[0][0:lastIndex]
itemPort.HostPort = itemStr[0][lastIndex+1:]
}
itemContainer := strings.Split(itemStr[1], "/")
if len(itemContainer) != 2 {
continue
}
itemPort.ContainerPort = itemContainer[0]
itemPort.Protocol = itemContainer[1]
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
if val, ok := samePortMap[keyItem]; ok {
val.HostIP = ""
samePortMap[keyItem] = val
} else {
samePortMap[keyItem] = itemPort
}
}
for _, val := range samePortMap {
exposedPorts = append(exposedPorts, val)
}
return exposedPorts
}
func transPortToStr(ports []types.Port) []string {
var (
ipv4Ports []types.Port
ipv6Ports []types.Port
+1 -1
View File
@@ -52,7 +52,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
records = list.Volumes[start:end]
}
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, item := range records {
tag := make([]string, 0)
for _, val := range item.Labels {
+36 -25
View File
@@ -17,15 +17,20 @@ import (
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
var apps []model.AppInstall
if cronjob.AppID == "all" {
if strings.Contains(cronjob.AppID, "all") {
apps, _ = appInstallRepo.ListBy()
} else {
itemID, _ := strconv.Atoi(cronjob.AppID)
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
appIds := strings.Split(cronjob.AppID, ",")
var idItems []uint
for i := 0; i < len(appIds); i++ {
itemID, _ := strconv.Atoi(appIds[i])
idItems = append(idItems, uint(itemID))
}
appItems, err := appInstallRepo.ListBy(commonRepo.WithIdsIn(idItems))
if err != nil {
return err
}
apps = append(apps, app)
apps = appItems
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
@@ -232,7 +237,7 @@ type databaseHelper struct {
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
var dbs []databaseHelper
if cronjob.DBName == "all" {
if strings.Contains(cronjob.DBName, "all") {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItems, _ := mysqlRepo.List()
for _, mysql := range mysqlItems {
@@ -254,36 +259,42 @@ func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
}
return dbs
}
itemID, _ := strconv.Atoi(cronjob.DBName)
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: mysqlItem.MysqlName,
Name: mysqlItem.Name,
})
} else {
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: pgItem.PostgresqlName,
Name: pgItem.Name,
})
dbNames := strings.Split(cronjob.DBName, ",")
for _, name := range dbNames {
itemID, _ := strconv.Atoi(name)
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: mysqlItem.MysqlName,
Name: mysqlItem.Name,
})
} else {
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: pgItem.PostgresqlName,
Name: pgItem.Name,
})
}
}
return dbs
}
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
var weblist []model.Website
if cronjob.Website == "all" {
if strings.Contains(cronjob.Website, "all") {
weblist, _ = websiteRepo.List()
return weblist
}
itemID, _ := strconv.Atoi(cronjob.Website)
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
if webItem.ID != 0 {
weblist = append(weblist, webItem)
websites := strings.Split(cronjob.Website, ",")
var idItems []uint
for i := 0; i < len(websites); i++ {
itemID, _ := strconv.Atoi(websites[i])
idItems = append(idItems, uint(itemID))
}
weblist, _ = websiteRepo.List(commonRepo.WithIdsIn(idItems))
return weblist
}
+7 -6
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
pathUtils "path"
"strings"
"time"
@@ -139,7 +140,6 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
excludes := strings.Split(exclusionRules, ",")
excludeRules := ""
excludes = append(excludes, "*.sock")
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
@@ -162,10 +162,14 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
if len(secret) != 0 {
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
itemPrefix := pathUtils.Base(sourceDir)
if itemPrefix == "/" {
itemPrefix = ""
}
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|%s/|') -zcf %s %s %s", sourceDir, "%P\n", itemPrefix, targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands)
}
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
@@ -239,7 +243,6 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
}
msg := i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath})
global.LOG.Infof(msg)
msgs = append(msgs, msg)
}
u.removeExpiredLog(*cronjob)
@@ -326,7 +329,6 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
}
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, record model.BackupRecord) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
var opts []repo.DBOption
opts = append(opts, commonRepo.WithByFrom("cronjob"))
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
@@ -361,7 +363,6 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
}
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) <= int(cronjob.RetainCopies) {
return
+117 -68
View File
@@ -3,13 +3,18 @@ package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
network "net"
"os"
"sort"
"strings"
"sync"
"time"
"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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -17,9 +22,6 @@ import (
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
)
type DashboardService struct{}
@@ -27,7 +29,7 @@ type DashboardService struct{}
type IDashboardService interface {
LoadOsInfo() (*dto.OsInfo, error)
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent
Restart(operation string) error
}
@@ -94,11 +96,20 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
baseInfo.KernelVersion = hostInfo.KernelVersion
ss, _ := json.Marshal(hostInfo)
baseInfo.VirtualizationSystem = string(ss)
baseInfo.IpV4Addr = GetOutboundIP()
httpProxy := os.Getenv("http_proxy")
if httpProxy == "" {
httpProxy = os.Getenv("HTTP_PROXY")
}
if httpProxy != "" {
baseInfo.SystemProxy = httpProxy
}
baseInfo.SystemProxy = "noProxy"
appInstall, err := appInstallRepo.ListBy()
if err != nil {
return nil, err
}
baseInfo.AppInstalledNumber = len(appInstall)
postgresqlDbs, err := postgresqlRepo.List()
if err != nil {
@@ -128,78 +139,88 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
baseInfo.CPUCores, _ = cpu.Counts(false)
baseInfo.CPULogicalCores, _ = cpu.Counts(true)
baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption)
baseInfo.CurrentInfo = *u.LoadCurrentInfo(dto.DashboardReq{
Scope: "ioNet",
IoOption: ioOption,
NetOption: netOption,
})
return &baseInfo, nil
}
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
func (u *DashboardService) LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent {
var currentInfo dto.DashboardCurrent
if req.Scope == "gpu" {
currentInfo.GPUData = loadGPUInfo()
currentInfo.XPUData = loadXpuInfo()
}
hostInfo, _ := host.Info()
currentInfo.Uptime = hostInfo.Uptime
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
currentInfo.Procs = hostInfo.Procs
currentInfo.CPUTotal, _ = cpu.Counts(true)
totalPercent, _ := cpu.Percent(0, false)
if len(totalPercent) == 1 {
currentInfo.CPUUsedPercent = totalPercent[0]
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
}
currentInfo.CPUPercent, _ = cpu.Percent(0, true)
loadInfo, _ := load.Avg()
currentInfo.Load1 = loadInfo.Load1
currentInfo.Load5 = loadInfo.Load5
currentInfo.Load15 = loadInfo.Load15
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
currentInfo.SwapMemoryTotal = swapInfo.Total
currentInfo.SwapMemoryAvailable = swapInfo.Free
currentInfo.SwapMemoryUsed = swapInfo.Used
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
currentInfo.DiskData = loadDiskInfo()
currentInfo.GPUData = loadGPUInfo()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
} else {
diskInfo, _ := disk.IOCounters(ioOption)
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
if req.Scope == "basic" {
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
currentInfo.Procs = hostInfo.Procs
currentInfo.CPUTotal, _ = cpu.Counts(true)
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
if len(totalPercent) == 1 {
currentInfo.CPUUsedPercent = totalPercent[0]
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
}
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true)
loadInfo, _ := load.Avg()
currentInfo.Load1 = loadInfo.Load1
currentInfo.Load5 = loadInfo.Load5
currentInfo.Load15 = loadInfo.Load15
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
currentInfo.SwapMemoryTotal = swapInfo.Total
currentInfo.SwapMemoryAvailable = swapInfo.Free
currentInfo.SwapMemoryUsed = swapInfo.Used
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
currentInfo.DiskData = loadDiskInfo()
}
if netOption == "all" {
netInfo, _ := net.IOCounters(false)
if len(netInfo) != 0 {
currentInfo.NetBytesSent = netInfo[0].BytesSent
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
if req.Scope == "ioNet" {
if req.IoOption == "all" {
diskInfo, _ := disk.IOCounters()
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
} else {
diskInfo, _ := disk.IOCounters(req.IoOption)
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
}
} else {
netInfo, _ := net.IOCounters(true)
for _, state := range netInfo {
if state.Name == netOption {
currentInfo.NetBytesSent = state.BytesSent
currentInfo.NetBytesRecv = state.BytesRecv
if req.NetOption == "all" {
netInfo, _ := net.IOCounters(false)
if len(netInfo) != 0 {
currentInfo.NetBytesSent = netInfo[0].BytesSent
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
}
} else {
netInfo, _ := net.IOCounters(true)
for _, state := range netInfo {
if state.Name == req.NetOption {
currentInfo.NetBytesSent = state.BytesSent
currentInfo.NetBytesRecv = state.BytesRecv
}
}
}
}
@@ -208,6 +229,18 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
return &currentInfo
}
func GetOutboundIP() string {
conn, err := network.Dial("udp", "8.8.8.8:80")
if err != nil {
return "IPNotFound"
}
defer conn.Close()
localAddr := conn.LocalAddr().(*network.UDPAddr)
return localAddr.IP.String()
}
type diskInfo struct {
Type string
Mount string
@@ -323,3 +356,19 @@ func loadGPUInfo() []dto.GPUInfo {
}
return data
}
func loadXpuInfo() []dto.XPUInfo {
list := xpack.LoadXpuInfo()
if len(list) == 0 {
return nil
}
var data []dto.XPUInfo
for _, gpu := range list {
var dataItem dto.XPUInfo
if err := copier.Copy(&dataItem, &gpu); err != nil {
continue
}
data = append(data, dataItem)
}
return data
}
+8 -1
View File
@@ -141,6 +141,10 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
}
func (u *MysqlService) BindUser(req dto.BindUser) error {
if cmd.CheckIllegal(req.Username, req.Password, req.Permission) {
return buserr.New(constant.ErrCmdIllegal)
}
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
if err != nil {
return err
@@ -533,7 +537,10 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
info.Position = "OFF"
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "show master status;")
if err != nil {
return nil, err
rows, err = executeSqlForRows(app.ContainerName, app.Key, app.Password, "SHOW BINARY LOG STATUS;")
if err != nil {
return nil, err
}
}
if len(rows) > 2 {
itemValue := strings.Split(rows[1], "\t")
+8
View File
@@ -198,6 +198,10 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
}
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
@@ -304,6 +308,10 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
if len(req.File) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
err := createIfNotExistDaemonJsonFile()
+16 -3
View File
@@ -62,9 +62,13 @@ func NewIFileService() IFileService {
func (f *FileService) GetFileList(op request.FileOption) (response.FileInfo, error) {
var fileInfo response.FileInfo
if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
data, err := os.Stat(op.Path)
if err != nil && os.IsNotExist(err) {
return fileInfo, nil
}
if !data.IsDir() {
op.FileOption.Path = filepath.Dir(op.FileOption.Path)
}
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
return fileInfo, err
@@ -141,7 +145,7 @@ func shouldFilterPath(path string) bool {
func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error {
for _, v := range items {
if shouldFilterPath(v.Path) {
global.LOG.Info("File Tree: Skipping %s due to filter\n", v.Path)
global.LOG.Infof("File Tree: Skipping %s due to filter\n", v.Path)
continue
}
childNode := response.FileTree{
@@ -167,7 +171,7 @@ func (f *FileService) buildChildNode(childNode *response.FileTree, fileInfo *fil
subInfo, err := files.NewFileInfo(op.FileOption)
if err != nil {
if os.IsPermission(err) || errors.Is(err, unix.EACCES) {
global.LOG.Info("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
global.LOG.Infof("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
return nil
}
global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error())
@@ -474,11 +478,20 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
if err != nil {
return nil, err
}
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
if err != nil {
return nil, err
}
lines = append(preLines, lines...)
}
res := &response.FileLineContent{
Content: strings.Join(lines, "\n"),
End: isEndOfFile,
Path: logFilePath,
Total: total,
Lines: lines,
}
return res, nil
}
-8
View File
@@ -588,14 +588,6 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
apps := u.loadPortByApp()
for _, app := range apps {
if len(app.HttpPort) != 0 && app.HttpPort != "0" {
if err := client.Port(fireClient.FireInfo{Port: app.HttpPort, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
}
}
return client.Reload()
}
+2 -2
View File
@@ -286,7 +286,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
if err != nil {
return "", err
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
authStr := base64.StdEncoding.EncodeToString(encodedJSON)
options.RegistryAuth = authStr
}
image := repo.DownloadUrl + "/" + req.ImageName
@@ -295,7 +295,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
out, err := client.ImagePull(context.TODO(), image, options)
if err != nil {
_, _ = file.WriteString("image pull failed!")
global.LOG.Errorf("image %s pull failed, err: %v", image, err)
_, _ = file.WriteString(fmt.Sprintf("image %s pull failed, err: %v", image, err))
return
}
defer out.Close()
+9 -6
View File
@@ -201,16 +201,19 @@ func StartMonitor(removeBefore bool, interval string) error {
service := NewIMonitorService()
ctx, cancel := context.WithCancel(context.Background())
monitorCancel = cancel
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), service)
if err != nil {
return err
}
now := time.Now()
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
time.AfterFunc(time.Until(nextMinute), func() {
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), service)
if err != nil {
return
}
global.MonitorCronID = monitorID
})
service.Run()
go service.saveIODataToDB(ctx, float64(intervalItem))
go service.saveNetDataToDB(ctx, float64(intervalItem))
global.MonitorCronID = monitorID
return nil
}
+6 -3
View File
@@ -27,7 +27,7 @@ type SnapshotService struct {
}
type ISnapshotService interface {
SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error)
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
SnapshotCreate(req dto.SnapshotCreate) error
SnapshotRecover(req dto.SnapshotRecover) error
SnapshotRollback(req dto.SnapshotRecover) error
@@ -46,8 +46,11 @@ func NewISnapshotService() ISnapshotService {
return &SnapshotService{}
}
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) {
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if err != nil {
return 0, nil, err
}
dtoSnap, err := loadSnapSize(systemBackups)
if err != nil {
return 0, nil, err
+2 -3
View File
@@ -231,7 +231,6 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
exMap := make(map[string]struct{})
exStr := ""
excludes := strings.Split(exclusionRules, ";")
excludes = append(excludes, "*.sock")
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
@@ -257,10 +256,10 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
commands := ""
if len(secret) != 0 {
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+exStr, path, extraCmd, targetDir+"/"+name)
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
global.LOG.Debug(commands)
}
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
+2 -2
View File
@@ -47,7 +47,7 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "Backup" {
if err := backupBeforeRecover(snap, ""); err != nil {
if err := backupBeforeRecover(snap); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
return
}
@@ -149,7 +149,7 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}
func backupBeforeRecover(snap model.Snapshot, secret string) error {
func backupBeforeRecover(snap model.Snapshot) error {
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
var wg sync.WaitGroup
var status model.SnapshotStatus
+1 -1
View File
@@ -312,7 +312,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
showCountFrom := (req.Page - 1) * req.PageSize
showCountTo := req.Page * req.PageSize
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
qqWry, err := qqwry.NewQQwry()
if err != nil {
global.LOG.Errorf("load qqwry datas failed: %s", err)
+25 -4
View File
@@ -9,6 +9,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"os"
"path"
"reflect"
@@ -139,8 +140,10 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
}
for _, web := range websites {
var (
appName string
runtimeName string
appName string
runtimeName string
runtimeType string
appInstallID uint
)
switch web.Type {
case constant.Deployment:
@@ -149,12 +152,15 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
return 0, nil, err
}
appName = appInstall.Name
appInstallID = appInstall.ID
case constant.Runtime:
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID))
if err != nil {
return 0, nil, err
}
runtimeName = runtime.Name
runtimeType = runtime.Type
appInstallID = runtime.ID
}
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
@@ -173,6 +179,8 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
SSLStatus: checkSSLStatus(web.WebsiteSSL.ExpireDate),
RuntimeName: runtimeName,
SitePath: sitePath,
AppInstallID: appInstallID,
RuntimeType: runtimeType,
})
}
return total, websiteDTOs, nil
@@ -305,6 +313,14 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
switch runtime.Type {
case constant.RuntimePHP:
if runtime.Resource == constant.ResourceAppstore {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
if !checkImageExist(client, runtime.Image) {
return buserr.WithName("ErrImageNotExist", runtime.Name)
}
var (
req request.AppInstallCreate
install *model.AppInstall
@@ -471,7 +487,9 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
tx, ctx := helper.GetTxAndContext()
defer tx.Rollback()
go NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
go func() {
_ = NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
}()
if err := websiteRepo.DeleteBy(ctx, commonRepo.WithByID(req.ID)); err != nil {
return err
}
@@ -623,8 +641,10 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
wafSite := wafWebsite
oldDomains := wafSite.Domains
var newDomains []string
removed := false
for _, domain := range oldDomains {
if domain == webSiteDomain.Domain {
if domain == webSiteDomain.Domain && !removed {
removed = true
continue
}
newDomains = append(newDomains, domain)
@@ -910,6 +930,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
websiteSSL.Provider = constant.Manual
websiteSSL.PrivateKey = privateKey
websiteSSL.Pem = certificate
websiteSSL.Status = constant.SSLReady
res.SSL = websiteSSL
}
+3 -3
View File
@@ -432,14 +432,14 @@ func (w WebsiteCAService) DownloadFile(id uint) (*os.File, error) {
if err = fileOp.CreateDir(dir, 0666); err != nil {
return nil, err
}
if err = fileOp.WriteFile(path.Join(dir, "ca.csr"), strings.NewReader(ca.CSR), 0644); err != nil {
if err = fileOp.WriteFile(path.Join(dir, "ca.crt"), strings.NewReader(ca.CSR), 0644); err != nil {
return nil, err
}
if err = fileOp.WriteFile(path.Join(dir, "private.key"), strings.NewReader(ca.PrivateKey), 0644); err != nil {
if err = fileOp.WriteFile(path.Join(dir, "ca.key"), strings.NewReader(ca.PrivateKey), 0644); err != nil {
return nil, err
}
fileName := ca.Name + ".zip"
if err = fileOp.Compress([]string{path.Join(dir, "ca.csr"), path.Join(dir, "private.key")}, dir, fileName, files.SdkZip, ""); err != nil {
if err = fileOp.Compress([]string{path.Join(dir, "ca.crt"), path.Join(dir, "ca.key")}, dir, fileName, files.SdkZip, ""); err != nil {
return nil, err
}
return os.Open(path.Join(dir, fileName))
+11
View File
@@ -0,0 +1,11 @@
package constant
const (
AlertEnable = "Enable"
AlertDisable = "Disable"
AlertSuccess = "Success"
AlertError = "Error"
AlertSyncError = "SyncError"
AlertPushError = "PushError"
AlertPushSuccess = "PushSuccess"
)
+10 -1
View File
@@ -95,7 +95,7 @@ var (
ErrPathNotFound = "ErrPathNotFound"
ErrMovePathFailed = "ErrMovePathFailed"
ErrLinkPathNotFound = "ErrLinkPathNotFound"
ErrFileIsExist = "ErrFileIsExist"
ErrFileIsExist = "ErrFileIsExist"
ErrFileUpload = "ErrFileUpload"
ErrFileDownloadDir = "ErrFileDownloadDir"
ErrCmdNotFound = "ErrCmdNotFound"
@@ -162,3 +162,12 @@ var (
ErrLicenseSave = "ErrLicenseSave"
ErrLicenseSync = "ErrLicenseSync"
)
// alert
var (
ErrAlert = "ErrAlert"
ErrAlertPush = "ErrAlertPush"
ErrAlertSave = "ErrAlertSave"
ErrAlertSync = "ErrAlertSync"
ErrAlertRemote = "ErrAlertRemote"
)
+2 -2
View File
@@ -17,14 +17,14 @@ import (
)
func Run() {
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
var (
interval model.Setting
status model.Setting
)
syncBeforeStart()
go syncBeforeStart()
if err := global.DB.Where("key = ?", "MonitorStatus").Find(&status).Error; err != nil {
global.LOG.Errorf("load monitor status from db failed, err: %v", err)
}
+5 -4
View File
@@ -1,6 +1,10 @@
package job
import (
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/app/service"
@@ -8,9 +12,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"path"
"strings"
"time"
)
type ssl struct {
@@ -25,7 +26,7 @@ func (ssl *ssl) Run() {
sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
global.LOG.Info("The scheduled certificate update task is currently in progress ...")
now := time.Now().Add(10 * time.Second)
for _, s := range sslList {
+4 -3
View File
@@ -1,6 +1,9 @@
package job
import (
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
@@ -8,8 +11,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"sync"
"time"
)
type website struct{}
@@ -19,7 +20,7 @@ func NewWebsiteJob() *website {
}
func (w *website) Run() {
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
websites, _ := repo.NewIWebsiteRepo().List()
global.LOG.Info("Website scheduled task in progress ...")
now := time.Now().Add(10 * time.Minute)
+9
View File
@@ -94,6 +94,7 @@ ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
ErrDomainFormat: "{{ .name }} domain format error"
ErrDefaultAlias: "default is a reserved code name, please use another code name"
ErrImageNotExist: "Running environment {{.name}} image does not exist, please re-edit the running environment"
#ssl
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
@@ -196,3 +197,11 @@ ErrLicenseSync: "Failed to sync license information, no license information dete
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
#license
ErrAlert: "Alert information format error, please check and try again!"
ErrAlertPush: "Alert push error, please check and try again!"
ErrAlertSave: "Alert save error, please check and try again!"
ErrAlertSync: "Alert sync error, please check and try again!"
ErrAlertRemote: "Remote alert error, please check and try again!"
+8
View File
@@ -94,6 +94,7 @@ ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
ErrDomainFormat: "{{ .name }} 域名格式不正確"
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
ErrImageNotExist: "執行環境 {{.name}} 鏡像不存在,請重新編輯執行環境"
#ssl
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
@@ -198,3 +199,10 @@ ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
#license
ErrAlert: "告警資訊格式錯誤,請檢查後重試!"
ErrAlertPush: "告警資訊推送錯誤,請檢查後重試!"
ErrAlertSave: "告警資訊保存錯誤,請檢查後重試!"
ErrAlertSync: "告警資訊同步錯誤,請檢查後重試!"
ErrAlertRemote: "告警資訊遠端錯誤,請檢查後重試!"
+8
View File
@@ -94,6 +94,7 @@ ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
ErrDomainFormat: "{{ .name }} 域名格式不正确"
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
ErrImageNotExist: "运行环境 {{.name}} 镜像不存在,请重新编辑运行环境"
#ssl
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
@@ -200,3 +201,10 @@ ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
#license
ErrAlert: "告警信息格式错误,请检查后重试!"
ErrAlertPush: "告警信息推送错误,请检查后重试!"
ErrAlertSave: "告警信息保存错误,请检查后重试!"
ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
+3 -1
View File
@@ -1,6 +1,7 @@
package cache
import (
"os"
"time"
"github.com/1Panel-dev/1Panel/backend/global"
@@ -10,7 +11,8 @@ import (
func Init() {
c := global.CONF.System.Cache
_ = os.RemoveAll(c)
_ = os.Mkdir(c, 0755)
options := badger.Options{
Dir: c,
ValueDir: c,
+1
View File
@@ -93,6 +93,7 @@ func Init() {
migrations.AddShellColumn,
migrations.AddClam,
migrations.AddClamStatus,
migrations.AddAlertMenu,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -288,3 +288,29 @@ var AddClamStatus = &gormigrate.Migration{
return nil
},
}
var AddAlertMenu = &gormigrate.Migration{
ID: "20240706-update-xpack-hide-menu",
Migrate: func(tx *gorm.DB) error {
var (
setting model.Setting
menu dto.XpackHideMenu
)
tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").First(&setting)
if err := json.Unmarshal([]byte(setting.Value), &menu); err != nil {
return err
}
menu.Children = append(menu.Children, dto.XpackHideMenu{
ID: "7",
Title: "xpack.alert.alert",
Path: "/xpack/alert/dashboard",
Label: "XAlertDashboard",
IsCheck: true,
})
data, err := json.Marshal(menu)
if err != nil {
return err
}
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
},
}
+18 -7
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"reflect"
"strings"
"time"
@@ -96,10 +97,11 @@ func OperationLog() gin.HandlerFunc {
if funcs.InputValue == key {
var names []string
if funcs.IsList {
sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
_ = global.DB.Raw(sql, value).Scan(&names)
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s in (?)", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
_ = global.DB.Raw(query, value).Scan(&names)
} else {
_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
_ = global.DB.Raw(query, value).Scan(&names)
}
formatMap[funcs.OutputValue] = strings.Join(names, ",")
break
@@ -109,12 +111,21 @@ func OperationLog() gin.HandlerFunc {
}
for key, value := range formatMap {
if strings.Contains(operationDic.FormatEN, "["+key+"]") {
if arrays, ok := value.([]string); ok {
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
} else {
t := reflect.TypeOf(value)
if t.Kind() != reflect.Array && t.Kind() != reflect.Slice {
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value))
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value))
} else {
val := reflect.ValueOf(value)
length := val.Len()
var elements []string
for i := 0; i < length; i++ {
element := val.Index(i).Interface().(string)
elements = append(elements, element)
}
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ",")))
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ",")))
}
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ func PasswordExpired() gin.HandlerFunc {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
return
}
loc, _ := time.LoadLocation(common.LoadTimeZone())
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, extime.Value, loc)
if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
+1 -1
View File
@@ -18,7 +18,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
{
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
cmdRouter.POST("/current", baseApi.LoadDashboardCurrentInfo)
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
}
}
+24 -7
View File
@@ -256,13 +256,6 @@ func LoadSizeUnit2F(value float64) string {
return fmt.Sprintf("%.2f", value)
}
func LoadTimeZone() string {
loc := time.Now().Location()
if _, err := time.LoadLocation(loc.String()); err != nil {
return "Asia/Shanghai"
}
return loc.String()
}
func LoadTimeZoneByCmd() string {
loc := time.Now().Location().String()
if _, err := time.LoadLocation(loc); err != nil {
@@ -325,3 +318,27 @@ func SplitStr(str string, spi ...string) []string {
func IsValidIP(ip string) bool {
return net.ParseIP(ip) != nil
}
const (
b = uint64(1)
kb = 1024 * b
mb = 1024 * kb
gb = 1024 * mb
)
func FormatBytes(bytes uint64) string {
switch {
case bytes < kb:
return fmt.Sprintf("%dB", bytes)
case bytes < mb:
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
case bytes < gb:
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
default:
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
}
}
func FormatPercent(percent float64) string {
return fmt.Sprintf("%.2f%%", percent)
}
+3
View File
@@ -613,6 +613,9 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secret string) error {
if cType == Tar || cType == Zip || cType == TarGz {
shellArchiver, err := NewShellArchiver(cType)
if !f.Stat(dst) {
_ = f.CreateDir(dst, 0755)
}
if err == nil {
if err = shellArchiver.Extract(srcFile, dst, secret); err == nil {
return nil
+43 -2
View File
@@ -72,6 +72,9 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
info, err := appFs.Stat(op.Path)
if err != nil {
if os.IsNotExist(err) {
return nil, buserr.New(constant.ErrLinkPathNotFound)
}
return nil, err
}
@@ -101,7 +104,26 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
}
if file.IsSymlink {
file.LinkPath = GetSymlink(op.Path)
linkPath := GetSymlink(op.Path)
if !filepath.IsAbs(linkPath) {
dir := filepath.Dir(op.Path)
var err error
linkPath, err = filepath.Abs(filepath.Join(dir, linkPath))
if err != nil {
return nil, err
}
}
file.LinkPath = linkPath
targetInfo, err := appFs.Stat(linkPath)
if err != nil {
file.IsDir = false
file.Mode = "-"
file.User = "-"
file.Group = "-"
} else {
file.IsDir = targetInfo.IsDir()
}
file.Extension = filepath.Ext(file.LinkPath)
}
if op.Expand {
if err := handleExpansion(file, op); err != nil {
@@ -308,7 +330,26 @@ func (f *FileInfo) processFiles(files []FileSearchInfo, option FileOption) ([]*F
file.FavoriteID = favorite.ID
}
if isSymlink {
file.LinkPath = GetSymlink(fPath)
linkPath := GetSymlink(fPath)
if !filepath.IsAbs(linkPath) {
dir := filepath.Dir(fPath)
var err error
linkPath, err = filepath.Abs(filepath.Join(dir, linkPath))
if err != nil {
return nil, err
}
}
file.LinkPath = linkPath
targetInfo, err := file.Fs.Stat(linkPath)
if err != nil {
file.IsDir = false
file.Mode = "-"
file.User = "-"
file.Group = "-"
} else {
file.IsDir = targetInfo.IsDir()
}
file.Extension = filepath.Ext(file.LinkPath)
}
if df.Size() > 0 {
file.MimeType = GetMimeType(fPath)
+1 -1
View File
@@ -18,7 +18,7 @@ func NewTarArchiver(compressType CompressType) ShellArchiver {
}
func (t TarArchiver) Extract(FilePath string, dstDir string, secret string) error {
return cmd.ExecCmd(fmt.Sprintf("%s %s %s -C %s", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir))
return cmd.ExecCmd(fmt.Sprintf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir))
}
func (t TarArchiver) Compress(sourcePaths []string, dstFile string, secret string) error {
+11 -13
View File
@@ -20,11 +20,11 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
var err error
commands := ""
if len(secret) != 0 {
extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + filePath + " | "
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, dstDir+" > /dev/null 2>&1")
extraCmd := fmt.Sprintf("openssl enc -d -aes-256-cbc -k '%s' -in '%s' | ", secret, filePath)
commands = fmt.Sprintf("%s tar -zxvf - -C '%s' > /dev/null 2>&1", extraCmd, dstDir)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
commands = fmt.Sprintf("tar -zxvf %s %s", filePath+" -C ", dstDir+" > /dev/null 2>&1")
commands = fmt.Sprintf("tar -zxvf '%s' -C '%s' > /dev/null 2>&1", filePath, dstDir)
global.LOG.Debug(commands)
}
if err = cmd.ExecCmd(commands); err != nil {
@@ -34,27 +34,25 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
}
func (t TarGzArchiver) Compress(sourcePaths []string, dstFile string, secret string) error {
var err error
path := ""
itemDir := ""
var itemDirs []string
for _, item := range sourcePaths {
itemDir += filepath.Base(item) + " "
itemDirs = append(itemDirs, fmt.Sprintf("\"%s\"", filepath.Base(item)))
}
aheadDir := dstFile[:strings.LastIndex(dstFile, "/")]
itemDir := strings.Join(itemDirs, " ")
aheadDir := filepath.Dir(sourcePaths[0])
if len(aheadDir) == 0 {
aheadDir = "/"
}
path += fmt.Sprintf("- -C %s %s", aheadDir, itemDir)
commands := ""
if len(secret) != 0 {
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
commands = fmt.Sprintf("tar -zcf %s %s %s", path, extraCmd, dstFile)
extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, dstFile)
commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
commands = fmt.Sprintf("tar -zcf %s -C %s %s", dstFile, aheadDir, itemDir)
commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", dstFile, aheadDir, itemDir)
global.LOG.Debug(commands)
}
if err = cmd.ExecCmd(commands); err != nil {
if err := cmd.ExecCmd(commands); err != nil {
return err
}
return nil
+14 -9
View File
@@ -72,16 +72,21 @@ func countLines(path string) (int, error) {
return 0, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineCount := 0
for scanner.Scan() {
lineCount++
reader := bufio.NewReader(file)
count := 0
for {
_, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
if count > 0 {
count++
}
return count, nil
}
return count, err
}
count++
}
if err := scanner.Err(); err != nil {
return 0, err
}
return lineCount, nil
}
func ReadFileByLine(filename string, page, pageSize int, latest bool) (lines []string, isEndOfFile bool, total int, err error) {
+4
View File
@@ -2,6 +2,7 @@ package http
import (
"context"
"errors"
"io"
"net/http"
"time"
@@ -35,6 +36,9 @@ func HandleGetWithTransport(url, method string, transport *http.Transport, timeo
if err != nil {
return 0, nil, err
}
if resp.StatusCode != http.StatusOK {
return 0, nil, errors.New(resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, nil, err
+2 -3
View File
@@ -40,7 +40,6 @@ func ValidCode(code, intervalStr, secret string) bool {
}
totp := gotp.NewTOTP(secret, 6, interval, nil)
now := time.Now().Unix()
strInt64 := strconv.FormatInt(now, 10)
id16, _ := strconv.Atoi(strInt64)
return totp.Verify(code, int64(id16))
prevTime := now - int64(interval)
return totp.Verify(code, now) || totp.Verify(code, prevTime)
}
-303
View File
@@ -1,303 +0,0 @@
package helper
import (
"bufio"
"database/sql"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
_ "github.com/go-sql-driver/mysql"
)
func init() {}
type dumpOption struct {
isData bool
tables []string
isAllTable bool
isDropTable bool
writer io.Writer
}
type DumpOption func(*dumpOption)
func WithDropTable() DumpOption {
return func(option *dumpOption) {
option.isDropTable = true
}
}
func WithData() DumpOption {
return func(option *dumpOption) {
option.isData = true
}
}
func WithWriter(writer io.Writer) DumpOption {
return func(option *dumpOption) {
option.writer = writer
}
}
func Dump(dns string, opts ...DumpOption) error {
start := time.Now()
global.LOG.Infof("dump start at %s\n", start.Format(constant.DateTimeLayout))
defer func() {
end := time.Now()
global.LOG.Infof("dump end at %s, cost %s\n", end.Format(constant.DateTimeLayout), end.Sub(start))
}()
var err error
var o dumpOption
for _, opt := range opts {
opt(&o)
}
if len(o.tables) == 0 {
o.isAllTable = true
}
if o.writer == nil {
o.writer = os.Stdout
}
buf := bufio.NewWriter(o.writer)
defer buf.Flush()
itemFile, lineNumber := "", 0
itemFile += "-- ----------------------------\n"
itemFile += "-- MySQL Database Dump\n"
itemFile += "-- Start Time: " + start.Format(constant.DateTimeLayout) + "\n"
itemFile += "-- ----------------------------\n\n\n"
db, err := sql.Open("mysql", dns)
if err != nil {
global.LOG.Errorf("open mysql db failed, err: %v", err)
return err
}
defer db.Close()
dbName, err := getDBNameFromDNS(dns)
if err != nil {
global.LOG.Errorf("get db name from dns failed, err: %v", err)
return err
}
_, err = db.Exec(fmt.Sprintf("USE `%s`", dbName))
if err != nil {
global.LOG.Errorf("exec `use %s` failed, err: %v", dbName, err)
return err
}
var tables []string
if o.isAllTable {
tmp, err := getAllTables(db)
if err != nil {
global.LOG.Errorf("get all tables failed, err: %v", err)
return err
}
tables = tmp
} else {
tables = o.tables
}
for _, table := range tables {
if o.isDropTable {
itemFile += fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table)
}
itemFile += "-- ----------------------------\n"
itemFile += fmt.Sprintf("-- Table structure for %s\n", table)
itemFile += "-- ----------------------------\n"
createTableSQL, err := getCreateTableSQL(db, table)
if err != nil {
global.LOG.Errorf("get create table sql failed, err: %v", err)
return err
}
itemFile += createTableSQL
itemFile += ";\n\n\n\n"
if o.isData {
itemFile += "-- ----------------------------\n"
itemFile += fmt.Sprintf("-- Records of %s\n", table)
itemFile += "-- ----------------------------\n"
lineRows, err := db.Query(fmt.Sprintf("SELECT * FROM `%s`", table))
if err != nil {
global.LOG.Errorf("exec `select * from %s` failed, err: %v", table, err)
return err
}
defer lineRows.Close()
var columns []string
columns, err = lineRows.Columns()
if err != nil {
global.LOG.Errorf("get columes failed, err: %v", err)
return err
}
columnTypes, err := lineRows.ColumnTypes()
if err != nil {
global.LOG.Errorf("get colume types failed, err: %v", err)
return err
}
for lineRows.Next() {
row := make([]interface{}, len(columns))
rowPointers := make([]interface{}, len(columns))
for i := range columns {
rowPointers[i] = &row[i]
}
if err = lineRows.Scan(rowPointers...); err != nil {
global.LOG.Errorf("scan row data failed, err: %v", err)
return err
}
ssql := loadDataSql(row, columnTypes, table)
if len(ssql) != 0 {
itemFile += ssql
lineNumber++
}
if lineNumber > 500 {
_, _ = buf.WriteString(itemFile)
itemFile = ""
lineNumber = 0
}
}
itemFile += "\n\n"
}
}
itemFile += "-- ----------------------------\n"
itemFile += "-- Dumped by mysqldump\n"
itemFile += "-- Cost Time: " + time.Since(start).String() + "\n"
itemFile += "-- ----------------------------\n"
_, _ = buf.WriteString(itemFile)
_ = buf.Flush()
return nil
}
func getCreateTableSQL(db *sql.DB, table string) (string, error) {
var createTableSQL string
err := db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`", table)).Scan(&table, &createTableSQL)
if err != nil {
return "", err
}
createTableSQL = strings.Replace(createTableSQL, "CREATE TABLE", "CREATE TABLE IF NOT EXISTS", 1)
return createTableSQL, nil
}
func getAllTables(db *sql.DB) ([]string, error) {
var tables []string
rows, err := db.Query("SHOW TABLES")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var table string
err = rows.Scan(&table)
if err != nil {
return nil, err
}
tables = append(tables, table)
}
return tables, nil
}
func loadDataSql(row []interface{}, columnTypes []*sql.ColumnType, table string) string {
ssql := "INSERT INTO `" + table + "` VALUES ("
for i, col := range row {
if col == nil {
ssql += "NULL"
} else {
Type := columnTypes[i].DatabaseTypeName()
Type = strings.Replace(Type, "UNSIGNED", "", -1)
Type = strings.Replace(Type, " ", "", -1)
switch Type {
case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER", "BIGINT":
if bs, ok := col.([]byte); ok {
ssql += string(bs)
} else {
ssql += fmt.Sprintf("%d", col)
}
case "FLOAT", "DOUBLE":
if bs, ok := col.([]byte); ok {
ssql += string(bs)
} else {
ssql += fmt.Sprintf("%f", col)
}
case "DECIMAL", "DEC":
ssql += fmt.Sprintf("%s", col)
case "DATE":
t, ok := col.(time.Time)
if !ok {
global.LOG.Errorf("the DATE type conversion failed, err value: %v", col)
return ""
}
ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02"))
case "DATETIME":
t, ok := col.(time.Time)
if !ok {
global.LOG.Errorf("the DATETIME type conversion failed, err value: %v", col)
return ""
}
ssql += fmt.Sprintf("'%s'", t.Format(constant.DateTimeLayout))
case "TIMESTAMP":
t, ok := col.(time.Time)
if !ok {
global.LOG.Errorf("the TIMESTAMP type conversion failed, err value: %v", col)
return ""
}
ssql += fmt.Sprintf("'%s'", t.Format(constant.DateTimeLayout))
case "TIME":
t, ok := col.([]byte)
if !ok {
global.LOG.Errorf("the TIME type conversion failed, err value: %v", col)
return ""
}
ssql += fmt.Sprintf("'%s'", string(t))
case "YEAR":
t, ok := col.([]byte)
if !ok {
global.LOG.Errorf("the YEAR type conversion failed, err value: %v", col)
return ""
}
ssql += string(t)
case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT":
ssql += fmt.Sprintf("'%s'", strings.Replace(fmt.Sprintf("%s", col), "'", "''", -1))
case "BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB":
ssql += fmt.Sprintf("0x%X", col)
case "ENUM", "SET":
ssql += fmt.Sprintf("'%s'", col)
case "BOOL", "BOOLEAN":
if col.(bool) {
ssql += "true"
} else {
ssql += "false"
}
case "JSON":
ssql += fmt.Sprintf("'%s'", col)
default:
global.LOG.Errorf("unsupported colume type: %s", Type)
return ""
}
}
if i < len(row)-1 {
ssql += ","
}
}
ssql += ");\n"
return ssql
}
-244
View File
@@ -1,244 +0,0 @@
package helper
import (
"bufio"
"database/sql"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
)
type sourceOption struct {
dryRun bool
mergeInsert int
debug bool
}
type SourceOption func(*sourceOption)
func WithMergeInsert(size int) SourceOption {
return func(o *sourceOption) {
o.mergeInsert = size
}
}
type dbWrapper struct {
DB *sql.DB
debug bool
dryRun bool
}
func newDBWrapper(db *sql.DB, dryRun, debug bool) *dbWrapper {
return &dbWrapper{
DB: db,
dryRun: dryRun,
debug: debug,
}
}
func (db *dbWrapper) Exec(query string, args ...interface{}) (sql.Result, error) {
if db.debug {
global.LOG.Debugf("query %s", query)
}
if db.dryRun {
return nil, nil
}
return db.DB.Exec(query, args...)
}
func Source(dns string, reader io.Reader, opts ...SourceOption) error {
start := time.Now()
global.LOG.Infof("source start at %s", start.Format(constant.DateTimeLayout))
defer func() {
end := time.Now()
global.LOG.Infof("source end at %s, cost %s", end.Format(constant.DateTimeLayout), end.Sub(start))
}()
var err error
var db *sql.DB
var o sourceOption
for _, opt := range opts {
opt(&o)
}
dbName, err := getDBNameFromDNS(dns)
if err != nil {
global.LOG.Errorf("get db name from dns failed, err: %v", err)
return err
}
db, err = sql.Open("mysql", dns)
if err != nil {
global.LOG.Errorf("open mysql db failed, err: %v", err)
return err
}
defer db.Close()
dbWrapper := newDBWrapper(db, o.dryRun, o.debug)
_, err = dbWrapper.Exec(fmt.Sprintf("USE `%s`;", dbName))
if err != nil {
global.LOG.Errorf("exec `use %s` failed, err: %v", dbName, err)
return err
}
db.SetConnMaxLifetime(3600)
r := bufio.NewReader(reader)
_, err = dbWrapper.Exec("SET autocommit=0;")
if err != nil {
global.LOG.Errorf("exec `set autocommit=0` failed, err: %v", err)
return err
}
for {
line, err := readLine(r)
if err != nil {
if err == io.EOF {
break
}
global.LOG.Errorf("read sql failed, err: %v", err)
return err
}
ssql, err := trim(line)
if err != nil {
global.LOG.Errorf("trim sql failed, err: %v", err)
return err
}
afterInsertSql := ""
if o.mergeInsert > 1 && strings.HasPrefix(ssql, "INSERT INTO") {
var insertSQLs []string
insertSQLs = append(insertSQLs, ssql)
for i := 0; i < o.mergeInsert-1; i++ {
line, err := readLine(r)
if err != nil {
if err == io.EOF {
break
}
return err
}
ssql2, err := trim(line)
if err != nil {
global.LOG.Errorf("trim merge insert sql failed, err: %v", err)
return err
}
if strings.HasPrefix(ssql2, "INSERT INTO") {
insertSQLs = append(insertSQLs, ssql2)
continue
}
afterInsertSql = ssql2
break
}
ssql, err = mergeInsert(insertSQLs)
if err != nil {
global.LOG.Errorf("do merge insert failed, err: %v", err)
return err
}
}
_, err = dbWrapper.Exec(ssql)
if err != nil {
global.LOG.Errorf("exec sql failed, err: %v", err)
return err
}
if len(afterInsertSql) != 0 {
_, err = dbWrapper.Exec(afterInsertSql)
if err != nil {
global.LOG.Errorf("exec sql failed, err: %v", err)
return err
}
}
}
_, err = dbWrapper.Exec("COMMIT;")
if err != nil {
global.LOG.Errorf("exec `commit` failed, err: %v", err)
return err
}
_, err = dbWrapper.Exec("SET autocommit=1;")
if err != nil {
global.LOG.Errorf("exec `autocommit=1` failed, err: %v", err)
return err
}
return nil
}
func mergeInsert(insertSQLs []string) (string, error) {
if len(insertSQLs) == 0 {
return "", errors.New("no input provided")
}
builder := strings.Builder{}
sql1 := insertSQLs[0]
sql1 = strings.TrimSuffix(sql1, ";")
builder.WriteString(sql1)
for i, insertSQL := range insertSQLs[1:] {
if i < len(insertSQLs)-1 {
builder.WriteString(",")
}
valuesIdx := strings.Index(insertSQL, "VALUES")
if valuesIdx == -1 {
return "", errors.New("invalid SQL: missing VALUES keyword")
}
sqln := insertSQL[valuesIdx:]
sqln = strings.TrimPrefix(sqln, "VALUES")
sqln = strings.TrimSuffix(sqln, ";")
builder.WriteString(sqln)
}
builder.WriteString(";")
return builder.String(), nil
}
func trim(s string) (string, error) {
s = strings.TrimLeft(s, "\n")
s = strings.TrimSpace(s)
return s, nil
}
func getDBNameFromDNS(dns string) (string, error) {
ss1 := strings.Split(dns, "/")
if len(ss1) == 2 {
ss2 := strings.Split(ss1[1], "?")
if len(ss2) == 2 {
return ss2[0], nil
}
}
return "", fmt.Errorf("dns error: %s", dns)
}
func readLine(r *bufio.Reader) (string, error) {
lineItem, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
return lineItem, err
}
global.LOG.Errorf("read merge insert sql failed, err: %v", err)
return "", err
}
if strings.HasSuffix(lineItem, ";\n") {
return lineItem, nil
}
lineAppend, err := readLine(r)
if err != nil {
if err == io.EOF {
return lineItem, err
}
global.LOG.Errorf("read merge insert sql failed, err: %v", err)
return "", err
}
return lineItem + lineAppend, nil
}
+6 -1
View File
@@ -4,6 +4,7 @@ import (
"crypto"
"encoding/json"
"os"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
@@ -292,8 +293,12 @@ func (c *AcmeClient) GetDNSResolve(domains []string) (map[string]Resolve, error)
continue
}
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
fqdn := challengeInfo.FQDN
if strings.HasPrefix(domain, "*.") && strings.Contains(fqdn, "*.") {
fqdn = strings.Replace(fqdn, "*.", "", 1)
}
resolves[domain] = Resolve{
Key: challengeInfo.FQDN,
Key: fqdn,
Value: challengeInfo.Value,
}
}
+10 -29
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/net"
@@ -147,26 +148,6 @@ func getDownloadProcess(progress DownloadProgress) (res []byte, err error) {
return
}
const (
b = uint64(1)
kb = 1024 * b
mb = 1024 * kb
gb = 1024 * mb
)
func formatBytes(bytes uint64) string {
switch {
case bytes < kb:
return fmt.Sprintf("%dB", bytes)
case bytes < mb:
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
case bytes < gb:
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
default:
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
}
}
func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
var processes []*process.Process
processes, err = process.Processes()
@@ -230,14 +211,14 @@ func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
menInfo, procErr := proc.MemoryInfo()
if procErr == nil {
procData.Rss = formatBytes(menInfo.RSS)
procData.Rss = common.FormatBytes(menInfo.RSS)
procData.RssValue = menInfo.RSS
procData.Data = formatBytes(menInfo.Data)
procData.VMS = formatBytes(menInfo.VMS)
procData.HWM = formatBytes(menInfo.HWM)
procData.Stack = formatBytes(menInfo.Stack)
procData.Locked = formatBytes(menInfo.Locked)
procData.Swap = formatBytes(menInfo.Swap)
procData.Data = common.FormatBytes(menInfo.Data)
procData.VMS = common.FormatBytes(menInfo.VMS)
procData.HWM = common.FormatBytes(menInfo.HWM)
procData.Stack = common.FormatBytes(menInfo.Stack)
procData.Locked = common.FormatBytes(menInfo.Locked)
procData.Swap = common.FormatBytes(menInfo.Swap)
} else {
procData.Rss = "--"
procData.Data = "--"
@@ -251,8 +232,8 @@ func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
}
ioStat, procErr := proc.IOCounters()
if procErr == nil {
procData.DiskWrite = formatBytes(ioStat.WriteBytes)
procData.DiskRead = formatBytes(ioStat.ReadBytes)
procData.DiskWrite = common.FormatBytes(ioStat.WriteBytes)
procData.DiskRead = common.FormatBytes(ioStat.ReadBytes)
} else {
procData.DiskWrite = "--"
procData.DiskRead = "--"
+4
View File
@@ -32,6 +32,10 @@ func LoadGpuInfo() []interface{} {
return nil
}
func LoadXpuInfo() []interface{} {
return nil
}
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New(constant.ErrXpackNotFound)
}
+43 -22
View File
@@ -1,5 +1,4 @@
// Code generated by swaggo/swag. DO NOT EDIT.
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
@@ -216,28 +215,12 @@ const docTemplate = `{
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "app_installs",
"input_column": "name",
"input_value": "name",
"isList": false,
"output_column": "app_id",
"output_value": "appId"
},
{
"db": "apps",
"info": "appId",
"isList": false,
"output_column": "key",
"output_value": "appKey"
}
],
"BeforeFunctions": [],
"bodyKeys": [
"name"
],
"formatEN": "Install app [appKey]-[name]",
"formatZH": "安装应用 [appKey]-[name]",
"formatEN": "Install app [name]",
"formatZH": "安装应用 [name]",
"paramKeys": []
}
}
@@ -11486,7 +11469,7 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SearchWithPage"
"$ref": "#/definitions/dto.SearchClamWithPage"
}
}
],
@@ -19321,6 +19304,42 @@ const docTemplate = `{
}
}
},
"dto.SearchClamWithPage": {
"type": "object",
"required": [
"order",
"orderBy",
"page",
"pageSize"
],
"properties": {
"info": {
"type": "string"
},
"order": {
"type": "string",
"enum": [
"null",
"ascending",
"descending"
]
},
"orderBy": {
"type": "string",
"enum": [
"name",
"status",
"created_at"
]
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.SearchForTree": {
"type": "object",
"properties": {
@@ -23457,6 +23476,8 @@ var SwaggerInfo = &swag.Spec{
Description: "开源Linux面板",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
+40 -20
View File
@@ -209,28 +209,12 @@
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "app_installs",
"input_column": "name",
"input_value": "name",
"isList": false,
"output_column": "app_id",
"output_value": "appId"
},
{
"db": "apps",
"info": "appId",
"isList": false,
"output_column": "key",
"output_value": "appKey"
}
],
"BeforeFunctions": [],
"bodyKeys": [
"name"
],
"formatEN": "Install app [appKey]-[name]",
"formatZH": "安装应用 [appKey]-[name]",
"formatEN": "Install app [name]",
"formatZH": "安装应用 [name]",
"paramKeys": []
}
}
@@ -11479,7 +11463,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SearchWithPage"
"$ref": "#/definitions/dto.SearchClamWithPage"
}
}
],
@@ -19314,6 +19298,42 @@
}
}
},
"dto.SearchClamWithPage": {
"type": "object",
"required": [
"order",
"orderBy",
"page",
"pageSize"
],
"properties": {
"info": {
"type": "string"
},
"order": {
"type": "string",
"enum": [
"null",
"ascending",
"descending"
]
},
"orderBy": {
"type": "string",
"enum": [
"name",
"status",
"created_at"
]
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.SearchForTree": {
"type": "object",
"properties": {
+30 -15
View File
@@ -2748,6 +2748,32 @@ definitions:
- ssl
- sslType
type: object
dto.SearchClamWithPage:
properties:
info:
type: string
order:
enum:
- "null"
- ascending
- descending
type: string
orderBy:
enum:
- name
- status
- created_at
type: string
page:
type: integer
pageSize:
type: integer
required:
- order
- orderBy
- page
- pageSize
type: object
dto.SearchForTree:
properties:
info:
@@ -5633,22 +5659,11 @@ paths:
tags:
- App
x-panel-log:
BeforeFunctions:
- db: app_installs
input_column: name
input_value: name
isList: false
output_column: app_id
output_value: appId
- db: apps
info: appId
isList: false
output_column: key
output_value: appKey
BeforeFunctions: []
bodyKeys:
- name
formatEN: Install app [appKey]-[name]
formatZH: 安装应用 [appKey]-[name]
formatEN: Install app [name]
formatZH: 安装应用 [name]
paramKeys: []
/apps/installed/check:
post:
@@ -12769,7 +12784,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/dto.SearchWithPage'
$ref: '#/definitions/dto.SearchClamWithPage'
responses:
"200":
description: OK
+3 -2
View File
@@ -5,9 +5,10 @@ location ^~ /test {
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_set_header Connection $http_connection;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
proxy_ssl_server_name off;
}
+1 -1
View File
@@ -10,7 +10,7 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Connection $http_connection;
access_log /www/sites/domain/log/access.log main;
+3 -3
View File
@@ -37,11 +37,11 @@
"codemirror": "^6.0.1",
"echarts": "^5.5.0",
"element-plus": "^2.7.5",
"fit2cloud-ui-plus": "^1.1.4",
"fit2cloud-ui-plus": "^1.1.6",
"highlight.js": "^11.9.0",
"js-base64": "^3.7.7",
"md-editor-v3": "^2.11.3",
"monaco-editor": "^0.34.1",
"monaco-editor": "^0.51.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^1.6.1",
@@ -72,7 +72,7 @@
"postcss-html": "^1.4.1",
"prettier": "^2.6.2",
"rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.49.7",
"sass": "^1.77.8",
"standard-version": "^9.5.0",
"stylelint": "^15.10.1",
"tailwindcss": "^3.4.1",
+1
View File
@@ -6,6 +6,7 @@ export namespace Backup {
type: string;
accessKey: string;
bucket: string;
bucketInput: boolean;
credential: string;
backupPath: string;
vars: string;
+3
View File
@@ -24,6 +24,9 @@ export namespace Cronjob {
backupAccounts: string;
defaultDownload: string;
backupAccountList: Array<string>;
appIdList: Array<string>;
websiteList: Array<string>;
dbNameList: Array<string>;
retainCopies: number;
status: string;
secret: string;
+19
View File
@@ -22,6 +22,8 @@ export namespace Dashboard {
kernelArch: string;
kernelVersion: string;
virtualizationSystem: string;
ipv4Addr: string;
systemProxy: string;
cpuCores: number;
cpuLogicalCores: number;
@@ -62,6 +64,7 @@ export namespace Dashboard {
diskData: Array<DiskInfo>;
gpuData: Array<GPUInfo>;
xpuData: Array<XPUInfo>;
netBytesSent: number;
netBytesRecv: number;
@@ -92,4 +95,20 @@ export namespace Dashboard {
memoryUsage: string;
fanSpeed: string;
}
export interface XPUInfo {
deviceID: number;
deviceName: string;
memory: string;
temperature: string;
memoryUsed: string;
power: string;
memoryUtil: string;
}
export interface DashboardReq {
scope: string;
ioOption: string;
netOption: string;
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ export namespace File {
size: number;
isDir: boolean;
isSymlink: boolean;
linkPath: boolean;
linkPath: string;
type: string;
updateTime: string;
modTime: string;
+1
View File
@@ -31,6 +31,7 @@ export namespace Website {
sitePath: string;
appName: string;
runtimeName: string;
runtimeType: string;
}
export interface WebsiteRes extends CommonModel {
protocol: string;
+2 -2
View File
@@ -9,8 +9,8 @@ export const loadBaseInfo = (ioOption: string, netOption: string) => {
return http.get<Dashboard.BaseInfo>(`/dashboard/base/${ioOption}/${netOption}`);
};
export const loadCurrentInfo = (ioOption: string, netOption: string) => {
return http.get<Dashboard.CurrentInfo>(`/dashboard/current/${ioOption}/${netOption}`);
export const loadCurrentInfo = (req: Dashboard.DashboardReq) => {
return http.post<Dashboard.CurrentInfo>(`/dashboard/current`, req);
};
export const systemRestart = (operation: string) => {
+48 -36
View File
@@ -1,9 +1,9 @@
@font-face {
font-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1717570629440') format('woff2'),
url('iconfont.woff?t=1717570629440') format('woff'),
url('iconfont.ttf?t=1717570629440') format('truetype'),
url('iconfont.svg?t=1717570629440#panel') format('svg');
src: url('iconfont.woff2?t=1724290117056') format('woff2'),
url('iconfont.woff?t=1724290117056') format('woff'),
url('iconfont.ttf?t=1724290117056') format('truetype'),
url('iconfont.svg?t=1724290117056#panel') format('svg');
}
.panel {
@@ -14,6 +14,50 @@
-moz-osx-font-smoothing: grayscale;
}
.p-alert-1:before {
content: "\e611";
}
.p-alert-2:before {
content: "\e701";
}
.p-aws:before {
content: "\e600";
}
.p-tengxunyun1:before {
content: "\e651";
}
.p-qiniuyun:before {
content: "\e62c";
}
.p-minio:before {
content: "\e63c";
}
.p-onedrive:before {
content: "\e601";
}
.p-oss:before {
content: "\e607";
}
.p-SFTP:before {
content: "\e647";
}
.p-webdav:before {
content: "\e622";
}
.p-zhongduan:before {
content: "\e731";
}
.p-docker1:before {
content: "\e76a";
}
@@ -178,18 +222,10 @@
content: "\e856";
}
.p-webdav:before {
content: "\e622";
}
.p-xiangqing:before {
content: "\e677";
}
.p-onedrive:before {
content: "\e601";
}
.p-caidan:before {
content: "\e61d";
}
@@ -198,14 +234,6 @@
content: "\e744";
}
.p-tengxunyun1:before {
content: "\e651";
}
.p-qiniuyun:before {
content: "\e62c";
}
.p-file-png:before {
content: "\e7ae";
}
@@ -250,10 +278,6 @@
content: "\e60f";
}
.p-aws:before {
content: "\e600";
}
.p-taolun:before {
content: "\e602";
}
@@ -262,22 +286,10 @@
content: "\e616";
}
.p-SFTP:before {
content: "\e647";
}
.p-huaban88:before {
content: "\e67c";
}
.p-oss:before {
content: "\e607";
}
.p-minio:before {
content: "\e63c";
}
.p-logout:before {
content: "\e8fe";
}
File diff suppressed because one or more lines are too long
+77 -56
View File
@@ -5,6 +5,83 @@
"css_prefix_text": "p-",
"description": "",
"glyphs": [
{
"icon_id": "29851742",
"name": "短信告警",
"font_class": "alert-1",
"unicode": "e611",
"unicode_decimal": 58897
},
{
"icon_id": "33427786",
"name": "告警日志",
"font_class": "alert-2",
"unicode": "e701",
"unicode_decimal": 59137
},
{
"icon_id": "32101973",
"name": "Amazon_Web_Services_Logo",
"font_class": "aws",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "12959160",
"name": "腾讯云",
"font_class": "tengxunyun1",
"unicode": "e651",
"unicode_decimal": 58961
},
{
"icon_id": "24877229",
"name": "七牛云",
"font_class": "qiniuyun",
"unicode": "e62c",
"unicode_decimal": 58924
},
{
"icon_id": "20290513",
"name": "minio",
"font_class": "minio",
"unicode": "e63c",
"unicode_decimal": 58940
},
{
"icon_id": "13015332",
"name": "onedrive",
"font_class": "onedrive",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "16268521",
"name": "oss",
"font_class": "oss",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "13532955",
"name": "SFTP",
"font_class": "SFTP",
"unicode": "e647",
"unicode_decimal": 58951
},
{
"icon_id": "23044673",
"name": "webdav",
"font_class": "webdav",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "30053592",
"name": "终端",
"font_class": "zhongduan",
"unicode": "e731",
"unicode_decimal": 59185
},
{
"icon_id": "1064806",
"name": "docker",
@@ -292,13 +369,6 @@
"unicode": "e856",
"unicode_decimal": 59478
},
{
"icon_id": "23044673",
"name": "webdav",
"font_class": "webdav",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "10293150",
"name": "详情",
@@ -306,13 +376,6 @@
"unicode": "e677",
"unicode_decimal": 58999
},
{
"icon_id": "13015332",
"name": "onedrive",
"font_class": "onedrive",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "7708032",
"name": "菜单",
@@ -327,20 +390,6 @@
"unicode": "e744",
"unicode_decimal": 59204
},
{
"icon_id": "12959160",
"name": "腾讯云",
"font_class": "tengxunyun1",
"unicode": "e651",
"unicode_decimal": 58961
},
{
"icon_id": "24877229",
"name": "七牛云",
"font_class": "qiniuyun",
"unicode": "e62c",
"unicode_decimal": 58924
},
{
"icon_id": "19671162",
"name": "png-1",
@@ -418,13 +467,6 @@
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "32101973",
"name": "Amazon_Web_Services_Logo",
"font_class": "aws",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "1760690",
"name": "讨论",
@@ -439,13 +481,6 @@
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "13532955",
"name": "SFTP",
"font_class": "SFTP",
"unicode": "e647",
"unicode_decimal": 58951
},
{
"icon_id": "15337722",
"name": "Logo GitHub",
@@ -453,20 +488,6 @@
"unicode": "e67c",
"unicode_decimal": 59004
},
{
"icon_id": "16268521",
"name": "oss",
"font_class": "oss",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "20290513",
"name": "minio",
"font_class": "minio",
"unicode": "e63c",
"unicode_decimal": 58940
},
{
"icon_id": "924436",
"name": "logout",
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
+65 -72
View File
@@ -2,75 +2,68 @@
<div>
<div class="app-status" v-if="data.isExist">
<el-card>
<div class="flex items-center">
<div>
<div class="flex w-full flex-col gap-4 md:flex-row">
<div class="flex flex-wrap gap-4">
<el-tag effect="dark" type="success">{{ data.app }}</el-tag>
<Status :key="refresh" :status="data.status"></Status>
<el-tag>{{ $t('app.version') }}:{{ data.version }}</el-tag>
</div>
<div>
<Status class="status-content" :key="refresh" :status="data.status"></Status>
</div>
<div>
<el-tag class="status-content">{{ $t('app.version') }}:{{ data.version }}</el-tag>
</div>
<div>
<span class="buttons">
<el-button
type="primary"
v-if="data.status != 'Running'"
link
@click="onOperate('start')"
:disabled="data.status === 'Installing'"
>
{{ $t('app.start') }}
</el-button>
<el-button type="primary" v-if="data.status === 'Running'" link @click="onOperate('stop')">
{{ $t('app.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button
type="primary"
link
:disabled="data.status === 'Installing'"
@click="onOperate('restart')"
>
{{ $t('app.restart') }}
</el-button>
<el-divider direction="vertical" />
<el-button
type="primary"
link
v-if="data.app === 'OpenResty'"
@click="onOperate('reload')"
:disabled="data.status !== 'Running'"
>
{{ $t('app.reload') }}
</el-button>
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
<el-button
type="primary"
@click="setting"
link
:disabled="
data.status === 'Installing' ||
(data.status !== 'Running' && data.app === 'OpenResty')
"
>
{{ $t('commons.button.set') }}
</el-button>
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
<el-button
v-if="data.app === 'OpenResty'"
type="primary"
@click="clear"
link
:disabled="
data.status === 'Installing' ||
(data.status !== 'Running' && data.app === 'OpenResty')
"
>
{{ $t('nginx.clearProxyCache') }}
</el-button>
</span>
<div class="mt-0.5">
<el-button
type="primary"
v-if="data.status != 'Running'"
link
@click="onOperate('start')"
:disabled="data.status === 'Installing'"
>
{{ $t('app.start') }}
</el-button>
<el-button type="primary" v-if="data.status === 'Running'" link @click="onOperate('stop')">
{{ $t('app.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button
type="primary"
link
:disabled="data.status === 'Installing'"
@click="onOperate('restart')"
>
{{ $t('app.restart') }}
</el-button>
<el-divider direction="vertical" />
<el-button
type="primary"
link
v-if="data.app === 'OpenResty'"
@click="onOperate('reload')"
:disabled="data.status !== 'Running'"
>
{{ $t('app.reload') }}
</el-button>
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
<el-button
type="primary"
@click="setting"
link
:disabled="
data.status === 'Installing' || (data.status !== 'Running' && data.app === 'OpenResty')
"
>
{{ $t('commons.button.set') }}
</el-button>
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
<el-button
v-if="data.app === 'OpenResty'"
type="primary"
@click="clear"
link
:disabled="
data.status === 'Installing' || (data.status !== 'Running' && data.app === 'OpenResty')
"
>
{{ $t('nginx.clearProxyCache') }}
</el-button>
</div>
<div class="ml-5" v-if="key === 'openresty' && (httpPort != 80 || httpsPort != 443)">
@@ -95,12 +88,12 @@
<LayoutContent :title="getTitle(key)" :divider="true">
<template #main>
<div class="app-warn">
<div class="flx-center">
<span>{{ $t('app.checkInstalledWarn', [data.app]) }}</span>
<span @click="goRouter(key)" class="flx-align-center">
<el-icon class="ml-2"><Position /></el-icon>
<div class="flex flex-col gap-4 items-cetner justify-center w-full sm:flex-row">
<div>{{ $t('app.checkInstalledWarn', [data.app]) }}</div>
<div @click="goRouter(key)" class="flex items-cetner justify-center text-[#74a4f3]">
<el-icon class="flex items-center justify-center"><Position /></el-icon>
{{ $t('database.goInstall') }}
</span>
</div>
</div>
<div>
<img src="@/assets/images/no_app.svg" />
+106 -18
View File
@@ -72,30 +72,52 @@
</ComplexTable>
</el-drawer>
<el-dialog
v-model="open"
:title="isBackup ? $t('commons.button.backup') : $t('commons.button.recover') + ' - ' + name"
width="40%"
:close-on-click-modal="false"
:before-close="handleBackupClose"
>
<el-form ref="backupForm" label-position="left" v-loading="loading">
<el-form-item
:label="$t('setting.compressPassword')"
style="margin-top: 10px"
v-if="type === 'app' || type === 'website'"
>
<el-input v-model="secret" :placeholder="$t('setting.backupRecoverMessage')" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleBackupClose" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
<OpDialog ref="opRef" @search="search" />
</div>
<AppBackUp ref="backupRef" @close="search" />
<AppRecover ref="recoverRef" />
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
import { getBackupList } from '@/api/modules/setting';
import { getBackupList, handleBackup, handleRecover } from '@/api/modules/setting';
import i18n from '@/lang';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/setting';
import { Backup } from '@/api/interface/backup';
import router from '@/routers';
import AppBackUp from '@/views/app-store/installed/backup/index.vue';
import AppRecover from '@/views/app-store/installed/recover/index.vue';
import { MsgSuccess } from '@/utils/message';
const selects = ref<any>([]);
const loading = ref();
const opRef = ref();
const backupRef = ref();
const recoverRef = ref();
const data = ref();
const paginationConfig = reactive({
@@ -113,6 +135,10 @@ const backupPath = ref();
const status = ref();
const secret = ref();
const open = ref();
const isBackup = ref();
const currentRow = ref();
interface DialogProps {
type: string;
name: string;
@@ -133,6 +159,9 @@ const acceptParams = (params: DialogProps): void => {
const handleClose = () => {
backupVisible.value = false;
};
const handleBackupClose = () => {
open.value = false;
};
const loadBackupDir = () => {
getBackupList().then((res) => {
@@ -175,25 +204,84 @@ const search = async () => {
};
const onBackup = async () => {
isBackup.value = true;
if (type.value !== 'app' && type.value !== 'website') {
ElMessageBox.confirm(
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']),
i18n.global.t('commons.button.backup'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
).then(async () => {
onSubmit();
});
return;
}
open.value = true;
};
const onSubmit = async () => {
if (isBackup.value) {
let params = {
type: type.value,
name: name.value,
detailName: detailName.value,
secret: secret.value,
};
loading.value = true;
await handleBackup(params)
.then(() => {
loading.value = false;
handleClose();
handleBackupClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
return;
}
let params = {
source: currentRow.value.source,
type: type.value,
name: name.value,
detailName: detailName.value,
file: currentRow.value.fileDir + '/' + currentRow.value.fileName,
secret: secret.value,
};
backupRef.value.acceptParams(params);
loading.value = true;
await handleRecover(params)
.then(() => {
loading.value = false;
handleClose();
handleBackupClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
};
const onRecover = async (row: Backup.RecordInfo) => {
let params = {
source: row.source,
type: type.value,
name: name.value,
detailName: detailName.value,
file: row.fileDir + '/' + row.fileName,
secret: secret.value,
};
recoverRef.value.acceptParams(params);
isBackup.value = false;
currentRow.value = row;
if (type.value !== 'app' && type.value !== 'website') {
ElMessageBox.confirm(
i18n.global.t('commons.msg.recoverHelper', [name.value + '( ' + detailName.value + ' )']),
i18n.global.t('commons.button.recover'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
).then(async () => {
onSubmit();
});
return;
}
open.value = true;
};
const onDownload = async (row: Backup.RecordInfo) => {
@@ -105,6 +105,9 @@ const showBack = computed(() => {
.content-container__toolbar {
margin-top: 20px;
.el-button + .el-button {
margin: 0 !important;
}
}
.content-container_form {
+88 -24
View File
@@ -1,5 +1,5 @@
<template>
<div>
<div v-loading="isLoading">
<div v-if="defaultButton">
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
{{ $t('commons.button.watch') }}
@@ -29,7 +29,6 @@ import { ReadByLine } from '@/api/modules/files';
import { watch } from 'vue';
const editorRef = ref();
interface LogProps {
id?: number;
type: string;
@@ -74,10 +73,11 @@ let timer: NodeJS.Timer | null = null;
const tailLog = ref(false);
const content = ref('');
const end = ref(false);
const lastContent = ref('');
const scrollerElement = ref<HTMLElement | null>(null);
const minPage = ref(1);
const maxPage = ref(1);
const logs = ref([]);
const isLoading = ref(false);
const readReq = reactive({
id: 0,
@@ -114,7 +114,12 @@ const stopSignals = [
'image push successful!',
];
const lastLogs = ref([]);
const getContent = (pre: boolean) => {
if (isLoading.value) {
return;
}
emit('update:isReading', true);
readReq.id = props.config.id;
readReq.type = props.config.type;
@@ -122,38 +127,55 @@ const getContent = (pre: boolean) => {
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
ReadByLine(readReq).then((res) => {
if (!end.value && res.data.end) {
lastContent.value = content.value;
lastLogs.value = [...logs.value];
}
res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
});
data.value = res.data;
if (res.data.content != '') {
if (stopSignals.some((signal) => res.data.content.endsWith(signal))) {
if (res.data.lines && res.data.lines.length > 0) {
res.data.lines = res.data.lines.map((line) =>
line.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
}),
);
const newLogs = res.data.lines;
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
readReq.page++;
}
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
return;
}
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog();
}
if (end.value) {
if (lastContent.value == '') {
content.value = res.data.content;
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
content.value = pre
? res.data.content + '\n' + lastContent.value
: lastContent.value + '\n' + res.data.content;
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if (content.value == '') {
content.value = res.data.content;
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
content.value = pre
? res.data.content + '\n' + content.value
: content.value + '\n' + res.data.content;
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
}
end.value = res.data.end;
content.value = logs.value.join('\n');
emit('update:hasContent', content.value !== '');
nextTick(() => {
if (pre) {
@@ -171,16 +193,52 @@ const getContent = (pre: boolean) => {
maxPage.value = res.data.total;
minPage.value = res.data.total;
}
if (logs.value && logs.value.length > 3000) {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value--;
}
}
isLoading.value = false;
});
};
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
let inThrottle: boolean;
let lastFunc: ReturnType<typeof setTimeout>;
let lastRan: number;
return function (this: any, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
lastRan = Date.now();
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
const throttledGetContent = throttle(getContent, 3000);
const search = () => {
throttledGetContent(false);
};
const changeTail = (fromOutSide: boolean) => {
if (fromOutSide) {
tailLog.value = !tailLog.value;
}
if (tailLog.value) {
timer = setInterval(() => {
getContent(false);
search();
}, 1000 * 3);
} else {
onCloseLog();
@@ -198,6 +256,7 @@ const onCloseLog = async () => {
tailLog.value = false;
clearInterval(Number(timer));
timer = null;
isLoading.value = false;
};
function isScrolledToBottom(element: HTMLElement): boolean {
@@ -218,7 +277,7 @@ const init = () => {
changeTail(false);
}
readReq.latest = true;
getContent(false);
search();
nextTick(() => {});
};
@@ -232,9 +291,14 @@ const initCodemirror = () => {
if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement;
scrollerElement.value.addEventListener('scroll', function () {
if (tailLog.value) {
return;
}
if (isScrolledToBottom(scrollerElement.value)) {
readReq.page = maxPage.value;
getContent(false);
if (maxPage.value > 1) {
readReq.page = maxPage.value;
}
search();
}
if (isScrolledToTop(scrollerElement.value)) {
readReq.page = minPage.value - 1;
@@ -242,7 +306,7 @@ const initCodemirror = () => {
return;
}
minPage.value = readReq.page;
getContent(true);
throttledGetContent(true);
}
});
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
+20 -15
View File
@@ -1,20 +1,24 @@
<template>
<el-card class="router_card">
<el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button
class="router_card_button"
:label="button.label"
:value="button.label"
v-for="(button, index) in buttonArray"
size="large"
:key="index"
>
<el-badge :value="button.count" v-if="button.count" is-dot>
<span>{{ button.label }}</span>
</el-badge>
</el-radio-button>
</el-radio-group>
<slot name="route-button"></slot>
<div class="flex w-full flex-col md:justify-between md:flex-row">
<el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button
class="router_card_button"
:label="button.label"
:value="button.label"
v-for="(button, index) in buttonArray"
size="large"
:key="index"
>
<el-badge :value="button.count" v-if="button.count" is-dot>
<span>{{ button.label }}</span>
</el-badge>
</el-radio-button>
</el-radio-group>
<div class="flex flex-row gap-2 md:flex-col lg:flex-row">
<slot name="route-button"></slot>
</div>
</div>
</el-card>
</template>
@@ -86,6 +90,7 @@ onMounted(() => {
background-color: var(--panel-button-active) !important;
box-shadow: none !important;
border: 2px solid transparent !important;
color: var(--el-text-color-regular) !important;
}
.el-radio-button__original-radio:checked + .el-radio-button__inner {
@@ -1,38 +1,47 @@
<template>
<div class="flx-center">
<span v-if="props.footer">
<el-button type="primary" link @click="toForum">
<span class="font-normal">{{ $t('setting.forum') }}</span>
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="toDoc">
<span class="font-normal">{{ $t('setting.doc2') }}</span>
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="toGithub">
<span class="font-normal">{{ $t('setting.project') }}</span>
</el-button>
<el-divider direction="vertical" />
</span>
<el-button type="primary" link @click="toHalo">
<span class="font-normal">{{ isProductPro ? $t('license.pro') : $t('license.community') }}</span>
</el-button>
<span class="version">{{ version }}</span>
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
<el-button type="primary" link @click="onLoadUpgradeInfo">
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
</el-button>
</el-badge>
<el-button
v-if="version !== 'Waiting' && !globalStore.hasNewVersion"
type="primary"
link
@click="onLoadUpgradeInfo"
>
<span>({{ $t('setting.upgradeCheck') }})</span>
</el-button>
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">{{ $t('setting.upgrading') }}</el-tag>
<div>
<div class="flex w-full flex-col gap-2 md:flex-row">
<div class="flex flex-wrap" v-if="props.footer">
<el-button type="primary" link @click="toForum">
<span class="font-normal">{{ $t('setting.forum') }}</span>
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="toDoc">
<span class="font-normal">{{ $t('setting.doc2') }}</span>
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="toGithub">
<span class="font-normal">{{ $t('setting.project') }}</span>
</el-button>
<el-divider v-if="!mobile" direction="vertical" />
</div>
<div class="flex flex-wrap">
<el-button type="primary" link @click="toHalo">
<span class="font-normal">
{{ isProductPro ? $t('license.pro') : $t('license.community') }}
</span>
</el-button>
<span class="version" @click="copyText(version)">{{ version }}</span>
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
<el-button type="primary" link @click="onLoadUpgradeInfo">
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
</el-button>
</el-badge>
<el-button
v-if="version !== 'Waiting' && !globalStore.hasNewVersion"
type="primary"
link
@click="onLoadUpgradeInfo"
>
<span>({{ $t('setting.upgradeCheck') }})</span>
</el-button>
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">
{{ $t('setting.upgrading') }}
</el-tag>
</div>
</div>
</div>
<el-drawer
:close-on-click-modal="false"
:close-on-press-escape="false"
@@ -81,7 +90,8 @@ import MdEditor from 'md-editor-v3';
import i18n from '@/lang';
import 'md-editor-v3/lib/style.css';
import { MsgSuccess } from '@/utils/message';
import { onMounted, ref } from 'vue';
import { copyText } from '@/utils/util';
import { onMounted, ref, computed } from 'vue';
import { GlobalStore } from '@/store';
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
@@ -89,6 +99,10 @@ import { storeToRefs } from 'pinia';
const globalStore = GlobalStore();
const { isDarkTheme } = storeToRefs(globalStore);
const mobile = computed(() => {
return globalStore.isMobile();
});
const version = ref<string>('');
const isProductPro = ref();
const loading = ref(false);
@@ -190,6 +204,8 @@ onMounted(() => {
color: var(--dark-gold-base-color);
text-decoration: none;
letter-spacing: 0.5px;
cursor: pointer;
margin-top: 2px;
}
.line-height {
line-height: 25px;
+66 -6
View File
@@ -92,9 +92,36 @@
</div>
</el-drawer>
<el-dialog
v-model="open"
:title="$t('commons.button.recover') + ' - ' + name"
width="40%"
:close-on-click-modal="false"
:before-close="handleBackupClose"
>
<el-form ref="backupForm" label-position="left" v-loading="loading">
<el-form-item
:label="$t('setting.compressPassword')"
style="margin-top: 10px"
v-if="type === 'app' || type === 'website'"
>
<el-input v-model="secret" :placeholder="$t('setting.backupRecoverMessage')" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleBackupClose" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onHandleRecover" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
<OpDialog ref="opRef" @search="search" />
</div>
<AppRecover ref="recoverRef" />
</template>
<script lang="ts" setup>
@@ -105,9 +132,8 @@ import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
import { File } from '@/api/interface/file';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { BatchDeleteFile, CheckFile, ChunkUploadFileData, GetUploadList } from '@/api/modules/files';
import { loadBaseDir } from '@/api/modules/setting';
import { handleRecoverByUpload, loadBaseDir } from '@/api/modules/setting';
import { MsgError, MsgSuccess } from '@/utils/message';
import AppRecover from '@/views/app-store/installed/recover/index.vue';
const loading = ref();
const isUpload = ref();
@@ -115,7 +141,9 @@ const uploadPercent = ref<number>(0);
const selects = ref<any>([]);
const baseDir = ref();
const opRef = ref();
const recoverRef = ref();
const open = ref();
const currentRow = ref();
const data = ref();
const title = ref();
@@ -180,16 +208,48 @@ const search = async () => {
};
const onRecover = async (row: File.File) => {
currentRow.value = row;
if (type.value !== 'app' && type.value !== 'website') {
ElMessageBox.confirm(
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']),
i18n.global.t('commons.button.backup'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
).then(async () => {
onHandleRecover();
});
return;
}
open.value = true;
};
const handleBackupClose = () => {
open.value = false;
};
const onHandleRecover = async () => {
let params = {
source: 'LOCAL',
type: type.value,
name: name.value,
detailName: detailName.value,
file: baseDir.value + row.name,
file: baseDir.value + currentRow.value.name,
secret: secret.value,
recoverType: 'upload',
};
recoverRef.value.acceptParams(params);
loading.value = true;
await handleRecoverByUpload(params)
.then(() => {
loading.value = false;
handleClose();
handleBackupClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
};
const uploaderFiles = ref<UploadFiles>([]);
+1 -1
View File
@@ -211,7 +211,7 @@ const checkImageName = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.imageName')));
} else {
const reg = /^[a-zA-Z0-9]{1}[a-z:A-Z0-9_/.-]{0,149}$/;
const reg = /^[a-zA-Z0-9]{1}[a-z:@A-Z0-9_/.-]{0,256}$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.imageName')));
} else {
+12 -3
View File
@@ -69,6 +69,7 @@ const message = {
createNewFolder: 'Create new folder',
createNewFile: 'Create new file',
helpDoc: 'Help Document',
unbind: 'UnBind',
},
search: {
timeStart: 'Time start',
@@ -190,7 +191,7 @@ const message = {
simpleName: 'Supports non-underscore starting, English, numbers, _, length 3-30',
simplePassword: 'Supports non-underscore starting, English, numbers, _, length 1-30',
dbName: 'Supports non-special character starting, including English, Chinese, numbers, .-_, with a length of 1-64',
imageName: 'Support English, numbers, :/.-_, length 1-150',
imageName: 'Support English, numbers, :@/.-_, length 1-256',
volumeName: 'Support English, numbers, .-_, length 2-30',
supervisorName: 'Supports non-special characters starting with English, numbers, - and _, length 1-128',
complexityPassword:
@@ -1175,6 +1176,7 @@ const message = {
info: 'Attribute',
linkFile: 'Soft link',
terminal: 'Terminal',
batchoperation: 'batch operation',
shareList: 'Share list',
zip: 'Compressed',
group: 'Group',
@@ -1275,6 +1277,7 @@ const message = {
noNameFolder: 'Untitled Folder',
noNameFile: 'Untitled File',
minimap: 'Code Mini Map',
fileCanNotRead: 'File can not read',
},
ssh: {
autoStart: 'Auto Start',
@@ -1523,6 +1526,7 @@ const message = {
allowIPEgs:
'If multiple ip authorizations exist, newlines need to be displayed. For example, \n172.16.10.111 \n172.16.10.0/24',
mfa: 'MFA',
mfaClose: 'Disabling MFA will reduce the security of the service. Do you want to continue?',
secret: 'Secret',
mfaInterval: 'Refresh interval (s)',
mfaTitleHelper:
@@ -1571,7 +1575,7 @@ const message = {
status: 'Snapshot status',
ignoreRule: 'Ignore Rule',
ignoreHelper:
'This rule will be used to compress and backup the 1Panel data directory during snapshots, please modify with caution.',
'This rule will be used to compress and backup the 1Panel data directory during snapshots, with socket files ignored by default.',
ignoreHelper1: 'One item per line, e.g.: \n*.log\n/opt/1panel/cache',
panelInfo: 'Write 1Panel basic information',
panelBin: 'Backup 1Panel system files',
@@ -1679,6 +1683,7 @@ const message = {
'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.',
monitor:
'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ',
alert: 'Upgrade to the professional version to receive alarm information via SMS and view alarm logs, fully control various key events, and ensure worry-free system operation',
},
clean: {
scan: 'Start Scanning',
@@ -1828,7 +1833,7 @@ const message = {
appHelper: 'Please view the installation instructions of some applications on the application details page',
backupApp: 'Backup application before upgrade',
backupAppHelper:
'If the upgrade fails, the backup will be automatically rolled back. Please check the failure reason in the log audit-system log',
'If the upgrade fails, the backup will be automatically rolled back. Please check the failure reason in the log audit-system log. The backup will retain the latest 3 copies by default',
delete: 'Delete',
openrestyDeleteHelper:
'Forcibly deleting OpenResty will delete all websites, please confirm the risk before operation',
@@ -1851,6 +1856,10 @@ const message = {
useCustom: 'Customize docker-compose.yml',
useCustomHelper:
'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it',
diffHelper:
'The left side is the old version, the right side is the new version. After editing, click to save the custom version',
pullImage: 'Pull Image',
pullImageHelper: 'Execute docker pull to pull the image before the application starts',
},
website: {
website: 'Website',
+11 -4
View File
@@ -19,7 +19,7 @@ const message = {
delete: '刪除',
edit: '編輯',
enable: '啟用',
disable: '禁用',
disable: '停用',
confirm: '確認',
cancel: '取消',
reset: '重置',
@@ -68,6 +68,7 @@ const message = {
createNewFolder: '新建資料夾',
createNewFile: '新建檔案',
helpDoc: '幫助文档',
unbind: '解綁',
},
search: {
timeStart: '開始時間',
@@ -189,7 +190,7 @@ const message = {
simpleName: '支持非底線開頭,英文、數字、_,長度3-30',
simplePassword: '支持非底線開頭,英文、數字、_,長度1-30',
dbName: '支持非特殊字符開頭,英文、中文、數字、.-_,長度1-64',
imageName: '支持英文、數字、:/.-_,長度1-150',
imageName: '支持英文、數字、:@/.-_,長度1-256',
volumeName: '支持英文、數字、.-和_,長度2-30',
supervisorName: '支援非特殊字元開頭,英文、數字、-和_,長度1-128',
complexityPassword: '請輸入長度為 8-30 位,並包含字母、數字、至少兩種特殊字符的密碼組合',
@@ -1113,6 +1114,7 @@ const message = {
info: '屬性',
linkFile: '軟連接文件',
terminal: '終端',
batchoperation: '批量操作',
shareList: '分享列表',
zip: '壓縮',
group: '用戶組',
@@ -1209,6 +1211,7 @@ const message = {
noNameFolder: '未命名資料夾',
noNameFile: '未命名檔案',
minimap: '縮略圖',
fileCanNotRead: '此文件不支持預覽',
},
ssh: {
autoStart: '開機自啟',
@@ -1387,7 +1390,7 @@ const message = {
deleteHelper: '將刪除該快照的所有備份文件,包括第三方備份賬號中的文件。',
status: '快照狀態',
ignoreRule: '排除規則',
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,請謹慎修改。',
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,預設忽略套接字檔案。',
ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache',
panelInfo: '寫入 1Panel 基礎信息',
panelBin: '備份 1Panel 系統文件',
@@ -1466,6 +1469,7 @@ const message = {
allowIPsHelper1: '授權 IP 為空時,則取消授權 IP',
allowIPEgs: '當存在多個授權 IP 時,需要換行顯示,例: \n172.16.10.111 \n172.16.10.0/24',
mfa: '兩步驗證',
mfaClose: '關閉兩步驗證將導致服務安全性降低,是否繼續?',
secret: '密鑰',
mfaAlert: '兩步驗證密碼是基於當前時間生成,請確保服務器時間已同步',
mfaHelper: '開啟後會驗證手機應用驗證碼',
@@ -1560,6 +1564,7 @@ const message = {
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。',
setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。',
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
alert: '陞級專業版可通過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統運行無憂。',
},
clean: {
scan: '開始掃描',
@@ -1700,7 +1705,7 @@ const message = {
ignoreList: '忽略列表',
appHelper: '部分應用的安裝使用說明請在應用詳情頁查看',
backupApp: '升級前備份應用',
backupAppHelper: '升級失敗會使用備份自動回滾,請在日誌審計-系統日誌中查看失敗原因',
backupAppHelper: '升級失敗會使用備份自動回滾,請在日誌審計-系統日誌中查看失敗原因,備份預設保留最新的3份',
delete: '刪除',
openrestyDeleteHelper: '強制刪除 OpenResty 會刪除所有的網站,請確認風險後操作',
downloadLogHelper1: '即將下載 {0} 套用所有日誌,是否繼續? ',
@@ -1719,6 +1724,8 @@ const message = {
useCustom: '自訂 docker-compose.yml',
useCustomHelper: '使用自訂 docker-compose.yml 文件,可能會導致應用程式升級失敗,如無必要,請勿勾選',
diffHelper: '左側為舊版本,右側為新版,編輯之後點選使用自訂版本儲存',
pullImage: '拉取鏡像',
pullImageHelper: '在應用啟動之前執行 docker pull 來拉取鏡像',
},
website: {
website: '網站',
+13 -4
View File
@@ -19,7 +19,7 @@ const message = {
delete: '删除',
edit: '编辑',
enable: '启用',
disable: '禁用',
disable: '停用',
confirm: '确认',
cancel: '取消',
reset: '重置',
@@ -68,6 +68,7 @@ const message = {
createNewFolder: '新建文件夹',
createNewFile: '新建文件',
helpDoc: '帮助文档',
unbind: '解绑',
},
search: {
timeStart: '开始时间',
@@ -189,7 +190,7 @@ const message = {
simpleName: '支持非下划线开头,英文、数字、_,长度3-30',
simplePassword: '支持非下划线开头,英文、数字、_,长度1-30',
dbName: '支持非特殊字符开头,英文、中文、数字、.-_,长度1-64',
imageName: '支持英文、数字、:/.-_,长度1-150',
imageName: '支持英文、数字、:@/.-_,长度1-256',
volumeName: '支持英文、数字、.-和_,长度2-30',
supervisorName: '支持非特殊字符开头,英文、数字、-和_,长度1-128',
complexityPassword: '请输入长度为 8-30 位且包含字母、数字、特殊字符至少两项的密码组合',
@@ -336,6 +337,8 @@ const message = {
kernelArch: '系统类型',
network: '流量',
io: '磁盘 IO',
ip: '主机地址',
proxy: '系统代理',
baseInfo: '基本信息',
totalSend: '总发送',
totalRecv: '总接收',
@@ -1115,6 +1118,7 @@ const message = {
info: '属性',
linkFile: '软连接文件',
terminal: '终端',
batchoperation: '批量操作',
shareList: '分享列表',
zip: '压缩',
group: '用户组',
@@ -1211,6 +1215,7 @@ const message = {
noNameFolder: '未命名文件夹',
noNameFile: '未命名文件',
minimap: '缩略图',
fileCanNotRead: '此文件不支持预览',
},
ssh: {
autoStart: '开机自启',
@@ -1388,7 +1393,7 @@ const message = {
snapshot: '快照',
deleteHelper: '将删除该快照的所有备份文件,包括第三方备份账号中的文件。',
ignoreRule: '排除规则',
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,请谨慎修改。',
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,默认忽略套接字文件。',
ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache',
status: '快照状态',
panelInfo: '写入 1Panel 基础信息',
@@ -1468,6 +1473,7 @@ const message = {
allowIPsHelper1: '授权 IP 为空时,则取消授权 IP',
allowIPEgs: '当存在多个授权 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24',
mfa: '两步验证',
mfaClose: '关闭两步验证将导致服务安全性降低,是否继续?',
secret: '密钥',
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
mfaHelper: '开启后会验证手机应用验证码',
@@ -1562,6 +1568,7 @@ const message = {
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。',
setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。',
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。',
},
clean: {
scan: '开始扫描',
@@ -1701,7 +1708,7 @@ const message = {
ignoreList: '忽略列表',
appHelper: '部分应用的安装使用说明请在应用详情页查看',
backupApp: '升级前备份应用',
backupAppHelper: '升级失败会使用备份自动回滚,请在日志审计-系统日志中查看失败原因',
backupAppHelper: '升级失败会使用备份自动回滚,请在日志审计-系统日志中查看失败原因,备份默认保留最新的3份',
delete: '删除',
openrestyDeleteHelper: '强制删除 OpenResty 会删除所有的网站,请确认风险之后操作',
downloadLogHelper1: '即将下载 {0} 应用所有日志,是否继续?',
@@ -1720,6 +1727,8 @@ const message = {
useCustom: '自定义 docker-compose.yml',
useCustomHelper: '使用自定义 docker-compose.yml 文件,可能会导致应用升级失败,如无必要,请勿勾选',
diffHelper: '左侧为旧版本,右侧为新版,编辑之后点击使用自定义版本保存',
pullImage: '拉取镜像',
pullImageHelper: '在应用启动之前执行 docker pull 来拉取镜像',
},
website: {
website: '网站',
+16 -3
View File
@@ -1,12 +1,25 @@
<template>
<div class="footer">
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2024 FIT2CLOUD 飞致云</a>
<SystemUpgrade :footer="true" />
<div class="footer" :style="{ height: mobile ? '108px' : '48px' }">
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
<div class="flex flex-wrap gap-4">
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2024 FIT2CLOUD 飞致云</a>
</div>
<div class="flex flex-row gap-2 md:flex-col lg:flex-row">
<SystemUpgrade :footer="true" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import SystemUpgrade from '@/components/system-upgrade/index.vue';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
</script>
<style scoped lang="scss">
+15 -8
View File
@@ -181,8 +181,8 @@ html {
.mask-prompt {
position: absolute;
z-index: 9998;
top: 220px;
z-index: 1;
top: 320px;
left: 45%;
transform: translate(-50%, -50%);
width: 400px;
@@ -361,19 +361,19 @@ html {
.el-input-group__append {
border-left: 0;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
background-color: #ffffff !important;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
-1px 0 0 0 var(--el-input-border-color) inset;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
}
.tag-button {
margin-right: 10px;
&.no-active {
background: none;
border: none;
@@ -386,7 +386,8 @@ html {
}
.router-button {
margin-right: 20px;
margin-right: 30px;
padding: 10px;
}
.text-parent {
@@ -443,6 +444,12 @@ html {
cursor: pointer;
}
.dialog-footer{
display: flex;
align-items: center;
justify-content: flex-end;
}
.monaco-editor-tree-light .el-tree-node__content:hover {
background-color: #e5eefd;
}
+8 -9
View File
@@ -80,10 +80,6 @@ html.dark {
border: 1px solid var(--panel-border-color);
}
// * wangEditor
--w-e-textarea-bg-color: #1b1b1b;
--w-e-textarea-color: #eeeeee;
.md-editor-dark {
--md-bk-color: #111417;
}
@@ -251,14 +247,16 @@ html.dark {
color: rgb(174, 166, 153);
}
.el-dialog {
border: 1px solid var(--panel-border-color);
.el-dialog__header {
border-bottom: var(--panel-border);
color: #999999;
.el-dialog__title {
color: var(--el-menu-text-color);
}
}
border: 1px solid var(--panel-border-color);
}
.el-tabs__item {
color: #999999;
@@ -330,15 +328,16 @@ html.dark {
.el-input-group__append {
border-left: 0;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
background-color: #212426 !important;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
-1px 0 0 0 var(--el-input-border-color) inset;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
}
.el-collapse-item__header {
+2 -1
View File
@@ -173,12 +173,13 @@ html {
}
.el-dialog {
border-radius: 5px;
.el-dialog__header {
.el-dialog__title {
font-size: 17px;
}
}
border-radius: 5px;
}
.row-box {

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