Compare commits

..
Author SHA1 Message Date
ssongliu 534bd98230 style: 修改按钮组边框宽度不一致的问题 (#6706)
Refs #6704
2024-10-14 14:44:58 +08:00
ssongliu 1045f2d475 feat: 修改容器列表应用商店容器默认显示 (#6705) 2024-10-14 14:44:55 +08:00
wild_pointer c5d007e4dd fix: 修复“修改文件权限时,文件所属用户和用户组不显示”问题 (#6698) 2024-10-14 10:57:38 +08:00
zzzandzengzz01 da249a2cef feat:firewalld和ufw同时存在时,不进行操作 (#6688)
Co-authored-by: zengzz01 <zengzz01@mingyuanyun.com>
2024-10-14 10:57:33 +08:00
ssongliu f738cd42be fix: 概览页磁盘显示排除 overlay 类型 (#6672)
Refs #6637
2024-10-14 10:56:44 +08:00
ssongliu d7d15479f2 feat: 容器列表缓存是否显示应用商店容器 (#6667)
Refs #5885
2024-10-14 10:56:40 +08:00
see-more d9de82dc04 chore;删除error_code组件的body标签 (#6656) 2024-10-14 10:56:26 +08:00
zhengkunwang 2d0d3704ae feat: 反向代理文本替换增加 sub_filter_types 参数 (#6661)
Refs https://github.com/1Panel-dev/1Panel/issues/6490
2024-10-14 10:56:09 +08:00
zhengkunwang a3bad3ccf9 feat: 反代回源 SNI 支持设置 proxy_ssl_name (#6659)
Refs https://github.com/1Panel-dev/1Panel/issues/5960
2024-10-14 10:54:29 +08:00
ssongliu 41471f93f0 fix: 修改上传恢复提示信息 (#6647) 2024-10-14 10:54:11 +08:00
zhengkunwang a817beb029 feat: 自签证书支持 ipv6 (#6627)
Refs https://github.com/1Panel-dev/1Panel/issues/6623
2024-10-14 10:54:08 +08:00
ssongliu e6322ad76b style: 修改许可证即将过期样式 (#6616) 2024-09-29 14:40:52 +08:00
ssongliu df003c5fb7 feat: 移除编排删除后跳转 (#6613) 2024-09-29 10:17:04 +08:00
ssongliu 10f8e8f3c4 feat: 调整编排删除逻辑 (#6610) 2024-09-28 22:29:44 +08:00
ssongliu a750e87940 feat: Compose 环境变量编辑增加是否 1Panel 创建校验 (#6605) 2024-09-28 09:43:03 +08:00
John Bro c12f690753 fix: 重构容器编排环境变量设置逻辑 (#6600) 2024-09-27 22:34:38 +08:00
zhengkunwang 0dd51d0c35 feat: 优化日志加载 (#6593) 2024-09-27 22:34:33 +08:00
Kataick 7f627c041c flx: 修复会默认出现水平滚动条的问题 (#6564) 2024-09-25 10:50:55 +08:00
wanghe-fit2cloud 472ca884fd Revert "style:优化概览界面的布局 (#6491)"
This reverts commit 7f06c7e92e.
2024-09-25 10:50:29 +08:00
John Bro 236a2dffea fix: 添加国际化及样式修改 (#6555) 2024-09-23 18:12:50 +08:00
John Bro 9be9f1974f fix: 编排模板列表日期格式化 (#6551) 2024-09-23 18:12:46 +08:00
ssongliu 7961bddf9e fix: 解决服务包含 .socket 时起停失败的问题 (#6553)
Refs #6524
2024-09-23 18:12:42 +08:00
ssongliu a05f5c71b9 fix: 解决部分浏览器无法打开文件菜单的问题 (#6537) 2024-09-20 16:02:15 +08:00
John Bro 139e77112f feat: 容器编排显示容器状态,编排模板样式优化 (#6534)
Refs #4404
2024-09-20 16:02:03 +08:00
蘭 d4e21ae032 fix: 解决图片/视频预览时,重命名文件名导致浏览器缓存文件内容问题 (#6533)
Refs #6505
2024-09-20 16:02:00 +08:00
John Bro 104af30cbf fix: 创建和编辑 compose 文件支持设置环境变量 (#6503) 2024-09-20 16:01:57 +08:00
Anye f68dd7f00b fix: 调整Logo长宽比,防止显示被拉伸 (#6510)
* fix: 调整Logo长宽比,防止显示被拉伸
2024-09-20 16:01:53 +08:00
zhengkunwang 220a6fffa0 feat: demo 环境增加首页状态接口过滤 (#6498) 2024-09-20 16:01:50 +08:00
see-more 23c4365c0e style:优化概览界面的布局 (#6491)
* fix: <body> cannot be child of <div>

* style:优化概览界面的布局
2024-09-20 16:01:46 +08:00
ssongliu 301bc4f6b8 feat: S3 备份账号支持 Path Style (#6489)
Refs #4553
2024-09-20 16:01:40 +08:00
John Bro ab03b05843 fix: 删除容器后 compose 记录保存以便后续处理 (#6447)
Refs https://github.com/1Panel-dev/1Panel/issues/5099
2024-09-20 16:01:34 +08:00
zhengkunwang 1dfbca3d5b feat: 删除多余代码 (#6463) 2024-09-11 23:10:15 +08:00
zhengkunwang d2ca265cc2 perf: 优化首页加载速度 (#6461) 2024-09-11 14:12:16 +00:00
ssongliu fc70345fcd fix: 优化移动端运行时样式 (#6460) 2024-09-11 22:02:11 +08:00
ssongliu 6ae9b982a9 fix: 优化移动端样式 (#6455) 2024-09-11 10:22:15 +00:00
蘭 0122b4c6ce fix: 解决主机文件压缩解压特殊字符问题 (#6451) 2024-09-11 08:20:15 +00:00
ssongliu 08c0faa20e feat: 优化概览页内存单位 (#6450) 2024-09-11 07:38:15 +00:00
ssongliu ab7efea8c5 style: 优化移动端样式 (#6448) 2024-09-11 07:24:15 +00:00
zhengkunwang f4623664d5 feat: 日志页面增加加载状态 (#6446) 2024-09-11 03:48:14 +00:00
ssongliu df147435c5 fix: 移除重复样式 (#6445) 2024-09-11 03:16:14 +00:00
see-more 71c1c5ae83 style:优化设置移动端界面 (#6443)
* style:优化设置移动端界面

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

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

* style:优化移动端设置界面
2024-09-11 10:30:46 +08:00
see-more 0ea142d6db style:优化日志审计页面样式 (#6441) 2024-09-11 10:16:02 +08:00
ssongliu f88a3059be fix: 修改移动端样式 (#6440) 2024-09-10 14:04:12 +00:00
蘭 48281ddf86 fix: 主机文件解压文件 mimetype 类型增加 (#6437) 2024-09-10 14:02:11 +00:00
zhengkunwang 7cdcf9b757 feat: 增加 Intel Arc 显卡 适配 (#6435) 2024-09-10 18:55:29 +08:00
ssongliu ef0bb12fbd fix: 处理代码格式问题 (#6433) 2024-09-10 07:40:09 +00:00
蘭 a0857f86b2 fix: 主机文件打开软链接优化 (#6432) 2024-09-10 07:26:10 +00:00
wangdan-fit2cloud 2219b3f47b perf: 优化工具栏样式 (#6431)
#### What this PR does / why we need it?

#### Summary of your change

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

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-09-10 07:18:10 +00:00
蘭 b6c873f22d fix: 主机文件的路径栏路径 Bug 解决 (#6428)
Refs #6310
2024-09-10 03:46:08 +00:00
蘭 64bc92a7fd fix: 限制二进制文件解压的类型 (#6425)
Refs #6113
2024-09-10 02:14:08 +00:00
ssongliu 6c4c0f7595 fix: 解决服务器重启按钮被遮住的样式问题 (#6424) 2024-09-09 22:56:14 +08:00
蘭 2d9c977805 fix: 解决文件编辑器行号问题 (#6423)
Refs #5918

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

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

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

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

* style:修改database页面样式

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

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

* chore:prettier格式化

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-31 19:05:05 +08:00
zhengkunwang 40f7ef90b3 feat: 修改反向代理默认 Connection 请求头 (#5951)
Refs https://github.com/1Panel-dev/1Panel/issues/5937
2024-07-26 07:01:40 +00:00
zhengkunwang 50efe607d4 feat: 优化泛域名申请证书手动解析值 (#5950)
Refs https://github.com/1Panel-dev/1Panel/issues/4609
2024-07-26 06:59:40 +00:00
igophper 3b057f2c27 feat: 2FA支持前一个验证码 (#5942)
Refs #5940
2024-07-26 11:15:53 +08:00
maninhill d3d018bd44 chore: Update README.md 2024-07-24 17:01:38 +08:00
maninhill 1378609aa2 chore: Update README.md 2024-07-24 16:58:59 +08:00
zhengkunwang 001c896eed fix: 解决上传文件夹权限没改的问题 (#5919) 2024-07-23 17:52:30 +08:00
ssongliu 1cf3a0143c fix: 解决系统日志解析错误的问题 (#5917) 2024-07-23 07:57:32 +00:00
zhengkunwang f04be521e5 feat: 回滚代码 (#5914) 2024-07-23 14:42:06 +08:00
ssongliu 2873632a40 pref: 移除部分废弃代码 (#5910) 2024-07-23 02:31:32 +00:00
zhengkunwang 075879b5b5 fix: 解决应用商店 Go 和 Java 安装跳转错误的问题 (#5909)
Refs https://github.com/1Panel-dev/1Panel/issues/5900
2024-07-23 02:29:32 +00:00
zhengkunwang 9b12930376 fix: 解决安装 gitea 跳转到 postgres 报错的问题 (#5908)
Refs https://github.com/1Panel-dev/1Panel/issues/5902
2024-07-23 02:27:31 +00:00
zhengkunwang 3339ba9bad fix: 解决潜在的 SQL 注入漏洞 (#5906)
影响范围:操作日志
2024-07-22 14:41:31 +00:00
zhengkunwang 68922676fc fix: 解决从网站上传的证书状态显示异常的问题 (#5869)
Refs https://github.com/1Panel-dev/1Panel/issues/5867
2024-07-19 07:41:28 +00:00
zhengkunwang dcf2afb4a6 feat: 上传文件使用父目录用户和用户组 (#5868)
Refs https://github.com/1Panel-dev/1Panel/issues/5866
2024-07-19 07:33:28 +00:00
ssongliu 16a35141dd fix: 解决病毒定时扫描校验国际化的问题 (#5856) 2024-07-18 03:27:28 +00:00
John Bro f6c334bdad feat: 计划任务备份应用/网站/数据库支持多选 (#5849) 2024-07-17 13:35:27 +00:00
蘭 cbb119117b fix: 多个文件同时计算大小,计算状态独自显示 (#5851) 2024-07-17 11:09:30 +00:00
ssongliu cae0a6118f pref: 优化远程数据库解绑提示信息 (#5850) 2024-07-17 09:53:28 +00:00
ssongliu 3c0dc7459c feat: 工具箱病毒扫描支持定时扫描 (#5847) 2024-07-17 08:55:28 +00:00
zhengkunwang ca0c96cb12 fix: 解决网站按过期时间排序之后导致的日期控件错误问题 (#5846) 2024-07-17 06:57:29 +00:00
蘭 ed599265e4 fix: 查看文件/文件夹属性详情 (#5844)
Refs #5842

查看文件/文件夹属性详情,不再获取文件内容。
2024-07-17 06:43:27 +00:00
wanghe-fit2cloud 864bff574a chore: 删除 FUNDING.yml 2024-07-17 12:10:43 +08:00
蘭 3b1df05ae0 fix: 关闭 pdf 预览 (#5838)
pdfjs 安全漏洞,后续版本增加预览 pdf
2024-07-16 11:17:27 +00:00
蘭 464392f7e1 fix: 文本管理计算文件夹大小请求超时 (#5836)
文本管理计算文件夹大小调整为局部加载,增加请求时长
2024-07-16 07:05:27 +00:00
zhengkunwang b562d9f8e8 feat: 网站过期时间增加排序 (#5833) 2024-07-16 04:03:28 +00:00
ssongliu 74bd997f07 pref: 优化远程数据库删除提示信息 (#5824)
Refs #5819 #5818
2024-07-15 10:43:27 +00:00
ssongliu 12c6fe6331 feat: 病毒扫描增加状态判断 (#5817)
Refs #5769
2024-07-15 04:05:26 +00:00
maoball eb4d550305 fix: 修复变量名拼错 (#5792)
* fix: 修复变量名拼错

* fix: 修复变量名拼错

* fix: 修复变量名拼错

* fix: 修复变量名拼错

* fix: 修复变量名拼错
2024-07-13 21:58:02 +08:00
zhengkunwang 9a635e99b2 feat: 增加 clamdscan 服务检测 (#5784) 2024-07-12 06:11:25 +00:00
蘭 03b04eea6b feat: 文本编辑器新增右侧缩略图开关 (#5781) 2024-07-12 02:19:25 +00:00
蘭 d2740308a1 fix: 关闭抽屉同时销毁全部子元素 (#5776) 2024-07-11 13:59:24 +00:00
Anye 6f86dda1aa Add port to vscodeUrl in SSH remote connection (#5775)
修改了 vscodeUrl 模板以在主机和路径之间包含来自 addForm.port 的端口,确保正确设置 SSH 远程连接。
2024-07-11 18:45:52 +08:00
zhengkunwang 0194362a35 feat: 增加 Go 运行环境跳转 (#5764) 2024-07-11 08:21:25 +00:00
John Bro a5bf435d2c fix: 字符校验修正 (#5761) 2024-07-11 06:41:24 +00:00
蘭 bcfbbb656a feat: 文本编辑器全屏高度处理 (#5760) 2024-07-11 13:04:54 +08:00
zhengkunwang c0b6a06fb7 feat: 删除无用代码 (#5757) 2024-07-11 03:41:24 +00:00
zhengkunwang 89628fbbcc fix: 解决安装 mailserver 失败的问题 (#5756) 2024-07-11 03:19:24 +00:00
John Bro e670e1f2e7 fix: 前端优化 (#5755) 2024-07-11 03:09:24 +00:00
John Bro b3759cebd8 fix: 前端优化 (#5754) 2024-07-11 02:59:25 +00:00
zhengkunwang e427b2a219 fix: 解决网站运行目录显示错误的问题 (#5753) 2024-07-11 02:49:26 +00:00
ssongliu b1ec7d9f04 fix: 解决 ftp 创建用户失败的问题 (#5751) 2024-07-10 10:39:24 +00:00
zhengkunwang 0ccfeed10d feat: 网站配置 HTTPS 证书时增加组织显示 (#5749)
Refs https://github.com/1Panel-dev/1Panel/issues/3523
2024-07-10 10:25:24 +00:00
John Bro afd4c8486a feat: 七牛云备份账号支持设置超时时间 (#5745)
Refs #4330
2024-07-10 10:07:26 +00:00
蘭 730a25fcd2 refactor: 文本编辑器全屏按钮样式处理 (#5748) 2024-07-10 10:05:24 +00:00
蘭 9fa191ad69 fix: VS Code 打开文件可以再次编辑配置 (#5744) 2024-07-10 08:15:25 +00:00
maninhill 756dbe08eb chore: Update README.md (#5743) 2024-07-10 15:17:29 +08:00
ssongliu 5f2cfe6e20 fix: 解决快照恢复失败的问题 (#5739) 2024-07-10 04:09:24 +00:00
zhengkunwang af9aef4d34 fix: 解决运行环境显示容器名称错误的问题 (#5733) 2024-07-09 09:41:24 +00:00
蘭 16d26077be fix: 监听编辑器内容变化事件调整 (#5732)
调整监听编辑器内容变化事件到初始化编辑器之后,避免编辑器初始化时导致默认内容被视为用户编辑行为。
2024-07-09 08:49:27 +00:00
ssongliu aef163df29 pref: 优化代理提示信息 (#5731) 2024-07-09 08:47:23 +00:00
蘭 f449557281 fix: 判断文件是否为二进制文件,忽略文本类型文件 (#5730)
Fixes #5722
2024-07-09 08:41:24 +00:00
ssongliu d01e34aa40 fix: 解决 UFW 防火墙列表解析错误的问题 (#5724) 2024-07-09 03:35:23 +00:00
zhengkunwang 02e7c2e974 fix: 解决 Go 运行环境无法保存的问题 (#5721) 2024-07-09 02:21:23 +00:00
ssongliu 047d49e0eb fix: 调整病毒扫描设置界面样式 (#5713) 2024-07-08 10:21:23 +00:00
ssongliu 1c167e828a fix: 端口转发增加 ipv4 限制 (#5712) 2024-07-08 08:11:23 +00:00
ssongliu 8d1322af37 fix: 修改端口转发校验规则 (#5711) 2024-07-08 07:49:25 +00:00
ssongliu 577dfadb9a feat: 病毒扫描增加病毒库刷新状态 (#5710) 2024-07-08 07:47:23 +00:00
ssongliu 0c2653d270 fix: 解决病毒扫描病毒策略更新失败的问题 (#5709) 2024-07-08 10:32:24 +08:00
蘭 0d3e834b37 feat: 查看文件详情解除二进制文件限制 (#5696) 2024-07-05 10:13:08 +00:00
ssongliu 2e8403347a fix: 解决社区版刷新时网站标题显示问题 (#5695) 2024-07-05 16:54:50 +08:00
ssongliu 0105da02af pref: 优化 ClamAV 提示信息 (#5693) 2024-07-05 08:52:39 +00:00
zhengkunwang 40d9dea96c feat: 暂时删除 SSL 证书 OCSP 校验 (#5692)
Refs https://github.com/1Panel-dev/1Panel/issues/5688
2024-07-05 08:50:39 +00:00
蘭 f0fbb02495 feat: 文件编辑器侧边栏指上和选中文件样式调整 (#5689) 2024-07-05 08:48:38 +00:00
ssongliu 9ca69732ca fix: 解决 Firewalld 防火墙列表显示不全的问题 (#5684) 2024-07-04 14:32:34 +00:00
蘭 966b10fbb5 feat: 文件列表组件新增创建文件夹以及文件功能 (#5680) 2024-07-04 14:30:33 +00:00
ssongliu 9e103baf94 fix: 解决概览页磁盘挂载点路径带空格无法正常显示的问题 (#5678) 2024-07-04 09:54:32 +00:00
ssongliu 9a38dcd2a7 fix: 解决防火墙筛选失效的问题 (#5677) 2024-07-04 08:04:33 +00:00
zhengkunwang 08125b150a feat: 机构证书增加下载功能 (#5676)
Refs #4911
2024-07-04 08:02:34 +00:00
ssongliu f370f7eacf fix: 优化病毒扫描报告界面 (#5675) 2024-07-04 08:00:32 +00:00
zhengkunwang a4accc071d feat: 反向代理支持设置回源 SNI (#5674)
Refs https://github.com/1Panel-dev/1Panel/issues/3461
2024-07-04 07:58:32 +00:00
John Bro fd0260f43a feat: 文本编辑器支持更多语言 vue 补充 (#5666) 2024-07-03 11:07:29 +00:00
John Bro 8301382770 feat: 容器仓库配置地址,增加有效性校 (#5664)
Refs #4542
2024-07-03 10:05:29 +00:00
zhengkunwang 0872374fe4 feat: 优化英文翻译 (#5662) 2024-07-03 10:03:31 +00:00
ssongliu 80f78548fd fix: 修改 OneDrive token 刷新 (#5659) 2024-07-03 10:01:30 +00:00
John Bro 1e553fc14e feat: 屏蔽选择网站运行目录中,辅助开发包目录 (#5657)
Refs #4242
2024-07-03 07:05:29 +00:00
蘭 5426c4ea68 chore: 编辑器统一代码风格和 prettier 风格保持一致 (#5656) 2024-07-03 07:03:29 +00:00
蘭 a8842c806c fix: 解决文件上传拖拽栏上传多文件失败问题 (#5655)
Refs #4746
2024-07-03 07:01:28 +00:00
刘瑞斌 8888c9f4ee chore: use unseq list (#5652) 2024-07-03 13:37:28 +08:00
蘭 9da5e67183 feat: 文件列表组件新增创建文件夹功能 (#5647) 2024-07-02 14:43:24 +00:00
maninhill 2a1ec4db75 chore: Update CONTRIBUTING.md (#5646) 2024-07-02 18:16:18 +08:00
John Bro ca803ab26a fix: 升级的时候忽略升级置灰 (#5644)
Refs #5593
2024-07-02 09:43:23 +00:00
蘭 7f77a13b0c feat: 文件管理文本编辑后退出前保存提示 (#5636)
#### What this PR does / why we need it?
Refs #5356
#### Summary of your change
检测文本是否编辑,退出/切换文件作出提示,可以重置当前修改!
#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-07-02 09:27:24 +00:00
ssongliu f131aae344 feat: ClamAV 增加病毒文件处理策略 (#5635) 2024-07-02 09:25:25 +00:00
John Broandzhoujunhong d71514885d fix: docker 镜像加速地址支持添加带端口 (#5638)
Co-authored-by: zhoujunhong <1298308460@qq.com>
2024-07-02 17:25:19 +08:00
zhengkunwang 433055b851 feat: 增加 Go 运行环境 (#5632) 2024-07-01 18:35:15 +08:00
John Bro 32e2e493cc fix: 字体样式调整 (#5631)
Refs #4562
2024-07-01 10:33:17 +00:00
蘭 b5b710f8b2 feat: 文件管理支持预览常见文件格式 (#5629)
#### What this PR does / why we need it?
 Refs #5431
#### Summary of your change
支持预览 pdf/word/excel/image/video/audio
#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-07-01 08:11:17 +00:00
Anye 6fd9dc53e2 Update v_1_9.go (#5609)
为 WordPress 模版添加 gd 扩展
2024-06-28 16:08:06 +08:00
maninhill 7f80d1455d chore: Update README.md 2024-06-28 15:54:25 +08:00
半岛的蒟蒻bddjr b3605f54d6 移除前端打包时生成的gzip文件 #5599 (#5606) 2024-06-28 15:44:27 +08:00
ssongliu 98e73395fc fix: 解决 MariaDB 数据库同步失败的问题 (#5605)
Refs #5591
2024-06-28 07:30:59 +00:00
ssongliu 65fb2bd401 fix: 补全依赖 (#5604) 2024-06-28 14:56:58 +08:00
maninhill ebcfcf87c8 chore: Update README.md 2024-06-28 14:53:36 +08:00
John Bro be469333d1 feat: 文本编辑器支持更多语言 (#5602)
Refs #5169
2024-06-28 06:48:58 +00:00
蘭 9cc4ec49ef feat: 文件管理编辑文件增加左侧目录树 (#5594)
Refs: #3624

#### What this PR does / why we need it?

#### Summary of your change

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

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-06-28 06:16:57 +00:00
yonwoo9 3adf1aebb8 style:fit some code style (#5601) 2024-06-28 14:04:08 +08:00
John Bro 4504a8b188 fit: 修复概览数据库数量显示错误问题 (#5598)
Refs #5280
2024-06-28 05:44:58 +00:00
wanghe-fit2cloud e3d87c542d chore: Update README.md 2024-06-28 11:01:19 +08:00
yonwoo9 a6367fd96b fit:replace ctx err equal check as error.is (#5588) 2024-06-27 17:47:10 +08:00
wanghe 070484772b Update README.md (#5589) 2024-06-27 17:38:44 +08:00
John Bro d00fb2163a fit: 文件显示大小值问题修复 (#5583) 2024-06-27 06:00:53 +00:00
ssongliu 7c7d30376f fix: 解决加密压缩文件解压失败的问题 (#5580) 2024-06-27 04:14:52 +00:00
zhengkunwang 90886bda7e feat: 升级 golang.org/x/image (#5581) 2024-06-27 04:08:53 +00:00
igophper 2e61af2c83 fix: 解决备份密码特殊字符问题 (#5578)
Refs #5573
2024-06-27 11:09:55 +08:00
BugKing 4482bc0b1d feat: 文件管理增加使用 VS Code 打开 (#5572) 2024-06-27 10:49:47 +08:00
ssongliu 2fafe3dcb5 fix: 优化病毒扫描样式 (#5577) 2024-06-26 14:40:52 +00:00
zhengkunwang eded682ecc feat: 增加 java 运行环境 (#5576) 2024-06-26 22:39:25 +08:00
John Bro 151f9d58d3 fit: 文件属性显示大小值调整 (#5575)
Refs #3934
2024-06-26 14:38:50 +00:00
ssongliu 18719f9112 fix: 镜像拉取同步系统认证信息 (#5571)
Refs #5537
2024-06-26 06:28:49 +00:00
John Broandzhoujunhong 0886bcd310 feat: 容器日志下载优化 (#5557)
* feat: 容器日志下载优化

Co-authored-by: zhoujunhong <1298308460@qq.com>
2024-06-25 18:17:15 +08:00
ssongliu 8fedb04c95 feat: 防火墙允许操作 ipv6 (#5564)
Refs #2409
2024-06-25 10:16:45 +00:00
zhengkunwang 15a43ef81b feat: 升级 lego 依赖 (#5563) 2024-06-25 05:48:45 +00:00
zhengkunwang 9121d57e7e feat: 优化应用升级逻辑,拉取镜像失败无需回滚 (#5561) 2024-06-25 03:44:45 +00:00
ssongliu f6cc0b272c fix: 增加系统主题变化监听事件 (#5555) 2024-06-24 09:02:43 +00:00
ssongliu 6e191fc342 pref: 优化容器编排编辑逻辑 (#5556) 2024-06-24 09:00:43 +00:00
ssongliu 6da38ee166 pref: 优化容器编排创建、镜像拉取等前端逻辑 (#5553)
Refs #5523
2024-06-24 08:58:43 +00:00
ShuBo6 064fddb2ee fix: 一些被忽略的句柄关闭问题 (#5552) 2024-06-24 15:29:19 +08:00
maninhill 32e0c6fb3e chore: Update README.md 2024-06-24 14:00:24 +08:00
ssongliu 419343364d fix: 解决面板主题跟随系统失效的问题 (#5547)
Refs #5527
2024-06-24 05:54:43 +00:00
ssongliu 97b790a092 feat: 增加病毒扫描工具 (#5546) 2024-06-24 05:52:42 +00:00
wanghe a08ce58563 Update README.md (#5548) 2024-06-24 11:40:30 +08:00
zhengkunwang f2350d0a65 feat: 优化应用升级逻辑,避免镜像拉取成功之前容器被删除 (#5525) 2024-06-21 06:30:10 +00:00
zhengkunwang ee0aa532d7 feat: 申请证书成功之后支持执行脚本 (#5517)
Refs https://github.com/1Panel-dev/1Panel/issues/3197
2024-06-20 14:18:07 +00:00
zhengkunwang 9f3181b8f3 feat: 优化文件解压,保留文件更新时间 (#5509)
Refs https://github.com/1Panel-dev/1Panel/issues/5474
2024-06-19 14:30:59 +00:00
zhengkunwang 003c87bf00 feat: 优化文件上传逻辑,减少临时空间占用 (#5508)
Refs https://github.com/1Panel-dev/1Panel/issues/5184
2024-06-19 09:54:58 +00:00
zhengkunwang 1577581257 feat: 优化网站 WAF 配置文件生成 (#5504) 2024-06-19 09:42:57 +00:00
maninhill b6d916de6a chore: Update README.md 2024-06-19 17:23:04 +08:00
BugKing 534bbb0211 docs: 修改预览图片地址 2024-06-19 17:21:16 +08:00
BugKing f3f08ce2d3 docs: 去掉原有的 UI 展示部分 2024-06-19 17:21:16 +08:00
BugKing 7f19532051 docs: readme 中增加视频图片 2024-06-19 17:21:16 +08:00
zhengkunwang 0bdeba7288 feat: 证书推送自动创建文件夹 (#5501)
Refs https://github.com/1Panel-dev/1Panel/issues/3523
2024-06-19 08:48:58 +00:00
zhengkunwang 4460329d2d style: 优化申请证书 acme 账户显示 (#5500)
Refs https://github.com/1Panel-dev/1Panel/issues/3580
2024-06-19 08:46:58 +00:00
zhengkunwang f59bea27e8 feat: 创建静态、PHP 网站默认增加 404 页面 (#5498) 2024-06-19 06:08:58 +00:00
John Bro 4c59090180 feat: 回收站文件支持批量还原 (#5497)
Refs #5148
2024-06-19 06:06:57 +00:00
endymx f17db1d6d5 fix: 端口转发功能在ufw下重启面板导致Docker自带规则被清除 (#5490)
* refactor: iptables重载逻辑

* fix: 去除debug代码
2024-06-19 10:26:16 +08:00
zhengkunwang 39a5767319 feat: 增加修改网站默认页面功能 (#5491) 2024-06-19 10:17:36 +08:00
ssongliu 7e182d32a6 pref: 快照增加恢复前资源检查 (#5489) 2024-06-18 10:08:54 +00:00
zhengkunwang e32104581b feat: 网站列表增加证书过期时间显示 (#5488)
Refs https://github.com/1Panel-dev/1Panel/issues/4271
2024-06-18 10:06:54 +00:00
John Bro c30ea553bd feat: 文件编辑器支持网页全屏 (#5486)
Refs #5357
2024-06-18 10:04:53 +00:00
zhengkunwang 345dbda400 feat: 优化日志读取 (#5485)
Refs https://github.com/1Panel-dev/1Panel/issues/3690
2024-06-17 14:24:50 +00:00
ssongliu f4103e285d fix: 解决升级导致的计划任务未执行问题 (#5483)
Refs #5414
2024-06-17 14:18:50 +00:00
John Bro dca47af92a feat: 进程守护日志支持全屏 (#5482)
Refs #4136
2024-06-17 14:16:49 +00:00
endymx af0ecce25b feat: 端口转发功能 (#5439) 2024-06-15 22:28:03 +08:00
zhengkunwang b36e2c5676 feat:网站 HTTPS 增加 HSTS 配置 (#5463)
Refs https://github.com/1Panel-dev/1Panel/issues/3906
2024-06-14 10:22:39 +00:00
zhengkunwang b428d818e7 feat: 更新 SSL 证书的加密算法 (#5460)
Refs https://github.com/1Panel-dev/1Panel/issues/4835
2024-06-14 08:22:39 +00:00
zhengkunwang 25b0725570 feat: 续签证书不重置私钥 (#5458)
Refs https://github.com/1Panel-dev/1Panel/issues/5314
2024-06-14 08:20:39 +00:00
John Bro 2513c10d22 feat: 容器操作增加制作容器镜像功能 (#5447) 2024-06-14 08:18:39 +00:00
ssongliu 584234c4f1 fix: 解决界面设置主题色不统一的问题 (#5449) 2024-06-13 14:18:37 +00:00
zhengkunwang 8a1b0d6913 feat: 网站列表按照名称排序 (#5448)
Refs https://github.com/1Panel-dev/1Panel/issues/5437
2024-06-13 14:16:37 +00:00
zhengkunwang 68ac4341ac feat: demo 环境适配网站监控 (#5446) 2024-06-13 08:22:36 +00:00
zhengkunwang 4209452e0b fix: 解决应用安装 services 为空报系统错误的问题 (#5445) 2024-06-13 08:20:36 +00:00
zhengkunwang 3b7f8c2724 feat: 优化非80端口网站开启 https 端口配置 (#5443)
Refs https://github.com/1Panel-dev/1Panel/issues/5364
2024-06-13 08:14:37 +00:00
ssongliu 01588ed9b5 fix: 修改终端全屏按钮样式 (#5442)
Refs #5435
2024-06-13 08:12:36 +00:00
zhengkunwang 99fc39d401 perf: 优化前端打包速度 (#5440) 2024-06-13 05:54:36 +00:00
ssongliu b6f2497346 fix: 解决浏览器切换时部分设置未同步的问题 (#5436)
Refs #5422
2024-06-13 03:52:35 +00:00
zhengkunwang 09dc15a940 fix: 解决部分情况下打开文件中文乱码的问题 (#5425)
Refs https://github.com/1Panel-dev/1Panel/issues/5374
2024-06-12 06:32:37 +00:00
zhengkunwang 9bd9700bac fix: 解决升级页面 链接点击不跳转的问题 (#5413)
Refs https://github.com/1Panel-dev/1Panel/issues/5371
2024-06-11 10:10:35 +00:00
ssongliu 05e62a6f94 fix: 解决计划任务执行周期错误的问题 (#5412)
Refs #5404
2024-06-11 10:08:34 +00:00
ssongliu ff549a4793 fix: 解决 gorm 的 sql 注入问题 (#5409) 2024-06-11 10:06:34 +00:00
Frost-ZX 0341587716 fix: 修正未认证设置表单初始值 (#5383) 2024-06-11 11:33:46 +08:00
ssongliu e2316369a7 fix: 解决深色模式下版本显示问题 (#5402)
Refs #5370
2024-06-11 02:56:32 +00:00
zhengkunwang 4285fedc5d style: 黑金主题样式优化 (#5360) 2024-06-08 04:20:25 +00:00
ssongliu 05c8370f0d pref: 优化暗黑主题样式 (#5355) 2024-06-07 16:24:24 +00:00
Anye b866816800 添加Flarum的PHP扩展模板 (#5354) 2024-06-07 23:56:46 +08:00
ssongliu e0f8cd96f8 fix: 修复应用升级文件对比显示问题 (#5352) 2024-06-07 15:02:23 +00:00
ssongliu c81f8c1bc4 fix: 解决登录跳概览页时样式错误的问题 (#5351) 2024-06-07 14:48:23 +00:00
ssongliu 45d628838f fix: 解决获取 SSL 信息错误的问题 (#5347)
Refs #5339
2024-06-07 14:38:24 +00:00
ssongliu f433895c97 fix: 修改部分黑金样式 (#5345) 2024-06-07 14:36:22 +00:00
zhengkunwang 1fcfbaf0a9 style: 黑金主题样式优化 (#5337) 2024-06-07 07:44:22 +00:00
zhengkunwang 63305e7316 fix: 解决自签证书开启自动续签报错的问题 (#5335) 2024-06-07 11:02:03 +08:00
ssongliu c394efa2ec fix: 解决 PostgreSQL 页面切换的样式问题 (#5332) 2024-06-06 14:41:38 +00:00
zhengkunwang 859ace71df fix: 解决限制应用安装之后安装按钮还能点的问题 (#5331) 2024-06-06 14:15:38 +00:00
zhengkunwang a7cf69e5a2 fix: 解决应用已安装时间显示错误的问题 (#5327) 2024-06-06 11:01:38 +00:00
John Bro d0105c7863 fix: 快照恢复弹框增加压缩密码输入框 (#5326) 2024-06-06 10:49:38 +00:00
ssongliu e18102dfc9 pref: 加密备份代码优化 (#5325) 2024-06-06 10:43:38 +00:00
ssongliu 135ac91160 fix: 增加系统请求超时时间 (#5324) 2024-06-06 09:37:38 +00:00
ssongliu f670095c08 fix: 修改登录页弹框判断 (#5323) 2024-06-06 08:29:37 +00:00
ssongliu ac384628c9 fix: 解决压缩备份恢复失败的问题 (#5322) 2024-06-06 08:03:39 +00:00
John Bro 60df7786d8 fix: 文件返回前进功能优化 (#5320) 2024-06-06 07:13:37 +00:00
ssongliu 0e74acbec0 fix: 解决应用安装失败的问题 (#5319) 2024-06-06 06:16:14 +00:00
ssongliu 37de6aae66 fix: 调整 Redis 空状态样式 (#5315) 2024-06-06 03:18:12 +00:00
John Bro f03051ce43 fix: 备份恢复弹框中增加输入压缩密码输入框 (#5305) 2024-06-05 15:54:10 +00:00
ssongliu d83402b819 fix: 解决 HTTP 类型代理失败的问题 (#5306) 2024-06-05 15:46:11 +00:00
ssongliu 97e800ee15 fix: 解决删除网站没删除备份文件的问题 (#5309) 2024-06-05 23:45:24 +08:00
zhengkunwang 7f583e5678 feat: 限制 PHP 运行环境制作镜像中被删除 (#5304) 2024-06-05 10:08:09 +00:00
吴小白 80b7bb44f7 fix: 修正 goreleaser v2 参数 (#5303)
#### What this PR does / why we need it?

DEPRECATED: --skip-publish was deprecated in favor of --skip=publish, check https://goreleaser.com/deprecations#-skip for more details

#### Summary of your change

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

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-06-05 08:58:09 +00:00
ssongliu 752e2ec17e fix: 面板代理地址自动增加 http 或者 https (#5302) 2024-06-05 08:52:09 +00:00
zhengkunwang 0118fbb944 fix: 修复编辑证书,秘钥算法没有回显的问题 (#5301)
Refs https://github.com/1Panel-dev/1Panel/issues/5299
2024-06-05 08:48:10 +00:00
ssongliu dba5302d68 fix: 解决代理选项为空的问题 (#5300) 2024-06-05 08:18:09 +00:00
ssongliu 546bc53f28 fix: 解决开启代理时同步应用失败的问题 (#5298) 2024-06-05 08:04:10 +00:00
zhengkunwang 4883c9dd49 feat: 升级增加 docker-compose 文件对比功能 (#5297) 2024-06-05 07:54:09 +00:00
ssongliuandssonglius11 428743d600 fix: 解决不带密码压缩文件失败的问题 (#5296)
Co-authored-by: ssonglius11 <ssonglius11@163.com>
2024-06-05 14:31:26 +08:00
zhengkunwang 33c4b8bba9 fix: 解决某些情况下安装应用错误没有正确显示的问题 (#5295)
Refs https://github.com/1Panel-dev/1Panel/issues/5289
2024-06-05 06:18:08 +00:00
ssongliu 296ae1c2e1 fix: 解决主题色切换报错的问题 (#5287) 2024-06-05 02:29:08 +00:00
ssongliu 7fb41f43cc fix: 解决登录页跳转空白的问题 (#5285) 2024-06-04 14:29:05 +00:00
ssongliu 0afc6242a2 fix: 优化 FTP 列表分页样式 (#5283) 2024-06-04 14:03:06 +00:00
zhengkunwang 62beecf337 fix: 解决自签证书关闭页面之后没有刷新的问题 (#5282) 2024-06-04 14:01:06 +00:00
zhengkunwang b08c7ff89d style: 统一修改名称样式 (#5281) 2024-06-04 13:59:05 +00:00
zhengkunwang daab425c78 fix: 解决打包报错问题 (#5278) 2024-06-04 09:02:31 +00:00
ssongliu 8ec9f727bd fix: 解决表格超出隐藏显示不正确的问题 (#5277) 2024-06-04 08:52:32 +00:00
zhengkunwang 8b781d466a feat: 应用升级支持对比 docker-compose 文件 (#5274)
Refs https://github.com/1Panel-dev/1Panel/issues/2826
2024-06-04 08:50:31 +00:00
John Bro 0182586869 feat: 压缩和解压缩 tar.gz 文件时支持设置压缩密码 (#5258) 2024-06-04 08:48:31 +00:00
John Bro dab5434125 fix: 容器垃圾清理不展示细节下拉清除 (#5271) 2024-06-04 06:38:48 +00:00
ssongliu e638a416ff fix: 修改面板设置证书时间显示不一致的问题 (#5267) 2024-06-04 03:41:50 +00:00
zhengkunwang ceca7fade0 fix: 解决部分情况下升级版本不显示的问题 (#5261)
Refs https://github.com/1Panel-dev/1Panel/issues/5256
2024-06-03 09:55:46 +00:00
zhengkunwang bdf8ba45ac fix: 解决应用安装升级状态错误的问题 (#5260) 2024-06-03 07:41:45 +00:00
ssongliu e45f43da9a pref: 优化登录页确认框样式 (#5259) 2024-06-03 15:41:35 +08:00
ssongliu 32fb7feb44 fix: 修改远程数据库排序问题 (#5257) 2024-06-03 06:07:44 +00:00
zhengkunwang caee2159a8 feat: 网站 access_log 使用主配置文件参数 (#5255) 2024-06-03 03:23:43 +00:00
zhengkunwang 6011f35f59 style: 增加 Chrome OS 图标 (#5243) 2024-05-31 10:27:19 +00:00
ssongliu 869d0c73bd fix: 服务器代理修改默认值 (#5242) 2024-05-31 10:07:19 +00:00
zhengkunwang 9af00cc80c perf: 优化应用同步查询接口 (#5241) 2024-05-31 09:57:20 +00:00
ssongliu 400dd79b9f fix: Redis 数据库连接信息移除密码限制 (#5239) 2024-05-31 09:35:20 +00:00
zhengkunwang 8c929004e9 feat: 修改 xpack 加载顺序 (#5238) 2024-05-31 09:33:21 +00:00
ssongliu e044d92075 fix: 快照删除增加备份文件删除选项 (#5237)
Refs #4962
2024-05-31 09:31:19 +00:00
ssongliu a02f160adf fix: demo 环境禁用终端工具 (#5235) 2024-05-31 06:51:18 +00:00
ssongliu ac33f5461d fix: 优化 Redis 数据库样式 (#5229) 2024-05-31 14:12:42 +08:00
ssongliu bf7a4f3254 fix: 解决计划任务时区显示不一致的问题 (#5230)
Refs  #5211
2024-05-31 05:47:19 +00:00
zhengkunwang ea40e32991 feat: 删除无用依赖 (#5227) 2024-05-30 11:01:15 +00:00
zhengkunwang d38d2c617d feat: 增加定时清理缓存 (#5226) 2024-05-30 10:09:15 +00:00
zhengkunwang 90bcf464d3 feat: Node.js 增加 pnpm 支持 (#5225) 2024-05-30 09:39:15 +00:00
ssongliu bd91c88357 fix: 解决 FTP 状态同步失败的问题 (#5224) 2024-05-30 09:11:16 +00:00
ssongliu 60dbd75ce2 fix: 远程数据库增加名称校验 (#5222) 2024-05-30 09:09:15 +00:00
ssongliu d5abd7ec3f fix: 解决远程数据库备份失败的问题 (#5221)
Refs #5166
2024-05-30 07:25:15 +00:00
ssongliu c133f39b5e fix: 解决主题颜色切换失败的问题 (#5218) 2024-05-30 07:17:15 +00:00
zhengkunwang 9df8f13fa7 feat: 升级 lego 版本 (#5216) 2024-05-30 07:15:14 +00:00
ssongliu e5bcb07cf6 fix: 处理数据库遮罩问题 (#5215) 2024-05-30 15:13:21 +08:00
ssongliu c62232e648 fix: 修复 Redis 数据库快速命令部分问题 (#5206) 2024-05-29 14:13:11 +00:00
zhengkunwang a4a66e1e18 fix: 解决应用安装切换版本校验条件没变的问题 (#5205) 2024-05-29 10:49:11 +00:00
zhengkunwang 86d021cbfc fix: 解决打开非文本文件报错的问题 (#5203) 2024-05-29 10:43:13 +00:00
zhengkunwang 9f3573bcb8 perf: 优化应用查询接口的执行速度 (#5202)
Refs https://github.com/1Panel-dev/1Panel/issues/5008
2024-05-29 10:41:13 +00:00
ssongliu 28f2dddcf6 fix: 解决黑金模式下概览页异常闪动的问题 (#5201) 2024-05-29 10:39:12 +00:00
zhengkunwang 3aa62f91bf fix: 解决使用本地 PHP 环境停止启动网站之后配置错误的问题 (#5198)
Refs https://github.com/1Panel-dev/1Panel/issues/5163
2024-05-29 07:49:10 +00:00
ssongliu fe00cb8015 style: 固定登录页样式 (#5197) 2024-05-29 07:15:10 +00:00
ssongliu 312c19e3cc fix: 概览页磁盘显示过滤 /snap (#5196) 2024-05-29 06:27:11 +00:00
Takagi 409e90d2c5 fix: the issue where the auto theme is invalid due to an incorrect class setting (#5194) 2024-05-29 14:26:20 +08:00
ssongliu 8fc708be97 feat: 增加服务器代理设置 (#5189) 2024-05-29 03:05:09 +00:00
ssongliu 2c7dcbfe17 fix: 解决概览页磁盘显示不全的问题 (#5190)
Refs #5174
2024-05-29 02:39:09 +00:00
zhengkunwang c189c48d51 fix: 解决清空伪静态配置报错的问题 (#5179)
Refs https://github.com/1Panel-dev/1Panel/issues/5086
2024-05-28 08:49:06 +00:00
wanghe a08612e0a0 docs: 增加安装包安装说明 2024-05-28 15:42:20 +08:00
ssongliu acda63d2f7 fix: 登录页跳转增加 404 (#5164) 2024-05-27 10:42:56 +00:00
ssongliu c39f029808 feat: 修改安全入口页特征样式 (#5159) 2024-05-27 03:32:54 +00:00
zhengkunwang 41bc8b634b feat: 增加华为 Icon (#5143) 2024-05-24 23:11:25 +08:00
Node b80b67b060 feat: 提升分段上传文件的体验 (#5123)
* feat: 提升分段上传文件的体验

* 原新建文件 mode 为755,修正为644
2024-05-24 11:15:24 +08:00
ssongliu 9c357f1ea4 fix: 解决升级过程中备份错误问题 (#5120)
Refs #5103
2024-05-23 15:16:50 +00:00
ssongliu 99ed9be1dd fix: 解决计划任务执行周期解析错误的问题 (#5119) 2024-05-23 09:50:49 +00:00
Node 3df8863904 fix: 修复分块上传文件资源未释放问题 (#5109) 2024-05-23 16:41:19 +08:00
Node f211b6b5ae fix: 修复几处日志文件未释放问题 (#5110) 2024-05-23 16:41:05 +08:00
ssongliu b59c9ee9ad fix: 解决升级过程中备份问题 (#5107) 2024-05-23 03:26:49 +00:00
ssongliu 8798babe07 pref: 1pctl 引用路径使用源文件代替软链地址 (#5105)
Refs #4930
2024-05-23 02:14:49 +00:00
Takagi 63c330054e refactor: refactor theme and logo switching functionality (#5015) 2024-05-22 18:38:03 +08:00
zhengkunwang f30b05a495 feat: 增加一些 icon (#5098) 2024-05-22 10:36:47 +00:00
zhengkunwang 260fbb6e19 feat: 文件夹名称增加完整路径展示 (#5097)
Refs https://github.com/1Panel-dev/1Panel/issues/5094
2024-05-22 10:34:47 +00:00
ssongliu 5cd59c7f39 fix: 解决在线文本编辑器打开 GBK 编码文件乱码问题 (#5096)
Refs #3804
2024-05-22 10:32:47 +00:00
zhengkunwang ede664be18 feat: 文件列表行内权限支持修改用户和用户组 (#5093)
Refs https://github.com/1Panel-dev/1Panel/issues/4981
2024-05-22 06:30:46 +00:00
zhengkunwang 0a7144f625 fix: 解决网站排序错误的问题 (#5092)
Refs https://github.com/1Panel-dev/1Panel/issues/5021
2024-05-22 06:26:47 +00:00
ssongliu 4dc722b83c feat: 进程守护增加运行目录跳转 (#5088)
Refs #4953
2024-05-22 03:10:45 +00:00
ssongliu 6131a99db0 fix: 解决部分 ssh 登录日志解析失败的问题 (#5081) 2024-05-21 08:44:45 +00:00
ssongliu d9f9f9a629 feat: 网站创建增加 FTP 选项 (#5076) 2024-05-21 08:42:46 +00:00
zhengkunwang 5e951f028e fix: 解决部分应用图标不显示的问题 (#5080) 2024-05-21 08:40:45 +00:00
ssongliu 65f92bf0c3 feat: FTP 增加状态及日志 (#5065) 2024-05-20 10:48:42 +00:00
zhengkunwang 201600ea06 feat: 增加网站监控的说明 (#5053) 2024-05-20 09:54:41 +00:00
ssongliu 64ed07e4fa feat: 工具箱支持 FTP (#5039) 2024-05-17 14:36:38 +00:00
zhengkunwang 35c4f4edfd feat: 增加网站监控页面 (#5038) 2024-05-17 09:54:38 +00:00
John Bro c31b3e9ddc feat: 工具箱的缓存清理添加容器垃圾清理功能 (#5024) 2024-05-17 06:02:37 +00:00
zhengkunwang 2e47afef3b feat: 优化网站目录跳转 (#5025) 2024-05-17 04:12:37 +00:00
ssongliu 54e81b2de5 fix: 修改数据库状态异常样式 (#5020) 2024-05-15 15:04:35 +00:00
ssongliu 8870b92ceb feat: Redis 数据库支持快速命令 (#5018)
Refs #3220
2024-05-15 15:02:35 +00:00
Anye cce60f4712 添加WordPress的PHP扩展模板 (#5016) 2024-05-15 13:48:49 +08:00
ssongliu 94cebfd8cc feat: Redis 支持远程数据库 (#5014) 2024-05-15 11:56:40 +08:00
ssongliu c56435970a feat: 数据库支持多 Redis 切换 (#5001)
Refs #3927
2024-05-13 14:40:31 +00:00
Zolo 188d9dd4c7 feat: 首页监控中上下行流量数据单位自动格式化 (#4207) (#4989) 2024-05-13 22:37:52 +08:00
zhengkunwang 8b9d1d9052 fix: 解决上传证书状态异常的问题 (#4999)
Refs https://github.com/1Panel-dev/1Panel/issues/4976
2024-05-13 05:54:30 +00:00
zhengkunwang 5a8869e832 feat: 优化网站域名跳转 (#4998)
Refs https://github.com/1Panel-dev/1Panel/issues/4964
2024-05-13 05:52:29 +00:00
lewisay 81835ef9a7 fix: 解决编辑证书时,其他域名格式不正确问题 (#4991)
#### What this PR does / why we need it?

#### Summary of your change

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

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2024-05-13 03:52:30 +00:00
zhengkunwang b3ce35d516 fix: 解决进程守护命令不能输入空格的问题 (#4997) 2024-05-13 03:34:30 +00:00
ssongliu 5979e8b5d4 feat: 存储卷清理增加清除所有标签 (#4994) 2024-05-13 03:32:29 +00:00
zhengkunwang 79c2ba2125 fix: 解决应用名称存在大写导致无法升级的问题 (#4987)
Refs https://github.com/1Panel-dev/1Panel/issues/4986
2024-05-12 14:16:28 +00:00
ssongliu 95bbf4ee52 feat: 右侧抽屉取消 esc 关闭 (#4979) 2024-05-11 09:52:28 +00:00
John Broandzhoujunhong d43df6941b feat: 运行环境及容器菜单增加清理构建缓存功能 (#4978)
Co-authored-by: zhoujunhong <1298308460@qq.com>
2024-05-11 17:50:24 +08:00
573 changed files with 25916 additions and 5649 deletions
-1
View File
@@ -1 +0,0 @@
custom: ["https://afdian.net/a/1Panel"]
+6 -6
View File
@@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '18.14'
node-version: '20.2'
- name: Build Web
id: build_frontend
run: |
@@ -25,10 +25,10 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: '1.20.x'
go-version: '1.22'
- name: Build Server
uses: goreleaser/goreleaser-action@v4
uses: goreleaser/goreleaser-action@v6
with:
args: release --snapshot --clean
+6 -6
View File
@@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '20.2'
- name: Build Web
@@ -22,15 +22,15 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
go-version: '1.22'
- name: Build Release
uses: goreleaser/goreleaser-action@v4
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release --skip-publish --clean
args: release --skip=publish --clean
- name: Upload Assets
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
+2 -3
View File
@@ -29,6 +29,7 @@ build/1panel
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories
/pkg/
backend/__debug_bin
@@ -40,9 +41,7 @@ frontend/auto-imports.d.ts
frontend/components.d.ts
frontend/src/xpack
backend/xpack
backend/router/entry_xpack.go
backend/server/init_xpack.go
backend/utils/xpack/xpack_xpack.go
*_xpack.go
.history/
dist/
+1 -1
View File
@@ -44,7 +44,7 @@ archives:
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
version_template: "{{ incpatch .Version }}-next"
release:
draft: true
mode: append
+5
View File
@@ -1,5 +1,10 @@
# Contributing
As a contributor, you should agree that:
- The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
- Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
## Create pull request
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.
+18 -17
View File
@@ -1,26 +1,32 @@
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>Web-based linux server management control panel</b></p>
<p align="center"><b>现代化、开源的 Linux 服务器运维管理面板</b></p>
<p align="center">
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://hellogithub.com/repository/71791baf930149ac9b84e1acf186573f" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=71791baf930149ac9b84e1acf186573f&claim_uid=p8vB3kP5CMrthiL&theme=dark&theme=neutral" alt="Featured|HelloGitHub" style="width: 250px; height: 55px;" width="250" height="55" /></a>
</p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel.svg?type=shield" alt="FOSSA Status"></a><br>
[<a href="docs/README_TW.md">中文(繁體)</a>] | [<a href="docs/README_EN.md">English</a>] | [<a href="docs/README_JP.md">日本語</a>]
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="GitHub Stars"></a>
<a href="https://gitee.com/fit2cloud-feizhiyun/1Panel"><img src="https://gitee.com/fit2cloud-feizhiyun/1Panel/badge/star.svg?theme=gvp" alt="Gitee Stars"></a><br>
[<a href="docs/README_EN.md">English</a>] | [<a href="docs/README_TW.md">中文(繁體)</a>] | [<a href="docs/README_JP.md">日本語</a>]
</p>
[![Watch the video](https://resource.fit2cloud.com/1panel/img/overview_video.png)](https://www.bilibili.com/video/BV1Mt421n7LZ/)
------------------------------
## 什么是 1Panel?
1Panel 是新一代的 Linux 服务器运维管理面板。
- **高效管理**:用户可以通过 Web 图形界面轻松管理 Linux 服务器,实现主机监控、文件管理、数据库管理、容器管理等功能;
- **高效管理**:用户可以通过 Web 界面轻松管理 Linux 服务器,如主机监控、文件管理、数据库管理、容器管理等;
- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定;
- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级;
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供防火墙和日志审计等功能;
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供病毒防护、防火墙和日志审计等功能;
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
1Panel 三分钟速览:https://www.bilibili.com/video/BV1Mt421n7LZ/
## 快速开始
**一键安装**
@@ -31,27 +37,22 @@
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
```
如果是用于离线环境,推荐使用 [安装包方式](https://1panel.cn/docs/installation/package_installation/) 进行安装部署。
**学习资料**
- [在线文档](https://1panel.cn/docs/)
- [社区论坛](https://bbs.fit2cloud.com/c/1p/7)
- [如何加入微信交流群?](https://bbs.fit2cloud.com/t/topic/2147)
## UI 展示
![UI展示](https://resource.fit2cloud.com/1panel/img/overview.png)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date)
## 我们的其他明星项目
## 飞致云的其他明星项目
- [MaxKB](https://github.com/1Panel-dev/MaxKB/) - 基于 LLM 大语言模型的开源知识库问答系统
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源自动化测试平台
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源持续测试工具
- [KubePi](https://github.com/1Panel-dev/KubePi/) - 现代化、开源的 K8s 面板
## License
+1 -1
View File
@@ -155,7 +155,7 @@ func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
// @Success 200 {object} model.AppInstall
// @Security ApiKeyAuth
// @Router /apps/install [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"name","input_value":"name","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"}
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"安装应用 [name]","formatEN":"Install app [name]"}
func (b *BaseApi) InstallApp(c *gin.Context) {
var req request.AppInstallCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+4 -5
View File
@@ -205,14 +205,13 @@ func (b *BaseApi) GetServices(c *gin.Context) {
// @Param appInstallId path integer true "request"
// @Success 200 {array} dto.AppVersion
// @Security ApiKeyAuth
// @Router /apps/installed/:appInstallId/versions [get]
// @Router /apps/installed/update/versions [post]
func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
var req request.AppUpdateVersion
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
versions, err := appInstallService.GetUpdateVersions(appInstallId)
versions, err := appInstallService.GetUpdateVersions(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
+14 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/middleware"
"github.com/1Panel-dev/1Panel/backend/utils/captcha"
"github.com/1Panel-dev/1Panel/backend/utils/qqwry"
"github.com/gin-gonic/gin"
@@ -118,7 +119,19 @@ func (b *BaseApi) CheckIsSafety(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, status)
if status == "disable" && len(code) != 0 {
helper.ErrorWithDetail(c, constant.CodeErrNotFound, constant.ErrTypeInternalServer, err)
return
}
if status == "unpass" {
if middleware.LoadErrCode("err-entrance") != 200 {
helper.ErrResponse(c, middleware.LoadErrCode("err-entrance"))
return
}
helper.ErrorWithDetail(c, constant.CodeErrEntrance, constant.ErrTypeInternalServer, nil)
return
}
helper.SuccessWithOutData(c)
}
func (b *BaseApi) GetResponsePage(c *gin.Context) {
+3 -3
View File
@@ -118,11 +118,11 @@ func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
// @Summary Delete backup account
// @Description 删除备份账号
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":true,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
func (b *BaseApi) DeleteBackup(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -343,7 +343,7 @@ func (b *BaseApi) Backup(c *gin.Context) {
return
}
case "redis":
if err := backupService.RedisBackup(); err != nil {
if err := backupService.RedisBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
+296
View File
@@ -0,0 +1,296 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags Clam
// @Summary Create clam
// @Description 创建扫描规则
// @Accept json
// @Param request body dto.ClamCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam [post]
// @x-panel-log {"bodyKeys":["name","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建扫描规则 [name][path]","formatEN":"create clam [name][path]"}
func (b *BaseApi) CreateClam(c *gin.Context) {
var req dto.ClamCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Clam
// @Summary Update clam
// @Description 修改扫描规则
// @Accept json
// @Param request body dto.ClamUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam/update [post]
// @x-panel-log {"bodyKeys":["name","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改扫描规则 [name][path]","formatEN":"update clam [name][path]"}
func (b *BaseApi) UpdateClam(c *gin.Context) {
var req dto.ClamUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Clam
// @Summary Update clam status
// @Description 修改扫描规则状态
// @Accept json
// @Param request body dto.ClamUpdateStatus true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam/status/update [post]
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"修改扫描规则 [name] 状态为 [status]","formatEN":"change the status of clam [name] to [status]."}
func (b *BaseApi) UpdateClamStatus(c *gin.Context) {
var req dto.ClamUpdateStatus
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.UpdateStatus(req.ID, req.Status); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Clam
// @Summary Page clam
// @Description 获取扫描规则列表分页
// @Accept json
// @Param request body dto.SearchClamWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /toolbox/clam/search [post]
func (b *BaseApi) SearchClam(c *gin.Context) {
var req dto.SearchClamWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := clamService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Clam
// @Summary Load clam base info
// @Description 获取 Clam 基础信息
// @Accept json
// @Success 200 {object} dto.ClamBaseInfo
// @Security ApiKeyAuth
// @Router /toolbox/clam/base [get]
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
info, err := clamService.LoadBaseInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, info)
}
// @Tags Clam
// @Summary Operate Clam
// @Description 修改 Clam 状态
// @Accept json
// @Param request body dto.Operate true "request"
// @Security ApiKeyAuth
// @Router /toolbox/clam/operate [post]
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Clam","formatEN":"[operation] FTP"}
func (b *BaseApi) OperateClam(c *gin.Context) {
var req dto.Operate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.Operate(req.Operation); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Clam
// @Summary Clean clam record
// @Description 清空扫描报告
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Security ApiKeyAuth
// @Router /toolbox/clam/record/clean [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":true,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"清空扫描报告 [name]","formatEN":"clean clam record [name]"}
func (b *BaseApi) CleanClamRecord(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.CleanRecord(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Clam
// @Summary Page clam record
// @Description 获取扫描结果列表分页
// @Accept json
// @Param request body dto.ClamLogSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /toolbox/clam/record/search [post]
func (b *BaseApi) SearchClamRecord(c *gin.Context) {
var req dto.ClamLogSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := clamService.LoadRecords(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Clam
// @Summary Load clam record detail
// @Description 获取扫描结果详情
// @Accept json
// @Param request body dto.ClamLogReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam/record/log [post]
func (b *BaseApi) LoadClamRecordLog(c *gin.Context) {
var req dto.ClamLogReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := clamService.LoadRecordLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Clam
// @Summary Load clam file
// @Description 获取扫描文件
// @Accept json
// @Param request body dto.ClamFileReq true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /toolbox/clam/file/search [post]
func (b *BaseApi) SearchClamFile(c *gin.Context) {
var req dto.ClamFileReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := clamService.LoadFile(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Clam
// @Summary Update clam file
// @Description 更新病毒扫描配置文件
// @Accept json
// @Param request body dto.UpdateByNameAndFile true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam/file/update [post]
func (b *BaseApi) UpdateFile(c *gin.Context) {
var req dto.UpdateByNameAndFile
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.UpdateFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Clam
// @Summary Delete clam
// @Description 删除扫描规则
// @Accept json
// @Param request body dto.ClamDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"clams","output_column":"name","output_value":"names"}],"formatZH":"删除扫描规则 [names]","formatEN":"delete clam [names]"}
func (b *BaseApi) DeleteClam(c *gin.Context) {
var req dto.ClamDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Clam
// @Summary Handle clam scan
// @Description 执行病毒扫描
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/clam/handle [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":true,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"执行病毒扫描 [name]","formatEN":"handle clam scan [name]"}
func (b *BaseApi) HandleClamScan(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.HandleOnce(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
+86
View File
@@ -29,6 +29,28 @@ func (b *BaseApi) CreateCommand(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Redis Command
// @Summary Save redis command
// @Description 保存 Redis 快速命令
// @Accept json
// @Param request body dto.RedisCommand true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/command/redis [post]
// @x-panel-log {"bodyKeys":["name","command"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"保存 redis 快捷命令 [name][command]","formatEN":"save quick command for redis [name][command]"}
func (b *BaseApi) SaveRedisCommand(c *gin.Context) {
var req dto.RedisCommand
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := commandService.SaveRedisCommand(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Command
// @Summary Page commands
// @Description 获取快速命令列表分页
@@ -55,6 +77,32 @@ func (b *BaseApi) SearchCommand(c *gin.Context) {
})
}
// @Tags Redis Command
// @Summary Page redis commands
// @Description 获取 redis 快速命令列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /hosts/command/redis/search [post]
func (b *BaseApi) SearchRedisCommand(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := commandService.SearchRedisCommandWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Command
// @Summary Tree commands
// @Description 获取快速命令树
@@ -72,6 +120,22 @@ func (b *BaseApi) SearchCommandTree(c *gin.Context) {
helper.SuccessWithData(c, list)
}
// @Tags Redis Command
// @Summary List redis commands
// @Description 获取 redis 快速命令列表
// @Success 200 {Array} dto.RedisCommand
// @Security ApiKeyAuth
// @Router /hosts/command/redis [get]
func (b *BaseApi) ListRedisCommand(c *gin.Context) {
list, err := commandService.ListRedisCommand()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Command
// @Summary List commands
// @Description 获取快速命令列表
@@ -110,6 +174,28 @@ func (b *BaseApi) DeleteCommand(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Redis Command
// @Summary Delete redis command
// @Description 删除 redis 快速命令
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/command/redis/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"redis_commands","output_column":"name","output_value":"names"}],"formatZH":"删除 redis 快捷命令 [names]","formatEN":"delete quick command of redis [names]"}
func (b *BaseApi) DeleteRedisCommand(c *gin.Context) {
var req dto.BatchDeleteReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := commandService.DeleteRedisCommand(req.Ids); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Command
// @Summary Update command
// @Description 更新快速命令
+34
View File
@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"strconv"
)
// @Tags Container
@@ -346,6 +347,26 @@ func (b *BaseApi) ContainerRename(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Container
// @Summary Commit Container
// @Description 容器提交生成新镜像
// @Accept json
// @Param request body dto.ContainerCommit true "request"
// @Success 200
// @Router /containers/commit [post]
func (b *BaseApi) ContainerCommit(c *gin.Context) {
var req dto.ContainerCommit
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := containerService.ContainerCommit(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Container
// @Summary Operate Container
// @Description 容器操作
@@ -440,6 +461,19 @@ func (b *BaseApi) ContainerLogs(c *gin.Context) {
}
}
// @Description 下载容器日志
// @Router /containers/download/log [post]
func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
var req dto.ContainerLog
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
}
}
// @Tags Container Network
// @Summary Page networks
// @Description 获取容器网络列表分页
+3 -3
View File
@@ -36,12 +36,12 @@ func (b *BaseApi) CreateCronjob(c *gin.Context) {
// @Summary Page cronjobs
// @Description 获取计划任务分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Param request body dto.PageCronjob true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /cronjobs/search [post]
func (b *BaseApi) SearchCronjob(c *gin.Context) {
var req dto.SearchWithPage
var req dto.PageCronjob
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
@@ -72,7 +72,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
return
}
loc, _ := time.LoadLocation(common.LoadTimeZone())
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
+7 -14
View File
@@ -2,6 +2,7 @@ package v1
import (
"errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -55,25 +56,17 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
// @Tags Dashboard
// @Summary Load dashboard current info
// @Description 获取首页实时数据
// @Accept json
// @Param ioOption path string true "request"
// @Param netOption path string true "request"
// @Accept json、
// @Param request body dto.DashboardReq true "request"
// @Success 200 {object} dto.DashboardCurrent
// @Security ApiKeyAuth
// @Router /dashboard/current/:ioOption/:netOption [get]
// @Router /dashboard/current [post]
func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
ioOption, ok := c.Params.Get("ioOption")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error ioOption in path"))
var req dto.DashboardReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
netOption, ok := c.Params.Get("netOption")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error netOption in path"))
return
}
data := dashboardService.LoadCurrentInfo(ioOption, netOption)
data := dashboardService.LoadCurrentInfo(req)
helper.SuccessWithData(c, data)
}
+43 -32
View File
@@ -12,11 +12,17 @@ import (
// @Tags Database Redis
// @Summary Load redis status info
// @Description 获取 redis 状态信息
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.RedisStatus
// @Security ApiKeyAuth
// @Router /databases/redis/status [get]
// @Router /databases/redis/status [post]
func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
data, err := redisService.LoadStatus()
var req dto.OperationWithName
if err := helper.CheckBind(&req, c); err != nil {
return
}
data, err := redisService.LoadStatus(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -28,11 +34,17 @@ func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
// @Tags Database Redis
// @Summary Load redis conf
// @Description 获取 redis 配置信息
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.RedisConf
// @Security ApiKeyAuth
// @Router /databases/redis/conf [get]
// @Router /databases/redis/conf [post]
func (b *BaseApi) LoadRedisConf(c *gin.Context) {
data, err := redisService.LoadConf()
var req dto.OperationWithName
if err := helper.CheckBind(&req, c); err != nil {
return
}
data, err := redisService.LoadConf(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -44,11 +56,17 @@ func (b *BaseApi) LoadRedisConf(c *gin.Context) {
// @Tags Database Redis
// @Summary Load redis persistence conf
// @Description 获取 redis 持久化配置
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.RedisPersistence
// @Security ApiKeyAuth
// @Router /databases/redis/persistence/conf [get]
// @Router /databases/redis/persistence/conf [post]
func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
data, err := redisService.LoadPersistenceConf()
var req dto.OperationWithName
if err := helper.CheckBind(&req, c); err != nil {
return
}
data, err := redisService.LoadPersistenceConf(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -57,6 +75,25 @@ func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
helper.SuccessWithData(c, data)
}
func (b *BaseApi) CheckHasCli(c *gin.Context) {
helper.SuccessWithData(c, redisService.CheckHasCli())
}
// @Tags Database Redis
// @Summary Install redis-cli
// @Description 安装 redis cli
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/redis/install/cli [post]
func (b *BaseApi) InstallCli(c *gin.Context) {
if err := redisService.InstallCli(); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Database Redis
// @Summary Update redis conf
// @Description 更新 redis 配置信息
@@ -131,29 +168,3 @@ func (b *BaseApi) UpdateRedisPersistenceConf(c *gin.Context) {
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Redis
// @Summary Page redis backups
// @Description 获取 redis 备份记录分页
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/redis/backup/search [post]
func (b *BaseApi) RedisBackupList(c *gin.Context) {
var req dto.PageInfo
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := redisService.SearchBackupListWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
+2
View File
@@ -37,6 +37,8 @@ var (
deviceService = service.NewIDeviceService()
fail2banService = service.NewIFail2BanService()
ftpService = service.NewIFtpService()
clamService = service.NewIClamService()
settingService = service.NewISettingService()
backupService = service.NewIBackupService()
+48 -9
View File
@@ -11,6 +11,7 @@ import (
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -336,8 +337,12 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
return
}
mode := info.Mode()
fileOp := files.NewFileOp()
stat, ok := info.Sys().(*syscall.Stat_t)
uid, gid := -1, -1
if ok {
uid, gid = int(stat.Uid), int(stat.Gid)
}
success := 0
failures := make(buserr.MultiErr)
@@ -351,6 +356,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
global.LOG.Error(e)
continue
}
_ = os.Chown(dstDir, uid, gid)
}
tmpFilename := dstFilename + ".tmp"
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
@@ -378,6 +384,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
} else {
_ = os.Chmod(dstFilename, mode)
}
if uid != -1 && gid != -1 {
_ = os.Chown(dstFilename, uid, gid)
}
success++
}
if success == 0 {
@@ -592,24 +601,46 @@ func (b *BaseApi) Size(c *gin.Context) {
helper.SuccessWithData(c, res)
}
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int, overwrite bool) error {
defer func() {
_ = os.RemoveAll(fileDir)
}()
op := files.NewFileOp()
dstDir = strings.TrimSpace(dstDir)
mode, _ := files.GetParentMode(dstDir)
if mode == 0 {
mode = 0755
}
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = op.CreateDir(dstDir, mode); err != nil {
return err
uid, gid := -1, -1
if info, err := os.Stat(dstDir); err != nil {
if os.IsNotExist(err) {
if err = op.CreateDir(dstDir, mode); err != nil {
return err
}
}
} else {
stat, ok := info.Sys().(*syscall.Stat_t)
if ok {
uid, gid = int(stat.Uid), int(stat.Gid)
}
}
dstFileName := filepath.Join(dstDir, fileName)
dstInfo, statErr := os.Stat(dstFileName)
if statErr == nil {
mode = dstInfo.Mode()
} else {
mode = 0644
}
targetFile, err := os.OpenFile(filepath.Join(dstDir, fileName), os.O_RDWR|os.O_CREATE, mode)
if overwrite {
_ = os.Remove(dstFileName)
}
targetFile, err := os.OpenFile(dstFileName, os.O_RDWR|os.O_CREATE, mode)
if err != nil {
return err
}
defer targetFile.Close()
for i := 0; i < chunkCount; i++ {
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i))
chunkData, err := os.ReadFile(chunkPath)
@@ -620,9 +651,13 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int)
if err != nil {
return err
}
_ = os.Remove(chunkPath)
}
if uid != -1 && gid != -1 {
_ = os.Chown(dstFileName, uid, gid)
}
return files.NewFileOp().DeleteDir(fileDir)
return nil
}
// @Tags File
@@ -704,7 +739,11 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
}
if chunkIndex+1 == chunkCount {
err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount)
overwrite := true
if ow := c.PostForm("overwrite"); ow != "" {
overwrite, _ = strconv.ParseBool(ow)
}
err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount, overwrite)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err))
return
+23
View File
@@ -94,6 +94,29 @@ func (b *BaseApi) OperatePortRule(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// OperateForwardRule
// @Tags Firewall
// @Summary Create group
// @Description 更新防火墙端口转发规则
// @Accept json
// @Param request body dto.ForwardRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/forward [post]
// @x-panel-log {"bodyKeys":["source_port"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则 [source_port]","formatEN":"update port forward rules [source_port]"}
func (b *BaseApi) OperateForwardRule(c *gin.Context) {
var req dto.ForwardRuleOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := firewallService.OperateForwardRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 创建防火墙 IP 规则
+199
View File
@@ -0,0 +1,199 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags FTP
// @Summary Load FTP base info
// @Description 获取 FTP 基础信息
// @Success 200 {object} dto.FtpBaseInfo
// @Security ApiKeyAuth
// @Router /toolbox/ftp/base [get]
func (b *BaseApi) LoadFtpBaseInfo(c *gin.Context) {
data, err := ftpService.LoadBaseInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags FTP
// @Summary Load FTP operation log
// @Description 获取 FTP 操作日志
// @Accept json
// @Param request body dto.FtpLogSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /toolbox/ftp/log/search [post]
func (b *BaseApi) LoadFtpLogInfo(c *gin.Context) {
var req dto.FtpLogSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := ftpService.LoadLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags FTP
// @Summary Operate FTP
// @Description 修改 FTP 状态
// @Accept json
// @Param request body dto.Operate true "request"
// @Security ApiKeyAuth
// @Router /toolbox/ftp/operate [post]
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] FTP","formatEN":"[operation] FTP"}
func (b *BaseApi) OperateFtp(c *gin.Context) {
var req dto.Operate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := ftpService.Operate(req.Operation); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags FTP
// @Summary Page FTP user
// @Description 获取 FTP 账户列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /toolbox/ftp/search [post]
func (b *BaseApi) SearchFtp(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := ftpService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags FTP
// @Summary Create FTP user
// @Description 创建 FTP 账户
// @Accept json
// @Param request body dto.FtpCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/ftp [post]
// @x-panel-log {"bodyKeys":["user", "path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 FTP 账户 [user][path]","formatEN":"create FTP [user][path]"}
func (b *BaseApi) CreateFtp(c *gin.Context) {
var req dto.FtpCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Password) != 0 {
pass, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = string(pass)
}
if _, err := ftpService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags FTP
// @Summary Delete FTP user
// @Description 删除 FTP 账户
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/ftp/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ftps","output_column":"user","output_value":"users"}],"formatZH":"删除 FTP 账户 [users]","formatEN":"delete FTP users [users]"}
func (b *BaseApi) DeleteFtp(c *gin.Context) {
var req dto.BatchDeleteReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := ftpService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags FTP
// @Summary Sync FTP user
// @Description 同步 FTP 账户
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/ftp/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 FTP 账户","formatEN":"sync FTP users"}
func (b *BaseApi) SyncFtp(c *gin.Context) {
if err := ftpService.Sync(); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags FTP
// @Summary Update FTP user
// @Description 修改 FTP 账户
// @Accept json
// @Param request body dto.FtpUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/ftp/update [post]
// @x-panel-log {"bodyKeys":["user", "path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 FTP 账户 [user][path]","formatEN":"update FTP [user][path]"}
func (b *BaseApi) UpdateFtp(c *gin.Context) {
var req dto.FtpUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Password) != 0 {
pass, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = string(pass)
}
if err := ftpService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+5
View File
@@ -133,3 +133,8 @@ func CheckBind(req interface{}, c *gin.Context) error {
}
return nil
}
func ErrResponse(ctx *gin.Context, code int) {
ctx.JSON(code, nil)
ctx.Abort()
}
+1 -1
View File
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
return
}
loc, _ := time.LoadLocation(common.LoadTimeZone())
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
+32
View File
@@ -1,6 +1,7 @@
package v1
import (
"encoding/base64"
"errors"
"os"
"path"
@@ -60,6 +61,37 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Update proxy setting
// @Description 服务器代理配置
// @Accept json
// @Param request body dto.ProxyUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/proxy/update [post]
// @x-panel-log {"bodyKeys":["proxyUrl","proxyPort"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"服务器代理配置 [proxyPort]:[proxyPort]","formatEN":"set proxy [proxyPort]:[proxyPort]."}
func (b *BaseApi) UpdateProxy(c *gin.Context) {
var req dto.ProxyUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.ProxyPasswd) != 0 && len(req.ProxyType) != 0 {
pass, err := base64.StdEncoding.DecodeString(req.ProxyPasswd)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.ProxyPasswd = string(pass)
}
if err := settingService.UpdateProxy(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Update system setting
// @Description 隐藏高级功能菜单
+4 -4
View File
@@ -99,12 +99,12 @@ func (b *BaseApi) UpdateSnapDescription(c *gin.Context) {
// @Summary Page system snapshot
// @Description 获取系统快照列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Param request body dto.PageSnapshot true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /settings/snapshot/search [post]
func (b *BaseApi) SearchSnapshot(c *gin.Context) {
var req dto.SearchWithPage
var req dto.PageSnapshot
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
@@ -168,13 +168,13 @@ func (b *BaseApi) RollbackSnapshot(c *gin.Context) {
// @Summary Delete system backup
// @Description 删除系统快照
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Param request body dto.SnapshotBatchDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"删除系统快照 [name]","formatEN":"Delete system backup [name]"}
func (b *BaseApi) DeleteSnapshot(c *gin.Context) {
var req dto.BatchDeleteReq
var req dto.SnapshotBatchDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
+40 -10
View File
@@ -3,11 +3,13 @@ package v1
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -77,6 +79,13 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
if global.CONF.System.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
@@ -86,23 +95,38 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
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")) {
name := c.Query("name")
from := c.Query("from")
commands := []string{"redis-cli"}
database, err := databaseService.Get(name)
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
return
}
defer wsConn.Close()
commands := []string{"redis-cli"}
if len(redisConf.Requirepass) != 0 {
commands = []string{"redis-cli", "-a", redisConf.Requirepass, "--no-auth-warning"}
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
return
}
name = redisInfo.ContainerName
if len(database.Password) != 0 {
commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
}
} else {
itemPort := fmt.Sprintf("%v", database.Port)
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort}
if len(database.Password) != 0 {
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"}
}
name = "1Panel-redis-cli-tools"
}
pidMap := loadMapFromDockerTop(redisConf.ContainerName)
itemCmds := append([]string{"exec", "-it", redisConf.ContainerName}, commands...)
pidMap := loadMapFromDockerTop(name)
itemCmds := append([]string{"exec", "-it", name}, commands...)
slave, err := terminal.NewCommand(itemCmds)
if wshandleError(wsConn, err) {
return
}
defer killBash(redisConf.ContainerName, strings.Join(commands, " "), pidMap)
defer killBash(name, strings.Join(commands, " "), pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
@@ -130,6 +154,12 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
}
defer wsConn.Close()
if global.CONF.System.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
containerID := c.Query("containerid")
command := c.Query("command")
user := c.Query("user")
+52
View File
@@ -1,6 +1,8 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
@@ -76,6 +78,14 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.FtpPassword) != 0 {
pass, err := base64.StdEncoding.DecodeString(req.FtpPassword)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.FtpPassword = string(pass)
}
err := websiteService.CreateWebsite(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -768,3 +778,45 @@ func (b *BaseApi) GetDirConfig(c *gin.Context) {
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Get default html
// @Description 获取默认 html
// @Accept json
// @Success 200 {object} response.FileInfo
// @Security ApiKeyAuth
// @Router /websites/default/html/:type [get]
func (b *BaseApi) GetDefaultHtml(c *gin.Context) {
resourceType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
fileInfo, err := websiteService.GetDefaultHtml(resourceType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, fileInfo)
}
// @Tags Website
// @Summary Update default html
// @Description 更新默认 html
// @Accept json
// @Param request body request.WebsiteHtmlUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/default/html/update [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新默认 html","formatEN":"Update default html"}
func (b *BaseApi) UpdateDefaultHtml(c *gin.Context) {
var req request.WebsiteHtmlUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.UpdateDefaultHtml(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+33
View File
@@ -6,6 +6,9 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
"strconv"
)
// @Tags Website CA
@@ -142,3 +145,33 @@ func (b *BaseApi) RenewWebsiteCA(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Website CA
// @Summary Download CA file
// @Description 下载 CA 证书文件
// @Accept json
// @Param request body request.WebsiteResourceReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ca/download [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"下载 CA 证书文件 [name]","formatEN":"download ca file [name]"}
func (b *BaseApi) DownloadCAFile(c *gin.Context) {
var req request.WebsiteResourceReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
file, err := websiteCAService.DownloadFile(req.ID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
defer file.Close()
info, err := file.Stat()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.Header("Content-Length", strconv.FormatInt(info.Size(), 10))
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
}
+1
View File
@@ -236,6 +236,7 @@ func (b *BaseApi) DownloadWebsiteSSL(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
defer file.Close()
info, err := file.Stat()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
+3 -2
View File
@@ -37,8 +37,9 @@ type AppOssConfig struct {
}
type AppVersion struct {
Version string `json:"version"`
DetailId uint `json:"detailId"`
Version string `json:"version"`
DetailId uint `json:"detailId"`
DockerCompose string `json:"dockerCompose"`
}
type AppList struct {
+3
View File
@@ -35,6 +35,8 @@ type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
FileName string `json:"fileName"`
}
type CommonRecover struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
@@ -42,6 +44,7 @@ type CommonRecover struct {
Name string `json:"name"`
DetailName string `json:"detailName"`
File string `json:"file"`
Secret string `json:"secret"`
}
type RecordSearch struct {
+96
View File
@@ -0,0 +1,96 @@
package dto
import (
"time"
)
type SearchClamWithPage struct {
PageInfo
Info string `json:"info"`
OrderBy string `json:"orderBy" validate:"required,oneof=name status created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type ClamBaseInfo struct {
Version string `json:"version"`
IsActive bool `json:"isActive"`
IsExist bool `json:"isExist"`
FreshVersion string `json:"freshVersion"`
FreshIsActive bool `json:"freshIsActive"`
FreshIsExist bool `json:"freshIsExist"`
}
type ClamInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
Status string `json:"status"`
Path string `json:"path"`
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
LastHandleDate string `json:"lastHandleDate"`
Spec string `json:"spec"`
Description string `json:"description"`
}
type ClamLogSearch struct {
PageInfo
ClamID uint `json:"clamID"`
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
}
type ClamLogReq struct {
Tail string `json:"tail"`
ClamName string `json:"clamName"`
RecordName string `json:"recordName"`
}
type ClamFileReq struct {
Tail string `json:"tail"`
Name string `json:"name" validate:"required"`
}
type ClamLog struct {
Name string `json:"name"`
ScanDate string `json:"scanDate"`
ScanTime string `json:"scanTime"`
InfectedFiles string `json:"infectedFiles"`
TotalError string `json:"totalError"`
Status string `json:"status"`
}
type ClamCreate struct {
Name string `json:"name"`
Status string `json:"status"`
Path string `json:"path"`
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
}
type ClamUpdate struct {
ID uint `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
}
type ClamUpdateStatus struct {
ID uint `json:"id"`
Status string `json:"status"`
}
type ClamDelete struct {
RemoveRecord bool `json:"removeRecord"`
RemoveInfected bool `json:"removeInfected"`
Ids []uint `json:"ids" validate:"required"`
}
+9 -3
View File
@@ -1,9 +1,9 @@
package dto
type SearchCommandWithPage struct {
SearchWithPage
OrderBy string `json:"orderBy"`
Order string `json:"order"`
PageInfo
OrderBy string `json:"orderBy" validate:"required,oneof=name command created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
GroupID uint `json:"groupID"`
Info string `json:"info"`
Name string `json:"name"`
@@ -30,3 +30,9 @@ type CommandTree struct {
Label string `json:"label"`
Children []CommandInfo `json:"children"`
}
type RedisCommand struct {
ID uint `json:"id"`
Name string `json:"name"`
Command string `json:"command"`
}
+1 -3
View File
@@ -2,9 +2,7 @@ package dto
type SearchWithPage struct {
PageInfo
Info string `json:"info"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
Info string `json:"info"`
}
type PageInfo struct {
+35 -14
View File
@@ -1,13 +1,15 @@
package dto
import "time"
import (
"time"
)
type PageContainer struct {
PageInfo
Name string `json:"name"`
State string `json:"state" validate:"required,oneof=all created running paused restarting removing exited dead"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
OrderBy string `json:"orderBy" validate:"required,oneof=name state created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
Filters string `json:"filters"`
ExcludeAppStore bool `json:"excludeAppStore"`
}
@@ -114,7 +116,7 @@ type PortHelper struct {
type ContainerOperation struct {
Names []string `json:"names" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause remove"`
Operation string `json:"operation" validate:"required,oneof=up start stop restart kill pause unpause remove"`
}
type ContainerRename struct {
@@ -122,8 +124,17 @@ type ContainerRename struct {
NewName string `json:"newName" validate:"required"`
}
type ContainerCommit struct {
ContainerId string `json:"containerID" validate:"required"`
ContainerName string `json:"containerName"`
NewImageName string `json:"newImageName"`
Comment string `json:"comment"`
Author string `json:"author"`
Pause bool `json:"pause"`
}
type ContainerPrune struct {
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network buildcache"`
WithTagAll bool `json:"withTagAll"`
}
@@ -189,6 +200,7 @@ type ComposeInfo struct {
Workdir string `json:"workdir"`
Path string `json:"path"`
Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
}
type ComposeContainer struct {
ContainerID string `json:"containerID"`
@@ -197,20 +209,29 @@ type ComposeContainer struct {
State string `json:"state"`
}
type ComposeCreate struct {
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Env []string `json:"env"`
}
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop down"`
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
WithFile bool `json:"withFile"`
}
type ComposeUpdate struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Env []string `json:"env"`
}
type ContainerLog struct {
Container string `json:"container" validate:"required"`
Since string `json:"since"`
Tail uint `json:"tail"`
ContainerType string `json:"containerType"`
}
+21 -9
View File
@@ -1,6 +1,15 @@
package dto
import "time"
import (
"time"
)
type PageCronjob struct {
PageInfo
Info string `json:"info"`
OrderBy string `json:"orderBy" validate:"required,oneof=name status created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type CronjobCreate struct {
Name string `json:"name" validate:"required"`
@@ -21,6 +30,7 @@ type CronjobCreate struct {
BackupAccounts string `json:"backupAccounts"`
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
}
type CronjobUpdate struct {
@@ -42,6 +52,7 @@ type CronjobUpdate struct {
BackupAccounts string `json:"backupAccounts"`
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
}
type CronjobUpdateStatus struct {
@@ -87,6 +98,7 @@ type CronjobInfo struct {
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
Secret string `json:"secret"`
}
type SearchRecord struct {
@@ -98,12 +110,12 @@ type SearchRecord struct {
}
type Record struct {
ID uint `json:"id"`
StartTime time.Time `json:"startTime"`
Records string `json:"records"`
Status string `json:"status"`
Message string `json:"message"`
TargetPath string `json:"targetPath"`
Interval int `json:"interval"`
File string `json:"file"`
ID uint `json:"id"`
StartTime string `json:"startTime"`
Records string `json:"records"`
Status string `json:"status"`
Message string `json:"message"`
TargetPath string `json:"targetPath"`
Interval int `json:"interval"`
File string `json:"file"`
}
+24 -4
View File
@@ -16,10 +16,11 @@ type DashboardBase struct {
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
VirtualizationSystem string `json:"virtualizationSystem"`
CPUCores int `json:"cpuCores"`
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
IpV4Addr string `json:"ipv4Addr"`
SystemProxy string `json:"SystemProxy"`
CPUCores int `json:"cpuCores"`
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
@@ -30,6 +31,14 @@ type OsInfo struct {
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
DiskSize int64 `json:"diskSize"`
}
type DashboardReq struct {
Scope string `json:"scope"`
IoOption string `json:"ioOption"`
NetOption string `json:"netOption"`
}
type DashboardCurrent struct {
@@ -70,6 +79,7 @@ type DashboardCurrent struct {
NetBytesRecv uint64 `json:"netBytesRecv"`
GPUData []GPUInfo `json:"gpuData"`
XPUData []XPUInfo `json:"xpuData"`
ShotTime time.Time `json:"shotTime"`
}
@@ -103,3 +113,13 @@ type GPUInfo struct {
MemTotal string `json:"memTotal"`
FanSpeed string `json:"fanSpeed"`
}
type XPUInfo struct {
DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"`
Memory string `json:"memory"`
Temperature string `json:"temperature"`
MemoryUsed string `json:"memoryUsed"`
Power string `json:"power"`
MemoryUtil string `json:"memoryUtil"`
}
+19 -15
View File
@@ -27,8 +27,8 @@ type MysqlDBSearch struct {
PageInfo
Info string `json:"info"`
Database string `json:"database" validate:"required"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type MysqlDBInfo struct {
@@ -131,7 +131,7 @@ type MysqlStatus struct {
}
type MysqlVariables struct {
BinlogCacheSize string `json:"binlog_cache_size"`
BinlogCacheSize string `json:"binlog_cache_size"`
InnodbBufferPoolSize string `json:"innodb_buffer_pool_size"`
InnodbLogBufferSize string `json:"innodb_log_buffer_size"`
JoinBufferSize string `json:"join_buffer_size"`
@@ -139,7 +139,7 @@ type MysqlVariables struct {
MaxConnections string `json:"max_connections"`
MaxHeapTableSize string `json:"max_heap_table_size"`
QueryCacheSize string `json:"query_cache_size"`
QueryCache_type string `json:"query_cache_type"`
QueryCacheType string `json:"query_cache_type"`
ReadBufferSize string `json:"read_buffer_size"`
ReadRndBufferSize string `json:"read_rnd_buffer_size"`
SortBufferSize string `json:"sort_buffer_size"`
@@ -165,26 +165,26 @@ type MysqlVariablesUpdateHelper struct {
// redis
type ChangeRedisPass struct {
Value string `json:"value" validate:"required"`
Database string `json:"database" validate:"required"`
Value string `json:"value"`
}
type RedisConfUpdate struct {
Database string `json:"database" validate:"required"`
Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"`
Maxmemory string `json:"maxmemory"`
}
type RedisConfPersistenceUpdate struct {
Database string `json:"database" validate:"required"`
Type string `json:"type" validate:"required,oneof=aof rbd"`
Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"`
Save string `json:"save"`
}
type RedisConfUpdateByFile struct {
File string `json:"file" validate:"required"`
RestartNow bool `json:"restartNow"`
}
type RedisConf struct {
Database string `json:"database" validate:"required"`
Name string `json:"name"`
Port int64 `json:"port"`
ContainerName string `json:"containerName"`
@@ -195,18 +195,20 @@ type RedisConf struct {
}
type RedisPersistence struct {
Database string `json:"database" validate:"required"`
Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"`
Save string `json:"save"`
}
type RedisStatus struct {
Database string `json:"database" validate:"required"`
TcpPort string `json:"tcp_port"`
UptimeInDays string `json:"uptime_in_days"`
ConnectedClients string `json:"connected_clients"`
UsedMemory string `json:"used_memory"`
UsedMemory_rss string `json:"used_memory_rss"`
UsedMemory_peak string `json:"used_memory_peak"`
UsedMemoryRss string `json:"used_memory_rss"`
UsedMemoryPeak string `json:"used_memory_peak"`
MemFragmentationRatio string `json:"mem_fragmentation_ratio"`
TotalConnectionsReceived string `json:"total_connections_received"`
TotalCommandsProcessed string `json:"total_commands_processed"`
@@ -217,12 +219,14 @@ type RedisStatus struct {
}
type DatabaseFileRecords struct {
Database string `json:"database" validate:"required"`
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
CreatedAt string `json:"createdAt"`
Size int `json:"size"`
}
type RedisBackupRecover struct {
Database string `json:"database" validate:"required"`
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
}
@@ -232,8 +236,8 @@ type DatabaseSearch struct {
PageInfo
Info string `json:"info"`
Type string `json:"type"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type DatabaseInfo struct {
@@ -281,7 +285,7 @@ type DatabaseCreate struct {
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Password string `json:"password"`
SSL bool `json:"ssl"`
RootCert string `json:"rootCert"`
@@ -299,7 +303,7 @@ type DatabaseUpdate struct {
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Password string `json:"password"`
SSL bool `json:"ssl"`
RootCert string `json:"rootCert"`
+2 -2
View File
@@ -6,8 +6,8 @@ type PostgresqlDBSearch struct {
PageInfo
Info string `json:"info"`
Database string `json:"database" validate:"required"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type PostgresqlDBInfo struct {
+11
View File
@@ -29,6 +29,17 @@ type PortRuleOperate struct {
Description string `json:"description"`
}
type ForwardRuleOperate struct {
Rules []struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Num string `json:"num"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
Port string `json:"port" validate:"required"`
TargetIP string `json:"targetIP"`
TargetPort string `json:"targetPort" validate:"required"`
} `json:"rules"`
}
type UpdateFirewallDescription struct {
Type string `json:"type"`
Address string `json:"address"`
+43
View File
@@ -0,0 +1,43 @@
package dto
import (
"time"
)
type FtpInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
User string `json:"user"`
Password string `json:"password"`
Path string `json:"path"`
Status string `json:"status"`
Description string `json:"description"`
}
type FtpBaseInfo struct {
IsActive bool `json:"isActive"`
IsExist bool `json:"isExist"`
}
type FtpLogSearch struct {
PageInfo
User string `json:"user"`
Operation string `json:"operation"`
}
type FtpCreate struct {
User string `json:"user" validate:"required"`
Password string `json:"password" validate:"required"`
Path string `json:"path" validate:"required"`
Description string `json:"description"`
}
type FtpUpdate struct {
ID uint `json:"id"`
Password string `json:"password" validate:"required"`
Path string `json:"path" validate:"required"`
Status string `json:"status"`
Description string `json:"description"`
}
+24 -9
View File
@@ -43,6 +43,7 @@ type AppInstalledSearch struct {
Update bool `json:"update"`
Unused bool `json:"unused"`
All bool `json:"all"`
Sync bool `json:"sync"`
}
type AppInstalledInfo struct {
@@ -60,15 +61,24 @@ type AppBackupDelete struct {
}
type AppInstalledOperate struct {
InstallId uint `json:"installId" validate:"required"`
BackupId uint `json:"backupId"`
DetailId uint `json:"detailId"`
Operate constant.AppOperate `json:"operate" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
DeleteDB bool `json:"deleteDB"`
Backup bool `json:"backup"`
PullImage bool `json:"pullImage"`
InstallId uint `json:"installId" validate:"required"`
BackupId uint `json:"backupId"`
DetailId uint `json:"detailId"`
Operate constant.AppOperate `json:"operate" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
DeleteDB bool `json:"deleteDB"`
Backup bool `json:"backup"`
PullImage bool `json:"pullImage"`
DockerCompose string `json:"dockerCompose"`
}
type AppInstallUpgrade struct {
InstallID uint `json:"installId"`
DetailID uint `json:"detailId"`
Backup bool `json:"backup"`
PullImage bool `json:"pullImage"`
DockerCompose string `json:"dockerCompose"`
}
type AppInstalledUpdate struct {
@@ -87,3 +97,8 @@ type PortUpdate struct {
Name string `json:"name"`
Port int64 `json:"port"`
}
type AppUpdateVersion struct {
AppInstallID uint `json:"appInstallID" validate:"required"`
UpdateVersion string `json:"updateVersion"`
}
+8 -4
View File
@@ -10,7 +10,8 @@ type FileOption struct {
}
type FileContentReq struct {
Path string `json:"path" validate:"required"`
Path string `json:"path" validate:"required"`
IsDetail bool `json:"isDetail"`
}
type SearchUploadWithPage struct {
@@ -54,12 +55,14 @@ type FileCompress struct {
Type string `json:"type" validate:"required"`
Name string `json:"name" validate:"required"`
Replace bool `json:"replace"`
Secret string `json:"secret"`
}
type FileDeCompress struct {
Dst string `json:"dst" validate:"required"`
Type string `json:"type" validate:"required"`
Path string `json:"path" validate:"required"`
Dst string `json:"dst" validate:"required"`
Type string `json:"type" validate:"required"`
Path string `json:"path" validate:"required"`
Secret string `json:"secret"`
}
type FileEdit struct {
@@ -124,6 +127,7 @@ type FileReadByLineReq struct {
Type string `json:"type" validate:"required"`
ID uint `json:"ID"`
Name string `json:"name"`
Latest bool `json:"latest"`
}
type FileExistReq struct {
+1 -1
View File
@@ -27,7 +27,7 @@ type NginxRewriteReq struct {
type NginxRewriteUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Name string `json:"name" validate:"required"`
Content string `json:"content" validate:"required"`
Content string `json:"content"`
}
type NginxProxyUpdate struct {
+32 -17
View File
@@ -7,8 +7,8 @@ import (
type WebsiteSearch struct {
dto.PageInfo
Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
OrderBy string `json:"orderBy" validate:"required,oneof=primary_domain type status created_at expire_date"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
WebsiteGroupID uint `json:"websiteGroupId"`
}
@@ -27,6 +27,9 @@ type WebsiteCreate struct {
AppID uint `json:"appID"`
AppInstallID uint `json:"appInstallID"`
FtpUser string `json:"ftpUser"`
FtpPassword string `json:"ftpPassword"`
RuntimeID uint `json:"runtimeID"`
RuntimeConfig
}
@@ -52,7 +55,7 @@ type WebsiteUpdate struct {
ID uint `json:"id" validate:"required"`
PrimaryDomain string `json:"primaryDomain" validate:"required"`
Remark string `json:"remark"`
WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"`
WebsiteGroupID uint `json:"webSiteGroupID"`
ExpireDate string `json:"expireDate"`
IPV6 bool `json:"IPV6"`
}
@@ -120,6 +123,7 @@ type WebsiteHTTPSOp struct {
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
}
type WebsiteNginxUpdate struct {
@@ -171,20 +175,22 @@ type WebsiteUpdateDirPermission struct {
}
type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
}
type WebsiteProxyReq struct {
@@ -204,3 +210,12 @@ type WafWebsite struct {
Domains []string `json:"domains"`
Host []string `json:"host"`
}
type WebsiteHtmlReq struct {
Type string `json:"type" validate:"required"`
}
type WebsiteHtmlUpdate struct {
Type string `json:"type" validate:"required"`
Content string `json:"content" validate:"required"`
}
+7 -1
View File
@@ -24,6 +24,8 @@ type WebsiteSSLCreate struct {
SkipDNS bool `json:"skipDNS"`
Nameserver1 string `json:"nameserver1"`
Nameserver2 string `json:"nameserver2"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
}
type WebsiteDNSReq struct {
@@ -77,7 +79,7 @@ type WebsiteSSLUpdate struct {
PrimaryDomain string `json:"primaryDomain" validate:"required"`
OtherDomains string `json:"otherDomains"`
Provider string `json:"provider" validate:"required"`
AcmeAccountID uint `json:"acmeAccountId" validate:"required"`
AcmeAccountID uint `json:"acmeAccountId"`
DnsAccountID uint `json:"dnsAccountId"`
KeyType string `json:"keyType"`
Apply bool `json:"apply"`
@@ -87,6 +89,8 @@ type WebsiteSSLUpdate struct {
SkipDNS bool `json:"skipDNS"`
Nameserver1 string `json:"nameserver1"`
Nameserver2 string `json:"nameserver2"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
}
type WebsiteSSLUpload struct {
@@ -126,6 +130,8 @@ type WebsiteCAObtain struct {
Renew bool `json:"renew"`
SSLID uint `json:"sslID"`
Description string `json:"description"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
}
type WebsiteCARenew struct {
+48 -1
View File
@@ -10,7 +10,7 @@ import (
)
type AppRes struct {
Items []*AppDTO `json:"items"`
Items []*AppDto `json:"items"`
Total int64 `json:"total"`
}
@@ -28,6 +28,22 @@ type AppDTO struct {
Tags []model.Tag `json:"tags"`
}
type AppDto struct {
Name string `json:"name"`
Key string `json:"key"`
ID uint `json:"id"`
ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"`
Icon string `json:"icon"`
Type string `json:"type"`
Status string `json:"status"`
Resource string `json:"resource"`
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Limit int `json:"limit"`
Tags []model.Tag `json:"tags"`
}
type TagDTO struct {
model.Tag
}
@@ -72,7 +88,38 @@ type AppInstalledDTO struct {
Path string `json:"path"`
}
type AppDetail struct {
Website string `json:"website"`
Document string `json:"document"`
Github string `json:"github"`
}
type AppInstallDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
AppID uint `json:"appID"`
AppDetailID uint `json:"appDetailID"`
Version string `json:"version"`
Status string `json:"status"`
Message string `json:"message"`
HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
Path string `json:"path"`
CanUpdate bool `json:"canUpdate"`
Icon string `json:"icon"`
AppName string `json:"appName"`
Ready int `json:"ready"`
Total int `json:"total"`
AppKey string `json:"appKey"`
AppType string `json:"appType"`
AppStatus string `json:"appStatus"`
DockerCompose string `json:"dockerCompose"`
CreatedAt time.Time `json:"createdAt"`
App AppDetail `json:"app"`
}
type DatabaseConn struct {
Status string `json:"status"`
Username string `json:"username"`
Password string `json:"password"`
ContainerName string `json:"containerName"`
+11 -7
View File
@@ -15,10 +15,12 @@ type UploadInfo struct {
}
type FileTree struct {
ID string `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
Children []FileTree `json:"children"`
ID string `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
IsDir bool `json:"isDir"`
Extension string `json:"extension"`
Children []FileTree `json:"children"`
}
type DirSizeRes struct {
@@ -34,9 +36,11 @@ type FileWgetRes struct {
}
type FileLineContent struct {
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
Lines []string `json:"lines"`
}
type FileExist struct {
+26
View File
@@ -2,6 +2,7 @@ package response
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"time"
)
type WebsiteDTO struct {
@@ -11,6 +12,26 @@ type WebsiteDTO struct {
SitePath string `json:"sitePath"`
AppName string `json:"appName"`
RuntimeName string `json:"runtimeName"`
SiteDir string `gorm:"type:varchar;" json:"siteDir"`
}
type WebsiteRes struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Protocol string `json:"protocol"`
PrimaryDomain string `json:"primaryDomain"`
Type string `json:"type"`
Alias string `json:"alias"`
Remark string `json:"remark"`
Status string `json:"status"`
ExpireDate time.Time `json:"expireDate"`
SitePath string `json:"sitePath"`
AppName string `json:"appName"`
RuntimeName string `json:"runtimeName"`
SSLExpireDate time.Time `json:"sslExpireDate"`
SSLStatus string `json:"sslStatus"`
AppInstallID uint `json:"appInstallId"`
RuntimeType string `json:"runtimeType"`
}
type WebsiteOption struct {
@@ -37,6 +58,7 @@ type WebsiteHTTPS struct {
SSL model.WebsiteSSL `json:"SSL"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
}
type WebsiteLog struct {
@@ -62,3 +84,7 @@ type WebsiteDirConfig struct {
UserGroup string `json:"userGroup"`
Msg string `json:"msg"`
}
type WebsiteHtmlRes struct {
Content string `json:"content"`
}
+42 -3
View File
@@ -58,6 +58,13 @@ type SettingInfo struct {
SnapshotIgnore string `json:"snapshotIgnore"`
XpackHideMenu string `json:"xpackHideMenu"`
NoAuthSetting string `json:"noAuthSetting"`
ProxyUrl string `json:"proxyUrl"`
ProxyType string `json:"proxyType"`
ProxyPort string `json:"proxyPort"`
ProxyUser string `json:"proxyUser"`
ProxyPasswd string `json:"proxyPasswd"`
ProxyPasswdKeep string `json:"proxyPasswdKeep"`
}
type SettingUpdate struct {
@@ -91,6 +98,12 @@ type PortUpdate struct {
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
}
type PageSnapshot struct {
PageInfo
Info string `json:"info"`
OrderBy string `json:"orderBy" validate:"required,oneof=name created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type SnapshotStatus struct {
Panel string `json:"panel"`
PanelInfo string `json:"panelInfo"`
@@ -109,11 +122,17 @@ type SnapshotCreate struct {
From string `json:"from" validate:"required"`
DefaultDownload string `json:"defaultDownload" validate:"required"`
Description string `json:"description" validate:"max=256"`
Secret string `json:"secret"`
}
type SnapshotRecover struct {
IsNew bool `json:"isNew"`
ReDownload bool `json:"reDownload"`
ID uint `json:"id" validate:"required"`
IsNew bool `json:"isNew"`
ReDownload bool `json:"reDownload"`
ID uint `json:"id" validate:"required"`
Secret string `json:"secret"`
}
type SnapshotBatchDelete struct {
DeleteWithFile bool `json:"deleteWithFile"`
Ids []uint `json:"ids" validate:"required"`
}
type SnapshotImport struct {
From string `json:"from"`
@@ -130,6 +149,7 @@ type SnapshotInfo struct {
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
Version string `json:"version"`
Size int64 `json:"size"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`
@@ -160,11 +180,21 @@ type Upgrade struct {
Version string `json:"version" validate:"required"`
}
type ProxyUpdate struct {
ProxyUrl string `json:"proxyUrl"`
ProxyType string `json:"proxyType"`
ProxyPort string `json:"proxyPort"`
ProxyUser string `json:"proxyUser"`
ProxyPasswd string `json:"proxyPasswd"`
ProxyPasswdKeep string `json:"proxyPasswdKeep"`
}
type CleanData struct {
SystemClean []CleanTree `json:"systemClean"`
UploadClean []CleanTree `json:"uploadClean"`
DownloadClean []CleanTree `json:"downloadClean"`
SystemLogClean []CleanTree `json:"systemLogClean"`
ContainerClean []CleanTree `json:"containerClean"`
}
type CleanTree struct {
@@ -185,3 +215,12 @@ type Clean struct {
Name string `json:"name"`
Size uint64 `json:"size"`
}
type XpackHideMenu struct {
ID string `json:"id"`
Label string `json:"label"`
IsCheck bool `json:"isCheck"`
Title string `json:"title"`
Path string `json:"path,omitempty"`
Children []XpackHideMenu `json:"children,omitempty"`
}
+1 -1
View File
@@ -16,7 +16,7 @@ type App struct {
Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
Required string `json:"required" gorm:"type:varchar(64);"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
Limit int `json:"limit" gorm:"type:Integer;not null"`
Website string `json:"website" gorm:"type:varchar(64);not null"`
Github string `json:"github" gorm:"type:varchar(64);not null"`
+14
View File
@@ -0,0 +1,14 @@
package model
type Clam struct {
BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"`
Status string `gorm:"type:varchar(64)" json:"status"`
Path string `gorm:"type:varchar(64);not null" json:"path"`
InfectedStrategy string `gorm:"type:varchar(64)" json:"infectedStrategy"`
InfectedDir string `gorm:"type:varchar(64)" json:"infectedDir"`
Spec string `gorm:"type:varchar(64)" json:"spec"`
EntryID int `gorm:"type:varchar(64)" json:"entryID"`
Description string `gorm:"type:varchar(64)" json:"description"`
}
+6
View File
@@ -6,3 +6,9 @@ type Command struct {
GroupID uint `gorm:"type:decimal" json:"groupID"`
Command string `gorm:"type:varchar(256);not null" json:"command"`
}
type RedisCommand struct {
BaseModel
Name string `gorm:"type:varchar(64);unique;not null" json:"name"`
Command string `gorm:"type:varchar(256);not null" json:"command"`
}
+1
View File
@@ -12,4 +12,5 @@ type Compose struct {
BaseModel
Name string `gorm:"type:varchar(256)" json:"name"`
Path string `gorm:"type:varchar(256)" json:"path"`
}
+4 -1
View File
@@ -1,6 +1,8 @@
package model
import "time"
import (
"time"
)
type Cronjob struct {
BaseModel
@@ -31,6 +33,7 @@ type Cronjob struct {
Status string `gorm:"type:varchar(64)" json:"status"`
EntryIDs string `gorm:"type:varchar(64)" json:"entryIDs"`
Records []JobRecords `json:"records"`
Secret string `gorm:"type:varchar(64)" json:"secret"`
}
type JobRecords struct {
+9
View File
@@ -10,3 +10,12 @@ type Firewall struct {
Strategy string `gorm:"type:varchar(64);not null" json:"strategy"`
Description string `gorm:"type:varchar(64);not null" json:"description"`
}
type Forward struct {
BaseModel
Protocol string `gorm:"type:varchar(64);not null" json:"protocol"`
Port string `gorm:"type:varchar(64);not null" json:"port"`
TargetIP string `gorm:"type:varchar(64);not null" json:"targetIP"`
TargetPort string `gorm:"type:varchar(64);not null" json:"targetPort"`
}
+11
View File
@@ -0,0 +1,11 @@
package model
type Ftp struct {
BaseModel
User string `gorm:"type:varchar(64);not null" json:"user"`
Password string `gorm:"type:varchar(64);not null" json:"password"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
Path string `gorm:"type:varchar(64);not null" json:"path"`
Description string `gorm:"type:varchar(64);not null" json:"description"`
}
+1
View File
@@ -26,6 +26,7 @@ type Website struct {
WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"`
RuntimeID uint `gorm:"type:integer" json:"runtimeID"`
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
FtpID uint `gorm:"type:integer" json:"ftpId"`
User string `gorm:"type:varchar;" json:"user"`
Group string `gorm:"type:varchar;" json:"group"`
+2
View File
@@ -33,6 +33,8 @@ type WebsiteSSL struct {
Nameserver1 string `json:"nameserver1"`
Nameserver2 string `json:"nameserver2"`
DisableCNAME bool `json:"disableCNAME"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"`
DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"`
+3
View File
@@ -3,6 +3,7 @@ package repo
import (
"context"
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/constant"
"gorm.io/gorm/clause"
@@ -160,6 +161,7 @@ func (a *AppInstallRepo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOp
type RootInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Port int64 `json:"port"`
HttpsPort int64 `json:"httpsPort"`
UserName string `json:"userName"`
@@ -234,5 +236,6 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
info.Key = app.Key
appInstall.App = app
info.AppPath = appInstall.GetAppPath()
info.Status = appInstall.Status
return &info, nil
}
+7
View File
@@ -25,6 +25,7 @@ type IBackupRepo interface {
WithByFileName(fileName string) DBOption
WithByType(backupType string) DBOption
WithByCronID(cronjobID uint) DBOption
WithFileNameStartWith(filePrefix string) DBOption
}
func NewIBackupRepo() IBackupRepo {
@@ -81,6 +82,12 @@ func (u *BackupRepo) WithByFileName(fileName string) DBOption {
}
}
func (u *BackupRepo) WithFileNameStartWith(filePrefix string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("file_name LIKE ?", filePrefix+"%")
}
}
func (u *BackupRepo) WithByType(backupType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(backupType) == 0 {
+69
View File
@@ -0,0 +1,69 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
)
type ClamRepo struct{}
type IClamRepo interface {
Page(limit, offset int, opts ...DBOption) (int64, []model.Clam, error)
Create(clam *model.Clam) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Clam, error)
List(opts ...DBOption) ([]model.Clam, error)
}
func NewIClamRepo() IClamRepo {
return &ClamRepo{}
}
func (u *ClamRepo) Get(opts ...DBOption) (model.Clam, error) {
var clam model.Clam
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&clam).Error
return clam, err
}
func (u *ClamRepo) List(opts ...DBOption) ([]model.Clam, error) {
var clam []model.Clam
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&clam).Error
return clam, err
}
func (u *ClamRepo) Page(page, size int, opts ...DBOption) (int64, []model.Clam, error) {
var users []model.Clam
db := global.DB.Model(&model.Clam{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
}
func (u *ClamRepo) Create(clam *model.Clam) error {
return global.DB.Create(clam).Error
}
func (u *ClamRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Clam{}).Where("id = ?", id).Updates(vars).Error
}
func (u *ClamRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.Clam{}).Error
}
+50
View File
@@ -17,6 +17,12 @@ type ICommandRepo interface {
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Command, error)
WithLikeName(name string) DBOption
PageRedis(limit, offset int, opts ...DBOption) (int64, []model.RedisCommand, error)
GetRedis(opts ...DBOption) (model.RedisCommand, error)
GetRedisList(opts ...DBOption) ([]model.RedisCommand, error)
SaveRedis(command *model.RedisCommand) error
DeleteRedis(opts ...DBOption) error
}
func NewICommandRepo() ICommandRepo {
@@ -33,6 +39,16 @@ func (u *CommandRepo) Get(opts ...DBOption) (model.Command, error) {
return command, err
}
func (u *CommandRepo) GetRedis(opts ...DBOption) (model.RedisCommand, error) {
var command model.RedisCommand
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&command).Error
return command, err
}
func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Command, error) {
var users []model.Command
db := global.DB.Model(&model.Command{})
@@ -45,6 +61,18 @@ func (u *CommandRepo) Page(page, size int, opts ...DBOption) (int64, []model.Com
return count, users, err
}
func (u *CommandRepo) PageRedis(page, size int, opts ...DBOption) (int64, []model.RedisCommand, error) {
var users []model.RedisCommand
db := global.DB.Model(&model.RedisCommand{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
}
func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) {
var commands []model.Command
db := global.DB.Model(&model.Command{})
@@ -55,6 +83,16 @@ func (u *CommandRepo) GetList(opts ...DBOption) ([]model.Command, error) {
return commands, err
}
func (u *CommandRepo) GetRedisList(opts ...DBOption) ([]model.RedisCommand, error) {
var commands []model.RedisCommand
db := global.DB.Model(&model.RedisCommand{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&commands).Error
return commands, err
}
func (c *CommandRepo) WithByInfo(info string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(info) == 0 {
@@ -69,6 +107,10 @@ func (u *CommandRepo) Create(command *model.Command) error {
return global.DB.Create(command).Error
}
func (u *CommandRepo) SaveRedis(command *model.RedisCommand) error {
return global.DB.Save(command).Error
}
func (u *CommandRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Command{}).Where("id = ?", id).Updates(vars).Error
}
@@ -81,6 +123,14 @@ func (u *CommandRepo) Delete(opts ...DBOption) error {
return db.Delete(&model.Command{}).Error
}
func (u *CommandRepo) DeleteRedis(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.RedisCommand{}).Error
}
func (a CommandRepo) WithLikeName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 {
+5
View File
@@ -19,6 +19,7 @@ type IComposeTemplateRepo interface {
CreateRecord(compose *model.Compose) error
DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error)
UpdateRecord(name string, vars map[string]interface{}) error
}
func NewIComposeTemplateRepo() IComposeTemplateRepo {
@@ -102,3 +103,7 @@ func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
}
return db.Delete(&model.Compose{}).Error
}
func (u *ComposeTemplateRepo) UpdateRecord(name string, vars map[string]interface{}) error {
return global.DB.Model(&model.Compose{}).Where("name = ?", name).Updates(vars).Error
}
+80
View File
@@ -0,0 +1,80 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
type FtpRepo struct{}
type IFtpRepo interface {
Get(opts ...DBOption) (model.Ftp, error)
GetList(opts ...DBOption) ([]model.Ftp, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.Ftp, error)
Create(ftp *model.Ftp) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
WithByUser(user string) DBOption
}
func NewIFtpRepo() IFtpRepo {
return &FtpRepo{}
}
func (u *FtpRepo) Get(opts ...DBOption) (model.Ftp, error) {
var ftp model.Ftp
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&ftp).Error
return ftp, err
}
func (h *FtpRepo) WithByUser(user string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(user) == 0 {
return g
}
return g.Where("user like ?", "%"+user+"%")
}
}
func (u *FtpRepo) GetList(opts ...DBOption) ([]model.Ftp, error) {
var ftps []model.Ftp
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&ftps).Error
return ftps, err
}
func (h *FtpRepo) Page(page, size int, opts ...DBOption) (int64, []model.Ftp, error) {
var users []model.Ftp
db := global.DB.Model(&model.Ftp{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
}
func (h *FtpRepo) Create(ftp *model.Ftp) error {
return global.DB.Create(ftp).Error
}
func (h *FtpRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Ftp{}).Where("id = ?", id).Updates(vars).Error
}
func (h *FtpRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.Ftp{}).Error
}
+1 -1
View File
@@ -96,7 +96,7 @@ func (w *WebsiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.Web
db := getDb(opts...).Model(&model.Website{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Preload("WebsiteSSL").Find(&websites).Error
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("WebsiteSSL").Find(&websites).Error
return count, websites, err
}
-1
View File
@@ -50,7 +50,6 @@ func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
return db.Where("ca_id = ?", caID)
}
}
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
var sslList []model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
+39 -45
View File
@@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
@@ -26,6 +25,8 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"gopkg.in/yaml.v3"
)
@@ -88,14 +89,22 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
if err != nil {
return nil, err
}
var appDTOs []*response.AppDTO
var appDTOs []*response.AppDto
for _, ap := range apps {
ap.ReadMe = ""
ap.Website = ""
ap.Document = ""
ap.Github = ""
appDTO := &response.AppDTO{
App: ap,
// ap.ReadMe = ""
// ap.Website = ""
// ap.Document = ""
// ap.Github = ""
appDTO := &response.AppDto{
ID: ap.ID,
Name: ap.Name,
Key: ap.Key,
Type: ap.Type,
Icon: ap.Icon,
ShortDescZh: ap.ShortDescZh,
ShortDescEn: ap.ShortDescEn,
Resource: ap.Resource,
Limit: ap.Limit,
}
appDTOs = append(appDTOs, appDTO)
appTags, err := appTagRepo.GetByAppId(ap.ID)
@@ -136,6 +145,9 @@ func (a AppService) GetAppTags() ([]response.TagDTO, error) {
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
var appDTO response.AppDTO
if key == "postgres" {
key = "postgresql"
}
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
@@ -176,7 +188,7 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
if !fileOp.Stat(versionPath) || detail.Update {
if err = downloadApp(app, detail, nil); err != nil {
if err = downloadApp(app, detail, nil); err != nil && !fileOp.Stat(versionPath) {
return appDetailDTO, err
}
}
@@ -226,21 +238,16 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
if appDetailDTO.DockerCompose == "" {
filename := filepath.Base(appDetailDTO.DownloadUrl)
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
composeRes, err := http.Get(dockerComposeUrl)
statusCode, composeRes, err := httpUtil.HandleGet(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
}
defer composeRes.Body.Close()
bodyContent, err := io.ReadAll(composeRes.Body)
if err != nil {
return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err)
if statusCode > 200 {
return appDetailDTO, buserr.WithDetail("ErrGetCompose", string(composeRes), err)
}
if composeRes.StatusCode > 200 {
return appDetailDTO, buserr.WithDetail("ErrGetCompose", string(bodyContent), err)
}
detail.DockerCompose = string(bodyContent)
detail.DockerCompose = string(composeRes)
_ = appDetailRepo.Update(context.Background(), detail)
appDetailDTO.DockerCompose = string(bodyContent)
appDetailDTO.DockerCompose = string(composeRes)
}
appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose)
@@ -363,7 +370,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
value, ok := composeMap["services"]
if !ok {
if !ok || value == nil {
err = buserr.New(constant.ErrFileParse)
return
}
@@ -440,6 +447,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
return
}
appInstall.Env = string(paramByte)
if err = appInstallRepo.Create(ctx, appInstall); err != nil {
return
}
@@ -449,7 +457,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
go func() {
defer func() {
if err != nil {
appInstall.Status = constant.Error
appInstall.Status = constant.UpErr
appInstall.Message = err.Error()
_ = appInstallRepo.Save(context.Background(), appInstall)
}
@@ -699,16 +707,11 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
}
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.System.AppRepo, global.CONF.System.Mode)
versionRes, err := http2.GetHttpRes(versionUrl)
_, versionRes, err := http2.HandleGet(versionUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return nil, err
}
defer versionRes.Body.Close()
body, err := io.ReadAll(versionRes.Body)
if err != nil {
return nil, err
}
lastModifiedStr := string(body)
lastModifiedStr := string(versionRes)
lastModified, err := strconv.Atoi(lastModifiedStr)
if err != nil {
return nil, err
@@ -753,10 +756,10 @@ func getAppFromRepo(downloadPath string) error {
global.LOG.Infof("[AppStore] download file from %s", downloadUrl)
fileOp := files.NewFileOp()
packagePath := filepath.Join(constant.ResourceDir, filepath.Base(downloadUrl))
if err := fileOp.DownloadFile(downloadUrl, packagePath); err != nil {
if err := fileOp.DownloadFileWithProxy(downloadUrl, packagePath); err != nil {
return err
}
if err := fileOp.Decompress(packagePath, constant.ResourceDir, files.SdkZip); err != nil {
if err := fileOp.Decompress(packagePath, constant.ResourceDir, files.SdkZip, ""); err != nil {
return err
}
defer func() {
@@ -834,25 +837,21 @@ func (a AppService) SyncAppListFromRemote() (err error) {
oldAppIds = append(oldAppIds, old.ID)
}
transport := xpack.LoadRequestTransport()
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode)
appsMap := getApps(oldApps, list.Apps)
global.LOG.Infof("Starting synchronization of application details...")
for _, l := range list.Apps {
app := appsMap[l.AppProperty.Key]
iconRes, err := http.Get(l.Icon)
if err != nil {
return err
}
body, err := io.ReadAll(iconRes.Body)
_, iconRes, err := httpUtil.HandleGetWithTransport(l.Icon, http.MethodGet, transport, constant.TimeOut20s)
if err != nil {
return err
}
iconStr := ""
if !strings.Contains(string(body), "xml") {
iconStr = base64.StdEncoding.EncodeToString(body)
if !strings.Contains(string(iconRes), "<xml>") {
iconStr = base64.StdEncoding.EncodeToString(iconRes)
}
_ = iconRes.Body.Close()
app.Icon = iconStr
app.TagsKey = l.AppProperty.Tags
@@ -872,16 +871,11 @@ func (a AppService) SyncAppListFromRemote() (err error) {
if _, ok := InitTypes[app.Type]; ok {
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
composeRes, err := http.Get(dockerComposeUrl)
_, composeRes, err := httpUtil.HandleGetWithTransport(dockerComposeUrl, http.MethodGet, transport, constant.TimeOut20s)
if err != nil {
return err
}
defer composeRes.Body.Close()
bodyContent, err := io.ReadAll(composeRes.Body)
if err != nil {
return err
}
detail.DockerCompose = string(bodyContent)
detail.DockerCompose = string(composeRes)
} else {
detail.DockerCompose = ""
}
+83 -82
View File
@@ -5,14 +5,18 @@ import (
"encoding/json"
"fmt"
"math"
"net/http"
"os"
"path"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/files"
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
"github.com/docker/docker/api/types"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/backend/utils/env"
@@ -39,17 +43,17 @@ type AppInstallService struct {
}
type IAppInstallService interface {
Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error)
Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error)
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
LoadPort(req dto.OperationWithNameAndType) (int64, error)
LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error)
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, 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)
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error)
GetParams(id uint) (*response.AppConfig, error)
ChangeAppPort(req request.PortUpdate) error
GetDefaultConfigByKey(key, name string) (string, error)
@@ -74,7 +78,7 @@ func (a *AppInstallService) GetInstallList() ([]dto.AppInstallInfo, error) {
return datas, nil
}
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error) {
var (
opts []repo.DBOption
total int64
@@ -118,7 +122,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
}
}
installDTOs, err := handleInstalled(installs, req.Update)
installDTOs, err := handleInstalled(installs, req.Update, req.Sync)
if err != nil {
return 0, nil, err
}
@@ -150,7 +154,7 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
return res, nil
}
if err = syncAppInstallStatus(&appInstall); err != nil {
if err = syncAppInstallStatus(&appInstall, false); err != nil {
return nil, err
}
@@ -182,6 +186,7 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
if err != nil {
return data, nil
}
data.Status = app.Status
data.Username = app.UserName
data.Password = app.Password
data.ServiceName = app.ServiceName
@@ -190,7 +195,7 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
return data, nil
}
func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) {
func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error) {
var (
installs []model.AppInstall
err error
@@ -220,7 +225,7 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]
}
}
return handleInstalled(installs, false)
return handleInstalled(installs, false, true)
}
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
@@ -240,28 +245,35 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
if err != nil {
return handleErr(install, err, out)
}
return syncAppInstallStatus(&install)
return syncAppInstallStatus(&install, false)
case constant.Stop:
out, err := compose.Stop(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncAppInstallStatus(&install)
return syncAppInstallStatus(&install, false)
case constant.Restart:
out, err := compose.Restart(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncAppInstallStatus(&install)
return syncAppInstallStatus(&install, false)
case constant.Delete:
if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete {
return err
}
return nil
case constant.Sync:
return syncAppInstallStatus(&install)
return syncAppInstallStatus(&install, true)
case constant.Upgrade:
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
upgradeReq := request.AppInstallUpgrade{
InstallID: install.ID,
DetailID: req.DetailId,
Backup: req.Backup,
PullImage: req.PullImage,
DockerCompose: req.DockerCompose,
}
return upgradeInstall(upgradeReq)
case constant.Reload:
return opNginx(install.ContainerName, constant.NginxReload)
default:
@@ -420,7 +432,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
continue
}
if !systemInit {
if err = syncAppInstallStatus(&i); err != nil {
if err = syncAppInstallStatus(&i, false); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
@@ -483,8 +495,8 @@ func (a *AppInstallService) GetServices(key string) ([]response.AppService, erro
return res, nil
}
func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, error) {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error) {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.AppInstallID))
var versions []dto.AppVersion
if err != nil {
return versions, err
@@ -505,9 +517,34 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
continue
}
if common.CompareVersion(detail.Version, install.Version) {
var newCompose string
if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() {
filename := filepath.Base(detail.DownloadUrl)
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
statusCode, composeRes, err := httpUtil.HandleGet(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return versions, err
}
if statusCode > 200 {
return versions, err
}
detail.DockerCompose = string(composeRes)
_ = appDetailRepo.Update(context.Background(), detail)
}
newCompose, err = getUpgradeCompose(install, detail)
if err != nil {
return versions, err
}
if app.Key == constant.AppMysql {
majorVersion := getMajorVersion(install.Version)
if !strings.HasPrefix(detail.Version, majorVersion) {
continue
}
}
versions = append(versions, dto.AppVersion{
Version: detail.Version,
DetailId: detail.ID,
Version: detail.Version,
DetailId: detail.ID,
DockerCompose: newCompose,
})
}
}
@@ -672,6 +709,22 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
}
}
appParam.Values = form.Values
} else if form.Type == "apps" {
if m, ok := form.Child.(map[string]interface{}); ok {
result := make(map[string]string)
for key, value := range m {
if strVal, ok := value.(string); ok {
result[key] = strVal
}
}
if envKey, ok := result["envKey"]; ok {
serviceName := envs[envKey]
if serviceName != nil {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string)))
appParam.ShowValue = appInstall.Name
}
}
}
}
params = append(params, appParam)
} else {
@@ -700,82 +753,30 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
return &res, nil
}
func syncAppInstallStatus(appInstall *model.AppInstall) error {
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
if appInstall.Status == constant.Installing || appInstall.Status == constant.Rebuilding || appInstall.Status == constant.Upgrading {
return nil
}
containerNames, err := getContainerNames(*appInstall)
if err != nil {
return err
}
cli, err := docker.NewClient()
if err != nil {
return err
}
defer cli.Close()
containers, err := cli.ListContainersByName(containerNames)
var (
containers []types.Container
containersMap map[string]types.Container
containerNames = strings.Split(appInstall.ContainerName, ",")
)
containers, err = cli.ListContainersByName(containerNames)
if err != nil {
return err
}
var (
runningCount int
exitedCount int
pausedCount int
exitedContainerNames []string
total = len(containerNames)
count = len(containers)
)
foundNames := make(map[string]bool)
containersMap = make(map[string]types.Container)
for _, con := range containers {
foundNames[con.Names[0]] = true
switch con.State {
case "exited":
exitedContainerNames = append(exitedContainerNames, strings.TrimPrefix(con.Names[0], "/"))
exitedCount++
case "running":
runningCount++
case "paused":
pausedCount++
}
containersMap[con.Names[0]] = con
}
var notFoundNames []string
for _, name := range containerNames {
if !foundNames["/"+name] {
notFoundNames = append(notFoundNames, name)
}
}
switch {
case count == 0:
if appInstall.Status != constant.Error {
appInstall.Status = constant.SyncErr
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
}
case exitedCount == total:
appInstall.Status = constant.Stopped
case runningCount == total:
appInstall.Status = constant.Running
case pausedCount == total:
appInstall.Status = constant.Paused
default:
var msg string
if exitedCount > 0 {
msg = buserr.WithName("ErrContainerMsg", strings.Join(exitedContainerNames, ",")).Error()
}
if len(notFoundNames) > 0 {
msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error()
}
if msg == "" {
msg = buserr.New("ErrAppWarn").Error()
}
appInstall.Message = msg
appInstall.Status = constant.UnHealthy
}
_ = appInstallRepo.Save(context.Background(), appInstall)
synAppInstall(containersMap, appInstall, force)
return nil
}
@@ -803,7 +804,7 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
}
case "port":
envKey = "PANEL_APP_PORT_HTTP="
case "user-password":
default:
envKey = "PANEL_DB_USER_PASSWORD="
}
files := strings.Split(string(lineBytes), "\n")
+298 -112
View File
@@ -14,7 +14,11 @@ import (
"regexp"
"strconv"
"strings"
"time"
"github.com/docker/docker/api/types"
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/docker/docker/api/types/container"
@@ -38,6 +42,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
composeV2 "github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
@@ -181,17 +186,18 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
}
case constant.AppRedis:
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.RedisAuthParam{
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
authParam := dto.RedisAuthParam{
RootPassword: "",
}
database.Password = password.(string)
if password != "" {
authParam.RootPassword = password.(string)
database.Password = password.(string)
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
}
}
if err := databaseRepo.Create(ctx, database); err != nil {
@@ -436,12 +442,65 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil {
return "", err
}
value, ok := composeMap["services"]
if !ok || value == nil {
return "", buserr.New(constant.ErrFileParse)
}
servicesMap := value.(map[string]interface{})
if len(servicesMap) == 1 {
index := 0
oldServiceName := ""
for k := range servicesMap {
oldServiceName = k
index++
if index > 0 {
break
}
}
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
if install.ServiceName != oldServiceName {
delete(servicesMap, oldServiceName)
}
}
envs := make(map[string]interface{})
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
return "", err
}
config := getAppCommonConfig(envs)
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
envs[constant.ContainerName] = install.ContainerName
}
config.Advanced = true
if err := addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); err != nil {
return "", err
}
paramByte, err := json.Marshal(envs)
if err != nil {
return "", err
}
install.Env = string(paramByte)
composeByte, err := yaml.Marshal(composeMap)
if err != nil {
return "", err
}
return string(composeByte), nil
}
func upgradeInstall(req request.AppInstallUpgrade) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallID))
if err != nil {
return err
}
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailID))
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.DetailID))
if err != nil {
return err
}
@@ -454,11 +513,23 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
var (
upErr error
backupFile string
preErr error
)
global.LOG.Infof(i18n.GetMsgByKey("UpgradeAppStart"))
if backup {
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name})
global.LOG.Infof(i18n.GetMsgWithName("UpgradeAppStart", install.Name, nil))
if req.Backup {
iBackUpService := NewIBackupService()
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupRecord, err := iBackUpService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
if err == nil {
backups, _ := iBackUpService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
if len(backups) > 3 {
backupsToDelete := backups[:len(backups)-3]
var deleteIDs []uint
for _, backup := range backupsToDelete {
deleteIDs = append(deleteIDs, backup.ID)
}
_ = iBackUpService.BatchDeleteRecord(deleteIDs)
}
localDir, err := loadLocalDir()
if err == nil {
backupFile = path.Join(localDir, backupRecord.FileDir, backupRecord.FileName)
@@ -473,38 +544,47 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
defer func() {
if upErr != nil {
global.LOG.Infof(i18n.GetMsgWithName("ErrAppUpgrade", install.Name, upErr))
if backup {
if req.Backup {
global.LOG.Infof(i18n.GetMsgWithName("AppRecover", install.Name, nil))
if err := NewIBackupService().AppRecover(dto.CommonRecover{Name: install.App.Key, DetailName: install.Name, Type: "app", Source: constant.ResourceLocal, File: backupFile}); err != nil {
global.LOG.Errorf("recover app [%s] [%s] failed %v", install.App.Key, install.Name, err)
}
}
existInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
existInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallID))
if existInstall.ID > 0 {
existInstall.Status = constant.UpgradeErr
existInstall.Message = upErr.Error()
_ = appInstallRepo.Save(context.Background(), &existInstall)
}
}
if preErr != nil {
global.LOG.Infof(i18n.GetMsgWithName("ErrAppUpgrade", install.Name, preErr))
existInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallID))
if existInstall.ID > 0 {
existInstall.Status = constant.UpgradeErr
existInstall.Message = preErr.Error()
_ = appInstallRepo.Save(context.Background(), &existInstall)
}
}
}()
fileOp := files.NewFileOp()
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
if install.App.Resource == constant.AppResourceRemote {
if upErr = downloadApp(install.App, detail, &install); upErr != nil {
if preErr = downloadApp(install.App, detail, &install); preErr != nil {
return
}
if detail.DockerCompose == "" {
composeDetail, err := fileOp.GetContent(path.Join(detailDir, "docker-compose.yml"))
if err != nil {
upErr = err
preErr = err
return
}
detail.DockerCompose = string(composeDetail)
_ = appDetailRepo.Update(context.Background(), detail)
}
go func() {
_, _ = http.Get(detail.DownloadCallBackUrl)
_, _, _ = httpUtil.HandleGet(detail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
}()
}
@@ -512,6 +592,32 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
}
content, err := fileOp.GetContent(install.GetEnvPath())
if err != nil {
preErr = err
return
}
if req.PullImage {
projectName := strings.ToLower(install.Name)
images, err := composeV2.GetDockerComposeImages(projectName, content, []byte(detail.DockerCompose))
if err != nil {
preErr = err
return
}
for _, image := range images {
global.LOG.Infof(i18n.GetMsgWithName("PullImageStart", image, nil))
if out, err := cmd.ExecWithTimeOut("docker pull "+image, 20*time.Minute); err != nil {
if out != "" {
err = errors.New(out)
}
preErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return
} else {
global.LOG.Infof(i18n.GetMsgByKey("PullImageSuccess"))
}
}
}
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
stdout, _ := command.CombinedOutput()
if stdout != nil {
@@ -526,88 +632,19 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
_, _ = scriptCmd.CombinedOutput()
}
composeMap := make(map[string]interface{})
if upErr = yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); upErr != nil {
return
}
value, ok := composeMap["services"]
if !ok {
upErr = buserr.New(constant.ErrFileParse)
return
}
servicesMap := value.(map[string]interface{})
if len(servicesMap) == 1 {
index := 0
oldServiceName := ""
for k := range servicesMap {
oldServiceName = k
index++
if index > 0 {
break
}
var newCompose string
if req.DockerCompose == "" {
newCompose, upErr = getUpgradeCompose(install, detail)
if upErr != nil {
return
}
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
if install.ServiceName != oldServiceName {
delete(servicesMap, oldServiceName)
}
}
envs := make(map[string]interface{})
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
return
}
config := getAppCommonConfig(envs)
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
envs[constant.ContainerName] = install.ContainerName
}
config.Advanced = true
if upErr = addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); upErr != nil {
return
}
paramByte, err := json.Marshal(envs)
if err != nil {
upErr = err
return
}
install.Env = string(paramByte)
composeByte, err := yaml.Marshal(composeMap)
if err != nil {
upErr = err
return
} else {
newCompose = req.DockerCompose
}
install.DockerCompose = string(composeByte)
install.DockerCompose = newCompose
install.Version = detail.Version
install.AppDetailId = detailID
content, err := fileOp.GetContent(install.GetEnvPath())
if err != nil {
upErr = err
return
}
if pullImage {
images, err := composeV2.GetDockerComposeImages(install.Name, content, []byte(detail.DockerCompose))
if err != nil {
upErr = err
return
}
dockerCli, err := composeV2.NewClient()
if err != nil {
upErr = err
return
}
defer dockerCli.Close()
for _, image := range images {
global.LOG.Infof(i18n.GetMsgWithName("PullImageStart", image, nil))
if err = dockerCli.PullImage(image, true); err != nil {
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return
} else {
global.LOG.Infof(i18n.GetMsgByKey("PullImageSuccess"))
}
}
}
install.AppDetailId = req.DetailID
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
@@ -617,6 +654,10 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
upErr = err
return
}
envs := make(map[string]interface{})
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
return
}
envParams := make(map[string]string, len(envs))
handleMap(envs, envParams)
if upErr = env.Write(envParams, install.GetEnvPath()); upErr != nil {
@@ -757,11 +798,11 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
}
}()
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
if err = fileOp.DownloadFileWithProxy(appDetail.DownloadUrl, filePath); err != nil {
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
return
}
if err = fileOp.Decompress(filePath, appResourceDir, files.SdkTarGz); err != nil {
if err = fileOp.Decompress(filePath, appResourceDir, files.SdkTarGz, ""); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
return
}
@@ -781,7 +822,7 @@ func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppIns
return
}
go func() {
_, _ = http.Get(appDetail.DownloadCallBackUrl)
_, _, _ = httpUtil.HandleGet(appDetail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
}()
}
appKey := app.Key
@@ -908,12 +949,19 @@ func upApp(appInstall *model.AppInstall, pullImages bool) {
return
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
appInstall.Status = constant.UpErr
} else {
appInstall.Status = constant.Running
}
exist, _ := appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID))
if exist.ID > 0 {
containerNames, err := getContainerNames(*appInstall)
if err != nil {
return
}
if len(containerNames) > 0 {
appInstall.ContainerName = strings.Join(containerNames, ",")
}
_ = appInstallRepo.Save(context.Background(), appInstall)
}
}
@@ -933,6 +981,12 @@ func rebuildApp(appInstall model.AppInstall) error {
_ = handleErr(appInstall, err, out)
return
}
containerNames, err := getContainerNames(appInstall)
if err != nil {
_ = handleErr(appInstall, err, out)
return
}
appInstall.ContainerName = strings.Join(containerNames, ",")
appInstall.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &appInstall)
@@ -1110,24 +1164,156 @@ func handleErr(install model.AppInstall, err error, out string) error {
install.Message = out
reErr = errors.New(out)
}
install.Status = constant.Error
install.Status = constant.UpErr
_ = appInstallRepo.Save(context.Background(), &install)
return reErr
}
func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) {
var res []response.AppInstalledDTO
func doNotNeedSync(installed model.AppInstall) bool {
return installed.Status == constant.Installing || installed.Status == constant.Rebuilding || installed.Status == constant.Upgrading ||
installed.Status == constant.Syncing
}
func synAppInstall(containers map[string]types.Container, appInstall *model.AppInstall, force bool) {
containerNames := strings.Split(appInstall.ContainerName, ",")
if len(containers) == 0 {
if appInstall.Status == constant.UpErr && !force {
return
}
appInstall.Status = constant.Error
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
_ = appInstallRepo.Save(context.Background(), appInstall)
return
}
notFoundNames := make([]string, 0)
exitNames := make([]string, 0)
exitedCount := 0
pausedCount := 0
runningCount := 0
total := len(containerNames)
for _, name := range containerNames {
if con, ok := containers["/"+name]; ok {
switch con.State {
case "exited":
exitedCount++
exitNames = append(exitNames, name)
case "running":
runningCount++
case "paused":
pausedCount++
}
} else {
notFoundNames = append(notFoundNames, name)
}
}
switch {
case exitedCount == total:
appInstall.Status = constant.Stopped
case runningCount == total:
appInstall.Status = constant.Running
case pausedCount == total:
appInstall.Status = constant.Paused
case len(notFoundNames) == total:
if appInstall.Status == constant.UpErr && !force {
return
}
appInstall.Status = constant.Error
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error()
default:
var msg string
if exitedCount > 0 {
msg = buserr.WithName("ErrContainerMsg", strings.Join(exitNames, ",")).Error()
}
if len(notFoundNames) > 0 {
msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error()
}
if msg == "" {
msg = buserr.New("ErrAppWarn").Error()
}
appInstall.Message = msg
appInstall.Status = constant.UnHealthy
}
_ = appInstallRepo.Save(context.Background(), appInstall)
}
func getMajorVersion(version string) string {
parts := strings.Split(version, ".")
if len(parts) >= 2 {
return parts[0] + "." + parts[1]
}
return version
}
func ignoreUpdate(installed model.AppInstall) bool {
if installed.App.Type == "php" || installed.Status == constant.Installing {
return true
}
if installed.App.Key == constant.AppMysql {
majorVersion := getMajorVersion(installed.Version)
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
for _, appDetail := range appDetails {
if strings.HasPrefix(appDetail.Version, majorVersion) && common.CompareVersion(appDetail.Version, installed.Version) {
return false
}
}
return true
}
return false
}
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
var (
res []response.AppInstallDTO
containersMap map[string]types.Container
)
if sync {
cli, err := docker.NewClient()
if err != nil {
return nil, err
}
defer cli.Close()
containers, err := cli.ListAllContainers()
if err != nil {
return nil, err
}
containersMap = make(map[string]types.Container, len(containers))
for _, contain := range containers {
containersMap[contain.Names[0]] = contain
}
}
for _, installed := range appInstallList {
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
if updated && ignoreUpdate(installed) {
continue
}
if err := syncAppInstallStatus(&installed); err != nil {
global.LOG.Error("sync app install status error : ", err)
if sync && !doNotNeedSync(installed) {
synAppInstall(containersMap, &installed, false)
}
installDTO := response.AppInstalledDTO{
AppInstall: installed,
Path: installed.GetPath(),
installDTO := response.AppInstallDTO{
ID: installed.ID,
Name: installed.Name,
AppID: installed.AppId,
AppDetailID: installed.AppDetailId,
Version: installed.Version,
Status: installed.Status,
Message: installed.Message,
HttpPort: installed.HttpPort,
HttpsPort: installed.HttpsPort,
Icon: installed.App.Icon,
AppName: installed.App.Name,
AppKey: installed.App.Key,
AppType: installed.App.Type,
Path: installed.GetPath(),
CreatedAt: installed.CreatedAt,
App: response.AppDetail{
Github: installed.App.Github,
Website: installed.App.Website,
Document: installed.App.Document,
},
}
if updated {
installDTO.DockerCompose = installed.DockerCompose
}
app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId))
if err != nil {
+57 -20
View File
@@ -37,8 +37,10 @@ type IBackupService interface {
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(ireq dto.BackupOperate) error
Delete(id uint) error
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
ListFiles(req dto.BackupSearchFile) []string
@@ -49,7 +51,7 @@ type IBackupService interface {
MysqlRecoverByUpload(req dto.CommonRecover) error
PostgresqlRecoverByUpload(req dto.CommonRecover) error
RedisBackup() error
RedisBackup(db dto.CommonBackup) error
RedisRecover(db dto.CommonRecover) error
WebsiteBackup(db dto.CommonBackup) error
@@ -99,6 +101,20 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
return total, datas, err
}
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
records, err := backupRepo.ListRecord(
commonRepo.WithOrderBy("created_at asc"),
commonRepo.WithByName(name),
commonRepo.WithByType("app"),
backupRepo.WithFileNameStartWith(fileName),
backupRepo.WithByDetailName(detailName),
)
if err != nil {
return nil, err
}
return records, err
}
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
total, records, err := backupRepo.PageRecord(
search.Page, search.PageSize,
@@ -260,6 +276,35 @@ func (u *BackupService) Delete(id uint) error {
return backupRepo.Delete(commonRepo.WithByID(id))
}
func (u *BackupService) DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error {
if !withDeleteFile {
return backupRepo.DeleteRecord(context.Background(), commonRepo.WithByType(backupType), commonRepo.WithByName(name), backupRepo.WithByDetailName(detailName))
}
records, err := backupRepo.ListRecord(commonRepo.WithByType(backupType), commonRepo.WithByName(name), backupRepo.WithByDetailName(detailName))
if err != nil {
return err
}
for _, record := range records {
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
global.LOG.Errorf("load backup account %s info from db failed, err: %v", record.Source, err)
continue
}
client, err := u.NewClient(&backupAccount)
if err != nil {
global.LOG.Errorf("new client for backup account %s failed, err: %v", record.Source, err)
continue
}
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
global.LOG.Errorf("remove file %s from %s failed, err: %v", path.Join(record.FileDir, record.FileName), record.Source, err)
}
_ = backupRepo.DeleteRecord(context.Background(), commonRepo.WithByID(record.ID))
}
return nil
}
func (u *BackupService) BatchDeleteRecord(ids []uint) error {
records, err := backupRepo.ListRecord(commonRepo.WithIdsIn(ids))
if err != nil {
@@ -334,7 +379,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
}
if err := copyDir(oldDir, dirStr); err != nil {
_ = backupRepo.Update(req.ID, (map[string]interface{}{"vars": oldVars}))
_ = backupRepo.Update(req.ID, map[string]interface{}{"vars": oldVars})
return err
}
global.CONF.System.Backup = dirStr
@@ -384,8 +429,6 @@ 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(backup.Type, varMap)
@@ -423,14 +466,13 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
}
token, refreshToken, err := client.RefreshToken("authorization_code", varMap)
refreshToken, err := client.RefreshToken("authorization_code", "refreshToken", varMap)
if err != nil {
return err
}
delete(varMap, "code")
backup.Credential = token
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
varMap["refresh_token"] = refreshToken
itemVars, err := json.Marshal(varMap)
if err != nil {
@@ -557,10 +599,10 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(string("1Panel 备份账号测试文件。\n"))
_, _ = write.WriteString(string("1Panel 備份賬號測試文件。\n"))
_, _ = write.WriteString(string("1Panel Backs up account test files.\n"))
_, _ = write.WriteString(string("1Panelアカウントのテストファイルをバックアップします。\n"))
_, _ = write.WriteString("1Panel 备份账号测试文件。\n")
_, _ = write.WriteString("1Panel 備份賬號測試文件。\n")
_, _ = write.WriteString("1Panel Backs up account test files.\n")
_, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n")
write.Flush()
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
@@ -569,7 +611,7 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
func StartRefreshOneDriveToken() {
service := NewIBackupService()
oneDriveCronID, err := global.Cron.AddJob("0 * * * *", service)
oneDriveCronID, err := global.Cron.AddJob("0 3 */31 * *", service)
if err != nil {
global.LOG.Errorf("can not add OneDrive corn job: %s", err.Error())
return
@@ -583,19 +625,15 @@ func (u *BackupService) Run() {
if backupItem.ID == 0 {
return
}
if len(backupItem.Credential) == 0 {
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
return
}
global.LOG.Info("start to refresh token of OneDrive ...")
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
return
}
token, refreshToken, err := client.RefreshToken("refresh_token", varMap)
refreshToken, err := client.RefreshToken("refresh_token", "refreshToken", varMap)
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
if err != nil {
varMap["refresh_status"] = constant.StatusFailed
varMap["refresh_msg"] = err.Error()
@@ -608,8 +646,7 @@ func (u *BackupService) Run() {
_ = global.DB.Model(&model.BackupAccount{}).
Where("id = ?", backupItem.ID).
Updates(map[string]interface{}{
"credential": token,
"vars": varsItem,
"vars": varsItem,
}).Error
global.LOG.Info("Successfully refreshed OneDrive token.")
}
+16 -13
View File
@@ -35,12 +35,15 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
if err != nil {
return nil, err
}
timeNow := time.Now().Format("20060102150405")
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
backupDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
if err := handleAppBackup(&install, backupDir, fileName, ""); err != nil {
fileName := req.FileName
if req.FileName == "" {
fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
}
if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil {
return nil, err
}
@@ -78,13 +81,13 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error {
if _, err := compose.Down(install.GetComposePath()); err != nil {
return err
}
if err := handleAppRecover(&install, req.File, false); err != nil {
if err := handleAppRecover(&install, req.File, false, req.Secret); err != nil {
return err
}
return nil
}
func handleAppBackup(install *model.AppInstall, backupDir, fileName string, excludes string) error {
func handleAppBackup(install *model.AppInstall, backupDir, fileName string, excludes string, secret string) error {
fileOp := files.NewFileOp()
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
if !fileOp.Stat(tmpDir) {
@@ -103,7 +106,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string, excl
}
appPath := install.GetPath()
if err := handleTar(appPath, tmpDir, "app.tar.gz", excludes); err != nil {
if err := handleTar(appPath, tmpDir, "app.tar.gz", excludes, ""); err != nil {
return err
}
@@ -129,16 +132,16 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string, excl
}
}
if err := handleTar(tmpDir, backupDir, fileName, ""); err != nil {
if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
return err
}
return nil
}
func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool) error {
func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool, secret string) error {
isOk := false
fileOp := files.NewFileOp()
if err := handleUnTar(recoverFile, path.Dir(recoverFile)); err != nil {
if err := handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil {
return err
}
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
@@ -163,14 +166,14 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}
if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format("20060102150405")))
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile), ""); err != nil {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile), "", ""); err != nil {
return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
}
defer func() {
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := handleAppRecover(install, rollbackFile, true); err != nil {
if err := handleAppRecover(install, rollbackFile, true, secret); err != nil {
global.LOG.Errorf("rollback app %s from %s failed, err: %v", install.Name, rollbackFile, err)
return
}
@@ -251,7 +254,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
_ = fileOp.Rename(appDir, backPath)
_ = fileOp.CreateDir(appDir, 0755)
if err := handleUnTar(tmpPath+"/app.tar.gz", install.GetAppPath()); err != nil {
if err := handleUnTar(tmpPath+"/app.tar.gz", install.GetAppPath(), ""); err != nil {
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
_ = fileOp.DeleteDir(appDir)
_ = fileOp.Rename(backPath, appDir)
+5 -4
View File
@@ -2,6 +2,7 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"os"
"path"
"path/filepath"
@@ -24,7 +25,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
return err
}
timeNow := time.Now().Format("20060102150405")
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName)
targetDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
@@ -59,14 +60,14 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
file := req.File
fileName := path.Base(req.File)
if strings.HasSuffix(fileName, ".tar.gz") {
fileNameItem := time.Now().Format("20060102150405")
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
}
}
if err := handleUnTar(req.File, dstDir); err != nil {
if err := handleUnTar(req.File, dstDir, ""); err != nil {
_ = os.RemoveAll(dstDir)
return err
}
@@ -142,7 +143,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
}
if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout)))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
Type: req.Type,
+5 -4
View File
@@ -2,6 +2,7 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"os"
"path"
"path/filepath"
@@ -25,7 +26,7 @@ func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
return err
}
timeNow := time.Now().Format("20060102150405")
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName)
targetDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
@@ -59,14 +60,14 @@ func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
file := req.File
fileName := path.Base(req.File)
if strings.HasSuffix(fileName, ".tar.gz") {
fileNameItem := time.Now().Format("20060102150405")
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
}
}
if err := handleUnTar(req.File, dstDir); err != nil {
if err := handleUnTar(req.File, dstDir, ""); err != nil {
_ = os.RemoveAll(dstDir)
return err
}
@@ -136,7 +137,7 @@ func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
defer cli.Close()
if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout)))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
TargetDir: path.Dir(rollbackFile),
+14 -13
View File
@@ -20,12 +20,12 @@ import (
"github.com/pkg/errors"
)
func (u *BackupService) RedisBackup() error {
func (u *BackupService) RedisBackup(db dto.CommonBackup) error {
localDir, err := loadLocalDir()
if err != nil {
return err
}
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", db.Name)
if err != nil {
return err
}
@@ -35,7 +35,7 @@ func (u *BackupService) RedisBackup() error {
}
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
timeNow := time.Now().Format("20060102150405") + common.RandStrAndNum(5)
timeNow := time.Now().Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
fileName := fmt.Sprintf("%s.rdb", timeNow)
if appendonly == "yes" {
if strings.HasPrefix(redisInfo.Version, "6.") {
@@ -46,11 +46,12 @@ func (u *BackupService) RedisBackup() error {
}
itemDir := fmt.Sprintf("database/redis/%s", redisInfo.Name)
backupDir := path.Join(localDir, itemDir)
if err := handleRedisBackup(redisInfo, backupDir, fileName); err != nil {
if err := handleRedisBackup(redisInfo, backupDir, fileName, db.Secret); err != nil {
return err
}
record := &model.BackupRecord{
Type: "redis",
Name: db.Name,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: itemDir,
@@ -64,18 +65,18 @@ func (u *BackupService) RedisBackup() error {
}
func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil {
return err
}
global.LOG.Infof("recover redis from backup file %s", req.File)
if err := handleRedisRecover(redisInfo, req.File, false); err != nil {
if err := handleRedisRecover(redisInfo, req.File, false, req.Secret); err != nil {
return err
}
return nil
}
func handleRedisBackup(redisInfo *repo.RootInfo, backupDir, fileName string) error {
func handleRedisBackup(redisInfo *repo.RootInfo, backupDir, fileName string, secret string) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(backupDir) {
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
@@ -90,7 +91,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, backupDir, fileName string) err
if strings.HasSuffix(fileName, ".tar.gz") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", constant.AppInstallDir, "redis", redisInfo.Name)
if err := handleTar(redisDataDir, backupDir, fileName, ""); err != nil {
if err := handleTar(redisDataDir, backupDir, fileName, "", secret); err != nil {
return err
}
return nil
@@ -110,7 +111,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, backupDir, fileName string) err
return nil
}
func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback bool) error {
func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback bool, secret string) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(recoverFile) {
return buserr.WithName("ErrFileNotFound", recoverFile)
@@ -145,14 +146,14 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
suffix = "tar.gz"
}
}
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format("20060102150405"), suffix))
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format(constant.DateTimeSlimLayout), suffix))
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile), secret); err != nil {
return fmt.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
}
defer func() {
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := handleRedisRecover(redisInfo, rollbackFile, true); err != nil {
if err := handleRedisRecover(redisInfo, rollbackFile, true, secret); err != nil {
global.LOG.Errorf("rollback redis from %s failed, err: %v", rollbackFile, err)
return
}
@@ -169,7 +170,7 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
}
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data", constant.AppInstallDir, "redis", redisInfo.Name)
if err := handleUnTar(recoverFile, redisDataDir); err != nil {
if err := handleUnTar(recoverFile, redisDataDir, secret); err != nil {
return err
}
} else {
+9 -9
View File
@@ -16,7 +16,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
func handleRuntimeBackup(runtime *model.Runtime, backupDir, fileName string, excludes string) error {
func handleRuntimeBackup(runtime *model.Runtime, backupDir, fileName string, excludes string, secret string) error {
fileOp := files.NewFileOp()
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
if !fileOp.Stat(tmpDir) {
@@ -35,19 +35,19 @@ func handleRuntimeBackup(runtime *model.Runtime, backupDir, fileName string, exc
}
appPath := runtime.GetPath()
if err := handleTar(appPath, tmpDir, "runtime.tar.gz", excludes); err != nil {
if err := handleTar(appPath, tmpDir, "runtime.tar.gz", excludes, secret); err != nil {
return err
}
if err := handleTar(tmpDir, backupDir, fileName, ""); err != nil {
if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
return err
}
return nil
}
func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback bool) error {
func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback bool, secret string) error {
isOk := false
fileOp := files.NewFileOp()
if err := handleUnTar(recoverFile, path.Dir(recoverFile)); err != nil {
if err := handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil {
return err
}
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
@@ -72,14 +72,14 @@ func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback
}
if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("runtime/%s_%s.tar.gz", runtime.Name, time.Now().Format("20060102150405")))
if err := handleRuntimeBackup(runtime, path.Dir(rollbackFile), path.Base(rollbackFile), ""); err != nil {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("runtime/%s_%s.tar.gz", runtime.Name, time.Now().Format(constant.DateTimeSlimLayout)))
if err := handleRuntimeBackup(runtime, path.Dir(rollbackFile), path.Base(rollbackFile), "", secret); err != nil {
return fmt.Errorf("backup runtime %s for rollback before recover failed, err: %v", runtime.Name, err)
}
defer func() {
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := handleRuntimeRecover(runtime, rollbackFile, true); err != nil {
if err := handleRuntimeRecover(runtime, rollbackFile, true, secret); err != nil {
global.LOG.Errorf("rollback runtime %s from %s failed, err: %v", runtime.Name, rollbackFile, err)
return
}
@@ -100,7 +100,7 @@ func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback
_ = fileOp.Rename(runtimeDir, backPath)
_ = fileOp.CreateDir(runtimeDir, 0755)
if err := handleUnTar(tmpPath+"/runtime.tar.gz", fmt.Sprintf("%s/%s", constant.RuntimeDir, runtime.Type)); err != nil {
if err := handleUnTar(tmpPath+"/runtime.tar.gz", fmt.Sprintf("%s/%s", constant.RuntimeDir, runtime.Type), secret); err != nil {
global.LOG.Errorf("handle recover from runtime.tar.gz failed, err: %v", err)
_ = fileOp.DeleteDir(runtimeDir)
_ = fileOp.Rename(backPath, runtimeDir)
+18 -18
View File
@@ -31,11 +31,11 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
return err
}
timeNow := time.Now().Format("20060102150405")
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
itemDir := fmt.Sprintf("website/%s", req.Name)
backupDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow+common.RandStrAndNum(5))
if err := handleWebsiteBackup(&website, backupDir, fileName, ""); err != nil {
if err := handleWebsiteBackup(&website, backupDir, fileName, "", req.Secret); err != nil {
return err
}
@@ -65,16 +65,16 @@ func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
return err
}
global.LOG.Infof("recover website %s from backup file %s", req.Name, req.File)
if err := handleWebsiteRecover(&website, req.File, false); err != nil {
if err := handleWebsiteRecover(&website, req.File, false, req.Secret); err != nil {
return err
}
return nil
}
func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool) error {
func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool, secret string) error {
fileOp := files.NewFileOp()
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
if err := handleUnTar(recoverFile, path.Dir(recoverFile)); err != nil {
if err := handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil {
return err
}
defer func() {
@@ -106,14 +106,14 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
isOk := false
if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format("20060102150405")))
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), ""); err != nil {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout)))
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", ""); err != nil {
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
}
defer func() {
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := handleWebsiteRecover(website, rollbackFile, true); err != nil {
if err := handleWebsiteRecover(website, rollbackFile, true, ""); err != nil {
global.LOG.Errorf("rollback website %s from %s failed, err: %v", website.Alias, rollbackFile, err)
return
}
@@ -141,7 +141,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
if err != nil {
return err
}
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true); err != nil {
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
return err
}
@@ -154,8 +154,8 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
if err != nil {
return err
}
if runtime.Type == constant.RuntimeNode {
if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true); err != nil {
if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo {
if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
return err
}
global.LOG.Info("put runtime.tar.gz into tmp dir successful")
@@ -163,7 +163,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
}
siteDir := fmt.Sprintf("%s/openresty/%s/www/sites", constant.AppInstallDir, nginxInfo.Name)
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir); err != nil {
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir, ""); err != nil {
global.LOG.Errorf("handle recover from web.tar.gz failed, err: %v", err)
return err
}
@@ -182,7 +182,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
return nil
}
func handleWebsiteBackup(website *model.Website, backupDir, fileName string, excludes string) error {
func handleWebsiteBackup(website *model.Website, backupDir, fileName string, excludes string, secret string) error {
fileOp := files.NewFileOp()
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
if !fileOp.Stat(tmpDir) {
@@ -216,7 +216,7 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string, exc
if err != nil {
return err
}
if err := handleAppBackup(&app, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes); err != nil {
if err := handleAppBackup(&app, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, ""); err != nil {
return err
}
global.LOG.Info("put app.tar.gz into tmp dir successful")
@@ -225,8 +225,8 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string, exc
if err != nil {
return err
}
if runtime.Type == constant.RuntimeNode {
if err := handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes); err != nil {
if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo {
if err := handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil {
return err
}
global.LOG.Info("put runtime.tar.gz into tmp dir successful")
@@ -234,11 +234,11 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string, exc
}
websiteDir := fmt.Sprintf("%s/openresty/%s/www/sites/%s", constant.AppInstallDir, nginxInfo.Name, website.Alias)
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), excludes); err != nil {
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), excludes, ""); err != nil {
return err
}
global.LOG.Info("put web.tar.gz into tmp dir successful, now start to tar tmp dir")
if err := handleTar(tmpDir, backupDir, fileName, ""); err != nil {
if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
return err
}
+587
View File
@@ -0,0 +1,587 @@
package service
import (
"bufio"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"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"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/jinzhu/copier"
"github.com/robfig/cron/v3"
"github.com/pkg/errors"
)
const (
clamServiceNameCentOs = "clamd@scan.service"
clamServiceNameUbuntu = "clamav-daemon.service"
freshClamService = "clamav-freshclam.service"
resultDir = "clamav"
)
type ClamService struct {
serviceName string
}
type IClamService interface {
LoadBaseInfo() (dto.ClamBaseInfo, error)
Operate(operate string) error
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
Create(req dto.ClamCreate) error
Update(req dto.ClamUpdate) error
UpdateStatus(id uint, status string) error
Delete(req dto.ClamDelete) error
HandleOnce(req dto.OperateByID) error
LoadFile(req dto.ClamFileReq) (string, error)
UpdateFile(req dto.UpdateByNameAndFile) error
LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error)
CleanRecord(req dto.OperateByID) error
LoadRecordLog(req dto.ClamLogReq) (string, error)
}
func NewIClamService() IClamService {
return &ClamService{}
}
func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
var baseInfo dto.ClamBaseInfo
baseInfo.Version = "-"
baseInfo.FreshVersion = "-"
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
c.serviceName = clamServiceNameCentOs
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
c.serviceName = clamServiceNameUbuntu
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
}
freshExist, _ := systemctl.IsExist(freshClamService)
if freshExist {
baseInfo.FreshIsExist = true
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
}
if !cmd.Which("clamdscan") {
baseInfo.IsActive = false
}
if baseInfo.IsActive {
version, err := cmd.Exec("clamdscan --version")
if err == nil {
if strings.Contains(version, "/") {
baseInfo.Version = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
} else {
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
}
}
} else {
_ = StopAllCronJob(false)
}
if baseInfo.FreshIsActive {
version, err := cmd.Exec("freshclam --version")
if err == nil {
if strings.Contains(version, "/") {
baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
} else {
baseInfo.FreshVersion = strings.TrimPrefix(version, "ClamAV ")
}
}
}
return baseInfo, nil
}
func (c *ClamService) Operate(operate string) error {
switch operate {
case "start", "restart", "stop":
stdout, err := cmd.Execf("systemctl %s %s", operate, c.serviceName)
if err != nil {
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
}
return nil
case "fresh-start", "fresh-restart", "fresh-stop":
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
if err != nil {
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
}
return nil
default:
return fmt.Errorf("not support such operation: %v", operate)
}
}
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if err != nil {
return 0, nil, err
}
var datas []dto.ClamInfo
for _, command := range commands {
var item dto.ClamInfo
if err := copier.Copy(&item, &command); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.LastHandleDate = "-"
datas = append(datas, item)
}
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for i := 0; i < len(datas); i++ {
logPaths := loadFileByName(datas[i].Name)
sort.Slice(logPaths, func(i, j int) bool {
return logPaths[i] > logPaths[j]
})
if len(logPaths) != 0 {
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, logPaths[0], nyc)
if err != nil {
continue
}
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
}
}
return total, datas, err
}
func (c *ClamService) Create(req dto.ClamCreate) error {
clam, _ := clamRepo.Get(commonRepo.WithByName(req.Name))
if clam.ID != 0 {
return constant.ErrRecordExist
}
if err := copier.Copy(&clam, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if clam.InfectedStrategy == "none" || clam.InfectedStrategy == "remove" {
clam.InfectedDir = ""
}
if len(req.Spec) != 0 {
entryID, err := xpack.StartClam(clam, false)
if err != nil {
return err
}
clam.EntryID = entryID
clam.Status = constant.StatusEnable
}
if err := clamRepo.Create(&clam); err != nil {
return err
}
return nil
}
func (c *ClamService) Update(req dto.ClamUpdate) error {
clam, _ := clamRepo.Get(commonRepo.WithByName(req.Name))
if clam.ID == 0 {
return constant.ErrRecordNotFound
}
if req.InfectedStrategy == "none" || req.InfectedStrategy == "remove" {
req.InfectedDir = ""
}
var clamItem model.Clam
if err := copier.Copy(&clamItem, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
clamItem.EntryID = clam.EntryID
upMap := map[string]interface{}{}
if len(clam.Spec) != 0 && clam.EntryID != 0 {
global.Cron.Remove(cron.EntryID(clamItem.EntryID))
upMap["entry_id"] = 0
}
if len(req.Spec) == 0 {
upMap["status"] = ""
upMap["entry_id"] = 0
}
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
newEntryID, err := xpack.StartClam(clamItem, true)
if err != nil {
return err
}
upMap["entry_id"] = newEntryID
}
if len(clam.Spec) == 0 && len(req.Spec) != 0 {
upMap["status"] = constant.StatusEnable
}
upMap["name"] = req.Name
upMap["path"] = req.Path
upMap["infected_dir"] = req.InfectedDir
upMap["infected_strategy"] = req.InfectedStrategy
upMap["spec"] = req.Spec
upMap["description"] = req.Description
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
}
return nil
}
func (c *ClamService) UpdateStatus(id uint, status string) error {
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
if clam.ID == 0 {
return constant.ErrRecordNotFound
}
var (
entryID int
err error
)
if status == constant.StatusEnable {
entryID, err = xpack.StartClam(clam, true)
if err != nil {
return err
}
} else {
global.Cron.Remove(cron.EntryID(clam.EntryID))
global.LOG.Infof("stop cronjob entryID: %v", clam.EntryID)
}
return clamRepo.Update(clam.ID, map[string]interface{}{"status": status, "entry_id": entryID})
}
func (c *ClamService) Delete(req dto.ClamDelete) error {
for _, id := range req.Ids {
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
if clam.ID == 0 {
continue
}
if req.RemoveRecord {
_ = os.RemoveAll(path.Join(global.CONF.System.DataDir, resultDir, clam.Name))
}
if req.RemoveInfected {
_ = os.RemoveAll(path.Join(clam.InfectedDir, "1panel-infected", clam.Name))
}
if err := clamRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
}
return nil
}
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
if cleaned := StopAllCronJob(true); cleaned {
return buserr.New("ErrClamdscanNotFound")
}
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
if clam.ID == 0 {
return constant.ErrRecordNotFound
}
if cmd.CheckIllegal(clam.Path) {
return buserr.New(constant.ErrCmdIllegal)
}
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
logFile := path.Join(global.CONF.System.DataDir, resultDir, clam.Name, timeNow)
if _, err := os.Stat(path.Dir(logFile)); err != nil {
_ = os.MkdirAll(path.Dir(logFile), os.ModePerm)
}
go func() {
strategy := ""
switch clam.InfectedStrategy {
case "remove":
strategy = "--remove"
case "move":
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
strategy = "--move=" + dir
if _, err := os.Stat(dir); err != nil {
_ = os.MkdirAll(dir, os.ModePerm)
}
case "copy":
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
strategy = "--copy=" + dir
if _, err := os.Stat(dir); err != nil {
_ = os.MkdirAll(dir, os.ModePerm)
}
}
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
stdout, err := cmd.Execf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
if err != nil {
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
}
}()
return nil
}
func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ClamID))
if clam.ID == 0 {
return 0, nil, constant.ErrRecordNotFound
}
logPaths := loadFileByName(clam.Name)
if len(logPaths) == 0 {
return 0, nil, nil
}
var filterFiles []string
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, item := range logPaths {
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
if err != nil {
continue
}
if t1.After(req.StartTime) && t1.Before(req.EndTime) {
filterFiles = append(filterFiles, item)
}
}
if len(filterFiles) == 0 {
return 0, nil, nil
}
sort.Slice(filterFiles, func(i, j int) bool {
return filterFiles[i] > filterFiles[j]
})
var records []string
total, start, end := len(filterFiles), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
records = make([]string, 0)
} else {
if end >= total {
end = total
}
records = filterFiles[start:end]
}
var datas []dto.ClamLog
for i := 0; i < len(records); i++ {
item := loadResultFromLog(path.Join(global.CONF.System.DataDir, resultDir, clam.Name, records[i]))
datas = append(datas, item)
}
return int64(total), datas, nil
}
func (c *ClamService) LoadRecordLog(req dto.ClamLogReq) (string, error) {
logPath := path.Join(global.CONF.System.DataDir, resultDir, req.ClamName, req.RecordName)
var tail string
if req.Tail != "0" {
tail = req.Tail
} else {
tail = "+1"
}
cmd := exec.Command("tail", "-n", tail, logPath)
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
}
return string(stdout), nil
}
func (c *ClamService) CleanRecord(req dto.OperateByID) error {
clam, _ := clamRepo.Get(commonRepo.WithByID(req.ID))
if clam.ID == 0 {
return constant.ErrRecordNotFound
}
pathItem := path.Join(global.CONF.System.DataDir, resultDir, clam.Name)
_ = os.RemoveAll(pathItem)
return nil
}
func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
filePath := ""
switch req.Name {
case "clamd":
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/clamd.conf"
} else {
filePath = "/etc/clamd.d/scan.conf"
}
case "clamd-log":
filePath = c.loadLogPath("clamd-log")
if len(filePath) != 0 {
break
}
if c.serviceName == clamServiceNameUbuntu {
filePath = "/var/log/clamav/clamav.log"
} else {
filePath = "/var/log/clamd.scan"
}
case "freshclam":
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
}
case "freshclam-log":
filePath = c.loadLogPath("freshclam-log")
if len(filePath) != 0 {
break
}
if c.serviceName == clamServiceNameUbuntu {
filePath = "/var/log/clamav/freshclam.log"
} else {
filePath = "/var/log/freshclam.log"
}
default:
return "", fmt.Errorf("not support such type")
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
var tail string
if req.Tail != "0" {
tail = req.Tail
} else {
tail = "+1"
}
cmd := exec.Command("tail", "-n", tail, filePath)
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
}
return string(stdout), nil
}
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
filePath := ""
service := ""
switch req.Name {
case "clamd":
if c.serviceName == clamServiceNameUbuntu {
service = clamServiceNameUbuntu
filePath = "/etc/clamav/clamd.conf"
} else {
service = clamServiceNameCentOs
filePath = "/etc/clamd.d/scan.conf"
}
case "freshclam":
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
}
service = "clamav-freshclam.service"
default:
return fmt.Errorf("not support such type")
}
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.File)
write.Flush()
_ = systemctl.Restart(service)
return nil
}
func StopAllCronJob(withCheck bool) bool {
if withCheck {
isActive := false
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
}
if isActive {
return false
}
}
clams, _ := clamRepo.List(commonRepo.WithByStatus(constant.StatusEnable))
for i := 0; i < len(clams); i++ {
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
}
return true
}
func loadFileByName(name string) []string {
var logPaths []string
pathItem := path.Join(global.CONF.System.DataDir, resultDir, name)
_ = filepath.Walk(pathItem, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() || info.Name() == name {
return nil
}
logPaths = append(logPaths, info.Name())
return nil
})
return logPaths
}
func loadResultFromLog(pathItem string) dto.ClamLog {
var data dto.ClamLog
data.Name = path.Base(pathItem)
data.Status = constant.StatusWaiting
file, err := os.ReadFile(pathItem)
if err != nil {
return data
}
lines := strings.Split(string(file), "\n")
for _, line := range lines {
if strings.Contains(line, "- SCAN SUMMARY -") {
data.Status = constant.StatusDone
}
if data.Status != constant.StatusDone {
continue
}
switch {
case strings.HasPrefix(line, "Infected files:"):
data.InfectedFiles = strings.TrimPrefix(line, "Infected files:")
case strings.HasPrefix(line, "Total errors:"):
data.TotalError = strings.TrimPrefix(line, "Total errors:")
case strings.HasPrefix(line, "Time:"):
if strings.Contains(line, "(") {
data.ScanTime = strings.ReplaceAll(strings.Split(line, "(")[1], ")", "")
continue
}
data.ScanTime = strings.TrimPrefix(line, "Time:")
case strings.HasPrefix(line, "Start Date:"):
data.ScanDate = strings.TrimPrefix(line, "Start Date:")
}
}
return data
}
func (c *ClamService) loadLogPath(name string) string {
confPath := ""
if name == "clamd-log" {
if c.serviceName == clamServiceNameUbuntu {
confPath = "/etc/clamav/clamd.conf"
} else {
confPath = "/etc/clamd.d/scan.conf"
}
} else {
if c.serviceName == clamServiceNameUbuntu {
confPath = "/etc/clamav/freshclam.conf"
} else {
confPath = "/etc/freshclam.conf"
}
}
if _, err := os.Stat(confPath); err != nil {
return ""
}
content, err := os.ReadFile(confPath)
if err != nil {
return ""
}
lines := strings.Split(string(content), "\n")
if name == "clamd-log" {
for _, line := range lines {
if strings.HasPrefix(line, "LogFile ") {
return strings.Trim(strings.ReplaceAll(line, "LogFile ", ""), " ")
}
}
} else {
for _, line := range lines {
if strings.HasPrefix(line, "UpdateLogFile ") {
return strings.Trim(strings.ReplaceAll(line, "UpdateLogFile ", ""), " ")
}
}
}
return ""
}
+66
View File
@@ -2,6 +2,7 @@ package service
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/jinzhu/copier"
"github.com/pkg/errors"
@@ -16,6 +17,11 @@ type ICommandService interface {
Create(commandDto dto.CommandOperate) error
Update(id uint, upMap map[string]interface{}) error
Delete(ids []uint) error
SearchRedisCommandWithPage(search dto.SearchWithPage) (int64, interface{}, error)
ListRedisCommand() ([]dto.RedisCommand, error)
SaveRedisCommand(commandDto dto.RedisCommand) error
DeleteRedisCommand(ids []uint) error
}
func NewICommandService() ICommandService {
@@ -115,3 +121,63 @@ func (u *CommandService) Delete(ids []uint) error {
func (u *CommandService) Update(id uint, upMap map[string]interface{}) error {
return commandRepo.Update(id, upMap)
}
func (u *CommandService) SearchRedisCommandWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, commands, err := commandRepo.PageRedis(search.Page, search.PageSize, commandRepo.WithLikeName(search.Info))
if err != nil {
return 0, nil, err
}
var dtoCommands []dto.RedisCommand
for _, command := range commands {
var item dto.RedisCommand
if err := copier.Copy(&item, &command); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtoCommands = append(dtoCommands, item)
}
return total, dtoCommands, err
}
func (u *CommandService) ListRedisCommand() ([]dto.RedisCommand, error) {
commands, err := commandRepo.GetRedisList()
if err != nil {
return nil, constant.ErrRecordNotFound
}
var dtoCommands []dto.RedisCommand
for _, command := range commands {
var item dto.RedisCommand
if err := copier.Copy(&item, &command); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtoCommands = append(dtoCommands, item)
}
return dtoCommands, err
}
func (u *CommandService) SaveRedisCommand(req dto.RedisCommand) error {
if req.ID == 0 {
command, _ := commandRepo.GetRedis(commonRepo.WithByName(req.Name))
if command.ID != 0 {
return constant.ErrRecordExist
}
}
var command model.RedisCommand
if err := copier.Copy(&command, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if err := commandRepo.SaveRedis(&command); err != nil {
return err
}
return nil
}
func (u *CommandService) DeleteRedisCommand(ids []uint) error {
if len(ids) == 1 {
command, _ := commandRepo.GetRedis(commonRepo.WithByID(ids[0]))
if command.ID == 0 {
return constant.ErrRecordNotFound
}
return commandRepo.DeleteRedis(commonRepo.WithByID(ids[0]))
}
return commandRepo.DeleteRedis(commonRepo.WithIdsIn(ids))
}
+202 -27
View File
@@ -1,11 +1,14 @@
package service
import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
@@ -17,6 +20,8 @@ import (
"time"
"unicode/utf8"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -29,6 +34,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/registry"
@@ -59,9 +65,11 @@ type IContainerService interface {
ContainerListStats() ([]dto.ContainerListStats, error)
LoadResourceLimit() (*dto.ResourceLimit, error)
ContainerRename(req dto.ContainerRename) error
ContainerCommit(req dto.ContainerCommit) error
ContainerLogClean(req dto.OperationWithName) error
ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error
DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error
ContainerStats(id string) (*dto.ContainerStats, error)
Inspect(req dto.InspectReq) (string, error)
DeleteNetwork(req dto.BatchDelete) error
@@ -179,16 +187,16 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
IsFromApp = true
}
ports := loadContainerPort(item.Ports)
exposePorts := transPortToStr(records[i].Ports)
info := dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
Ports: ports,
Ports: exposePorts,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
@@ -323,12 +331,28 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneRepo
}
report.DeletedNumber = len(rep.NetworksDeleted)
case "volume":
versions, err := client.ServerVersion(context.Background())
if err != nil {
return report, err
}
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
pruneFilters.Add("all", "true")
}
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.VolumesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
case "buildcache":
opts := types.BuildCachePruneOptions{}
opts.All = true
rep, err := client.BuildCachePrune(context.Background(), opts)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.CachesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
}
return report, nil
}
@@ -421,6 +445,8 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
}
}
exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
networkSettings := oldContainer.NetworkSettings
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
@@ -441,19 +467,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
for key, val := range oldContainer.Config.Labels {
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
}
for key, val := range oldContainer.HostConfig.PortBindings {
var itemPort dto.PortHelper
if !strings.Contains(string(key), "/") {
continue
}
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
itemPort.Protocol = strings.Split(string(key), "/")[1]
for _, binds := range val {
itemPort.HostIP = binds.HostIP
itemPort.HostPort = binds.HostPort
data.ExposedPorts = append(data.ExposedPorts, itemPort)
}
}
data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.Privileged = oldContainer.HostConfig.Privileged
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
@@ -580,6 +594,28 @@ func (u *ContainerService) ContainerRename(req dto.ContainerRename) error {
return client.ContainerRename(ctx, req.Name, req.NewName)
}
func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error {
ctx := context.Background()
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
options := container.CommitOptions{
Reference: req.NewImageName,
Comment: req.Comment,
Author: req.Author,
Changes: nil,
Pause: req.Pause,
Config: nil,
}
_, err = client.ContainerCommit(ctx, req.ContainerId, options)
if err != nil {
return fmt.Errorf("failed to commit container, err: %v", err)
}
return nil
}
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
var err error
ctx := context.Background()
@@ -729,6 +765,79 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
return nil
}
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error {
if cmd.CheckIllegal(container, since, tail) {
return buserr.New(constant.ErrCmdIllegal)
}
commandName := "docker"
commandArg := []string{"logs", container}
if containerType == "compose" {
commandName = "docker-compose"
commandArg = []string{"-f", container, "logs"}
}
if tail != "0" {
commandArg = append(commandArg, "--tail")
commandArg = append(commandArg, tail)
}
if since != "all" {
commandArg = append(commandArg, "--since")
commandArg = append(commandArg, since)
}
cmd := exec.Command(commandName, commandArg...)
stdout, err := cmd.StdoutPipe()
if err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
return err
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
return err
}
tempFile, err := os.CreateTemp("", "cmd_output_*.txt")
if err != nil {
return err
}
defer tempFile.Close()
defer func() {
if err := os.Remove(tempFile.Name()); err != nil {
global.LOG.Errorf("os.Remove() failed: %v", err)
}
}()
errCh := make(chan error)
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
if _, err := tempFile.WriteString(line + "\n"); err != nil {
errCh <- err
return
}
}
if err := scanner.Err(); err != nil {
errCh <- err
return
}
errCh <- nil
}()
select {
case err := <-errCh:
if err != nil {
global.LOG.Errorf("Error: %v", err)
}
case <-time.After(3 * time.Second):
global.LOG.Errorf("Timeout reached")
}
info, _ := tempFile.Stat()
c.Header("Content-Length", strconv.FormatInt(info.Size(), 10))
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), tempFile)
return nil
}
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
client, err := docker.NewDockerClient()
if err != nil {
@@ -789,6 +898,10 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
break
}
}
if len(containers) == 0 {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
filePath = composeItem.Path
}
}
if _, err := os.Stat(filePath); err != nil {
return ""
@@ -854,16 +967,15 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64)
return rx, tx
}
func checkImageExist(client *client.Client, image string) bool {
images, err := client.ImageList(context.Background(), types.ImageListOptions{})
func checkImageExist(client *client.Client, imageItem string) bool {
images, err := client.ImageList(context.Background(), image.ListOptions{})
if err != nil {
fmt.Println(err)
return false
}
for _, img := range images {
for _, tag := range img.RepoTags {
if tag == image || tag == image+":latest" {
if tag == imageItem || tag == imageItem+":latest" {
return true
}
}
@@ -871,12 +983,12 @@ func checkImageExist(client *client.Client, image string) bool {
return false
}
func pullImages(ctx context.Context, client *client.Client, image string) error {
options := types.ImagePullOptions{}
func pullImages(ctx context.Context, client *client.Client, imageName string) error {
options := image.PullOptions{}
repos, _ := imageRepoRepo.List()
if len(repos) != 0 {
for _, repo := range repos {
if strings.HasPrefix(image, repo.DownloadUrl) && repo.Auth {
if strings.HasPrefix(imageName, repo.DownloadUrl) && repo.Auth {
authConfig := registry.AuthConfig{
Username: repo.Username,
Password: repo.Password,
@@ -889,8 +1001,13 @@ func pullImages(ctx context.Context, client *client.Client, image string) error
options.RegistryAuth = authStr
}
}
} else {
hasAuth, authStr := loadAuthInfo(imageName)
if hasAuth {
options.RegistryAuth = authStr
}
}
out, err := client.ImagePull(ctx, image, options)
out, err := client.ImagePull(ctx, imageName, options)
if err != nil {
return err
}
@@ -1024,7 +1141,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
}
} else {
if req.Ipv4 != "" || req.Ipv6 != "" {
return nil, nil, nil, fmt.Errorf("Please set up the network")
return nil, nil, nil, fmt.Errorf("please set up the network")
}
networkConf = network.NetworkingConfig{}
}
@@ -1078,7 +1195,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
global.LOG.Errorf("restart after container update failed, err: %v", err)
}
global.LOG.Errorf("recreate after container update successful")
global.LOG.Info("recreate after container update successful")
}
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
@@ -1101,7 +1218,65 @@ func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
return datas
}
func loadContainerPort(ports []types.Port) []string {
func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) {
container, err := client.ContainerInspect(context.Background(), id)
if err != nil {
return nil, err
}
var itemPorts []types.Port
for key, val := range container.ContainerJSONBase.HostConfig.PortBindings {
if !strings.Contains(string(key), "/") {
continue
}
item := strings.Split(string(key), "/")
itemPort, _ := strconv.ParseUint(item[0], 10, 16)
for _, itemVal := range val {
publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16)
itemPorts = append(itemPorts, types.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
}
}
return itemPorts, nil
}
func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
var exposedPorts []dto.PortHelper
samePortMap := make(map[string]dto.PortHelper)
ports := transPortToStr(itemPorts)
var itemPort dto.PortHelper
for _, item := range ports {
itemStr := strings.Split(item, "->")
if len(itemStr) < 2 {
continue
}
lastIndex := strings.LastIndex(itemStr[0], ":")
if lastIndex == -1 {
itemPort.HostPort = itemStr[0]
} else {
itemPort.HostIP = itemStr[0][0:lastIndex]
itemPort.HostPort = itemStr[0][lastIndex+1:]
}
itemContainer := strings.Split(itemStr[1], "/")
if len(itemContainer) != 2 {
continue
}
itemPort.ContainerPort = itemContainer[0]
itemPort.Protocol = itemContainer[1]
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
if val, ok := samePortMap[keyItem]; ok {
val.HostIP = ""
samePortMap[keyItem] = val
} else {
samePortMap[keyItem] = itemPort
}
}
for _, val := range samePortMap {
exposedPorts = append(exposedPorts, val)
}
return exposedPorts
}
func transPortToStr(ports []types.Port) []string {
var (
ipv4Ports []types.Port
ipv6Ports []types.Port
+109 -21
View File
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
@@ -31,6 +32,12 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
const composeCreatedBy = "createdBy"
type DockerCompose struct {
Version string `yaml:"version"`
Services map[string]map[string]interface{} `yaml:"services"`
Networks map[string]interface{} `yaml:"networks"`
}
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
var (
records []dto.ComposeInfo
@@ -52,6 +59,20 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
composeCreatedByLocal, _ := composeRepo.ListRecord()
composeLocalMap := make(map[string]dto.ComposeInfo)
for _, localItem := range composeCreatedByLocal {
composeItemLocal := dto.ComposeInfo{
ContainerNumber: 0,
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
ConfigFile: localItem.Path,
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
}
composeItemLocal.CreatedBy = "1Panel"
composeItemLocal.Path = localItem.Path
composeLocalMap[localItem.Name] = composeItemLocal
}
composeMap := make(map[string]dto.ComposeInfo)
for _, container := range list {
if name, ok := container.Labels[composeProjectLabel]; ok {
@@ -59,7 +80,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
ContainerID: container.ID,
Name: container.Names[0][1:],
State: container.State,
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
CreateTime: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
}
if compose, has := composeMap[name]; has {
compose.ContainerNumber++
@@ -70,7 +91,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
workdir := container.Labels[composeWorkdirLabel]
composeItem := dto.ComposeInfo{
ContainerNumber: 1,
CreatedAt: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
CreatedAt: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
ConfigFile: config,
Workdir: workdir,
Containers: []dto.ComposeContainer{containerItem},
@@ -95,12 +116,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
}
}
for _, item := range composeCreatedByLocal {
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
global.LOG.Error(err)
mergedMap := make(map[string]dto.ComposeInfo)
for key, localItem := range composeLocalMap {
mergedMap[key] = localItem
}
for key, item := range composeMap {
if existingItem, exists := mergedMap[key]; exists {
if item.ContainerNumber > 0 {
if existingItem.ContainerNumber <= 0 {
mergedMap[key] = item
}
}
} else {
mergedMap[key] = item
}
}
for key, value := range composeMap {
for key, value := range mergedMap {
value.Name = key
records = append(records, value)
}
@@ -127,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
BackDatas = records[start:end]
}
return int64(total), BackDatas, nil
listItem := loadEnv(BackDatas)
return int64(total), listItem, nil
}
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
for i := 0; i < len(list); i++ {
envFilePath := filepath.Join(path.Dir(list[i].Path), "1panel.env")
file, err := os.ReadFile(envFilePath)
if err != nil {
continue
}
lines := strings.Split(string(file), "\n")
for _, line := range lines {
lineItem := strings.TrimSpace(line)
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
list[i].Env = append(list[i].Env, lineItem)
}
}
}
return list
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
@@ -141,6 +193,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := u.loadPath(&req); err != nil {
return false, err
}
if err := newComposeEnv(req.Path, req.Env); err != nil {
return false, err
}
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
@@ -168,11 +223,14 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
return "", err
}
}
logItem := fmt.Sprintf("%s/compose_create_%s_%s.log", dockerLogDir, req.Name, time.Now().Format("20060102150405"))
logItem := fmt.Sprintf("%s/compose_create_%s_%s.log", dockerLogDir, req.Name, time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
if err := newComposeEnv(req.Path, req.Env); err != nil {
return "", err
}
go func() {
defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
@@ -186,7 +244,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
return
}
global.LOG.Infof("docker-compose up %s successful!", req.Name)
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name, Path: req.Path})
_, _ = file.WriteString("docker-compose up successful!")
}()
@@ -200,17 +258,26 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
if req.Operation == "down" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
if req.Operation == "delete" {
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
return errors.New(string(stdout))
}
if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path))
}
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
return nil
}
if req.Operation == "up" {
if stdout, err := compose.Up(req.Path); err != nil {
return errors.New(string(stdout))
}
} else {
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
}
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
return nil
}
@@ -232,12 +299,10 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
if stdout, err := compose.Down(req.Path); err != nil {
if err := recreateCompose(string(oldFile), req.Path); err != nil {
return fmt.Errorf("update failed when handle compose down, err: %s, recreate failed: %v", string(stdout), err)
}
return fmt.Errorf("update failed when handle compose down, err: %s", string(stdout))
if err := newComposeEnv(req.Path, req.Env); err != nil {
return err
}
if stdout, err := compose.Up(req.Path); err != nil {
if err := recreateCompose(string(oldFile), req.Path); err != nil {
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
@@ -286,3 +351,26 @@ func recreateCompose(content, path string) error {
}
return nil
}
func newComposeEnv(pathItem string, env []string) error {
if len(env) == 0 {
return nil
}
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
global.LOG.Errorf("failed to create env file: %v", err)
return err
}
defer file.Close()
for _, env := range env {
envItem := strings.TrimSpace(env)
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
global.LOG.Errorf("failed to write env to file: %v", err)
return err
}
}
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
return nil
}
+1 -1
View File
@@ -52,7 +52,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
records = list.Volumes[start:end]
}
nyc, _ := time.LoadLocation(common.LoadTimeZone())
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, item := range records {
tag := make([]string, 0)
for _, val := range item.Labels {
@@ -21,7 +21,7 @@ import (
type CronjobService struct{}
type ICronjobService interface {
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
Create(cronjobDto dto.CronjobCreate) error
HandleOnce(id uint) error
@@ -29,7 +29,7 @@ type ICronjobService interface {
UpdateStatus(id uint, status string) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob) (string, error)
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
CleanRecord(req dto.CronjobClean) error
LoadRecordLog(req dto.OperateByID) string
@@ -39,7 +39,7 @@ func NewICronjobService() ICronjobService {
return &CronjobService{}
}
func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) {
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 {
@@ -49,7 +49,7 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
}
record, _ := cronjobRepo.RecordFirst(cronjob.ID)
if record.ID != 0 {
item.LastRecordTime = record.StartTime.Format("2006-01-02 15:04:05")
item.LastRecordTime = record.StartTime.Format(constant.DateTimeLayout)
} else {
item.LastRecordTime = "-"
}
@@ -71,6 +71,7 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
if err := copier.Copy(&item, &record); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.StartTime = record.StartTime.Format(constant.DateTimeLayout)
dtoCronjobs = append(dtoCronjobs, item)
}
return total, dtoCronjobs, err
@@ -172,6 +173,7 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
if cronjob.ID != 0 {
return constant.ErrRecordExist
}
cronjob.Secret = cronjobDto.Secret
if err := copier.Copy(&cronjob, &cronjobDto); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
@@ -179,7 +181,7 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
global.LOG.Infof("create cronjob %s successful, spec: %s", cronjob.Name, cronjob.Spec)
spec := cronjob.Spec
entryIDs, err := u.StartJob(&cronjob)
entryIDs, err := u.StartJob(&cronjob, false)
if err != nil {
return err
}
@@ -191,8 +193,8 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
return nil
}
func (u *CronjobService) StartJob(cronjob *model.Cronjob) (string, error) {
if len(cronjob.EntryIDs) != 0 {
func (u *CronjobService) StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error) {
if len(cronjob.EntryIDs) != 0 && isUpdate {
ids := strings.Split(cronjob.EntryIDs, ",")
for _, id := range ids {
idItem, _ := strconv.Atoi(id)
@@ -249,7 +251,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
cronjob.Type = cronModel.Type
spec := cronjob.Spec
if cronModel.Status == constant.StatusEnable {
newEntryIDs, err := u.StartJob(&cronjob)
newEntryIDs, err := u.StartJob(&cronjob, true)
if err != nil {
return err
}
@@ -278,6 +280,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["backup_accounts"] = req.BackupAccounts
upMap["default_download"] = req.DefaultDownload
upMap["retain_copies"] = req.RetainCopies
upMap["secret"] = req.Secret
return cronjobRepo.Update(id, upMap)
}
@@ -291,7 +294,7 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
err error
)
if status == constant.StatusEnable {
entryIDs, err = u.StartJob(&cronjob)
entryIDs, err = u.StartJob(&cronjob, false)
if err != nil {
return err
}
@@ -327,7 +330,7 @@ func mkdirAndWriteFile(cronjob *model.Cronjob, startTime time.Time, msg []byte)
}
}
path := fmt.Sprintf("%s/%s.log", dir, startTime.Format("20060102150405"))
path := fmt.Sprintf("%s/%s.log", dir, startTime.Format(constant.DateTimeSlimLayout))
global.LOG.Infof("cronjob %s has generated some logs %s", cronjob.Name, path)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
+48 -37
View File
@@ -17,15 +17,20 @@ import (
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
var apps []model.AppInstall
if cronjob.AppID == "all" {
if strings.Contains(cronjob.AppID, "all") {
apps, _ = appInstallRepo.ListBy()
} else {
itemID, _ := (strconv.Atoi(cronjob.AppID))
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
appIds := strings.Split(cronjob.AppID, ",")
var idItems []uint
for i := 0; i < len(appIds); i++ {
itemID, _ := strconv.Atoi(appIds[i])
idItems = append(idItems, uint(itemID))
}
appItems, err := appInstallRepo.ListBy(commonRepo.WithIdsIn(idItems))
if err != nil {
return err
}
apps = append(apps, app)
apps = appItems
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
@@ -40,8 +45,8 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
record.DetailName = app.Name
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
if err := handleAppBackup(&app, backupDir, record.FileName, cronjob.ExclusionRules); err != nil {
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
if err := handleAppBackup(&app, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
return err
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
@@ -73,8 +78,8 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
record.DetailName = web.Alias
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s", web.PrimaryDomain))
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format("20060102150405")+common.RandStrAndNum(5))
if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules); err != nil {
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
return err
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
@@ -107,7 +112,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if err := handleMysqlBackup(dbInfo.Database, dbInfo.DBType, dbInfo.Name, backupDir, record.FileName); err != nil {
return err
@@ -136,9 +141,9 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
if err != nil {
return err
}
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405")+common.RandStrAndNum(5))
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
return err
}
var record model.BackupRecord
@@ -166,10 +171,10 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
if err != nil {
return err
}
nameItem := startTime.Format("20060102150405") + common.RandStrAndNum(5)
nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem)
backupDir := path.Join(global.CONF.System.TmpDir, "log", nameItem)
if err := handleBackupLogs(backupDir, fileName); err != nil {
if err := handleBackupLogs(backupDir, fileName, cronjob.Secret); err != nil {
return err
}
var record model.BackupRecord
@@ -210,7 +215,7 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, startTime time.Ti
From: record.BackupType,
DefaultDownload: cronjob.DefaultDownload,
}
name, err := NewISnapshotService().HandleSnapshot(true, logPath, req, startTime.Format("20060102150405")+common.RandStrAndNum(5))
name, err := NewISnapshotService().HandleSnapshot(true, logPath, req, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5), cronjob.Secret)
if err != nil {
return err
}
@@ -232,7 +237,7 @@ type databaseHelper struct {
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
var dbs []databaseHelper
if cronjob.DBName == "all" {
if strings.Contains(cronjob.DBName, "all") {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItems, _ := mysqlRepo.List()
for _, mysql := range mysqlItems {
@@ -254,36 +259,42 @@ func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
}
return dbs
}
itemID, _ := (strconv.Atoi(cronjob.DBName))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: mysqlItem.MysqlName,
Name: mysqlItem.Name,
})
} else {
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: pgItem.PostgresqlName,
Name: pgItem.Name,
})
dbNames := strings.Split(cronjob.DBName, ",")
for _, name := range dbNames {
itemID, _ := strconv.Atoi(name)
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItem, _ := mysqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: mysqlItem.MysqlName,
Name: mysqlItem.Name,
})
} else {
pgItem, _ := postgresqlRepo.Get(commonRepo.WithByID(uint(itemID)))
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: pgItem.PostgresqlName,
Name: pgItem.Name,
})
}
}
return dbs
}
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
var weblist []model.Website
if cronjob.Website == "all" {
if strings.Contains(cronjob.Website, "all") {
weblist, _ = websiteRepo.List()
return weblist
}
itemID, _ := (strconv.Atoi(cronjob.Website))
webItem, _ := websiteRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
if webItem.ID != 0 {
weblist = append(weblist, webItem)
websites := strings.Split(cronjob.Website, ",")
var idItems []uint
for i := 0; i < len(websites); i++ {
itemID, _ := strconv.Atoi(websites[i])
idItems = append(idItems, uint(itemID))
}
weblist, _ = websiteRepo.List(commonRepo.WithIdsIn(idItems))
return weblist
}
@@ -303,7 +314,7 @@ func loadRecordPath(cronjob model.Cronjob, accountMap map[string]cronjobUploadHe
return source, backupType
}
func handleBackupLogs(targetDir, fileName string) error {
func handleBackupLogs(targetDir, fileName string, secret string) error {
websites, err := websiteRepo.List()
if err != nil {
return err
@@ -376,7 +387,7 @@ func handleBackupLogs(targetDir, fileName string) error {
}
global.LOG.Debug("backup ssh log successful!")
if err := handleTar(targetDir, path.Dir(targetDir), fileName, ""); err != nil {
if err := handleTar(targetDir, path.Dir(targetDir), fileName, "", secret); err != nil {
return err
}
defer func() {
+29 -13
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
pathUtils "path"
"strings"
"time"
@@ -124,13 +125,13 @@ func (u *CronjobService) handleNtpSync() error {
if err != nil {
return err
}
if err := ntp.UpdateSystemTime(ntime.Format("2006-01-02 15:04:05")); err != nil {
if err := ntp.UpdateSystemTime(ntime.Format(constant.DateTimeLayout)); err != nil {
return err
}
return nil
}
func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
func handleTar(sourceDir, targetDir, name, exclusionRules string, secret string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
return err
@@ -139,7 +140,6 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
excludes := strings.Split(exclusionRules, ",")
excludeRules := ""
excludes = append(excludes, "*.sock")
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
@@ -158,8 +158,20 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
path = sourceDir
}
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands)
commands := ""
if len(secret) != 0 {
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
itemPrefix := pathUtils.Base(sourceDir)
if itemPrefix == "/" {
itemPrefix = ""
}
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|%s/|') -zcf %s %s %s", sourceDir, "%P\n", itemPrefix, targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands)
}
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
if len(stdout) != 0 {
@@ -170,15 +182,22 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
return nil
}
func handleUnTar(sourceFile, targetDir string) error {
func handleUnTar(sourceFile, targetDir string, secret string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
return err
}
}
commands := ""
if len(secret) != 0 {
extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + sourceFile + " | "
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1")
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
} else {
commands = fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands)
}
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
@@ -209,7 +228,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
_ = os.MkdirAll(dstLogDir, 0755)
}
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout))
dstFilePath := path.Join(dstLogDir, dstName)
filePaths = append(filePaths, dstFilePath)
@@ -224,7 +243,6 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
}
msg := i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath})
global.LOG.Infof(msg)
msgs = append(msgs, msg)
}
u.removeExpiredLog(*cronjob)
@@ -311,7 +329,6 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
}
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, record model.BackupRecord) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
var opts []repo.DBOption
opts = append(opts, commonRepo.WithByFrom("cronjob"))
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
@@ -346,7 +363,6 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
}
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) <= int(cronjob.RetainCopies) {
return
@@ -369,7 +385,7 @@ func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.
_ = os.MkdirAll(dir, os.ModePerm)
}
path := fmt.Sprintf("%s/%s.log", dir, startTime.Format("20060102150405"))
path := fmt.Sprintf("%s/%s.log", dir, startTime.Format(constant.DateTimeSlimLayout))
return path
}
+136 -73
View File
@@ -3,11 +3,18 @@ package service
import (
"encoding/json"
"fmt"
network "net"
"os"
"sort"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -16,9 +23,6 @@ import (
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
)
type DashboardService struct{}
@@ -26,7 +30,7 @@ type DashboardService struct{}
type IDashboardService interface {
LoadOsInfo() (*dto.OsInfo, error)
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent
Restart(operation string) error
}
@@ -64,6 +68,11 @@ func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
diskInfo, err := disk.Usage(global.CONF.System.BaseDir)
if err == nil {
baseInfo.DiskSize = int64(diskInfo.Free)
}
if baseInfo.KernelArch == "armv7l" {
baseInfo.KernelArch = "armv7"
}
@@ -88,17 +97,30 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
baseInfo.KernelVersion = hostInfo.KernelVersion
ss, _ := json.Marshal(hostInfo)
baseInfo.VirtualizationSystem = string(ss)
baseInfo.IpV4Addr = GetOutboundIP()
httpProxy := os.Getenv("http_proxy")
if httpProxy == "" {
httpProxy = os.Getenv("HTTP_PROXY")
}
if httpProxy != "" {
baseInfo.SystemProxy = httpProxy
}
baseInfo.SystemProxy = "noProxy"
appInstall, err := appInstallRepo.ListBy()
if err != nil {
return nil, err
}
baseInfo.AppInstalledNumber = len(appInstall)
dbs, err := mysqlRepo.List()
postgresqlDbs, err := postgresqlRepo.List()
if err != nil {
return nil, err
}
baseInfo.DatabaseNumber = len(dbs)
mysqlDbs, err := mysqlRepo.List()
if err != nil {
return nil, err
}
baseInfo.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs)
website, err := websiteRepo.GetBy()
if err != nil {
return nil, err
@@ -118,78 +140,88 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
baseInfo.CPUCores, _ = cpu.Counts(false)
baseInfo.CPULogicalCores, _ = cpu.Counts(true)
baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption)
baseInfo.CurrentInfo = *u.LoadCurrentInfo(dto.DashboardReq{
Scope: "ioNet",
IoOption: ioOption,
NetOption: netOption,
})
return &baseInfo, nil
}
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
func (u *DashboardService) LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent {
var currentInfo dto.DashboardCurrent
if req.Scope == "gpu" {
currentInfo.GPUData = loadGPUInfo()
currentInfo.XPUData = loadXpuInfo()
}
hostInfo, _ := host.Info()
currentInfo.Uptime = hostInfo.Uptime
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format("2006-01-02 15:04:05")
currentInfo.Procs = hostInfo.Procs
currentInfo.CPUTotal, _ = cpu.Counts(true)
totalPercent, _ := cpu.Percent(0, false)
if len(totalPercent) == 1 {
currentInfo.CPUUsedPercent = totalPercent[0]
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
}
currentInfo.CPUPercent, _ = cpu.Percent(0, true)
loadInfo, _ := load.Avg()
currentInfo.Load1 = loadInfo.Load1
currentInfo.Load5 = loadInfo.Load5
currentInfo.Load15 = loadInfo.Load15
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
currentInfo.SwapMemoryTotal = swapInfo.Total
currentInfo.SwapMemoryAvailable = swapInfo.Free
currentInfo.SwapMemoryUsed = swapInfo.Used
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
currentInfo.DiskData = loadDiskInfo()
currentInfo.GPUData = loadGPUInfo()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
} else {
diskInfo, _ := disk.IOCounters(ioOption)
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
if req.Scope == "basic" {
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
currentInfo.Procs = hostInfo.Procs
currentInfo.CPUTotal, _ = cpu.Counts(true)
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
if len(totalPercent) == 1 {
currentInfo.CPUUsedPercent = totalPercent[0]
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
}
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true)
loadInfo, _ := load.Avg()
currentInfo.Load1 = loadInfo.Load1
currentInfo.Load5 = loadInfo.Load5
currentInfo.Load15 = loadInfo.Load15
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
currentInfo.SwapMemoryTotal = swapInfo.Total
currentInfo.SwapMemoryAvailable = swapInfo.Free
currentInfo.SwapMemoryUsed = swapInfo.Used
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
currentInfo.DiskData = loadDiskInfo()
}
if netOption == "all" {
netInfo, _ := net.IOCounters(false)
if len(netInfo) != 0 {
currentInfo.NetBytesSent = netInfo[0].BytesSent
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
if req.Scope == "ioNet" {
if req.IoOption == "all" {
diskInfo, _ := disk.IOCounters()
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
} else {
diskInfo, _ := disk.IOCounters(req.IoOption)
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
}
} else {
netInfo, _ := net.IOCounters(true)
for _, state := range netInfo {
if state.Name == netOption {
currentInfo.NetBytesSent = state.BytesSent
currentInfo.NetBytesRecv = state.BytesRecv
if req.NetOption == "all" {
netInfo, _ := net.IOCounters(false)
if len(netInfo) != 0 {
currentInfo.NetBytesSent = netInfo[0].BytesSent
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
}
} else {
netInfo, _ := net.IOCounters(true)
for _, state := range netInfo {
if state.Name == req.NetOption {
currentInfo.NetBytesSent = state.BytesSent
currentInfo.NetBytesRecv = state.BytesRecv
}
}
}
}
@@ -198,6 +230,18 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
return &currentInfo
}
func GetOutboundIP() string {
conn, err := network.Dial("udp", "8.8.8.8:80")
if err != nil {
return "IPNotFound"
}
defer conn.Close()
localAddr := conn.LocalAddr().(*network.UDPAddr)
return localAddr.IP.String()
}
type diskInfo struct {
Type string
Mount string
@@ -222,13 +266,16 @@ func loadDiskInfo() []dto.DiskInfo {
if len(fields) < 7 {
continue
}
if fields[1] == "tmpfs" {
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
continue
}
if strings.Contains(fields[2], "M") || strings.Contains(fields[2], "K") {
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
continue
}
if strings.Contains(fields[6], "docker") {
if strings.Contains(fields[2], "K") {
continue
}
if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
continue
}
isExclude := false
@@ -240,7 +287,7 @@ func loadDiskInfo() []dto.DiskInfo {
if isExclude {
continue
}
mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: fields[6]})
mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: strings.Join(fields[6:], " ")})
}
var (
@@ -310,3 +357,19 @@ func loadGPUInfo() []dto.GPUInfo {
}
return data
}
func loadXpuInfo() []dto.XPUInfo {
list := xpack.LoadXpuInfo()
if len(list) == 0 {
return nil
}
var data []dto.XPUInfo
for _, gpu := range list {
var dataItem dto.XPUInfo
if err := copier.Copy(&dataItem, &gpu); err != nil {
continue
}
data = append(data, dataItem)
}
return data
}
+32 -4
View File
@@ -7,7 +7,8 @@ import (
"path"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
client2 "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
redisclient "github.com/1Panel-dev/1Panel/backend/utils/redis"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
@@ -42,6 +43,7 @@ func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, inte
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
databaseRepo.WithTypeList(search.Type),
commonRepo.WithLikeName(search.Info),
commonRepo.WithOrderRuleBy(search.OrderBy, search.Order),
databaseRepo.WithoutByFrom("local"),
)
var datas []dto.DatabaseInfo
@@ -69,6 +71,9 @@ func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
func (u *DatabaseService) List(dbType string) ([]dto.DatabaseOption, error) {
dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType))
if err != nil {
return nil, err
}
var datas []dto.DatabaseOption
for _, db := range dbs {
var item dto.DatabaseOption
@@ -113,7 +118,7 @@ func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
switch req.Type {
case constant.AppPostgresql:
_, err := postgresql.NewPostgresqlClient(client2.DBInfo{
_, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
@@ -122,6 +127,13 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
Timeout: 6,
})
return err == nil
case constant.AppRedis:
_, err := redisclient.NewRedisClient(redisclient.DBInfo{
Address: req.Address,
Port: req.Port,
Password: req.Password,
})
return err == nil
case "mysql", "mariadb":
_, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
@@ -153,7 +165,7 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
}
switch req.Type {
case constant.AppPostgresql:
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
@@ -163,6 +175,14 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
}); err != nil {
return err
}
case constant.AppRedis:
if _, err := redisclient.NewRedisClient(redisclient.DBInfo{
Address: req.Address,
Port: req.Port,
Password: req.Password,
}); err != nil {
return err
}
case "mysql", "mariadb":
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
@@ -249,7 +269,7 @@ func (u *DatabaseService) Delete(req dto.DatabaseDelete) error {
func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
switch req.Type {
case constant.AppPostgresql:
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
@@ -259,6 +279,14 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
}); err != nil {
return err
}
case constant.AppRedis:
if _, err := redisclient.NewRedisClient(redisclient.DBInfo{
Address: req.Address,
Port: req.Port,
Password: req.Password,
}); err != nil {
return err
}
case "mysql", "mariadb":
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
+25 -4
View File
@@ -141,6 +141,10 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
}
func (u *MysqlService) BindUser(req dto.BindUser) error {
if cmd.CheckIllegal(req.Username, req.Password, req.Permission) {
return buserr.New(constant.ErrCmdIllegal)
}
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
if err != nil {
return err
@@ -196,6 +200,9 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) {
hasOld = true
if databases[i].IsDelete {
_ = mysqlRepo.Update(databases[i].ID, map[string]interface{}{"is_delete": false})
}
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
@@ -240,7 +247,7 @@ func (u *MysqlService) DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error)
}
}
} else {
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID))
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID), appRepo.WithKey(req.Type))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
@@ -363,6 +370,17 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
if req.From == "local" {
remote, err := databaseRepo.Get(commonRepo.WithByName(req.Database))
if err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Value)
if err != nil {
return fmt.Errorf("decrypt database password failed, err: %v", err)
}
_ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass})
}
return nil
}
@@ -507,11 +525,11 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
if value, ok := statusMap["Run"]; ok {
uptime, _ := strconv.Atoi(value)
info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format("2006-01-02 15:04:05")
info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format(constant.DateTimeLayout)
} else {
if value, ok := statusMap["Uptime"]; ok {
uptime, _ := strconv.Atoi(value)
info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format("2006-01-02 15:04:05")
info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format(constant.DateTimeLayout)
}
}
@@ -519,7 +537,10 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
info.Position = "OFF"
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "show master status;")
if err != nil {
return nil, err
rows, err = executeSqlForRows(app.ContainerName, app.Key, app.Password, "SHOW BINARY LOG STATUS;")
if err != nil {
return nil, err
}
}
if len(rows) > 2 {
itemValue := strings.Split(rows[1], "\t")
+15 -1
View File
@@ -222,6 +222,9 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) {
hasOld = true
if databases[i].IsDelete {
_ = postgresqlRepo.Update(databases[i].ID, map[string]interface{}{"is_delete": false})
}
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
@@ -266,7 +269,7 @@ func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]stri
}
}
} else {
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID))
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID), appRepo.WithKey(req.Type))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
@@ -414,5 +417,16 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
if req.From == "local" {
remote, err := databaseRepo.Get(commonRepo.WithByName(req.Database))
if err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Value)
if err != nil {
return fmt.Errorf("decrypt database password failed, err: %v", err)
}
_ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass})
}
return nil
}
+54 -56
View File
@@ -1,18 +1,20 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/docker/docker/api/types/container"
_ "github.com/go-sql-driver/mysql"
)
@@ -23,11 +25,12 @@ type IRedisService interface {
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
ChangePassword(info dto.ChangeRedisPass) error
LoadStatus() (*dto.RedisStatus, error)
LoadConf() (*dto.RedisConf, error)
LoadPersistenceConf() (*dto.RedisPersistence, error)
LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error)
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error)
CheckHasCli() bool
InstallCli() error
}
func NewIRedisService() IRedisService {
@@ -35,7 +38,7 @@ func NewIRedisService() IRedisService {
}
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
if err != nil {
return err
}
@@ -54,19 +57,54 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
return nil
}
func (u *RedisService) CheckHasCli() bool {
client, err := docker.NewDockerClient()
if err != nil {
return false
}
defer client.Close()
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return false
}
for _, item := range containerLists {
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
return true
}
}
return false
}
func (u *RedisService) InstallCli() error {
item := dto.ContainerOperate{
Name: "1Panel-redis-cli-tools",
Image: "redis:7.2.4",
Network: "1panel-network",
}
return NewIContainerService().ContainerCreate(item)
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil {
if err := updateInstallInfoInDB("redis", req.Database, "password", req.Value); err != nil {
return err
}
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
remote, err := databaseRepo.Get(commonRepo.WithByName(req.Database))
if err != nil {
return err
}
if remote.From == "local" {
pass, err := encrypt.StringEncrypt(req.Value)
if err != nil {
return fmt.Errorf("decrypt database password failed, err: %v", err)
}
_ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass})
}
return nil
}
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
if err != nil {
return err
}
@@ -88,8 +126,8 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
return nil
}
func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil {
return nil, err
}
@@ -116,8 +154,8 @@ func (u *RedisService) LoadStatus() (*dto.RedisStatus, error) {
return &info, nil
}
func (u *RedisService) LoadConf() (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil {
return nil, err
}
@@ -133,8 +171,8 @@ func (u *RedisService) LoadConf() (*dto.RedisConf, error) {
return &item, nil
}
func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
func (u *RedisService) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
if err != nil {
return nil, err
}
@@ -151,46 +189,6 @@ func (u *RedisService) LoadPersistenceConf() (*dto.RedisPersistence, error) {
return &item, nil
}
func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interface{}, error) {
var (
list []dto.DatabaseFileRecords
backDatas []dto.DatabaseFileRecords
)
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", "")
if err != nil {
return 0, nil, err
}
localDir, err := loadLocalDir()
if err != nil {
return 0, nil, err
}
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
list = append(list, dto.DatabaseFileRecords{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
Size: int(info.Size()),
FileDir: backupDir,
FileName: info.Name(),
})
}
return nil
})
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]dto.DatabaseFileRecords, 0)
} else {
if end >= total {
end = total
}
backDatas = list[start:end]
}
return int64(total), backDatas, nil
}
func configGetStr(containerName, password, param string) (string, error) {
commands := append(redisExec(containerName, password), []string{"config", "get", param}...)
cmd := exec.Command("docker", commands...)
+1 -1
View File
@@ -158,7 +158,7 @@ func (u *DeviceService) Update(key, value string) error {
if err != nil {
return err
}
ts := ntime.Format("2006-01-02 15:04:05")
ts := ntime.Format(constant.DateTimeLayout)
if err := ntp.UpdateSystemTime(ts); err != nil {
return err
}
+123 -5
View File
@@ -1,6 +1,7 @@
package service
import (
"context"
"fmt"
"os"
"path"
@@ -8,6 +9,11 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -67,7 +73,7 @@ func (u *DeviceService) Scan() dto.CleanData {
snapTree := loadSnapshotTree(fileOp)
snapSize := uint64(0)
for _, snap := range snapTree {
snapSize += uint64(snap.Size)
snapSize += snap.Size
}
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
@@ -82,7 +88,7 @@ func (u *DeviceService) Scan() dto.CleanData {
rollBackTree := loadRollBackTree(fileOp)
rollbackSize := uint64(0)
for _, rollback := range rollBackTree {
rollbackSize += uint64(rollback.Size)
rollbackSize += rollback.Size
}
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
@@ -108,7 +114,7 @@ func (u *DeviceService) Scan() dto.CleanData {
unusedTree := loadUnusedFile(fileOp)
unusedSize := uint64(0)
for _, unused := range unusedTree {
unusedSize += uint64(unused.Size)
unusedSize += unused.Size
}
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
@@ -130,6 +136,9 @@ func (u *DeviceService) Scan() dto.CleanData {
logTree := loadLogTree(fileOp)
SystemClean.SystemLogClean = append(SystemClean.SystemLogClean, logTree...)
containerTree := loadContainerTree()
SystemClean.ContainerClean = append(SystemClean.ContainerClean, containerTree...)
return SystemClean
}
@@ -259,10 +268,18 @@ func (u *DeviceService) Clean(req []dto.Clean) {
} else {
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByRecordFile(pathItem))
}
case "images":
dropImages()
case "containers":
dropContainers()
case "volumes":
dropVolumes()
case "build_cache":
dropBuildCache()
}
}
_ = settingRepo.Update("LastCleanTime", time.Now().Format("2006-01-02 15:04:05"))
_ = settingRepo.Update("LastCleanTime", time.Now().Format(constant.DateTimeLayout))
_ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size))
_ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", len(req)))
@@ -326,7 +343,7 @@ func (u *DeviceService) CleanForCronjob() (string, error) {
}
}
}
timeNow := time.Now().Format("2006-01-02 15:04:05")
timeNow := time.Now().Format(constant.DateTimeLayout)
dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, dockerLogPath), &logs, &size, &fileCount)
logs += fmt.Sprintf("\n%s: total clean: %s, total count: %d", timeNow, common.LoadSizeUnit2F(float64(size)), fileCount)
@@ -496,6 +513,50 @@ func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
return treeData
}
func loadContainerTree() []dto.CleanTree {
var treeData []dto.CleanTree
client, err := docker.NewDockerClient()
if err != nil {
return treeData
}
diskUsage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
if err != nil {
return treeData
}
imageSize := uint64(0)
for _, file := range diskUsage.Images {
if file.Containers == 0 {
imageSize += uint64(file.Size)
}
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_images", Size: imageSize, Children: nil, Type: "images", IsRecommend: true})
containerSize := uint64(0)
for _, file := range diskUsage.Containers {
if file.State != "running" {
containerSize += uint64(file.SizeRw)
}
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_containers", Size: containerSize, Children: nil, Type: "containers", IsRecommend: true})
volumeSize := uint64(0)
for _, file := range diskUsage.Volumes {
if file.UsageData.RefCount <= 0 {
volumeSize += uint64(file.UsageData.Size)
}
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_volumes", Size: volumeSize, Children: nil, Type: "volumes", IsRecommend: true})
var buildCacheTotalSize int64
for _, cache := range diskUsage.BuildCache {
if cache.Type == "source.local" {
buildCacheTotalSize += cache.Size
}
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "build_cache", Size: uint64(buildCacheTotalSize), Type: "build_cache", IsRecommend: true})
return treeData
}
func loadTreeWithDir(isCheck bool, treeType, pathItem string, fileOp fileUtils.FileOp) []dto.CleanTree {
var lists []dto.CleanTree
files, err := os.ReadDir(pathItem)
@@ -586,6 +647,63 @@ func dropFileOrDir(itemPath string) {
}
}
func dropBuildCache() {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
}
opts := types.BuildCachePruneOptions{}
opts.All = true
_, err = client.BuildCachePrune(context.Background(), opts)
if err != nil {
global.LOG.Errorf("drop build cache failed, err %v", err)
}
}
func dropImages() {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
}
pruneFilters := filters.NewArgs()
pruneFilters.Add("dangling", "false")
_, err = client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
global.LOG.Errorf("drop images failed, err %v", err)
}
}
func dropContainers() {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
}
pruneFilters := filters.NewArgs()
_, err = client.ContainersPrune(context.Background(), pruneFilters)
if err != nil {
global.LOG.Errorf("drop containers failed, err %v", err)
}
}
func dropVolumes() {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
}
pruneFilters := filters.NewArgs()
versions, err := client.ServerVersion(context.Background())
if err != nil {
global.LOG.Errorf("do not get docker api versions")
}
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
pruneFilters.Add("all", "true")
}
_, err = client.VolumesPrune(context.Background(), pruneFilters)
if err != nil {
global.LOG.Errorf("drop volumes failed, err %v", err)
}
}
func dropFileOrDirWithLog(itemPath string, log *string, size *int64, count *int) {
itemSize := int64(0)
itemCount := 0
+41 -22
View File
@@ -4,15 +4,16 @@ import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
)
@@ -104,7 +105,6 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
return &data
}
if err := json.Unmarshal(arr, &conf); err != nil {
fmt.Println(err)
return &data
}
if _, ok := daemonMap["iptables"]; !ok {
@@ -131,13 +131,10 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
}
func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
@@ -203,6 +200,10 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
}
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
@@ -219,15 +220,26 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
}
return nil
}
func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
func createIfNotExistDaemonJsonFile() error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
var daemonFile *os.File
daemonFile, err = os.Create(constant.DaemonJsonPath)
if err != nil {
return err
}
defer daemonFile.Close()
}
return nil
}
func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
@@ -256,11 +268,9 @@ func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
}
func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
}
file, err := os.ReadFile(constant.DaemonJsonPath)
@@ -300,13 +310,15 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
if len(req.File) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
}
file, err := os.OpenFile(constant.DaemonJsonPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
@@ -326,8 +338,15 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
sudo := cmd.SudoHandleCmd()
if req.Operation == "stop" {
service = "docker.socket"
isSocketActive, _ := systemctl.IsActive("docker.socket")
if isSocketActive {
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
if err != nil {
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
}
}
}
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
if err != nil {
+2
View File
@@ -24,6 +24,8 @@ var (
hostRepo = repo.NewIHostRepo()
groupRepo = repo.NewIGroupRepo()
commandRepo = repo.NewICommandRepo()
ftpRepo = repo.NewIFtpRepo()
clamRepo = repo.NewIClamRepo()
settingRepo = repo.NewISettingRepo()
backupRepo = repo.NewIBackupRepo()
+117 -21
View File
@@ -2,6 +2,7 @@ package service
import (
"fmt"
"io"
"io/fs"
"os"
"os/exec"
@@ -9,11 +10,16 @@ import (
"path/filepath"
"strings"
"time"
"unicode/utf8"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"golang.org/x/net/html/charset"
"golang.org/x/sys/unix"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
@@ -46,15 +52,23 @@ type IFileService interface {
ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error)
}
var filteredPaths = []string{
"/.1panel_clash",
}
func NewIFileService() IFileService {
return &FileService{}
}
func (f *FileService) GetFileList(op request.FileOption) (response.FileInfo, error) {
var fileInfo response.FileInfo
if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
data, err := os.Stat(op.Path)
if err != nil && os.IsNotExist(err) {
return fileInfo, nil
}
if !data.IsDir() {
op.FileOption.Path = filepath.Dir(op.FileOption.Path)
}
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
return fileInfo, err
@@ -74,7 +88,7 @@ func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (in
}
if !info.IsDir() {
files = append(files, response.UploadInfo{
CreatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
CreatedAt: info.ModTime().Format(constant.DateTimeLayout),
Size: int(info.Size()),
Name: info.Name(),
})
@@ -95,34 +109,85 @@ func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (in
func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, error) {
var treeArray []response.FileTree
if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
return treeArray, nil
}
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
return nil, err
}
node := response.FileTree{
ID: common.GetUuid(),
Name: info.Name,
Path: info.Path,
ID: common.GetUuid(),
Name: info.Name,
Path: info.Path,
IsDir: info.IsDir,
Extension: info.Extension,
}
for _, v := range info.Items {
if v.IsDir {
node.Children = append(node.Children, response.FileTree{
ID: common.GetUuid(),
Name: v.Name,
Path: v.Path,
})
}
err = f.buildFileTree(&node, info.Items, op, 2)
if err != nil {
return nil, err
}
return append(treeArray, node), nil
}
func shouldFilterPath(path string) bool {
cleanedPath := filepath.Clean(path)
for _, filteredPath := range filteredPaths {
cleanedFilteredPath := filepath.Clean(filteredPath)
if cleanedFilteredPath == cleanedPath || strings.HasPrefix(cleanedPath, cleanedFilteredPath+"/") {
return true
}
}
return false
}
// 递归构建文件树(只取当前目录以及当前目录下的第一层子节点)
func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error {
for _, v := range items {
if shouldFilterPath(v.Path) {
global.LOG.Infof("File Tree: Skipping %s due to filter\n", v.Path)
continue
}
childNode := response.FileTree{
ID: common.GetUuid(),
Name: v.Name,
Path: v.Path,
IsDir: v.IsDir,
Extension: v.Extension,
}
if level > 1 && v.IsDir {
if err := f.buildChildNode(&childNode, v, op, level); err != nil {
return err
}
}
node.Children = append(node.Children, childNode)
}
return nil
}
func (f *FileService) buildChildNode(childNode *response.FileTree, fileInfo *files.FileInfo, op request.FileOption, level int) error {
op.Path = fileInfo.Path
subInfo, err := files.NewFileInfo(op.FileOption)
if err != nil {
if os.IsPermission(err) || errors.Is(err, unix.EACCES) {
global.LOG.Infof("File Tree: Skipping %s due to permission denied\n", fileInfo.Path)
return nil
}
global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error())
return nil
}
return f.buildFileTree(childNode, subInfo.Items, op, level-1)
}
func (f *FileService) Create(op request.FileCreate) error {
if files.IsInvalidChar(op.Path) {
return buserr.New("ErrInvalidChar")
}
fo := files.NewFileOp()
if fo.Stat(op.Path) {
return buserr.New(constant.ErrFileIsExit)
return buserr.New(constant.ErrFileIsExist)
}
mode := op.Mode
if mode == 0 {
@@ -212,24 +277,45 @@ func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error {
func (f *FileService) Compress(c request.FileCompress) error {
fo := files.NewFileOp()
if !c.Replace && fo.Stat(filepath.Join(c.Dst, c.Name)) {
return buserr.New(constant.ErrFileIsExit)
return buserr.New(constant.ErrFileIsExist)
}
return fo.Compress(c.Files, c.Dst, c.Name, files.CompressType(c.Type))
return fo.Compress(c.Files, c.Dst, c.Name, files.CompressType(c.Type), c.Secret)
}
func (f *FileService) DeCompress(c request.FileDeCompress) error {
fo := files.NewFileOp()
return fo.Decompress(c.Path, c.Dst, files.CompressType(c.Type))
if c.Type == "tar" && len(c.Secret) != 0 {
c.Type = "tar.gz"
}
return fo.Decompress(c.Path, c.Dst, files.CompressType(c.Type), c.Secret)
}
func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo, error) {
info, err := files.NewFileInfo(files.FileOption{
Path: op.Path,
Expand: true,
Path: op.Path,
Expand: true,
IsDetail: op.IsDetail,
})
if err != nil {
return response.FileInfo{}, err
}
content := []byte(info.Content)
if len(content) > 1024 {
content = content[:1024]
}
if !utf8.Valid(content) {
_, decodeName, _ := charset.DetermineEncoding(content, "")
if decodeName == "windows-1252" {
reader := strings.NewReader(info.Content)
item := transform.NewReader(reader, simplifiedchinese.GBK.NewDecoder())
contents, err := io.ReadAll(item)
if err != nil {
return response.FileInfo{}, err
}
info.Content = string(contents)
}
}
return response.FileInfo{FileInfo: *info}, nil
}
@@ -304,7 +390,7 @@ func (f *FileService) FileDownload(d request.FileDownload) (string, error) {
return "", err
}
fo := files.NewFileOp()
if err := fo.Compress(d.Paths, tempPath, d.Name, files.CompressType(d.Type)); err != nil {
if err := fo.Compress(d.Paths, tempPath, d.Name, files.CompressType(d.Type), ""); err != nil {
return "", err
}
filePath = filepath.Join(tempPath, d.Name)
@@ -388,14 +474,24 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
}
lines, isEndOfFile, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize)
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
if err != nil {
return nil, err
}
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
if err != nil {
return nil, err
}
lines = append(preLines, lines...)
}
res := &response.FileLineContent{
Content: strings.Join(lines, "\n"),
End: isEndOfFile,
Path: logFilePath,
Total: total,
Lines: lines,
}
return res, nil
}
+74 -36
View File
@@ -3,6 +3,7 @@ package service
import (
"fmt"
"os"
"sort"
"strconv"
"strings"
"sync"
@@ -28,6 +29,7 @@ type IFirewallService interface {
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
OperateFirewall(operation string) error
OperatePortRule(req dto.PortRuleOperate, reload bool) error
OperateForwardRule(req dto.ForwardRuleOperate) error
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
UpdatePortRule(req dto.PortRuleUpdate) error
UpdateAddrRule(req dto.AddrRuleUpdate) error
@@ -78,42 +80,39 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
if err != nil {
return 0, nil, err
}
if req.Type == "port" {
ports, err := client.ListPort()
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, port := range ports {
if strings.Contains(port.Port, req.Info) {
datas = append(datas, port)
}
}
} else {
datas = ports
}
} else {
addrs, err := client.ListAddress()
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, addr := range addrs {
if strings.Contains(addr.Address, req.Info) {
datas = append(datas, addr)
}
}
} else {
datas = addrs
}
var rules []fireClient.FireInfo
switch req.Type {
case "port":
rules, err = client.ListPort()
case "forward":
rules, err = client.ListForward()
case "address":
rules, err = client.ListAddress()
}
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, addr := range rules {
if strings.Contains(addr.Address, req.Info) ||
strings.Contains(addr.Port, req.Info) ||
strings.Contains(addr.TargetPort, req.Info) ||
strings.Contains(addr.TargetIP, req.Info) {
datas = append(datas, addr)
}
}
} else {
datas = rules
}
if req.Type == "port" {
apps := u.loadPortByApp()
for i := 0; i < len(datas); i++ {
datas[i].UsedStatus = checkPortUsed(datas[i].Port, datas[i].Protocol, apps)
}
}
var datasFilterStatus []fireClient.FireInfo
if len(req.Status) != 0 {
for _, data := range datas {
@@ -127,6 +126,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
} else {
datasFilterStatus = datas
}
var datasFilterStrategy []fireClient.FireInfo
if len(req.Strategy) != 0 {
for _, data := range datasFilterStatus {
@@ -300,6 +300,52 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
return nil
}
func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
rules, _ := client.ListForward()
for _, rule := range rules {
for _, reqRule := range req.Rules {
if reqRule.Operation == "remove" {
continue
}
if reqRule.TargetIP == "" {
reqRule.TargetIP = "127.0.0.1"
}
if reqRule.Port == rule.Port && reqRule.TargetPort == rule.TargetPort && reqRule.TargetIP == rule.TargetIP {
return constant.ErrRecordExist
}
}
}
sort.SliceStable(req.Rules, func(i, j int) bool {
n1, _ := strconv.Atoi(req.Rules[i].Num)
n2, _ := strconv.Atoi(req.Rules[j].Num)
return n1 > n2
})
for _, r := range req.Rules {
for _, p := range strings.Split(r.Protocol, "/") {
if r.TargetIP == "" {
r.TargetIP = "127.0.0.1"
}
if err = client.PortForward(fireClient.Forward{
Num: r.Num,
Protocol: p,
Port: r.Port,
TargetIP: r.TargetIP,
TargetPort: r.TargetPort,
}, r.Operation); err != nil {
return err
}
}
}
return nil
}
func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error {
client, err := firewall.NewFirewallClient()
if err != nil {
@@ -542,14 +588,6 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
apps := u.loadPortByApp()
for _, app := range apps {
if len(app.HttpPort) != 0 && app.HttpPort != "0" {
if err := client.Port(fireClient.FireInfo{Port: app.HttpPort, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
}
}
return client.Reload()
}
+245
View File
@@ -0,0 +1,245 @@
package service
import (
"os"
"sort"
"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/toolbox"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
type FtpService struct{}
type IFtpService interface {
LoadBaseInfo() (dto.FtpBaseInfo, error)
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
Operate(operation string) error
Create(req dto.FtpCreate) (uint, error)
Delete(req dto.BatchDeleteReq) error
Update(req dto.FtpUpdate) error
Sync() error
LoadLog(req dto.FtpLogSearch) (int64, interface{}, error)
}
func NewIFtpService() IFtpService {
return &FtpService{}
}
func (f *FtpService) LoadBaseInfo() (dto.FtpBaseInfo, error) {
var baseInfo dto.FtpBaseInfo
client, err := toolbox.NewFtpClient()
if err != nil {
return baseInfo, err
}
baseInfo.IsActive, baseInfo.IsExist = client.Status()
return baseInfo, nil
}
func (f *FtpService) LoadLog(req dto.FtpLogSearch) (int64, interface{}, error) {
client, err := toolbox.NewFtpClient()
if err != nil {
return 0, nil, err
}
logItem, err := client.LoadLogs(req.User, req.Operation)
if err != nil {
return 0, nil, err
}
sort.Slice(logItem, func(i, j int) bool {
return logItem[i].Time > logItem[j].Time
})
var logs []toolbox.FtpLog
total, start, end := len(logItem), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
logs = make([]toolbox.FtpLog, 0)
} else {
if end >= total {
end = total
}
logs = logItem[start:end]
}
return int64(total), logs, nil
}
func (u *FtpService) Operate(operation string) error {
client, err := toolbox.NewFtpClient()
if err != nil {
return err
}
return client.Operate(operation)
}
func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithByUser(req.Info), commonRepo.WithOrderBy("created_at desc"))
if err != nil {
return 0, nil, err
}
var users []dto.FtpInfo
for _, user := range lists {
var item dto.FtpInfo
if err := copier.Copy(&item, &user); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.Password, _ = encrypt.StringDecrypt(item.Password)
users = append(users, item)
}
return total, users, err
}
func (f *FtpService) Sync() error {
client, err := toolbox.NewFtpClient()
if err != nil {
return err
}
lists, err := client.LoadList()
if err != nil {
return nil
}
listsInDB, err := ftpRepo.GetList()
if err != nil {
return err
}
sameData := make(map[string]struct{})
for _, item := range lists {
for _, itemInDB := range listsInDB {
if item.User == itemInDB.User {
sameData[item.User] = struct{}{}
if item.Path != itemInDB.Path || item.Status != itemInDB.Status {
_ = ftpRepo.Update(itemInDB.ID, map[string]interface{}{"path": item.Path, "status": item.Status})
}
break
}
}
}
for _, item := range lists {
if _, ok := sameData[item.User]; !ok {
_ = ftpRepo.Create(&model.Ftp{User: item.User, Path: item.Path, Status: item.Status})
}
}
for _, item := range listsInDB {
if _, ok := sameData[item.User]; !ok {
_ = ftpRepo.Update(item.ID, map[string]interface{}{"status": "deleted"})
}
}
return nil
}
func (f *FtpService) Create(req dto.FtpCreate) (uint, error) {
if _, err := os.Stat(req.Path); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(req.Path, os.ModePerm); err != nil {
return 0, err
}
} else {
return 0, err
}
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return 0, err
}
userInDB, _ := ftpRepo.Get(hostRepo.WithByUser(req.User))
if userInDB.ID != 0 {
return 0, constant.ErrRecordExist
}
client, err := toolbox.NewFtpClient()
if err != nil {
return 0, err
}
if err := client.UserAdd(req.User, req.Password, req.Path); err != nil {
return 0, err
}
var ftp model.Ftp
if err := copier.Copy(&ftp, &req); err != nil {
return 0, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
ftp.Status = constant.StatusEnable
ftp.Password = pass
if err := ftpRepo.Create(&ftp); err != nil {
return 0, err
}
return ftp.ID, nil
}
func (f *FtpService) Delete(req dto.BatchDeleteReq) error {
client, err := toolbox.NewFtpClient()
if err != nil {
return err
}
for _, id := range req.Ids {
ftpItem, err := ftpRepo.Get(commonRepo.WithByID(id))
if err != nil {
return err
}
_ = client.UserDel(ftpItem.User)
_ = ftpRepo.Delete(commonRepo.WithByID(id))
}
return nil
}
func (f *FtpService) Update(req dto.FtpUpdate) error {
if _, err := os.Stat(req.Path); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(req.Path, os.ModePerm); err != nil {
return err
}
} else {
return err
}
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return err
}
ftpItem, _ := ftpRepo.Get(commonRepo.WithByID(req.ID))
if ftpItem.ID == 0 {
return constant.ErrRecordNotFound
}
passItem, err := encrypt.StringDecrypt(ftpItem.Password)
if err != nil {
return err
}
client, err := toolbox.NewFtpClient()
if err != nil {
return err
}
needReload := false
updates := make(map[string]interface{})
if req.Password != passItem {
if err := client.SetPasswd(ftpItem.User, req.Password); err != nil {
return err
}
updates["password"] = pass
needReload = true
}
if req.Status != ftpItem.Status {
if err := client.SetStatus(ftpItem.User, req.Status); err != nil {
return err
}
updates["status"] = req.Status
needReload = true
}
if req.Path != ftpItem.Path {
if err := client.SetPath(ftpItem.User, req.Path); err != nil {
return err
}
updates["path"] = req.Path
needReload = true
}
if req.Description != ftpItem.Description {
updates["description"] = req.Description
}
if needReload {
_ = client.Reload()
}
if len(updates) != 0 {
return ftpRepo.Update(ftpItem.ID, updates)
}
return nil
}
+2 -2
View File
@@ -307,7 +307,7 @@ func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcess
if err != nil {
return err
}
_, _ = section.NewKey("command", req.Command)
_, _ = section.NewKey("command", strings.TrimSpace(req.Command))
_, _ = section.NewKey("directory", req.Dir)
_, _ = section.NewKey("autorestart", "true")
_, _ = section.NewKey("startsecs", "3")
@@ -338,7 +338,7 @@ func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcess
}
commandKey := section.Key("command")
commandKey.SetValue(req.Command)
commandKey.SetValue(strings.TrimSpace(req.Command))
directoryKey := section.Key("directory")
directoryKey.SetValue(req.Dir)
userKey := section.Key("user")
+58 -7
View File
@@ -24,6 +24,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/homedir"
)
type ImageService struct{}
@@ -198,7 +199,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
return "", err
}
}
logItem := fmt.Sprintf("%s/image_build_%s_%s.log", dockerLogDir, strings.ReplaceAll(req.Name, ":", "_"), time.Now().Format("20060102150405"))
logItem := fmt.Sprintf("%s/image_build_%s_%s.log", dockerLogDir, strings.ReplaceAll(req.Name, ":", "_"), time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
@@ -248,15 +249,20 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
}
}
imageItemName := strings.ReplaceAll(path.Base(req.ImageName), ":", "_")
logItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
logItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
options := image.PullOptions{}
if req.RepoID == 0 {
hasAuth, authStr := loadAuthInfo(req.ImageName)
if hasAuth {
options.RegistryAuth = authStr
}
go func() {
defer file.Close()
out, err := client.ImagePull(context.TODO(), req.ImageName, types.ImagePullOptions{})
out, err := client.ImagePull(context.TODO(), req.ImageName, options)
if err != nil {
global.LOG.Errorf("image %s pull failed, err: %v", req.ImageName, err)
return
@@ -271,7 +277,6 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
if err != nil {
return "", err
}
options := image.PullOptions{}
if repo.Auth {
authConfig := registry.AuthConfig{
Username: repo.Username,
@@ -281,7 +286,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
if err != nil {
return "", err
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
authStr := base64.StdEncoding.EncodeToString(encodedJSON)
options.RegistryAuth = authStr
}
image := repo.DownloadUrl + "/" + req.ImageName
@@ -290,7 +295,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
out, err := client.ImagePull(context.TODO(), image, options)
if err != nil {
_, _ = file.WriteString("image pull failed!")
global.LOG.Errorf("image %s pull failed, err: %v", image, err)
_, _ = file.WriteString(fmt.Sprintf("image %s pull failed, err: %v", image, err))
return
}
defer out.Close()
@@ -398,7 +403,7 @@ func (u *ImageService) ImagePush(req dto.ImagePush) (string, error) {
}
}
imageItemName := strings.ReplaceAll(path.Base(req.Name), ":", "_")
logItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
logItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
@@ -464,3 +469,49 @@ func checkUsed(imageID string, containers []types.Container) bool {
}
return false
}
func loadAuthInfo(image string) (bool, string) {
if !strings.Contains(image, "/") {
return false, ""
}
homeDir := homedir.Get()
confPath := path.Join(homeDir, ".docker/config.json")
configFileBytes, err := os.ReadFile(confPath)
if err != nil {
return false, ""
}
var config dockerConfig
if err = json.Unmarshal(configFileBytes, &config); err != nil {
return false, ""
}
var (
user string
passwd string
)
imagePrefix := strings.Split(image, "/")[0]
if val, ok := config.Auths[imagePrefix]; ok {
itemByte, _ := base64.StdEncoding.DecodeString(val.Auth)
itemStr := string(itemByte)
if strings.Contains(itemStr, ":") {
user = strings.Split(itemStr, ":")[0]
passwd = strings.Split(itemStr, ":")[1]
}
}
authConfig := registry.AuthConfig{
Username: user,
Password: passwd,
}
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
return false, ""
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
return true, authStr
}
type dockerConfig struct {
Auths map[string]authConfig `json:"auths"`
}
type authConfig struct {
Auth string `json:"auth"`
}
+4 -7
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"time"
@@ -92,6 +91,7 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return errors.New(string(stdout))
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
if err := func() error {
for range ticker.C {
@@ -195,13 +195,10 @@ func (u *ImageRepoService) CheckConn(host, user, password string) error {
}
func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
}
daemonMap := make(map[string]interface{})
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {

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