Compare commits

...
121 Commits
Author SHA1 Message Date
ssongliu 3d6a97e5a6 fix: 解决操作日志打印错误的问题 (#6899) 2024-10-30 17:06:31 +08:00
ssongliu c2bb422b66 fix: 解决远程数据库无法切换的问题 (#6893)
Refs #6883
2024-10-30 17:06:28 +08:00
ssongliu cdb0f8e85c fix: 解决全屏切换直接关闭后菜单不显示的问题 (#6894)
Refs #6889
2024-10-30 17:06:25 +08:00
ssongliu 31576138bf fix: 解决容器编排路径为空时无法删除的问题 (#6862) 2024-10-30 17:06:20 +08:00
ssongliu bb6d4635a1 fix: 解决容器镜像仓库编辑失败的问题 (#6845)
Refs #6839
2024-10-30 17:06:17 +08:00
ssongliu 22a61886c7 fix: 补全进程守护 watch 引用 (#6836) 2024-10-24 17:13:28 +08:00
zhengkunwang 376ec86c57 feat: 创建运行环境网站增加默认网络创建 (#6807) 2024-10-24 14:37:48 +08:00
ssongliu 57120d7fa8 fix: 解决低版本 dockerd 命令不兼容的问题 (#6824) 2024-10-24 14:25:30 +08:00
ssongliu 8da5891490 style: 容器样式调整 (#6822)
Refs #6819
2024-10-24 14:25:27 +08:00
zhengkunwang 6f3acd47e2 feat: 启动运行环境增加默认网络检测 (#6820) 2024-10-24 14:25:24 +08:00
zhengkunwang 1519615ea3 feat: 创建运行环境网站增加 Python 选项 (#6815) 2024-10-24 14:25:07 +08:00
ssongliu 26b47bae69 fix: 解决快照列表排序的问题 (#6816)
Refs #6810
2024-10-24 14:25:02 +08:00
ssongliu 8a6ba5c007 fix: 解决 Docker 重启失败的问题 (#6805) 2024-10-24 14:24:56 +08:00
zhengkunwang 73c594081b fix: 解决 Python 运行环境编辑报错的问题 (#6801) 2024-10-24 14:24:52 +08:00
蘭 5d0a4c597b fix: 文件夹选择控件,选择文件时不默认选择当前目录 (#6799)
Refs #6012
2024-10-24 14:24:49 +08:00
zhengkunwang ab0e777501 fix: 解决 Mysql 特定版本忽略升级失效的问题 (#6800)
Refs https://github.com/1Panel-dev/1Panel/issues/6796
2024-10-24 14:24:40 +08:00
ssongliu ca38780da8 feat: 修改容器操作超时时间 (#6798)
Refs #6747
2024-10-24 14:24:33 +08:00
Ludovic Fernandez 68347d79d6 fix: otel dependencies (#6793) 2024-10-24 14:24:29 +08:00
ssongliu e615f3d0fb feat: SSL 配置增加重启选项 (#6792) 2024-10-24 14:24:26 +08:00
Ludovic Fernandez 21cdd05d24 fix: huawei cloud (#6791) 2024-10-24 14:24:17 +08:00
zhengkunwang 2ad30db698 fix: 解决创建运行环境报名称存在的问题 (#6785) 2024-10-24 14:24:12 +08:00
zhengkunwang 2076a8a8f3 fix: 解决证书自动续签日志报错的问题 (#6784) 2024-10-24 14:24:08 +08:00
zhengkunwang 3027ae4a50 fix: 解决网站开启 HTTPS 之后 404 配置丢失的问题 (#6775) 2024-10-24 14:24:04 +08:00
zhengkunwang facea38ab7 style: 日志样式调整 (#6774) 2024-10-24 14:23:59 +08:00
ssongliu 0243a4c255 fix: 解决容器编排跳转路径错误的问题 (#6773) 2024-10-24 14:23:45 +08:00
蘭 1486bf3f69 fix: 解决移动端主机文件批量删除错误 (#6765) 2024-10-24 14:23:41 +08:00
ssongliu ac2c20bd81 feat: postgresql 远程数据库支持 17.x 版本 (#6759) 2024-10-24 14:23:24 +08:00
ssongliu fcce6fec44 feat: 优化数据库切换效果 (#6757)
Refs #6755
2024-10-24 14:23:13 +08:00
John Bro a6ab6d6aba fix: 容器部分操作动态修改执行命令 (#6750)
Refs #3242
2024-10-24 14:23:09 +08:00
zhengkunwang cad50320eb feat: PHP 镜像 build 增加超时时间 (#6744) 2024-10-24 14:22:34 +08:00
zhengkunwang 0a6a1f11c7 style: 优化一些样式 (#6740) 2024-10-24 14:22:14 +08:00
ssongliu fcb8a9c860 feat: 移除多余代码 2024-10-16 14:27:19 +08:00
zhengkunwang 65da51a478 style: 优化应用升级文件对比样式 (#6733) 2024-10-16 04:07:26 +00:00
蘭 0c31ba1c5f feat: 文件夹选择控件,默认选中当前目录 (#6720)
Refs #6012
2024-10-15 10:21:22 +00:00
蘭 acf94b052b feat: 增加病毒扫描和计划任务告警 (#6709) 2024-10-14 09:51:17 +00:00
蘭 556441d811 style: 解决 Alert 提示组件标题存在链接时文字错位问题 (#6711) 2024-10-14 09:41:17 +00:00
zhengkunwang efcf17e47c style: 优化登录页软件许可协议提示语样式 (#6712) 2024-10-14 09:31:18 +00:00
zhengkunwang 02b7744995 pref: 优化网站日志读取 (#6710)
Refs https://github.com/1Panel-dev/1Panel/issues/6642
2024-10-14 09:29:17 +00:00
ssongliu c86947b69c style: 修改按钮组边框宽度不一致的问题 (#6706)
Refs #6704
2024-10-14 06:41:16 +00:00
ssongliu 839b8c2844 feat: 修改容器列表应用商店容器默认显示 (#6705) 2024-10-14 06:29:16 +00:00
John Bro e4a634e6e8 fix: 容器重启前增加校验操作 (#6696) 2024-10-14 10:36:55 +08:00
wild_pointer eb99c10ad0 fix: 修复“修改文件权限时,文件所属用户和用户组不显示”问题 (#6698) 2024-10-14 10:24:48 +08:00
zzzandzengzz01 175b7b89e4 feat:firewalld和ufw同时存在时,不进行操作 (#6688)
Co-authored-by: zengzz01 <zengzz01@mingyuanyun.com>
2024-10-11 18:03:33 +08:00
zhengkunwang 5374afb226 feat: 增加华为云申请证书 (#6687)
Refs https://github.com/1Panel-dev/1Panel/issues/6047
2024-10-11 06:41:05 +00:00
John Bro 1a44f48fad fix: 日志查看统一改成网页全屏以及样式调整 (#6682)
Refs #5873
2024-10-10 10:55:02 +00:00
zhengkunwang 50dbe7cfc0 feat: 增加 Python 运行环境 (#6681) 2024-10-10 18:49:13 +08:00
ssongliu c7e576c879 fix: 概览页磁盘显示排除 overlay 类型 (#6672)
Refs #6637
2024-10-10 03:07:00 +00:00
ssongliu a3e4847744 feat: 快照列表改为异步获取大小 (#6670)
Refs #6540
2024-10-10 03:05:00 +00:00
ssongliu 903f8ed5a7 feat: 容器列表缓存是否显示应用商店容器 (#6667)
Refs #5885
2024-10-10 02:38:59 +00:00
zhengkunwang b091c7f364 feat: 反向代理文本替换增加 sub_filter_types 参数 (#6661)
Refs https://github.com/1Panel-dev/1Panel/issues/6490
2024-10-09 07:02:56 +00:00
zhengkunwang e069db3c45 feat: 反代回源 SNI 支持设置 proxy_ssl_name (#6659)
Refs https://github.com/1Panel-dev/1Panel/issues/5960
2024-10-09 06:42:56 +00:00
see-more 047264dbbc chore;删除error_code组件的body标签 (#6656) 2024-10-09 10:24:47 +08:00
ssongliu 0c216ba87a feat: 支持离线版许可证 (#6651) 2024-10-08 13:56:55 +00:00
ssongliu 31cdc28143 fix: 修改上传恢复提示信息 (#6647) 2024-10-08 02:58:52 +00:00
zhengkunwang e03b728240 feat: 自签证书支持 ipv6 (#6627)
Refs https://github.com/1Panel-dev/1Panel/issues/6623
2024-09-29 10:25:47 +00:00
ssongliu 4c275ba52f style: 修改许可证即将过期样式 (#6616) 2024-09-29 06:37:45 +00:00
ssongliu 2ac9a888a8 feat: 移除编排删除后跳转 (#6613) 2024-09-29 02:15:45 +00:00
Anye f9bdcf8938 feat: 新增 BookStack 的 PHP 扩展模板 (#6611) 2024-09-29 10:01:55 +08:00
ssongliu c3bef26ae6 feat: 调整编排删除逻辑 (#6610) 2024-09-28 22:29:08 +08:00
ssongliu 634849c347 feat: Compose 环境变量编辑增加是否 1Panel 创建校验 (#6605) 2024-09-28 01:37:40 +00:00
John Bro c557b737f6 fix: 重构容器编排环境变量设置逻辑 (#6600) 2024-09-27 14:25:37 +00:00
zhengkunwang 1d326afa93 feat: 优化日志加载 (#6593) 2024-09-26 14:37:32 +00:00
wanghe-fit2cloud cb6c58cb5d Revert "style:优化概览界面的布局 (#6491)"
This reverts commit 7f06c7e92e.
2024-09-25 10:24:33 +08:00
John Bro ec4d708569 feat: 容器编排列表增加编排目录并支持跳转 (#6565)
Refs #3579
2024-09-24 14:25:23 +00:00
John Bro 05b19cefa5 feat: 容器日志下载只支持全量下载 (#6559)
Refs #2399
2024-09-24 14:23:24 +00:00
John Broandzhoujunhong fdd57d0801 feat: 容器编排及模板列表支持排序 (#6566)
Co-authored-by: zhoujunhong <1298308460@qq.com>
2024-09-24 22:21:28 +08:00
Kataick c16d6d1644 flx: 修复会默认出现水平滚动条的问题 (#6564) 2024-09-24 16:01:05 +08:00
John Bro 656b39c880 fix: 添加国际化及样式修改 (#6555) 2024-09-23 10:11:14 +00:00
John Bro 753c996291 fix: 编排模板列表日期格式化 (#6551) 2024-09-23 06:33:15 +00:00
ssongliu dcdd0cc2bb fix: 解决服务包含 .socket 时起停失败的问题 (#6553)
Refs #6524
2024-09-23 06:31:14 +00:00
ssongliu 2f32df16d9 fix: 解决部分浏览器无法打开文件菜单的问题 (#6537) 2024-09-19 10:56:55 +00:00
John Bro 9caa9c47b4 feat: 容器编排显示容器状态,编排模板样式优化 (#6534)
Refs #4404
2024-09-19 10:54:56 +00:00
蘭 55bec8a911 fix: 解决图片/视频预览时,重命名文件名导致浏览器缓存文件内容问题 (#6533)
Refs #6505
2024-09-19 06:00:54 +00:00
John Bro 43f95fd40e fix: 创建和编辑 compose 文件支持设置环境变量 (#6503) 2024-09-18 06:00:49 +00:00
Anye 641812e7c1 fix: 调整Logo长宽比,防止显示被拉伸 (#6510)
* fix: 调整Logo长宽比,防止显示被拉伸
2024-09-17 12:52:58 +08:00
zhengkunwang 95d3e75425 feat: demo 环境增加首页状态接口过滤 (#6498) 2024-09-14 10:12:32 +00:00
see-more 7f06c7e92e style:优化概览界面的布局 (#6491)
* fix: <body> cannot be child of <div>

* style:优化概览界面的布局
2024-09-14 13:43:52 +08:00
ssongliu 0e6fdffeec feat: S3 备份账号支持 Path Style (#6489)
Refs #4553
2024-09-13 10:42:27 +00:00
endymx f77681b9c9 feat(ssl证书): 实现华为云dns (#6407) 2024-09-13 10:53:06 +08:00
John Bro 535d4bbe1b fix: 删除容器后 compose 记录保存以便后续处理 (#6447)
Refs https://github.com/1Panel-dev/1Panel/issues/5099
2024-09-12 10:06:22 +00:00
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
193 changed files with 4653 additions and 2800 deletions
+7 -13
View File
@@ -3,6 +3,8 @@ 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"
"github.com/gin-gonic/gin"
@@ -56,24 +58,16 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
// @Summary Load dashboard current info
// @Description 获取首页实时数据
// @Accept json
// @Param ioOption path string true "request"
// @Param netOption path string true "request"
// @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)
}
+1 -1
View File
@@ -62,7 +62,7 @@ func (b *BaseApi) CreateRuntime(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除网站 [name]","formatEN":"Delete website [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [name]","formatEN":"Delete runtime [name]"}
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
var req request.RuntimeDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+22
View File
@@ -120,6 +120,28 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
})
}
// @Tags System Setting
// @Summary Load system snapshot size
// @Description 获取系统快照大小
// @Accept json
// @Param request body dto.PageSnapshot true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/size [post]
func (b *BaseApi) LoadSnapshotSize(c *gin.Context) {
var req dto.PageSnapshot
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := snapshotService.LoadSize(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags System Setting
// @Summary Recover system backup
// @Description 从系统快照恢复
+2
View File
@@ -82,6 +82,8 @@ type ClamUpdate struct {
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type ClamUpdateStatus struct {
+14 -11
View File
@@ -116,7 +116,7 @@ type PortHelper struct {
type ContainerOperation struct {
Names []string `json:"names" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause remove"`
Operation string `json:"operation" validate:"required,oneof=up start stop restart kill pause unpause remove"`
}
type ContainerRename struct {
@@ -200,6 +200,7 @@ type ComposeInfo struct {
Workdir string `json:"workdir"`
Path string `json:"path"`
Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
}
type ComposeContainer struct {
ContainerID string `json:"containerID"`
@@ -208,22 +209,24 @@ type ComposeContainer struct {
State string `json:"state"`
}
type ComposeCreate struct {
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Env []string `json:"env"`
}
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop down"`
Path string `json:"path"`
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
WithFile bool `json:"withFile"`
}
type ComposeUpdate struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Env []string `json:"env"`
}
type ContainerLog struct {
+17
View File
@@ -35,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"`
@@ -73,6 +79,7 @@ type DashboardCurrent struct {
NetBytesRecv uint64 `json:"netBytesRecv"`
GPUData []GPUInfo `json:"gpuData"`
XPUData []XPUInfo `json:"xpuData"`
ShotTime time.Time `json:"shotTime"`
}
@@ -106,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"`
}
+16 -15
View File
@@ -175,21 +175,22 @@ type WebsiteUpdateDirPermission struct {
}
type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
}
type WebsiteProxyReq struct {
+1
View File
@@ -41,6 +41,7 @@ type WebsiteSSLApply struct {
ID uint `json:"ID" validate:"required"`
SkipDNSCheck bool `json:"skipDNSCheck"`
Nameservers []string `json:"nameservers"`
DisableLog bool `json:"disableLog"`
}
type WebsiteAcmeAccountCreate struct {
+15 -8
View File
@@ -30,6 +30,7 @@ type SettingInfo struct {
ServerPort string `json:"serverPort"`
SSL string `json:"ssl"`
SSLType string `json:"sslType"`
AutoRestart string `json:"autoRestart"`
BindDomain string `json:"bindDomain"`
AllowIPs string `json:"allowIPs"`
SecurityEntrance string `json:"securityEntrance"`
@@ -73,12 +74,13 @@ type SettingUpdate struct {
}
type SSLUpdate struct {
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`
AutoRestart string `json:"autoRestart"`
}
type SSLInfo struct {
Domain string `json:"domain"`
@@ -147,9 +149,8 @@ type SnapshotInfo struct {
DefaultDownload string `json:"defaultDownload"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
CreatedAt time.Time `json:"created_at"`
Version string `json:"version"`
Size int64 `json:"size"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`
@@ -160,6 +161,12 @@ type SnapshotInfo struct {
LastRollbackedAt string `json:"lastRollbackedAt"`
}
type SnapshotFile struct {
ID uint `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
}
type UpgradeInfo struct {
TestVersion string `json:"testVersion"`
NewVersion string `json:"newVersion"`
+1
View File
@@ -12,4 +12,5 @@ type Compose struct {
BaseModel
Name string `gorm:"type:varchar(256)" json:"name"`
Path string `gorm:"type:varchar(256)" json:"path"`
}
+5
View File
@@ -19,6 +19,7 @@ type IComposeTemplateRepo interface {
CreateRecord(compose *model.Compose) error
DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error)
UpdateRecord(name string, vars map[string]interface{}) error
}
func NewIComposeTemplateRepo() IComposeTemplateRepo {
@@ -102,3 +103,7 @@ func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
}
return db.Delete(&model.Compose{}).Error
}
func (u *ComposeTemplateRepo) UpdateRecord(name string, vars map[string]interface{}) error {
return global.DB.Model(&model.Compose{}).Where("name = ?", name).Updates(vars).Error
}
+12
View File
@@ -1342,6 +1342,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
continue
}
lastVersion := versions[0]
if app.Key == constant.AppMysql {
for _, version := range versions {
majorVersion := getMajorVersion(installed.Version)
if !strings.HasPrefix(version, majorVersion) {
continue
} else {
lastVersion = version
break
}
}
}
if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate
} else {
+67 -48
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,7 +445,8 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
}
}
data.ExposedPorts, _ = loadContainerPortForInfo(req.Name, client)
exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
networkSettings := oldContainer.NetworkSettings
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
@@ -897,6 +898,10 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
break
}
}
if len(containers) == 0 {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
filePath = composeItem.Path
}
}
if _, err := os.Stat(filePath); err != nil {
return ""
@@ -1213,7 +1218,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
@@ -1299,47 +1362,3 @@ func simplifyPort(ports []types.Port) []string {
}
return datas
}
func loadContainerPortForInfo(id string, client *client.Client) ([]dto.PortHelper, error) {
var exposedPorts []dto.PortHelper
containers, err := client.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return nil, err
}
samePortMap := make(map[string]dto.PortHelper)
for _, container := range containers {
if container.ID == id {
ports := loadContainerPort(container.Ports)
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 {
continue
}
itemContainer := strings.Split(itemStr[1], "/")
if len(itemContainer) != 2 {
continue
}
itemPort.HostIP = itemStr[0][0:lastIndex]
itemPort.HostPort = itemStr[0][lastIndex+1:]
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, nil
}
+111 -13
View File
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
@@ -31,6 +32,12 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
const composeCreatedBy = "createdBy"
type DockerCompose struct {
Version string `yaml:"version"`
Services map[string]map[string]interface{} `yaml:"services"`
Networks map[string]interface{} `yaml:"networks"`
}
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
var (
records []dto.ComposeInfo
@@ -52,6 +59,20 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
composeCreatedByLocal, _ := composeRepo.ListRecord()
composeLocalMap := make(map[string]dto.ComposeInfo)
for _, localItem := range composeCreatedByLocal {
composeItemLocal := dto.ComposeInfo{
ContainerNumber: 0,
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
ConfigFile: localItem.Path,
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
}
composeItemLocal.CreatedBy = "1Panel"
composeItemLocal.Path = localItem.Path
composeLocalMap[localItem.Name] = composeItemLocal
}
composeMap := make(map[string]dto.ComposeInfo)
for _, container := range list {
if name, ok := container.Labels[composeProjectLabel]; ok {
@@ -95,12 +116,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
}
}
for _, item := range composeCreatedByLocal {
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
global.LOG.Error(err)
mergedMap := make(map[string]dto.ComposeInfo)
for key, localItem := range composeLocalMap {
mergedMap[key] = localItem
}
for key, item := range composeMap {
if existingItem, exists := mergedMap[key]; exists {
if item.ContainerNumber > 0 {
if existingItem.ContainerNumber <= 0 {
mergedMap[key] = item
}
}
} else {
mergedMap[key] = item
}
}
for key, value := range composeMap {
for key, value := range mergedMap {
value.Name = key
records = append(records, value)
}
@@ -127,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
BackDatas = records[start:end]
}
return int64(total), BackDatas, nil
listItem := loadEnv(BackDatas)
return int64(total), listItem, nil
}
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
for i := 0; i < len(list); i++ {
envFilePath := filepath.Join(path.Dir(list[i].Path), "1panel.env")
file, err := os.ReadFile(envFilePath)
if err != nil {
continue
}
lines := strings.Split(string(file), "\n")
for _, line := range lines {
lineItem := strings.TrimSpace(line)
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
list[i].Env = append(list[i].Env, lineItem)
}
}
}
return list
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
@@ -141,6 +193,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := u.loadPath(&req); err != nil {
return false, err
}
if err := newComposeEnv(req.Path, req.Env); err != nil {
return false, err
}
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
@@ -173,6 +228,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
if err != nil {
return "", err
}
if err := newComposeEnv(req.Path, req.Env); err != nil {
return "", err
}
go func() {
defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
@@ -186,7 +244,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
return
}
global.LOG.Infof("docker-compose up %s successful!", req.Name)
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name, Path: req.Path})
_, _ = file.WriteString("docker-compose up successful!")
}()
@@ -194,23 +252,36 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if len(req.Path) == 0 && req.Operation == "delete" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
return nil
}
if cmd.CheckIllegal(req.Path, req.Operation) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
if req.Operation == "down" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
if req.Operation == "delete" {
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
return errors.New(string(stdout))
}
if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path))
}
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
return nil
}
if req.Operation == "up" {
if stdout, err := compose.Up(req.Path); err != nil {
return errors.New(string(stdout))
}
} else {
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
}
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
return nil
}
@@ -232,6 +303,10 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
if err := newComposeEnv(req.Path, req.Env); err != nil {
return err
}
if stdout, err := compose.Up(req.Path); err != nil {
if err := recreateCompose(string(oldFile), req.Path); err != nil {
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
@@ -280,3 +355,26 @@ func recreateCompose(content, path string) error {
}
return nil
}
func newComposeEnv(pathItem string, env []string) error {
if len(env) == 0 {
return nil
}
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
global.LOG.Errorf("failed to create env file: %v", err)
return err
}
defer file.Close()
for _, env := range env {
envItem := strings.TrimSpace(env)
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
global.LOG.Errorf("failed to write env to file: %v", err)
return err
}
}
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
return nil
}
+10
View File
@@ -1,6 +1,7 @@
package service
import (
"errors"
"fmt"
"os"
"path"
@@ -30,6 +31,9 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
if err != nil {
return err
}
if len(appItems) == 0 {
return errors.New("no such app in database!")
}
apps = appItems
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
@@ -65,6 +69,9 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time) error {
webs := loadWebsForJob(cronjob)
if len(webs) == 0 {
return errors.New("no such website in database!")
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
@@ -98,6 +105,9 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time) error {
dbs := loadDbsForJob(cronjob)
if len(dbs) == 0 {
return errors.New("no such db in database!")
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
-1
View File
@@ -85,7 +85,6 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleSnapshot(*cronjob, record.StartTime, record.Records)
}
if err != nil {
if len(message) != 0 {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
+96 -69
View File
@@ -10,8 +10,12 @@ import (
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"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"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -19,9 +23,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{}
@@ -29,7 +30,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
}
@@ -139,78 +140,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
}
}
}
}
@@ -258,13 +269,13 @@ func loadDiskInfo() []dto.DiskInfo {
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
continue
}
if strings.TrimSpace(fields[1]) == "tmpfs" {
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
continue
}
if strings.Contains(fields[2], "K") {
continue
}
if strings.Contains(fields[6], "docker") {
if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
continue
}
isExclude := false
@@ -346,3 +357,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
}
+91 -23
View File
@@ -4,14 +4,17 @@ import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"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/docker"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
)
@@ -198,9 +201,8 @@ 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))
if err := restartDocker(); err != nil {
return err
}
return nil
}
@@ -211,10 +213,12 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
if err := validateDockerConfig(); err != nil {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
if err := restartDocker(); err != nil {
return err
}
return nil
}
@@ -258,9 +262,12 @@ func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
return nil
}
@@ -298,9 +305,12 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
return nil
}
@@ -308,9 +318,8 @@ 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))
if err := restartDocker(); err != nil {
return err
}
return nil
}
@@ -327,21 +336,42 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
_, _ = write.WriteString(req.File)
write.Flush()
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
return nil
}
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
if req.Operation == "stop" {
service = "docker.socket"
}
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
sudo := cmd.SudoHandleCmd()
dockerCmd, err := getDockerRestartCommand()
if err != nil {
return errors.New(string(stdout))
return err
}
if req.Operation == "stop" {
isSocketActive, _ := systemctl.IsActive("docker.socket")
if isSocketActive {
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
if err != nil {
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
}
}
}
if req.Operation == "restart" {
if err := validateDockerConfig(); err != nil {
return err
}
}
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
if err != nil {
return errors.New(stdout)
}
return nil
}
@@ -394,3 +424,41 @@ func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize st
}
}
}
func validateDockerConfig() error {
if !cmd.Which("dockerd") {
return nil
}
stdout, err := cmd.Exec("dockerd --validate")
if strings.Contains(stdout, "unknown flag: --validate") {
return nil
}
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
}
return nil
}
func getDockerRestartCommand() (string, error) {
stdout, err := cmd.Exec("which docker")
if err != nil {
return "", fmt.Errorf("failed to find docker: %v", err)
}
dockerPath := stdout
if strings.Contains(dockerPath, "snap") {
return "snap", nil
}
return "systemctl", nil
}
func restartDocker() error {
restartCmd, err := getDockerRestartCommand()
if err != nil {
return err
}
stdout, err := cmd.Execf("%s restart docker", restartCmd)
if err != nil {
return fmt.Errorf("failed to restart Docker: %s", stdout)
}
return nil
}
+5 -1
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
+12 -7
View File
@@ -179,6 +179,7 @@ func (u *FirewallService) OperateFirewall(operation string) error {
if err != nil {
return err
}
needRestartDocker := false
switch operation {
case "start":
if err := client.Start(); err != nil {
@@ -188,26 +189,30 @@ func (u *FirewallService) OperateFirewall(operation string) error {
_ = client.Stop()
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
needRestartDocker = true
case "stop":
if err := client.Stop(); err != nil {
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
needRestartDocker = true
case "restart":
if err := client.Restart(); err != nil {
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
needRestartDocker = true
case "disablePing":
return u.updatePingStatus("0")
case "enablePing":
return u.updatePingStatus("1")
default:
return fmt.Errorf("not supported operation: %s", operation)
}
return fmt.Errorf("not support such operation: %s", operation)
if needRestartDocker {
if err := restartDocker(); err != nil {
return err
}
}
return nil
}
func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool) error {
+42 -27
View File
@@ -84,11 +84,16 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if imageRepo.ID != 0 {
return constant.ErrRecordExist
}
if req.Protocol == "http" {
_ = u.handleRegistries(req.DownloadUrl, "", "create")
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
@@ -112,23 +117,18 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return err
}
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
}
if err := copier.Copy(&imageRepo, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
imageRepo.Status = constant.StatusSuccess
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
imageRepo.Status = constant.StatusFailed
imageRepo.Message = err.Error()
}
}
if err := imageRepoRepo.Create(&imageRepo); err != nil {
return err
}
return nil
return imageRepoRepo.Create(&imageRepo)
}
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
@@ -154,32 +154,47 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if err != nil {
return err
}
if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) {
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
if repo.Protocol == "http" && req.Protocol == "https" {
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
}
}
if repo.Protocol == "http" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
}
if repo.Protocol == "https" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
}
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
if repo.Auth {
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
}
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
upMap := make(map[string]interface{})
upMap["download_url"] = req.DownloadUrl
upMap["protocol"] = req.Protocol
upMap["username"] = req.Username
upMap["password"] = req.Password
upMap["auth"] = req.Auth
upMap["status"] = constant.StatusSuccess
upMap["message"] = ""
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
upMap["status"] = constant.StatusFailed
upMap["message"] = err.Error()
}
}
return imageRepoRepo.Update(req.ID, upMap)
}
+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
}
+11 -8
View File
@@ -56,7 +56,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
opts []repo.DBOption
)
if create.Name != "" {
opts = append(opts, commonRepo.WithLikeName(create.Name))
opts = append(opts, commonRepo.WithByName(create.Name))
}
if create.Type != "" {
opts = append(opts, commonRepo.WithByType(create.Type))
@@ -83,7 +83,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if exist != nil {
return nil, buserr.New(constant.ErrImageExist)
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
if !fileOp.Stat(create.CodeDir) {
return nil, buserr.New(constant.ErrPathNotFound)
}
@@ -133,7 +133,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
runtime.Port = create.Port
if err = handleNodeAndJava(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
@@ -217,7 +217,7 @@ func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error {
global.LOG.Errorf("delete image id [%s] error %v", imageID, err)
}
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete {
if out != "" {
return errors.New(out)
@@ -300,7 +300,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
}
}
res.AppParams = appParams
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
res.Params = make(map[string]interface{})
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
@@ -308,7 +308,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
}
for k, v := range envs {
switch k {
case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP", "JAVA_APP_PORT", "GO_APP_PORT":
case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP", "JAVA_APP_PORT", "GO_APP_PORT", "APP_PORT":
port, err := strconv.Atoi(v)
if err != nil {
return nil, err
@@ -361,7 +361,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if exist != nil {
return buserr.New(constant.ErrImageExist)
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
if runtime.Port != req.Port {
if err = checkPortExist(req.Port); err != nil {
return err
@@ -441,7 +441,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return err
}
go buildRuntime(runtime, imageID, req.Rebuild)
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
runtime.Version = req.Version
runtime.CodeDir = req.CodeDir
runtime.Port = req.Port
@@ -495,6 +495,9 @@ func (r *RuntimeService) OperateRuntime(req request.RuntimeOperate) error {
_ = runtimeRepo.Save(runtime)
}
}()
go func() {
_ = docker.CreateDefaultDockerNetwork()
}()
switch req.Operate {
case constant.RuntimeUp:
if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
+20 -3
View File
@@ -2,6 +2,7 @@ package service
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
@@ -218,7 +219,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
_ = logFile.Close()
}()
cmd := exec.Command("docker-compose", "-f", composePath, "build")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
defer cancel()
cmd := exec.CommandContext(ctx, "docker-compose", "-f", composePath, "build")
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
cmd.Stdout = multiWriterStdout
var stderrBuf bytes.Buffer
@@ -228,7 +232,11 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
err = cmd.Run()
if err != nil {
runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + buserr.New("ErrCmdTimeout").Error()
} else {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
}
} else {
runtime.Status = constant.RuntimeNormal
runtime.Message = ""
@@ -338,6 +346,14 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
if err != nil {
return
}
case constant.RuntimePython:
create.Params["CODE_DIR"] = create.CodeDir
create.Params["PYTHON_VERSION"] = create.Version
create.Params["PANEL_APP_PORT_HTTP"] = create.Port
composeContent, err = handleCompose(env, composeContent, create, projectDir)
if err != nil {
return
}
}
newMap := make(map[string]string)
@@ -384,7 +400,8 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${JAVA_APP_PORT}")
case constant.RuntimeGo:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${GO_APP_PORT}")
case constant.RuntimePython:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${APP_PORT}")
}
for i, port := range create.ExposedPorts {
+3
View File
@@ -322,6 +322,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err
}
if err := settingRepo.Update("AutoRestart", req.AutoRestart); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
+25 -11
View File
@@ -28,6 +28,7 @@ type SnapshotService struct {
type ISnapshotService interface {
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
LoadSize(req dto.PageSnapshot) ([]dto.SnapshotFile, error)
SnapshotCreate(req dto.SnapshotCreate) error
SnapshotRecover(req dto.SnapshotRecover) error
SnapshotRollback(req dto.SnapshotRecover) error
@@ -47,15 +48,31 @@ func NewISnapshotService() ISnapshotService {
}
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))
total, records, 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
var data []dto.SnapshotInfo
for _, record := range records {
var item dto.SnapshotInfo
if err := copier.Copy(&item, &record); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
data = append(data, item)
}
return total, dtoSnap, err
return total, data, err
}
func (u *SnapshotService) LoadSize(req dto.PageSnapshot) ([]dto.SnapshotFile, error) {
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if err != nil {
return nil, err
}
data, err := loadSnapSize(records)
if err != nil {
return nil, err
}
return data, err
}
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
@@ -510,15 +527,12 @@ func loadOs() string {
}
}
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotInfo, error) {
var datas []dto.SnapshotInfo
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotFile, error) {
var datas []dto.SnapshotFile
clientMap := make(map[string]loadSizeHelper)
var wg sync.WaitGroup
for i := 0; i < len(records); i++ {
var item dto.SnapshotInfo
if err := copier.Copy(&item, &records[i]); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item := dto.SnapshotFile{Name: records[i].Name, ID: records[i].ID}
itemPath := fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)
if _, ok := clientMap[records[i].DefaultDownload]; !ok {
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].DefaultDownload))
+3 -1
View File
@@ -231,7 +231,9 @@ func recoverDaemonJson(src string, fileOp files.FileOp) error {
}
}
_, _ = cmd.Exec("systemctl restart docker")
if err := restartDocker(); err != nil {
return err
}
return nil
}
+10
View File
@@ -124,6 +124,16 @@ func (u *SSHService) OperateSSH(operation string) error {
if operation == "enable" || operation == "disable" {
serviceName += ".service"
}
if operation == "stop" {
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
if isSocketActive {
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
if err != nil {
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
}
}
}
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
if err != nil {
if strings.Contains(stdout, "alias name or linked unit file") {
+14 -2
View File
@@ -309,6 +309,10 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
if err != nil {
return err
}
if err = docker.CreateDefaultDockerNetwork(); err != nil {
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
return
}
website.RuntimeID = runtime.ID
switch runtime.Type {
case constant.RuntimePHP:
@@ -352,7 +356,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
}
website.Proxy = proxy
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
}
}
@@ -641,8 +645,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)
@@ -1665,6 +1671,9 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
}
if req.SNI {
location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
if req.ProxySSLName != "" {
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
}
} else {
location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
}
@@ -1747,6 +1756,9 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
if directive.GetName() == "proxy_ssl_server_name" {
proxyConfig.SNI = directive.GetParameters()[0] == "on"
}
if directive.GetName() == "proxy_ssl_name" {
proxyConfig.ProxySSLName = directive.GetParameters()[0]
}
}
res = append(res, proxyConfig)
}
+10 -11
View File
@@ -217,15 +217,14 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
} else {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
if ipAddress := net.ParseIP(domain); ipAddress == nil {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
}
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
}
}
if len(domains) > 0 {
@@ -432,14 +431,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))
+21 -12
View File
@@ -181,6 +181,13 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
return create, nil
}
func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{}, disableLog bool) {
if disableLog {
return
}
logger.Println(i18n.GetMsgWithMap(msgKey, params))
}
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var (
err error
@@ -272,11 +279,13 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags)
legoLogger.Logger = logger
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
if !apply.DisableLog {
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
legoLogger.Logger.Println(startMsg)
}
legoLogger.Logger.Println(startMsg)
resource, err := client.ObtainSSL(domains, privateKey)
if err != nil {
handleError(websiteSSL, err)
@@ -296,7 +305,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.Status = constant.SSLReady
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
printSSLLog(logger, "ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}, apply.DisableLog)
saveCertificateFile(websiteSSL, logger)
if websiteSSL.ExecShell {
@@ -304,11 +313,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
if websiteSSL.PushDir {
workDir = websiteSSL.Dir
}
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellStart"))
printSSLLog(logger, "ExecShellStart", nil, apply.DisableLog)
if err = cmd.ExecShellWithTimeOut(websiteSSL.Shell, workDir, logger, 30*time.Minute); err != nil {
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()}))
printSSLLog(logger, "ErrExecShell", map[string]interface{}{"err": err.Error()}, apply.DisableLog)
} else {
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
printSSLLog(logger, "ExecShellSuccess", nil, apply.DisableLog)
}
}
@@ -320,9 +329,9 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) > 0 {
for _, website := range websites {
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}))
printSSLLog(logger, "ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}, apply.DisableLog)
if err := createPemFile(website, *websiteSSL); err != nil {
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}))
printSSLLog(logger, "ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}, apply.DisableLog)
}
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
@@ -330,10 +339,10 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
return
}
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
legoLogger.Logger.Println(i18n.GetMsgByKey(constant.ErrSSLApply))
printSSLLog(logger, constant.ErrSSLApply, nil, apply.DisableLog)
return
}
legoLogger.Logger.Println(i18n.GetMsgByKey("ApplyWebSiteSSLSuccess"))
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
}
}()
+15 -8
View File
@@ -3,7 +3,6 @@ package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"log"
"os"
"path"
@@ -13,6 +12,8 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
@@ -276,7 +277,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
server.UpdateRoot(rootIndex)
server.UpdatePHPProxy([]string{website.Proxy}, "")
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port)
server.UpdateRootProxy([]string{proxy})
}
@@ -1001,20 +1002,23 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
}
}
func GetSystemSSL() (bool, uint) {
func GetSystemSSL() (bool, bool, uint) {
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
return false, 0
return false, false, 0
}
if sslSetting.Value == "enable" {
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
idValue, _ := strconv.Atoi(sslID.Value)
if idValue > 0 {
return true, uint(idValue)
if idValue <= 0 {
return false, false, 0
}
auto, _ := settingRepo.Get(settingRepo.WithByKey("AutoRestart"))
return true, auto.Value == "enable", uint(idValue)
}
return false, 0
return false, false, 0
}
func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
@@ -1033,7 +1037,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
return buserr.WithErr(constant.ErrSSLApply, err)
}
}
enable, sslID := GetSystemSSL()
enable, auto, sslID := GetSystemSSL()
if enable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
@@ -1045,6 +1049,9 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
return nil
}
+5 -4
View File
@@ -14,10 +14,11 @@ const (
RuntimeUnhealthy = "unhealthy"
RuntimeCreating = "creating"
RuntimePHP = "php"
RuntimeNode = "node"
RuntimeJava = "java"
RuntimeGo = "go"
RuntimePHP = "php"
RuntimeNode = "node"
RuntimeJava = "java"
RuntimeGo = "go"
RuntimePython = "python"
RuntimeProxyUnix = "unix"
RuntimeProxyTcp = "tcp"
+7 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
@@ -22,7 +23,7 @@ func NewSSLJob() *ssl {
}
func (ssl *ssl) Run() {
systemSSLEnable, sslID := service.GetSystemSSL()
systemSSLEnable, auto, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
@@ -51,7 +52,8 @@ func (ssl *ssl) Run() {
}
} else {
if err := sslService.ObtainSSL(request.WebsiteSSLApply{
ID: s.ID,
ID: s.ID,
DisableLog: true,
}); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue
@@ -69,6 +71,9 @@ func (ssl *ssl) Run() {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
}
+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,
+2
View File
@@ -178,6 +178,8 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
}
sudo := cmd.SudoHandleCmd()
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
return
}
if strings.Contains(global.CONF.System.ChangeUserInfo, "username") {
+3
View File
@@ -94,6 +94,9 @@ func Init() {
migrations.AddClam,
migrations.AddClamStatus,
migrations.AddAlertMenu,
migrations.AddComposeColumn,
migrations.AddAutoRestart,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -314,3 +314,23 @@ var AddAlertMenu = &gormigrate.Migration{
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
},
}
var AddComposeColumn = &gormigrate.Migration{
ID: "20240906-add-compose-command",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Compose{}); err != nil {
return err
}
return nil
},
}
var AddAutoRestart = &gormigrate.Migration{
ID: "20241021-add-auto-restart",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "AutoRestart", Value: "enable"}).Error; err != nil {
return err
}
return nil
},
}
@@ -127,6 +127,9 @@ var AddTablePHPExtensions = &gormigrate.Migration{
if err := tx.Create(&model.PHPExtensions{Name: "WordPress", Extensions: "exif,igbinary,imagick,intl,zip,apcu,memcached,opcache,redis,bc,image,shmop,mysqli,pdo_mysql,gd"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "BookStack", Extensions: "gd,dom,iconv,mbstring,mysqlnd,openssl,pdo,pdo_mysql,tokenizer,xml"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "Flarum", Extensions: "curl,gd,pdo_mysql,mysqli,bz2,exif,yaf,imap"}).Error; err != nil {
return err
}
+1
View File
@@ -19,6 +19,7 @@ var whiteUrlList = map[string]struct{}{
"/api/v1/logs/operation": {},
"/api/v1/logs/login": {},
"/api/v1/auth/logout": {},
"/api/v1/dashboard/current": {},
"/api/v1/apps/installed/loadport": {},
"/api/v1/apps/installed/check": {},
+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)
}
}
+1
View File
@@ -37,6 +37,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
settingRouter.POST("/snapshot/status", baseApi.LoadSnapShotStatus)
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
settingRouter.POST("/snapshot/size", baseApi.LoadSnapshotSize)
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)
+5 -1
View File
@@ -27,12 +27,16 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
if len(scType) == 0 {
scType = "Standard"
}
mode := loadParamFromVars("mode", vars)
if len(mode) == 0 {
mode = "virtual hosted"
}
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
Endpoint: aws.String(endpoint),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(false),
S3ForcePathStyle: aws.Bool(mode == "path"),
})
if err != nil {
return nil, err
+1 -1
View File
@@ -204,7 +204,7 @@ func SudoHandleCmd() string {
func Which(name string) bool {
stdout, err := Execf("which %s", name)
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) {
return false
}
return true
+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
+9 -2
View File
@@ -29,10 +29,17 @@ type FirewallClient interface {
}
func NewFirewallClient() (FirewallClient, error) {
if _, err := os.Stat("/usr/sbin/firewalld"); err == nil {
_, firewalldErr := os.Stat("/usr/sbin/firewalld")
_, ufwErr := os.Stat("/usr/sbin/ufw")
if firewalldErr == nil && ufwErr == nil {
return nil, buserr.New("firewalld and ufw both found, only one firewall should be active")
}
if firewalldErr == nil {
return client.NewFirewalld()
}
if _, err := os.Stat("/usr/sbin/ufw"); err == nil {
if ufwErr == nil {
return client.NewUfw()
}
return nil, buserr.New(constant.ErrFirewall)
+1
View File
@@ -35,6 +35,7 @@ var repeatKeys = map[string]struct {
"include": {},
"sub_filter": {},
"add_header": {},
"error_page": {},
}
func IsRepeatKey(key string) bool {
@@ -234,11 +234,13 @@ func (l *Location) AddSubFilter(subFilters map[string]string) {
}
l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.UpdateDirective("sub_filter_once", []string{"off"})
l.UpdateDirective("sub_filter_types", []string{"*"})
}
func (l *Location) RemoveSubFilter() {
l.RemoveDirective("sub_filter", []string{})
l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.RemoveDirective("sub_filter_once", []string{"off"})
l.RemoveDirective("sub_filter_types", []string{"*"})
l.Replaces = nil
}
+13 -1
View File
@@ -18,6 +18,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/dnspod"
"github.com/go-acme/lego/v4/providers/dns/godaddy"
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
"github.com/go-acme/lego/v4/providers/dns/namecheap"
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
"github.com/go-acme/lego/v4/providers/dns/namesilo"
@@ -72,6 +73,7 @@ const (
NameCom DnsType = "NameCom"
Godaddy DnsType = "Godaddy"
TencentCloud DnsType = "TencentCloud"
HuaweiCloud DnsType = "HuaweiCloud"
)
type DNSParam struct {
@@ -84,6 +86,7 @@ type DNSParam struct {
APIUser string `json:"apiUser"`
APISecret string `json:"apiSecret"`
SecretID string `json:"secretID"`
Region string `json:"region"`
}
var (
@@ -166,6 +169,15 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
tencentCloudConfig.PollingInterval = pollingInterval
tencentCloudConfig.TTL = ttl
p, err = tencentcloud.NewDNSProviderConfig(tencentCloudConfig)
case HuaweiCloud:
huaweiCloudConfig := huaweicloud.NewDefaultConfig()
huaweiCloudConfig.AccessKeyID = param.AccessKey
huaweiCloudConfig.SecretAccessKey = param.SecretKey
huaweiCloudConfig.Region = param.Region
huaweiCloudConfig.PropagationTimeout = propagationTimeout
huaweiCloudConfig.PollingInterval = pollingInterval
huaweiCloudConfig.TTL = int32(ttl)
p, err = huaweicloud.NewDNSProviderConfig(huaweiCloudConfig)
}
if err != nil {
return err
@@ -187,7 +199,7 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
dns01.CondOption(len(nameservers) > 0,
dns01.AddRecursiveNameservers(nameservers)),
dns01.CondOption(websiteSSL.SkipDNS,
dns01.DisableCompletePropagationRequirement()),
dns01.DisableAuthoritativeNssPropagationRequirement()),
dns01.AddDNSTimeout(10*time.Minute),
)
}
+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)
}
+1 -1
View File
@@ -2,7 +2,7 @@ system:
db_file: 1Panel.db
base_dir: /opt
mode: dev
repo_url: https://resource.fit2cloud.com/1panel/package
repo_url: https://resource.fit2cloud.com/1panel/package/aliyun
app_repo: https://apps-assets.fit2cloud.com
is_demo: false
port: 9999
+192 -20
View File
@@ -3872,8 +3872,8 @@ const docTemplate = `{
}
}
},
"/dashboard/current/:ioOption/:netOption": {
"get": {
"/dashboard/current": {
"post": {
"security": [
{
"ApiKeyAuth": []
@@ -3889,18 +3889,13 @@ const docTemplate = `{
"summary": "Load dashboard current info",
"parameters": [
{
"type": "string",
"description": "request",
"name": "ioOption",
"in": "path",
"required": true
},
{
"type": "string",
"description": "request",
"name": "netOption",
"in": "path",
"required": true
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.DashboardReq"
}
}
],
"responses": {
@@ -9130,8 +9125,8 @@ const docTemplate = `{
"bodyKeys": [
"id"
],
"formatEN": "Delete website [name]",
"formatZH": "删除网站 [name]",
"formatEN": "Delete runtime [name]",
"formatZH": "删除运行环境 [name]",
"paramKeys": []
}
}
@@ -10808,7 +10803,7 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SearchWithPage"
"$ref": "#/definitions/dto.PageSnapshot"
}
}
],
@@ -10822,6 +10817,39 @@ const docTemplate = `{
}
}
},
"/settings/snapshot/size": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统快照大小",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Load system snapshot size",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageSnapshot"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/settings/snapshot/status": {
"post": {
"security": [
@@ -15591,6 +15619,12 @@ const docTemplate = `{
"dto.ClamCreate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15689,6 +15723,12 @@ const docTemplate = `{
"dto.ClamUpdate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15805,6 +15845,9 @@ const docTemplate = `{
"detailName": {
"type": "string"
},
"fileName": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -15876,6 +15919,12 @@ const docTemplate = `{
"from"
],
"properties": {
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"file": {
"type": "string"
},
@@ -15902,8 +15951,7 @@ const docTemplate = `{
"type": "object",
"required": [
"name",
"operation",
"path"
"operation"
],
"properties": {
"name": {
@@ -15912,9 +15960,11 @@ const docTemplate = `{
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"down"
"down",
"delete"
]
},
"path": {
@@ -15987,6 +16037,12 @@ const docTemplate = `{
"content": {
"type": "string"
},
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"
},
@@ -16162,6 +16218,7 @@ const docTemplate = `{
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"restart",
@@ -16309,6 +16366,12 @@ const docTemplate = `{
"type"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16383,9 +16446,16 @@ const docTemplate = `{
"required": [
"id",
"name",
"spec"
"spec",
"type"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16432,6 +16502,9 @@ const docTemplate = `{
"spec": {
"type": "string"
},
"type": {
"type": "string"
},
"url": {
"type": "string"
},
@@ -16557,6 +16630,9 @@ const docTemplate = `{
"dto.DashboardBase": {
"type": "object",
"properties": {
"SystemProxy": {
"type": "string"
},
"appInstalledNumber": {
"type": "integer"
},
@@ -16581,6 +16657,9 @@ const docTemplate = `{
"hostname": {
"type": "string"
},
"ipv4Addr": {
"type": "string"
},
"kernelArch": {
"type": "string"
},
@@ -16705,6 +16784,26 @@ const docTemplate = `{
},
"uptime": {
"type": "integer"
},
"xpuData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.XPUInfo"
}
}
}
},
"dto.DashboardReq": {
"type": "object",
"properties": {
"ioOption": {
"type": "string"
},
"netOption": {
"type": "string"
},
"scope": {
"type": "string"
}
}
},
@@ -18608,6 +18707,41 @@ const docTemplate = `{
}
}
},
"dto.PageSnapshot": {
"type": "object",
"required": [
"order",
"orderBy",
"page",
"pageSize"
],
"properties": {
"info": {
"type": "string"
},
"order": {
"type": "string",
"enum": [
"null",
"ascending",
"descending"
]
},
"orderBy": {
"type": "string",
"enum": [
"name",
"created_at"
]
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.PasswordUpdate": {
"type": "object",
"required": [
@@ -19273,6 +19407,9 @@ const docTemplate = `{
"sslType"
],
"properties": {
"autoRestart": {
"type": "string"
},
"cert": {
"type": "string"
},
@@ -19501,6 +19638,9 @@ const docTemplate = `{
"appStoreVersion": {
"type": "string"
},
"autoRestart": {
"type": "string"
},
"bindAddress": {
"type": "string"
},
@@ -19925,6 +20065,32 @@ const docTemplate = `{
}
}
},
"dto.XPUInfo": {
"type": "object",
"properties": {
"deviceID": {
"type": "integer"
},
"deviceName": {
"type": "string"
},
"memory": {
"type": "string"
},
"memoryUsed": {
"type": "string"
},
"memoryUtil": {
"type": "string"
},
"power": {
"type": "string"
},
"temperature": {
"type": "string"
}
}
},
"files.FileInfo": {
"type": "object",
"properties": {
@@ -22434,6 +22600,9 @@ const docTemplate = `{
"proxyPass": {
"type": "string"
},
"proxySSLName": {
"type": "string"
},
"replaces": {
"type": "object",
"additionalProperties": {
@@ -22476,6 +22645,9 @@ const docTemplate = `{
"ID": {
"type": "integer"
},
"disableLog": {
"type": "boolean"
},
"nameservers": {
"type": "array",
"items": {
+192 -20
View File
@@ -3866,8 +3866,8 @@
}
}
},
"/dashboard/current/:ioOption/:netOption": {
"get": {
"/dashboard/current": {
"post": {
"security": [
{
"ApiKeyAuth": []
@@ -3883,18 +3883,13 @@
"summary": "Load dashboard current info",
"parameters": [
{
"type": "string",
"description": "request",
"name": "ioOption",
"in": "path",
"required": true
},
{
"type": "string",
"description": "request",
"name": "netOption",
"in": "path",
"required": true
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.DashboardReq"
}
}
],
"responses": {
@@ -9124,8 +9119,8 @@
"bodyKeys": [
"id"
],
"formatEN": "Delete website [name]",
"formatZH": "删除网站 [name]",
"formatEN": "Delete runtime [name]",
"formatZH": "删除运行环境 [name]",
"paramKeys": []
}
}
@@ -10802,7 +10797,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SearchWithPage"
"$ref": "#/definitions/dto.PageSnapshot"
}
}
],
@@ -10816,6 +10811,39 @@
}
}
},
"/settings/snapshot/size": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统快照大小",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Load system snapshot size",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageSnapshot"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/settings/snapshot/status": {
"post": {
"security": [
@@ -15585,6 +15613,12 @@
"dto.ClamCreate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15683,6 +15717,12 @@
"dto.ClamUpdate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15799,6 +15839,9 @@
"detailName": {
"type": "string"
},
"fileName": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -15870,6 +15913,12 @@
"from"
],
"properties": {
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"file": {
"type": "string"
},
@@ -15896,8 +15945,7 @@
"type": "object",
"required": [
"name",
"operation",
"path"
"operation"
],
"properties": {
"name": {
@@ -15906,9 +15954,11 @@
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"down"
"down",
"delete"
]
},
"path": {
@@ -15981,6 +16031,12 @@
"content": {
"type": "string"
},
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"
},
@@ -16156,6 +16212,7 @@
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"restart",
@@ -16303,6 +16360,12 @@
"type"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16377,9 +16440,16 @@
"required": [
"id",
"name",
"spec"
"spec",
"type"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16426,6 +16496,9 @@
"spec": {
"type": "string"
},
"type": {
"type": "string"
},
"url": {
"type": "string"
},
@@ -16551,6 +16624,9 @@
"dto.DashboardBase": {
"type": "object",
"properties": {
"SystemProxy": {
"type": "string"
},
"appInstalledNumber": {
"type": "integer"
},
@@ -16575,6 +16651,9 @@
"hostname": {
"type": "string"
},
"ipv4Addr": {
"type": "string"
},
"kernelArch": {
"type": "string"
},
@@ -16699,6 +16778,26 @@
},
"uptime": {
"type": "integer"
},
"xpuData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.XPUInfo"
}
}
}
},
"dto.DashboardReq": {
"type": "object",
"properties": {
"ioOption": {
"type": "string"
},
"netOption": {
"type": "string"
},
"scope": {
"type": "string"
}
}
},
@@ -18602,6 +18701,41 @@
}
}
},
"dto.PageSnapshot": {
"type": "object",
"required": [
"order",
"orderBy",
"page",
"pageSize"
],
"properties": {
"info": {
"type": "string"
},
"order": {
"type": "string",
"enum": [
"null",
"ascending",
"descending"
]
},
"orderBy": {
"type": "string",
"enum": [
"name",
"created_at"
]
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.PasswordUpdate": {
"type": "object",
"required": [
@@ -19267,6 +19401,9 @@
"sslType"
],
"properties": {
"autoRestart": {
"type": "string"
},
"cert": {
"type": "string"
},
@@ -19495,6 +19632,9 @@
"appStoreVersion": {
"type": "string"
},
"autoRestart": {
"type": "string"
},
"bindAddress": {
"type": "string"
},
@@ -19919,6 +20059,32 @@
}
}
},
"dto.XPUInfo": {
"type": "object",
"properties": {
"deviceID": {
"type": "integer"
},
"deviceName": {
"type": "string"
},
"memory": {
"type": "string"
},
"memoryUsed": {
"type": "string"
},
"memoryUtil": {
"type": "string"
},
"power": {
"type": "string"
},
"temperature": {
"type": "string"
}
}
},
"files.FileInfo": {
"type": "object",
"properties": {
@@ -22428,6 +22594,9 @@
"proxyPass": {
"type": "string"
},
"proxySSLName": {
"type": "string"
},
"replaces": {
"type": "object",
"additionalProperties": {
@@ -22470,6 +22639,9 @@
"ID": {
"type": "integer"
},
"disableLog": {
"type": "boolean"
},
"nameservers": {
"type": "array",
"items": {
+128 -14
View File
@@ -233,6 +233,10 @@ definitions:
type: object
dto.ClamCreate:
properties:
alertCount:
type: integer
alertTitle:
type: string
description:
type: string
infectedDir:
@@ -297,6 +301,10 @@ definitions:
type: object
dto.ClamUpdate:
properties:
alertCount:
type: integer
alertTitle:
type: string
description:
type: string
id:
@@ -371,6 +379,8 @@ definitions:
properties:
detailName:
type: string
fileName:
type: string
name:
type: string
secret:
@@ -424,6 +434,10 @@ definitions:
type: object
dto.ComposeCreate:
properties:
env:
items:
type: string
type: array
file:
type: string
from:
@@ -447,9 +461,11 @@ definitions:
type: string
operation:
enum:
- up
- start
- stop
- down
- delete
type: string
path:
type: string
@@ -458,7 +474,6 @@ definitions:
required:
- name
- operation
- path
type: object
dto.ComposeTemplateCreate:
properties:
@@ -497,6 +512,10 @@ definitions:
properties:
content:
type: string
env:
items:
type: string
type: array
name:
type: string
path:
@@ -614,6 +633,7 @@ definitions:
type: array
operation:
enum:
- up
- start
- stop
- restart
@@ -713,6 +733,10 @@ definitions:
type: object
dto.CronjobCreate:
properties:
alertCount:
type: integer
alertTitle:
type: string
appID:
type: string
backupAccounts:
@@ -765,6 +789,10 @@ definitions:
type: object
dto.CronjobUpdate:
properties:
alertCount:
type: integer
alertTitle:
type: string
appID:
type: string
backupAccounts:
@@ -796,6 +824,8 @@ definitions:
type: string
spec:
type: string
type:
type: string
url:
type: string
website:
@@ -804,6 +834,7 @@ definitions:
- id
- name
- spec
- type
type: object
dto.CronjobUpdateStatus:
properties:
@@ -883,6 +914,8 @@ definitions:
type: object
dto.DashboardBase:
properties:
SystemProxy:
type: string
appInstalledNumber:
type: integer
cpuCores:
@@ -899,6 +932,8 @@ definitions:
type: integer
hostname:
type: string
ipv4Addr:
type: string
kernelArch:
type: string
kernelVersion:
@@ -982,6 +1017,19 @@ definitions:
type: string
uptime:
type: integer
xpuData:
items:
$ref: '#/definitions/dto.XPUInfo'
type: array
type: object
dto.DashboardReq:
properties:
ioOption:
type: string
netOption:
type: string
scope:
type: string
type: object
dto.DatabaseCreate:
properties:
@@ -2278,6 +2326,31 @@ definitions:
total:
type: integer
type: object
dto.PageSnapshot:
properties:
info:
type: string
order:
enum:
- "null"
- ascending
- descending
type: string
orderBy:
enum:
- name
- created_at
type: string
page:
type: integer
pageSize:
type: integer
required:
- order
- orderBy
- page
- pageSize
type: object
dto.PasswordUpdate:
properties:
newPassword:
@@ -2723,6 +2796,8 @@ definitions:
type: object
dto.SSLUpdate:
properties:
autoRestart:
type: string
cert:
type: string
domain:
@@ -2882,6 +2957,8 @@ definitions:
type: string
appStoreVersion:
type: string
autoRestart:
type: string
bindAddress:
type: string
bindDomain:
@@ -3161,6 +3238,23 @@ definitions:
type:
type: string
type: object
dto.XPUInfo:
properties:
deviceID:
type: integer
deviceName:
type: string
memory:
type: string
memoryUsed:
type: string
memoryUtil:
type: string
power:
type: string
temperature:
type: string
type: object
files.FileInfo:
properties:
content:
@@ -4836,6 +4930,8 @@ definitions:
type: string
proxyPass:
type: string
proxySSLName:
type: string
replaces:
additionalProperties:
type: string
@@ -4868,6 +4964,8 @@ definitions:
properties:
ID:
type: integer
disableLog:
type: boolean
nameservers:
items:
type: string
@@ -7981,22 +8079,18 @@ paths:
summary: Load os info
tags:
- Dashboard
/dashboard/current/:ioOption/:netOption:
get:
/dashboard/current:
post:
consumes:
- application/json
description: 获取首页实时数据
parameters:
- description: request
in: path
name: ioOption
in: body
name: request
required: true
type: string
- description: request
in: path
name: netOption
required: true
type: string
schema:
$ref: '#/definitions/dto.DashboardReq'
responses:
"200":
description: OK
@@ -11310,8 +11404,8 @@ paths:
BeforeFunctions: []
bodyKeys:
- id
formatEN: Delete website [name]
formatZH: 删除网站 [name]
formatEN: Delete runtime [name]
formatZH: 删除运行环境 [name]
paramKeys: []
/runtimes/node/modules:
post:
@@ -12368,7 +12462,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/dto.SearchWithPage'
$ref: '#/definitions/dto.PageSnapshot'
responses:
"200":
description: OK
@@ -12379,6 +12473,26 @@ paths:
summary: Page system snapshot
tags:
- System Setting
/settings/snapshot/size:
post:
consumes:
- application/json
description: 获取系统快照大小
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PageSnapshot'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Load system snapshot size
tags:
- System Setting
/settings/snapshot/status:
post:
consumes:
+3 -1
View File
@@ -8,6 +8,8 @@ location ^~ /test {
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;
proxy_ssl_name $proxy_host;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+1 -1
View File
@@ -41,7 +41,7 @@
"highlight.js": "^11.9.0",
"js-base64": "^3.7.7",
"md-editor-v3": "^2.11.3",
"monaco-editor": "^0.34.1",
"monaco-editor": "^0.50.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^1.6.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+1
View File
@@ -6,6 +6,7 @@ export namespace Backup {
type: string;
accessKey: string;
bucket: string;
bucketInput: boolean;
credential: string;
backupPath: string;
vars: string;
+6
View File
@@ -241,6 +241,8 @@ export namespace Container {
path: string;
containers: Array<ComposeContainer>;
expand: boolean;
envStr: string;
env: Array<string>;
}
export interface ComposeContainer {
name: string;
@@ -254,6 +256,8 @@ export namespace Container {
file: string;
path: string;
template: number;
env: Array<string>;
envStr: string;
}
export interface ComposeOperation {
name: string;
@@ -265,6 +269,8 @@ export namespace Container {
name: string;
path: string;
content: string;
env: Array<string>;
createdBy: string;
}
export interface TemplateCreate {
+17
View File
@@ -64,6 +64,7 @@ export namespace Dashboard {
diskData: Array<DiskInfo>;
gpuData: Array<GPUInfo>;
xpuData: Array<XPUInfo>;
netBytesSent: number;
netBytesRecv: number;
@@ -94,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;
+9 -1
View File
@@ -29,6 +29,7 @@ export namespace Setting {
bindAddress: string;
ssl: string;
sslType: string;
autoRestart: string;
allowIPs: string;
bindDomain: string;
securityEntrance: string;
@@ -134,7 +135,7 @@ export namespace Setting {
description: string;
status: string;
message: string;
createdAt: DateTimeFormats;
created_at: DateTimeFormats;
version: string;
interruptStep: string;
recoverStatus: string;
@@ -145,6 +146,11 @@ export namespace Setting {
lastRollbackedAt: string;
secret: string;
}
export interface SnapshotFile {
id: number;
name: string;
size: number;
}
export interface SnapshotStatus {
panel: string;
panelInfo: string;
@@ -169,11 +175,13 @@ export namespace Setting {
assigneeName: string;
productPro: string;
trial: boolean;
offline: boolean;
status: string;
message: string;
}
export interface LicenseStatus {
productPro: string;
trial: boolean;
status: string;
}
}
+2
View File
@@ -373,6 +373,8 @@ export namespace Website {
content?: string;
proxyAddress?: string;
proxyProtocol?: string;
sni: boolean;
proxySSLName: string;
}
export interface ProxReplace {
+1 -1
View File
@@ -43,7 +43,7 @@ export const containerRename = (params: Container.ContainerRename) => {
return http.post(`/containers/rename`, params);
};
export const containerOperator = (params: Container.ContainerOperate) => {
return http.post(`/containers/operate`, params);
return http.post(`/containers/operate`, params, TimeoutEnum.T_60S);
};
export const containerPrune = (params: Container.ContainerPrune) => {
return http.post<Container.ContainerPruneReport>(`/containers/prune`, params);
+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) => {
+3
View File
@@ -196,6 +196,9 @@ export const snapshotRollback = (param: Setting.SnapshotRecover) => {
export const searchSnapshotPage = (param: SearchWithPage) => {
return http.post<ResPage<Setting.SnapshotInfo>>(`/settings/snapshot/search`, param);
};
export const loadSnapshotSize = (param: SearchWithPage) => {
return http.post<Array<Setting.SnapshotFile>>(`/settings/snapshot/size`, param);
};
// upgrade
export const loadUpgradeInfo = () => {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+70 -88
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" />
@@ -114,7 +107,7 @@
<script lang="ts" setup>
import { CheckAppInstalled, InstalledOp } from '@/api/modules/app';
import router from '@/routers';
import { onMounted, reactive, ref, watch } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import Status from '@/components/status/index.vue';
import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
@@ -132,21 +125,6 @@ const props = defineProps({
},
});
watch(
() => props.appKey,
(val) => {
key.value = val;
onCheck();
},
);
watch(
() => props.appName,
(val) => {
name.value = val;
onCheck();
},
);
let key = ref('');
let name = ref('');
@@ -257,4 +235,8 @@ onMounted(() => {
name.value = props.appName;
onCheck();
});
defineExpose({
onCheck,
});
</script>
+2 -2
View File
@@ -77,7 +77,7 @@
:title="isBackup ? $t('commons.button.backup') : $t('commons.button.recover') + ' - ' + name"
width="40%"
:close-on-click-modal="false"
:before-close="handleClose"
:before-close="handleBackupClose"
>
<el-form ref="backupForm" label-position="left" v-loading="loading">
<el-form-item
@@ -90,7 +90,7 @@
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">
<el-button @click="handleBackupClose" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">
@@ -95,15 +95,14 @@ const logSearch = reactive({
const handleClose = () => {
terminalSocket.value?.send('close conn');
open.value = false;
globalStore.isFullScreen = false;
};
function toggleFullscreen() {
if (screenfull.isEnabled) {
screenfull.toggle();
}
globalStore.isFullScreen = !globalStore.isFullScreen;
}
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
};
watch(logVisible, (val) => {
@@ -214,6 +213,9 @@ defineExpose({
</script>
<style scoped lang="scss">
.fullScreen {
border: none;
}
.selectWidth {
width: 200px;
}
@@ -1,16 +1,14 @@
<template>
<div>
<body>
<div>
<el-row type="flex" justify="center">
<h1>{{ loadErrInfo() }}</h1>
</el-row>
</div>
<hr />
<div>
<el-row type="flex" justify="center"><span>nginx</span></el-row>
</div>
</body>
<div>
<el-row type="flex" justify="center">
<h1>{{ loadErrInfo() }}</h1>
</el-row>
</div>
<hr />
<div>
<el-row type="flex" justify="center"><span>nginx</span></el-row>
</div>
</div>
</template>
+6 -6
View File
@@ -141,7 +141,7 @@ const data = ref([]);
const loading = ref(false);
const paths = ref<string[]>([]);
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300, showHidden: true });
const selectRow = ref();
const selectRow = ref({ path: '', name: '' });
const rowRefs = ref();
const popoverVisible = ref(false);
const newFolder = ref();
@@ -183,12 +183,12 @@ const selectFile = () => {
const closePage = () => {
popoverVisible.value = false;
selectRow.value = {};
selectRow.value = { path: '', name: '' };
};
const openPage = () => {
popoverVisible.value = true;
selectRow.value = {};
selectRow.value.path = props.dir ? props.path || '/' : '';
rowName.value = '';
};
@@ -216,7 +216,7 @@ const open = async (row: File.File) => {
}
await search(req);
}
selectRow.value = {};
selectRow.value.path = props.dir ? req.path : '';
rowName.value = '';
};
@@ -230,7 +230,7 @@ const jump = async (index: number) => {
}
path = path || '/';
req.path = path;
selectRow.value = {};
selectRow.value.path = props.dir ? req.path : '';
rowName.value = '';
await search(req);
popoverVisible.value = true;
@@ -286,7 +286,7 @@ const cancelFolder = (row: any) => {
data.value.shift();
row.isCreate = false;
disBtn.value = false;
selectRow.value = {};
selectRow.value.path = props.dir ? req.path : '';
rowName.value = '';
newFolder.value = '';
};
@@ -105,6 +105,12 @@ const showBack = computed(() => {
.content-container__toolbar {
margin-top: 20px;
.el-button + .el-button {
margin: 0 !important;
}
.el-button-group .el-button {
margin: 0 !important;
}
}
.content-container_form {
@@ -1,119 +0,0 @@
<template>
<div>
<el-dialog class="level-up-pro" v-model="open" :close-on-click-modal="false" @close="handleClose">
<div style="text-align: center" v-loading="loading">
<span class="text-3xl font-medium title">{{ $t('license.levelUpPro') }}</span>
<el-row type="flex" justify="center" class="mt-6">
<el-col :span="22">
<el-upload
action="#"
:auto-upload="false"
ref="uploadRef"
class="upload-demo"
drag
:limit="1"
:on-change="fileOnChange"
:on-exceed="handleExceed"
v-model:file-list="uploaderFiles"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
{{ $t('license.importHelper') }}
</div>
</el-upload>
</el-col>
</el-row>
<el-button
type="primary"
class="mt-3 w-52"
:disabled="loading || uploaderFiles.length == 0"
plain
@click="submit"
>
{{ $t('license.power') }}
</el-button>
<div class="mt-3 mb-5">
<el-button text type="primary" @click="toHalo">{{ $t('license.knowMorePro') }}</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import i18n from '@/lang';
import { ref } from 'vue';
import { MsgSuccess } from '@/utils/message';
import { UploadFileData } from '@/api/modules/setting';
import { GlobalStore } from '@/store';
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
import { useTheme } from '@/hooks/use-theme';
import { getXpackSetting } from '@/utils/xpack';
const globalStore = GlobalStore();
const { switchTheme } = useTheme();
const loading = ref(false);
const open = ref(false);
const uploadRef = ref<UploadInstance>();
const uploaderFiles = ref<UploadFiles>([]);
const handleClose = () => {
open.value = false;
uploadRef.value!.clearFiles();
};
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
uploaderFiles.value = uploadFiles;
};
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadRef.value!.handleStart(file);
};
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const submit = async () => {
if (uploaderFiles.value.length !== 1) {
return;
}
const file = uploaderFiles.value[0];
const formData = new FormData();
formData.append('file', file.raw);
loading.value = true;
await UploadFileData(formData)
.then(async () => {
globalStore.isProductPro = true;
const xpackRes = await getXpackSetting();
if (xpackRes) {
globalStore.themeConfig.isGold = xpackRes.data.theme === 'dark-gold';
}
loading.value = false;
switchTheme();
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
open.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
window.location.reload();
})
.catch(() => {
loading.value = false;
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
});
};
const acceptParams = () => {
uploaderFiles.value = [];
uploadRef.value?.clearFiles();
open.value = true;
};
defineExpose({
acceptParams,
});
</script>
+12 -8
View File
@@ -4,6 +4,7 @@
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleClose"
:size="globalStore.isFullScreen ? '100%' : '50%'"
>
<template #header>
@@ -21,10 +22,10 @@
</el-drawer>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import LogFile from '@/components/log-file/index.vue';
import { GlobalStore } from '@/store';
import screenfull from 'screenfull';
import { GlobalStore } from '@/store';
import i18n from '@/lang';
const globalStore = GlobalStore();
@@ -45,19 +46,22 @@ const open = ref(false);
const config = ref();
const em = defineEmits(['close']);
const handleClose = (search: boolean) => {
const handleClose = () => {
open.value = false;
em('close', search);
em('close', false);
globalStore.isFullScreen = false;
};
watch(open, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
function toggleFullscreen() {
if (screenfull.isEnabled) {
screenfull.toggle();
}
globalStore.isFullScreen = !globalStore.isFullScreen;
}
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
};
const acceptParams = (props: LogProps) => {
+204 -224
View File
@@ -1,41 +1,41 @@
<template>
<div>
<div v-loading="firstLoading">
<div v-if="defaultButton">
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
{{ $t('commons.button.watch') }}
</el-checkbox>
<el-button class="ml-2.5" @click="onDownload" icon="Download" :disabled="data.content === ''">
<el-button class="ml-2.5" @click="onDownload" icon="Download" :disabled="logs.length === 0">
{{ $t('file.download') }}
</el-button>
<span v-if="$slots.button" class="ml-2.5">
<slot name="button"></slot>
</span>
</div>
<div class="mt-2.5">
<highlightjs
ref="editorRef"
class="editor-main"
language="JavaScript"
:autodetect="false"
:code="content"
></highlightjs>
<div class="log-container" ref="logContainer" @scroll="onScroll">
<div class="log-spacer" :style="{ height: `${totalHeight}px` }"></div>
<div
v-for="(log, index) in visibleLogs"
:key="startIndex + index"
class="log-item"
:style="{ top: `${(startIndex + index) * logHeight}px` }"
>
<span>{{ log }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
import { downloadFile } from '@/utils/util';
import { ReadByLine } from '@/api/modules/files';
import { watch } from 'vue';
const editorRef = ref();
<script lang="ts" setup>
import { ReadByLine } from '@/api/modules/files';
import { ref, computed, onMounted, watch, nextTick, reactive } from 'vue';
import { downloadFile } from '@/utils/util';
interface LogProps {
id?: number;
type: string;
name?: string;
tail?: boolean;
}
const props = defineProps({
config: {
type: Object as () => LogProps | null,
@@ -63,46 +63,6 @@ const props = defineProps({
default: false,
},
});
const data = ref({
enable: false,
content: '',
path: '',
});
let timer: NodeJS.Timer | null = null;
const tailLog = ref(false);
const content = ref('');
const end = ref(false);
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,
type: '',
name: '',
page: 1,
pageSize: 500,
latest: false,
});
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
const loading = ref(props.loading);
watch(
() => props.loading,
(newLoading) => {
loading.value = newLoading;
},
);
const changeLoading = () => {
loading.value = !loading.value;
emit('update:loading', loading.value);
};
const stopSignals = [
'docker-compose up failed!',
'docker-compose up successful!',
@@ -113,123 +73,63 @@ const stopSignals = [
'image push failed!',
'image push successful!',
];
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
const tailLog = ref(false);
const loading = ref(props.loading);
const readReq = reactive({
id: 0,
type: '',
name: '',
page: 1,
pageSize: 500,
latest: false,
});
const isLoading = ref(false);
const end = ref(false);
const lastLogs = ref([]);
const maxPage = ref(0);
const minPage = ref(0);
let timer: NodeJS.Timer | null = null;
const logPath = ref('');
const getContent = (pre: boolean) => {
if (isLoading.value) {
return;
}
emit('update:isReading', true);
readReq.id = props.config.id;
readReq.type = props.config.type;
readReq.name = props.config.name;
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
ReadByLine(readReq).then((res) => {
if (!end.value && res.data.end) {
lastLogs.value = [...logs.value];
}
const firstLoading = ref(false);
const logs = ref<string[]>([]);
const logContainer = ref<HTMLElement | null>(null);
const logHeight = 20;
const logCount = ref(0);
const totalHeight = computed(() => logHeight * logCount.value);
const containerHeight = ref(500);
const visibleCount = computed(() => Math.ceil(containerHeight.value / logHeight)); // 计算可见日志条数(容器高度 / 日志高度)
const startIndex = ref(0);
data.value = res.data;
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++;
}
const visibleLogs = computed(() => {
return logs.value.slice(startIndex.value, startIndex.value + visibleCount.value);
});
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
const onScroll = () => {
if (logContainer.value) {
const scrollTop = logContainer.value.scrollTop;
if (scrollTop == 0) {
readReq.page = minPage.value - 1;
if (readReq.page < 1) {
return;
}
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog();
}
if (end.value) {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
minPage.value = readReq.page;
getContent(true);
}
end.value = res.data.end;
content.value = logs.value.join('\n');
emit('update:hasContent', content.value !== '');
nextTick(() => {
if (pre) {
if (scrollerElement.value.scrollHeight > 2000) {
scrollerElement.value.scrollTop = 2000;
}
} else {
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
}
});
if (readReq.latest) {
readReq.page = res.data.total;
readReq.latest = false;
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;
});
startIndex.value = Math.floor(scrollTop / logHeight);
}
};
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 changeLoading = () => {
loading.value = !loading.value;
emit('update:loading', loading.value);
};
const throttledGetContent = throttle(getContent, 3000);
const search = () => {
throttledGetContent(false);
const onDownload = async () => {
changeLoading();
downloadFile(logPath.value);
changeLoading();
};
const changeTail = (fromOutSide: boolean) => {
@@ -238,36 +138,127 @@ const changeTail = (fromOutSide: boolean) => {
}
if (tailLog.value) {
timer = setInterval(() => {
search();
getContent(false);
}, 1000 * 3);
} else {
onCloseLog();
}
};
const onDownload = async () => {
changeLoading();
downloadFile(data.value.path);
changeLoading();
const clearLog = (): void => {
logs.value = [];
readReq.page = 1;
lastLogs.value = [];
};
const getContent = async (pre: boolean) => {
if (isLoading.value) {
return;
}
readReq.id = props.config.id;
readReq.type = props.config.type;
readReq.name = props.config.name;
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
emit('update:isReading', true);
const res = await ReadByLine(readReq);
logPath.value = res.data.path;
firstLoading.value = false;
if (!end.value && res.data.end) {
lastLogs.value = [...logs.value];
}
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 ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
nextTick(() => {
if (pre) {
logContainer.value.scrollTop = 2000;
} else {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
}
logCount.value = logs.value.length;
end.value = res.data.end;
emit('update:hasContent', logs.value.length > 0);
if (readReq.latest) {
readReq.page = res.data.total;
readReq.latest = false;
maxPage.value = res.data.total;
minPage.value = res.data.total;
}
if (logs.value && logs.value.length > 3000) {
if (pre) {
logs.value.splice(logs.value.length - readReq.pageSize, readReq.pageSize);
if (maxPage.value > 1) {
maxPage.value--;
}
} else {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value++;
}
}
}
isLoading.value = false;
};
const onCloseLog = async () => {
emit('update:isReading', false);
tailLog.value = false;
clearInterval(Number(timer));
timer = null;
isLoading.value = false;
emit('update:isReading', false);
};
function isScrolledToBottom(element: HTMLElement): boolean {
return element.scrollTop + element.clientHeight + 1 >= element.scrollHeight;
}
watch(
() => props.loading,
(newLoading) => {
loading.value = newLoading;
},
);
function isScrolledToTop(element: HTMLElement): boolean {
return element.scrollTop === 0;
}
const init = () => {
const init = async () => {
if (props.config.tail) {
tailLog.value = props.config.tail;
} else {
@@ -277,60 +268,49 @@ const init = () => {
changeTail(false);
}
readReq.latest = true;
search();
nextTick(() => {});
await getContent(false);
};
const clearLog = (): void => {
content.value = '';
};
const initCodemirror = () => {
onMounted(async () => {
firstLoading.value = true;
await init();
nextTick(() => {
if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement;
scrollerElement.value.addEventListener('scroll', function () {
if (tailLog.value) {
return;
}
if (isScrolledToBottom(scrollerElement.value)) {
if (maxPage.value > 1) {
readReq.page = maxPage.value;
}
search();
}
if (isScrolledToTop(scrollerElement.value)) {
readReq.page = minPage.value - 1;
if (readReq.page < 1) {
return;
}
minPage.value = readReq.page;
throttledGetContent(true);
}
});
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
hljsDom.style['min-height'] = '500px';
if (logContainer.value) {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
};
onUnmounted(() => {
onCloseLog();
});
onMounted(() => {
initCodemirror();
init();
});
defineExpose({ changeTail, onDownload, clearLog });
</script>
<style lang="scss" scoped>
.editor-main {
height: calc(100vh - 480px);
<style scoped>
.log-container {
height: calc(100vh - 405px);
overflow-y: auto;
overflow-x: auto;
position: relative;
background-color: #1e1e1e;
margin-top: 10px;
}
.log-spacer {
position: relative;
width: 100%;
min-height: 600px;
overflow: auto;
}
.log-item {
position: absolute;
width: 100%;
padding: 5px;
color: #f5f5f5;
box-sizing: border-box;
white-space: nowrap;
}
.log-item span {
font-size: 14px;
font-weight: 300;
color: #cccccc;
}
</style>
+19 -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>
@@ -1,38 +1,42 @@
<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" @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 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">
<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"
@@ -82,7 +86,7 @@ import i18n from '@/lang';
import 'md-editor-v3/lib/style.css';
import { MsgSuccess } from '@/utils/message';
import { copyText } from '@/utils/util';
import { onMounted, ref } from 'vue';
import { onMounted, ref, computed } from 'vue';
import { GlobalStore } from '@/store';
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
@@ -90,6 +94,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);
@@ -113,10 +121,6 @@ const handleClose = () => {
drawerVisible.value = false;
};
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const toDoc = () => {
window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer');
};
@@ -192,6 +196,7 @@ onMounted(() => {
text-decoration: none;
letter-spacing: 0.5px;
cursor: pointer;
margin-top: 2px;
}
.line-height {
line-height: 25px;
+4 -4
View File
@@ -97,7 +97,7 @@
:title="$t('commons.button.recover') + ' - ' + name"
width="40%"
:close-on-click-modal="false"
:before-close="handleClose"
:before-close="handleBackupClose"
>
<el-form ref="backupForm" label-position="left" v-loading="loading">
<el-form-item
@@ -110,7 +110,7 @@
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">
<el-button @click="handleBackupClose" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onHandleRecover" :disabled="loading">
@@ -211,8 +211,8 @@ 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'),
i18n.global.t('commons.msg.recoverHelper', [row.name]),
i18n.global.t('commons.button.recover'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
+4
View File
@@ -164,6 +164,10 @@ export const DNSTypes = [
label: i18n.global.t('website.tencentCloud'),
value: 'TencentCloud',
},
{
label: i18n.global.t('website.huaweicloud'),
value: 'HuaweiCloud',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
+45 -53
View File
@@ -56,8 +56,8 @@ const message = {
copy: 'Copy',
random: 'Random',
uninstall: 'Uninstall',
fullscreen: 'Fullscreen',
quitFullscreen: 'Quit Fullscreen',
fullscreen: 'WebsiteFullscreen',
quitFullscreen: 'Quit WebsiteFullscreen',
update: 'Edit',
showAll: 'Show All',
hideSome: 'Hide Some',
@@ -575,8 +575,13 @@ const message = {
container: {
create: 'Create',
edit: 'Edit container',
updateContainerHelper:
'Container editing requires rebuilding the container. Any data that has not been persisted will be lost. Do you want to continue?',
updateHelper1: 'Detected that this container comes from the app store. Please note the following two points:',
updateHelper2:
'1. The current modifications will not be synchronized to the installed applications in the app store.',
updateHelper3:
'2. If you modify the application on the installed page, the currently edited content will become invalid.',
updateHelper4:
'Editing the container requires rebuilding, and any non-persistent data will be lost. Do you want to continue?',
containerList: 'Container list',
operatorHelper: '{0} will be performed on the following container, Do you want to continue?',
operatorAppHelper:
@@ -643,7 +648,7 @@ const message = {
containerFromAppHelper:
'Detected that this container originates from the app store. App operations may cause current edits to be invalidated.',
containerFromAppHelper1:
'Click the `Settings` button in the installed applications list to enter the editing page and modify the container name.',
'Click the [Param] button in the installed applications list to enter the editing page and modify the container name.',
command: 'Command',
console: 'Console Interaction',
tty: 'TTY (-t)',
@@ -654,6 +659,8 @@ const message = {
privileged: 'Privileged',
privilegedHelper:
'Allows the container to perform certain privileged operations on the host, which may increase container risks. Use with caution!',
editComposeHelper:
'Note: The environment variables set will be written to the 1panel.env file by default.\nIf you want to use these parameters in the container, you also need to manually add an env_file reference in the compose file.',
upgradeHelper: 'Repository Name/Image Name: Image Version',
upgradeWarning2:
@@ -729,6 +736,8 @@ const message = {
urlWarning: 'The URL prefix does not need to include http:// or https://. Please modify.',
network: 'Network',
networkHelper:
'Deleting the 1panel-network container network will affect the normal use of some applications and runtime environments. Do you want to continue?',
createNetwork: 'Create',
networkName: 'Name',
driver: 'Driver',
@@ -768,12 +777,14 @@ const message = {
composeHelper:
'The composition created through 1Panel editor or template will be saved in the {0}/docker/compose directory.',
deleteFile: 'Delete file',
allDelete: 'Permanently Delete',
deleteComposeHelper:
'Delete all files in the {0} directory, including persistent files in this directory. Please proceed with caution!',
'Delete all files related to container compose, including configuration files and persistent files. Please proceed with caution!',
deleteCompose: '" Delete this composition.',
apps: 'Apps',
local: 'Local',
createCompose: 'Create',
composeDirectory: 'Compose Directory',
template: 'Template',
composeTemplate: 'Compose template',
createComposeTemplate: 'Create',
@@ -781,11 +792,16 @@ const message = {
content: 'Content',
contentEmpty: 'Compose content cannot be empty, please enter and try again!',
containerNumber: 'Container number',
containerStatus: 'Container Status',
exited: 'Exited',
running: 'Running',
down: 'Down',
up: 'Up',
composeDetailHelper:
'The compose is created external to 1Panel. The start and stop operations are not supported.',
composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?',
composeDownHelper:
'This will stop and remove all containers and networks under the {0} compose. Do you want to continue?',
setting: 'Setting',
operatorStatusHelper: 'This action will {0} Docker service, do you want to continue?',
@@ -926,6 +942,7 @@ const message = {
requestExpirationTime: 'Upload Request Expiration Time(Hours)',
unitHours: 'Unit: Hours',
alertTitle: 'Planned Task - {0} 「{1}」 Task Failure Alert',
},
monitor: {
monitor: 'Monitor',
@@ -1084,7 +1101,6 @@ const message = {
clam: {
clam: 'Virus Scan',
cron: 'Scheduled scan',
cronHelper: 'Professional version supports scheduled scan feature',
specErr: 'Execution schedule format error, please check and retry!',
disableMsg:
'Stopping scheduled execution will prevent this scan task from running automatically. Do you want to continue?',
@@ -1123,6 +1139,8 @@ const message = {
clamLog: 'Scan Logs',
freshClam: 'Update Virus Definitions',
freshClamLog: 'Update Virus Definitions Logs',
alertHelper: 'Professional version supports SMS alert',
alertTitle: 'Virus scanning 「{0}」 task failed alert',
},
},
logs: {
@@ -1176,6 +1194,7 @@ const message = {
info: 'Attribute',
linkFile: 'Soft link',
terminal: 'Terminal',
batchoperation: 'batch operation',
shareList: 'Share list',
zip: 'Compressed',
group: 'Group',
@@ -1276,6 +1295,7 @@ const message = {
noNameFolder: 'Untitled Folder',
noNameFile: 'Untitled File',
minimap: 'Code Mini Map',
fileCanNotRead: 'File can not read',
},
ssh: {
autoStart: 'Auto Start',
@@ -1408,6 +1428,7 @@ const message = {
LOCAL: 'Server Disks',
OSS: 'Ali OSS',
S3: 'Amazon S3',
mode: 'Mode',
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
@@ -1503,8 +1524,11 @@ const message = {
bindDomain: 'Bind Domain',
unBindDomain: 'Unbind domain',
panelSSL: 'Panel SSL',
panelSSLHelper:
'After the automatic renewal of the panel SSL, you need to manually restart the 1Panel service for the changes to take effect.',
sslAutoRestart: 'Restart 1Panel service after certificate auto-renewal',
sslChangeHelper1:
'Currently, automatic restart of 1Panel service is not selected. The certificate auto-renewal will not take effect immediately and will still require a manual restart of 1Panel.',
sslChangeHelper2:
'The 1Panel service will automatically restart after setting the panel SSL. Do you want to continue?',
unBindDomainHelper:
'The action of unbinding a domain name may cause system insecurity. Do you want to continue?',
bindDomainHelper:
@@ -1640,49 +1664,6 @@ const message = {
compressPassword: 'Compression Password',
backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)',
},
license: {
community: 'Community Edition: ',
community2: 'Community Edition',
pro: 'Professional Edition: ',
trial: 'Trial Edition',
office: 'Official Edition',
trialInfo: 'Version',
authorizationId: 'Subscription Authorization ID',
authorizedUser: 'Authorized User',
expiresAt: 'Expiry Date',
productName: 'Product Name',
productStatus: 'Product Status',
Lost01: 'Lost * 1',
Lost02: 'Lost * 2',
Lost03: 'Lost',
Enable: 'Enabled',
Disable: 'Disabled',
lostHelper:
'The License needs to be periodically synchronized for availability. Please ensure normal external network access. After three losses of connection, the License binding will be released.',
quickUpdate: 'Quick Update',
import: 'Import',
power: 'Authorize',
unbind: 'Unbind License',
unbindHelper: 'All Pro related Settings will be cleared after unbinding. Do you want to continue? ',
importLicense: 'Import License',
importHelper: 'Please click or drag the license file here',
technicalAdvice: 'Technical Advice',
advice: 'Consultation',
indefinitePeriod: 'Indefinite Period',
levelUpPro: 'Upgrade to Professional Edition',
licenseSync: 'License Sync',
knowMorePro: 'Learn More',
closeAlert: 'The current page can be closed in the panel settings',
introduce: 'Feature Introduction',
waf: 'Upgrading to the professional version can provide features such as interception map, logs, block records, geographical location blocking, custom rules, custom interception pages, etc.',
tamper: 'Upgrading to the professional version can protect websites from unauthorized modifications or tampering.',
gpu: 'Upgrading to the professional version can help users visually monitor important parameters of GPU such as workload, temperature, memory usage in real time.',
setting:
'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',
scanHelper: 'Easily tidy up junk files accumulated during 1Panel runtime',
@@ -1903,7 +1884,7 @@ const message = {
applySSL: 'Certificate Application',
SSLList: 'Certificate List',
createDnsAccount: 'DNS Account',
aliyun: 'Aliyun DNS',
aliyun: 'Aliyun',
manual: 'Manual parsing',
key: 'Key',
check: 'View',
@@ -2126,6 +2107,7 @@ const message = {
sni: 'Origin SNI',
sniHelper:
"When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.",
huaweicloud: 'Huawei Cloud',
},
php: {
short_open_tag: 'Short tag support',
@@ -2376,6 +2358,8 @@ const message = {
javaDirHelper: 'The directory must contain jar files, subdirectories are also acceptable',
goHelper: 'Please provide a complete start command, for example: go run main.go or ./main',
goDirHelper: 'The directory must contain go files or binary files, subdirectories are also acceptable',
pythonHelper:
'Please fill in the complete startup command, for example: pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
},
process: {
pid: 'Process ID',
@@ -2446,6 +2430,14 @@ const message = {
manage: 'Management',
},
},
alert: {
isAlert: 'Alert',
alertCount: 'Alert Count',
clamHelper: 'Trigger SMS alert when scanning infected files',
cronJobHelper: 'Trigger SMS alert when scheduled task execution fails',
licenseHelper: 'Professional version supports SMS alert',
alertCountHelper: 'Maximum daily alarm frequency',
},
};
export default {
+40 -50
View File
@@ -55,8 +55,8 @@ const message = {
copy: '復製',
random: '隨機密碼',
uninstall: '卸載',
fullscreen: '全屏',
quitFullscreen: '退出全屏',
fullscreen: '網頁全屏',
quitFullscreen: '退出網頁全屏',
update: '編輯',
showAll: '顯示所有',
hideSome: '隱藏部分',
@@ -557,7 +557,10 @@ const message = {
container: {
create: '創建容器',
edit: '編輯容器',
updateContainerHelper: '容器編輯需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
updateHelper1: '檢測到該容器來源於應用商店,請注意以下兩點:',
updateHelper2: '1. 當前修改內容不會同步到應用商店的已安裝應用。',
updateHelper3: '2. 如果在已安裝頁面修改應用,當前編輯的部分內容將失效。',
updateHelper4: '編輯容器需要重建,任何未持久化的數據將丟失,是否繼續?',
containerList: '容器列表',
operatorHelper: '將對以下容器進行 {0} 操作,是否繼續?',
operatorAppHelper:
@@ -619,7 +622,7 @@ const message = {
inputIpv6: '請輸入 IPv6 地址',
containerFromAppHelper: '檢測到該容器來源於應用商店,應用操作可能會導致當前編輯失效',
containerFromAppHelper1: '在已安裝應用程式列表點擊 `參數` 按鈕,進入編輯頁面即可修改容器名稱。',
containerFromAppHelper1: '在已安裝應用程式列表點擊 [參數] 按鈕,進入編輯頁面即可修改容器名稱。',
command: '命令',
console: '控製臺交互',
tty: '偽終端 ( -t )',
@@ -629,6 +632,8 @@ const message = {
emptyUser: '為空時,將使用容器默認的用戶登錄',
privileged: '特權模式',
privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!',
editComposeHelper:
'注意:設置的環境變數會默認寫入 1panel.env 文件。\n若需在容器中使用這些參數,還需在 compose 文件中手動添加 env_file 引用。',
upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本',
upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
@@ -705,6 +710,7 @@ const message = {
urlWarning: '路徑前綴不需要添加 http:// 或 https://,請修改',
network: '網絡',
networkHelper: '刪除 1panel-network 容器網絡將影響部分應用和運行環境的正常使用,是否繼續?',
createNetwork: '創建網絡',
networkName: '網絡名',
driver: '模式',
@@ -739,11 +745,13 @@ const message = {
composePathHelper: '配置文件保存路徑: {0}',
composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下',
deleteFile: '刪除文件',
deleteComposeHelper: '刪除 {0} 目录下所有文件,包括該文件下的持久化文件等,請謹慎操作!',
allDelete: '徹底刪除',
deleteComposeHelper: '刪除容器編排的所有文件,包括配置文件和持久化文件,請謹慎操作!',
deleteCompose: '" 刪除此編排',
apps: '應用商店',
local: '本地',
createCompose: '創建編排',
composeDirectory: '編排目錄',
template: '模版',
composeTemplate: '編排模版',
createComposeTemplate: '創建編排模版',
@@ -751,10 +759,14 @@ const message = {
content: '內容',
contentEmpty: '編排內容不能為空,請輸入後重試!',
containerNumber: '容器數量',
containerStatus: '容器狀態',
exited: '已停止',
running: '運行中',
down: '刪除',
up: '啟動',
composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。',
composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網絡,是否繼續?',
setting: '配置',
operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?',
@@ -881,6 +893,7 @@ const message = {
requestExpirationTime: '上傳請求過期時間(小時)',
unitHours: '單位:小時',
alertTitle: '計畫任務-{0}「{1}」任務失敗告警',
},
monitor: {
monitor: '監控',
@@ -1025,7 +1038,6 @@ const message = {
clam: {
clam: '病毒掃描',
cron: '定時掃描',
cronHelper: '專業版支持定時掃描功能',
specErr: '執行周期格式錯誤,請檢查後重試!',
disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?',
enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?',
@@ -1061,6 +1073,8 @@ const message = {
clamLog: '掃描日誌',
freshClam: '病毒庫刷新配置',
freshClamLog: '病毒庫刷新日誌',
alertHelper: '專業版支持短信告警功能',
alertTitle: '病毒掃描「{0}」任務失敗告警',
},
},
logs: {
@@ -1114,6 +1128,7 @@ const message = {
info: '屬性',
linkFile: '軟連接文件',
terminal: '終端',
batchoperation: '批量操作',
shareList: '分享列表',
zip: '壓縮',
group: '用戶組',
@@ -1210,6 +1225,7 @@ const message = {
noNameFolder: '未命名資料夾',
noNameFile: '未命名檔案',
minimap: '縮略圖',
fileCanNotRead: '此文件不支持預覽',
},
ssh: {
autoStart: '開機自啟',
@@ -1295,8 +1311,8 @@ const message = {
systemIP: '伺服器地址',
proxy: '代理伺服器',
proxyHelper: '設置代理伺服器後,將在以下場景中生效:',
proxyHelper1: '應用商店的安裝包下載和同步(專業版功能)',
proxyHelper2: '系統版本升級及獲取更新說明(專業版功能)',
proxyHelper1: '應用商店的安裝包下載和同步',
proxyHelper2: '系統版本升級及獲取更新說明',
proxyHelper3: '系統許可證的驗證和同步',
proxyType: '代理類型',
proxyUrl: '代理地址',
@@ -1321,6 +1337,7 @@ const message = {
LOCAL: '服務器磁盤',
OSS: '阿裏雲 OSS',
S3: '亞馬遜 S3 雲存儲',
mode: '模式',
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
@@ -1454,7 +1471,9 @@ const message = {
bindDomain: '域名綁定',
unBindDomain: '域名解綁',
panelSSL: '面板 SSL',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
sslAutoRestart: '證書自動續期後重啟 1Panel 服務',
sslChangeHelper1: '當前未勾選自動重啟 1Panel 服務,證書自動續期後不會立即生效,仍需手動重啟 1Panel。',
sslChangeHelper2: '設置面板 SSL 後將自動重啟 1Panel 服務,是否繼續?',
unBindDomainHelper: '解除域名綁定可能造成系統不安全,是否繼續?',
bindDomainHelper: '設置域名綁定後,僅能通過設置中域名訪問 1Panel 服務',
bindDomainHelper1: '綁定域名為空時,則取消域名綁定',
@@ -1524,46 +1543,6 @@ const message = {
compressPassword: '壓縮密碼',
backupRecoverMessage: '請輸入壓縮或解壓縮密碼(留空則不設定)',
},
license: {
community: '社區版:',
community2: '社區版',
pro: '專業版:',
trial: '試用版',
office: '正式版',
trialInfo: '版本',
authorizationId: '訂閱授權 ID',
authorizedUser: '被授權方',
expiresAt: '到期時間',
productName: '產品名稱',
productStatus: '產品狀態',
Lost01: '失聯 * 1',
Lost02: '失聯 * 2',
Lost03: '已失聯',
Enable: '已啟用',
Disable: '未啟用',
lostHelper: '許可證需要定時同步是否可用,請保證正常外網訪問,失聯三次後將解除許可證綁定',
quickUpdate: '快速更新',
import: '導入',
power: '授 權',
unbind: '解除綁定',
unbindHelper: '解除綁定後將清除所有專業版相關設置,是否繼續?',
importLicense: '導入許可證',
importHelper: '請點擊或拖動許可文件到此處',
technicalAdvice: '技術諮詢',
advice: '諮詢',
indefinitePeriod: '無限期',
levelUpPro: '升級專業版',
licenseSync: '許可證同步',
knowMorePro: '了解更多',
closeAlert: '當前頁面可在面板設置中關閉顯示',
introduce: '功能介紹',
waf: '升級專業版可以獲得攔截地圖、日誌、封鎖記錄、地理位置封禁、自定義規則、自定義攔截頁面等功能。',
tamper: '升級專業版可以保護網站免受未經授權的修改或篡改。',
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。',
setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。',
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
alert: '陞級專業版可通過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統運行無憂。',
},
clean: {
scan: '開始掃描',
scanHelper: '輕鬆梳理 1Panel 運行期間積累的垃圾文件',
@@ -1767,7 +1746,7 @@ const message = {
applySSL: '證書申請',
SSLList: '證書列表',
createDnsAccount: 'DNS賬戶',
aliyun: '阿裏雲DNS',
aliyun: '阿裏雲',
manual: '手動解析',
key: '密鑰',
check: '查看',
@@ -1976,6 +1955,7 @@ const message = {
website404Helper: '網站 404 錯誤頁僅支援 PHP 運行環境網站和靜態網站',
sni: '回源 SNI',
sniHelper: '反代後端為 https 的時候可能需要設置回源 SNI,具體需要看 CDN 服務商文檔',
huaweicloud: '華為雲',
},
php: {
short_open_tag: '短標簽支持',
@@ -2203,6 +2183,8 @@ const message = {
javaDirHelper: '目錄中要包含 jar 包,子目錄中包含也可',
goHelper: '請填寫完整啟動命令,例如:go run main.go 或 ./main',
goDirHelper: '目錄中要包含 go 文件或者二進制文件,子目錄中包含也可',
pythonHelper:
'請填入完整啟動指令,例如:pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
},
process: {
pid: '進程ID',
@@ -2271,6 +2253,14 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次數',
clamHelper: '掃描到感染檔案時觸發簡訊告警',
cronJobHelper: '定時執行計畫任務失敗時觸發簡訊告警',
licenseHelper: '專業版支持簡訊告警功能',
alertCountHelper: '每日最大告警次數',
},
};
export default {
...fit2cloudTwLocale,
+40 -53
View File
@@ -55,8 +55,8 @@ const message = {
copy: '复制',
random: '随机密码',
uninstall: '卸载',
fullscreen: '全屏',
quitFullscreen: '退出全屏',
fullscreen: '网页全屏',
quitFullscreen: '退出网页全屏',
update: '编辑',
showAll: '显示所有',
hideSome: '隐藏部分',
@@ -559,7 +559,10 @@ const message = {
container: {
create: '创建容器',
edit: '编辑容器',
updateContainerHelper: '容器编辑需要重建容器,任何未持久化的数据将会丢失,是否继续?',
updateHelper1: '检测到该容器来源于应用商店,请注意以下两点:',
updateHelper2: '1. 当前修改内容不会同步到应用商店的已安装应用。',
updateHelper3: '2. 如果在已安装页面修改应用,当前编辑的部分内容将失效。',
updateHelper4: '编辑容器需要重建,任何未持久化的数据将丢失,是否继续?',
containerList: '容器列表',
operatorHelper: '将对以下容器进行 {0} 操作,是否继续?',
operatorAppHelper:
@@ -622,7 +625,7 @@ const message = {
inputIpv6: '请输入 ipv6 地址',
containerFromAppHelper: '检测到该容器来源于应用商店,应用操作可能会导致当前编辑失效',
containerFromAppHelper1: '在已安装应用列表点击 `参数` 按钮,进入编辑页面即可修改容器名称。',
containerFromAppHelper1: '在应用商店的已安装页面,点击 [参数] 按钮,进入编辑页面修改容器名称。',
command: '命令',
console: '控制台交互',
tty: '伪终端 ( -t )',
@@ -632,7 +635,8 @@ const message = {
emptyUser: '为空时,将使用容器默认的用户登录',
privileged: '特权模式',
privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!',
editComposeHelper:
'注意:设置的环境变量会默认写入 1panel.env 文件。\n如需在容器中使用这些参数,还需在 compose 文件中手动添加 env_file 引用。',
upgradeHelper: '仓库名称/镜像名称:镜像版本',
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
oldImage: '当前镜像',
@@ -708,6 +712,7 @@ const message = {
urlWarning: '路径前缀不需要添加 http:// 或 https://, 请修改',
network: '网络',
networkHelper: '删除 1panel-network 容器网络将影响部分应用和运行环境的正常使用,是否继续?',
createNetwork: '创建网络',
networkName: '网络名',
driver: '模式',
@@ -736,17 +741,18 @@ const message = {
mirrorHelper:
'当存在多个加速器时,需要换行显示,例: \nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com',
registrieHelper: '当存在多个私有仓库时,需要换行显示,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: '编排',
fromChangeHelper: '切换来源将清空当前已编辑内容,是否继续?',
composePathHelper: '配置文件保存路径: {0}',
composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下',
deleteFile: '删除文件',
deleteComposeHelper: '删除 {0} 目录下所有文件,包括该文件下的持久化文件等,请谨慎操作!',
allDelete: '彻底删除',
deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!',
deleteCompose: '" 删除此编排',
apps: '应用商店',
local: '本地',
createCompose: '创建编排',
composeDirectory: '编排目录',
template: '模版',
composeTemplate: '编排模版',
createComposeTemplate: '创建编排模版',
@@ -754,10 +760,14 @@ const message = {
content: '内容',
contentEmpty: '编排内容不能为空,请输入后重试!',
containerNumber: '容器数量',
containerStatus: '容器状态',
exited: '已停止',
running: '运行中',
down: '删除',
up: '启动',
composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。',
composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?',
setting: '配置',
operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?',
@@ -884,6 +894,7 @@ const message = {
requestExpirationTime: '上传请求过期时间(小时)',
unitHours: '单位:小时',
alertTitle: '计划任务-{0}「 {1} 」任务失败告警',
},
monitor: {
monitor: '监控',
@@ -1028,7 +1039,6 @@ const message = {
clam: {
clam: '病毒扫描',
cron: '定时扫描',
cronHelper: '专业版支持定时扫描功能 ',
specErr: '执行周期格式错误,请检查后重试!',
disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?',
enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?',
@@ -1065,6 +1075,8 @@ const message = {
clamLog: '扫描日志',
freshClam: '病毒库刷新配置',
freshClamLog: '病毒库刷新日志',
alertHelper: '专业版支持短信告警功能',
alertTitle: '病毒扫描「 {0} 」任务失败告警',
},
},
logs: {
@@ -1118,6 +1130,7 @@ const message = {
info: '属性',
linkFile: '软连接文件',
terminal: '终端',
batchoperation: '批量操作',
shareList: '分享列表',
zip: '压缩',
group: '用户组',
@@ -1214,6 +1227,7 @@ const message = {
noNameFolder: '未命名文件夹',
noNameFile: '未命名文件',
minimap: '缩略图',
fileCanNotRead: '此文件不支持预览',
},
ssh: {
autoStart: '开机自启',
@@ -1299,8 +1313,8 @@ const message = {
systemIP: '服务器地址',
proxy: '代理服务器',
proxyHelper: '设置代理服务器后,将在以下场景中生效:',
proxyHelper1: '应用商店的安装包下载和同步(专业版功能)',
proxyHelper2: '系统版本升级及获取更新说明(专业版功能)',
proxyHelper1: '应用商店的安装包下载和同步',
proxyHelper2: '系统版本升级及获取更新说明',
proxyHelper3: '系统许可证的验证和同步',
proxyType: '代理类型',
proxyUrl: '代理地址',
@@ -1325,6 +1339,7 @@ const message = {
LOCAL: '服务器磁盘',
OSS: '阿里云 OSS',
S3: '亚马逊 S3 云存储',
mode: '模式',
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
@@ -1458,7 +1473,9 @@ const message = {
bindDomain: '域名绑定',
unBindDomain: '域名解绑',
panelSSL: '面板 SSL',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
sslAutoRestart: '证书自动续期后重启 1Panel 服务',
sslChangeHelper1: '当前未勾选自动重启 1Panel 服务,证书自动续期后不会立即生效,仍需手动重启 1Panel。',
sslChangeHelper2: '设置面板 SSL 后将自动重启 1Panel 服务,是否继续?',
unBindDomainHelper: '解除域名绑定可能造成系统不安全,是否继续?',
bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务',
bindDomainHelper1: '绑定域名为空时,则取消域名绑定',
@@ -1482,7 +1499,6 @@ const message = {
mfaInterval: '刷新时间(秒)',
mfaTitleHelper: '用于区分不同 1Panel 主机,修改后请重新扫描或手动添加密钥信息!',
mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!',
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
sslDisable: '禁用',
sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续?',
noAuthSetting: '未认证设置',
@@ -1528,46 +1544,6 @@ const message = {
compressPassword: '压缩密码',
backupRecoverMessage: '请输入压缩或解压缩密码(留空则不设置)',
},
license: {
community: '社区版:',
community2: '社区版',
pro: '专业版:',
trial: '试用版',
office: '正式版',
trialInfo: '版本',
authorizationId: '订阅授权 ID',
authorizedUser: '被授权方',
expiresAt: '到期时间',
productName: '产品名称',
productStatus: '产品状态',
Lost01: '失联 * 1',
Lost02: '失联 * 2',
Lost03: '已失联',
Enable: '已激活',
Disable: '未激活',
lostHelper: '许可证需要定时同步是否可用,请保证正常外网访问,失联三次后将解除许可证绑定',
quickUpdate: '快速更新',
import: '导入',
power: '授 权',
unbind: '解除绑定',
unbindHelper: '解除绑定后将清除所有专业版相关设置,是否继续?',
importLicense: '导入许可证',
importHelper: '请点击或拖动许可文件到此处',
technicalAdvice: '技术咨询',
advice: '咨询',
indefinitePeriod: '无限期',
levelUpPro: '升级专业版',
licenseSync: '许可证同步',
knowMorePro: '了解更多',
closeAlert: '当前页面可在面板设置中关闭显示',
introduce: '功能介绍',
waf: '升级专业版可以获得拦截地图、日志、封锁记录、地理位置封禁、自定义规则、自定义拦截页面等功能。',
tamper: '升级专业版可以保护网站免受未经授权的修改或篡改。',
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。',
setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。',
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。',
},
clean: {
scan: '开始扫描',
scanHelper: '轻松梳理 1Panel 运行期间积累的垃圾文件',
@@ -1771,7 +1747,7 @@ const message = {
applySSL: '证书申请',
SSLList: '证书列表',
createDnsAccount: 'DNS账户',
aliyun: '阿里云DNS',
aliyun: '阿里云',
manual: '手动解析',
key: '密钥',
check: '查看',
@@ -1980,6 +1956,7 @@ const message = {
website404Helper: '网站 404 错误页仅支持 PHP 运行环境网站和静态网站',
sni: '回源 SNI',
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI,具体需要看 CDN 服务商文档',
huaweicloud: '华为云',
},
php: {
short_open_tag: '短标签支持',
@@ -2208,6 +2185,8 @@ const message = {
javaDirHelper: '目录中要包含 jar 包,子目录中包含也可',
goHelper: '请填写完整启动命令,例如:go run main.go 或 ./main',
goDirHelper: '目录中要包含 go 文件或者二进制文件,子目录中包含也可',
pythonHelper:
'请填写完整启动命令,例如:pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
},
process: {
pid: '进程ID',
@@ -2276,6 +2255,14 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次数',
clamHelper: '扫描到感染文件时触发短信告警',
cronJobHelper: '定时执行计划任务失败时触发短信告警',
licenseHelper: '专业版支持短信告警功能',
alertCountHelper: '每日最大告警次数',
},
};
export default {
...fit2cloudZhLocale,
+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">
+1 -1
View File
@@ -100,7 +100,7 @@ onMounted(() => {
loadStatus();
loadProductProFromDB();
globalStore.isFullScreen = false;
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
if (mqList.addEventListener) {
mqList.addEventListener('change', () => {
-10
View File
@@ -37,16 +37,6 @@ const settingRouter = {
activeMenu: 'Setting',
},
},
{
path: 'license',
name: 'License',
component: () => import('@/views/setting/license/index.vue'),
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
},
},
{
path: 'about',
name: 'About',
+12 -2
View File
@@ -64,7 +64,7 @@ const webSiteRouter = {
hidden: true,
component: () => import('@/views/website/runtime/java/index.vue'),
meta: {
activeMenu: '/websites/runtimes/java',
activeMenu: '/websites/runtimes/php',
requiresAuth: false,
},
},
@@ -74,7 +74,17 @@ const webSiteRouter = {
hidden: true,
component: () => import('@/views/website/runtime/go/index.vue'),
meta: {
activeMenu: '/websites/runtimes/go',
activeMenu: '/websites/runtimes/php',
requiresAuth: false,
},
},
{
path: '/websites/runtimes/python',
name: 'python',
hidden: true,
component: () => import('@/views/website/runtime/python/index.vue'),
meta: {
activeMenu: '/websites/runtimes/php',
requiresAuth: false,
},
},
+1
View File
@@ -34,6 +34,7 @@ export interface GlobalState {
defaultNetwork: string;
isProductPro: boolean;
isTrial: boolean;
productProExpires: number;
errStatus: string;
+1
View File
@@ -38,6 +38,7 @@ const GlobalStore = defineStore({
defaultNetwork: 'all',
isProductPro: false,
isTrial: false,
productProExpires: 0,
errStatus: '',
+10 -4
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;
@@ -374,7 +374,6 @@ html {
}
.tag-button {
margin-right: 10px;
&.no-active {
background: none;
border: none;
@@ -387,7 +386,8 @@ html {
}
.router-button {
margin-right: 20px;
margin-right: 30px;
padding: 10px;
}
.text-parent {
@@ -444,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;
}
+1 -1
View File
@@ -22,4 +22,4 @@ body,
.el-switch--small,.is-checked .el-switch__core::after {
margin-left: 12px !important;
}
}
+16 -1
View File
@@ -194,6 +194,8 @@ let icons = new Map([
['.zip', 'p-file-zip'],
['.gz', 'p-file-zip'],
['.tar.bz2', 'p-file-zip'],
['.bz2', 'p-file-zip'],
['.xz', 'p-file-zip'],
['.tar', 'p-file-zip'],
['.tar.gz', 'p-file-zip'],
['.war', 'p-file-zip'],
@@ -347,6 +349,19 @@ export function checkCidr(value: string): boolean {
return false;
}
}
export function checkCidrV6(value: string): boolean {
if (value === '') {
return true;
}
if (checkIpV6(value.split('/')[0])) {
return true;
}
const reg = /^(?:[1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/;
if (!reg.test(value.split('/')[1])) {
return true;
}
return false;
}
export function checkPort(value: string): boolean {
if (Number(value) <= 0) {
@@ -556,7 +571,7 @@ export function emptyLineFilter(str: string, spilt: string) {
// 文件类型映射
let fileTypes = {
image: ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.ico', '.svg', '.webp'],
compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar'],
compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar', '.bz2', '.xz', '.tar.bz2', '.tar.xz'],
video: ['.mp4', '.webm', '.mov', '.wmv', '.mkv', '.avi', '.wma', '.flv'],
audio: ['.mp3', '.wav', '.wma', '.ape', '.acc', '.ogg', '.flac'],
pdf: ['.pdf'],
+2
View File
@@ -61,6 +61,7 @@ export async function loadProductProFromDB() {
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro);
globalStore.isTrial = res.data.trial;
}
}
switchTheme();
@@ -81,6 +82,7 @@ export async function getXpackSettingForTheme() {
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro);
globalStore.isTrial = res.data.trial;
}
if (!globalStore.isProductPro) {
globalStore.isProductPro = false;
+2 -7
View File
@@ -224,16 +224,11 @@ const search = async (req: App.AppReq) => {
const openInstall = (app: App.App) => {
switch (app.type) {
case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node':
router.push({ path: '/websites/runtimes/node' });
break;
case 'java':
router.push({ path: '/websites/runtimes/java' });
break;
case 'go':
router.push({ path: '/websites/runtimes/go' });
case 'python':
router.push({ path: '/websites/runtimes/' + app.type });
break;
default:
const params = {
@@ -142,16 +142,11 @@ const toLink = (link: string) => {
const openInstall = () => {
switch (app.value.type) {
case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node':
router.push({ path: '/websites/runtimes/node' });
break;
case 'java':
router.push({ path: '/websites/runtimes/java' });
break;
case 'go':
router.push({ path: '/websites/runtimes/go' });
case 'python':
router.push({ path: '/websites/runtimes/' + app.value.type });
break;
default:
const params = {
-1
View File
@@ -85,7 +85,6 @@
}
.tag-button {
margin-right: 10px;
&.no-active {
background: none;
border: none;

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