132 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
274 changed files with 11754 additions and 3230 deletions

View File

@@ -12,9 +12,7 @@ jobs:
build-linux-binary:
runs-on: ubuntu-latest
steps:
- name: Install cross-compilers
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
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
@@ -23,7 +21,7 @@ jobs:
- name: Build Web
id: build_frontend
run: |
cd frontend && npm install && npm run build:dev
cd frontend && npm install && npm run build:pro
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go

View File

@@ -10,9 +10,7 @@ jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Install cross-compilers
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
- name: Checkout Code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v3
@@ -20,7 +18,7 @@ jobs:
node-version: '18.14'
- name: Build Web
run: |
cd frontend && npm install && npm run build:dev
cd frontend && npm install && npm run build:pro
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go
@@ -30,17 +28,33 @@ jobs:
- name: Build Release
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: release --skip-publish --clean
- name: Upload Assets
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
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: |
dist/*.tar.gz
dist/checksums.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup OSSUTIL
uses: yizhoumo/setup-ossutil@v1
with:
@@ -49,4 +63,4 @@ jobs:
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
ossutil-version: '1.7.14'
- 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
1panel.service
install.sh
quick_start.sh
cmd/server/web/.DS_Store
cmd/server/.DS_Store

View File

@@ -16,18 +16,8 @@ builds:
- -trimpath
ldflags:
- -w -s
- --extldflags "-static -fpic"
tags:
- osusergo
env:
- CGO_ENABLED=1
- >-
{{- 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 }}
- CGO_ENABLED=0
goos:
- linux
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:
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:
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:
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)
}
// @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
// @Summary Install app
// @Description 安装应用

View File

@@ -118,7 +118,7 @@ func (b *BaseApi) LoadConnInfo(c *gin.Context) {
// @Description 删除前检查
// @Accept json
// @Param appInstallId path integer true "App install id"
// @Success 200 {anrry} dto.AppResource
// @Success 200 {array} dto.AppResource
// @Security ApiKeyAuth
// @Router /apps/installed/delete/check/:appInstallId [get]
func (b *BaseApi) DeleteCheck(c *gin.Context) {
@@ -178,7 +178,7 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) {
// @Description 通过 key 获取应用 service
// @Accept json
// @Param key path string true "request"
// @Success 200 {anrry} response.AppService
// @Success 200 {array} response.AppService
// @Security ApiKeyAuth
// @Router /apps/services/:key [get]
func (b *BaseApi) GetServices(c *gin.Context) {
@@ -196,7 +196,7 @@ func (b *BaseApi) GetServices(c *gin.Context) {
// @Description 通过 install id 获取应用更新版本
// @Accept json
// @Param appInstallId path integer true "request"
// @Success 200 {anrry} dto.AppVersion
// @Success 200 {array} dto.AppVersion
// @Security ApiKeyAuth
// @Router /apps/installed/:appInstallId/versions [get]
func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
@@ -305,3 +305,25 @@ func (b *BaseApi) UpdateInstalled(c *gin.Context) {
}
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 列表
// @Accept json
// @Param request body dto.ForBuckets true "request"
// @Success 200 {anrry} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /settings/backup/search [post]
func (b *BaseApi) ListBuckets(c *gin.Context) {
@@ -98,6 +98,22 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
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
// @Summary Delete backup account
// @Description 删除备份账号
@@ -253,7 +269,7 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
// @Tags Backup Account
// @Summary List backup accounts
// @Description 获取备份账号列表
// @Success 200 {anrry} dto.BackupInfo
// @Success 200 {array} dto.BackupInfo
// @Security ApiKeyAuth
// @Router /settings/backup/search [get]
func (b *BaseApi) ListBackup(c *gin.Context) {
@@ -271,7 +287,7 @@ func (b *BaseApi) ListBackup(c *gin.Context) {
// @Description 获取备份账号内文件列表
// @Accept json
// @Param request body dto.BackupSearchFile true "request"
// @Success 200 {anrry} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /settings/backup/search/files [post]
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {

View File

@@ -66,7 +66,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
// @Summary List compose templates
// @Description 获取容器编排模版列表
// @Produce json
// @Success 200 {anrry} dto.ComposeTemplateInfo
// @Success 200 {array} dto.ComposeTemplateInfo
// @Security ApiKeyAuth
// @Router /containers/template [get]
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
// @Summary Page composes
// @Description 获取编排列表分页
@@ -153,17 +170,97 @@ func (b *BaseApi) OperatorCompose(c *gin.Context) {
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
// @Summary Create container
// @Description 创建容器
// @Accept json
// @Param request body dto.ContainerCreate true "request"
// @Param request body dto.ContainerOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers [post]
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"}
func (b *BaseApi) ContainerCreate(c *gin.Context) {
var req dto.ContainerCreate
var req dto.ContainerOperate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@@ -179,6 +276,32 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
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
// @Summary Clean container
// @Description 容器清理
@@ -262,7 +385,7 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) {
// @Summary Container stats
// @Description 容器监控信息
// @Param id path integer true "容器id"
// @Success 200 {object} dto.ContainterStats
// @Success 200 {object} dto.ContainerStats
// @Security ApiKeyAuth
// @Router /containers/stats/:id [get]
func (b *BaseApi) ContainerStats(c *gin.Context) {
@@ -310,27 +433,29 @@ func (b *BaseApi) Inspect(c *gin.Context) {
// @Tags Container
// @Summary Container logs
// @Description 容器日志
// @Accept json
// @Param request body dto.ContainerLog true "request"
// @Success 200 {string} logs
// @Param container query string false "容器名称"
// @Param since query string false "时间筛选"
// @Param follow query string false "是否追踪"
// @Param tail query string false "显示行号"
// @Security ApiKeyAuth
// @Router /containers/search/log [post]
func (b *BaseApi) ContainerLogs(c *gin.Context) {
var req dto.ContainerLog
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)
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, 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
}
helper.SuccessWithData(c, logs)
}
// @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
// @Summary Delete network
// @Description 删除容器网络
@@ -453,11 +595,10 @@ func (b *BaseApi) SearchVolume(c *gin.Context) {
// @Summary List volumes
// @Description 获取容器存储卷列表
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Produce json
// @Success 200 {object} dto.PageResult
// @Success 200 {array} dto.Options
// @Security ApiKeyAuth
// @Router /containers/volume/search [get]
// @Router /containers/volume [get]
func (b *BaseApi) ListVolume(c *gin.Context) {
list, err := containerService.ListVolume()
if err != nil {

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
// @Description 分页获取上传文件
// @Accept json
// @Param request body request.SearchUploadWithPage true "request"
// @Success 200 {anrry} response.FileInfo
// @Success 200 {array} response.FileInfo
// @Security ApiKeyAuth
// @Router /files/upload/search [post]
func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
@@ -81,7 +81,7 @@ func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
// @Description 加载文件树
// @Accept json
// @Param request body request.FileOption true "request"
// @Success 200 {anrry} response.FileTree
// @Success 200 {array} response.FileTree
// @Security ApiKeyAuth
// @Router /files/tree [post]
func (b *BaseApi) GetFileTree(c *gin.Context) {
@@ -532,7 +532,7 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
}
defer file.Close()
file.Seek(startPos, 0)
_, _ = file.Seek(startPos, 0)
reader := io.LimitReader(file, endPos-startPos+1)
_, err = io.CopyBuffer(c.Writer, reader, buffer)
if err != nil {
@@ -683,7 +683,12 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
}
filename := c.PostForm("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)
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) {
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
wsClient := websocket2.NewWsClient("wsClient", ws)
wsClient := websocket2.NewWsClient("fileClient", ws)
go wsClient.Read()
go wsClient.Write()
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/gin-gonic/gin"
)
@@ -36,7 +36,14 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
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 {
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)
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)
@@ -102,7 +124,7 @@ func (b *BaseApi) TestByID(c *gin.Context) {
// @Description 加载主机树
// @Accept json
// @Param request body dto.SearchForTree true "request"
// @Success 200 {anrry} dto.HostTree
// @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth
// @Router /hosts/tree [post]
func (b *BaseApi) HostTree(c *gin.Context) {
@@ -126,7 +148,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Description 获取主机列表分页
// @Accept json
// @Param request body dto.SearchHostWithPage true "request"
// @Success 200 {anrry} dto.HostTree
// @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth
// @Router /hosts/search [post]
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
// @Summary Delete host
// @Description 删除主机
@@ -227,7 +222,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
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 {
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)
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{})
@@ -246,10 +260,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
upMap["user"] = req.User
upMap["auth_mode"] = req.AuthMode
upMap["remember_password"] = req.RememberPassword
if len(req.Password) != 0 {
if req.AuthMode == "password" {
upMap["password"] = req.Password
}
if len(req.PrivateKey) != 0 {
upMap["private_key"] = ""
upMap["pass_phrase"] = ""
} else {
upMap["password"] = ""
upMap["private_key"] = req.PrivateKey
upMap["pass_phrase"] = req.PassPhrase
}

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) {
// @Description 获取部分 OpenResty 配置信息
// @Accept json
// @Param request body request.NginxScopeReq true "request"
// @Success 200 {anrry} response.NginxParam
// @Success 200 {array} response.NginxParam
// @Security ApiKeyAuth
// @Router /openResty/scope [post]
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 (
"errors"
"fmt"
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -260,11 +262,23 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) {
// @Tags System Setting
// @Summary Load mfa info
// @Description 获取 mfa 信息
// @Param interval path string true "request"
// @Success 200 {object} mfa.Otp
// @Security ApiKeyAuth
// @Router /settings/mfa [get]
// @Router /settings/mfa/:interval [get]
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 {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -288,12 +302,17 @@ func (b *BaseApi) MFABind(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
success := mfa.ValidCode(req.Code, req.Secret)
success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
if !success {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid"))
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 {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

View File

@@ -9,8 +9,6 @@ import (
"strings"
"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/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -22,24 +20,27 @@ import (
)
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 {
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
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
host, err := hostService.GetHostInfo(uint(id))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
return
}
var connInfo ssh.ConnInfo
@@ -49,13 +50,6 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
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()
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
@@ -85,28 +79,25 @@ func (b *BaseApi) WsSsh(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)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
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()
commands := "redis-cli"
if len(redisConf.Requirepass) != 0 {
@@ -138,24 +129,6 @@ func (b *BaseApi) RedisWsSsh(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)
if err != nil {
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()
cmds := fmt.Sprintf("docker exec %s %s", containerID, command)
if len(user) != 0 {
cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command)
containerID := c.Query("containerid")
command := c.Query("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)) {
return
}

View File

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

View File

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

View File

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

View File

@@ -8,15 +8,17 @@ type BackupOperate struct {
Bucket string `json:"bucket"`
AccessKey string `json:"accessKey"`
Credential string `json:"credential"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars" validate:"required"`
}
type BackupInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Type string `json:"type"`
Bucket string `json:"bucket"`
Vars string `json:"vars"`
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Type string `json:"type"`
Bucket string `json:"bucket"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars"`
}
type BackupSearch struct {
@@ -36,7 +38,7 @@ type CommonBackup struct {
DetailName string `json:"detailName"`
}
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"`
Name string `json:"name"`
DetailName string `json:"detailName"`
@@ -60,7 +62,7 @@ type BackupRecords 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"`
FileName string `json:"fileName" validate:"required"`
}

View File

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

View File

@@ -5,6 +5,8 @@ import "time"
type PageContainer struct {
PageInfo
Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
Filters string `json:"filters"`
}
@@ -22,22 +24,29 @@ type ContainerInfo struct {
State string `json:"state"`
RunTime string `json:"runTime"`
CPUPercent float64 `json:"cpuPercent"`
MemoryPercent float64 `json:"memoryPercent"`
Ports []string `json:"ports"`
Ports []string `json:"ports"`
IsFromApp bool `json:"isFromApp"`
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"`
Image string `json:"image"`
Network string `json:"network"`
PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"`
Cmd []string `json:"cmd"`
NanoCPUs int64 `json:"nanoCPUs"`
Memory int64 `json:"memory"`
CPUShares int64 `json:"cpuShares"`
NanoCPUs float64 `json:"nanoCPUs"`
Memory float64 `json:"memory"`
AutoRemove bool `json:"autoRemove"`
Volumes []VolumeHelper `json:"volumes"`
Labels []string `json:"labels"`
@@ -45,7 +54,19 @@ type ContainerCreate struct {
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"`
Memory float64 `json:"memory"`
Cache float64 `json:"cache"`
@@ -69,11 +90,6 @@ type PortHelper struct {
Protocol string `json:"protocol"`
}
type ContainerLog struct {
ContainerID string `json:"containerID" validate:"required"`
Mode string `json:"mode" validate:"required"`
}
type ContainerOperation struct {
Name string `json:"name" validate:"required"`
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 {
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
WithTagAll bool `josn:"withTagAll"`
WithTagAll bool `json:"withTagAll"`
}
type ContainerPruneReport struct {

View File

@@ -13,6 +13,7 @@ type CronjobCreate struct {
Second int `json:"second" validate:"number"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -34,6 +35,7 @@ type CronjobUpdate struct {
Second int `json:"second" validate:"number"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -76,6 +78,7 @@ type CronjobInfo struct {
Second int `json:"second"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`

View File

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

View File

@@ -66,6 +66,11 @@ type AppInstalledUpdate struct {
AppContainerConfig
}
type AppInstalledIgnoreUpgrade struct {
DetailID uint `json:"detailID" validate:"required"`
Operate string `json:"operate" validate:"required,oneof=cancel ignore"`
}
type PortUpdate struct {
Key string `json:"key"`
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 {
dto.PageInfo
Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
WebsiteGroupID uint `json:"websiteGroupId"`
}
@@ -113,15 +115,18 @@ type WebsiteDomainDelete struct {
}
type WebsiteHTTPSOp struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
}
type WebsiteNginxUpdate struct {

View File

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

View File

@@ -48,6 +48,13 @@ type AppDetailDTO struct {
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 {
model.AppInstall
Total int `json:"total"`

View File

@@ -5,6 +5,7 @@ import "time"
type SettingInfo struct {
UserName string `json:"userName"`
Email string `json:"email"`
SystemIP string `json:"systemIP"`
SystemVersion string `json:"systemVersion"`
SessionTimeout string `json:"sessionTimeout"`
@@ -28,6 +29,7 @@ type SettingInfo struct {
ComplexityVerification string `json:"complexityVerification"`
MFAStatus string `json:"mfaStatus"`
MFASecret string `json:"mfaSecret"`
MFAInterval string `json:"mfaInterval"`
MonitorStatus string `json:"monitorStatus"`
MonitorInterval string `json:"monitorInterval"`
@@ -74,7 +76,7 @@ type PortUpdate 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"`
}
type SnapshotRecover struct {

View File

@@ -12,4 +12,5 @@ type AppDetail struct {
DownloadUrl string `json:"downloadUrl" gorm:"type:varchar;"`
DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"`
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"`
AccessKey string `gorm:"type:varchar(256)" json:"accessKey"`
Credential string `gorm:"type:varchar(256)" json:"credential"`
BackupPath string `gorm:"type:varchar(256)" json:"backupPath"`
Vars string `gorm:"type:longText" json:"vars"`
}

View File

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

View File

@@ -13,6 +13,7 @@ type AppDetailRepo struct {
type IAppDetailRepo interface {
WithVersion(version string) DBOption
WithAppId(id uint) DBOption
WithIgnored() DBOption
GetFirst(opts ...DBOption) (model.AppDetail, error)
Update(ctx context.Context, detail 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)
}
}
func (a AppDetailRepo) WithAppId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
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) {
var detail model.AppDetail
err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error

View File

@@ -2,6 +2,7 @@ package repo
import (
"context"
"fmt"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -16,6 +17,7 @@ type ICommonRepo interface {
WithByName(name string) DBOption
WithByType(tp string) DBOption
WithOrderBy(orderStr string) DBOption
WithOrderRuleBy(orderBy, order string) DBOption
WithByGroupID(groupID uint) DBOption
WithLikeName(name string) 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 {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id in (?)", ids)

View File

@@ -15,6 +15,7 @@ type IComposeTemplateRepo interface {
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
GetRecord(opts ...DBOption) (model.Compose, error)
CreateRecord(compose *model.Compose) error
DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error)
@@ -72,6 +73,16 @@ func (u *ComposeTemplateRepo) Delete(opts ...DBOption) 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) {
var composes []model.Compose
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)
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
}

View File

@@ -24,6 +24,7 @@ import (
"os"
"path"
"strconv"
"strings"
)
type AppService struct {
@@ -39,6 +40,7 @@ type IAppService interface {
GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal()
GetIgnoredApp() ([]response.IgnoredApp, error)
}
func NewIAppService() IAppService {
@@ -82,12 +84,16 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
return nil, err
}
var appDTOs []*response.AppDTO
for _, a := range apps {
for _, ap := range apps {
ap.ReadMe = ""
ap.Website = ""
ap.Document = ""
ap.Github = ""
appDTO := &response.AppDTO{
App: a,
App: ap,
}
appDTOs = append(appDTOs, appDTO)
appTags, err := appTagRepo.GetByAppId(a.ID)
appTags, err := appTagRepo.GetByAppId(ap.ID)
if err != nil {
continue
}
@@ -100,7 +106,7 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
continue
}
appDTO.Tags = tags
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(a.ID))
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID))
appDTO.Installed = len(installs) > 0
}
res.Items = appDTOs
@@ -163,7 +169,7 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response.
}
fileOp := files.NewFileOp()
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 {
return appDetailDTO, err
}
@@ -232,6 +238,27 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
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) {
if err = docker.CreateDefaultDockerNetwork(); err != 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
app model.App
)
httpPort, err = checkPort("PANEL_APP_PORT_HTTP", req.Params)
if err != nil {
return
}
httpsPort, err = checkPort("PANEL_APP_PORT_HTTPS", req.Params)
if err != nil {
return
for key := range req.Params {
if !strings.Contains(key, "PANEL_APP_PORT") {
continue
}
var port int
if port, err = checkPort(key, req.Params); err == nil {
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))
if err != nil {
return
@@ -470,9 +506,13 @@ func (a AppService) SyncAppListFromLocal() {
oldDetail, exist := appDetails[version]
if exist {
newDetail.ID = oldDetail.ID
delete(appDetails, version)
}
app.Details[i] = newDetail
}
for _, v := range appDetails {
app.Details = append(app.Details, v)
}
}
app.TagsKey = append(app.TagsKey, constant.AppResourceLocal)
apps[app.Key] = app
@@ -730,10 +770,8 @@ func (a AppService) SyncAppListFromRemote() error {
detail.Params = string(paramByte)
detail.DownloadUrl = v.DownloadUrl
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
if v.LastModified > detail.LastModified {
detail.Update = true
detail.LastModified = v.LastModified
}
detail.Update = true
detail.LastModified = v.LastModified
detailsMap[version] = detail
}
var newDetails []model.AppDetail

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/i18n"
"math"
"os"
"path"
@@ -45,6 +46,7 @@ type IAppInstallService interface {
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
Operate(req request.AppInstalledOperate) error
Update(req request.AppInstalledUpdate) error
IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error
SyncAll(systemInit bool) error
GetServices(key string) ([]response.AppService, error)
GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
@@ -352,6 +354,15 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
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 {
allList, err := appInstallRepo.ListBy()
if err != nil {
@@ -366,8 +377,10 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
}
continue
}
if err := syncById(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
if !systemInit {
if err := syncById(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
}
return nil
@@ -412,6 +425,9 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
return versions, err
}
for _, detail := range details {
if detail.IgnoreUpgrade {
continue
}
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
continue
}
@@ -577,6 +593,9 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
config := getAppCommonConfig(envs)
config.DockerCompose = install.DockerCompose
res.Params = params
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
}
res.AppContainerConfig = config
return &res, nil
}
@@ -589,12 +608,10 @@ func syncById(installId uint) error {
if appInstall.Status == constant.Installing {
return nil
}
containerNames, err := getContainerNames(appInstall)
if err != nil {
return err
}
cli, err := docker.NewClient()
if err != nil {
return err
@@ -620,16 +637,16 @@ func syncById(installId uint) error {
errorContainers = append(errorContainers, n.Names[0])
}
}
for _, old := range containerNames {
for _, name := range containerNames {
exist := false
for _, new := range containers {
if common.ExistWithStrArray(old, new.Names) {
for _, container := range containers {
if common.ExistWithStrArray(name, container.Names) {
exist = true
break
}
}
if !exist {
notFoundContainers = append(notFoundContainers, old)
notFoundContainers = append(notFoundContainers, name)
}
}
@@ -642,10 +659,10 @@ func syncById(installId uint) error {
if containerCount == 0 {
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)
}
if errCount == 0 && existedCount == 0 {
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
appInstall.Status = constant.Running
return appInstallRepo.Save(context.Background(), &appInstall)
}
@@ -660,22 +677,14 @@ func syncById(installId uint) error {
appInstall.Status = constant.UnHealthy
}
var errMsg strings.Builder
var errMsg string
if errCount > 0 {
errMsg.Write([]byte(string(rune(errCount)) + " error containers:"))
for _, e := range errorContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
}
if notFoundCount > 0 {
errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:"))
for _, e := range notFoundContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
}
appInstall.Message = errMsg.String()
appInstall.Message = errMsg
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/dto/request"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/compose-spec/compose-go/types"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"math"
@@ -49,7 +48,20 @@ var (
func checkPort(key string, params map[string]interface{}) (int, error) {
port, ok := params[key]
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))
if len(oldInstalled) > 0 {
var apps []string
@@ -359,7 +371,6 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
return nil, err
}
containerMap := make(map[string]struct{})
containerMap[install.ContainerName] = struct{}{}
for _, service := range project.AllServices() {
if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" {
continue
@@ -370,6 +381,9 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
for k := range containerMap {
containerNames = append(containerNames, k)
}
if len(containerNames) == 0 {
containerNames = append(containerNames, install.ContainerName)
}
return containerNames, nil
}
@@ -522,27 +536,6 @@ func upAppPre(app model.App, appInstall *model.AppInstall) error {
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) {
client, err := composeV2.NewDockerClient()
if err != nil {
@@ -571,13 +564,30 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
func upApp(appInstall *model.AppInstall) {
upProject := func(appInstall *model.AppInstall) (err error) {
if err == nil {
var composeService *composeV2.ComposeService
composeService, err = getServiceFromInstall(appInstall)
if err != nil {
return err
var (
out string
errMsg string
)
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 out != "" {
appInstall.Message = errMsg + out
}
return err
}
return
@@ -587,7 +597,6 @@ func upApp(appInstall *model.AppInstall) {
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
appInstall.Message = err.Error()
} else {
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) {
var res []response.AppInstalledDTO
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
}
installDTO := response.AppInstalledDTO{
@@ -768,12 +777,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
}
var versions []string
for _, detail := range details {
if detail.IgnoreUpgrade {
continue
}
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
continue
}
versions = append(versions, detail.Version)
}
versions = common.GetSortedVersions(versions)
if len(versions) == 0 {
continue
}
lastVersion := versions[0]
if common.IsCrossVersion(installed.Version, lastVersion) {
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 {
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 {
return nil, constant.ErrAuth
}

View File

@@ -2,8 +2,12 @@ package service
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
@@ -24,6 +28,7 @@ type BackupService struct{}
type IBackupService interface {
List() ([]dto.BackupInfo, error)
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
LoadOneDriveInfo() (string, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) 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("COS", ops))
dtobas = append(dtobas, u.loadByType("KODO", ops))
dtobas = append(dtobas, u.loadByType("OneDrive", ops))
return dtobas, err
}
@@ -84,6 +90,18 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
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) {
if info.Source == "LOCAL" {
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 {
return "", err
}
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket
switch backup.Type {
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:
varMap["accessKey"] = backup.AccessKey
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 {
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)
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
srcPath = itemPath + srcPath
}
if exist, _ := backClient.Exist(srcPath); exist {
isOK, err := backClient.Download(srcPath, targetPath)
if !isOK {
@@ -134,6 +158,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error {
if err := copier.Copy(&backup, &backupDto); err != nil {
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 {
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 {
return nil, err
}
varMap["type"] = backupDto.Type
switch backupDto.Type {
case constant.Sftp:
varMap["username"] = backupDto.AccessKey
@@ -154,7 +183,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
varMap["accessKey"] = backupDto.AccessKey
varMap["secretKey"] = backupDto.Credential
}
client, err := cloud_storage.NewCloudStorageClient(varMap)
client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
if err != nil {
return nil, err
}
@@ -214,7 +243,17 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
upMap := make(map[string]interface{})
upMap["bucket"] = req.Bucket
upMap["credential"] = req.Credential
upMap["backup_path"] = req.BackupPath
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 {
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 {
return nil, err
}
varMap["type"] = backup.Type
if backup.Type == "LOCAL" {
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:
varMap["accessKey"] = backup.AccessKey
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 {
return nil, err
}
@@ -286,6 +326,53 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
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) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {

View File

@@ -3,11 +3,11 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -26,24 +26,34 @@ import (
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/gorilla/websocket"
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 IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
List() ([]string, error)
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
ListNetwork() ([]dto.Options, error)
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) (string, 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
ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(param dto.ContainerLog) (string, error)
ContainerStats(id string) (*dto.ContainterStats, error)
ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error
ContainerStats(id string) (*dto.ContainerStats, error)
Inspect(req dto.InspectReq) (string, error)
DeleteNetwork(req dto.BatchDelete) error
CreateNetwork(req dto.NetworkCreate) error
@@ -60,9 +70,8 @@ func NewIContainerService() IContainerService {
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
var (
records []types.Container
list []types.Container
backDatas []dto.ContainerInfo
records []types.Container
list []types.Container
)
client, err := docker.NewDockerClient()
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 {
return list[i].Created > list[j].Created
})
switch req.OrderBy {
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
if start > total {
records = make([]types.Container, 0)
@@ -101,49 +131,87 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
records = list[start:end]
}
var wg sync.WaitGroup
wg.Add(len(records))
for _, container := range records {
go func(item types.Container) {
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
backDatas := make([]dto.ContainerInfo, len(records))
for i := 0; i < len(records); i++ {
item := records[i]
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
var ports []string
for _, port := range item.Ports {
if port.IP == "::" || port.PublicPort == 0 {
continue
}
ports = append(ports, fmt.Sprintf("%v:%v/%s", port.PublicPort, port.PrivatePort, port.Type))
var ports []string
for _, port := range item.Ports {
itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type)
if port.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)
}
cpu, mem := loadCpuAndMem(client, item.ID)
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
CPUPercent: cpu,
MemoryPercent: mem,
Ports: ports,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
})
wg.Done()
}(container)
ports = append(ports, itemPortStr)
}
backDatas[i] = dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
Ports: ports,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
}
wg.Wait()
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) {
client, err := docker.NewDockerClient()
if err != nil {
@@ -213,63 +281,52 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneRepo
return report, nil
}
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
portMap, err := checkPortStats(req.ExposedPorts)
func (u *ContainerService) LoadResouceLimit() (*dto.ResourceLimit, error) {
cpuCounts, err := cpu.Counts(true)
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()
if err != nil {
return err
}
ctx := context.Background()
newContainer, _ := client.ContainerInspect(ctx, req.Name)
if newContainer.ContainerJSONBase != nil {
return buserr.New(constant.ErrContainerName)
}
exposeds := make(nat.PortSet)
for port := range portMap {
exposeds[port] = struct{}{}
}
config := &container.Config{
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
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))
}
var config container.Config
var hostConf container.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 made, now start to create", req.Name)
ctx := context.Background()
if !checkImageExist(client, req.Image) {
if !checkImageExist(client, req.Image) || req.ForcePull {
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 {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return err
@@ -282,6 +339,159 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
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 {
var err error
ctx := context.Background()
@@ -304,6 +514,10 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
case constant.ContainerOpUnpause:
err = client.ContainerUnpause(ctx, req.Name)
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)
case constant.ContainerOpRemove:
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
}
_, _ = file.Seek(0, 0)
files, _ := filepath.Glob(fmt.Sprintf("%s.*", container.LogPath))
for _, file := range files {
_ = os.Remove(file)
}
return nil
}
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
cmd := exec.Command("docker", "logs", req.ContainerID)
if req.Mode != "all" {
cmd = exec.Command("docker", "logs", req.ContainerID, "--since", req.Mode)
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error {
command := fmt.Sprintf("docker logs %s", container)
if tail != "0" {
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 {
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()
if err != nil {
return nil, err
@@ -365,7 +611,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
}
var data dto.ContainterStats
var data dto.ContainerStats
data.CPUPercent = calculateCPUPercentUnix(stats)
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
@@ -529,3 +775,39 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
}
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 {
if err := composeRepo.DeleteRecord(commonRepo.WithByName(item.Name)); err != nil {
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
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) {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
if composeItem.ID != 0 {
return false, constant.ErrRecordExist
}
if err := u.loadPath(&req); err != nil {
return false, err
}

View File

@@ -75,6 +75,26 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface
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 {
client, err := docker.NewDockerClient()
if err != nil {

View File

@@ -80,13 +80,16 @@ func (u *ContainerService) ListVolume() ([]dto.Options, error) {
if err != nil {
return nil, err
}
var data []dto.Options
var datas []dto.Options
for _, item := range list.Volumes {
data = append(data, dto.Options{
datas = append(datas, dto.Options{
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 {
client, err := docker.NewDockerClient()

View File

@@ -36,7 +36,7 @@ func NewICronjobService() ICronjobService {
}
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
for _, cronjob := range cronjobs {
var item dto.CronjobInfo
@@ -100,9 +100,9 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if err != nil {
return err
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
} else {
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
}
}
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 _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
return "", constant.ErrRecordNotFound
return "", err
}
return record.File, nil
}
@@ -145,7 +145,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", constant.ErrRecordNotFound
return "", err
}
return tempPath, nil
}
@@ -238,6 +238,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["name"] = req.Name
upMap["spec"] = cronjob.Spec
upMap["script"] = req.Script
upMap["container_name"] = req.ContainerName
upMap["spec_type"] = req.SpecType
upMap["week"] = req.Week
upMap["day"] = req.Day

View File

@@ -32,17 +32,21 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
if len(cronjob.Script) == 0 {
return
}
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
u.HandleRmExpired("LOCAL", "", cronjob, nil)
if len(cronjob.ContainerName) != 0 {
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":
if len(cronjob.URL) == 0 {
return
}
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":
err = u.handleNtpSync()
u.HandleRmExpired("LOCAL", "", cronjob, nil)
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "website":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "database":
@@ -142,39 +146,55 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
if err != nil {
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 {
return "", err
}
}
u.HandleRmExpired(backup.Type, localDir, cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
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)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) > int(cronjob.RetainCopies) {
for i := int(cronjob.RetainCopies); i < len(records); i++ {
if len(records[i].File) != 0 {
files := strings.Split(records[i].File, ",")
for _, file := range files {
if backType != "LOCAL" {
_, _ = backClient.Delete(strings.ReplaceAll(file, localDir+"/", ""))
_ = os.Remove(file)
} else {
_ = os.Remove(file)
}
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
if len(records) <= int(cronjob.RetainCopies) {
return
}
for i := int(cronjob.RetainCopies); i < len(records); i++ {
if len(records[i].File) != 0 {
files := strings.Split(records[i].File, ",")
for _, file := range files {
_ = os.Remove(file)
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
if backType == "LOCAL" {
continue
}
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
fileItem := file
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, "/") {
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
if len(aheadDir) == 0 {
aheadDir = "/"
}
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
} else {
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)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
@@ -221,7 +244,7 @@ func handleUnTar(sourceFile, targetDir string) error {
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
@@ -270,12 +293,11 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
}
record.DetailName = dbName
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = itemFileDir
}
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := backupRepo.CreateRecord(&record); err != nil {
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))
}()
}
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 {
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
}
@@ -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"))
filePaths = append(filePaths, path.Join(dstLogDir, dstName))
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 {
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755)
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
@@ -358,7 +390,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
}()
}
wg.Wait()
u.HandleRmExpired("LOCAL", "", cronjob, nil)
u.HandleRmExpired("LOCAL", "", "", cronjob, 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)
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
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"))
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil {
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))
}()
}
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 {
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
}

View File

@@ -48,7 +48,7 @@ func NewIMysqlService() IMysqlService {
}
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
for _, mysql := range mysqls {
var item dto.MysqlDBInfo
@@ -148,8 +148,14 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
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 {
return err
if strings.HasPrefix(app.Version, "5.6") {
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 {
return err
@@ -194,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)
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)
}
if info.ID != 0 {
@@ -229,7 +235,7 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
for _, host := range hosts {
if host == "%" || host == "localhost" {
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)
}
if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil {
@@ -280,8 +286,14 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
}
for _, user := range userlist {
if len(user) != 0 {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil {
return err
if strings.HasPrefix(app.Version, "5.6") {
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
}
}
}
}
@@ -346,7 +358,7 @@ func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error
group := "[mysqld]"
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" {
continue
}
@@ -495,7 +507,7 @@ func (u *MysqlService) createUser(container, password, version string, req dto.M
if req.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
}
if strings.HasPrefix(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)
}
if err := excSQL(container, password, grantStr); err != nil {

View File

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

View File

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

View File

@@ -3,12 +3,14 @@ package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"time"
"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/global"
"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 {
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
}
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
if imageRepo.ID != 0 {
return constant.ErrRecordExist
@@ -142,6 +147,9 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if req.ID == 1 {
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))
if err != nil {
return err
@@ -149,7 +157,7 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) {
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
if repo.Auth {
_, _ = cmd.Execf("docker logout %s", repo.DownloadUrl)
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
@@ -176,9 +184,9 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) 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 {
return errors.New(string(stdout))
return fmt.Errorf("stdout: %s, stderr: %v", stdout, err)
}
if strings.Contains(string(stdout), "Login Succeeded") {
return nil

View File

@@ -76,17 +76,31 @@ func loadDiskIO() {
if io2.Name == io1.Name {
var itemIO model.MonitorIO
itemIO.Name = io1.Name
itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
if io2.ReadBytes != 0 && io1.ReadBytes != 0 {
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)
writeCount := uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
if io2.ReadCount != 0 && io1.ReadCount != 0 {
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 {
itemIO.Count = writeCount
}
itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
writeTime := uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
if io2.ReadTime != 0 && io1.ReadTime != 0 {
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 {
itemIO.Time = writeTime
}
@@ -113,8 +127,13 @@ func loadNetIO() {
if net2.Name == net1.Name {
var itemNet model.MonitorNetwork
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)
break
}
@@ -123,14 +142,24 @@ func loadNetIO() {
netStatAll2, _ := net.IOCounters(false)
for _, net2 := range netStatAll2 {
for _, net1 := range netStatAll {
if net1.BytesSent == 0 || net1.BytesRecv == 0 {
continue
}
if net2.Name == net1.Name {
var itemNet model.MonitorNetwork
itemNet.Name = net1.Name
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 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)
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()
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 {
return err
}

View File

@@ -62,7 +62,7 @@ func handleParams(image, runtimeType, runtimeDir string, params map[string]inter
if extendsArray, ok := extends.([]interface{}); ok {
strArray := make([]string, len(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, ",")
}

View File

@@ -56,6 +56,12 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
if len(req.Names) == 0 {
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 {
nameItems := strings.Split(snap, "_")
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)
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
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()})
global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err)
return
@@ -253,7 +261,9 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
operation = "re-recover"
}
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 req.ReDownload {
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" {
_, err := os.Stat(daemonJsonPath)
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 := 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 {
switch operation {
case "snapshot":
exclusionRules := "./tmp;./cache;"
exclusionRules := "./tmp;./cache;./db/1Panel.db-*;"
if strings.Contains(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 {
command = fmt.Sprintf(" | grep '%s'", req.Info)
}
for i := 0; i < len(fileList); i++ {
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
dataItem := loadFailedSecureDatas(commandItem)
data.FailedCount += len(dataItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusSuccess {
withAppend := len(data.Logs) < req.Page*req.PageSize
if req.Status != constant.StatusSuccess {
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
dataItem, itemTotal := loadFailedSecureDatas(commandItem, withAppend)
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...)
}
}
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 -a Accepted %s", fileList[i], command)
dataItem := loadSuccessDatas(commandItem)
data.TotalCount += len(dataItem)
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...)
}
}
@@ -279,7 +279,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
global.LOG.Errorf("load qqwry datas failed: %s", err)
}
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].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])
@@ -293,18 +293,17 @@ func sortFileList(fileNames []string) []string {
if len(fileNames) < 2 {
return fileNames
}
var itemFile []string
if strings.Contains(fileNames[0], "secure") {
if strings.HasPrefix(path.Base(fileNames[0]), "secure") {
var itemFile []string
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[0])
itemFile = append(itemFile, fileNames[len(fileNames)-1])
itemFile = append(itemFile, fileNames[:len(fileNames)-2]...)
return itemFile
}
sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] > fileNames[j]
return fileNames[i] < fileNames[j]
})
return fileNames
}
@@ -339,82 +338,109 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string
return newFiles
}
func loadSuccessDatas(command string) []dto.SSHHistory {
var datas []dto.SSHHistory
func loadSuccessDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
Address: parts[10],
Port: parts[12],
Status: constant.StatusSuccess,
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
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 {
var datas []dto.SSHHistory
func loadFailedAuthDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[8],
User: parts[10],
Address: parts[11],
Port: parts[13],
Status: constant.StatusFailed,
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[8],
User: parts[10],
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 {
var datas []dto.SSHHistory
func loadFailedSecureDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
Address: parts[10],
Port: parts[12],
Status: constant.StatusFailed,
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
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 {

View File

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

View File

@@ -7,11 +7,13 @@ import (
"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/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
"path"
"strconv"
"strings"
)
@@ -21,7 +23,7 @@ type WebsiteSSLService struct {
type IWebsiteSSLService interface {
Page(search request.WebsiteSSLSearch) (int64, []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)
Renew(sslId uint) 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) {
var (
result []response.WebsiteSSLDTO
)
total, sslList, err := websiteSSLRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
if err != nil {
return 0, nil, err
}
var sslDTOs []response.WebsiteSSLDTO
for _, ssl := range sslList {
sslDTOs = append(sslDTOs, response.WebsiteSSLDTO{
WebsiteSSL: ssl,
for _, sslModel := range sslList {
result = append(result, response.WebsiteSSLDTO{
WebsiteSSL: sslModel,
})
}
return total, sslDTOs, err
return total, result, err
}
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
}
func (w WebsiteSSLService) Search() ([]response.WebsiteSSLDTO, error) {
sslList, err := websiteSSLRepo.List()
func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) {
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 {
return nil, err
}
var sslDTOs []response.WebsiteSSLDTO
for _, ssl := range sslList {
sslDTOs = append(sslDTOs, response.WebsiteSSLDTO{
WebsiteSSL: ssl,
for _, sslModel := range sslList {
result = append(result, response.WebsiteSSLDTO{
WebsiteSSL: sslModel,
})
}
return sslDTOs, err
return result, err
}
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
server := config.FindServers()[0]
server.UpdateListen("443", website.DefaultServer, "ssl")
server.UpdateListen("443", website.DefaultServer, "ssl", "http2")
if website.IPV6 {
server.UpdateListen("[::]:443", website.DefaultServer, "ssl")
server.UpdateListen("[::]:443", website.DefaultServer, "ssl", "http2")
}
switch req.HttpConfig {

View File

@@ -21,4 +21,6 @@ type System struct {
IsDemo bool `mapstructure:"is_demo"`
AppRepo string `mapstructure:"app_repo"`
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"
Upgrading = "Upgrading"
UpgradeErr = "UpgradeErr"
PullErr = "PullErr"
ContainerPrefix = "1Panel-"

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,13 @@
ErrInvalidParams: "Request parameter error: {{ .detail }}"
ErrToken: "Token information is incorrect.: {{ .detail }}"
ErrTokenParse: "Token generation error: {{ .detail }}"
ErrTokenTimeOut: "Login information is out of date: {{ .detail }}"
ErrInitialPassword: "Initial password error"
ErrInternalServer: "Service internal error: {{ .detail }}"
ErrRecordExist: "Record already exists"
ErrRecordNotFound: "Records not found"
ErrStructTransform: "Type conversion failure: {{ .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 }}"
ErrNotSupportType: "The system does not support the current type: {{ .detail }}"
ErrRepoNotValid: "Remote repository verification failed"
#common
ErrNameIsExist: "Name is already exist"
@@ -44,6 +40,9 @@ ErrHttpReqTimeOut: "Request timed out {{.err}}"
ErrHttpReqFailed: "Request failed {{.err}}"
ErrHttpReqNotFound: "The file does not exist"
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
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"
ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration"
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
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
ErrInUsed: "{{ .detail }} 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
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 }}"
ErrToken: "Token 信息错误: {{ .detail }}"
ErrTokenParse: "Token 生成错误: {{ .detail }}"
ErrTokenTimeOut: "登陆信息已过期: {{ .detail }}"
ErrInitialPassword: "原密码错误"
ErrInternalServer: "服务内部错误: {{ .detail }}"
ErrRecordExist: "记录已存在"
ErrRecordNotFound: "记录未能找到"
ErrStructTransform: "类型转换失败: {{ .detail }}"
ErrNotLogin: "用户未登录: {{ .detail }}"
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
ErrRepoNotValid: "远程仓库校验失败!"
#common
ErrNameIsExist: "名称已存在"
@@ -44,6 +40,9 @@ ErrHttpReqTimeOut: "请求超时 {{.err}}"
ErrHttpReqFailed: "请求失败 {{.err}}"
ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "网络连接失败"
ErrImagePullTimeOut: '镜像拉取超时'
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
#file
ErrFileCanNotRead: "此文件不支持预览"
@@ -66,6 +65,10 @@ ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
ErrAccountCannotDelete: "账号关联证书,无法删除"
ErrSSLApply: "证书续签成功openresty reload失败请检查配置"
ErrEmailIsExist: '邮箱已存在'
ErrSSLKeyNotFound: '私钥文件不存在'
ErrSSLCertificateNotFound: '证书文件不存在'
ErrSSLKeyFormat: '私钥文件校验失败'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
#mysql
ErrUserIsExist: "当前用户已存在,请重新输入"
@@ -78,6 +81,8 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrRepoConn: "仓库信息中存在不合法的字符"
ErrPortRules: "端口数目不匹配,请重新输入!"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"

View File

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

View File

@@ -1,6 +1,8 @@
package hook
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/global"
"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.CONF.System.Port = portSetting.Value
enptrySetting, err := settingRepo.Get(settingRepo.WithByKey("EncryptKey"))
encryptSetting, err := settingRepo.Get(settingRepo.WithByKey("EncryptKey"))
if err != nil {
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"))
if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
}
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 {
_ = settingRepo.Create("SystemStatus", "Free")
}

View File

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

View File

@@ -390,3 +390,92 @@ var UpdateWebsite = &gormigrate.Migration{
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.InitNginxRouter(PrivateGroup)
systemRouter.InitRuntimeRouter(PrivateGroup)
systemRouter.InitProcessRouter(PrivateGroup)
}
return Router

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ type RouterGroup struct {
DatabaseRouter
NginxRouter
RuntimeRouter
ProcessRouter
}
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/params/:appInstallId", baseApi.GetParams)
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.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/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("/inspect", baseApi.Inspect)
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/build", baseApi.ImageBuild)
baRouter.GET("/volume", baseApi.ListVolume)
baRouter.GET("/network", baseApi.ListNetwork)
baRouter.POST("/network/del", baseApi.DeleteNetwork)
baRouter.POST("/network/search", baseApi.SearchNetwork)
baRouter.POST("/network", baseApi.CreateNetwork)
baRouter.GET("/volume", baseApi.ListVolume)
baRouter.POST("/volume/del", baseApi.DeleteVolume)
baRouter.POST("/volume/search", baseApi.SearchVolume)
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("/test/byinfo", baseApi.TestByInfo)
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
hostRouter.GET(":id", baseApi.GetHostInfo)
hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo)
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.POST("/time/sync", baseApi.SyncTime)
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("/snapshot", baseApi.CreateSnapshot)
@@ -41,6 +41,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription)
settingRouter.GET("/backup/search", baseApi.ListBackup)
settingRouter.GET("/backup/onedrive", baseApi.LoadOneDriveInfo)
settingRouter.POST("/backup/backup", baseApi.Backup)
settingRouter.POST("/backup/recover", baseApi.Recover)
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)

View File

@@ -14,6 +14,7 @@ type cosClient struct {
region string
accessKey string
secretKey string
scType string
Vars map[string]interface{}
client *cosSDK.Client
}
@@ -21,6 +22,7 @@ type cosClient struct {
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
var accessKey string
var secretKey string
var scType string
var region string
if _, ok := vars["region"]; ok {
region = vars["region"].(string)
@@ -32,6 +34,11 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["secretKey"]; ok {
secretKey = vars["secretKey"].(string)
} 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) {
@@ -90,7 +97,12 @@ func (cos cosClient) Upload(src, target string) (bool, error) {
if err != nil {
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 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 {
scType string
Vars map[string]interface{}
client osssdk.Client
}
@@ -14,6 +15,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
var endpoint string
var accessKey string
var secretKey string
var scType string
if _, ok := vars["endpoint"]; ok {
endpoint = vars["endpoint"].(string)
} else {
@@ -24,6 +26,11 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["scType"]; ok {
scType = vars["scType"].(string)
} else {
scType = "Standard"
}
if _, ok := vars["secretKey"]; ok {
secretKey = vars["secretKey"].(string)
} else {
@@ -34,6 +41,7 @@ func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
return nil, err
}
return &ossClient{
scType: scType,
Vars: vars,
client: *client,
}, nil
@@ -77,7 +85,7 @@ func (oss ossClient) Upload(src, target string) (bool, error) {
if err != nil {
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 {
return false, err
}

View File

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

View File

@@ -14,24 +14,23 @@ type CloudStorageClient interface {
Download(src, target string) (bool, error)
}
func NewCloudStorageClient(vars map[string]interface{}) (CloudStorageClient, error) {
if vars["type"] == constant.S3 {
func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
switch backupType {
case constant.S3:
return client.NewS3Client(vars)
}
if vars["type"] == constant.OSS {
case constant.OSS:
return client.NewOssClient(vars)
}
if vars["type"] == constant.Sftp {
case constant.Sftp:
return client.NewSftpClient(vars)
}
if vars["type"] == constant.MinIo {
case constant.MinIo:
return client.NewMinIoClient(vars)
}
if vars["type"] == constant.Cos {
case constant.Cos:
return client.NewCosClient(vars)
}
if vars["type"] == constant.Kodo {
case constant.Kodo:
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"
"fmt"
"os/exec"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
@@ -117,6 +118,43 @@ func Execf(cmdStr string, a ...interface{}) (string, error) {
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 {
cmd2 := exec.Command("sudo", "-n", "ls")
err2 := cmd2.Run()

View File

@@ -4,32 +4,37 @@ import (
"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) {
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
}
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
}
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
}
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
}
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
}
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
}

View File

@@ -89,6 +89,10 @@ func (s *ComposeService) ComposeBuild() error {
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) {
var configFiles []types.ConfigFile
configFiles = append(configFiles, types.ConfigFile{

View File

@@ -8,6 +8,7 @@ import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
@@ -15,6 +16,9 @@ import (
)
func StringEncrypt(text string) (string, error) {
if len(text) == 0 {
return "", errors.New("it is not possible to encrypt an empty string.")
}
key := global.CONF.System.EncryptKey
pass := []byte(text)
xpass, err := aesEncryptWithSalt([]byte(key), pass)
@@ -26,6 +30,9 @@ func StringEncrypt(text string) (string, error) {
}
func StringDecrypt(text string) (string, error) {
if len(text) == 0 {
return "", errors.New("it is not possible to decrypt an empty string.")
}
key := global.CONF.System.EncryptKey
bytesPass, err := base64.StdEncoding.DecodeString(text)
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"strconv"
"time"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/skip2/go-qrcode"
"github.com/xlzd/gotp"
)
@@ -17,10 +18,10 @@ type Otp struct {
QrImage string `json:"qrImage"`
}
func GetOtp(username string) (otp Otp, err error) {
func GetOtp(username string, interval int) (otp Otp, err error) {
secret := gotp.RandomSecret(secretLength)
otp.Secret = secret
totp := gotp.NewDefaultTOTP(secret)
totp := gotp.NewTOTP(secret, 6, interval, nil)
uri := totp.ProvisioningUri(username, "1Panel")
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
dist := make([]byte, 3000)
@@ -31,8 +32,13 @@ func GetOtp(username string) (otp Otp, err error) {
return
}
func ValidCode(code string, secret string) bool {
totp := gotp.NewDefaultTOTP(secret)
func ValidCode(code, intervalStr, secret string) bool {
interval, err := strconv.Atoi(intervalStr)
if err != nil {
global.LOG.Errorf("type conversion failed, err: %v", err)
return false
}
totp := gotp.NewTOTP(secret, 6, interval, nil)
now := time.Now().Unix()
strInt64 := strconv.FormatInt(now, 10)
id16, _ := strconv.Atoi(strInt64)

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