144 Commits

Author SHA1 Message Date
zhengkunwang223
201e0562b6 style: 修改 dark 主题样式 2023-07-13 16:31:04 +08:00
zhengkunwang
87327053dc fix: 解决某些情况下应用状态异常的问题 (#1644) 2023-07-13 07:43:18 +00:00
ssongliu
5e7d5c0235 fix: 解决部分容器编辑无法正常打开的问题 (#1645) 2023-07-13 07:41:17 +00:00
zhengkunwang
1ccc56100c fix: 解决面板设置无法选择 https 证书的问题 (#1638) 2023-07-13 06:17:18 +00:00
ssongliu
ef948bca76 fix: 解决概览页多磁盘刷新问题 (#1640) 2023-07-13 06:11:18 +00:00
ssongliu
d01a964534 fix: 修改部分国际化问题 (#1639) 2023-07-13 06:09:23 +00:00
zhengkunwang
05004cb141 fix: 解决网站设置 HTTPS 证书显示错误的问题 (#1629)
Refs https://github.com/1Panel-dev/1Panel/issues/1626
2023-07-12 13:17:16 +00:00
zhengkunwang
dfcef390d0 feat: 取消 openresty 安装时的端口开放提示 (#1627) 2023-07-12 13:13:16 +00:00
ssongliu
ff3b41686c fix: 解决部分升级失败的问题 (#1624) 2023-07-12 13:11:20 +00:00
ssongliu
7dd5082bac fix: 异常监控数据增加日志打印 (#1622) 2023-07-12 08:39:16 +00:00
ssongliu
ec05e81286 fix: 容器列表样式调整 (#1620) 2023-07-12 08:07:16 +00:00
ssongliu
e7a7d391e4 fix: 增加备份超时时间 (#1619) 2023-07-12 07:49:15 +00:00
zhengkunwang
e6b1ef30a5 feat: 限制 mysql 5.6.51 版本的升级 (#1618) 2023-07-12 06:41:16 +00:00
ssongliu
f40b053e5b fix: 计划任务详情页删除时刷新选中值 (#1616) 2023-07-12 06:35:15 +00:00
ssongliu
1a891cb048 fix: 解决容器编辑时端口冲突的问题 (#1615) 2023-07-12 06:33:19 +00:00
ssongliu
66007c07e2 fix: 解决 Mysql5.6 删除用户失败的问题 2023-07-11 21:19:12 +08:00
ssongliu
5058a814aa fix: 释放 echarts 资源 (#1611) 2023-07-11 10:59:10 +00:00
ssongliu
37d8244414 fix: 解决计划任务保存份数错误的问题 (#1608) 2023-07-11 10:23:10 +00:00
zhengkunwang223
cda5112f5e feat: 修改用户名校验提示 (#1609) 2023-07-11 10:21:10 +00:00
zhengkunwang223
919f10cc11 feat: 文件上传优化 (#1607) 2023-07-11 10:19:20 +00:00
ssongliu
66b12800e4 fix: 暗色样式调整 (#1606) 2023-07-11 08:39:10 +00:00
ssongliu
227856b45b fix: 修改 mfa 最大刷新时间限制 2023-07-11 13:45:26 +08:00
ssongliu
44ca529027 fix: 解决网站备份路径问题 (#1604) 2023-07-11 03:27:09 +00:00
ssongliu
885e3b60f0 fix: 解决备份账号列表下载失败的问题 (#1603) 2023-07-11 03:01:09 +00:00
ssongliu
07bbf057d0 fix: 强制拉取镜像样式调整 (#1602) 2023-07-11 02:59:13 +00:00
ssongliu
b9227caaf8 feat: 容器创建编辑增加拉取最新镜像选项 (#1597) 2023-07-10 14:45:08 +00:00
zhengkunwang223
55ed67eaed feat: 优化进程管理的页面打开速度 (#1595) 2023-07-10 14:43:12 +00:00
zhengkunwang223
ac55385696 feat: 日志审计-网站日志增加下载和追踪功能 (#1592)
日志审计-网站日志增加下载和追踪功能
2023-07-10 13:27:07 +00:00
zhengkunwang223
f9b93e8c85 feat: 优化应用商店页面打开速度 (#1596)
Refs https://github.com/1Panel-dev/1Panel/issues/1485
2023-07-10 10:49:08 +00:00
ssongliu
ac7f33a29c fix: 空数据国际化调整 (#1594) 2023-07-10 10:39:07 +00:00
zhengkunwang223
4de99f66a8 feat: 修改端口检测规则 (#1591) 2023-07-10 06:47:08 +00:00
ssongliu
c0c1d519bf fix: 解决容器参数修改失败的问题 (#1590) 2023-07-10 06:45:08 +00:00
dependabot[bot]
f22980f4ce build(deps-dev): bump stylelint from 14.16.1 to 15.10.1 in /frontend (#1584)
Bumps [stylelint](https://github.com/stylelint/stylelint) from 14.16.1 to 15.10.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/14.16.1...15.10.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-08 19:05:50 +08:00
ssongliu
7b297e824c feat: 容器创建编辑支持自定义网络 (#1582) 2023-07-07 15:17:05 +00:00
zhengkunwang223
28bd0e3cc2 feat: 应用创建增加端口的检测范围 (#1581) 2023-07-07 15:15:10 +00:00
ssongliu
f90c009782 fix: 解决容器 label 或者 env 为空导致无法编辑的问题 (#1580) 2023-07-07 09:45:06 +00:00
ssongliu
a54913f788 fix: 解决计划任务执行周期显示错误的问题 (#1576) 2023-07-07 09:43:10 +00:00
ssongliu
ea3dca79aa fix: 系统正则兼容 ipv6 (#1575) 2023-07-07 09:41:13 +00:00
zhengkunwang223
2b207597b9 feat: 修改参数 (#1573) 2023-07-07 03:38:11 +00:00
ssongliu
044bb79ae9 fix: 解决终端字体大小变化时内容显示不全的问题 (#1572) 2023-07-07 03:18:11 +00:00
ssongliu
afcebb46a0 fix: 增加终端连接断开提示信息 (#1571) 2023-07-07 03:08:11 +00:00
zhengkunwang223
10427ddd65 feat: 增加忽略应用列表和取消忽略功能 (#1566)
增加忽略应用列表和取消忽略功能
2023-07-06 10:48:22 +00:00
ssongliu
0ac2b9df7a feat: 优化容器界面加载速度 (#1565) 2023-07-06 10:04:22 +00:00
zhengkunwang223
695aacbe14 feat: 应用同步优化 (#1564) 2023-07-06 08:30:22 +00:00
ssongliu
71107fa42f fix: 消息弹框样式调整 (#1563) 2023-07-06 08:06:21 +00:00
ssongliu
d36dfc5490 fix: 计划任务报告仅在移动端显示滚动条 (#1562) 2023-07-06 06:32:21 +00:00
ssongliu
a463f237b1 fix: 解决备份账号修改备份路径失败的问题 (#1560) 2023-07-06 06:30:25 +00:00
ssongliu
ec105ede83 fix: 忽略监控采集的异常数据 (#1559) 2023-07-06 06:28:27 +00:00
dependabot[bot]
92aff95b3a build(deps): bump google.golang.org/grpc from 1.50.1 to 1.53.0 (#1561)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.50.1 to 1.53.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.50.1...v1.53.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 14:28:01 +08:00
ssongliu
1fcc345a7a fix: 解决重复创建编排导致来源显示错误的问题 (#1558) 2023-07-06 06:26:22 +00:00
ssongliu
7e9a09c960 fix: 容器 CPU 限制上限改为机器逻辑核心数 (#1551) 2023-07-05 10:16:19 +00:00
ssongliu
8212ff117b fix: 修改重名容器校验规则 (#1550) 2023-07-05 09:40:19 +00:00
zhengkunwang223
b5a1ffe338 feat: 内存限制提示保留2位小数 (#1549) 2023-07-05 09:38:24 +00:00
zhengkunwang223
759382bcfb feat: 进程管理增加 tcp udp 网络列表 (#1548) 2023-07-05 09:08:19 +00:00
ssongliu
12eeb6503c fix: 解决容器编辑无法编辑名称的问题 (#1546) 2023-07-05 08:48:25 +00:00
zhengkunwang223
9fa9772c07 feat: 证书手动导入增加选择本地文件 (#1543) 2023-07-05 08:24:19 +00:00
ssongliu
35334c1650 fix: 解决数据库文件不存在时启用失败的问题 (#1544) 2023-07-05 08:10:19 +00:00
ssongliu
006c27fee5 fix: 随机密码按钮样式统一修改 (#1542) 2023-07-05 07:08:18 +00:00
ssongliu
271be81557 fix: 修改 Mysql 数据库长度限制 (#1541) 2023-07-05 06:12:12 +00:00
ssongliu
b61d8aaabc fix: 主机密码回显解密 (#1540) 2023-07-05 06:02:12 +00:00
zhengkunwang223
319afd2d51 feat: 应用创建增加 cpu 和内存最大值提示 (#1535) 2023-07-05 03:02:12 +00:00
ssongliu
bd2facebee fix: 容器端口跳转样式调整 (#1533) 2023-07-05 02:20:13 +00:00
zhengkunwang223
3cbaa052c8 feat: 优化文件选择逻辑 (#1532) 2023-07-05 01:54:12 +00:00
ssongliu
a0ceb62372 fix: 容器镜像升级样式调整 (#1531) 2023-07-04 11:24:11 +00:00
ssongliu
dddd190911 fix: 备份账号 OneDrive 绑定逻辑修改 (#1529) 2023-07-04 08:16:12 +00:00
zhengkunwang223
f70b0049d9 feat: 网站选择证书可以通过 acme 账号过滤 (#1530) 2023-07-04 08:14:11 +00:00
zhengkunwang223
6fea06729e fix: 解决网站删除密码访问后,无法关闭密码访问的问题 (#1528) 2023-07-04 06:40:11 +00:00
zhengkunwang223
24e6fe89c8 fix: 解决进程搜索为空一直转圈的问题 (#1526) 2023-07-04 03:38:10 +00:00
zhengkunwang223
e507611cad fix: 解决 SSH 会话在多个连接下显示错误的问题 (#1525) 2023-07-04 03:36:10 +00:00
ssongliu
72dcdbad1e feat: 应用增加跳转功能 (#1520) 2023-07-04 02:46:11 +00:00
ssongliu
0a55dec949 fix: 计划任务、容器创建样式移动端适配 (#1519) 2023-07-04 02:42:11 +00:00
zhengkunwang223
555a32c273 feat: 应用安装增加镜像拉取 (#1517) 2023-07-03 13:56:14 +00:00
ssongliu
fdf2a9d247 fix: 解决终端连接错误时前端无响应的问题 (#1518) 2023-07-03 12:32:15 +00:00
ssongliu
30bb64d058 feat: 主机密码加密存储 (#1516) 2023-07-03 08:48:14 +00:00
zhengkunwang223
695d4b4a16 feat: 增加应用忽略升级功能 (#1515) 2023-07-03 08:36:18 +00:00
ssongliu
01c08a8ef9 fix: OneDrive 增加操作描述信息 (#1513) 2023-07-03 07:14:15 +00:00
ssongliu
dd9f2edf40 fix: Mysql 慢日志样式调整 (#1512) 2023-07-03 07:00:19 +00:00
zhengkunwang223
105c249d97 feat: 证书申请延长超时时间 (#1511) 2023-07-03 06:58:22 +00:00
zhengkunwang223
b780df96af fix: 解决本地应用删除版本文件夹,页面版本没有删除的问题 (#1510) 2023-07-03 04:30:14 +00:00
ssongliu
46495937b1 feat: sftp 去掉重复的备份文件夹 (#1509) 2023-07-03 04:28:18 +00:00
ssongliu
597c9ea4c0 feat: 增加繁体中文适配 (#1497) 2023-07-01 14:56:13 +00:00
zhengkunwang223
4662f4703c feat: PHP 运行环境增加扩展列表提示 (#1500) 2023-06-30 15:26:12 +00:00
zhengkunwang223
5f750e6f49 fix: 解决修改上传限制导致 PHP 配置文件错误的问题 (#1499)
Refs https://github.com/1Panel-dev/1Panel/issues/1425
2023-06-30 15:24:16 +00:00
zhengkunwang223
152ba81c34 feat: 增加 SSH 会话管理 (#1498) 2023-06-30 09:14:13 +00:00
ssongliu
4bf76aacb1 fix: 移除部分重复国际化内容 (#1495) 2023-06-29 14:38:12 +00:00
Wang Siu Kao
6c4c73e825 Update zh-tw.yaml 2023-06-29 20:43:22 +08:00
Wang Siu Kao
917851dcd7 Create zh-tw.yaml 2023-06-29 20:43:22 +08:00
zhengkunwang223
2e5bf4202c feat: 应用安装增加端口放开提示 (#1492) 2023-06-29 10:46:11 +00:00
zhengkunwang223
d4319fa55c feat: 网站启用 https 增加 http2 配置 (#1491) 2023-06-29 10:44:15 +00:00
zhengkunwang223
2ff7726442 fix: 解决防盗链保存两次导致访问网站自动下载文件的问题 (#1489)
Refs https://github.com/1Panel-dev/1Panel/issues/1460
2023-06-29 10:42:11 +00:00
ssongliu
38bf54ec3b feat: 容器、数据库、网站、计划任务增加名称排序 (#1490) 2023-06-29 10:40:12 +00:00
zhengkunwang223
a1c76600e2 feat: 进程列表增加 loading (#1488) 2023-06-29 09:58:10 +00:00
zhengkunwang223
fbcb3da422 feat: 创建运行环境增加拉取最新版本 (#1486) 2023-06-29 07:02:11 +00:00
ssongliu
3978fb3e46 fix: 解决快照压缩数据库文件失败的问题 (#1477) 2023-06-29 03:24:11 +00:00
ssongliu
64f80a95ab feat: 增加系统 IP 设置,实现容器端口跳转功能 (#1479) 2023-06-29 03:18:15 +00:00
ssongliu
847c14ddda fix: 优化同步快照名称重复错误显示 (#1478) 2023-06-29 02:30:10 +00:00
zhengkunwang223
38c0d290e7 feat: 增加进程管理 (#1476) 2023-06-28 06:50:10 +00:00
ssongliu
c403eb55b1 feat: 计划任务脚本执行增加容器中选项 (#1474) 2023-06-28 06:30:11 +00:00
ssongliu
506d78cb00 feat: 监控增加全局过滤 (#1466) 2023-06-27 14:10:00 +00:00
ssongliu
27918e2dc5 feat: 备份账号 OSS 、COS 支持存储类型选择 (#1464) 2023-06-27 10:57:59 +00:00
ssongliu
d44231a0ff fix: 面板操作日志显示优化 (#1454) 2023-06-27 08:45:59 +00:00
ssongliu
11a58fde91 fix: 统一抽屉警告或提示信息样式 (#1453) 2023-06-27 08:44:03 +00:00
ssongliu
3a8c3b5816 fix: 解决终端点击本地服务器未打开新连接的问题 (#1452) 2023-06-27 08:42:07 +00:00
ssongliu
50deda27ca feat: 数据库兼容 mysql 5.6.51 版本 (#1450) 2023-06-27 08:40:13 +00:00
xushulang
4482fa23e6 新增tailwindcss做响应式 (#1411) 2023-06-27 16:38:11 +08:00
Mystery0
4c83a3bf36 fix: 网站日志切割定时任务执行失败时打印失败原因,方便排查问题 2023-06-26 23:29:00 +08:00
ssongliu
514538179e fix: 增加前端注入校验规则 (#1456) 2023-06-26 09:24:06 +00:00
zhengkunwang223
4c8245045d feat: 应用商店增加分页 (#1447) 2023-06-25 14:20:14 +00:00
zhengkunwang223
efe185e5dc fix: 解决 Docker 版本过低导致应用无法操作的问题 (#1446)
Refs https://github.com/1Panel-dev/1Panel/issues/1445
2023-06-25 14:00:14 +00:00
ssongliu
62becf819d fix: 解决计划任务备份根目录下文件夹失败的问题 (#1444) 2023-06-25 13:14:14 +00:00
ssongliu
f02f32456e fix: 解决终端连接注入漏洞问题 2023-06-25 18:34:16 +08:00
ssongliu
57916ed6d7 fix: 解决添加仓库注入漏洞问题 2023-06-25 18:34:16 +08:00
ssongliu
efa3d06673 feat: 两步验证增加自定义刷新时间 (#1441) 2023-06-25 09:52:13 +00:00
ssongliu
14f7435f82 feat: 概览、监控等界面增加单位区分显示 (#1440) 2023-06-25 09:50:18 +00:00
ssongliu
dbeaaa49f3 fix: 修改数据库无法正常访问情况下系统提示信息 (#1438) 2023-06-25 03:50:13 +00:00
ssongliu
7546391c17 fix: 解决容器镜像加速或仓库空行导致 Docker 无法正常启用的问题 (#1437) 2023-06-25 03:48:18 +00:00
ssongliu
d8d2ee0f46 fix: ssh 登录日志排序优化 (#1435) 2023-06-25 03:46:22 +00:00
凹凸曼
3c57fa76bf fix: 优化监控页图表Y轴数字大小和宽度,防止数字过长溢出 #1422 (#1423)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-25 11:26:17 +08:00
ssongliu
3fa4a240f7 feat: 备份账号增加 Onedrive (#1421) 2023-06-23 15:06:13 +00:00
凹凸曼
ae38239b47 fix: 修复ssh日志为空时不渲染的问题 (#1394)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-16 22:41:07 +08:00
wanghe-fit2cloud
6a63b2e5c4 fix: goreleaser 显示声明 CGO_ENABLED=0 2023-06-16 18:35:57 +08:00
ssongliu
3ead633343 feat: 支持容器镜像升级操作 (#1393) 2023-06-16 09:54:11 +00:00
凹凸曼
e71f765f2a feat: ssh登录日志增加一键屏蔽ip #1309 (#1352)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-16 16:27:35 +08:00
wanghe-fit2cloud
2b7f68f3fe feat: 静态编译不依赖 glibc 2023-06-15 23:33:11 +08:00
ssongliu
c82e20efd7 feat: 容器创建编辑增加 cpu 、内存最大限制 (#1383) 2023-06-15 12:44:13 +00:00
ssongliu
aa37e3885c fix: 构建移除对 cgo 的依赖 (#1386) 2023-06-15 10:02:13 +00:00
ssongliu
7918023322 fix: 解决容器、终端界面 ios 适配的问题 (#1385) 2023-06-15 10:00:17 +00:00
吴小白
80304937e1 feat: 不使用 cgo (#1382) 2023-06-15 11:19:52 +08:00
ssongliu
352978b54d feat: 增加容器编辑功能 (#1381) 2023-06-15 03:00:11 +00:00
ssongliu
b7cda1d2f1 feat: 容器创建增加 CPU 权重设置 (#1377) 2023-06-14 15:26:13 +00:00
ssongliu
20dbd6c181 fix: 容器日志接口文档修改 (#1372) 2023-06-14 06:16:13 +00:00
ssongliu
8808e1b0c3 feat: 优化容器日志加载方式,增加显示条数过滤 (#1370) 2023-06-13 15:04:12 +00:00
ssongliu
47dda4ac9f fix: 修改应用导入恢复时数据库连接信息 (#1367) 2023-06-13 08:32:12 +00:00
ssongliu
b1d40960c4 fix: 容器存储卷目录按钮样式修改 (#1366) 2023-06-13 06:28:12 +00:00
ssongliu
5222caecf9 fix: 应用恢复去掉版本限制 (#1365) 2023-06-13 04:28:11 +00:00
ssongliu
bc8d4cbb0f feat: 应用导入恢复增加提示信息 (#1362) 2023-06-13 02:46:11 +00:00
凹凸曼
0bb31f6caf fix: 修复创建数据库时误删数据库用户的bug (#1358)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:29:47 +08:00
凹凸曼
03c8e4d3cb feat: 容器存储卷快捷进入挂载点文件目录 #1325 (#1357)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:27:55 +08:00
凹凸曼
68ad653545 fix: 修复无法读取ssh成功日志 (#1356)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:26:39 +08:00
凹凸曼
fd5b34d644 fix: 修复英文语言包缺少"operateConfirm"的问题 (#1354)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:25:01 +08:00
吴小白
000096bc26 fix: 修正 actions 脚本 2023-06-12 21:16:54 +08:00
ssongliu
02023102d3 fix: 解决计划任务执行周期错误的问题 (#1349) 2023-06-12 10:42:11 +00:00
ssongliu
95bc3d9855 feat: 应用导入备份恢复优化 (#1347) 2023-06-12 09:48:11 +00:00
凹凸曼
f9fb16198b feat: 计划任务和容器日志的前端改进 (#1319)
* feat: 计划任务增加批量开启和停止 #1010 

* feat: 容器日志增加全屏按钮 #1176
2023-06-12 15:44:15 +08:00
278 changed files with 11942 additions and 3266 deletions

View File

@@ -12,9 +12,7 @@ jobs:
build-linux-binary: build-linux-binary:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install cross-compilers - name: Checkout Code
run: sudo apt-get update && sudo apt-get -y install gcc-x86-64-linux-gnu gcc-aarch64-linux-gnu gcc-arm-linux-gnueabi gcc-powerpc64le-linux-gnu gcc-s390x-linux-gnu
- name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@@ -23,7 +21,7 @@ jobs:
- name: Build Web - name: Build Web
id: build_frontend id: build_frontend
run: | run: |
cd frontend && npm install && npm run build:dev cd frontend && npm install && npm run build:pro
env: env:
NODE_OPTIONS: --max-old-space-size=8192 NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go - name: Setup Go

View File

@@ -10,9 +10,7 @@ jobs:
create-release: create-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install cross-compilers - name: Checkout Code
run: sudo apt-get update && sudo apt-get -y install gcc-x86-64-linux-gnu gcc-aarch64-linux-gnu gcc-arm-linux-gnueabi gcc-powerpc64le-linux-gnu gcc-s390x-linux-gnu
- name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@@ -20,7 +18,7 @@ jobs:
node-version: '18.14' node-version: '18.14'
- name: Build Web - name: Build Web
run: | run: |
cd frontend && npm install && npm run build:dev cd frontend && npm install && npm run build:pro
env: env:
NODE_OPTIONS: --max-old-space-size=8192 NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go - name: Setup Go
@@ -30,17 +28,33 @@ jobs:
- name: Build Release - name: Build Release
uses: goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v4
with: with:
distribution: goreleaser
version: latest
args: release --skip-publish --clean args: release --skip-publish --clean
- name: Upload Assets - name: Upload Assets
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
draft: true draft: true
body: |
# 一、安装和升级
## 1.1 一键安装
```sh
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
```
## 1.2 在线升级
登录 1Panel Web 控制台,在页面右下角点击 **【检查更新】** 进行在线升级。
>更多信息请查阅在线文档https://1panel.cn/docs/
# 二、更新日志
files: | files: |
dist/*.tar.gz dist/*.tar.gz
dist/checksums.txt dist/checksums.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup OSSUTIL - name: Setup OSSUTIL
uses: yizhoumo/setup-ossutil@v1 uses: yizhoumo/setup-ossutil@v1
with: with:
@@ -49,4 +63,4 @@ jobs:
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
ossutil-version: '1.7.14' ossutil-version: '1.7.14'
- name: Upload Assets to OSS - name: Upload Assets to OSS
- run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force

1
.gitignore vendored
View File

@@ -30,5 +30,6 @@ dist/
1pctl 1pctl
1panel.service 1panel.service
install.sh install.sh
quick_start.sh
cmd/server/web/.DS_Store cmd/server/web/.DS_Store
cmd/server/.DS_Store cmd/server/.DS_Store

View File

@@ -16,18 +16,8 @@ builds:
- -trimpath - -trimpath
ldflags: ldflags:
- -w -s - -w -s
- --extldflags "-static -fpic"
tags:
- osusergo
env: env:
- CGO_ENABLED=1 - CGO_ENABLED=0
- >-
{{- if eq .Arch "amd64"}}CC=x86_64-linux-gnu-gcc{{- end }}
{{- if eq .Arch "arm64"}}CC=aarch64-linux-gnu-gcc{{- end }}
{{- if eq .Arch "arm"}}CC=arm-linux-gnueabi-gcc{{- end }}
{{- if eq .Arch "loong64"}}CC=loongarch64-linux-gnu-gcc{{- end }}
{{- if eq .Arch "ppc64le"}}CC=powerpc64le-linux-gnu-gcc{{- end }}
{{- if eq .Arch "s390x"}}CC=s390x-linux-gnu-gcc{{- end }}
goos: goos:
- linux - linux
goarm: goarm:

View File

@@ -1,34 +0,0 @@
FROM node:18.14 as build_web
ARG TARGETARCH
ARG NPM_REGISTRY="https://registry.npmmirror.com"
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /data
RUN set -ex \
&& npm config set registry ${NPM_REGISTRY}
ADD . /data
RUN set -ex \
&& cd /data/frontend \
&& npm install
RUN set -ex \
&& cd /data/frontend \
&& npm run build:dev
FROM golang:1.20
ARG TARGETARCH
ARG GOPROXY="https://goproxy.cn,direct"
COPY --from=build_web /data /data
WORKDIR /data
RUN set -ex \
&& go env -w GOPROXY=${GOPROXY} \
&& go install github.com/goreleaser/goreleaser@latest \
&& goreleaser build --single-target --snapshot --clean
CMD ["/bin/bash"]

View File

@@ -23,16 +23,16 @@ build_frontend:
build_backend_on_linux: build_backend_on_linux:
cd $(SERVER_PATH) \ cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags 'osusergo,netgo' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) && GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_darwin: build_backend_on_darwin:
cd $(SERVER_PATH) \ cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) && GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_archlinux: build_backend_on_archlinux:
cd $(SERVER_PATH) \ cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) && GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_frontend build_backend_on_linux build_all: build_frontend build_backend_on_linux
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin

View File

@@ -130,6 +130,22 @@ func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
helper.SuccessWithData(c, appDetailDTO) helper.SuccessWithData(c, appDetailDTO)
} }
// @Tags App
// @Summary Get Ignore App
// @Description 获取忽略的应用版本
// @Accept json
// @Success 200 {object} response.IgnoredApp
// @Security ApiKeyAuth
// @Router /apps/ingored [get]
func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
res, err := appService.GetIgnoredApp()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags App // @Tags App
// @Summary Install app // @Summary Install app
// @Description 安装应用 // @Description 安装应用

View File

@@ -118,7 +118,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
// @Description 删除前检查 // @Description 删除前检查
// @Accept json // @Accept json
// @Param appInstallId path integer true "App install id" // @Param appInstallId path integer true "App install id"
// @Success 200 {anrry} dto.AppResource // @Success 200 {array} dto.AppResource
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /apps/installed/delete/check/:appInstallId [get] // @Router /apps/installed/delete/check/:appInstallId [get]
func (b *BaseApi) DeleteCheck(c *gin.Context) { func (b *BaseApi) DeleteCheck(c *gin.Context) {
@@ -178,7 +178,7 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) {
// @Description 通过 key 获取应用 service // @Description 通过 key 获取应用 service
// @Accept json // @Accept json
// @Param key path string true "request" // @Param key path string true "request"
// @Success 200 {anrry} response.AppService // @Success 200 {array} response.AppService
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /apps/services/:key [get] // @Router /apps/services/:key [get]
func (b *BaseApi) GetServices(c *gin.Context) { func (b *BaseApi) GetServices(c *gin.Context) {
@@ -196,7 +196,7 @@ func (b *BaseApi) GetServices(c *gin.Context) {
// @Description 通过 install id 获取应用更新版本 // @Description 通过 install id 获取应用更新版本
// @Accept json // @Accept json
// @Param appInstallId path integer true "request" // @Param appInstallId path integer true "request"
// @Success 200 {anrry} dto.AppVersion // @Success 200 {array} dto.AppVersion
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /apps/installed/:appInstallId/versions [get] // @Router /apps/installed/:appInstallId/versions [get]
func (b *BaseApi) GetUpdateVersions(c *gin.Context) { func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
@@ -305,3 +305,25 @@ func (b *BaseApi) UpdateInstalled(c *gin.Context) {
} }
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags App
// @Summary ignore App Update
// @Description 忽略应用升级版本
// @Accept json
// @Param request body request.AppInstalledIgnoreUpgrade true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /apps/installed/ignore [post]
// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"忽略应用 [installId] 版本升级","formatEN":"Application param update [installId]"}
func (b *BaseApi) IgnoreUpgrade(c *gin.Context) {
var req request.AppInstalledIgnoreUpgrade
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := appInstallService.IgnoreUpgrade(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@@ -60,7 +60,7 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
// @Description 获取 bucket 列表 // @Description 获取 bucket 列表
// @Accept json // @Accept json
// @Param request body dto.ForBuckets true "request" // @Param request body dto.ForBuckets true "request"
// @Success 200 {anrry} string // @Success 200 {array} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /settings/backup/search [post] // @Router /settings/backup/search [post]
func (b *BaseApi) ListBuckets(c *gin.Context) { func (b *BaseApi) ListBuckets(c *gin.Context) {
@@ -98,6 +98,22 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
helper.SuccessWithData(c, buckets) helper.SuccessWithData(c, buckets)
} }
// @Tags Backup Account
// @Summary Load OneDrive info
// @Description 获取 OneDrive 信息
// @Accept json
// @Success 200 string clientID
// @Security ApiKeyAuth
// @Router /settings/backup/onedrive [get]
func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
clientID, err := backupService.LoadOneDriveInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, clientID)
}
// @Tags Backup Account // @Tags Backup Account
// @Summary Delete backup account // @Summary Delete backup account
// @Description 删除备份账号 // @Description 删除备份账号
@@ -253,7 +269,7 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
// @Tags Backup Account // @Tags Backup Account
// @Summary List backup accounts // @Summary List backup accounts
// @Description 获取备份账号列表 // @Description 获取备份账号列表
// @Success 200 {anrry} dto.BackupInfo // @Success 200 {array} dto.BackupInfo
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /settings/backup/search [get] // @Router /settings/backup/search [get]
func (b *BaseApi) ListBackup(c *gin.Context) { func (b *BaseApi) ListBackup(c *gin.Context) {
@@ -271,7 +287,7 @@ func (b *BaseApi) ListBackup(c *gin.Context) {
// @Description 获取备份账号内文件列表 // @Description 获取备份账号内文件列表
// @Accept json // @Accept json
// @Param request body dto.BackupSearchFile true "request" // @Param request body dto.BackupSearchFile true "request"
// @Success 200 {anrry} string // @Success 200 {array} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /settings/backup/search/files [post] // @Router /settings/backup/search/files [post]
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) { func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {

View File

@@ -66,7 +66,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
// @Summary List compose templates // @Summary List compose templates
// @Description 获取容器编排模版列表 // @Description 获取容器编排模版列表
// @Produce json // @Produce json
// @Success 200 {anrry} dto.ComposeTemplateInfo // @Success 200 {array} dto.ComposeTemplateInfo
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/template [get] // @Router /containers/template [get]
func (b *BaseApi) ListComposeTemplate(c *gin.Context) { func (b *BaseApi) ListComposeTemplate(c *gin.Context) {

View File

@@ -40,6 +40,23 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
}) })
} }
// @Tags Container
// @Summary List containers
// @Description 获取容器名称
// @Accept json
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/list [post]
func (b *BaseApi) ListContainer(c *gin.Context) {
list, err := containerService.List()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Container Compose // @Tags Container Compose
// @Summary Page composes // @Summary Page composes
// @Description 获取编排列表分页 // @Description 获取编排列表分页
@@ -153,17 +170,97 @@ func (b *BaseApi) OperatorCompose(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Container
// @Summary Update container
// @Description 更新容器
// @Accept json
// @Param request body dto.ContainerOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/update [post]
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器 [name][image]","formatEN":"update container [name][image]"}
func (b *BaseApi) ContainerUpdate(c *gin.Context) {
var req dto.ContainerOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := containerService.ContainerUpdate(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Container
// @Summary Load container info
// @Description 获取容器表单信息
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.ContainerOperate
// @Security ApiKeyAuth
// @Router /containers/info [post]
func (b *BaseApi) ContainerInfo(c *gin.Context) {
var req dto.OperationWithName
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := containerService.ContainerInfo(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Summary Load container limis
// @Description 获取容器限制
// @Success 200 {object} dto.ResourceLimit
// @Security ApiKeyAuth
// @Router /containers/limit [get]
func (b *BaseApi) LoadResouceLimit(c *gin.Context) {
data, err := containerService.LoadResouceLimit()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Summary Load container stats
// @Description 获取容器列表资源占用
// @Success 200 {array} dto.ContainerListStats
// @Security ApiKeyAuth
// @Router /containers/list/stats [get]
func (b *BaseApi) ContainerListStats(c *gin.Context) {
datas, err := containerService.ContainerListStats()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, datas)
}
// @Tags Container // @Tags Container
// @Summary Create container // @Summary Create container
// @Description 创建容器 // @Description 创建容器
// @Accept json // @Accept json
// @Param request body dto.ContainerCreate true "request" // @Param request body dto.ContainerOperate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers [post] // @Router /containers [post]
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"} // @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"}
func (b *BaseApi) ContainerCreate(c *gin.Context) { func (b *BaseApi) ContainerCreate(c *gin.Context) {
var req dto.ContainerCreate var req dto.ContainerOperate
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
@@ -179,6 +276,32 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Container
// @Summary Upgrade container
// @Description 更新容器镜像
// @Accept json
// @Param request body dto.ContainerUpgrade true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/upgrade [post]
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器镜像 [name][image]","formatEN":"upgrade container image [name][image]"}
func (b *BaseApi) ContainerUpgrade(c *gin.Context) {
var req dto.ContainerUpgrade
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := containerService.ContainerUpgrade(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Container // @Tags Container
// @Summary Clean container // @Summary Clean container
// @Description 容器清理 // @Description 容器清理
@@ -262,7 +385,7 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) {
// @Summary Container stats // @Summary Container stats
// @Description 容器监控信息 // @Description 容器监控信息
// @Param id path integer true "容器id" // @Param id path integer true "容器id"
// @Success 200 {object} dto.ContainterStats // @Success 200 {object} dto.ContainerStats
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/stats/:id [get] // @Router /containers/stats/:id [get]
func (b *BaseApi) ContainerStats(c *gin.Context) { func (b *BaseApi) ContainerStats(c *gin.Context) {
@@ -310,27 +433,29 @@ func (b *BaseApi) Inspect(c *gin.Context) {
// @Tags Container // @Tags Container
// @Summary Container logs // @Summary Container logs
// @Description 容器日志 // @Description 容器日志
// @Accept json // @Param container query string false "容器名称"
// @Param request body dto.ContainerLog true "request" // @Param since query string false "时间筛选"
// @Success 200 {string} logs // @Param follow query string false "是否追踪"
// @Param tail query string false "显示行号"
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/search/log [post] // @Router /containers/search/log [post]
func (b *BaseApi) ContainerLogs(c *gin.Context) { func (b *BaseApi) ContainerLogs(c *gin.Context) {
var req dto.ContainerLog wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
logs, err := containerService.ContainerLogs(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
container := c.Query("container")
since := c.Query("since")
follow := c.Query("follow") == "true"
tail := c.Query("tail")
if err := containerService.ContainerLogs(wsConn, container, since, tail, follow); err != nil {
_ = wsConn.WriteMessage(1, []byte(err.Error()))
return return
} }
helper.SuccessWithData(c, logs)
} }
// @Tags Container Network // @Tags Container Network
@@ -364,6 +489,23 @@ func (b *BaseApi) SearchNetwork(c *gin.Context) {
}) })
} }
// @Tags Container Network
// @Summary List networks
// @Description 获取容器网络列表
// @Accept json
// @Produce json
// @Success 200 {array} dto.Options
// @Security ApiKeyAuth
// @Router /containers/network [get]
func (b *BaseApi) ListNetwork(c *gin.Context) {
list, err := containerService.ListNetwork()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Container Network // @Tags Container Network
// @Summary Delete network // @Summary Delete network
// @Description 删除容器网络 // @Description 删除容器网络
@@ -453,11 +595,10 @@ func (b *BaseApi) SearchVolume(c *gin.Context) {
// @Summary List volumes // @Summary List volumes
// @Description 获取容器存储卷列表 // @Description 获取容器存储卷列表
// @Accept json // @Accept json
// @Param request body dto.PageInfo true "request"
// @Produce json // @Produce json
// @Success 200 {object} dto.PageResult // @Success 200 {array} dto.Options
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/volume/search [get] // @Router /containers/volume [get]
func (b *BaseApi) ListVolume(c *gin.Context) { func (b *BaseApi) ListVolume(c *gin.Context) {
list, err := containerService.ListVolume() list, err := containerService.ListVolume()
if err != nil { if err != nil {

View File

@@ -216,7 +216,7 @@ func (b *BaseApi) SearchMysql(c *gin.Context) {
// @Description 获取 mysql 数据库列表 // @Description 获取 mysql 数据库列表
// @Accept json // @Accept json
// @Param request body dto.PageInfo true "request" // @Param request body dto.PageInfo true "request"
// @Success 200 {anrry} string // @Success 200 {array} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/options [get] // @Router /databases/options [get]
func (b *BaseApi) ListDBName(c *gin.Context) { func (b *BaseApi) ListDBName(c *gin.Context) {
@@ -234,7 +234,7 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
// @Description Mysql 数据库删除前检查 // @Description Mysql 数据库删除前检查
// @Accept json // @Accept json
// @Param request body dto.OperateByID true "request" // @Param request body dto.OperateByID true "request"
// @Success 200 {anrry} string // @Success 200 {array} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/del/check [post] // @Router /databases/del/check [post]
func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { func (b *BaseApi) DeleteCheckMysql(c *gin.Context) {

View File

@@ -49,4 +49,5 @@ var (
upgradeService = service.NewIUpgradeService() upgradeService = service.NewIUpgradeService()
runtimeService = service.NewRuntimeService() runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService()
) )

View File

@@ -52,7 +52,7 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
// @Description 分页获取上传文件 // @Description 分页获取上传文件
// @Accept json // @Accept json
// @Param request body request.SearchUploadWithPage true "request" // @Param request body request.SearchUploadWithPage true "request"
// @Success 200 {anrry} response.FileInfo // @Success 200 {array} response.FileInfo
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /files/upload/search [post] // @Router /files/upload/search [post]
func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
@@ -81,7 +81,7 @@ func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
// @Description 加载文件树 // @Description 加载文件树
// @Accept json // @Accept json
// @Param request body request.FileOption true "request" // @Param request body request.FileOption true "request"
// @Success 200 {anrry} response.FileTree // @Success 200 {array} response.FileTree
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /files/tree [post] // @Router /files/tree [post]
func (b *BaseApi) GetFileTree(c *gin.Context) { func (b *BaseApi) GetFileTree(c *gin.Context) {
@@ -532,7 +532,7 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
} }
defer file.Close() defer file.Close()
file.Seek(startPos, 0) _, _ = file.Seek(startPos, 0)
reader := io.LimitReader(file, endPos-startPos+1) reader := io.LimitReader(file, endPos-startPos+1)
_, err = io.CopyBuffer(c.Writer, reader, buffer) _, err = io.CopyBuffer(c.Writer, reader, buffer)
if err != nil { if err != nil {
@@ -683,7 +683,12 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
} }
filename := c.PostForm("filename") filename := c.PostForm("filename")
fileDir := filepath.Join(tmpDir, filename) fileDir := filepath.Join(tmpDir, filename)
_ = os.MkdirAll(fileDir, 0755) if chunkIndex == 0 {
if fileOp.Stat(fileDir) {
_ = fileOp.DeleteDir(fileDir)
}
_ = os.MkdirAll(fileDir, 0755)
}
filePath := filepath.Join(fileDir, filename) filePath := filepath.Join(fileDir, filename)
defer func() { defer func() {
@@ -734,19 +739,12 @@ var wsUpgrade = websocket.Upgrader{
}, },
} }
var WsManager = websocket2.Manager{
Group: make(map[string]*websocket2.Client),
Register: make(chan *websocket2.Client, 128),
UnRegister: make(chan *websocket2.Client, 128),
ClientCount: 0,
}
func (b *BaseApi) Ws(c *gin.Context) { func (b *BaseApi) Ws(c *gin.Context) {
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
return return
} }
wsClient := websocket2.NewWsClient("wsClient", ws) wsClient := websocket2.NewWsClient("fileClient", ws)
go wsClient.Read() go wsClient.Read()
go wsClient.Write() go wsClient.Write()
} }

View File

@@ -7,7 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/copier" "github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -36,7 +36,14 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
req.Password = string(password) passwordItem, err := encrypt.StringEncrypt(string(password))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = passwordItem
req.PrivateKey = ""
req.PassPhrase = ""
} }
if req.AuthMode == "key" && len(req.PrivateKey) != 0 { if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
@@ -44,7 +51,22 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
req.PrivateKey = string(privateKey) keyItem, err := encrypt.StringEncrypt(string(privateKey))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = keyItem
if len(req.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PassPhrase = pass
}
req.Password = ""
} }
host, err := hostService.Create(req) host, err := hostService.Create(req)
@@ -102,7 +124,7 @@ func (b *BaseApi) TestByID(c *gin.Context) {
// @Description 加载主机树 // @Description 加载主机树
// @Accept json // @Accept json
// @Param request body dto.SearchForTree true "request" // @Param request body dto.SearchForTree true "request"
// @Success 200 {anrry} dto.HostTree // @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/tree [post] // @Router /hosts/tree [post]
func (b *BaseApi) HostTree(c *gin.Context) { func (b *BaseApi) HostTree(c *gin.Context) {
@@ -126,7 +148,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Description 获取主机列表分页 // @Description 获取主机列表分页
// @Accept json // @Accept json
// @Param request body dto.SearchHostWithPage true "request" // @Param request body dto.SearchHostWithPage true "request"
// @Success 200 {anrry} dto.HostTree // @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /hosts/search [post] // @Router /hosts/search [post]
func (b *BaseApi) SearchHost(c *gin.Context) { func (b *BaseApi) SearchHost(c *gin.Context) {
@@ -148,33 +170,6 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
}) })
} }
// @Tags Host
// @Summary Load host info
// @Description 加载主机信息
// @Accept json
// @Param id path integer true "request"
// @Success 200 {object} dto.HostInfo
// @Security ApiKeyAuth
// @Router /hosts/:id [get]
func (b *BaseApi) GetHostInfo(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
host, err := hostService.GetHostInfo(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
var hostDto dto.HostInfo
if err := copier.Copy(&hostDto, host); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, hostDto)
}
// @Tags Host // @Tags Host
// @Summary Delete host // @Summary Delete host
// @Description 删除主机 // @Description 删除主机
@@ -227,7 +222,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
req.Password = string(password) passwordItem, err := encrypt.StringEncrypt(string(password))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = passwordItem
} }
if req.AuthMode == "key" && len(req.PrivateKey) != 0 { if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
@@ -235,7 +235,21 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
req.PrivateKey = string(privateKey) keyItem, err := encrypt.StringEncrypt(string(privateKey))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PrivateKey = keyItem
if len(req.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PassPhrase = pass
}
} }
upMap := make(map[string]interface{}) upMap := make(map[string]interface{})
@@ -246,10 +260,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
upMap["user"] = req.User upMap["user"] = req.User
upMap["auth_mode"] = req.AuthMode upMap["auth_mode"] = req.AuthMode
upMap["remember_password"] = req.RememberPassword upMap["remember_password"] = req.RememberPassword
if len(req.Password) != 0 { if req.AuthMode == "password" {
upMap["password"] = req.Password upMap["password"] = req.Password
} upMap["private_key"] = ""
if len(req.PrivateKey) != 0 { upMap["pass_phrase"] = ""
} else {
upMap["password"] = ""
upMap["private_key"] = req.PrivateKey upMap["private_key"] = req.PrivateKey
upMap["pass_phrase"] = req.PassPhrase upMap["pass_phrase"] = req.PassPhrase
} }

View File

@@ -44,7 +44,7 @@ func (b *BaseApi) SearchImage(c *gin.Context) {
// @Summary List images // @Summary List images
// @Description 获取镜像列表 // @Description 获取镜像列表
// @Produce json // @Produce json
// @Success 200 {anrry} dto.Options // @Success 200 {array} dto.Options
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/image [get] // @Router /containers/image [get]
func (b *BaseApi) ListImage(c *gin.Context) { func (b *BaseApi) ListImage(c *gin.Context) {

View File

@@ -44,7 +44,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
// @Summary List image repos // @Summary List image repos
// @Description 获取镜像仓库列表 // @Description 获取镜像仓库列表
// @Produce json // @Produce json
// @Success 200 {anrry} dto.ImageRepoOption // @Success 200 {array} dto.ImageRepoOption
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/repo [get] // @Router /containers/repo [get]
func (b *BaseApi) ListRepo(c *gin.Context) { func (b *BaseApi) ListRepo(c *gin.Context) {

View File

@@ -27,7 +27,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) {
// @Description 获取部分 OpenResty 配置信息 // @Description 获取部分 OpenResty 配置信息
// @Accept json // @Accept json
// @Param request body request.NginxScopeReq true "request" // @Param request body request.NginxScopeReq true "request"
// @Success 200 {anrry} response.NginxParam // @Success 200 {array} response.NginxParam
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /openResty/scope [post] // @Router /openResty/scope [post]
func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {

View File

@@ -0,0 +1,40 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
websocket2 "github.com/1Panel-dev/1Panel/backend/utils/websocket"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) ProcessWs(c *gin.Context) {
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
wsClient := websocket2.NewWsClient("processClient", ws)
go wsClient.Read()
go wsClient.Write()
}
// @Tags Process
// @Summary Stop Process
// @Description 停止进程
// @Param request body request.ProcessReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /process/stop [post]
// @x-panel-log {"bodyKeys":["PID"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"结束进程 [PID]","formatEN":"结束进程 [PID]"}
func (b *BaseApi) StopProcess(c *gin.Context) {
var req request.ProcessReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := processService.StopProcess(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@@ -2,6 +2,8 @@ package v1
import ( import (
"errors" "errors"
"fmt"
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -260,11 +262,23 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) {
// @Tags System Setting // @Tags System Setting
// @Summary Load mfa info // @Summary Load mfa info
// @Description 获取 mfa 信息 // @Description 获取 mfa 信息
// @Param interval path string true "request"
// @Success 200 {object} mfa.Otp // @Success 200 {object} mfa.Otp
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /settings/mfa [get] // @Router /settings/mfa/:interval [get]
func (b *BaseApi) GetMFA(c *gin.Context) { func (b *BaseApi) GetMFA(c *gin.Context) {
otp, err := mfa.GetOtp("admin") intervalStr, ok := c.Params.Get("interval")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error interval in path"))
return
}
interval, err := strconv.Atoi(intervalStr)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("type conversion failed, err: %v", err))
return
}
otp, err := mfa.GetOtp("admin", interval)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@@ -288,12 +302,17 @@ func (b *BaseApi) MFABind(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
success := mfa.ValidCode(req.Code, req.Secret) success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
if !success { if !success {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid")) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid"))
return return
} }
if err := settingService.Update("MFAInterval", req.Interval); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err := settingService.Update("MFAStatus", "enable"); err != nil { if err := settingService.Update("MFAStatus", "enable"); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return

View File

@@ -9,8 +9,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier" "github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -22,24 +20,27 @@ import (
) )
func (b *BaseApi) WsSsh(c *gin.Context) { func (b *BaseApi) WsSsh(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id")) wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
id, err := strconv.Atoi(c.Query("id"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
return return
} }
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil { if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil { if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
host, err := hostService.GetHostInfo(uint(id)) host, err := hostService.GetHostInfo(uint(id))
if err != nil { if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
var connInfo ssh.ConnInfo var connInfo ssh.ConnInfo
@@ -49,13 +50,6 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
connInfo.PassPhrase = []byte(host.PassPhrase) connInfo.PassPhrase = []byte(host.PassPhrase)
} }
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
client, err := connInfo.NewClient() client, err := connInfo.NewClient()
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return return
@@ -85,28 +79,25 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
} }
func (b *BaseApi) RedisWsSsh(c *gin.Context) { func (b *BaseApi) RedisWsSsh(c *gin.Context) {
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
redisConf, err := redisService.LoadConf()
if err != nil {
global.LOG.Errorf("load redis container failed, err: %v", err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err) global.LOG.Errorf("gin context http handler failed, err: %v", err)
return return
} }
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
redisConf, err := redisService.LoadConf()
if wshandleError(wsConn, errors.WithMessage(err, "load redis container failed")) {
return
}
defer wsConn.Close() defer wsConn.Close()
commands := "redis-cli" commands := "redis-cli"
if len(redisConf.Requirepass) != 0 { if len(redisConf.Requirepass) != 0 {
@@ -138,24 +129,6 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
} }
func (b *BaseApi) ContainerWsSsh(c *gin.Context) { func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
containerID := c.Query("containerid")
command := c.Query("command")
user := c.Query("user")
if len(command) == 0 || len(containerID) == 0 {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error param of command or containerID"))
return
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err) global.LOG.Errorf("gin context http handler failed, err: %v", err)
@@ -163,11 +136,33 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
} }
defer wsConn.Close() defer wsConn.Close()
cmds := fmt.Sprintf("docker exec %s %s", containerID, command) containerID := c.Query("containerid")
if len(user) != 0 { command := c.Query("command")
cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command) user := c.Query("user")
if len(command) == 0 || len(containerID) == 0 {
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
return
}
} }
stdout, err := cmd.Exec(cmds) cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
cmds := []string{"exec", containerID, command}
if len(user) != 0 {
cmds = []string{"exec", "-u", user, containerID, command}
}
if cmd.CheckIllegal(user, containerID, command) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
}
stdout, err := cmd.ExecWithCheck("docker", cmds...)
if wshandleError(wsConn, errors.WithMessage(err, stdout)) { if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
return return
} }

View File

@@ -36,7 +36,7 @@ func (b *BaseApi) PageWebsite(c *gin.Context) {
// @Tags Website // @Tags Website
// @Summary List websites // @Summary List websites
// @Description 获取网站列表 // @Description 获取网站列表
// @Success 200 {anrry} response.WebsiteDTO // @Success 200 {array} response.WebsiteDTO
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /websites/list [get] // @Router /websites/list [get]
func (b *BaseApi) GetWebsites(c *gin.Context) { func (b *BaseApi) GetWebsites(c *gin.Context) {
@@ -51,7 +51,7 @@ func (b *BaseApi) GetWebsites(c *gin.Context) {
// @Tags Website // @Tags Website
// @Summary List website names // @Summary List website names
// @Description 获取网站列表 // @Description 获取网站列表
// @Success 200 {anrry} string // @Success 200 {array} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /websites/options [get] // @Router /websites/options [get]
func (b *BaseApi) GetWebsiteOptions(c *gin.Context) { func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
@@ -207,7 +207,7 @@ func (b *BaseApi) GetWebsiteNginx(c *gin.Context) {
// @Description 通过网站 id 查询域名 // @Description 通过网站 id 查询域名
// @Accept json // @Accept json
// @Param websiteId path integer true "request" // @Param websiteId path integer true "request"
// @Success 200 {anrry} model.WebsiteDomain // @Success 200 {array} model.WebsiteDomain
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /websites/domains/:websiteId [get] // @Router /websites/domains/:websiteId [get]
func (b *BaseApi) GetWebDomains(c *gin.Context) { func (b *BaseApi) GetWebDomains(c *gin.Context) {
@@ -367,7 +367,7 @@ func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) {
// @Description 网站创建前检查 // @Description 网站创建前检查
// @Accept json // @Accept json
// @Param request body request.WebsiteInstallCheckReq true "request" // @Param request body request.WebsiteInstallCheckReq true "request"
// @Success 200 {anrry} request.WebsitePreInstallCheck // @Success 200 {array} response.WebsitePreInstallCheck
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /websites/check [post] // @Router /websites/check [post]
func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) { func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
@@ -541,7 +541,7 @@ func (b *BaseApi) UpdateWebsitePHPConfig(c *gin.Context) {
// @Tags Website PHP // @Tags Website PHP
// @Summary Update php conf // @Summary Update php conf
// @Description 更新 php 配置 // @Description 更新 php 配置文件
// @Accept json // @Accept json
// @Param request body request.WebsitePHPFileUpdate true "request" // @Param request body request.WebsitePHPFileUpdate true "request"
// @Success 200 // @Success 200

View File

@@ -35,7 +35,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
Items: accounts, Items: accounts,
}) })
} else { } else {
list, err := websiteSSLService.Search() list, err := websiteSSLService.Search(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@@ -94,7 +94,7 @@ func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) {
// @Description 解析网站 ssl // @Description 解析网站 ssl
// @Accept json // @Accept json
// @Param request body request.WebsiteDNSReq true "request" // @Param request body request.WebsiteDNSReq true "request"
// @Success 200 {anrry} response.WebsiteDNSRes // @Success 200 {array} response.WebsiteDNSRes
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /websites/ssl/resolve [post] // @Router /websites/ssl/resolve [post]
func (b *BaseApi) GetDNSResolve(c *gin.Context) { func (b *BaseApi) GetDNSResolve(c *gin.Context) {

View File

@@ -12,8 +12,9 @@ type UserLoginInfo struct {
} }
type MfaCredential struct { type MfaCredential struct {
Secret string `json:"secret"` Secret string `json:"secret"`
Code string `json:"code"` Code string `json:"code"`
Interval string `json:"interval"`
} }
type Login struct { type Login struct {

View File

@@ -8,15 +8,17 @@ type BackupOperate struct {
Bucket string `json:"bucket"` Bucket string `json:"bucket"`
AccessKey string `json:"accessKey"` AccessKey string `json:"accessKey"`
Credential string `json:"credential"` Credential string `json:"credential"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars" validate:"required"` Vars string `json:"vars" validate:"required"`
} }
type BackupInfo struct { type BackupInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Type string `json:"type"` Type string `json:"type"`
Bucket string `json:"bucket"` Bucket string `json:"bucket"`
Vars string `json:"vars"` BackupPath string `json:"backupPath"`
Vars string `json:"vars"`
} }
type BackupSearch struct { type BackupSearch struct {
@@ -36,7 +38,7 @@ type CommonBackup struct {
DetailName string `json:"detailName"` DetailName string `json:"detailName"`
} }
type CommonRecover struct { type CommonRecover struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO"` Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"`
Type string `json:"type" validate:"required,oneof=app mysql redis website"` Type string `json:"type" validate:"required,oneof=app mysql redis website"`
Name string `json:"name"` Name string `json:"name"`
DetailName string `json:"detailName"` DetailName string `json:"detailName"`
@@ -60,7 +62,7 @@ type BackupRecords struct {
} }
type DownloadRecord struct { type DownloadRecord struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO"` Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"`
FileDir string `json:"fileDir" validate:"required"` FileDir string `json:"fileDir" validate:"required"`
FileName string `json:"fileName" validate:"required"` FileName string `json:"fileName" validate:"required"`
} }

View File

@@ -2,7 +2,9 @@ package dto
type SearchWithPage struct { type SearchWithPage struct {
PageInfo PageInfo
Info string `json:"info"` Info string `json:"info"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
} }
type PageInfo struct { type PageInfo struct {

View File

@@ -5,6 +5,8 @@ import "time"
type PageContainer struct { type PageContainer struct {
PageInfo PageInfo
Name string `json:"name"` Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
Filters string `json:"filters"` Filters string `json:"filters"`
} }
@@ -22,22 +24,29 @@ type ContainerInfo struct {
State string `json:"state"` State string `json:"state"`
RunTime string `json:"runTime"` RunTime string `json:"runTime"`
CPUPercent float64 `json:"cpuPercent"` Ports []string `json:"ports"`
MemoryPercent float64 `json:"memoryPercent"`
Ports []string `json:"ports"`
IsFromApp bool `json:"isFromApp"` IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"` IsFromCompose bool `json:"isFromCompose"`
} }
type ContainerCreate struct { type ResourceLimit struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
}
type ContainerOperate struct {
ContainerID string `json:"containerID"`
ForcePull bool `json:"forcePull"`
Name string `json:"name"` Name string `json:"name"`
Image string `json:"image"` Image string `json:"image"`
Network string `json:"network"`
PublishAllPorts bool `json:"publishAllPorts"` PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"` ExposedPorts []PortHelper `json:"exposedPorts"`
Cmd []string `json:"cmd"` Cmd []string `json:"cmd"`
NanoCPUs int64 `json:"nanoCPUs"` CPUShares int64 `json:"cpuShares"`
Memory int64 `json:"memory"` NanoCPUs float64 `json:"nanoCPUs"`
Memory float64 `json:"memory"`
AutoRemove bool `json:"autoRemove"` AutoRemove bool `json:"autoRemove"`
Volumes []VolumeHelper `json:"volumes"` Volumes []VolumeHelper `json:"volumes"`
Labels []string `json:"labels"` Labels []string `json:"labels"`
@@ -45,7 +54,19 @@ type ContainerCreate struct {
RestartPolicy string `json:"restartPolicy"` RestartPolicy string `json:"restartPolicy"`
} }
type ContainterStats struct { type ContainerUpgrade struct {
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
ForcePull bool `json:"forcePull"`
}
type ContainerListStats struct {
ContainerID string `json:"containerID"`
CPUPercent float64 `json:"cpuPercent"`
MemoryPercent float64 `json:"memoryPercent"`
}
type ContainerStats struct {
CPUPercent float64 `json:"cpuPercent"` CPUPercent float64 `json:"cpuPercent"`
Memory float64 `json:"memory"` Memory float64 `json:"memory"`
Cache float64 `json:"cache"` Cache float64 `json:"cache"`
@@ -69,11 +90,6 @@ type PortHelper struct {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
} }
type ContainerLog struct {
ContainerID string `json:"containerID" validate:"required"`
Mode string `json:"mode" validate:"required"`
}
type ContainerOperation struct { type ContainerOperation struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause rename remove"` Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause rename remove"`
@@ -82,7 +98,7 @@ type ContainerOperation struct {
type ContainerPrune struct { type ContainerPrune struct {
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"` PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
WithTagAll bool `josn:"withTagAll"` WithTagAll bool `json:"withTagAll"`
} }
type ContainerPruneReport struct { type ContainerPruneReport struct {

View File

@@ -6,13 +6,14 @@ type CronjobCreate struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`
SpecType string `json:"specType" validate:"required"` SpecType string `json:"specType" validate:"required"`
Week int `json:"week" validate:"number,max=7,min=1"` Week int `json:"week" validate:"number,max=6,min=0"`
Day int `json:"day" validate:"number"` Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"` Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"` Minute int `json:"minute" validate:"number"`
Second int `json:"second" validate:"number"` Second int `json:"second" validate:"number"`
Script string `json:"script"` Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"` DBName string `json:"dbName"`
@@ -27,13 +28,14 @@ type CronjobUpdate struct {
ID uint `json:"id" validate:"required"` ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
SpecType string `json:"specType" validate:"required"` SpecType string `json:"specType" validate:"required"`
Week int `json:"week" validate:"number,max=7,min=1"` Week int `json:"week" validate:"number,max=6,min=0"`
Day int `json:"day" validate:"number"` Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"` Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"` Minute int `json:"minute" validate:"number"`
Second int `json:"second" validate:"number"` Second int `json:"second" validate:"number"`
Script string `json:"script"` Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"` DBName string `json:"dbName"`
@@ -76,6 +78,7 @@ type CronjobInfo struct {
Second int `json:"second"` Second int `json:"second"`
Script string `json:"script"` Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"` Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"` ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"` DBName string `json:"dbName"`

View File

@@ -10,35 +10,35 @@ type ImageInfo struct {
} }
type ImageLoad struct { type ImageLoad struct {
Path string `josn:"path" validate:"required"` Path string `json:"path" validate:"required"`
} }
type ImageBuild struct { type ImageBuild struct {
From string `josn:"from" validate:"required"` From string `json:"from" validate:"required"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Dockerfile string `josn:"dockerfile" validate:"required"` Dockerfile string `json:"dockerfile" validate:"required"`
Tags []string `josn:"tags"` Tags []string `json:"tags"`
} }
type ImagePull struct { type ImagePull struct {
RepoID uint `josn:"repoID"` RepoID uint `json:"repoID"`
ImageName string `josn:"imageName" validate:"required"` ImageName string `json:"imageName" validate:"required"`
} }
type ImageTag struct { type ImageTag struct {
RepoID uint `josn:"repoID"` RepoID uint `json:"repoID"`
SourceID string `json:"sourceID" validate:"required"` SourceID string `json:"sourceID" validate:"required"`
TargetName string `josn:"targetName" validate:"required"` TargetName string `json:"targetName" validate:"required"`
} }
type ImagePush struct { type ImagePush struct {
RepoID uint `josn:"repoID" validate:"required"` RepoID uint `json:"repoID" validate:"required"`
TagName string `json:"tagName" validate:"required"` TagName string `json:"tagName" validate:"required"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
} }
type ImageSave struct { type ImageSave struct {
TagName string `json:"tagName" validate:"required"` TagName string `json:"tagName" validate:"required"`
Path string `josn:"path" validate:"required"` Path string `json:"path" validate:"required"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
} }

View File

@@ -66,6 +66,11 @@ type AppInstalledUpdate struct {
AppContainerConfig AppContainerConfig
} }
type AppInstalledIgnoreUpgrade struct {
DetailID uint `json:"detailID" validate:"required"`
Operate string `json:"operate" validate:"required,oneof=cancel ignore"`
}
type PortUpdate struct { type PortUpdate struct {
Key string `json:"key"` Key string `json:"key"`
Name string `json:"name"` Name string `json:"name"`

View File

@@ -0,0 +1,5 @@
package request
type ProcessReq struct {
PID int32 `json:"PID" validate:"required"`
}

View File

@@ -7,6 +7,8 @@ import (
type WebsiteSearch struct { type WebsiteSearch struct {
dto.PageInfo dto.PageInfo
Name string `json:"name"` Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
WebsiteGroupID uint `json:"websiteGroupId"` WebsiteGroupID uint `json:"websiteGroupId"`
} }
@@ -113,15 +115,18 @@ type WebsiteDomainDelete struct {
} }
type WebsiteHTTPSOp struct { type WebsiteHTTPSOp struct {
WebsiteID uint `json:"websiteId" validate:"required"` WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"` Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"` WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"` Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"` PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"` Certificate string `json:"certificate"`
HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` PrivateKeyPath string `json:"privateKeyPath"`
SSLProtocol []string `json:"SSLProtocol"` CertificatePath string `json:"certificatePath"`
Algorithm string `json:"algorithm"` ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
} }
type WebsiteNginxUpdate struct { type WebsiteNginxUpdate struct {

View File

@@ -4,6 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/dto"
type WebsiteSSLSearch struct { type WebsiteSSLSearch struct {
dto.PageInfo dto.PageInfo
AcmeAccountID string `json:"acmeAccountID"`
} }
type WebsiteSSLCreate struct { type WebsiteSSLCreate struct {

View File

@@ -48,6 +48,13 @@ type AppDetailDTO struct {
Image string `json:"image"` Image string `json:"image"`
} }
type IgnoredApp struct {
Icon string `json:"icon"`
Name string `json:"name"`
Version string `json:"version"`
DetailID uint `json:"detailID"`
}
type AppInstalledDTO struct { type AppInstalledDTO struct {
model.AppInstall model.AppInstall
Total int `json:"total"` Total int `json:"total"`

View File

@@ -5,6 +5,7 @@ import "time"
type SettingInfo struct { type SettingInfo struct {
UserName string `json:"userName"` UserName string `json:"userName"`
Email string `json:"email"` Email string `json:"email"`
SystemIP string `json:"systemIP"`
SystemVersion string `json:"systemVersion"` SystemVersion string `json:"systemVersion"`
SessionTimeout string `json:"sessionTimeout"` SessionTimeout string `json:"sessionTimeout"`
@@ -28,6 +29,7 @@ type SettingInfo struct {
ComplexityVerification string `json:"complexityVerification"` ComplexityVerification string `json:"complexityVerification"`
MFAStatus string `json:"mfaStatus"` MFAStatus string `json:"mfaStatus"`
MFASecret string `json:"mfaSecret"` MFASecret string `json:"mfaSecret"`
MFAInterval string `json:"mfaInterval"`
MonitorStatus string `json:"monitorStatus"` MonitorStatus string `json:"monitorStatus"`
MonitorInterval string `json:"monitorInterval"` MonitorInterval string `json:"monitorInterval"`
@@ -74,7 +76,7 @@ type PortUpdate struct {
} }
type SnapshotCreate struct { type SnapshotCreate struct {
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO"` From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"`
Description string `json:"description" validate:"max=256"` Description string `json:"description" validate:"max=256"`
} }
type SnapshotRecover struct { type SnapshotRecover struct {

View File

@@ -12,4 +12,5 @@ type AppDetail struct {
DownloadUrl string `json:"downloadUrl" gorm:"type:varchar;"` DownloadUrl string `json:"downloadUrl" gorm:"type:varchar;"`
DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"` DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"`
Update bool `json:"update"` Update bool `json:"update"`
IgnoreUpgrade bool `json:"ignoreUpgrade"`
} }

View File

@@ -6,6 +6,7 @@ type BackupAccount struct {
Bucket string `gorm:"type:varchar(256)" json:"bucket"` Bucket string `gorm:"type:varchar(256)" json:"bucket"`
AccessKey string `gorm:"type:varchar(256)" json:"accessKey"` AccessKey string `gorm:"type:varchar(256)" json:"accessKey"`
Credential string `gorm:"type:varchar(256)" json:"credential"` Credential string `gorm:"type:varchar(256)" json:"credential"`
BackupPath string `gorm:"type:varchar(256)" json:"backupPath"`
Vars string `gorm:"type:longText" json:"vars"` Vars string `gorm:"type:longText" json:"vars"`
} }

View File

@@ -15,6 +15,7 @@ type Cronjob struct {
Minute uint64 `gorm:"type:decimal" json:"minute"` Minute uint64 `gorm:"type:decimal" json:"minute"`
Second uint64 `gorm:"type:decimal" json:"second"` Second uint64 `gorm:"type:decimal" json:"second"`
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
Script string `gorm:"longtext" json:"script"` Script string `gorm:"longtext" json:"script"`
Website string `gorm:"type:varchar(64)" json:"website"` Website string `gorm:"type:varchar(64)" json:"website"`
DBName string `gorm:"type:varchar(64)" json:"dbName"` DBName string `gorm:"type:varchar(64)" json:"dbName"`

View File

@@ -13,6 +13,7 @@ type AppDetailRepo struct {
type IAppDetailRepo interface { type IAppDetailRepo interface {
WithVersion(version string) DBOption WithVersion(version string) DBOption
WithAppId(id uint) DBOption WithAppId(id uint) DBOption
WithIgnored() DBOption
GetFirst(opts ...DBOption) (model.AppDetail, error) GetFirst(opts ...DBOption) (model.AppDetail, error)
Update(ctx context.Context, detail model.AppDetail) error Update(ctx context.Context, detail model.AppDetail) error
BatchCreate(ctx context.Context, details []model.AppDetail) error BatchCreate(ctx context.Context, details []model.AppDetail) error
@@ -31,12 +32,19 @@ func (a AppDetailRepo) WithVersion(version string) DBOption {
return g.Where("version = ?", version) return g.Where("version = ?", version)
} }
} }
func (a AppDetailRepo) WithAppId(id uint) DBOption { func (a AppDetailRepo) WithAppId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
return g.Where("app_id = ?", id) return g.Where("app_id = ?", id)
} }
} }
func (a AppDetailRepo) WithIgnored() DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("ignore_upgrade = 1")
}
}
func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) { func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) {
var detail model.AppDetail var detail model.AppDetail
err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error

View File

@@ -19,6 +19,7 @@ type IAppInstallResourceRpo interface {
GetFirst(opts ...DBOption) (model.AppInstallResource, error) GetFirst(opts ...DBOption) (model.AppInstallResource, error)
Create(ctx context.Context, resource *model.AppInstallResource) error Create(ctx context.Context, resource *model.AppInstallResource) error
DeleteBy(ctx context.Context, opts ...DBOption) error DeleteBy(ctx context.Context, opts ...DBOption) error
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
} }
func NewIAppInstallResourceRpo() IAppInstallResourceRpo { func NewIAppInstallResourceRpo() IAppInstallResourceRpo {
@@ -71,3 +72,11 @@ func (a AppInstallResourceRpo) Create(ctx context.Context, resource *model.AppIn
func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error { func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error
} }
func (a *AppInstallResourceRpo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error {
db := getDb(opts...).Model(&model.AppInstallResource{})
if len(opts) == 0 {
db = db.Where("1=1")
}
return db.Updates(&maps).Error
}

View File

@@ -2,6 +2,7 @@ package repo
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
@@ -16,6 +17,7 @@ type ICommonRepo interface {
WithByName(name string) DBOption WithByName(name string) DBOption
WithByType(tp string) DBOption WithByType(tp string) DBOption
WithOrderBy(orderStr string) DBOption WithOrderBy(orderStr string) DBOption
WithOrderRuleBy(orderBy, order string) DBOption
WithByGroupID(groupID uint) DBOption WithByGroupID(groupID uint) DBOption
WithLikeName(name string) DBOption WithLikeName(name string) DBOption
WithIdsIn(ids []uint) DBOption WithIdsIn(ids []uint) DBOption
@@ -93,6 +95,21 @@ func (c *CommonRepo) WithOrderBy(orderStr string) DBOption {
} }
} }
func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) DBOption {
switch order {
case constant.OrderDesc:
order = "desc"
case constant.OrderAsc:
order = "asc"
default:
orderBy = "created_at"
order = "desc"
}
return func(g *gorm.DB) *gorm.DB {
return g.Order(fmt.Sprintf("%s %s", orderBy, order))
}
}
func (c *CommonRepo) WithIdsIn(ids []uint) DBOption { func (c *CommonRepo) WithIdsIn(ids []uint) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
return g.Where("id in (?)", ids) return g.Where("id in (?)", ids)

View File

@@ -15,6 +15,7 @@ type IComposeTemplateRepo interface {
Update(id uint, vars map[string]interface{}) error Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error Delete(opts ...DBOption) error
GetRecord(opts ...DBOption) (model.Compose, error)
CreateRecord(compose *model.Compose) error CreateRecord(compose *model.Compose) error
DeleteRecord(opts ...DBOption) error DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error) ListRecord() ([]model.Compose, error)
@@ -72,6 +73,16 @@ func (u *ComposeTemplateRepo) Delete(opts ...DBOption) error {
return db.Delete(&model.ComposeTemplate{}).Error return db.Delete(&model.ComposeTemplate{}).Error
} }
func (u *ComposeTemplateRepo) GetRecord(opts ...DBOption) (model.Compose, error) {
var compose model.Compose
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&compose).Error
return compose, err
}
func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) { func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) {
var composes []model.Compose var composes []model.Compose
if err := global.DB.Find(&composes).Error; err != nil { if err := global.DB.Find(&composes).Error; err != nil {

View File

@@ -83,7 +83,7 @@ func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cro
} }
count := int64(0) count := int64(0)
db = db.Count(&count) db = db.Count(&count)
err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error err := db.Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error
return count, cronjobs, err return count, cronjobs, err
} }

View File

@@ -24,6 +24,7 @@ import (
"os" "os"
"path" "path"
"strconv" "strconv"
"strings"
) )
type AppService struct { type AppService struct {
@@ -39,6 +40,7 @@ type IAppService interface {
GetAppUpdate() (*response.AppUpdateRes, error) GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error) GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal() SyncAppListFromLocal()
GetIgnoredApp() ([]response.IgnoredApp, error)
} }
func NewIAppService() IAppService { func NewIAppService() IAppService {
@@ -82,12 +84,16 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
return nil, err return nil, err
} }
var appDTOs []*response.AppDTO var appDTOs []*response.AppDTO
for _, a := range apps { for _, ap := range apps {
ap.ReadMe = ""
ap.Website = ""
ap.Document = ""
ap.Github = ""
appDTO := &response.AppDTO{ appDTO := &response.AppDTO{
App: a, App: ap,
} }
appDTOs = append(appDTOs, appDTO) appDTOs = append(appDTOs, appDTO)
appTags, err := appTagRepo.GetByAppId(a.ID) appTags, err := appTagRepo.GetByAppId(ap.ID)
if err != nil { if err != nil {
continue continue
} }
@@ -100,7 +106,7 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
continue continue
} }
appDTO.Tags = tags appDTO.Tags = tags
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(a.ID)) installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID))
appDTO.Installed = len(installs) > 0 appDTO.Installed = len(installs) > 0
} }
res.Items = appDTOs res.Items = appDTOs
@@ -163,7 +169,7 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response.
} }
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
versionPath := path.Join(constant.AppResourceDir, app.Resource, app.Key, detail.Version) versionPath := path.Join(constant.AppResourceDir, app.Resource, app.Key, detail.Version)
if !fileOp.Stat(versionPath) { if !fileOp.Stat(versionPath) || detail.Update {
if err = downloadApp(app, detail, nil); err != nil { if err = downloadApp(app, detail, nil); err != nil {
return appDetailDTO, err return appDetailDTO, err
} }
@@ -232,6 +238,27 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
return res, nil return res, nil
} }
func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) {
var res []response.IgnoredApp
details, _ := appDetailRepo.GetBy(appDetailRepo.WithIgnored())
if len(details) == 0 {
return res, nil
}
for _, detail := range details {
app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId))
if err != nil {
return nil, err
}
res = append(res, response.IgnoredApp{
Name: app.Name,
Version: detail.Version,
DetailID: detail.ID,
Icon: app.Icon,
})
}
return res, nil
}
func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) { func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
if err = docker.CreateDefaultDockerNetwork(); err != nil { if err = docker.CreateDefaultDockerNetwork(); err != nil {
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
@@ -247,14 +274,23 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
appDetail model.AppDetail appDetail model.AppDetail
app model.App app model.App
) )
httpPort, err = checkPort("PANEL_APP_PORT_HTTP", req.Params) for key := range req.Params {
if err != nil { if !strings.Contains(key, "PANEL_APP_PORT") {
return continue
} }
httpsPort, err = checkPort("PANEL_APP_PORT_HTTPS", req.Params) var port int
if err != nil { if port, err = checkPort(key, req.Params); err == nil {
return if key == "PANEL_APP_PORT_HTTP" {
httpPort = port
}
if key == "PANEL_APP_PORT_HTTPS" {
httpsPort = port
}
} else {
return
}
} }
appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
if err != nil { if err != nil {
return return
@@ -470,9 +506,13 @@ func (a AppService) SyncAppListFromLocal() {
oldDetail, exist := appDetails[version] oldDetail, exist := appDetails[version]
if exist { if exist {
newDetail.ID = oldDetail.ID newDetail.ID = oldDetail.ID
delete(appDetails, version)
} }
app.Details[i] = newDetail app.Details[i] = newDetail
} }
for _, v := range appDetails {
app.Details = append(app.Details, v)
}
} }
app.TagsKey = append(app.TagsKey, constant.AppResourceLocal) app.TagsKey = append(app.TagsKey, constant.AppResourceLocal)
apps[app.Key] = app apps[app.Key] = app
@@ -730,10 +770,8 @@ func (a AppService) SyncAppListFromRemote() error {
detail.Params = string(paramByte) detail.Params = string(paramByte)
detail.DownloadUrl = v.DownloadUrl detail.DownloadUrl = v.DownloadUrl
detail.DownloadCallBackUrl = v.DownloadCallBackUrl detail.DownloadCallBackUrl = v.DownloadCallBackUrl
if v.LastModified > detail.LastModified { detail.Update = true
detail.Update = true detail.LastModified = v.LastModified
detail.LastModified = v.LastModified
}
detailsMap[version] = detail detailsMap[version] = detail
} }
var newDetails []model.AppDetail var newDetails []model.AppDetail

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/i18n"
"math" "math"
"os" "os"
"path" "path"
@@ -45,6 +46,7 @@ type IAppInstallService interface {
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
Operate(req request.AppInstalledOperate) error Operate(req request.AppInstalledOperate) error
Update(req request.AppInstalledUpdate) error Update(req request.AppInstalledUpdate) error
IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error
SyncAll(systemInit bool) error SyncAll(systemInit bool) error
GetServices(key string) ([]response.AppService, error) GetServices(key string) ([]response.AppService, error)
GetUpdateVersions(installId uint) ([]dto.AppVersion, error) GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
@@ -352,6 +354,15 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
return nil return nil
} }
func (a *AppInstallService) IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error {
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.DetailID))
if err != nil {
return err
}
appDetail.IgnoreUpgrade = req.Operate == "ignore"
return appDetailRepo.Update(context.Background(), appDetail)
}
func (a *AppInstallService) SyncAll(systemInit bool) error { func (a *AppInstallService) SyncAll(systemInit bool) error {
allList, err := appInstallRepo.ListBy() allList, err := appInstallRepo.ListBy()
if err != nil { if err != nil {
@@ -366,8 +377,10 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
} }
continue continue
} }
if err := syncById(i.ID); err != nil { if !systemInit {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error()) if err := syncById(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
} }
} }
return nil return nil
@@ -412,6 +425,9 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
return versions, err return versions, err
} }
for _, detail := range details { for _, detail := range details {
if detail.IgnoreUpgrade {
continue
}
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate { if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
continue continue
} }
@@ -577,6 +593,9 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
config := getAppCommonConfig(envs) config := getAppCommonConfig(envs)
config.DockerCompose = install.DockerCompose config.DockerCompose = install.DockerCompose
res.Params = params res.Params = params
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
}
res.AppContainerConfig = config res.AppContainerConfig = config
return &res, nil return &res, nil
} }
@@ -589,12 +608,10 @@ func syncById(installId uint) error {
if appInstall.Status == constant.Installing { if appInstall.Status == constant.Installing {
return nil return nil
} }
containerNames, err := getContainerNames(appInstall) containerNames, err := getContainerNames(appInstall)
if err != nil { if err != nil {
return err return err
} }
cli, err := docker.NewClient() cli, err := docker.NewClient()
if err != nil { if err != nil {
return err return err
@@ -620,16 +637,16 @@ func syncById(installId uint) error {
errorContainers = append(errorContainers, n.Names[0]) errorContainers = append(errorContainers, n.Names[0])
} }
} }
for _, old := range containerNames { for _, name := range containerNames {
exist := false exist := false
for _, new := range containers { for _, container := range containers {
if common.ExistWithStrArray(old, new.Names) { if common.ExistWithStrArray(name, container.Names) {
exist = true exist = true
break break
} }
} }
if !exist { if !exist {
notFoundContainers = append(notFoundContainers, old) notFoundContainers = append(notFoundContainers, name)
} }
} }
@@ -642,10 +659,10 @@ func syncById(installId uint) error {
if containerCount == 0 { if containerCount == 0 {
appInstall.Status = constant.Error appInstall.Status = constant.Error
appInstall.Message = "container is not found" appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
return appInstallRepo.Save(context.Background(), &appInstall) return appInstallRepo.Save(context.Background(), &appInstall)
} }
if errCount == 0 && existedCount == 0 { if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
appInstall.Status = constant.Running appInstall.Status = constant.Running
return appInstallRepo.Save(context.Background(), &appInstall) return appInstallRepo.Save(context.Background(), &appInstall)
} }
@@ -660,22 +677,14 @@ func syncById(installId uint) error {
appInstall.Status = constant.UnHealthy appInstall.Status = constant.UnHealthy
} }
var errMsg strings.Builder var errMsg string
if errCount > 0 { if errCount > 0 {
errMsg.Write([]byte(string(rune(errCount)) + " error containers:")) errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
for _, e := range errorContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
} }
if notFoundCount > 0 { if notFoundCount > 0 {
errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:")) errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
for _, e := range notFoundContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
} }
appInstall.Message = errMsg.String() appInstall.Message = errMsg
return appInstallRepo.Save(context.Background(), &appInstall) return appInstallRepo.Save(context.Background(), &appInstall)
} }

View File

@@ -8,7 +8,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/compose-spec/compose-go/types"
"github.com/subosito/gotenv" "github.com/subosito/gotenv"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"math" "math"
@@ -49,7 +48,20 @@ var (
func checkPort(key string, params map[string]interface{}) (int, error) { func checkPort(key string, params map[string]interface{}) (int, error) {
port, ok := params[key] port, ok := params[key]
if ok { if ok {
portN := int(math.Ceil(port.(float64))) portN := 0
var err error
switch p := port.(type) {
case string:
portN, err = strconv.Atoi(p)
if err != nil {
return portN, nil
}
case float64:
portN = int(math.Ceil(p))
case int:
portN = p
}
oldInstalled, _ := appInstallRepo.ListBy(appInstallRepo.WithPort(portN)) oldInstalled, _ := appInstallRepo.ListBy(appInstallRepo.WithPort(portN))
if len(oldInstalled) > 0 { if len(oldInstalled) > 0 {
var apps []string var apps []string
@@ -359,7 +371,6 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
return nil, err return nil, err
} }
containerMap := make(map[string]struct{}) containerMap := make(map[string]struct{})
containerMap[install.ContainerName] = struct{}{}
for _, service := range project.AllServices() { for _, service := range project.AllServices() {
if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" { if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" {
continue continue
@@ -370,6 +381,9 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
for k := range containerMap { for k := range containerMap {
containerNames = append(containerNames, k) containerNames = append(containerNames, k)
} }
if len(containerNames) == 0 {
containerNames = append(containerNames, install.ContainerName)
}
return containerNames, nil return containerNames, nil
} }
@@ -522,27 +536,6 @@ func upAppPre(app model.App, appInstall *model.AppInstall) error {
return nil return nil
} }
func getServiceFromInstall(appInstall *model.AppInstall) (service *composeV2.ComposeService, err error) {
var (
project *types.Project
envStr string
)
envStr, err = coverEnvJsonToStr(appInstall.Env)
if err != nil {
return
}
project, err = composeV2.GetComposeProject(appInstall.Name, appInstall.GetPath(), []byte(appInstall.DockerCompose), []byte(envStr), true)
if err != nil {
return
}
service, err = composeV2.NewComposeService()
if err != nil {
return
}
service.SetProject(project)
return
}
func checkContainerNameIsExist(containerName, appDir string) (bool, error) { func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
client, err := composeV2.NewDockerClient() client, err := composeV2.NewDockerClient()
if err != nil { if err != nil {
@@ -571,13 +564,30 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
func upApp(appInstall *model.AppInstall) { func upApp(appInstall *model.AppInstall) {
upProject := func(appInstall *model.AppInstall) (err error) { upProject := func(appInstall *model.AppInstall) (err error) {
if err == nil { if err == nil {
var composeService *composeV2.ComposeService var (
composeService, err = getServiceFromInstall(appInstall) out string
if err != nil { errMsg string
return err )
if appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
} }
err = composeService.ComposeUp() out, err = compose.Up(appInstall.GetComposePath())
if err != nil { if err != nil {
if out != "" {
appInstall.Message = errMsg + out
}
return err return err
} }
return return
@@ -587,7 +597,6 @@ func upApp(appInstall *model.AppInstall) {
} }
if err := upProject(appInstall); err != nil { if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error appInstall.Status = constant.Error
appInstall.Message = err.Error()
} else { } else {
appInstall.Status = constant.Running appInstall.Status = constant.Running
} }
@@ -751,7 +760,7 @@ func handleErr(install model.AppInstall, err error, out string) error {
func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) { func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) {
var res []response.AppInstalledDTO var res []response.AppInstalledDTO
for _, installed := range appInstallList { for _, installed := range appInstallList {
if updated && installed.App.Type == "php" { if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
continue continue
} }
installDTO := response.AppInstalledDTO{ installDTO := response.AppInstalledDTO{
@@ -768,12 +777,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
} }
var versions []string var versions []string
for _, detail := range details { for _, detail := range details {
if detail.IgnoreUpgrade {
continue
}
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate { if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
continue continue
} }
versions = append(versions, detail.Version) versions = append(versions, detail.Version)
} }
versions = common.GetSortedVersions(versions) versions = common.GetSortedVersions(versions)
if len(versions) == 0 {
continue
}
lastVersion := versions[0] lastVersion := versions[0]
if common.IsCrossVersion(installed.Version, lastVersion) { if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate installDTO.CanUpdate = app.CrossVersionUpdate

View File

@@ -76,7 +76,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi
if err != nil { if err != nil {
return nil, err return nil, err
} }
success := mfa.ValidCode(info.Code, mfaSecret.Value) mfaInterval, err := settingRepo.Get(settingRepo.WithByKey("MFAInterval"))
if err != nil {
return nil, err
}
success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value)
if !success { if !success {
return nil, constant.ErrAuth return nil, constant.ErrAuth
} }

View File

@@ -2,8 +2,12 @@ package service
import ( import (
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"os" "os"
"path" "path"
"strings" "strings"
@@ -24,6 +28,7 @@ type BackupService struct{}
type IBackupService interface { type IBackupService interface {
List() ([]dto.BackupInfo, error) List() ([]dto.BackupInfo, error)
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
LoadOneDriveInfo() (string, error)
DownloadRecord(info dto.DownloadRecord) (string, error) DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) error Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
@@ -62,6 +67,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
dtobas = append(dtobas, u.loadByType("MINIO", ops)) dtobas = append(dtobas, u.loadByType("MINIO", ops))
dtobas = append(dtobas, u.loadByType("COS", ops)) dtobas = append(dtobas, u.loadByType("COS", ops))
dtobas = append(dtobas, u.loadByType("KODO", ops)) dtobas = append(dtobas, u.loadByType("KODO", ops))
dtobas = append(dtobas, u.loadByType("OneDrive", ops))
return dtobas, err return dtobas, err
} }
@@ -84,6 +90,18 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
return total, dtobas, err return total, dtobas, err
} }
func (u *BackupService) LoadOneDriveInfo() (string, error) {
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
if err != nil {
return "", err
}
idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value)
if err != nil {
return "", err
}
return string(idItem), err
}
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) { func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
if info.Source == "LOCAL" { if info.Source == "LOCAL" {
return info.FileDir + "/" + info.FileName, nil return info.FileDir + "/" + info.FileName, nil
@@ -96,7 +114,6 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", err return "", err
} }
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket varMap["bucket"] = backup.Bucket
switch backup.Type { switch backup.Type {
case constant.Sftp: case constant.Sftp:
@@ -105,8 +122,10 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential varMap["secretKey"] = backup.Credential
case constant.OneDrive:
varMap["accessToken"] = backup.Credential
} }
backClient, err := cloud_storage.NewCloudStorageClient(varMap) backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil { if err != nil {
return "", fmt.Errorf("new cloud storage client failed, err: %v", err) return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
} }
@@ -117,6 +136,11 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
} }
} }
srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName) srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName)
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
srcPath = itemPath + srcPath
}
if exist, _ := backClient.Exist(srcPath); exist { if exist, _ := backClient.Exist(srcPath); exist {
isOK, err := backClient.Download(srcPath, targetPath) isOK, err := backClient.Download(srcPath, targetPath)
if !isOK { if !isOK {
@@ -134,6 +158,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error {
if err := copier.Copy(&backup, &backupDto); err != nil { if err := copier.Copy(&backup, &backupDto); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error()) return errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
if backupDto.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
}
if err := backupRepo.Create(&backup); err != nil { if err := backupRepo.Create(&backup); err != nil {
return err return err
} }
@@ -145,7 +175,6 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil { if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil {
return nil, err return nil, err
} }
varMap["type"] = backupDto.Type
switch backupDto.Type { switch backupDto.Type {
case constant.Sftp: case constant.Sftp:
varMap["username"] = backupDto.AccessKey varMap["username"] = backupDto.AccessKey
@@ -154,7 +183,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
varMap["accessKey"] = backupDto.AccessKey varMap["accessKey"] = backupDto.AccessKey
varMap["secretKey"] = backupDto.Credential varMap["secretKey"] = backupDto.Credential
} }
client, err := cloud_storage.NewCloudStorageClient(varMap) client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -214,7 +243,17 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
upMap := make(map[string]interface{}) upMap := make(map[string]interface{})
upMap["bucket"] = req.Bucket upMap["bucket"] = req.Bucket
upMap["credential"] = req.Credential upMap["credential"] = req.Credential
upMap["backup_path"] = req.BackupPath
upMap["vars"] = req.Vars upMap["vars"] = req.Vars
backup.Vars = req.Vars
if req.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
upMap["credential"] = backup.Credential
upMap["vars"] = backup.Vars
}
if err := backupRepo.Update(req.ID, upMap); err != nil { if err := backupRepo.Update(req.ID, upMap); err != nil {
return err return err
} }
@@ -251,7 +290,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err return nil, err
} }
varMap["type"] = backup.Type
if backup.Type == "LOCAL" { if backup.Type == "LOCAL" {
return nil, errors.New("not support") return nil, errors.New("not support")
} }
@@ -263,9 +301,11 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential varMap["secretKey"] = backup.Credential
case constant.OneDrive:
varMap["accessToken"] = backup.Credential
} }
backClient, err := cloud_storage.NewCloudStorageClient(varMap) backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -286,6 +326,53 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
return dto.BackupInfo{Type: accountType} return dto.BackupInfo{Type: accountType}
} }
func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
}
data := url.Values{}
data.Set("client_id", global.CONF.System.OneDriveID)
data.Set("client_secret", global.CONF.System.OneDriveSc)
data.Set("grant_type", "authorization_code")
data.Set("code", varMap["code"].(string))
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
if err != nil {
return fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request for access token failed, err: %v", err)
}
delete(varMap, "code")
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()
token := map[string]interface{}{}
if err := json.Unmarshal(respBody, &token); err != nil {
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := token["refresh_token"].(string)
if !ok {
return errors.New("no such access token in response")
}
itemVars, err := json.Marshal(varMap)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
backup.Credential = accessToken
backup.Vars = string(itemVars)
return nil
}
func loadLocalDir() (string, error) { func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL")) backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil { if err != nil {

View File

@@ -148,7 +148,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
if err := json.Unmarshal(appjson, &oldInstall); err != nil { if err := json.Unmarshal(appjson, &oldInstall); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err) return fmt.Errorf("unmarshal app.json failed, err: %v", err)
} }
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name || oldInstall.Version != install.Version || oldInstall.ID != install.ID { if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name {
return errors.New("the current backup file does not match the application") return errors.New("the current backup file does not match the application")
} }
@@ -172,6 +172,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}() }()
} }
newEnvFile := ""
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID)) resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && install.App.Key != "mysql" { if resource.ID != 0 && install.App.Key != "mysql" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "") mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
@@ -182,7 +183,22 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
if err != nil { if err != nil {
return err return err
} }
if err := handleMysqlRecover(mysqlInfo, tmpPath, db.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env)
if err != nil {
return err
}
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
if err != nil {
return err
}
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
if err := handleMysqlRecover(mysqlInfo, tmpPath, newDB.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err) global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
return err return err
} }
@@ -193,11 +209,50 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
return err return err
} }
oldInstall.Status = constant.Running if len(newEnvFile) != 0 {
if err := appInstallRepo.Save(context.Background(), install); err != nil { envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
_, _ = file.WriteString(newEnvFile)
}
oldInstall.ID = install.ID
oldInstall.Status = constant.StatusRunning
oldInstall.AppId = install.AppId
oldInstall.AppDetailId = install.AppDetailId
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
global.LOG.Errorf("save db app install failed, err: %v", err) global.LOG.Errorf("save db app install failed, err: %v", err)
return err return err
} }
isOk = true isOk = true
return nil return nil
} }
func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
mysqlService := NewIMysqlService()
ctx := context.Background()
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, DeleteBackup: true, ForceDelete: true})
envMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
return nil, envMap, err
}
oldName, _ := envMap["PANEL_DB_NAME"].(string)
oldUser, _ := envMap["PANEL_DB_USER"].(string)
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
Name: oldName,
Format: "utf8mb4",
Username: oldUser,
Password: oldPassword,
Permission: "%",
})
if err != nil {
return nil, envMap, err
}
return createDB, envMap, nil
}

View File

@@ -3,11 +3,11 @@ package service
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -26,24 +26,34 @@ import (
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/gorilla/websocket"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
) )
type ContainerService struct{} type ContainerService struct{}
type IContainerService interface { type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error) Page(req dto.PageContainer) (int64, interface{}, error)
List() ([]string, error)
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
ListNetwork() ([]dto.Options, error)
PageVolume(req dto.SearchWithPage) (int64, interface{}, error) PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error) ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error) PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) (string, error) CreateCompose(req dto.ComposeCreate) (string, error)
ComposeOperation(req dto.ComposeOperation) error ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerCreate) error ContainerCreate(req dto.ContainerOperate) error
ContainerUpdate(req dto.ContainerOperate) error
ContainerUpgrade(req dto.ContainerUpgrade) error
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
ContainerListStats() ([]dto.ContainerListStats, error)
LoadResouceLimit() (*dto.ResourceLimit, error)
ContainerLogClean(req dto.OperationWithName) error ContainerLogClean(req dto.OperationWithName) error
ContainerOperation(req dto.ContainerOperation) error ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(param dto.ContainerLog) (string, error) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error
ContainerStats(id string) (*dto.ContainterStats, error) ContainerStats(id string) (*dto.ContainerStats, error)
Inspect(req dto.InspectReq) (string, error) Inspect(req dto.InspectReq) (string, error)
DeleteNetwork(req dto.BatchDelete) error DeleteNetwork(req dto.BatchDelete) error
CreateNetwork(req dto.NetworkCreate) error CreateNetwork(req dto.NetworkCreate) error
@@ -60,9 +70,8 @@ func NewIContainerService() IContainerService {
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) { func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
var ( var (
records []types.Container records []types.Container
list []types.Container list []types.Container
backDatas []dto.ContainerInfo
) )
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {
@@ -88,9 +97,30 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
} }
} }
} }
sort.Slice(list, func(i, j int) bool { switch req.OrderBy {
return list[i].Created > list[j].Created case "name":
}) sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Names[0][1:] < list[j].Names[0][1:]
}
return list[i].Names[0][1:] > list[j].Names[0][1:]
})
case "state":
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].State < list[j].State
}
return list[i].State > list[j].State
})
default:
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Created < list[j].Created
}
return list[i].Created > list[j].Created
})
}
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total { if start > total {
records = make([]types.Container, 0) records = make([]types.Container, 0)
@@ -101,49 +131,87 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
records = list[start:end] records = list[start:end]
} }
var wg sync.WaitGroup backDatas := make([]dto.ContainerInfo, len(records))
wg.Add(len(records)) for i := 0; i < len(records); i++ {
for _, container := range records { item := records[i]
go func(item types.Container) { IsFromCompose := false
IsFromCompose := false if _, ok := item.Labels[composeProjectLabel]; ok {
if _, ok := item.Labels[composeProjectLabel]; ok { IsFromCompose = true
IsFromCompose = true }
} IsFromApp := false
IsFromApp := false if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { IsFromApp = true
IsFromApp = true }
}
var ports []string var ports []string
for _, port := range item.Ports { for _, port := range item.Ports {
if port.IP == "::" || port.PublicPort == 0 { itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type)
continue if port.PublicPort != 0 {
} itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)
ports = append(ports, fmt.Sprintf("%v:%v/%s", port.PublicPort, port.PrivatePort, port.Type))
} }
cpu, mem := loadCpuAndMem(client, item.ID) ports = append(ports, itemPortStr)
backDatas = append(backDatas, dto.ContainerInfo{ }
ContainerID: item.ID, backDatas[i] = dto.ContainerInfo{
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"), ContainerID: item.ID,
Name: item.Names[0][1:], CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
ImageId: strings.Split(item.ImageID, ":")[1], Name: item.Names[0][1:],
ImageName: item.Image, ImageId: strings.Split(item.ImageID, ":")[1],
State: item.State, ImageName: item.Image,
RunTime: item.Status, State: item.State,
CPUPercent: cpu, RunTime: item.Status,
MemoryPercent: mem, Ports: ports,
Ports: ports, IsFromApp: IsFromApp,
IsFromApp: IsFromApp, IsFromCompose: IsFromCompose,
IsFromCompose: IsFromCompose, }
})
wg.Done()
}(container)
} }
wg.Wait()
return int64(total), backDatas, nil return int64(total), backDatas, nil
} }
func (u *ContainerService) List() ([]string, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
var datas []string
for _, container := range containers {
for _, name := range container.Names {
if len(name) != 0 {
datas = append(datas, strings.TrimLeft(name, "/"))
}
}
}
return datas, nil
}
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
list, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
var datas []dto.ContainerListStats
var wg sync.WaitGroup
wg.Add(len(list))
for i := 0; i < len(list); i++ {
go func(item types.Container) {
cpu, mem := loadCpuAndMem(client, item.ID)
datas = append(datas, dto.ContainerListStats{CPUPercent: cpu, MemoryPercent: mem, ContainerID: item.ID})
wg.Done()
}(list[i])
}
wg.Wait()
return datas, nil
}
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {
@@ -213,63 +281,52 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneRepo
return report, nil return report, nil
} }
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { func (u *ContainerService) LoadResouceLimit() (*dto.ResourceLimit, error) {
portMap, err := checkPortStats(req.ExposedPorts) cpuCounts, err := cpu.Counts(true)
if err != nil { if err != nil {
return err return nil, fmt.Errorf("load cpu limit failed, err: %v", err)
} }
memoryInfo, err := mem.VirtualMemory()
if err != nil {
return nil, fmt.Errorf("load memory limit failed, err: %v", err)
}
data := dto.ResourceLimit{
CPU: cpuCounts,
Memory: int(memoryInfo.Total),
}
return &data, nil
}
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {
return err return err
} }
ctx := context.Background()
newContainer, _ := client.ContainerInspect(ctx, req.Name)
if newContainer.ContainerJSONBase != nil {
return buserr.New(constant.ErrContainerName)
}
exposeds := make(nat.PortSet) var config container.Config
for port := range portMap { var hostConf container.HostConfig
exposeds[port] = struct{}{} var networkConf network.NetworkingConfig
} if err := loadConfigInfo(req, &config, &hostConf, &networkConf); err != nil {
config := &container.Config{ return err
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
ExposedPorts: exposeds,
}
hostConf := &container.HostConfig{
AutoRemove: req.AutoRemove,
PublishAllPorts: req.PublishAllPorts,
RestartPolicy: container.RestartPolicy{Name: req.RestartPolicy},
}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
if req.NanoCPUs != 0 {
hostConf.NanoCPUs = req.NanoCPUs * 1000000000
}
if req.Memory != 0 {
hostConf.Memory = req.Memory
}
if len(req.ExposedPorts) != 0 {
hostConf.PortBindings = portMap
}
if len(req.Volumes) != 0 {
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
config.Volumes[volume.ContainerDir] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
}
} }
global.LOG.Infof("new container info %s has been made, now start to create", req.Name) global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
ctx := context.Background() if !checkImageExist(client, req.Image) || req.ForcePull {
if !checkImageExist(client, req.Image) {
if err := pullImages(ctx, client, req.Image); err != nil { if err := pullImages(ctx, client, req.Image); err != nil {
return err if !req.ForcePull {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
} }
} }
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name) container, err := client.ContainerCreate(ctx, &config, &hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil { if err != nil {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) _ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return err return err
@@ -282,6 +339,159 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
return nil return nil
} }
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
ctx := context.Background()
oldContainer, err := client.ContainerInspect(ctx, req.Name)
if err != nil {
return nil, err
}
var data dto.ContainerOperate
data.ContainerID = oldContainer.ID
data.Name = strings.ReplaceAll(oldContainer.Name, "/", "")
data.Image = oldContainer.Config.Image
if oldContainer.NetworkSettings != nil {
for network := range oldContainer.NetworkSettings.Networks {
data.Network = network
break
}
}
data.Cmd = oldContainer.Config.Cmd
data.Env = oldContainer.Config.Env
data.CPUShares = oldContainer.HostConfig.CPUShares
for key, val := range oldContainer.Config.Labels {
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
}
for key, val := range oldContainer.HostConfig.PortBindings {
var itemPort dto.PortHelper
if !strings.Contains(string(key), "/") {
continue
}
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
itemPort.Protocol = strings.Split(string(key), "/")[1]
for _, binds := range val {
itemPort.HostIP = binds.HostIP
itemPort.HostPort = binds.HostPort
data.ExposedPorts = append(data.ExposedPorts, itemPort)
}
}
data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
if oldContainer.HostConfig.NanoCPUs != 0 {
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
}
if oldContainer.HostConfig.Memory != 0 {
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
}
for _, bind := range oldContainer.HostConfig.Binds {
parts := strings.Split(bind, ":")
if len(parts) != 3 {
continue
}
data.Volumes = append(data.Volumes, dto.VolumeHelper{SourceDir: parts[0], ContainerDir: parts[1], Mode: parts[2]})
}
return &data, nil
}
func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
ctx := context.Background()
newContainer, _ := client.ContainerInspect(ctx, req.Name)
if newContainer.ContainerJSONBase != nil && newContainer.ID != req.ContainerID {
return buserr.New(constant.ErrContainerName)
}
oldContainer, err := client.ContainerInspect(ctx, req.ContainerID)
if err != nil {
return err
}
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
}
}
if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil {
return err
}
config := oldContainer.Config
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil {
return err
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
return fmt.Errorf("recreate contianer failed, err: %v", err)
}
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("update successful but start failed, err: %v", err)
}
return nil
}
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
ctx := context.Background()
oldContainer, err := client.ContainerInspect(ctx, req.Name)
if err != nil {
return err
}
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
}
}
config := oldContainer.Config
config.Image = req.Image
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if oldContainer.NetworkSettings != nil {
for networkKey := range oldContainer.NetworkSettings.Networks {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
}
if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil {
return err
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
if err != nil {
return fmt.Errorf("recreate contianer failed, err: %v", err)
}
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("update successful but start failed, err: %v", err)
}
return nil
}
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error { func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
var err error var err error
ctx := context.Background() ctx := context.Background()
@@ -304,6 +514,10 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
case constant.ContainerOpUnpause: case constant.ContainerOpUnpause:
err = client.ContainerUnpause(ctx, req.Name) err = client.ContainerUnpause(ctx, req.Name)
case constant.ContainerOpRename: case constant.ContainerOpRename:
newContainer, _ := client.ContainerInspect(ctx, req.NewName)
if newContainer.ContainerJSONBase != nil {
return buserr.New(constant.ErrContainerName)
}
err = client.ContainerRename(ctx, req.Name, req.NewName) err = client.ContainerRename(ctx, req.Name, req.NewName)
case constant.ContainerOpRemove: case constant.ContainerOpRemove:
err = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) err = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
@@ -329,22 +543,54 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
return err return err
} }
_, _ = file.Seek(0, 0) _, _ = file.Seek(0, 0)
files, _ := filepath.Glob(fmt.Sprintf("%s.*", container.LogPath))
for _, file := range files {
_ = os.Remove(file)
}
return nil return nil
} }
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) { func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error {
cmd := exec.Command("docker", "logs", req.ContainerID) command := fmt.Sprintf("docker logs %s", container)
if req.Mode != "all" { if tail != "0" {
cmd = exec.Command("docker", "logs", req.ContainerID, "--since", req.Mode) command += " -n " + tail
} }
stdout, err := cmd.CombinedOutput() if since != "all" {
command += " --since " + since
}
if follow {
command += " -f"
}
command += " 2>&1"
cmd := exec.Command("bash", "-c", command)
stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return "", errors.New(string(stdout)) return err
} }
return string(stdout), nil if err := cmd.Start(); err != nil {
return err
}
buffer := make([]byte, 1024)
for {
n, err := stdout.Read(buffer)
if err != nil {
if err == io.EOF {
break
}
global.LOG.Errorf("read bytes from container log failed, err: %v", err)
continue
}
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
global.LOG.Errorf("send message with container log to ws failed, err: %v", err)
break
}
}
return nil
} }
func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, error) { func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -365,7 +611,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
if err := json.Unmarshal(body, &stats); err != nil { if err := json.Unmarshal(body, &stats); err != nil {
return nil, err return nil, err
} }
var data dto.ContainterStats var data dto.ContainerStats
data.CPUPercent = calculateCPUPercentUnix(stats) data.CPUPercent = calculateCPUPercentUnix(stats)
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats) data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024 data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
@@ -529,3 +775,39 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
} }
return portMap, nil return portMap, nil
} }
func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig) error {
portMap, err := checkPortStats(req.ExposedPorts)
if err != nil {
return err
}
exposeds := make(nat.PortSet)
for port := range portMap {
exposeds[port] = struct{}{}
}
config.Image = req.Image
config.Cmd = req.Cmd
config.Env = req.Env
config.Labels = stringsToMap(req.Labels)
config.ExposedPorts = exposeds
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
hostConf.AutoRemove = req.AutoRemove
hostConf.CPUShares = req.CPUShares
hostConf.PublishAllPorts = req.PublishAllPorts
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000)
hostConf.Memory = int64(req.Memory * 1024 * 1024)
hostConf.PortBindings = portMap
hostConf.Binds = []string{}
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
config.Volumes[volume.ContainerDir] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
}
return nil
}

View File

@@ -92,7 +92,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
} }
} }
for _, item := range composeCreatedByLocal { for _, item := range composeCreatedByLocal {
if err := composeRepo.DeleteRecord(commonRepo.WithByName(item.Name)); err != nil { if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
global.LOG.Error(err) global.LOG.Error(err)
} }
} }
@@ -127,6 +127,10 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
} }
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) { func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
if composeItem.ID != 0 {
return false, constant.ErrRecordExist
}
if err := u.loadPath(&req); err != nil { if err := u.loadPath(&req); err != nil {
return false, err return false, err
} }

View File

@@ -75,6 +75,26 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface
return int64(total), data, nil return int64(total), data, nil
} }
func (u *ContainerService) ListNetwork() ([]dto.Options, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{})
if err != nil {
return nil, err
}
var datas []dto.Options
for _, item := range list {
datas = append(datas, dto.Options{Option: item.Name})
}
sort.Slice(datas, func(i, j int) bool {
return datas[i].Option < datas[j].Option
})
return datas, nil
}
func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {

View File

@@ -80,13 +80,16 @@ func (u *ContainerService) ListVolume() ([]dto.Options, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var data []dto.Options var datas []dto.Options
for _, item := range list.Volumes { for _, item := range list.Volumes {
data = append(data, dto.Options{ datas = append(datas, dto.Options{
Option: item.Name, Option: item.Name,
}) })
} }
return data, nil sort.Slice(datas, func(i, j int) bool {
return datas[i].Option < datas[j].Option
})
return datas, nil
} }
func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error { func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()

View File

@@ -36,7 +36,7 @@ func NewICronjobService() ICronjobService {
} }
func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) { func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info)) total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
var dtoCronjobs []dto.CronjobInfo var dtoCronjobs []dto.CronjobInfo
for _, cronjob := range cronjobs { for _, cronjob := range cronjobs {
var item dto.CronjobInfo var item dto.CronjobInfo
@@ -100,9 +100,9 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if err != nil { if err != nil {
return err return err
} }
u.HandleRmExpired(backup.Type, localDir, &cronjob, client) u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
} else { } else {
u.HandleRmExpired(backup.Type, "", &cronjob, nil) u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
} }
} }
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID))) delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
@@ -133,7 +133,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
} }
if backup.Type == "LOCAL" || record.FromLocal { if backup.Type == "LOCAL" || record.FromLocal {
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) { if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
return "", constant.ErrRecordNotFound return "", err
} }
return record.File, nil return record.File, nil
} }
@@ -145,7 +145,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm) _ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath) isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil { if !isOK || err != nil {
return "", constant.ErrRecordNotFound return "", err
} }
return tempPath, nil return tempPath, nil
} }
@@ -238,6 +238,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["name"] = req.Name upMap["name"] = req.Name
upMap["spec"] = cronjob.Spec upMap["spec"] = cronjob.Spec
upMap["script"] = req.Script upMap["script"] = req.Script
upMap["container_name"] = req.ContainerName
upMap["spec_type"] = req.SpecType upMap["spec_type"] = req.SpecType
upMap["week"] = req.Week upMap["week"] = req.Week
upMap["day"] = req.Day upMap["day"] = req.Day

View File

@@ -32,17 +32,21 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
if len(cronjob.Script) == 0 { if len(cronjob.Script) == 0 {
return return
} }
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script) if len(cronjob.ContainerName) != 0 {
u.HandleRmExpired("LOCAL", "", cronjob, nil) message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("docker exec %s %s", cronjob.ContainerName, cronjob.Script))
} else {
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
}
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "curl": case "curl":
if len(cronjob.URL) == 0 { if len(cronjob.URL) == 0 {
return return
} }
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL)) message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL))
u.HandleRmExpired("LOCAL", "", cronjob, nil) u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "ntp": case "ntp":
err = u.handleNtpSync() err = u.handleNtpSync()
u.HandleRmExpired("LOCAL", "", cronjob, nil) u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "website": case "website":
record.File, err = u.handleBackup(cronjob, record.StartTime) record.File, err = u.handleBackup(cronjob, record.StartTime)
case "database": case "database":
@@ -142,39 +146,55 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
if err != nil { if err != nil {
return "", err return "", err
} }
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil { if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
return "", err return "", err
} }
} }
u.HandleRmExpired(backup.Type, localDir, cronjob, client) u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
if backup.Type == "LOCAL" || cronjob.KeepLocal { if backup.Type == "LOCAL" || cronjob.KeepLocal {
return fmt.Sprintf("%s/%s/%s/%s", localDir, cronjob.Type, cronjob.Name, fileName), nil return fmt.Sprintf("%s/%s", backupDir, fileName), nil
} else {
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
} }
return fmt.Sprintf("%s/%s/%s", cronjob.Type, cronjob.Name, fileName), nil
} }
} }
func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) { func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies) global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc")) records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) > int(cronjob.RetainCopies) { if len(records) <= int(cronjob.RetainCopies) {
for i := int(cronjob.RetainCopies); i < len(records); i++ { return
if len(records[i].File) != 0 { }
files := strings.Split(records[i].File, ",") for i := int(cronjob.RetainCopies); i < len(records); i++ {
for _, file := range files { if len(records[i].File) != 0 {
if backType != "LOCAL" { files := strings.Split(records[i].File, ",")
_, _ = backClient.Delete(strings.ReplaceAll(file, localDir+"/", "")) for _, file := range files {
_ = os.Remove(file) _ = os.Remove(file)
} else { _ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
_ = os.Remove(file) if backType == "LOCAL" {
} continue
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
} }
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID))) fileItem := file
_ = os.Remove(records[i].Records) if cronjob.KeepLocal {
if len(backupPath) != 0 {
itemPath := strings.TrimPrefix(backupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
fileItem = itemPath + strings.TrimPrefix(file, localDir+"/")
} else {
fileItem = strings.TrimPrefix(file, localDir+"/")
}
}
_, _ = backClient.Delete(fileItem)
}
} }
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
} }
} }
@@ -197,6 +217,9 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
if strings.Contains(sourceDir, "/") { if strings.Contains(sourceDir, "/") {
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "") itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")] aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
if len(aheadDir) == 0 {
aheadDir = "/"
}
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir) path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
} else { } else {
path = sourceDir path = sourceDir
@@ -204,7 +227,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
commands := fmt.Sprintf("tar zcvf %s %s %s", targetDir+"/"+name, excludeRules, path) commands := fmt.Sprintf("tar zcvf %s %s %s", targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands) global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute) stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil { if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout) return errors.New(stdout)
@@ -221,7 +244,7 @@ func handleUnTar(sourceFile, targetDir string) error {
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir) commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands) global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute) stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil { if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err) global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout) return errors.New(stdout)
@@ -270,12 +293,11 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
} }
record.DetailName = dbName record.DetailName = dbName
record.FileDir = backupDir record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "") itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" { if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type record.Source = backup.Type
record.FileDir = itemFileDir record.FileDir = itemFileDir
} }
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := backupRepo.CreateRecord(&record); err != nil { if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err) global.LOG.Errorf("save backup record failed, err: %v", err)
@@ -287,12 +309,22 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName)) _ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}() }()
} }
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil { if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err return paths, err
} }
} }
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
} }
u.HandleRmExpired(backup.Type, localDir, &cronjob, client) u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil return paths, nil
} }
@@ -338,7 +370,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405")) dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
filePaths = append(filePaths, path.Join(dstLogDir, dstName)) filePaths = append(filePaths, path.Join(dstLogDir, dstName))
if err = fileOp.Compress([]string{srcAccessLogPath, srcErrorLogPath}, dstLogDir, dstName, files.Gz); err != nil { if err = fileOp.Compress([]string{srcAccessLogPath, srcErrorLogPath}, dstLogDir, dstName, files.Gz); err != nil {
global.LOG.Errorf("There was an error in compressing the website[%s] access.log", website.PrimaryDomain) global.LOG.Errorf("There was an error in compressing the website[%s] access.log, err: %v", website.PrimaryDomain, err)
} else { } else {
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755) _ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755)
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755) _ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
@@ -358,7 +390,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
}() }()
} }
wg.Wait() wg.Wait()
u.HandleRmExpired("LOCAL", "", cronjob, nil) u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
return strings.Join(filePaths, ","), nil return strings.Join(filePaths, ","), nil
} }
@@ -399,13 +431,12 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
} }
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain) backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
record.FileDir = backupDir record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "") itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" { if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type record.Source = backup.Type
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "") record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
} }
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405")) record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil { if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil {
return paths, err return paths, err
} }
@@ -420,11 +451,21 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName)) _ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}() }()
} }
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil { if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err return paths, err
} }
} }
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
} }
u.HandleRmExpired(backup.Type, localDir, &cronjob, client) u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil return paths, nil
} }

View File

@@ -14,7 +14,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
@@ -49,7 +48,7 @@ func NewIMysqlService() IMysqlService {
} }
func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) { func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info)) total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
var dtoMysqls []dto.MysqlDBInfo var dtoMysqls []dto.MysqlDBInfo
for _, mysql := range mysqls { for _, mysql := range mysqls {
var item dto.MysqlDBInfo var item dto.MysqlDBInfo
@@ -100,7 +99,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
} }
return nil, err return nil, err
} }
if err := u.createUser(app, req); err != nil { if err := u.createUser(app.ContainerName, app.Password, app.Version, req); err != nil {
return nil, err return nil, err
} }
@@ -149,8 +148,14 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
return err return err
} }
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete { if strings.HasPrefix(app.Version, "5.6") {
return err if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
return err
}
} else {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
return err
}
} }
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete { if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
return err return err
@@ -195,7 +200,7 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
} }
passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value) passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value)
if !strings.HasPrefix(app.Version, "5.7") { if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value) passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value)
} }
if info.ID != 0 { if info.ID != 0 {
@@ -230,7 +235,7 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
for _, host := range hosts { for _, host := range hosts {
if host == "%" || host == "localhost" { if host == "%" || host == "localhost" {
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value) passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value)
if !strings.HasPrefix(app.Version, "5.7") { if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value) passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value)
} }
if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil { if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil {
@@ -281,8 +286,14 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
} }
for _, user := range userlist { for _, user := range userlist {
if len(user) != 0 { if len(user) != 0 {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil { if strings.HasPrefix(app.Version, "5.6") {
return err if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", mysql.Username, user)); err != nil {
return err
}
} else {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil {
return err
}
} }
} }
} }
@@ -291,7 +302,7 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
} }
} }
if err := u.createUser(app, dto.MysqlDBCreate{ if err := u.createUser(app.ContainerName, app.Password, app.Version, dto.MysqlDBCreate{
Username: mysql.Username, Username: mysql.Username,
Name: mysql.Name, Name: mysql.Name,
Permission: info.Value, Permission: info.Value,
@@ -347,7 +358,7 @@ func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error
group := "[mysqld]" group := "[mysqld]"
for _, info := range updates { for _, info := range updates {
if !strings.HasPrefix(app.Version, "5.7") { if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
if info.Param == "query_cache_size" { if info.Param == "query_cache_size" {
continue continue
} }
@@ -470,7 +481,7 @@ func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
return &info, nil return &info, nil
} }
func (u *MysqlService) createUser(app *repo.RootInfo, req dto.MysqlDBCreate) error { func (u *MysqlService) createUser(container, password, version string, req dto.MysqlDBCreate) error {
var userlist []string var userlist []string
if strings.Contains(req.Permission, ",") { if strings.Contains(req.Permission, ",") {
ips := strings.Split(req.Permission, ",") ips := strings.Split(req.Permission, ",")
@@ -484,32 +495,35 @@ func (u *MysqlService) createUser(app *repo.RootInfo, req dto.MysqlDBCreate) err
} }
for _, user := range userlist { for _, user := range userlist {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil { if err := excSQL(container, password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil {
handleCreateError(req.Name, userlist, app)
if strings.Contains(err.Error(), "ERROR 1396") { if strings.Contains(err.Error(), "ERROR 1396") {
handleCreateError(container, password, req.Name, userlist, false)
return buserr.New(constant.ErrUserIsExist) return buserr.New(constant.ErrUserIsExist)
} }
handleCreateError(container, password, req.Name, userlist, true)
return err return err
} }
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", req.Name, user) grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", req.Name, user)
if req.Name == "*" { if req.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user) grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
} }
if strings.HasPrefix(app.Version, "5.7") { if strings.HasPrefix(version, "5.7") || strings.HasPrefix(version, "5.6") {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password) grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
} }
if err := excSQL(app.ContainerName, app.Password, grantStr); err != nil { if err := excSQL(container, password, grantStr); err != nil {
handleCreateError(req.Name, userlist, app) handleCreateError(container, password, req.Name, userlist, true)
return err return err
} }
} }
return nil return nil
} }
func handleCreateError(dbName string, userlist []string, app *repo.RootInfo) { func handleCreateError(contaienr, password, dbName string, userlist []string, dropUser bool) {
_ = excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", dbName)) _ = excSQL(contaienr, password, fmt.Sprintf("drop database `%s`", dbName))
for _, user := range userlist { if dropUser {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists %s", user)); err != nil { for _, user := range userlist {
global.LOG.Errorf("drop user failed, err: %v", err) if err := excSQL(contaienr, password, fmt.Sprintf("drop user if exists %s", user)); err != nil {
global.LOG.Errorf("drop user failed, err: %v", err)
}
} }
} }
} }

View File

@@ -136,12 +136,14 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
switch req.Key { switch req.Key {
case "Registries": case "Registries":
req.Value = strings.TrimRight(req.Value, ",")
if len(req.Value) == 0 { if len(req.Value) == 0 {
delete(daemonMap, "insecure-registries") delete(daemonMap, "insecure-registries")
} else { } else {
daemonMap["insecure-registries"] = strings.Split(req.Value, ",") daemonMap["insecure-registries"] = strings.Split(req.Value, ",")
} }
case "Mirrors": case "Mirrors":
req.Value = strings.TrimRight(req.Value, ",")
if len(req.Value) == 0 { if len(req.Value) == 0 {
delete(daemonMap, "registry-mirrors") delete(daemonMap, "registry-mirrors")
} else { } else {

View File

@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/ssh" "github.com/1Panel-dev/1Panel/backend/utils/ssh"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -89,8 +90,25 @@ func (u *HostService) TestLocalConn(id uint) bool {
if err := copier.Copy(&connInfo, &host); err != nil { if err := copier.Copy(&connInfo, &host); err != nil {
return false return false
} }
connInfo.PrivateKey = []byte(host.PrivateKey) if len(host.Password) != 0 {
host.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return false
}
connInfo.Password = host.Password
}
if len(host.PrivateKey) != 0 {
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return false
}
connInfo.PrivateKey = []byte(host.PrivateKey)
}
if len(host.PassPhrase) != 0 { if len(host.PassPhrase) != 0 {
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return false
}
connInfo.PassPhrase = []byte(host.PassPhrase) connInfo.PassPhrase = []byte(host.PassPhrase)
} }
client, err := connInfo.NewClient() client, err := connInfo.NewClient()
@@ -107,6 +125,25 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
if err != nil { if err != nil {
return nil, constant.ErrRecordNotFound return nil, constant.ErrRecordNotFound
} }
if len(host.Password) != 0 {
host.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return nil, err
}
}
if len(host.PrivateKey) != 0 {
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return nil, err
}
}
if len(host.PassPhrase) != 0 {
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return nil, err
}
}
return &host, err return &host, err
} }
@@ -127,6 +164,25 @@ func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, inte
item.Password = "" item.Password = ""
item.PrivateKey = "" item.PrivateKey = ""
item.PassPhrase = "" item.PassPhrase = ""
} else {
if len(host.Password) != 0 {
item.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return 0, nil, err
}
}
if len(host.PrivateKey) != 0 {
item.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return 0, nil, err
}
}
if len(host.PassPhrase) != 0 {
item.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return 0, nil, err
}
}
} }
dtoHosts = append(dtoHosts, item) dtoHosts = append(dtoHosts, item)
} }

View File

@@ -3,12 +3,14 @@ package service
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -76,6 +78,9 @@ func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
} }
func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error { func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
}
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name)) imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
if imageRepo.ID != 0 { if imageRepo.ID != 0 {
return constant.ErrRecordExist return constant.ErrRecordExist
@@ -142,6 +147,9 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if req.ID == 1 { if req.ID == 1 {
return errors.New("The default value cannot be deleted !") return errors.New("The default value cannot be deleted !")
} }
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
}
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.ID)) repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
if err != nil { if err != nil {
return err return err
@@ -149,7 +157,7 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) { if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) {
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update") _ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
if repo.Auth { if repo.Auth {
_, _ = cmd.Execf("docker logout %s", repo.DownloadUrl) _, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
} }
stdout, err := cmd.Exec("systemctl restart docker") stdout, err := cmd.Exec("systemctl restart docker")
if err != nil { if err != nil {
@@ -176,9 +184,9 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
} }
func (u *ImageRepoService) CheckConn(host, user, password string) error { func (u *ImageRepoService) CheckConn(host, user, password string) error {
stdout, err := cmd.Execf("docker login -u %s -p %s %s", user, password, host) stdout, err := cmd.ExecWithCheck("docker", "login", "-u", user, "-p", password, host)
if err != nil { if err != nil {
return errors.New(string(stdout)) return fmt.Errorf("stdout: %s, stderr: %v", stdout, err)
} }
if strings.Contains(string(stdout), "Login Succeeded") { if strings.Contains(string(stdout), "Login Succeeded") {
return nil return nil

View File

@@ -76,17 +76,31 @@ func loadDiskIO() {
if io2.Name == io1.Name { if io2.Name == io1.Name {
var itemIO model.MonitorIO var itemIO model.MonitorIO
itemIO.Name = io1.Name itemIO.Name = io1.Name
itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60) if io2.ReadBytes != 0 && io1.ReadBytes != 0 {
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60) itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
}
if io2.WriteBytes != 0 && io1.WriteBytes != 0 {
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
}
itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60) if io2.ReadCount != 0 && io1.ReadCount != 0 {
writeCount := uint64(float64(io2.WriteCount-io1.WriteCount) / 60) itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60)
}
writeCount := uint64(0)
if io2.WriteCount != 0 && io1.WriteCount != 0 {
writeCount = uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
}
if writeCount > itemIO.Count { if writeCount > itemIO.Count {
itemIO.Count = writeCount itemIO.Count = writeCount
} }
itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60) if io2.ReadTime != 0 && io1.ReadTime != 0 {
writeTime := uint64(float64(io2.WriteTime-io1.WriteTime) / 60) itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
}
writeTime := uint64(0)
if io2.WriteTime != 0 && io1.WriteTime != 0 {
writeTime = uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
}
if writeTime > itemIO.Time { if writeTime > itemIO.Time {
itemIO.Time = writeTime itemIO.Time = writeTime
} }
@@ -113,8 +127,13 @@ func loadNetIO() {
if net2.Name == net1.Name { if net2.Name == net1.Name {
var itemNet model.MonitorNetwork var itemNet model.MonitorNetwork
itemNet.Name = net1.Name itemNet.Name = net1.Name
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60 if net2.BytesSent != 0 && net1.BytesSent != 0 {
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
}
if net2.BytesRecv != 0 && net1.BytesRecv != 0 {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
}
netList = append(netList, itemNet) netList = append(netList, itemNet)
break break
} }
@@ -123,14 +142,24 @@ func loadNetIO() {
netStatAll2, _ := net.IOCounters(false) netStatAll2, _ := net.IOCounters(false)
for _, net2 := range netStatAll2 { for _, net2 := range netStatAll2 {
for _, net1 := range netStatAll { for _, net1 := range netStatAll {
if net1.BytesSent == 0 || net1.BytesRecv == 0 {
continue
}
if net2.Name == net1.Name { if net2.Name == net1.Name {
var itemNet model.MonitorNetwork var itemNet model.MonitorNetwork
itemNet.Name = net1.Name itemNet.Name = net1.Name
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60 if net2.BytesSent != 0 && net1.BytesSent != 0 {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60 itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
}
if itemNet.Up > 10485760 {
itemNet.Up = 0
global.LOG.Errorf("net2: %v, net1: %v, BytesSent: %v \n", net2.BytesSent, net1.BytesSent, float64(net2.BytesSent-net1.BytesSent)/1024/60)
}
if net2.BytesRecv != 0 && net1.BytesRecv != 0 {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
}
if itemNet.Down > 10485760 {
itemNet.Down = 0
global.LOG.Errorf("net2: %v, net1: %v, BytesRecv: %v \n", net2.BytesRecv, net1.BytesRecv, float64(net2.BytesRecv-net1.BytesRecv)/1024/60)
}
netList = append(netList, itemNet) netList = append(netList, itemNet)
break break
} }

View File

@@ -0,0 +1,27 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/shirou/gopsutil/v3/process"
)
type ProcessService struct{}
type IProcessService interface {
StopProcess(req request.ProcessReq) error
}
func NewIProcessService() IProcessService {
return &ProcessService{}
}
func (p *ProcessService) StopProcess(req request.ProcessReq) error {
proc, err := process.NewProcess(req.PID)
if err != nil {
return err
}
if err := proc.Kill(); err != nil {
return err
}
return nil
}

View File

@@ -65,7 +65,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
} }
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
appVersionDir := path.Join(constant.AppResourceDir, app.Resource, app.Key, appDetail.Version) appVersionDir := path.Join(constant.AppResourceDir, app.Resource, app.Key, appDetail.Version)
if !fileOp.Stat(appVersionDir) { if !fileOp.Stat(appVersionDir) || appDetail.Update {
if err := downloadApp(app, appDetail, nil); err != nil { if err := downloadApp(app, appDetail, nil); err != nil {
return err return err
} }

View File

@@ -62,7 +62,7 @@ func handleParams(image, runtimeType, runtimeDir string, params map[string]inter
if extendsArray, ok := extends.([]interface{}); ok { if extendsArray, ok := extends.([]interface{}); ok {
strArray := make([]string, len(extendsArray)) strArray := make([]string, len(extendsArray))
for i, v := range extendsArray { for i, v := range extendsArray {
strArray[i] = fmt.Sprintf("%v", v) strArray[i] = strings.ToLower(fmt.Sprintf("%v", v))
} }
params["PHP_EXTENSIONS"] = strings.Join(strArray, ",") params["PHP_EXTENSIONS"] = strings.Join(strArray, ",")
} }

View File

@@ -56,6 +56,12 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
if len(req.Names) == 0 { if len(req.Names) == 0 {
return fmt.Errorf("incorrect snapshot request body: %v", req.Names) return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
} }
for _, snapName := range req.Names {
snap, _ := snapshotRepo.Get(commonRepo.WithByName(strings.ReplaceAll(snapName, ".tar.gz", "")))
if snap.ID != 0 {
return constant.ErrRecordExist
}
}
for _, snap := range req.Names { for _, snap := range req.Names {
nameItems := strings.Split(snap, "_") nameItems := strings.Split(snap, "_")
if !strings.HasPrefix(snap, "1panel_v") || !strings.HasSuffix(snap, ".tar.gz") || len(nameItems) != 3 { if !strings.HasPrefix(snap, "1panel_v") || !strings.HasSuffix(snap, ".tar.gz") || len(nameItems) != 3 {
@@ -202,7 +208,9 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type) global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type)
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading}) _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
localPath := fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow) localPath := fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow)
if ok, err := backupAccount.Upload(localPath, fmt.Sprintf("system_snapshot/1panel_%s_%s.tar.gz", versionItem.Value, timeNow)); err != nil || !ok { itemBackupPath := strings.TrimLeft(backup.BackupPath, "/")
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
if ok, err := backupAccount.Upload(localPath, fmt.Sprintf("%s/system_snapshot/1panel_%s_%s.tar.gz", itemBackupPath, versionItem.Value, timeNow)); err != nil || !ok {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err) global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err)
return return
@@ -253,7 +261,9 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
operation = "re-recover" operation = "re-recover"
} }
if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) { if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
ok, err := client.Download(fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name)) itemBackupPath := strings.TrimLeft(backup.BackupPath, "/")
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
if err != nil || !ok { if err != nil || !ok {
if req.ReDownload { if req.ReDownload {
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err)) updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
@@ -535,7 +545,7 @@ func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string
if operation == "snapshot" || operation == "recover" { if operation == "snapshot" || operation == "recover" {
_, err := os.Stat(daemonJsonPath) _, err := os.Stat(daemonJsonPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
global.LOG.Info("no daemon.josn in snapshot and system now, nothing happened") global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
} }
if err == nil { if err == nil {
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil { if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
@@ -658,7 +668,7 @@ func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation strin
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error { func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
switch operation { switch operation {
case "snapshot": case "snapshot":
exclusionRules := "./tmp;./cache;" exclusionRules := "./tmp;./cache;./db/1Panel.db-*;"
if strings.Contains(backupDir, source) { if strings.Contains(backupDir, source) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";") exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
} }

View File

@@ -229,29 +229,29 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
if len(req.Info) != 0 { if len(req.Info) != 0 {
command = fmt.Sprintf(" | grep '%s'", req.Info) command = fmt.Sprintf(" | grep '%s'", req.Info)
} }
for i := 0; i < len(fileList); i++ { for i := 0; i < len(fileList); i++ {
if strings.HasPrefix(path.Base(fileList[i]), "secure") { withAppend := len(data.Logs) < req.Page*req.PageSize
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command) if req.Status != constant.StatusSuccess {
dataItem := loadFailedSecureDatas(commandItem) if strings.HasPrefix(path.Base(fileList[i]), "secure") {
data.FailedCount += len(dataItem) commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
data.TotalCount += len(dataItem) dataItem, itemTotal := loadFailedSecureDatas(commandItem, withAppend)
if req.Status != constant.StatusSuccess { data.FailedCount += itemTotal
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
dataItem, itemTotal := loadFailedAuthDatas(commandItem, withAppend)
data.FailedCount += itemTotal
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...) data.Logs = append(data.Logs, dataItem...)
} }
} }
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
dataItem := loadFailedAuthDatas(commandItem)
data.FailedCount += len(dataItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusSuccess {
data.Logs = append(data.Logs, dataItem...)
}
}
commandItem := fmt.Sprintf("cat %s | grep Accepted %s", fileList[i], command)
dataItem := loadSuccessDatas(commandItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusFailed { if req.Status != constant.StatusFailed {
commandItem := fmt.Sprintf("cat %s | grep -a Accepted %s", fileList[i], command)
dataItem, itemTotal := loadSuccessDatas(commandItem, withAppend)
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...) data.Logs = append(data.Logs, dataItem...)
} }
} }
@@ -279,7 +279,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
global.LOG.Errorf("load qqwry datas failed: %s", err) global.LOG.Errorf("load qqwry datas failed: %s", err)
} }
var itemLogs []dto.SSHHistory var itemLogs []dto.SSHHistory
for i := len(data.Logs) - 1; i >= 0; i-- { for i := 0; i < len(data.Logs); i++ {
data.Logs[i].Area = qqWry.Find(data.Logs[i].Address).Area data.Logs[i].Area = qqWry.Find(data.Logs[i].Address).Area
data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc) data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc)
itemLogs = append(itemLogs, data.Logs[i]) itemLogs = append(itemLogs, data.Logs[i])
@@ -293,18 +293,17 @@ func sortFileList(fileNames []string) []string {
if len(fileNames) < 2 { if len(fileNames) < 2 {
return fileNames return fileNames
} }
var itemFile []string if strings.HasPrefix(path.Base(fileNames[0]), "secure") {
if strings.Contains(fileNames[0], "secure") { var itemFile []string
sort.Slice(fileNames, func(i, j int) bool { sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] < fileNames[j] return fileNames[i] > fileNames[j]
}) })
itemFile = append(itemFile, fileNames[1:]...) itemFile = append(itemFile, fileNames[len(fileNames)-1])
itemFile = append(itemFile, fileNames[0]) itemFile = append(itemFile, fileNames[:len(fileNames)-2]...)
return itemFile return itemFile
} }
sort.Slice(fileNames, func(i, j int) bool { sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] > fileNames[j] return fileNames[i] < fileNames[j]
}) })
return fileNames return fileNames
} }
@@ -339,82 +338,109 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string
return newFiles return newFiles
} }
func loadSuccessDatas(command string) []dto.SSHHistory { func loadSuccessDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var datas []dto.SSHHistory var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command) stdout2, err := cmd.Exec(command)
if err == nil { if err == nil {
lines := strings.Split(string(stdout2), "\n") lines := strings.Split(string(stdout2), "\n")
for _, line := range lines { if len(lines) == 0 {
parts := strings.Fields(line) return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 { if len(parts) < 14 {
continue continue
} }
historyItem := dto.SSHHistory{ totalNum++
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), if withAppend {
AuthMode: parts[6], historyItem := dto.SSHHistory{
User: parts[8], DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
Address: parts[10], AuthMode: parts[6],
Port: parts[12], User: parts[8],
Status: constant.StatusSuccess, Address: parts[10],
Port: parts[12],
Status: constant.StatusSuccess,
}
datas = append(datas, historyItem)
} }
datas = append(datas, historyItem)
} }
} }
return datas return datas, totalNum
} }
func loadFailedAuthDatas(command string) []dto.SSHHistory { func loadFailedAuthDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var datas []dto.SSHHistory var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command) stdout2, err := cmd.Exec(command)
if err == nil { if err == nil {
lines := strings.Split(string(stdout2), "\n") lines := strings.Split(string(stdout2), "\n")
for _, line := range lines { if len(lines) == 0 {
parts := strings.Fields(line) return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 { if len(parts) < 14 {
continue continue
} }
historyItem := dto.SSHHistory{ totalNum++
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), if withAppend {
AuthMode: parts[8], historyItem := dto.SSHHistory{
User: parts[10], DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
Address: parts[11], AuthMode: parts[8],
Port: parts[13], User: parts[10],
Status: constant.StatusFailed, Address: parts[11],
Port: parts[13],
Status: constant.StatusFailed,
}
if strings.Contains(lines[i], ": ") {
historyItem.Message = strings.Split(lines[i], ": ")[1]
}
datas = append(datas, historyItem)
} }
if strings.Contains(line, ": ") {
historyItem.Message = strings.Split(line, ": ")[1]
}
datas = append(datas, historyItem)
} }
} }
return datas return datas, totalNum
} }
func loadFailedSecureDatas(command string) []dto.SSHHistory { func loadFailedSecureDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var datas []dto.SSHHistory var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command) stdout2, err := cmd.Exec(command)
if err == nil { if err == nil {
lines := strings.Split(string(stdout2), "\n") lines := strings.Split(string(stdout2), "\n")
for _, line := range lines { if len(lines) == 0 {
parts := strings.Fields(line) return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 { if len(parts) < 14 {
continue continue
} }
historyItem := dto.SSHHistory{ totalNum++
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), if withAppend {
AuthMode: parts[6], historyItem := dto.SSHHistory{
User: parts[8], DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
Address: parts[10], AuthMode: parts[6],
Port: parts[12], User: parts[8],
Status: constant.StatusFailed, Address: parts[10],
Port: parts[12],
Status: constant.StatusFailed,
}
if strings.Contains(lines[i], ": ") {
historyItem.Message = strings.Split(lines[i], ": ")[1]
}
datas = append(datas, historyItem)
} }
if strings.Contains(line, ": ") {
historyItem.Message = strings.Split(line, ": ")[1]
}
datas = append(datas, historyItem)
} }
} }
return datas return datas, totalNum
} }
func handleGunzip(path string) error { func handleGunzip(path string) error {

View File

@@ -8,6 +8,14 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"os"
"path"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/common"
@@ -18,13 +26,6 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"gorm.io/gorm" "gorm.io/gorm"
"os"
"path"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/dto/response"
@@ -57,7 +58,7 @@ type IWebsiteService interface {
UpdateNginxConfigByScope(req request.NginxConfigUpdate) error UpdateNginxConfigByScope(req request.NginxConfigUpdate) error
GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error) GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error)
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
UpdateWafConfig(req request.WebsiteWafUpdate) error UpdateWafConfig(req request.WebsiteWafUpdate) error
@@ -96,7 +97,7 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
} }
return 0, nil, err return 0, nil, err
} }
opts = append(opts, commonRepo.WithOrderBy("created_at desc")) opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if req.Name != "" { if req.Name != "" {
opts = append(opts, websiteRepo.WithDomainLike(req.Name)) opts = append(opts, websiteRepo.WithDomainLike(req.Name))
} }
@@ -618,10 +619,10 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
return res, nil return res, nil
} }
func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) { func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil { if err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
var ( var (
res response.WebsiteHTTPS res response.WebsiteHTTPS
@@ -634,7 +635,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
website.Protocol = constant.ProtocolHTTP website.Protocol = constant.ProtocolHTTP
website.WebsiteSSLID = 0 website.WebsiteSSLID = 0
if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil { if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil) nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
nginxParams = append(nginxParams, nginxParams = append(nginxParams,
@@ -655,28 +656,64 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
Name: "ssl_ciphers", Name: "ssl_ciphers",
}, },
) )
if err := deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
if err := websiteRepo.Save(ctx, &website); err != nil { if err = websiteRepo.Save(ctx, &website); err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
return res, nil return nil, nil
} }
if req.Type == constant.SSLExisted { if req.Type == constant.SSLExisted {
websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID)) websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID))
if err != nil { if err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
website.WebsiteSSLID = websiteSSL.ID website.WebsiteSSLID = websiteSSL.ID
res.SSL = websiteSSL res.SSL = websiteSSL
} }
if req.Type == constant.SSLManual { if req.Type == constant.SSLManual {
certBlock, _ := pem.Decode([]byte(req.Certificate)) var (
certificate string
privateKey string
)
switch req.ImportType {
case "paste":
certificate = req.Certificate
privateKey = req.PrivateKey
case "local":
fileOp := files.NewFileOp()
if !fileOp.Stat(req.PrivateKeyPath) {
return nil, buserr.New("ErrSSLKeyNotFound")
}
if !fileOp.Stat(req.CertificatePath) {
return nil, buserr.New("ErrSSLCertificateNotFound")
}
if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil {
return nil, err
} else {
privateKey = string(content)
}
if content, err := fileOp.GetContent(req.CertificatePath); err != nil {
return nil, err
} else {
certificate = string(content)
}
}
privateKeyCertBlock, _ := pem.Decode([]byte(privateKey))
if privateKeyCertBlock == nil {
return nil, buserr.New("ErrSSLKeyFormat")
}
certBlock, _ := pem.Decode([]byte(certificate))
if certBlock == nil {
return nil, buserr.New("ErrSSLCertificateFormat")
}
cert, err := x509.ParseCertificate(certBlock.Bytes) cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil { if err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
websiteSSL.ExpireDate = cert.NotAfter websiteSSL.ExpireDate = cert.NotAfter
websiteSSL.StartDate = cert.NotBefore websiteSSL.StartDate = cert.NotBefore
@@ -690,28 +727,28 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
websiteSSL.PrimaryDomain = cert.DNSNames[0] websiteSSL.PrimaryDomain = cert.DNSNames[0]
websiteSSL.Domains = strings.Join(cert.DNSNames, ",") websiteSSL.Domains = strings.Join(cert.DNSNames, ",")
} }
websiteSSL.Provider = constant.Manual websiteSSL.Provider = constant.Manual
websiteSSL.PrivateKey = req.PrivateKey websiteSSL.PrivateKey = privateKey
websiteSSL.Pem = req.Certificate websiteSSL.Pem = certificate
res.SSL = websiteSSL res.SSL = websiteSSL
} }
website.Protocol = constant.ProtocolHTTPS website.Protocol = constant.ProtocolHTTPS
if err := applySSL(website, websiteSSL, req); err != nil { if err := applySSL(website, websiteSSL, req); err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
website.HttpConfig = req.HttpConfig website.HttpConfig = req.HttpConfig
if websiteSSL.ID == 0 { if websiteSSL.ID == 0 {
if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
website.WebsiteSSLID = websiteSSL.ID website.WebsiteSSLID = websiteSSL.ID
} }
if err := websiteRepo.Save(ctx, &website); err != nil { if err := websiteRepo.Save(ctx, &website); err != nil {
return response.WebsiteHTTPS{}, err return nil, err
} }
return res, nil return &res, nil
} }
func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) { func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) {
@@ -1058,50 +1095,41 @@ func (w WebsiteService) UpdatePHPConfig(req request.WebsitePHPConfigUpdate) (err
return err return err
} }
if req.Scope == "params" { content := string(contentBytes)
content := string(contentBytes) lines := strings.Split(content, "\n")
lines := strings.Split(content, "\n") for i, line := range lines {
for i, line := range lines { if strings.HasPrefix(line, ";") {
if strings.HasPrefix(line, ";") { continue
continue }
} switch req.Scope {
case "params":
for key, value := range req.Params { for key, value := range req.Params {
pattern := "^" + regexp.QuoteMeta(key) + "\\s*=\\s*.*$" pattern := "^" + regexp.QuoteMeta(key) + "\\s*=\\s*.*$"
if matched, _ := regexp.MatchString(pattern, line); matched { if matched, _ := regexp.MatchString(pattern, line); matched {
lines[i] = key + " = " + value lines[i] = key + " = " + value
} }
} }
} case "disable_functions":
updatedContent := strings.Join(lines, "\n") pattern := "^" + regexp.QuoteMeta("disable_functions") + "\\s*=\\s*.*$"
if err := fileOp.WriteFile(phpConfigPath, strings.NewReader(updatedContent), 0755); err != nil { if matched, _ := regexp.MatchString(pattern, line); matched {
return err lines[i] = "disable_functions" + " = " + strings.Join(req.DisableFunctions, ",")
break
}
case "upload_max_filesize":
pattern := "^" + regexp.QuoteMeta("post_max_size") + "\\s*=\\s*.*$"
if matched, _ := regexp.MatchString(pattern, line); matched {
lines[i] = "post_max_size" + " = " + req.UploadMaxSize
}
patternUpload := "^" + regexp.QuoteMeta("upload_max_filesize") + "\\s*=\\s*.*$"
if matched, _ := regexp.MatchString(patternUpload, line); matched {
lines[i] = "upload_max_filesize" + " = " + req.UploadMaxSize
}
} }
} }
updatedContent := strings.Join(lines, "\n")
cfg, err := ini.Load(phpConfigPath) if err := fileOp.WriteFile(phpConfigPath, strings.NewReader(updatedContent), 0755); err != nil {
if err != nil {
return err return err
} }
phpConfig, err := cfg.GetSection("PHP")
if err != nil {
return err
}
if req.Scope == "disable_functions" {
disable := phpConfig.Key("disable_functions")
disable.SetValue(strings.Join(req.DisableFunctions, ","))
if err = cfg.SaveTo(phpConfigPath); err != nil {
return err
}
}
if req.Scope == "upload_max_filesize" {
postMaxSize := phpConfig.Key("post_max_size")
postMaxSize.SetValue(req.UploadMaxSize)
uploadMaxFileSize := phpConfig.Key("upload_max_filesize")
uploadMaxFileSize.SetValue(req.UploadMaxSize)
if err = cfg.SaveTo(phpConfigPath); err != nil {
return err
}
}
appInstallReq := request.AppInstalledOperate{ appInstallReq := request.AppInstalledOperate{
InstallId: appInstall.ID, InstallId: appInstall.ID,
@@ -1504,14 +1532,6 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
if !fileOp.Stat(absoluteAuthPath) { if !fileOp.Stat(absoluteAuthPath) {
_ = fileOp.CreateFile(absoluteAuthPath) _ = fileOp.CreateFile(absoluteAuthPath)
} }
defer func() {
if err != nil {
switch req.Operate {
case "create":
}
}
}()
params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}}) params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}})
params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}}) params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}})
@@ -1519,7 +1539,9 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
if err != nil { if err != nil {
return return
} }
authArray = strings.Split(string(authContent), "\n") if len(authContent) > 0 {
authArray = strings.Split(string(authContent), "\n")
}
switch req.Operate { switch req.Operate {
case "disable": case "disable":
return deleteNginxConfig(constant.NginxScopeServer, params, &website) return deleteNginxConfig(constant.NginxScopeServer, params, &website)
@@ -1590,6 +1612,9 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
defer passFile.Close() defer passFile.Close()
writer := bufio.NewWriter(passFile) writer := bufio.NewWriter(passFile)
for _, line := range authArray { for _, line := range authArray {
if line == "" {
continue
}
_, err = writer.WriteString(line + "\n") _, err = writer.WriteString(line + "\n")
if err != nil { if err != nil {
return return
@@ -1599,6 +1624,15 @@ func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
if err != nil { if err != nil {
return return
} }
authContent, err = fileOp.GetContent(absoluteAuthPath)
if err != nil {
return
}
if len(authContent) == 0 {
if err = deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
return
}
}
return return
} }

View File

@@ -7,11 +7,13 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/ssl" "github.com/1Panel-dev/1Panel/backend/utils/ssl"
"path" "path"
"strconv"
"strings" "strings"
) )
@@ -21,7 +23,7 @@ type WebsiteSSLService struct {
type IWebsiteSSLService interface { type IWebsiteSSLService interface {
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
GetSSL(id uint) (*response.WebsiteSSLDTO, error) GetSSL(id uint) (*response.WebsiteSSLDTO, error)
Search() ([]response.WebsiteSSLDTO, error) Search(req request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error)
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error) Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
Renew(sslId uint) error Renew(sslId uint) error
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error) GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
@@ -35,17 +37,19 @@ func NewIWebsiteSSLService() IWebsiteSSLService {
} }
func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) { func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) {
var (
result []response.WebsiteSSLDTO
)
total, sslList, err := websiteSSLRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc")) total, sslList, err := websiteSSLRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
var sslDTOs []response.WebsiteSSLDTO for _, sslModel := range sslList {
for _, ssl := range sslList { result = append(result, response.WebsiteSSLDTO{
sslDTOs = append(sslDTOs, response.WebsiteSSLDTO{ WebsiteSSL: sslModel,
WebsiteSSL: ssl,
}) })
} }
return total, sslDTOs, err return total, result, err
} }
func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) { func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
@@ -58,18 +62,29 @@ func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
return &res, nil return &res, nil
} }
func (w WebsiteSSLService) Search() ([]response.WebsiteSSLDTO, error) { func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) {
sslList, err := websiteSSLRepo.List() var (
opts []repo.DBOption
result []response.WebsiteSSLDTO
)
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil {
return nil, err
}
opts = append(opts, websiteSSLRepo.WithByAcmeAccountId(uint(acmeAccountID)))
}
sslList, err := websiteSSLRepo.List(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var sslDTOs []response.WebsiteSSLDTO for _, sslModel := range sslList {
for _, ssl := range sslList { result = append(result, response.WebsiteSSLDTO{
sslDTOs = append(sslDTOs, response.WebsiteSSLDTO{ WebsiteSSL: sslModel,
WebsiteSSL: ssl,
}) })
} }
return sslDTOs, err return result, err
} }
func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error) { func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error) {

View File

@@ -378,9 +378,9 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We
} }
config := nginxFull.SiteConfig.Config config := nginxFull.SiteConfig.Config
server := config.FindServers()[0] server := config.FindServers()[0]
server.UpdateListen("443", website.DefaultServer, "ssl") server.UpdateListen("443", website.DefaultServer, "ssl", "http2")
if website.IPV6 { if website.IPV6 {
server.UpdateListen("[::]:443", website.DefaultServer, "ssl") server.UpdateListen("[::]:443", website.DefaultServer, "ssl", "http2")
} }
switch req.HttpConfig { switch req.HttpConfig {

View File

@@ -21,4 +21,6 @@ type System struct {
IsDemo bool `mapstructure:"is_demo"` IsDemo bool `mapstructure:"is_demo"`
AppRepo string `mapstructure:"app_repo"` AppRepo string `mapstructure:"app_repo"`
ChangeUserInfo bool `mapstructure:"change_user_info"` ChangeUserInfo bool `mapstructure:"change_user_info"`
OneDriveID string `mapstructure:"one_drive_id"`
OneDriveSc string `mapstructure:"one_drive_sc"`
} }

View File

@@ -11,6 +11,7 @@ const (
DirNotFound = "DirNotFound" DirNotFound = "DirNotFound"
Upgrading = "Upgrading" Upgrading = "Upgrading"
UpgradeErr = "UpgradeErr" UpgradeErr = "UpgradeErr"
PullErr = "PullErr"
ContainerPrefix = "1Panel-" ContainerPrefix = "1Panel-"

View File

@@ -7,7 +7,10 @@ const (
S3 = "S3" S3 = "S3"
OSS = "OSS" OSS = "OSS"
Sftp = "SFTP" Sftp = "SFTP"
OneDrive = "OneDrive"
MinIo = "MINIO" MinIo = "MINIO"
Cos = "COS" Cos = "COS"
Kodo = "KODO" Kodo = "KODO"
OneDriveRedirectURI = "http://localhost/login/authorized"
) )

View File

@@ -105,6 +105,7 @@ var (
ErrInUsed = "ErrInUsed" ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed" ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules" ErrPortRules = "ErrPortRules"
ErrRepoConn = "ErrRepoConn"
) )
// runtime // runtime

View File

@@ -10,4 +10,7 @@ const (
StatusEnable = "Enable" StatusEnable = "Enable"
StatusDisable = "Disable" StatusDisable = "Disable"
StatusNone = "None" StatusNone = "None"
OrderDesc = "descending"
OrderAsc = "ascending"
) )

View File

@@ -60,7 +60,7 @@ func GinI18nLocalize() gin.HandlerFunc {
return ginI18n.Localize( return ginI18n.Localize(
ginI18n.WithBundle(&ginI18n.BundleCfg{ ginI18n.WithBundle(&ginI18n.BundleCfg{
RootPath: "./lang", RootPath: "./lang",
AcceptLanguage: []language.Tag{language.Chinese, language.English}, AcceptLanguage: []language.Tag{language.Chinese, language.English, language.TraditionalChinese},
DefaultLanguage: language.Chinese, DefaultLanguage: language.Chinese,
FormatBundleFile: "yaml", FormatBundleFile: "yaml",
UnmarshalFunc: yaml.Unmarshal, UnmarshalFunc: yaml.Unmarshal,

View File

@@ -1,17 +1,13 @@
ErrInvalidParams: "Request parameter error: {{ .detail }}" ErrInvalidParams: "Request parameter error: {{ .detail }}"
ErrToken: "Token information is incorrect.: {{ .detail }}"
ErrTokenParse: "Token generation error: {{ .detail }}" ErrTokenParse: "Token generation error: {{ .detail }}"
ErrTokenTimeOut: "Login information is out of date: {{ .detail }}"
ErrInitialPassword: "Initial password error" ErrInitialPassword: "Initial password error"
ErrInternalServer: "Service internal error: {{ .detail }}" ErrInternalServer: "Service internal error: {{ .detail }}"
ErrRecordExist: "Record already exists" ErrRecordExist: "Record already exists"
ErrRecordNotFound: "Records not found" ErrRecordNotFound: "Records not found"
ErrStructTransform: "Type conversion failure: {{ .detail }}" ErrStructTransform: "Type conversion failure: {{ .detail }}"
ErrNotLogin: "User is not Login: {{ .detail }}" ErrNotLogin: "User is not Login: {{ .detail }}"
ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}"
ErrPasswordExpired: "The current password has expired: {{ .detail }}" ErrPasswordExpired: "The current password has expired: {{ .detail }}"
ErrNotSupportType: "The system does not support the current type: {{ .detail }}" ErrNotSupportType: "The system does not support the current type: {{ .detail }}"
ErrRepoNotValid: "Remote repository verification failed"
#common #common
ErrNameIsExist: "Name is already exist" ErrNameIsExist: "Name is already exist"
@@ -44,6 +40,9 @@ ErrHttpReqTimeOut: "Request timed out {{.err}}"
ErrHttpReqFailed: "Request failed {{.err}}" ErrHttpReqFailed: "Request failed {{.err}}"
ErrHttpReqNotFound: "The file does not exist" ErrHttpReqNotFound: "The file does not exist"
ErrNoSuchHost: "Network connection failed" ErrNoSuchHost: "Network connection failed"
ErrImagePullTimeOut: 'Image pull timeout'
ErrContainerNotFound: '{{ .name }} container does not exist'
ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details'
#file #file
ErrFileCanNotRead: "File can not read" ErrFileCanNotRead: "File can not read"
@@ -66,6 +65,10 @@ ErrSSLCannotDelete: "The certificate is being used by the website and cannot be
ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted" ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted"
ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration" ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration"
ErrEmailIsExist: 'Email is already exist' ErrEmailIsExist: 'Email is already exist'
ErrSSLKeyNotFound: 'The private key file does not exist'
ErrSSLCertificateNotFound: 'The certificate file does not exist'
ErrSSLKeyFormat: 'Private key file verification error'
ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
#mysql #mysql
ErrUserIsExist: "The current user already exists. Please enter a new user" ErrUserIsExist: "The current user already exists. Please enter a new user"
@@ -78,6 +81,8 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container #container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted" ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted" ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrRepoConn: "The repository information contains illegal characters"
ErrPortRules: "The number of ports does not match, please re-enter!"
#runtime #runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity" ErrDirNotFound: "The build folder does not exist! Please check file integrity"

View File

@@ -0,0 +1,97 @@
ErrInvalidParams: "請求參數錯誤: {{ .detail }}"
ErrTokenParse: "Token 產生錯誤: {{ .detail }}"
ErrInitialPassword: "原密碼錯誤"
ErrInternalServer: "伺服器內部錯誤: {{ .detail }}"
ErrRecordExist: "記錄已存在"
ErrRecordNotFound: "記錄未找到"
ErrStructTransform: "類型轉換失敗: {{ .detail }}"
ErrNotLogin: "用戶未登入: {{ .detail }}"
ErrPasswordExpired: "當前密碼已過期: {{ .detail }}"
ErrNotSupportType: "系統暫不支持當前類型: {{ .detail }}"
#common
ErrNameIsExist: "名稱已存在"
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
ErrCmdTimeout: "指令執行超時!"
#app
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
ErrAppLimit: "應用超出安裝數量限制"
ErrAppRequired: "請先安裝 {{ .detail }} 應用"
ErrNotInstall: "應用未安裝"
ErrPortInOtherApp: "{{ .port }} 端口已被 {{ .apps }}佔用!"
ErrDbUserNotValid: "儲存資料庫,用戶名密碼不匹配!"
ErrDockerComposeNotValid: "docker-compose 文件格式錯誤"
ErrUpdateBuWebsite: '應用更新成功,但是網站配置文件修改失敗,請檢查配置!'
Err1PanelNetworkFailed: '默認容器網絡創建失敗!{{ .detail }}'
ErrFileParse: '應用 docker-compose 文件解析失敗!'
ErrInstallDirNotFound: '安裝目錄不存在'
AppStoreIsUpToDate: '應用商店已經是最新版本'
LocalAppVersionNull: '{{.name}} 應用未同步到版本!無法添加到應用列表'
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失敗!{{.err}}'
ErrFileNotFound: '{{.name}} 文件不存在'
ErrFileParseApp: '{{.name}} 文件解析失敗 {{.err}}'
ErrAppDirNull: '版本資料夾不存在'
LocalAppErr: "應用 {{.name}} 同步失敗!{{.err}}"
ErrContainerName: "容器名稱已存在"
ErrAppSystemRestart: "1Panel 重啟導致任務中斷"
ErrCreateHttpClient: "創建HTTP請求失敗 {{.err}}"
ErrHttpReqTimeOut: "請求超時 {{.err}}"
ErrHttpReqFailed: "請求失敗 {{.err}}"
ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "網路連接失敗"
ErrImagePullTimeOut: "鏡像拉取超時"
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
#file
ErrFileCanNotRead: "此文件不支持預覽"
ErrFileToLarge: "文件超過10M,無法打開"
ErrPathNotFound: "目錄不存在"
ErrMovePathFailed: "目標路徑不能包含原路徑!"
ErrLinkPathNotFound: "目標路徑不存在!"
ErrFileIsExit: "文件已存在!"
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
ErrFileDownloadDir: "不支持下載文件夾"
#website
ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代號已存在"
ErrAppDelete: '其他網站使用此應用,無法刪除'
ErrGroupIsUsed: '分組正在使用中,無法刪除'
#ssl
ErrSSLCannotDelete: "證書正在被網站使用,無法刪除"
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
ErrSSLApply: "證書續簽成功openresty reload失敗請檢查配置"
ErrEmailIsExist: '郵箱已存在'
ErrSSLKeyNotFound: '私鑰文件不存在'
ErrSSLCertificateNotFound: '證書文件不存在'
ErrSSLKeyFormat: '私鑰文件校驗錯誤'
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
#mysql
ErrUserIsExist: "當前用戶已存在,請重新輸入"
ErrDatabaseIsExist: "當前資料庫已存在,請重新輸入"
ErrExecTimeOut: "SQL 執行超時,請檢查{{ .detail }}容器"
#redis
ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後重試"
#container
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
ErrObjectInUsed: "該對象正被使用,無法刪除"
ErrRepoConn: "倉庫資訊中存在不合法的字符"
ErrPortRules: "端口數目不匹配,請重新輸入!"
#runtime
ErrDirNotFound: "build 文件夾不存在!請檢查文件完整性!"
ErrFileNotExist: "{{ .detail }} 文件不存在!請檢查源文件完整性!"
ErrImageBuildErr: "鏡像 build 失敗"
ErrImageExist: "鏡像已存在!"
ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
#setting
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
ErrOSSConn: "無法成功請求最新版本,請檢查伺服器是否能夠連接到外部網絡環境。"

View File

@@ -1,17 +1,13 @@
ErrInvalidParams: "请求参数错误: {{ .detail }}" ErrInvalidParams: "请求参数错误: {{ .detail }}"
ErrToken: "Token 信息错误: {{ .detail }}"
ErrTokenParse: "Token 生成错误: {{ .detail }}" ErrTokenParse: "Token 生成错误: {{ .detail }}"
ErrTokenTimeOut: "登陆信息已过期: {{ .detail }}"
ErrInitialPassword: "原密码错误" ErrInitialPassword: "原密码错误"
ErrInternalServer: "服务内部错误: {{ .detail }}" ErrInternalServer: "服务内部错误: {{ .detail }}"
ErrRecordExist: "记录已存在" ErrRecordExist: "记录已存在"
ErrRecordNotFound: "记录未能找到" ErrRecordNotFound: "记录未能找到"
ErrStructTransform: "类型转换失败: {{ .detail }}" ErrStructTransform: "类型转换失败: {{ .detail }}"
ErrNotLogin: "用户未登录: {{ .detail }}" ErrNotLogin: "用户未登录: {{ .detail }}"
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
ErrPasswordExpired: "当前密码已过期: {{ .detail }}" ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}" ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
ErrRepoNotValid: "远程仓库校验失败!"
#common #common
ErrNameIsExist: "名称已存在" ErrNameIsExist: "名称已存在"
@@ -44,6 +40,9 @@ ErrHttpReqTimeOut: "请求超时 {{.err}}"
ErrHttpReqFailed: "请求失败 {{.err}}" ErrHttpReqFailed: "请求失败 {{.err}}"
ErrHttpReqNotFound: "文件不存在" ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "网络连接失败" ErrNoSuchHost: "网络连接失败"
ErrImagePullTimeOut: '镜像拉取超时'
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
#file #file
ErrFileCanNotRead: "此文件不支持预览" ErrFileCanNotRead: "此文件不支持预览"
@@ -66,6 +65,10 @@ ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
ErrAccountCannotDelete: "账号关联证书,无法删除" ErrAccountCannotDelete: "账号关联证书,无法删除"
ErrSSLApply: "证书续签成功openresty reload失败请检查配置" ErrSSLApply: "证书续签成功openresty reload失败请检查配置"
ErrEmailIsExist: '邮箱已存在' ErrEmailIsExist: '邮箱已存在'
ErrSSLKeyNotFound: '私钥文件不存在'
ErrSSLCertificateNotFound: '证书文件不存在'
ErrSSLKeyFormat: '私钥文件校验失败'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
#mysql #mysql
ErrUserIsExist: "当前用户已存在,请重新输入" ErrUserIsExist: "当前用户已存在,请重新输入"
@@ -78,6 +81,8 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container #container
ErrInUsed: "{{ .detail }} 正被使用,无法删除" ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除" ErrObjectInUsed: "该对象正被使用,无法删除"
ErrRepoConn: "仓库信息中存在不合法的字符"
ErrPortRules: "端口数目不匹配,请重新输入!"
#runtime #runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!" ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"

View File

@@ -2,12 +2,13 @@ package db
import ( import (
"fmt" "fmt"
"gorm.io/gorm/logger"
"log" "log"
"os" "os"
"time" "time"
"gorm.io/driver/sqlite" "gorm.io/gorm/logger"
"github.com/glebarez/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"

View File

@@ -1,6 +1,8 @@
package hook package hook
import ( import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/repo" "github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -15,17 +17,30 @@ func Init() {
global.LOG.Errorf("load service port from setting failed, err: %v", err) global.LOG.Errorf("load service port from setting failed, err: %v", err)
} }
global.CONF.System.Port = portSetting.Value global.CONF.System.Port = portSetting.Value
enptrySetting, err := settingRepo.Get(settingRepo.WithByKey("EncryptKey")) encryptSetting, err := settingRepo.Get(settingRepo.WithByKey("EncryptKey"))
if err != nil { if err != nil {
global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err) global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err)
} }
global.CONF.System.EncryptKey = enptrySetting.Value global.CONF.System.EncryptKey = encryptSetting.Value
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL")) sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil { if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err) global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
} }
global.CONF.System.SSL = sslSetting.Value global.CONF.System.SSL = sslSetting.Value
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
if err != nil {
global.LOG.Errorf("load onedrive info from setting failed, err: %v", err)
}
idItem, _ := base64.StdEncoding.DecodeString(OneDriveID.Value)
global.CONF.System.OneDriveID = string(idItem)
OneDriveSc, err := settingRepo.Get(settingRepo.WithByKey("OneDriveSc"))
if err != nil {
global.LOG.Errorf("load onedrive info from setting failed, err: %v", err)
}
scItem, _ := base64.StdEncoding.DecodeString(OneDriveSc.Value)
global.CONF.System.OneDriveSc = string(scItem)
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil { if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
_ = settingRepo.Create("SystemStatus", "Free") _ = settingRepo.Create("SystemStatus", "Free")
} }

View File

@@ -31,6 +31,10 @@ func Init() {
migrations.AddBindAndAllowIPs, migrations.AddBindAndAllowIPs,
migrations.UpdateCronjobWithSecond, migrations.UpdateCronjobWithSecond,
migrations.UpdateWebsite, migrations.UpdateWebsite,
migrations.AddBackupAccountDir,
migrations.AddMfaInterval,
migrations.UpdateAppDetail,
migrations.EncryptHostPassword,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@@ -390,3 +390,92 @@ var UpdateWebsite = &gormigrate.Migration{
return nil return nil
}, },
} }
var AddBackupAccountDir = &gormigrate.Migration{
ID: "20200620-add-backup-dir",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.BackupAccount{}, &model.Cronjob{}); err != nil {
return err
}
return nil
},
}
var AddMfaInterval = &gormigrate.Migration{
ID: "20230625-add-mfa-interval",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "MFAInterval", Value: "30"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SystemIP", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "OneDriveID", Value: "MDEwOTM1YTktMWFhOS00ODU0LWExZGMtNmU0NWZlNjI4YzZi"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "OneDriveSc", Value: "akpuOFF+YkNXOU1OLWRzS1ZSRDdOcG1LT2ZRM0RLNmdvS1RkVWNGRA=="}).Error; err != nil {
return err
}
return nil
},
}
var UpdateAppDetail = &gormigrate.Migration{
ID: "20230704-update-app-detail",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.AppDetail{}); err != nil {
return err
}
if err := tx.Model(&model.AppDetail{}).Where("1 = 1").Update("ignore_upgrade", "0").Error; err != nil {
return err
}
return nil
},
}
var EncryptHostPassword = &gormigrate.Migration{
ID: "20230703-encrypt-host-password",
Migrate: func(tx *gorm.DB) error {
var hosts []model.Host
if err := tx.Where("1 = 1").Find(&hosts).Error; err != nil {
return err
}
var encryptSetting model.Setting
if err := tx.Where("key = ?", "EncryptKey").Find(&encryptSetting).Error; err != nil {
return err
}
global.CONF.System.EncryptKey = encryptSetting.Value
for _, host := range hosts {
if len(host.Password) != 0 {
pass, err := encrypt.StringEncrypt(host.Password)
if err != nil {
return err
}
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("password", pass).Error; err != nil {
return err
}
}
if len(host.PrivateKey) != 0 {
key, err := encrypt.StringEncrypt(host.PrivateKey)
if err != nil {
return err
}
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("private_key", key).Error; err != nil {
return err
}
}
if len(host.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(host.PassPhrase)
if err != nil {
return err
}
if err := tx.Model(&model.Host{}).Where("id = ?", host.ID).Update("pass_phrase", pass).Error; err != nil {
return err
}
}
}
return nil
},
}

View File

@@ -86,6 +86,7 @@ func Routers() *gin.Engine {
systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup) systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup)
systemRouter.InitNginxRouter(PrivateGroup) systemRouter.InitNginxRouter(PrivateGroup)
systemRouter.InitRuntimeRouter(PrivateGroup) systemRouter.InitRuntimeRouter(PrivateGroup)
systemRouter.InitProcessRouter(PrivateGroup)
} }
return Router return Router

View File

@@ -15,7 +15,7 @@ func BindDomain() gin.HandlerFunc {
settingRepo := repo.NewISettingRepo() settingRepo := repo.NewISettingRepo()
status, err := settingRepo.Get(settingRepo.WithByKey("BindDomain")) status, err := settingRepo.Get(settingRepo.WithByKey("BindDomain"))
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrDomain, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
if len(status.Value) == 0 { if len(status.Value) == 0 {

View File

@@ -15,7 +15,7 @@ func WhiteAllow() gin.HandlerFunc {
settingRepo := repo.NewISettingRepo() settingRepo := repo.NewISettingRepo()
status, err := settingRepo.Get(settingRepo.WithByKey("AllowIPs")) status, err := settingRepo.Get(settingRepo.WithByKey("AllowIPs"))
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }

View File

@@ -118,8 +118,8 @@ func OperationLog() gin.HandlerFunc {
} }
} }
} }
record.DetailEN = operationDic.FormatEN record.DetailEN = strings.ReplaceAll(operationDic.FormatEN, "[]", "")
record.DetailZH = operationDic.FormatZH record.DetailZH = strings.ReplaceAll(operationDic.FormatZH, "[]", "")
writer := responseBodyWriter{ writer := responseBodyWriter{
ResponseWriter: c.Writer, ResponseWriter: c.Writer,

View File

@@ -16,7 +16,7 @@ func PasswordExpired() gin.HandlerFunc {
settingRepo := repo.NewISettingRepo() settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays")) setting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
return return
} }
expiredDays, _ := strconv.Atoi(setting.Value) expiredDays, _ := strconv.Atoi(setting.Value)
@@ -27,7 +27,7 @@ func PasswordExpired() gin.HandlerFunc {
extime, err := settingRepo.Get(settingRepo.WithByKey("ExpirationTime")) extime, err := settingRepo.Get(settingRepo.WithByKey("ExpirationTime"))
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
return return
} }
loc, _ := time.LoadLocation(common.LoadTimeZone()) loc, _ := time.LoadLocation(common.LoadTimeZone())

View File

@@ -20,6 +20,7 @@ type RouterGroup struct {
DatabaseRouter DatabaseRouter
NginxRouter NginxRouter
RuntimeRouter RuntimeRouter
ProcessRouter
} }
var RouterGroupApp = new(RouterGroup) var RouterGroupApp = new(RouterGroup)

View File

@@ -36,5 +36,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.GET("/installed/conf/:key", baseApi.GetDefaultConfig) appRouter.GET("/installed/conf/:key", baseApi.GetDefaultConfig)
appRouter.GET("/installed/params/:appInstallId", baseApi.GetParams) appRouter.GET("/installed/params/:appInstallId", baseApi.GetParams)
appRouter.POST("/installed/params/update", baseApi.UpdateInstalled) appRouter.POST("/installed/params/update", baseApi.UpdateInstalled)
appRouter.POST("/installed/ignore", baseApi.IgnoreUpgrade)
appRouter.GET("/ignored/detail", baseApi.GetIgnoredApp)
} }
} }

View File

@@ -19,8 +19,14 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
baRouter.GET("/stats/:id", baseApi.ContainerStats) baRouter.GET("/stats/:id", baseApi.ContainerStats)
baRouter.POST("", baseApi.ContainerCreate) baRouter.POST("", baseApi.ContainerCreate)
baRouter.POST("/update", baseApi.ContainerUpdate)
baRouter.POST("/upgrade", baseApi.ContainerUpgrade)
baRouter.POST("/info", baseApi.ContainerInfo)
baRouter.POST("/search", baseApi.SearchContainer) baRouter.POST("/search", baseApi.SearchContainer)
baRouter.POST("/search/log", baseApi.ContainerLogs) baRouter.POST("/list", baseApi.ListContainer)
baRouter.GET("/list/stats", baseApi.ContainerListStats)
baRouter.GET("/search/log", baseApi.ContainerLogs)
baRouter.GET("/limit", baseApi.LoadResouceLimit)
baRouter.POST("/clean/log", baseApi.CleanContainerLog) baRouter.POST("/clean/log", baseApi.CleanContainerLog)
baRouter.POST("/inspect", baseApi.Inspect) baRouter.POST("/inspect", baseApi.Inspect)
baRouter.POST("/operate", baseApi.ContainerOperation) baRouter.POST("/operate", baseApi.ContainerOperation)
@@ -55,10 +61,11 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
baRouter.POST("/image/tag", baseApi.ImageTag) baRouter.POST("/image/tag", baseApi.ImageTag)
baRouter.POST("/image/build", baseApi.ImageBuild) baRouter.POST("/image/build", baseApi.ImageBuild)
baRouter.GET("/volume", baseApi.ListVolume) baRouter.GET("/network", baseApi.ListNetwork)
baRouter.POST("/network/del", baseApi.DeleteNetwork) baRouter.POST("/network/del", baseApi.DeleteNetwork)
baRouter.POST("/network/search", baseApi.SearchNetwork) baRouter.POST("/network/search", baseApi.SearchNetwork)
baRouter.POST("/network", baseApi.CreateNetwork) baRouter.POST("/network", baseApi.CreateNetwork)
baRouter.GET("/volume", baseApi.ListVolume)
baRouter.POST("/volume/del", baseApi.DeleteVolume) baRouter.POST("/volume/del", baseApi.DeleteVolume)
baRouter.POST("/volume/search", baseApi.SearchVolume) baRouter.POST("/volume/search", baseApi.SearchVolume)
baRouter.POST("/volume", baseApi.CreateVolume) baRouter.POST("/volume", baseApi.CreateVolume)

View File

@@ -24,7 +24,6 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/tree", baseApi.HostTree) hostRouter.POST("/tree", baseApi.HostTree)
hostRouter.POST("/test/byinfo", baseApi.TestByInfo) hostRouter.POST("/test/byinfo", baseApi.TestByInfo)
hostRouter.POST("/test/byid/:id", baseApi.TestByID) hostRouter.POST("/test/byid/:id", baseApi.TestByID)
hostRouter.GET(":id", baseApi.GetHostInfo)
hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo) hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo)
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule) hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)

View File

@@ -0,0 +1,20 @@
package router
import (
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
"github.com/1Panel-dev/1Panel/backend/middleware"
"github.com/gin-gonic/gin"
)
type ProcessRouter struct {
}
func (f *ProcessRouter) InitProcessRouter(Router *gin.RouterGroup) {
processRouter := Router.Group("process")
processRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
processRouter.GET("/ws", baseApi.ProcessWs)
processRouter.POST("/stop", baseApi.StopProcess)
}
}

View File

@@ -29,7 +29,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.GET("/time/option", baseApi.LoadTimeZone) settingRouter.GET("/time/option", baseApi.LoadTimeZone)
settingRouter.POST("/time/sync", baseApi.SyncTime) settingRouter.POST("/time/sync", baseApi.SyncTime)
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor) settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
settingRouter.GET("/mfa", baseApi.GetMFA) settingRouter.GET("/mfa/:interval", baseApi.GetMFA)
settingRouter.POST("/mfa/bind", baseApi.MFABind) settingRouter.POST("/mfa/bind", baseApi.MFABind)
settingRouter.POST("/snapshot", baseApi.CreateSnapshot) settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
@@ -41,6 +41,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription) settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription)
settingRouter.GET("/backup/search", baseApi.ListBackup) settingRouter.GET("/backup/search", baseApi.ListBackup)
settingRouter.GET("/backup/onedrive", baseApi.LoadOneDriveInfo)
settingRouter.POST("/backup/backup", baseApi.Backup) settingRouter.POST("/backup/backup", baseApi.Backup)
settingRouter.POST("/backup/recover", baseApi.Recover) settingRouter.POST("/backup/recover", baseApi.Recover)
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload) settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)

View File

@@ -14,6 +14,7 @@ type cosClient struct {
region string region string
accessKey string accessKey string
secretKey string secretKey string
scType string
Vars map[string]interface{} Vars map[string]interface{}
client *cosSDK.Client client *cosSDK.Client
} }
@@ -21,6 +22,7 @@ type cosClient struct {
func NewCosClient(vars map[string]interface{}) (*cosClient, error) { func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
var accessKey string var accessKey string
var secretKey string var secretKey string
var scType string
var region string var region string
if _, ok := vars["region"]; ok { if _, ok := vars["region"]; ok {
region = vars["region"].(string) region = vars["region"].(string)
@@ -32,6 +34,11 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
} else { } else {
return nil, constant.ErrInvalidParams return nil, constant.ErrInvalidParams
} }
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["secretKey"]; ok { if _, ok := vars["secretKey"]; ok {
secretKey = vars["secretKey"].(string) secretKey = vars["secretKey"].(string)
} else { } else {
@@ -47,7 +54,7 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
}, },
}) })
return &cosClient{Vars: vars, client: client, accessKey: accessKey, secretKey: secretKey, region: region}, nil return &cosClient{Vars: vars, client: client, accessKey: accessKey, secretKey: secretKey, scType: scType, region: region}, nil
} }
func (cos cosClient) ListBuckets() ([]interface{}, error) { func (cos cosClient) ListBuckets() ([]interface{}, error) {
@@ -90,7 +97,12 @@ func (cos cosClient) Upload(src, target string) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
if _, err := client.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{}); err != nil { if _, err := client.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{
ACLHeaderOptions: nil,
ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{
XCosStorageClass: cos.scType,
},
}); err != nil {
return false, err return false, err
} }
return true, nil return true, nil

View File

@@ -0,0 +1,331 @@
package client
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
"golang.org/x/oauth2"
)
type oneDriveClient struct {
Vars map[string]interface{}
client odsdk.Client
}
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
token := ""
if _, ok := vars["accessToken"]; ok {
token = vars["accessToken"].(string)
} else {
return nil, constant.ErrInvalidParams
}
ctx := context.Background()
newToken, err := refreshToken(token)
if err != nil {
return nil, err
}
_ = global.DB.Model(&model.Group{}).Where("type = ?", "OneDrive").Updates(map[string]interface{}{"credential": newToken}).Error
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: newToken},
)
tc := oauth2.NewClient(ctx, ts)
client := odsdk.NewClient(tc)
return &oneDriveClient{client: *client}, nil
}
func (onedrive oneDriveClient) ListBuckets() ([]interface{}, error) {
return nil, nil
}
func (onedrive oneDriveClient) Exist(path string) (bool, error) {
path = "/" + strings.TrimPrefix(path, "/")
fileID, err := onedrive.loadIDByPath(path)
if err != nil {
return false, err
}
return len(fileID) != 0, nil
}
func (onedrive oneDriveClient) Delete(path string) (bool, error) {
path = "/" + strings.TrimPrefix(path, "/")
req, err := onedrive.client.NewRequest("DELETE", fmt.Sprintf("me/drive/root:%s", path), nil)
if err != nil {
return false, fmt.Errorf("new request for delete file failed, err: %v \n", err)
}
if err := onedrive.client.Do(context.Background(), req, false, nil); err != nil {
return false, fmt.Errorf("do request for delete file failed, err: %v \n", err)
}
return true, nil
}
func (onedrive oneDriveClient) Upload(src, target string) (bool, error) {
target = "/" + strings.TrimPrefix(target, "/")
if _, err := onedrive.loadIDByPath(path.Dir(target)); err != nil {
if !strings.Contains(err.Error(), "itemNotFound") {
return false, err
}
if err := onedrive.createFolder(path.Dir(target)); err != nil {
return false, fmt.Errorf("create dir before upload failed, err: %v", err)
}
}
ctx := context.Background()
file, err := os.Open(src)
if err != nil {
return false, err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return false, err
}
if fileInfo.IsDir() {
return false, errors.New("Only file is allowed to be uploaded here.")
}
fileName := fileInfo.Name()
fileSize := fileInfo.Size()
folderID, err := onedrive.loadIDByPath(path.Dir(target))
if err != nil {
return false, err
}
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), fileName)
sessionCreationRequestInside := NewUploadSessionCreationRequest{
ConflictBehavior: "rename",
}
sessionCreationRequest := struct {
Item NewUploadSessionCreationRequest `json:"item"`
DeferCommit bool `json:"deferCommit"`
}{sessionCreationRequestInside, false}
sessionCreationReq, err := onedrive.client.NewRequest("POST", apiURL, sessionCreationRequest)
if err != nil {
return false, err
}
var sessionCreationResp *NewUploadSessionCreationResponse
err = onedrive.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
if err != nil {
return false, fmt.Errorf("session creation failed %w", err)
}
fileSessionUploadUrl := sessionCreationResp.UploadURL
sizePerSplit := int64(3200 * 1024)
buffer := make([]byte, 3200*1024)
splitCount := fileSize / sizePerSplit
if fileSize%sizePerSplit != 0 {
splitCount += 1
}
bfReader := bufio.NewReader(file)
var fileUploadResp *UploadSessionUploadResponse
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
length, err := bfReader.Read(buffer)
if err != nil {
return false, err
}
if int64(length) < sizePerSplit {
bufferLast := buffer[:length]
buffer = bufferLast
}
sessionFileUploadReq, err := onedrive.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
if err != nil {
return false, err
}
if err := onedrive.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
return false, err
}
}
if fileUploadResp.Id == "" {
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
}
return true, nil
}
func (onedrive oneDriveClient) Download(src, target string) (bool, error) {
src = "/" + strings.TrimPrefix(src, "/")
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", src), nil)
if err != nil {
return false, fmt.Errorf("new request for file id failed, err: %v", err)
}
var driveItem *odsdk.DriveItem
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
return false, fmt.Errorf("do request for file id failed, err: %v", err)
}
resp, err := http.Get(driveItem.DownloadURL)
if err != nil {
return false, err
}
defer resp.Body.Close()
out, err := os.Create(target)
if err != nil {
return false, err
}
defer out.Close()
buffer := make([]byte, 2*1024*1024)
_, err = io.CopyBuffer(out, resp.Body, buffer)
if err != nil {
return false, err
}
return true, nil
}
func (onedrive *oneDriveClient) ListObjects(prefix string) ([]interface{}, error) {
prefix = "/" + strings.TrimPrefix(prefix, "/")
folderID, err := onedrive.loadIDByPath(prefix)
if err != nil {
return nil, err
}
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/items/%s/children", folderID), nil)
if err != nil {
return nil, fmt.Errorf("new request for list failed, err: %v", err)
}
var driveItems *odsdk.OneDriveDriveItemsResponse
if err := onedrive.client.Do(context.Background(), req, false, &driveItems); err != nil {
return nil, fmt.Errorf("do request for list failed, err: %v", err)
}
for _, item := range driveItems.DriveItems {
return nil, fmt.Errorf("id: %v, name: %s \n", item.Id, item.Name)
}
var itemList []interface{}
for _, item := range driveItems.DriveItems {
itemList = append(itemList, item.Name)
}
return itemList, nil
}
func (onedrive *oneDriveClient) loadIDByPath(path string) (string, error) {
pathItem := "root:" + path
if path == "/" {
pathItem = "root"
}
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/%s", pathItem), nil)
if err != nil {
return "", fmt.Errorf("new request for file id failed, err: %v", err)
}
var driveItem *odsdk.DriveItem
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
return "", fmt.Errorf("do request for file id failed, err: %v", err)
}
return driveItem.Id, nil
}
func refreshToken(oldToken string) (string, error) {
data := url.Values{}
data.Set("client_id", global.CONF.System.OneDriveID)
data.Set("client_secret", global.CONF.System.OneDriveSc)
data.Set("grant_type", "refresh_token")
data.Set("refresh_token", oldToken)
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
if err != nil {
return "", fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("request for access token failed, err: %v", err)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()
tokenMap := map[string]interface{}{}
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := tokenMap["access_token"].(string)
if !ok {
return "", errors.New("no such access token in response")
}
return accessToken, nil
}
func (onedrive *oneDriveClient) createFolder(parent string) error {
if _, err := onedrive.loadIDByPath(path.Dir(parent)); err != nil {
if !strings.Contains(err.Error(), "itemNotFound") {
return err
}
_ = onedrive.createFolder(path.Dir(parent))
}
item2, err := onedrive.loadIDByPath(path.Dir(parent))
if err != nil {
return err
}
if _, err := onedrive.client.DriveItems.CreateNewFolder(context.Background(), "", item2, path.Base(parent)); err != nil {
return err
}
return nil
}
type NewUploadSessionCreationRequest struct {
ConflictBehavior string `json:"@microsoft.graph.conflictBehavior,omitempty"`
}
type NewUploadSessionCreationResponse struct {
UploadURL string `json:"uploadUrl"`
ExpirationDateTime string `json:"expirationDateTime"`
}
type UploadSessionUploadResponse struct {
ExpirationDateTime string `json:"expirationDateTime"`
NextExpectedRanges []string `json:"nextExpectedRanges"`
DriveItem
}
type DriveItem struct {
Name string `json:"name"`
Id string `json:"id"`
DownloadURL string `json:"@microsoft.graph.downloadUrl"`
Description string `json:"description"`
Size int64 `json:"size"`
WebURL string `json:"webUrl"`
}
func (onedrive *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOffset, grandTotalSize int64, byteReader *bytes.Reader) (*http.Request, error) {
apiUrl, err := onedrive.client.BaseURL.Parse(absoluteUrl)
if err != nil {
return nil, err
}
absoluteUrl = apiUrl.String()
contentLength := byteReader.Size()
req, err := http.NewRequest("PUT", absoluteUrl, byteReader)
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
preliminaryLength := grandOffset
preliminaryRange := grandOffset + contentLength - 1
if preliminaryRange >= grandTotalSize {
preliminaryRange = grandTotalSize - 1
preliminaryLength = preliminaryRange - grandOffset + 1
}
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", preliminaryLength, preliminaryRange, grandTotalSize))
return req, err
}

View File

@@ -6,6 +6,7 @@ import (
) )
type ossClient struct { type ossClient struct {
scType string
Vars map[string]interface{} Vars map[string]interface{}
client osssdk.Client client osssdk.Client
} }
@@ -14,6 +15,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
var endpoint string var endpoint string
var accessKey string var accessKey string
var secretKey string var secretKey string
var scType string
if _, ok := vars["endpoint"]; ok { if _, ok := vars["endpoint"]; ok {
endpoint = vars["endpoint"].(string) endpoint = vars["endpoint"].(string)
} else { } else {
@@ -24,6 +26,11 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
} else { } else {
return nil, constant.ErrInvalidParams return nil, constant.ErrInvalidParams
} }
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["secretKey"]; ok { if _, ok := vars["secretKey"]; ok {
secretKey = vars["secretKey"].(string) secretKey = vars["secretKey"].(string)
} else { } else {
@@ -34,6 +41,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
return nil, err return nil, err
} }
return &ossClient{ return &ossClient{
scType: scType,
Vars: vars, Vars: vars,
client: *client, client: *client,
}, nil }, nil
@@ -77,7 +85,7 @@ func (oss ossClient) Upload(src, target string) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, "")) err = bucket.UploadFile(target, src, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, ""), osssdk.ObjectStorageClass(osssdk.StorageClassType(oss.scType)))
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@@ -13,14 +13,16 @@ import (
) )
type s3Client struct { type s3Client struct {
Vars map[string]interface{} scType string
Sess session.Session Vars map[string]interface{}
Sess session.Session
} }
func NewS3Client(vars map[string]interface{}) (*s3Client, error) { func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
var accessKey string var accessKey string
var secretKey string var secretKey string
var endpoint string var endpoint string
var scType string
var region string var region string
if _, ok := vars["accessKey"]; ok { if _, ok := vars["accessKey"]; ok {
accessKey = vars["accessKey"].(string) accessKey = vars["accessKey"].(string)
@@ -32,6 +34,11 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
} else { } else {
return nil, constant.ErrInvalidParams return nil, constant.ErrInvalidParams
} }
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["endpoint"]; ok { if _, ok := vars["endpoint"]; ok {
endpoint = vars["endpoint"].(string) endpoint = vars["endpoint"].(string)
} else { } else {
@@ -53,8 +60,9 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
return nil, err return nil, err
} }
return &s3Client{ return &s3Client{
Vars: vars, scType: scType,
Sess: *sess, Vars: vars,
Sess: *sess,
}, nil }, nil
} }
@@ -126,9 +134,10 @@ func (s3C s3Client) Upload(src, target string) (bool, error) {
uploader := s3manager.NewUploader(&s3C.Sess) uploader := s3manager.NewUploader(&s3C.Sess)
_, err = uploader.Upload(&s3manager.UploadInput{ _, err = uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(bucket), Bucket: aws.String(bucket),
Key: aws.String(target), Key: aws.String(target),
Body: file, Body: file,
StorageClass: &s3C.scType,
}) })
if err != nil { if err != nil {
return false, err return false, err

View File

@@ -14,24 +14,23 @@ type CloudStorageClient interface {
Download(src, target string) (bool, error) Download(src, target string) (bool, error)
} }
func NewCloudStorageClient(vars map[string]interface{}) (CloudStorageClient, error) { func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
if vars["type"] == constant.S3 { switch backupType {
case constant.S3:
return client.NewS3Client(vars) return client.NewS3Client(vars)
} case constant.OSS:
if vars["type"] == constant.OSS {
return client.NewOssClient(vars) return client.NewOssClient(vars)
} case constant.Sftp:
if vars["type"] == constant.Sftp {
return client.NewSftpClient(vars) return client.NewSftpClient(vars)
} case constant.MinIo:
if vars["type"] == constant.MinIo {
return client.NewMinIoClient(vars) return client.NewMinIoClient(vars)
} case constant.Cos:
if vars["type"] == constant.Cos {
return client.NewCosClient(vars) return client.NewCosClient(vars)
} case constant.Kodo:
if vars["type"] == constant.Kodo {
return client.NewKodoClient(vars) return client.NewKodoClient(vars)
case constant.OneDrive:
return client.NewOneDriveClient(vars)
default:
return nil, constant.ErrNotSupportType
} }
return nil, constant.ErrNotSupportType
} }

View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"os/exec" "os/exec"
"strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
@@ -117,6 +118,43 @@ func Execf(cmdStr string, a ...interface{}) (string, error) {
return stdout.String(), nil return stdout.String(), nil
} }
func ExecWithCheck(name string, a ...string) (string, error) {
cmd := exec.Command(name, a...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
errMsg := ""
if len(stderr.String()) != 0 {
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
}
if len(stdout.String()) != 0 {
if len(errMsg) != 0 {
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
} else {
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
}
}
return errMsg, err
}
return stdout.String(), nil
}
func CheckIllegal(args ...string) bool {
if args == nil {
return false
}
for _, arg := range args {
if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") {
return true
}
}
return false
}
func HasNoPasswordSudo() bool { func HasNoPasswordSudo() bool {
cmd2 := exec.Command("sudo", "-n", "ls") cmd2 := exec.Command("sudo", "-n", "ls")
err2 := cmd2.Run() err2 := cmd2.Run()

View File

@@ -4,32 +4,37 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
) )
func Pull(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s pull", filePath)
return stdout, err
}
func Up(filePath string) (string, error) { func Up(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s up -d", filePath) stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
return stdout, err return stdout, err
} }
func Down(filePath string) (string, error) { func Down(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s down --remove-orphans", filePath) stdout, err := cmd.Execf("docker-compose -f %s down --remove-orphans", filePath)
return stdout, err return stdout, err
} }
func Start(filePath string) (string, error) { func Start(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s start", filePath) stdout, err := cmd.Execf("docker-compose -f %s start", filePath)
return stdout, err return stdout, err
} }
func Stop(filePath string) (string, error) { func Stop(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s stop", filePath) stdout, err := cmd.Execf("docker-compose -f %s stop", filePath)
return stdout, err return stdout, err
} }
func Restart(filePath string) (string, error) { func Restart(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s restart", filePath) stdout, err := cmd.Execf("docker-compose -f %s restart", filePath)
return stdout, err return stdout, err
} }
func Operate(filePath, operation string) (string, error) { func Operate(filePath, operation string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s %s", filePath, operation) stdout, err := cmd.Execf("docker-compose -f %s %s", filePath, operation)
return stdout, err return stdout, err
} }

View File

@@ -89,6 +89,10 @@ func (s *ComposeService) ComposeBuild() error {
return s.Build(context.Background(), s.project, api.BuildOptions{}) return s.Build(context.Background(), s.project, api.BuildOptions{})
} }
func (s *ComposeService) ComposePull() error {
return s.Pull(context.Background(), s.project, api.PullOptions{})
}
func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) { func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) {
var configFiles []types.ConfigFile var configFiles []types.ConfigFile
configFiles = append(configFiles, types.ConfigFile{ configFiles = append(configFiles, types.ConfigFile{

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