Compare commits

...
399 Commits
Author SHA1 Message Date
zhengkunwang b7c3dc0d2a fix: 解决创建 PHP 网站首页无法访问的 BUG (#4577) 2024-04-18 14:40:07 +00:00
ssongliu bb02991b05 fix: 解决监控数据迁移失败的问题 (#4569)
Refs #4568
2024-04-18 14:38:06 +00:00
writegr a5fdff61ff chore: fix some typos (#4564)
Signed-off-by: writegr <wellweek@outlook.com>
2024-04-18 12:24:40 +08:00
zhengkunwang 4832081b82 fix: 解决文件上传权限错误的问题 (#4563) 2024-04-18 03:18:07 +00:00
ssongliu ef5a13f87f fix: 解决本地备份账号路径获取错误的问题 (#4560) 2024-04-18 02:36:06 +00:00
老广 e2bc8e800a docs: add projects to other readme (#4561) 2024-04-18 10:26:00 +08:00
老广 85a397949e Update README.md
Add our other projects
2024-04-17 17:08:35 +08:00
ssongliu 5cfdcedf9a fix: 解决容器日志缺失的问题 (#4549) 2024-04-17 08:52:07 +00:00
ssongliu c5bb8dab8c fix: 解决复制导致的快照失败问题 (#4547) 2024-04-17 07:28:07 +00:00
zhengkunwang f1421ba7f9 style: 修改文件编辑器配置样式 (#4535) 2024-04-16 18:59:30 +08:00
ssongliu 9e44db53a7 pref: 调整部分界面的高度问题 (#4534) 2024-04-16 18:59:14 +08:00
zhengkunwang 4814a76c47 style: 修改 openresty 的端口提示 (#4532) 2024-04-16 15:58:51 +08:00
ssongliu 2ec4b2561c feat: 修改服务初始化操作逻辑 (#4530) 2024-04-16 14:44:07 +08:00
wanghe 4efe7240ad Update tyops-check.yml 2024-04-16 13:57:03 +08:00
ssongliu ed13136221 feat: 增加许可证解绑功能 (#4522) 2024-04-15 18:37:05 +08:00
ssongliu 735fc75e35 feat: 登陆时加载许可证状态 (#4520) 2024-04-15 18:06:38 +08:00
xuwei-fit2cloud b8a52ae707 chore: 添加拼写检查Action 2024-04-15 17:58:36 +08:00
John Bro 2501a9f78a fix: 修复容器未指定 IP 编辑显示报错问题 (#4519) 2024-04-15 07:12:10 +00:00
Langel 5a8d6db43e feat: 增加菜单标签页功能 (#3267) (#4510) 2024-04-15 15:10:24 +08:00
ssongliu a3312331d2 feat: 解决登录页授权 IP 跳转失败的问题 (#4517) 2024-04-15 07:10:10 +00:00
zhengkunwang fbd0e836d7 fix:解决新建文件夹部分权限无法创建的问题 (#4515) 2024-04-15 07:08:10 +00:00
Craftsman 4935d88301 typos: Modify wrong words (#4518) 2024-04-15 15:05:00 +08:00
zhengkunwang 8625979cc3 feat: 修改 supervisor 名称限制 (#4513) 2024-04-15 03:56:10 +00:00
zhengkunwang c77742e72e fix: 解决安装应用时候,原始 docker-compose.yml 的 deploy 字段下内容会被自动覆写的问题 (#4512)
Refs https://github.com/1Panel-dev/1Panel/issues/4340
2024-04-15 03:54:09 +00:00
John Bro 43a6b4b735 feat: 容器创建支持指定 IP 地址 (#4489)
#### 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-04-13 11:56:07 +00:00
mobeicanyue f4ee534101 fix typos (#4503) 2024-04-13 19:49:17 +08:00
ssongliu c055a07e57 fix: 解决概览页 CPU 核心过多导致的显示问题 (#4490)
Refs #3966
2024-04-13 11:44:07 +00:00
ssongliu 62bead5c50 fix: 修改登录页跳转问题 (#4487) 2024-04-12 08:20:07 +00:00
zhengkunwang 9edafc13a3 feat: 修改中国地区名称 (#4485) 2024-04-12 04:30:07 +00:00
zhengkunwang e6ccf8be1f fix: 解决文件搜索目录包含回收站目录能进入的问题 (#4484) 2024-04-12 04:08:06 +00:00
ssongliu 222089d56a fix: 修改系统版本比较方法 (#4483) 2024-04-12 03:04:06 +00:00
zhengkunwang 96e48847d0 fix: 解决特殊字符下文件放入回收站失败的问题 (#4480) 2024-04-11 11:08:11 +00:00
zhengkunwang 38b0c42555 feat: 增加回收站文件夹限制 (#4479) 2024-04-11 11:06:10 +00:00
zhengkunwang 2eae8274f6 fix: 创建文件增加非法字符校验 (#4478) 2024-04-11 10:32:11 +00:00
ssongliu 0de79e537e fix: 修改高级功能菜单 LOGO (#4477) 2024-04-11 10:30:11 +00:00
zhengkunwang e37b87d63e fix: 解决安装应用时 deploy 字段被覆盖的问题 (#4476) 2024-04-11 10:28:11 +00:00
ssongliu 37a0329d4a fix: 解决登录页跳转错误的问题 (#4474) 2024-04-11 09:16:10 +00:00
ssongliu e037b69f52 fix: 解决登录页跳转错误的问题 (#4470) 2024-04-11 15:53:22 +08:00
zhengkunwang 34498392dc style(waf): 修改网站图标位置 (#4469) 2024-04-11 07:00:13 +00:00
ssongliu 1687b99526 feat: 增加 GPU 监控 (#4468) 2024-04-11 06:16:10 +00:00
ssongliu 0bf6db1cf8 fix: 解决僵尸进程的问题 (#4464) 2024-04-10 14:18:08 +00:00
ssongliu 0cd06803e2 fix: 监控数据迁移增加数据长度判断 (#4454) 2024-04-10 06:06:08 +00:00
guangwu 45f6a415de fix: close resource (#4456) 2024-04-10 14:05:07 +08:00
John Bro 3e3f0d9452 feat: 未认证设置状态下系统跳转页面功能实现 (#4444)
#### 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-04-09 14:46:09 +00:00
zhengkunwang 35785f1e40 feat: 增加文件修改权限的超时时间 (#4448)
Refs https://github.com/1Panel-dev/1Panel/issues/4291
2024-04-09 09:34:09 +00:00
zhengkunwang 437d6eb0fd feat: 网站名称支持带端口 (#4446)
Refs https://github.com/1Panel-dev/1Panel/issues/3726
2024-04-09 09:32:10 +00:00
zhengkunwang 1e2c85e771 feat: 文本编辑器记住上次设置 (#4445)
Refs https://github.com/1Panel-dev/1Panel/issues/4397
Refs https://github.com/1Panel-dev/1Panel/issues/4266
2024-04-09 09:30:09 +00:00
zhengkunwang ef075df3fe feat: 进程守护名称支持-_等字符 (#4442)
Refs https://github.com/1Panel-dev/1Panel/issues/4303
2024-04-09 08:38:09 +00:00
zhengkunwang 1e22769c8b feat: 文件上传权限优化 (#4441)
Refs https://github.com/1Panel-dev/1Panel/issues/4362
2024-04-09 08:36:09 +00:00
zhengkunwang 0751326b46 feat: 优化文件创建 (#4440) 2024-04-09 07:34:08 +00:00
zhengkunwang db4973cfde fix: 解决读取 block 文件报错的问题 (#4439) 2024-04-09 06:28:09 +00:00
zhengkunwang 0b47d692b8 fix: 解决 upstream 配置报错的问题 (#4438)
Refs https://github.com/1Panel-dev/1Panel/issues/4258
2024-04-09 06:16:09 +00:00
zhengkunwang 672ef66be8 fix: 解决 docker-compose 文件错误导致的异常 (#4431) 2024-04-08 09:54:07 +00:00
ssongliu 7ab005540b fix: 切换菜单设置接口 (#4429) 2024-04-08 09:48:08 +00:00
clonefetch 7c928735cd chore: remove repetitive word (#4430)
Signed-off-by: clonefetch <c0217@outlook.com>
2024-04-08 17:47:17 +08:00
zhengkunwang 9ec1610f34 feat: 增加一些翻译 (#4428) 2024-04-08 09:06:08 +00:00
ssongliu 4f2a340212 fix: 修改监控相关接口 (#4427) 2024-04-08 08:52:07 +00:00
ssongliu e69fd3b388 pref: 优化 OSS 连接检测错误信息 (#4426) 2024-04-08 06:44:07 +00:00
ssongliu 6991c773da feat: 监控数据使用独立数据库 (#4424) 2024-04-08 05:48:07 +00:00
ssongliu 9b88979b69 fix: 解决替换根目录失败的问题 (#4420) 2024-04-07 23:21:15 +08:00
ssongliu 7c9608727d fix: 解决升级失败的问题 (#4419) 2024-04-07 14:09:46 +00:00
ssongliu 62828c464e fix: 解决文件保存失败一直处于加载中的问题 (#4418) 2024-04-07 21:38:59 +08:00
ssongliu f1ed051135 feat: 增加概览页跳转方式 (#4417) 2024-04-07 09:29:46 +00:00
zhengkunwang 33a0c2ba30 feat: 删除网站适配防篡改 (#4415) 2024-04-07 09:25:46 +00:00
ssongliu 494da3c209 fix: 修改 xpack 依赖 (#4414) 2024-04-07 08:29:46 +00:00
wanghe 646fc4e678 Update .gitattributes 2024-04-07 11:59:42 +08:00
ssongliu cdbe24a523 fix: 修改升级等过程中复制方式 (#4402) 2024-04-06 14:32:10 +00:00
maninhill 10012c6148 chore:更新 README 2024-04-04 08:28:43 +08:00
zhengkunwang bb4fd69f3a fix: 解决没有 xpack 打包报错的问题 (#4400) 2024-04-03 09:40:07 +00:00
John Broandzhoujunhong 6ff0f14841 fix: 专业版菜单选择显示前端优化 (#4399)
Co-authored-by: zhoujunhong <1298308460@qq.com>
2024-04-03 17:27:55 +08:00
ssongliu 1088034a5c feat: 计划任务容器中执行增加 Command 选项 (#4398)
Refs #4363
2024-04-03 08:50:07 +00:00
zhengkunwang 8a011b1cf9 feat: 优化文件上传体验 (#4396) 2024-04-03 08:40:07 +00:00
Node e3eebc938a 提升文件上传体验 (#4393)
* 上传文件支持覆盖被占用的文件且上传失败时不影响原文件,增加强制覆盖选项

* 原文件存在时,保持同样的文件权限

* 制表符更新
2024-04-03 14:37:22 +08:00
John Bro 08da7802c6 feat: 专业版菜单隐藏控制功能实现 (#4392) 2024-04-03 14:11:12 +08:00
ssongliu 79ca1b4d6b pref: 专业版界面增加图标显示 (#4391) 2024-04-02 09:16:46 +00:00
ssongliu 47119e508b fix: 解决容器挂载卷回显失败的问题 (#4389)
Refs #4387
2024-04-02 09:14:45 +00:00
ssongliu 13bb5e0d30 pref: 优化界面设置提示信息 (#4382) 2024-04-01 10:52:06 +00:00
zhengkunwang 218c165c26 feat: 删除部分 .gitignore (#4380) 2024-04-01 09:52:07 +00:00
zhengkunwang d6006d34c0 feat: 修改 WAF 专业版提示 (#4377) 2024-04-01 08:38:08 +00:00
ssongliu 810ea03ac1 pref: 优化 Xpack 错误信息 (#4376) 2024-04-01 08:28:07 +00:00
ssongliu e046315616 pref: 修改描述信息 (#4375) 2024-04-01 08:00:08 +00:00
zhengkunwang 0a7f1eb286 fix: 处理时序攻击漏洞 (#4373) 2024-04-01 07:52:07 +00:00
wanghe-fit2cloud b0411b2df6 feat: 增加构建标签 2024-04-01 15:22:51 +08:00
ssongliu 471b7d9657 style: 调整 Logo 样式 (#4357) 2024-03-29 09:24:07 +00:00
ssongliu 1527c63d37 feat: 同步数据库中界面设置数据 (#4355) 2024-03-29 07:50:06 +00:00
zhengkunwang 0ebeeaebd6 fix: 解决 WAF 首次安装创建网站没有生效的问题 (#4354) 2024-03-29 07:04:06 +00:00
zhengkunwang 1ae5bef40d feat: 调整 waf 配置文件生成顺序 (#4339) 2024-03-28 08:10:07 +00:00
zhengkunwang 9b9fe5444d feat: 创建网站增加 waf 关联 (#4333) 2024-03-27 10:36:08 +00:00
ssongliu a5d853c61a fix: 解决界面设置无法上传大图片的问题 (#4332) 2024-03-27 10:34:07 +00:00
zhengkunwang 6610a3debe fix: 解决前端打包报错问题 (#4331) 2024-03-27 10:26:07 +00:00
zhengkunwang 6b244b2b03 feat: 删除旧版本 WAF 配置 (#4327) 2024-03-27 07:42:07 +00:00
刘瑞斌 0b26d70927 chore: 增加 JetBrains Toolbox 图标 (#4325) 2024-03-27 13:47:05 +08:00
ssongliu 18faed79f3 style: 调整 Xpack 界面样式与菜单位置 (#4323) 2024-03-27 03:32:07 +00:00
zhengkunwang b0cc1248f0 feat: 删除 xpack 的翻译 (#4322) 2024-03-27 03:24:06 +00:00
ssongliu 8013c7fd96 fix: 修改 Xpack 路由路径 (#4317) 2024-03-26 09:40:07 +00:00
ssongliu 306082d625 feat: 完善界面设置功能 (#4316) 2024-03-26 08:44:07 +00:00
刘瑞斌 821b8dfe63 chore: 去掉某些字体 (#4315) 2024-03-26 16:15:58 +08:00
ssongliu 8d88ed812c fix: 修改 License 状态判断 (#4309) 2024-03-26 06:08:07 +00:00
zhengkunwang aa93a6d373 feat: 删除 plugins 目录 (#4311) 2024-03-26 06:00:07 +00:00
ssongliu b4e8c8503c feat: 调整 License 导入样式 (#4305) 2024-03-25 22:49:39 +08:00
zhengkunwang 4de733e084 feat: 优化网站目录显示 (#4301)
Refs https://github.com/1Panel-dev/1Panel/issues/4280
2024-03-25 09:58:07 +00:00
ssongliu 6c67dc6ed4 feat: 完善 License 导入错误显示 (#4297) 2024-03-25 07:12:07 +00:00
ssongliu ba6991bfb6 feat: 增加 License 导入界面 (#4295) 2024-03-25 04:20:07 +00:00
zhengkunwang 33309d2d93 feat(waf): 优化配置文件读取方式 (#4288) 2024-03-23 14:04:06 +00:00
zhengkunwang 1b2208a4d0 feat(waf): 增加 IP 组功能 (#4277) 2024-03-22 10:58:08 +00:00
刘瑞斌 e3fb97fc8c ci: 替换过时的组件 (#4273) 2024-03-22 14:33:48 +08:00
an4er 1ff5bf85e9 fix: 解决命令注入waf被绕过的问题 (#4268) 2024-03-22 14:18:47 +08:00
wanghe-fit2cloud 062b2f2ae6 chore: 修改同步仓库地址 2024-03-22 14:17:40 +08:00
wanghe-fit2cloud 44445aca93 chore: 修改使用手册显示信息 2024-03-21 22:36:32 +08:00
dependabot[bot] 94de830637 chore(deps): bump github.com/docker/docker (#4256)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.3+incompatible to 25.0.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v25.0.3...v25.0.5)

---
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-03-21 18:54:22 +08:00
Shang Yuanchun 9160726127 解决部分操作系统上 platformVersion 为空导致的显示问题 (#4259)
* fix: 解决部分操作系统上 platformVersion 为空导致的显示问题

例如archlinux,显示的是:arch-

* fix: 修改表达式 (#4259)
2024-03-21 18:54:13 +08:00
wanghe 3cd0c89c4c Update CONTRIBUTING.md 2024-03-20 22:36:07 +08:00
ssongliu c27fcf18fc feat: 升级依赖 fit2cloud-ui-plus (#4250) 2024-03-20 10:50:07 +00:00
zhengkunwang b1f20d125e fix: 解决 i18n 导致页面错误的问题 (#4247) 2024-03-20 08:22:07 +00:00
zhengkunwang cd43bff44d fix: 解决文件选择框关闭再打开依然选中的问题 (#4243) 2024-03-20 03:32:07 +00:00
ssongliu 8cf8375911 feat: 镜像拉取日志 Unicode 转义 (#4239) 2024-03-19 14:40:06 +00:00
zhengkunwang ebc3195df4 feat: ACL 规则增加匹配方式 (#4237) 2024-03-19 12:58:06 +00:00
zhengkunwang ce69f6a142 fix: 解决应用安装快速跳转失败的问题 (#4236) 2024-03-19 09:12:07 +00:00
zhengkunwang f6777d51b5 fix: 解决应用安装快速跳转失败的问题 (#4235) 2024-03-19 07:36:06 +00:00
zhengkunwang d916f2ba6f fix: 解决网站配置文件更新之后参数丢失的问题 (#4231)
Refs https://github.com/1Panel-dev/1Panel/issues/4171
2024-03-19 05:50:06 +00:00
zhengkunwang 0e159f944a feat: 修改应用已安装和可升级页面的标签选择 (#4229) 2024-03-18 14:34:10 +00:00
Anye 329bb94d3c 增加PHP模板 (#4227)
* PHP模板增加“苹果CMS-V10”

* PHP增加“SeaCMS”扩展模板
2024-03-18 22:32:06 +08:00
zhengkunwang 7f2f7fa195 feat(waf): 优化配置更新接口 (#4226) 2024-03-18 13:16:10 +00:00
ssongliu 67c79bcde4 fix: 还原 Makefile 内容 (#4225) 2024-03-18 09:50:08 +00:00
zhengkunwang d7d7c7fd73 feat(waf): 增加内部接口请求安全性 (#4224) 2024-03-18 09:36:08 +00:00
ssongliu 94028d97cf style: 修改重启面板及服务器按钮位置 (#4221) 2024-03-18 09:34:08 +00:00
zhengkunwang cbfedb5d5e feat(waf): 优化请求次数记录方式 (#4210) 2024-03-16 14:44:06 +00:00
ssongliu 7784ee9575 fix: 解决快照恢复时校验 daemon.json 路径失败的问题 (#4205)
Refs #4206
2024-03-16 02:12:06 +00:00
ssongliu 29d5d82fa5 feat: 增加 xpack Makefile (#4204) 2024-03-15 09:26:08 +00:00
zhengkunwang 46f01032b8 fix: 解决回收站文件放置错误问题 (#4203)
Refs https://github.com/1Panel-dev/1Panel/issues/4199
2024-03-15 08:40:07 +00:00
zhengkunwang 341f45b4ef feat: 修改网站导入证书的描述 (#4202)
Refs https://github.com/1Panel-dev/1Panel/issues/4184
2024-03-15 08:08:07 +00:00
ssongliu a0907592b2 fix: 解决未登录情况下的页面跳转问题 (#4195) 2024-03-15 08:00:08 +00:00
zhengkunwang 825ebd19fb feat: 守护进程增加加载状态 (#4201)
Refs https://github.com/1Panel-dev/1Panel/issues/4129
2024-03-15 07:40:08 +00:00
zhengkunwang 8f24432a01 feat: 网站搜索增加网站关联域名匹配 (#4198)
Refs https://github.com/1Panel-dev/1Panel/pull/4198
2024-03-15 07:38:08 +00:00
zhengkunwang 6ce35b723b fix: 解决 Node 运行环境状态异常问题 (#4197) 2024-03-15 07:36:07 +00:00
ssongliu a33eabb4f0 fix: 移除计划任务报告中的 ANSI 转义 (#4193)
Refs #4097
2024-03-15 03:56:07 +00:00
ssongliu 34a6587ebc fix: 升级依赖 jackc/pgx/v5 (#4190) 2024-03-15 02:30:07 +00:00
ssongliu 17020ffe9f fix: 解决计划任务列表备份账号显示问题 (#4187)
Refs #4096
2024-03-14 10:24:07 +00:00
ssongliu 0f9b0d5d82 feat: 同步数据库时标记已删除数据库 (#4185)
Refs #4165
2024-03-14 10:22:07 +00:00
zhengkunwang be36103475 feat: 增加应用商店显示的过滤数量 (#4183) 2024-03-14 08:02:06 +00:00
ssongliu a3b5869c2b fix: 移除未使用依赖 2024-03-14 15:19:39 +08:00
maninhill 0807da41a5 Update README.md 2024-03-14 15:19:04 +08:00
maninhill ea6870e3c9 Update README.md 2024-03-14 15:07:13 +08:00
ssongliu 70b7daf23a fix: 升级依赖 google.golang.org/protobuf (#4176) 2024-03-14 03:24:06 +00:00
建国 1d01fa1e14 fix: 解决计划任务报告刷新异常的问题 (#4177)
Refs #4175
2024-03-14 03:14:06 +00:00
Akirami b643fa2ca3 fix: 修复应用商店详情页面部分图标比例错误 (#4168)
* fix: 修复应用商店详情页面部分图标比例错误

* fix: 替换为内联样式
2024-03-13 22:28:03 +08:00
zhengkunwang 77f4f64f0d feat(waf): 优化规则 (#4166) 2024-03-12 12:34:07 +00:00
ssongliu 3b697c7520 feat: 快照支持设置排除目录 (#4154)
Refs #4076
2024-03-12 03:04:08 +00:00
刘瑞斌 8ca6b9b6fa chore: add pnpm-lock.yaml to .gitignore (#4162) 2024-03-12 10:40:37 +08:00
zhengkunwang 0b66be7a3a fix: 解决安装应用过程中出现的状态异常问题 (#4156) 2024-03-11 12:34:08 +00:00
ssongliu f9f68a975c feat: 增加界面设置功能 (#4128) 2024-03-11 09:12:08 +00:00
L1nyz-tel ba63907a65 fix: 解决命令注入waf被绕过的问题 (#4131) 2024-03-08 22:43:56 +08:00
zhengkunwang 2687c91e65 feat: 升级 github.com/go-jose/go-jose/v3 版本到 v3.0.3 (#4126) 2024-03-08 08:50:06 +00:00
zhengkunwang eb82c168c5 feat: 升级 github.com/go-jose/go-jose/v3 版本到 v3.0.3 (#4121) 2024-03-08 08:14:07 +00:00
ssongliu 21b1242a94 fix: 解决部分 OneDrive 上传大文件失败的问题 (#4117)
Refs #4125
2024-03-08 08:12:06 +00:00
zhengkunwang a6784772b9 feat: 增加一个外部访问地址校验规则 (#4109)
Refs https://github.com/1Panel-dev/1Panel/issues/4091
2024-03-07 10:19:06 +00:00
igophper cf4fcaa07f fix: 解决编辑ssh端口同步到终端本地服务器问题 (#4043) (#4093) 2024-03-07 11:48:00 +08:00
zhengkunwang 62b1bb3191 feat: 升级 element-plus 到 2.6.0 (#4092) 2024-03-07 03:07:06 +00:00
ssongliu 8ce066a9d4 fix: 修改升级过程中的架构判断 (#4102) 2024-03-07 03:05:05 +00:00
wanghe-fit2cloud 7d8da406cf feat: 增加论坛链接 2024-03-07 10:52:00 +08:00
mobeicanyue b3d6b2b0de Issue 跳转 GitHub discussions (#4090) 2024-03-07 10:47:53 +08:00
zhengkunwang 21f24f2e10 feat(waf): 增加全局设置 (#4088) 2024-03-06 10:16:08 +00:00
ssongliu fe717d993b fix: 快照重试后更新列表状态 (#4086) 2024-03-06 07:58:07 +00:00
zhengkunwang 703da8a09a fix: 解决网站日志查看报错的问题 (#4082)
Refs https://github.com/1Panel-dev/1Panel/issues/3789
2024-03-06 03:56:07 +00:00
ssongliu a99f19471b fix: 优化计划任务脚本执行输出 (#4080) 2024-03-06 02:36:07 +00:00
zhengkunwang ca3be03ae7 feat: 本地应用同步增加校验 (#4071) 2024-03-05 10:10:08 +00:00
ssongliu 71ca8016e1 fix: 解决命令执行超时未生效的问题 (#4070) 2024-03-05 10:06:08 +00:00
ssongliu 5633bdd41e fix: 快照前清空服务器地址 (#4068) 2024-03-05 04:04:07 +00:00
ssongliu ab11d7257c fix: 快照重试时忽略已成功步骤 (#4067) 2024-03-05 03:14:07 +00:00
ssongliu 39b8de7ada fix: 限制快照跨架构恢复 (#4064)
Refs #4056
2024-03-04 14:59:11 +00:00
zhengkunwang c49c4b7863 feat(waf): 增加概览页 (#4060) 2024-03-04 08:53:06 +00:00
ssongliu d42df7184c style: 修改主机记住密码显示标题 (#4061) 2024-03-04 08:37:05 +00:00
ssongliu e7f172520a pref: 优化 MySQL 备份恢复方式 (#4059)
Refs #3955
2024-03-04 08:35:05 +00:00
zhengkunwang dca29174ea feat: 同步本地应用增加 data.yml 正确性验证 (#4058)
Refs https://github.com/1Panel-dev/1Panel/issues/4052
2024-03-04 03:53:03 +00:00
ssongliu 94181538a9 fix: 解决单位转换导致的内存限制问题 (#4055)
Refs #4040
2024-03-04 03:29:04 +00:00
zhengkunwang e9cc7bf1cf feat(waf):增加请求日志统计 (#4048) 2024-03-02 14:45:02 +00:00
ssongliu fa1e945b8d pref: 处理代码警告 (#4036) 2024-03-01 08:55:02 +00:00
zhengkunwang 94702a79a4 feat(waf): 修改攻击日志格式 (#4024) 2024-03-01 05:53:02 +00:00
ssongliu 24f573f9fd fix: 修改快照恢复逻辑 (#4031) 2024-03-01 13:51:34 +08:00
ssongliu 7e4606eda5 feat: 增加防篡改功能 (#4016) 2024-03-01 05:51:01 +00:00
ssongliu 73e118a3e5 style: 封装表格搜索组件 (#4028)
Refs #4017
2024-03-01 03:21:01 +00:00
ssongliu 514d68d395 fix: 解决容器资源使用率获取错误的问题 (#4023)
Refs #4022
2024-02-29 03:08:18 +00:00
QuentinHsu d0bb07796b fix: 初始化版本号变量为字符串类型,默认为空字符串 (#4003) 2024-02-28 17:10:47 +08:00
ssongliu 66a451fc1f fix: 解决登陆页刷新跳转的问题 (#4014)
Refs #3739
2024-02-28 09:06:15 +00:00
zhengkunwang c13e2a36cc feat: 增加 x-Pack 菜单 (#4009) 2024-02-28 08:44:15 +00:00
zhengkunwang 2affd2bc79 feat: 增加 waf 模块 (#4008) 2024-02-28 06:34:09 +00:00
QuentinHsu c510a028db fix: 解决重复访问容器内部时,终端显示宽度异常的问题 (#3996) 2024-02-27 14:54:57 +08:00
ssongliu 121a1ff5da fix: 修改快照 json 文件生成逻辑 (#3993) 2024-02-27 03:58:12 +00:00
ssongliu 1ecc53dad3 feat: 面板帮助文档跳转到具体目录 (#3992) 2024-02-27 03:30:13 +00:00
zhengkunwang 279b3942b4 fix: 解决应用同步异常的问题 (#3991) 2024-02-27 03:28:12 +00:00
ssongliu a0af04bbf7 fix: 调整快照回滚逻辑 (#3989) 2024-02-27 02:20:12 +00:00
ssongliu d5d1125660 fix: 解决快照恢复过程中生成回滚文件不完整的问题 (#3987) 2024-02-26 22:45:52 +08:00
wanghe-fit2cloud 08801ebfcd chore: Update README.md 2024-02-26 18:05:40 +08:00
zhengkunwang 262cbb9405 fix: 解决删除 openresty 之后没有同步删除 php 运行容器的问题 (#3984) 2024-02-26 10:04:12 +00:00
zhengkunwang 96233ee9b2 fix: 解决拖动上传文件夹错误的问题 (#3983) 2024-02-26 10:02:11 +00:00
ssongliu 49dcb094dc fix: 解决容器镜像清理的显示问题 (#3980) 2024-02-26 06:14:10 +00:00
zhengkunwang f0d5aca657 fix: 解决应用重建失败的问题 (#3967) 2024-02-23 10:38:40 +00:00
zhengkunwang 58f58f0fac fix: 解决清空文件后保存失败的问题 (#3965) 2024-02-23 10:36:40 +00:00
QuentinHsu 3bd6f46fe2 perf: 应用商店的应用介绍样式 (#3961) 2024-02-22 23:03:56 +08:00
zhengkunwang e427ccf70c feat: 修改应用重建逻辑 (#3953) 2024-02-22 09:12:08 +00:00
ssongliu 1b47e8dd63 fix: 解决 Pr 导致的代码冲突 (#3949) 2024-02-22 07:34:07 +00:00
ssongliu b668f55222 fix: 移除项目未使用代码 (#3948) 2024-02-22 07:32:07 +00:00
wanghe-fit2cloud 9a127d9afe chore: 升级 Node.js 和 Golang 版本 2024-02-22 15:23:20 +08:00
ssongliu 63484601b5 fix: 忽略 WebDAV 获取端口信息错误日志 (#3946) 2024-02-22 05:42:06 +00:00
ssongliu ef5e42cf0b fix: 解决远程数据库添加验证失败的问题 (#3942) 2024-02-21 11:08:29 +00:00
ssongliu 50a83e7e11 fix: Cookie 启用 httponly (#3941) 2024-02-21 11:06:28 +00:00
ssongliu 2475c82a63 fix: 修改 Mysql 远程数据库添加校验 (#3939)
Refs #3936
2024-02-21 09:00:30 +00:00
ssongliu 61ff55e157 pref: 计划任务数据库备份增加实例标签 (#3938)
Refs #3937
2024-02-21 08:58:28 +00:00
ssongliu dbc7799e89 fix: 解决代码冲突 (#3930) 2024-02-20 12:30:08 +00:00
wangdan-fit2cloud 6275e06806 feat: 样式优化 (#3928) 2024-02-20 18:20:45 +08:00
ssongliu e4f9ec32cd fix: 快照时排除备份目录下的快照文件 (#3926)
Refs #3923
2024-02-20 08:36:08 +00:00
ssongliu 319c21fc36 style: 修改备份账号提示信息 (#3925) 2024-02-20 08:18:08 +00:00
ssongliu e346442d36 fix: 解决备份账号异常导致备份记录无法删除的问题 (#3922)
Refs #3919
2024-02-20 07:02:07 +00:00
ALMAS 1781362342 pref: 优化应用安装表单选择器组件支持多选 (#3918) 2024-02-20 14:58:58 +08:00
wanghe-fit2cloud 3035bb2bf6 chore: Fix typos 2024-02-19 18:02:09 +08:00
maninhill b896a5be3b Update README.md 2024-02-19 17:20:56 +08:00
maninhill d51abd529a Update README.md 2024-02-19 17:19:57 +08:00
zhengkunwang 550eed7962 feat: 升级依赖 (#3907) 2024-02-19 05:55:27 +00:00
mobeicanyue 51d09b7164 Fix typos (#3897)
* Fix typos in backend folder

* Fix typos in frontend

* Fix typos in frontend

* Fix Percent typo
2024-02-17 22:42:09 +08:00
moonrailgun 046d8f526b fix: fix typo for i18n-en (#3887) 2024-02-15 23:12:23 +08:00
zhengkunwang 5bf121b687 feat: 限制文件最大上传个数 (#3867) 2024-02-09 01:52:09 +00:00
zhengkunwang e6a43d22c8 feat: 优化应用状态同步 (#3866) 2024-02-09 01:50:11 +00:00
zhengkunwang 3fd7aa17a8 fix: 解决拖拽上传文件夹文件上传不全的问题 (#3865)
Refs https://github.com/1Panel-dev/1Panel/issues/3848
2024-02-08 08:00:09 +00:00
ssongliu 6a903b7fdf fix: 解决容器监控单位不正确的问题 (#3857)
Refs #3853
2024-02-07 14:30:09 +00:00
ssongliu 958f9a1aad fix: 修改容器镜像仓库正则表达式 (#3856)
Refs #3851
2024-02-07 14:28:08 +00:00
zhengkunwang e48830cd99 feat: 升级 Go 版本到 1.21 (#3847) 2024-02-06 08:36:13 +00:00
ssongliu ece2cb433d style: 系统快照恢复样式调整 (#3845) 2024-02-06 06:20:14 +00:00
zhengkunwang fb06e29aa3 feat: Nginx 配置支持 Lua 模块 (#3844) 2024-02-06 06:02:12 +00:00
ssongliu 4fc26a3061 fix: 解决升级后备份数据未同步的问题 (#3837) 2024-02-05 17:56:47 +08:00
ssongliu d8669b90bf feat: WebDAV 支持连接 Alist (#3836)
Refs #3495
2024-02-05 09:28:12 +00:00
ssongliu 9e980e8e0c feat: SFTP Client 增加可连接性校验 (#3834) 2024-02-05 07:54:13 +00:00
zhengkunwang 140cd564da feat: 适配多容器应用 (#3832) 2024-02-05 07:00:13 +00:00
ssongliu b40087c88c fix: 解决版本升级时,过滤备份失败的备份文件 (#3831)
Refs #3826
2024-02-05 06:24:21 +00:00
ssongliu 8e620172e6 fix: 解决删除备份文件路径错误的问题 (#3829) 2024-02-05 10:18:43 +08:00
ssongliu 9b8b9f505b fix: 修改计划任务执行周期校验 (#3821) 2024-02-04 14:50:38 +00:00
ssongliu 1169648162 feat: 面板开启 https 时,cookie 开启 secure 属性 (#3817) 2024-02-04 22:46:33 +08:00
zhengkunwang c169f211a0 feat: 限制版本为 latest 应用升级 (#3816) 2024-02-04 10:15:41 +00:00
zhengkunwang eef445c1eb style: 应用忽略升级样式修改 (#3809) 2024-02-04 04:02:54 +00:00
zhengkunwang bedb8ce286 style: 修改应用详情页面样式 (#3808) 2024-02-04 04:01:14 +00:00
ssongliu 3652c46ac9 fix: 解决移动设备下,容器名称丢失的问题 (#3805)
Refs #3798
2024-02-04 03:58:52 +00:00
ssongliu 0449868d3b fix: 修改同步快照过程中的名称校验 (#3797) 2024-02-03 05:14:22 +00:00
ssongliu fdf9046430 fix: 全局备份目录变量给定初始值 (#3787) 2024-02-02 10:47:24 +08:00
ssongliu b3852db39e fix: 解决网站日志切割备份路径错误的问题 (#3785) 2024-02-01 23:54:59 +08:00
ssongliu 2f7bb6d784 fix: 解决本地备份账号无法切换到根目录的问题 (#3782) 2024-02-01 22:36:01 +08:00
ssongliu e5d3c1bb86 feat: 计划任务备份增加随机后缀 (#3778) 2024-02-01 18:35:57 +08:00
ssongliu c758c1ce87 fix: 解决计划任务记录删除时文件被误删问题 (#3777) 2024-02-01 18:05:40 +08:00
ssongliu d82086ce4a fix: 解决 Redis rbd 规则设置报错的问题 (#3776) 2024-02-01 18:04:02 +08:00
ssongliu edc3540e67 pref: 优化容器列表选项列选项 (#3775) 2024-02-01 07:01:41 +00:00
ssongliu 13a69fa1ed fix: 解决 Redis 终端输入 Ctrl + C 时,浏览器崩溃的问题 (#3774)
Refs #3754
2024-02-01 06:33:41 +00:00
ssongliu 948653213e style: 修改计划任务报告页面样式 (#3772) 2024-02-01 06:29:40 +00:00
ssongliu a4cfb587d6 pref: 优化同步快照返回信息 (#3773) 2024-02-01 06:23:40 +00:00
zhengkunwang 6aa9a95971 fix: 解决切换运行环境之后 PHP 高级设置还存在的问题 (#3771)
Refs https://github.com/1Panel-dev/1Panel/issues/3762
2024-02-01 06:21:40 +00:00
ssongliu e3666d17a3 fix: 解决应用恢复计划任务执行失败的问题 (#3770) 2024-01-31 20:38:01 +08:00
zhengkunwang 7219566273 fix: 解决文件下载路径包含 + 下载失败的问题 (#3768) 2024-01-31 12:23:59 +00:00
ssongliu 08a45e9723 fix: 解决应用恢复导致计划任务执行失败的问题 (#3769) 2024-01-31 10:47:21 +00:00
ssongliu be53945f0e fix: 解决升级导致计划任务数据丢失的问题 (#3766) 2024-01-31 16:07:19 +08:00
ssongliu 725ad3f6d9 fix: 暂时移除网站备份规则功能 (#3764) 2024-01-31 15:08:34 +08:00
ssongliu 42828d0a48 feat: 适配世纪互联版本 OneDrive (#3763) 2024-01-31 15:08:14 +08:00
ssongliu 3804942ae4 pref: 优化计划任务备份账号切换逻辑 (#3758) 2024-01-31 15:08:03 +08:00
ssongliu 83dda25591 fix: 解决镜像清理列表显示不全的问题 (#3759) 2024-01-31 14:50:47 +08:00
ssongliu f91d7c6170 fix: 解决数据升级失败的问题 (#3755) 2024-01-30 23:25:29 +08:00
ssongliu 2cf3b27402 style: 修改同步服务器时间计划任务提示信息 (#3751) 2024-01-30 16:11:58 +08:00
ssongliu 0e4f82c434 fix: 解决快照恢复失败的问题 (#3750) 2024-01-30 16:11:49 +08:00
zhengkunwang 51d7a84062 feat: 安装升级应用增加强制拉取镜像选项 (#3746) 2024-01-30 16:11:41 +08:00
zhengkunwang 70863f411d fix: 解决扩展模版过长无法保存的问题 (#3749)
Refs https://github.com/1Panel-dev/1Panel/issues/3731
2024-01-30 06:54:11 +00:00
ssongliu 5c9315ff1c fix: 解决计划任务升级导致数据异常问题 (#3744) 2024-01-30 14:39:13 +08:00
ssongliu 3846dcef86 style: 快照状态界面样式调整 (#3748) 2024-01-30 14:39:00 +08:00
ssongliu 8b0d73b5d5 fix: 解决计划任务备份下载路径错误的问题 (#3743) 2024-01-29 10:52:38 +00:00
ssongliu e309b9c030 fix: 解决计划任务备份上传路径错误的问题 (#3742) 2024-01-29 16:35:06 +08:00
ssongliu a5304b66e3 fix: 解决服务重启后计划任务状态错误的问题 (#3741) 2024-01-29 16:34:57 +08:00
ssongliu 10eef260d4 perf: 优化计划任务备份列表打开速度 (#3740) 2024-01-29 16:34:49 +08:00
ssongliu 045dbaef91 fix: 数据库连接信息更改为容器名 (#3735) 2024-01-29 16:34:40 +08:00
zhengkunwang 7a45db71d5 fix: 解决 Node.js 运行环境网站停止失败的问题 (#3734) 2024-01-29 16:34:32 +08:00
ssongliu 0ee942cbef fix: 优化缓存清理界面样式 (#3733)
![image](https://github.com/1Panel-dev/1Panel/assets/73214554/30426b21-0bf8-4e3f-b59b-2016ae4b55b9)
2024-01-29 02:28:49 +00:00
ssongliu aa8429a836 fix: 计划任务列表样式调整 (#3718) 2024-01-26 14:44:11 +00:00
ssongliu 9f2227cf19 fix: 解决备份列表异常时,备份列表无法打开的问题 (#3717) 2024-01-26 17:08:19 +08:00
Leif 338ade095a fix: 解决日志中出现非UTF-8编码字符导致日志错误 (#3715) (#3716) 2024-01-26 16:07:37 +08:00
ssongliu d5432c8e59 fix: 界面表格样式调整 (#3714) 2024-01-26 15:08:36 +08:00
ssongliu 4eb34f24d1 feat: 页脚增加项目跳转地址 (#3712) 2024-01-26 13:46:13 +08:00
ssongliu 527c8143a1 feat: 增加备份恢复确认框 (#3711) 2024-01-26 13:46:04 +08:00
ssongliu a6891711b7 fix: 计划任务名称禁用空格 (#3710) 2024-01-26 13:45:56 +08:00
ssongliu 36a253ce03 feat: 主机快速命令增加排序 (#3707) 2024-01-26 13:45:41 +08:00
ssongliu bbe1161dc2 fix: 解决 Fail2ban 禁用方式切换失败的问题 (#3704) 2024-01-25 18:18:47 +08:00
ssongliu e85ed7427a fix: 解决接口文档描述不一致的问题 (#3703) 2024-01-25 18:18:26 +08:00
ssongliu 97f8a872c0 feat: PostgreSQL 远程数据库增加用户绑定提示信息 (#3701) 2024-01-25 18:18:10 +08:00
ssongliu 48187b508d fix: 解决登陆状态下跳转安全入口的问题 (#3702) 2024-01-25 18:17:26 +08:00
ssongliu ca1710cabc fix: WebDav 支持 https (#3700) 2024-01-25 18:17:13 +08:00
ssongliu df2a705713 fix: 调整快照页面备份路径样式 (#3696) 2024-01-25 17:24:09 +08:00
zhengkunwang d917008f47 style: 修改部分页面样式 (#3692) 2024-01-25 15:18:03 +08:00
ssongliu 61078a24fd feat: 快照支持本地目录及多选备份账号 (#3693) 2024-01-25 13:55:11 +08:00
ssongliu e35998a4be fix: 统一多行文本输入样式 (#3694) 2024-01-25 13:54:49 +08:00
ssongliu d1fde34a14 feat: 增加计划任务服务器时间同步提示信息 (#3695) 2024-01-25 13:54:32 +08:00
ssongliu 45394598d9 fix: 调整计划任务备份账号字段名 (#3691) 2024-01-25 11:20:42 +08:00
ssongliu edd6b52f05 feat: 计划任务支持备份到多个备份账号 (#3689) 2024-01-24 17:41:56 +08:00
zhengkunwang 0f53de56a7 feat: 容器列表增加选择列功能 (#3684) 2024-01-23 18:48:58 +08:00
zhengkunwang 4ce50328ba feat: 优化应用升级逻辑 支持 docker-compose.yml 文件的 image 值中包含参数 (#3681) 2024-01-23 15:24:13 +08:00
ssongliu 115b9c58b0 feat: 计划任务支持多周期执行 (#3676) 2024-01-22 22:29:19 +08:00
zhengkunwang 64def5bebd feat:证书 DNS 账号增加腾讯云类型 (#3673) 2024-01-22 22:28:52 +08:00
zhengkunwang e46f931989 style: 修改文件上传页面部分样式 (#3672) 2024-01-22 22:28:40 +08:00
dependabot[bot] 1a78aecfb1 build(deps-dev): bump vite from 2.9.16 to 2.9.17 in /frontend (#3669)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 2.9.16 to 2.9.17.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v2.9.17/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v2.9.17/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 10:41:44 +08:00
igophper e8c94450f4 fix: 删除带特殊符号文件的报错问题 (#3657) (#3658)
Refs #3657
2024-01-20 22:17:37 +08:00
ssongliu 6bc4cfc8ac feat: OneDrive 支持世纪互联 (#3655) 2024-01-19 23:06:41 +08:00
zhengkunwang 24579edad6 feat: 文件编辑器增加 lua xml 语言支持 (#3652) 2024-01-19 16:48:55 +08:00
ssongliu 6499d7f462 feat: OneDrive 增加 Token 刷新机制 (#3637) 2024-01-19 16:48:41 +08:00
zhengkunwang 1e5cd23bbe style: CloudFlare DNS 账号增加提示 (#3648) 2024-01-18 22:46:16 +08:00
zhengkunwang fce0ac2c7d fix: 反向代理的文本替换不兼容 " 符号的问题 (#3647) 2024-01-18 22:45:51 +08:00
zhengkunwang f5bf111203 fix: 解决网站日志清空页面没有刷新的问题 (#3645) 2024-01-18 17:05:22 +08:00
ssongliu 5152e7b8d5 feat: OneDrive 增加 Token 刷新机制 (#3638) 2024-01-18 10:38:25 +08:00
zhengkunwang d30be34def fix: 解决网站删除目录错误的问题 (#3632) 2024-01-17 18:15:59 +08:00
真心 1a9182bfde faet: 设置默认主题为 auto (#3634)
* faet: set default theme to `auto`
2024-01-17 18:12:52 +08:00
igophper aeb0549a9a fix: 解决Redis配置修改报错的问题 (#3627) (#3629)
Refs #3627
2024-01-17 10:14:40 +08:00
igophper 64f3bbb13b fix: sshd_config兼容prohibit-password (#3609) (#3626) 2024-01-16 21:10:37 +08:00
ssongliu bc0fbc4461 fix: 解决容器未启动时无法编辑计划任务的问题 (#3622) 2024-01-16 16:03:56 +08:00
ssongliu 1e5ea024ad fix: 解决部分 Onedrive 备份账号添加失败的问题 (#3621) 2024-01-16 16:03:45 +08:00
zhengkunwang f89365106a style: 容器列表页面适配手机端 (#3618) 2024-01-15 15:57:12 +08:00
ssongliu 4891de0a8a fix: 解决类型断言错误的问题 (#3619) 2024-01-15 15:57:02 +08:00
ssongliu be6b58b0a1 fix: WebDav 端口去掉必填校验 (#3617) 2024-01-15 15:07:00 +08:00
zhengkunwang 33910f7993 style: 容器列表页面适配手机端 (#3616) 2024-01-15 14:16:15 +08:00
ssongliu 7036ba0d5c fix: 移除备份账号保存份数最大限制 (#3615) 2024-01-15 14:16:05 +08:00
ssongliu 204a9fac87 fix: 删除 hosts 错误提示信息 (#3614) 2024-01-15 14:15:55 +08:00
zhengkunwang 6879b92db3 fix: 解决切换 PHP 版本扩展模版没办法继续选择的问题 (#3613) 2024-01-15 14:15:45 +08:00
ssongliu 54242ae220 fix: 解决本地备份路径无法编辑的问题 (#3612) 2024-01-15 14:15:36 +08:00
zhengkunwang 6f0f282b7e fix: 解决 zip 压缩前后大小不变的问题 (#3610) 2024-01-15 14:15:26 +08:00
ssongliu 817fd123da fix: 解决 WebDav 备份账号校验错误的问题 (#3587)
Refs #3585
2024-01-12 09:13:08 +00:00
ssongliu ad4aac51d0 fix: 优化 Redis 状态显示 (#3578)
Refs #3576
2024-01-11 08:18:50 +00:00
ssongliu cb85860ff6 fix: 解决网站日志切割失败的问题 (#3573) 2024-01-11 13:53:50 +08:00
ssongliu 48713b51f1 fix: 解决计划任务备份网站失败的问题 (#3572) 2024-01-11 03:01:40 +00:00
zhengkunwang ce3b841ca7 fix: 解决文件列表跳转每页条数限制不生效的问题 (#3570) 2024-01-11 02:59:38 +00:00
ssongliu d95ec29de3 feat: 移除 PostgreSQL 远程连接时删除对应数据库 (#3564) 2024-01-10 12:59:34 +00:00
ssongliu 54d841787e fix: 优化容器监控请求频率 (#3563) 2024-01-10 17:06:14 +08:00
zhengkunwang 714de55727 fix: 解决网站日志清理日志按钮无法点击的问题 (#3562) 2024-01-10 17:06:01 +08:00
ssongliu 3c39415d84 fix: 解决镜像清理显示不全的问题 (#3558) 2024-01-10 17:05:52 +08:00
ssongliu 2c04d24108 fix: 更新系统 IP 库 (#3561) 2024-01-10 08:47:44 +00:00
ssongliu 4591e44a62 fix: 解决 PostgreSQL 上传恢复报错的问题 (#3560) 2024-01-10 08:45:43 +00:00
zhengkunwang 3ffae2a9d4 style: 修改 pgsql 页面密码字段样式 (#3559) 2024-01-10 08:43:43 +00:00
ssongliu 009919f927 fix: 解决计划任务编辑报错的问题 (#3556) 2024-01-10 07:02:24 +00:00
zhengkunwang 1c1bc872a8 style: 修改部分校验的提示文字 (#3554) 2024-01-10 03:46:23 +00:00
ssongliu 3dca8e3a9c fix: 优化右侧抽屉内容过多导致显示不完整的问题 (#3547) 2024-01-09 14:33:35 +00:00
ssongliu c23c3db150 fix: 解决 PostgreSQL 数据恢复失败的问题 (#3546) 2024-01-09 14:31:34 +00:00
ssongliu 4f482ad891 fix: 补充代码依赖 (#3544) 2024-01-09 09:21:33 +00:00
lostmaniac 625189ce85 添加安全检查,确保转换后的值在int类型的范围内 (#3543) 2024-01-09 17:03:36 +08:00
ssongliu 6d03042e26 fix: 解决卸载 PostgreSQL 后未删除数据的问题 (#3542) 2024-01-09 08:27:33 +00:00
zhengkunwang 45a58b3b4b fix: 解决 PHP 模版添加失败的问题 (#3541) 2024-01-09 07:57:34 +00:00
zhengkunwang c4b7dd70cb feat: 解决部分情况下申请证书超时的问题 (#3540)
Refs https://github.com/1Panel-dev/1Panel/issues/3431
2024-01-09 07:55:34 +00:00
ssongliu 47524dc49b feat: 优化 PostgreSQL 远程数据库备份逻辑 (#3538) 2024-01-09 07:53:33 +00:00
ssongliu 9a905750af feat: PostgreSQL 增加权限设置 (#3537) 2024-01-09 04:43:33 +00:00
ssongliu 51077b0597 fix: 调整 PostgreSQL 用户绑定样式 (#3530) 2024-01-08 10:21:33 +00:00
ssongliu 3c2472ab6c feat: 优化 PostgreSQL 远程数据库备份恢复逻辑 (#3528) 2024-01-08 10:19:51 +00:00
ssongliu 39818b8424 feat: PostgreSQL 支持从服务器获取功能 (#3526) 2024-01-08 09:40:42 +00:00
ssongliu 2dc63e152b fix: 解决容器未能正常运行情况下的页面显示异常问题 (#3527) 2024-01-08 09:38:40 +00:00
ssongliu 6130d4e026 feat: 计划任务增加 pg 备份 (#3520) 2024-01-07 14:52:41 +00:00
zhengkunwang 9713b32ba8 feat: 暂时删除 xpack 相关代码 (#3519) 2024-01-07 14:36:41 +00:00
zhengkunwang 040a0663c6 feat: 应用安装支持选择 postgres (#3504) 2024-01-03 05:26:29 +00:00
ssongliu d626b2f4bd feat: 适配 Fail2ban 0.10.2 版本 (#3503)
Refs #3490
2024-01-02 13:56:28 +00:00
zhengkunwang b8a89d86a0 feat: PHP 运行环境增加扩展模版 (#3502)
Refs https://github.com/1Panel-dev/1Panel/issues/1636
2024-01-02 13:54:28 +00:00
ssongliu a8e10d0d71 fix: 解决工具箱 Hosts 解析失败的问题 (#3501)
Refs #3497
2024-01-02 09:22:12 +00:00
ssongliu e9f876853b fix: 修改快照备份失败的问题 (#3500) 2024-01-02 09:10:13 +00:00
ssongliu 9561336934 feat: 完善 PostgreSQL 功能 (#3499) 2024-01-02 09:08:13 +00:00
zhengkunwang 4d19e3904b fix: 解决网站反向代理在开启 HTTPS 后 HSTS 配置失效的问题 (#3483)
Refs https://github.com/1Panel-dev/1Panel/issues/2067
Refs https://github.com/1Panel-dev/1Panel/issues/3152
2023-12-29 10:41:41 +00:00
zhengkunwang d9005bc937 fix: 解决网站日志正在写入的情况下切割失败的问题 (#3481)
Refs https://github.com/1Panel-dev/1Panel/issues/2230
2023-12-29 10:39:39 +00:00
zhengkunwang b6948b6550 style: 数据库链接信息适配宽度不合适的情况 (#3479) 2023-12-29 06:07:39 +00:00
zhengkunwang 3acd449787 feat: 应用搜索支持描述字段 (#3473)
Refs https://github.com/1Panel-dev/1Panel/issues/2041
2023-12-28 09:53:38 +00:00
zhengkunwang 8d3fd42197 feat: openresty 增加清理反代缓存的功能 (#3471)
Refs https://github.com/1Panel-dev/1Panel/issues/2288
2023-12-28 09:51:39 +00:00
ssongliu d05d7bbcc2 fix: 更新 PostgreSQL 依赖 (#3474) 2023-12-28 09:41:39 +00:00
zhengkunwang ebfaf77c8f feat: 网站支持中文域名 (#3472)
Refs https://github.com/1Panel-dev/1Panel/issues/1522
2023-12-28 08:49:38 +00:00
wanghe-fit2cloud 9ae5592d2b docs: Modify Copyright 2023-12-28 16:43:28 +08:00
Node 46e7431c4c feat: 数据库面板新增PostgreSQL管理,支持远程/本地数据库,支持与应用关联等 #758 #1978 (#3447)
* feat: 数据库面板新增 PostgreSQL 管理,支持远程/本地数据库,支持与应用关联等 #758 #1978
2023-12-28 16:29:18 +08:00
zhengkunwang 14e2ced3da feat: 容器列表增加过滤应用商店容器功能 (#3469) 2023-12-28 03:44:11 +00:00
zhengkunwang 9716e3754e feat: 容器列表增加关联资源展示 (#3465)
Refs https://github.com/1Panel-dev/1Panel/issues/659
2023-12-27 14:38:10 +00:00
zhengkunwang aaafeb039e fix: 解决 zip 压缩失败之后压缩文件还存在的问题 (#3459)
Refs https://github.com/1Panel-dev/1Panel/issues/3012
2023-12-27 03:40:09 +00:00
ssongliu ee2fa70bb0 feat: 拆分备份账号代码 (#3454) 2023-12-26 14:48:07 +00:00
zhengkunwang b547955965 fix: 解决日志审计网站日志在网站为空的时候报错的问题 (#3452)
Refs https://github.com/1Panel-dev/1Panel/issues/3449
2023-12-26 06:48:06 +00:00
zhengkunwang a6c681a429 fix: 解决网站文件层级过多导致的授权超时问题 (#3451) 2023-12-26 06:40:06 +00:00
zhengkunwang 0b94524994 feat: 增加 xpack init 方法 (#3444) 2023-12-25 09:38:09 +00:00
ssongliu 85336bc73e fix: 解决 SSL 关闭失败的问题 (#3443)
Refs #3434
2023-12-25 09:10:10 +00:00
ssongliu 33484a9436 feat: 同步用户时,移除创建用户逻辑 (#3442) 2023-12-25 09:08:09 +00:00
Anye 57f496fe12 Fix CopyButton content in SSL apply page (#3422) 2023-12-22 10:17:03 +08:00
zhengkunwang 326bcb5b3c feat: 增加 xpack 相关代码 (#3407) 2023-12-20 14:44:09 +00:00
BlueNoob 74dd3ce6c3 Update upgrade.go (#3410)
change 'service' to 'system'
2023-12-20 22:38:01 +08:00
ssongliu 411402f9cb fix: 修改 UFW 防火墙重启方式 (#3402) 2023-12-20 07:34:09 +00:00
ssongliu 733889407e feat: 优化文本域换行符校验 (#3399) 2023-12-20 05:46:09 +00:00
ssongliu f50972a0e3 feat: 容器名称修改增加来源判断 (#3390) 2023-12-19 14:02:07 +00:00
ssongliu d50fa7d928 fix: 优化部分界面滚动条样式 (#3389) 2023-12-19 09:38:07 +00:00
ssongliu df513c49e9 fix: 解决暗黑模式下主机地址样式问题 (#3386)
Refs #3378
2023-12-19 08:40:06 +00:00
zhengkunwang 10bd640a9b fix: 解决本地备份文件被删除导致的备份列表读取错误 (#3384) 2023-12-19 08:22:07 +00:00
ssongliu 6b491710c9 feat: SSH 支持同时监听 IPv4 及 IPv6 (#3381)
Refs #3359
2023-12-19 08:20:06 +00:00
ssongliu b9c5fee411 feat: 面板别名支持中文及空格 (#3382)
Refs #3380
2023-12-19 08:06:06 +00:00
zhengkunwang 78f2b6f72d fix: 解决同名网站备份恢复异常 (#3370)
Refs https://github.com/1Panel-dev/1Panel/issues/3355
2023-12-18 09:54:15 +00:00
zhengkunwang 9aaa387b23 fix: 解决自签证书在导入机构 Root CA 之后仍然显示不安全的问题 (#3366)
Refs https://github.com/1Panel-dev/1Panel/issues/3352
2023-12-18 04:04:12 +00:00
ssongliu a6fb0f1e89 feat: 工具箱操作增加超时时间 (#3365)
Refs #3358
2023-12-18 03:54:12 +00:00
ssongliu cbe1e9b4e1 feat: 防火墙增加重启按钮 (#3364)
Refs #3345
2023-12-18 03:42:13 +00:00
ssongliu 0a4e48c057 feat: 优化终端搜索样式及回滚条数显示 (#3363)
Refs #3330
2023-12-18 03:40:13 +00:00
zhengkunwang 3dc829a630 feat: 证书部分增加一些提示 (#3349) 2023-12-15 10:14:09 +00:00
ssongliu bcd88c6eca feat: 优化 SSH 日志获取 (#3348)
Refs #3337
2023-12-15 10:12:08 +00:00
ssongliu 4d279a521e feat: 镜像 Tag 逻辑修改 (#3346)
Refs #3316
2023-12-15 08:22:08 +00:00
zhengkunwang 3fb5e523a8 feat: 不同类型 ACME 账号支持使用同一个邮箱 (#3344)
Refs https://github.com/1Panel-dev/1Panel/issues/3333
2023-12-15 07:48:09 +00:00
zhengkunwang b2926019cf fix: 解决部分文件上传失败的问题 (#3343) 2023-12-15 07:46:08 +00:00
ssongliu a9d8018e78 feat: 容器升级界面调整 (#3341) 2023-12-15 04:10:08 +00:00
ssongliu 471a245732 feat: 调整菜单次序 (#3335) 2023-12-14 15:30:22 +00:00
538 changed files with 84534 additions and 22254 deletions
+5 -1
View File
@@ -1 +1,5 @@
*.ts linguist-language=Go
*.ts linguist-language=Go
*.js linguist-language=Go
*.css linguist-language=Go
*.scss linguist-language=Go
*.html linguist-language=Go
+5 -5
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 问题与讨论 / Questions & Discussions
url: https://github.com/1Panel-dev/1Panel/discussions
about: 提出针对本项目安装部署、使用及其他方面的相关问题 / Raise questions about the installation, deployment, use and other aspects of the project.
- name: 对 1Panel 项目有其他问题
url: https://1panel.cn
about: 如果想要进一步了解 1Panel 项目,欢迎发送邮件到 support@fit2cloud.com 进行提问。
- name: 反馈安全问题 (Report Security Bug)
url: mailto://support@fit2cloud.com
about: 请通过邮箱 support@fit2cloud.com 反馈安全问题 (Please report security vulnerabilities to support@fit2cloud.com)
url: https://bbs.fit2cloud.com/c/1p/7
about: 如果你对 1Panel 有其他想要提问的,我们欢迎到我们的官方社区进行提问。
-12
View File
@@ -1,12 +0,0 @@
name: 问题咨询
description: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[QUESTION]"
labels: question
assignees: wanghe-fit2cloud
body:
- type: textarea
id: description
attributes:
label: "请描述您的问题"
validations:
required: true
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
name: 为 PR 添加标签
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions-ecosystem/action-add-labels@v1
with:
github_token: ${{ secrets.GITHUBTOKEN }}
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18.14'
node-version: '20.2'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
@@ -24,7 +24,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.20.x'
go-version: '1.21.x'
- name: Build Release
uses: goreleaser/goreleaser-action@v4
with:
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
uses: Yikun/hub-mirror-action@master
with:
src: 'github/1Panel-dev'
dst: 'gitee/fit2cloud-xlab'
dst: 'gitee/fit2cloud-feizhiyun'
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
static_list: "1Panel"
+13
View File
@@ -0,0 +1,13 @@
name: Typos Check
on: pull_request
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v2
- name: Check spelling
uses: crate-ci/typos@master
+8 -8
View File
@@ -16,18 +16,12 @@ build/1panel
*.factorypath
# IntelliJ IDEA
.idea
.idea/*
!.idea/icon.png
*.iws
*.iml
*.ipr
# NetBeans
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
# Test binary, built with `go test -c`
*.test
@@ -44,6 +38,11 @@ cmd/server/web/monacoeditorwork
cmd/server/web/index.html
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
.history/
dist/
@@ -53,3 +52,4 @@ install.sh
quick_start.sh
cmd/server/fileList.txt
.fileList.txt
1Panel.code-workspace
+1
View File
@@ -13,6 +13,7 @@ builds:
- main: ./cmd/server/main.go
binary: 1panel
flags:
- -tags=xpack
- -trimpath
ldflags:
- -w -s
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

+2 -2
View File
@@ -5,7 +5,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
This [development guideline](https://kubeoperator.io/docs/dev/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
This [development guideline](https://1panel.cn/docs/dev_manual/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
@@ -22,4 +22,4 @@ When reporting issues, always include:
* Snapshots or log files if needed
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
replace those parts with "REDACTED" or other strings like "****".
replace those parts with "REDACTED" or other strings like "****".
+17 -9
View File
@@ -11,13 +11,15 @@
------------------------------
1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括:
1Panel 是新一代的 Linux 服务器运维管理面板。
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定;
- **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等;
- **安全可靠**:基于容器来管理和部署应用,最小漏洞暴露面,提供防火墙和日志审计等功能;
- **一键备份**:支持一键备份和恢复,备份数据到各类云端存储,永不丢失;
- **应用商店**:精选各类高质量开源工具和应用软件,助力您轻松安装和升级。
- **高效管理**:用户可以通过 Web 图形界面轻松管理 Linux 服务器,实现主机监控、文件管理、数据库管理、容器管理等功能;
- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定;
- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级;
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供防火墙和日志审计等功能;
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
1Panel 三分钟速览:https://www.bilibili.com/video/BV1Mt421n7LZ/
## UI 展示
@@ -50,20 +52,26 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
如果您在使用过程中发现任何安全问题,请通过以下方式直接联系我们:
- 邮箱:support@fit2cloud.com
- 电话:400-052-0755
- 邮箱:wanghe@fit2cloud.com
## 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)
## 我们的其他明星项目
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
- [MeterSphere](https://github.com/metersphere/metersphere/) - 一站式开源自动化测试平台
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
## FOSSA Status
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_large)
## License
Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
Copyright (c) 2014-2024 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
-6
View File
@@ -3,9 +3,6 @@
如果您发现安全问题,请直接联系我们:
- wanghe@fit2cloud.com
- zhengkun@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
感谢您的支持!
@@ -14,8 +11,5 @@
All security bugs should be reported to the contact as below:
- wanghe@fit2cloud.com
- zhengkun@fit2cloud.com
- support@fit2cloud.com
- +86 400-052-0755
Thanks for your support!
+1 -1
View File
@@ -137,7 +137,7 @@ func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
// @Accept json
// @Success 200 {object} response.IgnoredApp
// @Security ApiKeyAuth
// @Router /apps/ingored [get]
// @Router /apps/ignored [get]
func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
res, err := appService.GetIgnoredApp()
if err != nil {
+9
View File
@@ -121,6 +121,15 @@ func (b *BaseApi) CheckIsSafety(c *gin.Context) {
helper.SuccessWithData(c, status)
}
func (b *BaseApi) GetResponsePage(c *gin.Context) {
pageCode, err := authService.GetResponsePage()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, pageCode)
}
// @Tags Auth
// @Summary Check System isDemo
// @Description 判断是否为demo环境
+62 -17
View File
@@ -49,6 +49,17 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Backup Account
// @Summary Refresh OneDrive token
// @Description 刷新 OneDrive token
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/refresh/onedrive [post]
func (b *BaseApi) RefreshOneDriveToken(c *gin.Context) {
backupService.Run()
helper.SuccessWithData(c, nil)
}
// @Tags Backup Account
// @Summary List buckets
// @Description 获取 bucket 列表
@@ -91,16 +102,16 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
// @Summary Load OneDrive info
// @Description 获取 OneDrive 信息
// @Accept json
// @Success 200 string clientID
// @Success 200 {object} dto.OneDriveInfo
// @Security ApiKeyAuth
// @Router /settings/backup/onedrive [get]
func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
clientID, err := backupService.LoadOneDriveInfo()
data, err := backupService.LoadOneDriveInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, clientID)
helper.SuccessWithData(c, data)
}
// @Tags Backup Account
@@ -151,6 +162,32 @@ func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
})
}
// @Tags Backup Account
// @Summary Page backup records by cronjob
// @Description 通过计划任务获取备份记录列表分页
// @Accept json
// @Param request body dto.RecordSearchByCronjob true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/record/search/bycronjob [post]
func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
var req dto.RecordSearchByCronjob
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := backupService.SearchRecordsByCronjobWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Backup Account
// @Summary Download backup record
// @Description 下载备份记录
@@ -265,12 +302,7 @@ func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
return
}
data, err := backupService.ListFiles(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
data := backupService.ListFiles(req)
helper.SuccessWithData(c, data)
}
@@ -281,7 +313,7 @@ func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
// @Param request body dto.CommonBackup true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/ [post]
// @Router /settings/backup/backup [post]
// @x-panel-log {"bodyKeys":["type","name","detailName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"备份 [type] 数据 [name][detailName]","formatEN":"backup [type] data [name][detailName]"}
func (b *BaseApi) Backup(c *gin.Context) {
var req dto.CommonBackup
@@ -300,6 +332,11 @@ func (b *BaseApi) Backup(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case constant.AppPostgresql:
if err := backupService.PostgresqlBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case "website":
if err := backupService.WebsiteBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -329,20 +366,23 @@ func (b *BaseApi) Recover(c *gin.Context) {
return
}
if req.Source != "LOCAL" {
downloadPath, err := backupService.DownloadRecord(dto.DownloadRecord{Source: req.Source, FileDir: path.Dir(req.File), FileName: path.Base(req.File)})
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("download file failed, err: %v", err))
return
}
req.File = downloadPath
downloadPath, err := backupService.DownloadRecord(dto.DownloadRecord{Source: req.Source, FileDir: path.Dir(req.File), FileName: path.Base(req.File)})
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("download file failed, err: %v", err))
return
}
req.File = downloadPath
switch req.Type {
case "mysql", "mariadb":
if err := backupService.MysqlRecover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case constant.AppPostgresql:
if err := backupService.PostgresqlRecover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case "website":
if err := backupService.WebsiteRecover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -383,6 +423,11 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case constant.AppPostgresql:
if err := backupService.PostgresqlRecoverByUpload(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case "app":
if err := backupService.AppRecover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
+2 -6
View File
@@ -95,17 +95,13 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjob/record/log [post]
// @Router /cronjobs/records/log [post]
func (b *BaseApi) LoadRecordLog(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := cronjobService.LoadRecordLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
content := cronjobService.LoadRecordLog(req)
helper.SuccessWithData(c, content)
}
+16
View File
@@ -8,6 +8,22 @@ import (
"github.com/gin-gonic/gin"
)
// @Tags Dashboard
// @Summary Load os info
// @Description 获取服务器基础数据
// @Accept json
// @Success 200 {object} dto.OsInfo
// @Security ApiKeyAuth
// @Router /dashboard/base/os [get]
func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
data, err := dashboardService.LoadOsInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary Load dashboard base info
// @Description 获取首页基础数据
+21
View File
@@ -112,6 +112,27 @@ func (b *BaseApi) ListDatabase(c *gin.Context) {
helper.SuccessWithData(c, list)
}
// @Tags Database
// @Summary List databases
// @Description 获取数据库列表
// @Success 200 {array} dto.DatabaseItem
// @Security ApiKeyAuth
// @Router /databases/db/item/:type [get]
func (b *BaseApi) LoadDatabaseItems(c *gin.Context) {
dbType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
list, err := databaseService.LoadItems(dbType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Database
// @Summary Get databases
// @Description 获取远程数据库
+75
View File
@@ -0,0 +1,75 @@
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 Database Common
// @Summary Load base info
// @Description 获取数据库基础信息
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {object} dto.DBBaseInfo
// @Security ApiKeyAuth
// @Router /databases/common/info [post]
func (b *BaseApi) LoadDBBaseInfo(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := dbCommonService.LoadBaseInfo(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Database Common
// @Summary Load Database conf
// @Description 获取数据库配置文件
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/common/load/file [post]
func (b *BaseApi) LoadDBFile(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := dbCommonService.LoadDatabaseFile(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Database Common
// @Summary Update conf by upload file
// @Description 上传替换配置文件
// @Accept json
// @Param request body dto.DBConfUpdateByFile true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/common/update/conf [post]
// @x-panel-log {"bodyKeys":["type","database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 [type] 数据库 [database] 配置信息","formatEN":"update the [type] [database] database configuration information"}
func (b *BaseApi) UpdateDBConfByFile(c *gin.Context) {
var req dto.DBConfUpdateByFile
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := dbCommonService.UpdateConfByFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
+31 -69
View File
@@ -41,6 +41,37 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Bind user of mysql database
// @Description 绑定 mysql 数据库用户
// @Accept json
// @Param request body dto.BindUser true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/bind [post]
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 mysql 数据库名 [database] [username]","formatEN":"bind mysql database [database] [username]"}
func (b *BaseApi) BindUser(c *gin.Context) {
var req dto.BindUser
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = string(password)
}
if err := mysqlService.BindUser(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Update mysql database description
// @Description 更新 mysql 数据库库描述信息
@@ -138,29 +169,6 @@ func (b *BaseApi) UpdateMysqlVariables(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Update mysql conf by upload file
// @Description 上传替换 mysql 配置文件
// @Accept json
// @Param request body dto.MysqlConfUpdateByFile true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/conffile/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 mysql 数据库配置信息","formatEN":"update the mysql database configuration information"}
func (b *BaseApi) UpdateMysqlConfByFile(c *gin.Context) {
var req dto.MysqlConfUpdateByFile
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := mysqlService.UpdateConfByFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Page mysql databases
// @Description 获取 mysql 数据库列表分页
@@ -273,51 +281,6 @@ func (b *BaseApi) DeleteMysql(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Load mysql base info
// @Description 获取 mysql 基础信息
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {object} dto.DBBaseInfo
// @Security ApiKeyAuth
// @Router /databases/baseinfo [post]
func (b *BaseApi) LoadBaseinfo(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := mysqlService.LoadBaseInfo(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Database
// @Summary Load Database file
// @Description 获取数据库文件
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/load/file [post]
func (b *BaseApi) LoadDatabaseFile(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := mysqlService.LoadDatabaseFile(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Database Mysql
// @Summary Load mysql remote access
// @Description 获取 mysql 远程访问权限
@@ -331,7 +294,6 @@ func (b *BaseApi) LoadRemoteAccess(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
isRemote, err := mysqlService.LoadRemoteAccess(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
+233
View File
@@ -0,0 +1,233 @@
package v1
import (
"context"
"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 Database Postgresql
// @Summary Create postgresql database
// @Description 创建 postgresql 数据库
// @Accept json
// @Param request body dto.PostgresqlDBCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 postgresql 数据库 [name]","formatEN":"create postgresql database [name]"}
func (b *BaseApi) CreatePostgresql(c *gin.Context) {
var req dto.PostgresqlDBCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = string(password)
}
if _, err := postgresqlService.Create(context.Background(), req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Bind postgresql user
// @Description 绑定 postgresql 数据库用户
// @Accept json
// @Param request body dto.PostgresqlBindUser true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/bind [post]
// @x-panel-log {"bodyKeys":["name", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 postgresql 数据库 [name] 用户 [username]","formatEN":"bind postgresql database [name] user [username]"}
func (b *BaseApi) BindPostgresqlUser(c *gin.Context) {
var req dto.PostgresqlBindUser
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := postgresqlService.BindUser(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Update postgresql database description
// @Description 更新 postgresql 数据库库描述信息
// @Accept json
// @Param request body dto.UpdateDescription true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/description [post]
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"postgresql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the postgresql database [name] is modified => [description]"}
func (b *BaseApi) UpdatePostgresqlDescription(c *gin.Context) {
var req dto.UpdateDescription
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := postgresqlService.UpdateDescription(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Change postgresql privileges
// @Description 修改 postgresql 用户权限
// @Accept json
// @Param request body dto.ChangeDBInfo true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/privileges [post]
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] 用户 [username] 权限","formatEN":"Update [user] privileges of database [database]"}
func (b *BaseApi) ChangePostgresqlPrivileges(c *gin.Context) {
var req dto.PostgresqlPrivileges
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := postgresqlService.ChangePrivileges(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Change postgresql password
// @Description 修改 postgresql 密码
// @Accept json
// @Param request body dto.ChangeDBInfo true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/password [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"}
func (b *BaseApi) ChangePostgresqlPassword(c *gin.Context) {
var req dto.ChangeDBInfo
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Value) != 0 {
value, err := base64.StdEncoding.DecodeString(req.Value)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Value = string(value)
}
if err := postgresqlService.ChangePassword(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Page postgresql databases
// @Description 获取 postgresql 数据库列表分页
// @Accept json
// @Param request body dto.PostgresqlDBSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/pg/search [post]
func (b *BaseApi) SearchPostgresql(c *gin.Context) {
var req dto.PostgresqlDBSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := postgresqlService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Database Postgresql
// @Summary Load postgresql database from remote
// @Description 从服务器获取
// @Accept json
// @Param request body dto.PostgresqlLoadDB true "request"
// @Security ApiKeyAuth
// @Router /databases/pg/:database/load [post]
func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) {
database, err := helper.GetStrParamByKey(c, "database")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := postgresqlService.LoadFromRemote(database); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Postgresql
// @Summary Check before delete postgresql database
// @Description Postgresql 数据库删除前检查
// @Accept json
// @Param request body dto.PostgresqlDBDeleteCheck true "request"
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /databases/pg/del/check [post]
func (b *BaseApi) DeleteCheckPostgresql(c *gin.Context) {
var req dto.PostgresqlDBDeleteCheck
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
apps, err := postgresqlService.DeleteCheck(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, apps)
}
// @Tags Database Postgresql
// @Summary Delete postgresql database
// @Description 删除 postgresql 数据库
// @Accept json
// @Param request body dto.PostgresqlDBDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/pg/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"删除 postgresql 数据库 [name]","formatEN":"delete postgresql database [name]"}
func (b *BaseApi) DeletePostgresql(c *gin.Context) {
var req dto.PostgresqlDBDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
tx, ctx := helper.GetTxAndContext()
if err := postgresqlService.Delete(ctx, req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
tx.Rollback()
return
}
tx.Commit()
helper.SuccessWithData(c, nil)
}
-46
View File
@@ -1,15 +1,11 @@
package v1
import (
"bufio"
"encoding/base64"
"fmt"
"os"
"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/1Panel-dev/1Panel/backend/utils/compose"
"github.com/gin-gonic/gin"
)
@@ -161,45 +157,3 @@ func (b *BaseApi) RedisBackupList(c *gin.Context) {
Total: total,
})
}
// @Tags Database Redis
// @Summary Update redis conf by file
// @Description 上传更新 redis 配置信息
// @Accept json
// @Param request body dto.RedisConfUpdateByFile true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/redis/conffile/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 redis 数据库配置信息","formatEN":"update the redis database configuration information"}
func (b *BaseApi) UpdateRedisConfByFile(c *gin.Context) {
var req dto.RedisConfUpdateByFile
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
redisInfo, err := redisService.LoadConf()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", constant.AppInstallDir, redisInfo.Name)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.File)
write.Flush()
if req.RestartNow {
composeDir := fmt.Sprintf("%s/redis/%s/docker-compose.yml", constant.AppInstallDir, redisInfo.Name)
if _, err := compose.Restart(composeDir); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
}
helper.SuccessWithData(c, nil)
}
+8 -5
View File
@@ -21,9 +21,11 @@ var (
imageService = service.NewIImageService()
dockerService = service.NewIDockerService()
mysqlService = service.NewIMysqlService()
databaseService = service.NewIDatabaseService()
redisService = service.NewIRedisService()
dbCommonService = service.NewIDBCommonService()
mysqlService = service.NewIMysqlService()
postgresqlService = service.NewIPostgresqlService()
databaseService = service.NewIDatabaseService()
redisService = service.NewIRedisService()
cronjobService = service.NewICronjobService()
@@ -52,8 +54,9 @@ var (
snapshotService = service.NewISnapshotService()
upgradeService = service.NewIUpgradeService()
runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService()
runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService()
phpExtensionsService = service.NewIPHPExtensionsService()
hostToolService = service.NewIHostToolService()
+69 -12
View File
@@ -301,28 +301,83 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
files := form.File["file"]
uploadFiles := form.File["file"]
paths := form.Value["path"]
overwrite := true
if ow, ok := form.Value["overwrite"]; ok {
if len(ow) != 0 {
parseBool, _ := strconv.ParseBool(ow[0])
overwrite = parseBool
}
}
if len(paths) == 0 || !strings.Contains(paths[0], "/") {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error paths in request"))
return
}
dir := path.Dir(paths[0])
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
info, err := os.Stat(dir)
if err != nil && os.IsNotExist(err) {
mode, err := files.GetParentMode(dir)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err = os.MkdirAll(dir, mode); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("mkdir %s failed, err: %v", dir, err))
return
}
}
info, err = os.Stat(dir)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
mode := info.Mode()
fileOp := files.NewFileOp()
success := 0
failures := make(buserr.MultiErr)
for _, file := range files {
if err := c.SaveUploadedFile(file, path.Join(paths[0], file.Filename)); err != nil {
for _, file := range uploadFiles {
dstFilename := path.Join(paths[0], file.Filename)
dstDir := path.Dir(dstFilename)
if !fileOp.Stat(dstDir) {
if err = fileOp.CreateDir(dstDir, mode); err != nil {
e := fmt.Errorf("create dir [%s] failed, err: %v", path.Dir(dstFilename), err)
failures[file.Filename] = e
global.LOG.Error(e)
continue
}
}
tmpFilename := dstFilename + ".tmp"
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
_ = os.Remove(tmpFilename)
e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
failures[file.Filename] = e
global.LOG.Error(e)
continue
}
dstInfo, statErr := os.Stat(dstFilename)
if overwrite {
_ = os.Remove(dstFilename)
}
err = os.Rename(tmpFilename, dstFilename)
if err != nil {
_ = os.Remove(tmpFilename)
e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
failures[file.Filename] = e
global.LOG.Error(e)
continue
}
if statErr == nil {
_ = os.Chmod(dstFilename, dstInfo.Mode())
} else {
_ = os.Chmod(dstFilename, mode)
}
success++
}
if success == 0 {
@@ -422,11 +477,9 @@ func (b *BaseApi) MoveFile(c *gin.Context) {
// @Summary Download file
// @Description 下载文件
// @Accept json
// @Param request body request.FileDownload true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/download [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"}
// @Router /files/download [get]
func (b *BaseApi) Download(c *gin.Context) {
filePath := c.Query("path")
file, err := os.Open(filePath)
@@ -541,16 +594,20 @@ func (b *BaseApi) Size(c *gin.Context) {
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
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, os.ModePerm); err != nil {
if err = op.CreateDir(dstDir, mode); err != nil {
return err
}
}
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
targetFile, err := os.OpenFile(filepath.Join(dstDir, fileName), 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))
@@ -689,7 +746,7 @@ func (b *BaseApi) Keys(c *gin.Context) {
// @Param request body request.FileReadByLineReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/log/read [post]
// @Router /files/read [post]
func (b *BaseApi) ReadFileByLine(c *gin.Context) {
var req request.FileReadByLineReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+18 -2
View File
@@ -35,8 +35,24 @@ func (b *BaseApi) SearchImage(c *gin.Context) {
}
// @Tags Container Image
// @Summary List images
// @Description 获取镜像列表
// @Summary List all images
// @Description 获取所有镜像列表
// @Produce json
// @Success 200 {array} dto.ImageInfo
// @Security ApiKeyAuth
// @Router /containers/image/all [get]
func (b *BaseApi) ListAllImage(c *gin.Context) {
list, err := imageService.ListAll()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Container Image
// @Summary load images options
// @Description 获取镜像名称列表
// @Produce json
// @Success 200 {array} dto.Options
// @Security ApiKeyAuth
+34 -3
View File
@@ -15,6 +15,13 @@ import (
"github.com/shirou/gopsutil/v3/net"
)
// @Tags Monitor
// @Summary Load monitor datas
// @Description 获取监控数据
// @Param request body dto.MonitorSearch true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/monitor/search [post]
func (b *BaseApi) LoadMonitor(c *gin.Context) {
var req dto.MonitorSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -28,7 +35,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
var backdatas []dto.MonitorData
if req.Param == "all" || req.Param == "cpu" || req.Param == "memory" || req.Param == "load" {
var bases []model.MonitorBase
if err := global.DB.
if err := global.MonitorDB.
Where("created_at > ? AND created_at < ?", req.StartTime, req.EndTime).
Find(&bases).Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -45,7 +52,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
}
if req.Param == "all" || req.Param == "io" {
var bases []model.MonitorIO
if err := global.DB.
if err := global.MonitorDB.
Where("created_at > ? AND created_at < ?", req.StartTime, req.EndTime).
Find(&bases).Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -62,7 +69,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
}
if req.Param == "all" || req.Param == "network" {
var bases []model.MonitorNetwork
if err := global.DB.
if err := global.MonitorDB.
Where("name = ? AND created_at > ? AND created_at < ?", req.Info, req.StartTime, req.EndTime).
Find(&bases).Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -80,6 +87,30 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
helper.SuccessWithData(c, backdatas)
}
// @Tags Monitor
// @Summary Clean monitor datas
// @Description 清空监控数据
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/monitor/clean [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
func (b *BaseApi) CleanMonitor(c *gin.Context) {
if err := global.MonitorDB.Exec("DELETE FROM monitor_bases").Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err := global.MonitorDB.Exec("DELETE FROM monitor_ios").Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err := global.MonitorDB.Exec("DELETE FROM monitor_networks").Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
netStat, _ := net.IOCounters(true)
var options []string
+20 -5
View File
@@ -12,7 +12,7 @@ import (
// @Description 获取 OpenResty 配置信息
// @Success 200 {object} response.FileInfo
// @Security ApiKeyAuth
// @Router /openResty [get]
// @Router /openresty [get]
func (b *BaseApi) GetNginx(c *gin.Context) {
fileInfo, err := nginxService.GetNginxConfig()
if err != nil {
@@ -29,7 +29,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) {
// @Param request body request.NginxScopeReq true "request"
// @Success 200 {array} response.NginxParam
// @Security ApiKeyAuth
// @Router /openResty/scope [post]
// @Router /openresty/scope [post]
func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
var req request.NginxScopeReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -51,7 +51,7 @@ func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
// @Param request body request.NginxConfigUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /openResty/update [post]
// @Router /openresty/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新 nginx 配置 [domain]","formatEN":"Update nginx conf [domain]"}
func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) {
var req request.NginxConfigUpdate
@@ -70,7 +70,7 @@ func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) {
// @Description 获取 OpenResty 状态信息
// @Success 200 {object} response.NginxStatus
// @Security ApiKeyAuth
// @Router /openResty/status [get]
// @Router /openresty/status [get]
func (b *BaseApi) GetNginxStatus(c *gin.Context) {
res, err := nginxService.GetStatus()
if err != nil {
@@ -87,7 +87,7 @@ func (b *BaseApi) GetNginxStatus(c *gin.Context) {
// @Param request body request.NginxConfigFileUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /openResty/file [post]
// @Router /openresty/file [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 nginx 配置","formatEN":"Update nginx conf"}
func (b *BaseApi) UpdateNginxFile(c *gin.Context) {
var req request.NginxConfigFileUpdate
@@ -101,3 +101,18 @@ func (b *BaseApi) UpdateNginxFile(c *gin.Context) {
}
helper.SuccessWithData(c, nil)
}
// @Tags OpenResty
// @Summary Clear OpenResty proxy cache
// @Description 清理 OpenResty 代理缓存
// @Success 200
// @Security ApiKeyAuth
// @Router /openresty/clear [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理 Openresty 代理缓存","formatEN":"Clear nginx proxy cache"}
func (b *BaseApi) ClearNginxProxyCache(c *gin.Context) {
if err := nginxService.ClearProxyCache(); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+103
View File
@@ -0,0 +1,103 @@
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/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags PHP Extensions
// @Summary Page Extensions
// @Description Page Extensions
// @Accept json
// @Param request body request.PHPExtensionsSearch true "request"
// @Success 200 {array} response.PHPExtensionsDTO
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/search [post]
func (b *BaseApi) PagePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if req.All {
list, err := phpExtensionsService.List()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
} else {
total, list, err := phpExtensionsService.Page(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: list,
})
}
}
// @Tags PHP Extensions
// @Summary Create Extensions
// @Description Create Extensions
// @Accept json
// @Param request body request.PHPExtensionsCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions [post]
func (b *BaseApi) CreatePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := phpExtensionsService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags PHP Extensions
// @Summary Update Extensions
// @Description Update Extensions
// @Accept json
// @Param request body request.PHPExtensionsUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/update [post]
func (b *BaseApi) UpdatePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := phpExtensionsService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags PHP Extensions
// @Summary Delete Extensions
// @Description Delete Extensions
// @Accept json
// @Param request body request.PHPExtensionsDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/del [post]
func (b *BaseApi) DeletePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := phpExtensionsService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+16
View File
@@ -203,3 +203,19 @@ func (b *BaseApi) OperateNodeModules(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Runtime
// @Summary Sync runtime status
// @Description 同步运行环境状态
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/sync [post]
func (b *BaseApi) SyncStatus(c *gin.Context) {
err := runtimeService.SyncRuntimeStatus()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+22 -24
View File
@@ -60,6 +60,28 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Update system setting
// @Description 隐藏高级功能菜单
// @Accept json
// @Param request body dto.SettingUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/menu/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"隐藏高级功能菜单","formatEN":"Hide advanced feature menu."}
func (b *BaseApi) UpdateMenu(c *gin.Context) {
var req dto.SettingUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := settingService.Update(req.Key, req.Value); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Update system password
// @Description 更新系统登录密码
@@ -227,30 +249,6 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
helper.SuccessWithData(c, global.CONF.System.DataDir)
}
// @Tags System Setting
// @Summary Clean monitor datas
// @Description 清空监控数据
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/monitor/clean [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
func (b *BaseApi) CleanMonitor(c *gin.Context) {
if err := global.DB.Exec("DELETE FROM monitor_bases").Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err := global.DB.Exec("DELETE FROM monitor_ios").Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err := global.DB.Exec("DELETE FROM monitor_networks").Error; err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Load mfa info
// @Description 获取 mfa 信息
-22
View File
@@ -131,28 +131,6 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags SSH
// @Summary Analysis host SSH logs
// @Description 分析 SSH 登录日志
// @Accept json
// @Param request body dto.SearchForAnalysis true "request"
// @Success 200 {array} dto.SSHLogAnalysis
// @Security ApiKeyAuth
// @Router /host/ssh/log/analysis [post]
func (b *BaseApi) AnalysisLog(c *gin.Context) {
var req dto.SearchForAnalysis
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := sshService.AnalysisLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags SSH
// @Summary Load host SSH logs
// @Description 获取 SSH 登录日志
+11 -12
View File
@@ -55,7 +55,6 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
return
}
defer client.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
if wshandleError(wsConn, err) {
return
@@ -103,10 +102,10 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
if wshandleError(wsConn, err) {
return
}
defer killBash(redisConf.ContainerName, commands, pidMap)
defer killBash(redisConf.ContainerName, pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
if wshandleError(wsConn, err) {
return
}
@@ -171,10 +170,10 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
if wshandleError(wsConn, err) {
return
}
defer killBash(containerID, command, pidMap)
defer killBash(containerID, pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
if wshandleError(wsConn, err) {
return
}
@@ -222,26 +221,26 @@ func loadMapFromDockerTop(containerID string) map[string]string {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) != 2 {
if len(parts) == 0 {
continue
}
pidMap[parts[0]] = parts[1]
pidMap[parts[0]] = strings.Join(parts, " ")
}
return pidMap
}
func killBash(containerID, comm string, pidMap map[string]string) {
func killBash(containerID string, pidMap map[string]string) {
sudo := cmd.SudoHandleCmd()
newPidMap := loadMapFromDockerTop(containerID)
for pid, command := range newPidMap {
for pid, newCmd := range newPidMap {
isOld := false
for pid2 := range pidMap {
if pid == pid2 {
for pid2, oldCmd := range pidMap {
if pid == pid2 && oldCmd == newCmd {
isOld = true
break
}
}
if !isOld && command == comm {
if !isOld {
_, _ = cmd.Execf("%s kill -9 %s", sudo, pid)
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ func (b *BaseApi) GetNotesByVersion(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/upgrade [post]
// @x-panel-log {"bodyKeys":["version"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新系统 => [version]","formatEN":"upgrade service => [version]"}
// @x-panel-log {"bodyKeys":["version"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新系统 => [version]","formatEN":"upgrade system => [version]"}
func (b *BaseApi) Upgrade(c *gin.Context) {
var req dto.Upgrade
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+1 -64
View File
@@ -305,69 +305,6 @@ func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Website WAF
// @Summary Load websit waf conf
// @Description 获取网站 waf 配置
// @Accept json
// @Param request body request.WebsiteWafReq true "request"
// @Success 200 {object} response.WebsiteWafConfig
// @Security ApiKeyAuth
// @Router /websites/waf/config [post]
func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) {
var req request.WebsiteWafReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := websiteService.GetWafConfig(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Website WAF
// @Summary Update website waf conf
// @Description 更新 网站 waf 配置
// @Accept json
// @Param request body request.WebsiteWafUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/waf/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置修改 [domain]","formatEN":"WAF conf update [domain]"}
func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
var req request.WebsiteWafUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.UpdateWafConfig(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website WAF
// @Summary Update website waf file
// @Description 更新 网站 waf 配置文件
// @Accept json
// @Param request body request.WebsiteWafUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/waf/file/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置文件修改 [domain]","formatEN":"WAF conf file update [domain]"}
func (b *BaseApi) UpdateWebsiteWafFile(c *gin.Context) {
var req request.WebsiteWafFileUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.UpdateWafFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website Nginx
// @Summary Update website nginx conf
// @Description 更新 网站 nginx 配置
@@ -433,7 +370,7 @@ func (b *BaseApi) ChangeDefaultServer(c *gin.Context) {
}
// @Tags Website
// @Summary Load websit php conf
// @Summary Load website php conf
// @Description 获取网站 php 配置
// @Accept json
// @Param id path integer true "request"
+1 -1
View File
@@ -124,7 +124,7 @@ func (b *BaseApi) ObtainWebsiteCA(c *gin.Context) {
// @Param request body request.WebsiteCAObtain true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ca/obtain [post]
// @Router /websites/ca/renew [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":"自签 SSL 证书 [name]","formatEN":"Obtain SSL [name]"}
func (b *BaseApi) RenewWebsiteCA(c *gin.Context) {
var req request.WebsiteCARenew
+13 -2
View File
@@ -21,18 +21,24 @@ type BackupInfo struct {
Vars string `json:"vars"`
}
type OneDriveInfo struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectUri string `json:"redirect_uri"`
}
type BackupSearchFile struct {
Type string `json:"type" validate:"required"`
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
Name string `json:"name"`
DetailName string `json:"detailName"`
}
type CommonRecover struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
Name string `json:"name"`
DetailName string `json:"detailName"`
File string `json:"file"`
@@ -45,6 +51,11 @@ type RecordSearch struct {
DetailName string `json:"detailName"`
}
type RecordSearchByCronjob struct {
PageInfo
CronjobID uint `json:"cronjobID" validate:"required"`
}
type BackupRecords struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
+2
View File
@@ -2,6 +2,8 @@ package dto
type SearchCommandWithPage struct {
SearchWithPage
OrderBy string `json:"orderBy"`
Order string `json:"order"`
GroupID uint `json:"groupID"`
Info string `json:"info"`
}
+15 -7
View File
@@ -4,11 +4,12 @@ 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"`
Filters string `json:"filters"`
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"`
Filters string `json:"filters"`
ExcludeAppStore bool `json:"excludeAppStore"`
}
type InspectReq struct {
@@ -30,11 +31,15 @@ type ContainerInfo struct {
IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"`
AppName string `json:"appName"`
AppInstallName string `json:"appInstallName"`
Websites []string `json:"websites"`
}
type ResourceLimit struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
CPU int `json:"cpu"`
Memory uint64 `json:"memory"`
}
type ContainerOperate struct {
@@ -43,6 +48,8 @@ type ContainerOperate struct {
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
Network string `json:"network"`
Ipv4 string `json:"ipv4"`
Ipv6 string `json:"ipv6"`
PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"`
Tty bool `json:"tty"`
@@ -93,6 +100,7 @@ type ContainerStats struct {
}
type VolumeHelper struct {
Type string `json:"type"`
SourceDir string `json:"sourceDir"`
ContainerDir string `json:"containerDir"`
Mode string `json:"mode"`
+36 -43
View File
@@ -3,49 +3,45 @@ package dto
import "time"
type CronjobCreate struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
SpecType string `json:"specType" validate:"required"`
Week int `json:"week" validate:"number,max=6,min=0"`
Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"`
Second int `json:"second" validate:"number"`
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
Spec string `json:"spec" validate:"required"`
Script string `json:"script"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
SourceDir string `json:"sourceDir"`
KeepLocal bool `json:"keepLocal"`
TargetDirID int `json:"targetDirID"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
BackupAccounts string `json:"backupAccounts"`
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
}
type CronjobUpdate struct {
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
SpecType string `json:"specType" validate:"required"`
Week int `json:"week" validate:"number,max=6,min=0"`
Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"`
Second int `json:"second" validate:"number"`
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
Spec string `json:"spec" validate:"required"`
Script string `json:"script"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
SourceDir string `json:"sourceDir"`
KeepLocal bool `json:"keepLocal"`
TargetDirID int `json:"targetDirID"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
BackupAccounts string `json:"backupAccounts"`
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
}
type CronjobUpdateStatus struct {
@@ -59,6 +55,7 @@ type CronjobDownload struct {
}
type CronjobClean struct {
IsDelete bool `json:"isDelete"`
CleanData bool `json:"cleanData"`
CronjobID uint `json:"cronjobID" validate:"required"`
}
@@ -69,28 +66,24 @@ type CronjobBatchDelete struct {
}
type CronjobInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
SpecType string `json:"specType"`
Week int `json:"week"`
Day int `json:"day"`
Hour int `json:"hour"`
Minute int `json:"minute"`
Second int `json:"second"`
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Spec string `json:"spec"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
URL string `json:"url"`
SourceDir string `json:"sourceDir"`
KeepLocal bool `json:"keepLocal"`
TargetDir string `json:"targetDir"`
TargetDirID int `json:"targetDirID"`
RetainCopies int `json:"retainCopies"`
Script string `json:"script"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
SourceDir string `json:"sourceDir"`
BackupAccounts string `json:"backupAccounts"`
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies"`
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
+25
View File
@@ -24,6 +24,14 @@ type DashboardBase struct {
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
type OsInfo struct {
OS string `json:"os"`
Platform string `json:"platform"`
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
}
type DashboardCurrent struct {
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`
@@ -61,6 +69,8 @@ type DashboardCurrent struct {
NetBytesSent uint64 `json:"netBytesSent"`
NetBytesRecv uint64 `json:"netBytesRecv"`
GPUData []GPUInfo `json:"gpuData"`
ShotTime time.Time `json:"shotTime"`
}
@@ -78,3 +88,18 @@ type DiskInfo struct {
InodesFree uint64 `json:"inodesFree"`
InodesUsedPercent float64 `json:"inodesUsedPercent"`
}
type GPUInfo struct {
Index uint `json:"index"`
ProductName string `json:"productName"`
GPUUtil string `json:"gpuUtil"`
Temperature string `json:"temperature"`
PerformanceState string `json:"performanceState"`
PowerUsage string `json:"powerUsage"`
PowerDraw string `json:"powerDraw"`
MaxPowerLimit string `json:"maxPowerLimit"`
MemoryUsage string `json:"memoryUsage"`
MemUsed string `json:"memUsed"`
MemTotal string `json:"memTotal"`
FanSpeed string `json:"fanSpeed"`
}
+38 -21
View File
@@ -2,6 +2,27 @@ package dto
import "time"
// common
type DBConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}
type DBBaseInfo struct {
Name string `json:"name"`
ContainerName string `json:"containerName"`
Port int64 `json:"port"`
}
// mysql
type MysqlDBSearch struct {
PageInfo
Info string `json:"info"`
@@ -20,7 +41,7 @@ type MysqlDBInfo struct {
Username string `json:"username"`
Password string `json:"password"`
Permission string `json:"permission"`
BackupCount int `json:"backupCount"`
IsDelete bool `json:"isDelete"`
Description string `json:"description"`
}
@@ -43,6 +64,14 @@ type MysqlDBCreate struct {
Description string `json:"description"`
}
type BindUser struct {
Database string `json:"database" validate:"required"`
DB string `json:"db" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Permission string `json:"permission" validate:"required"`
}
type MysqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
@@ -102,7 +131,7 @@ type MysqlStatus struct {
}
type MysqlVariables struct {
BinlogCachSize 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"`
@@ -133,25 +162,6 @@ type MysqlVariablesUpdateHelper struct {
Param string `json:"param"`
Value interface{} `json:"value"`
}
type MysqlConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}
type DBBaseInfo struct {
Name string `json:"name"`
ContainerName string `json:"containerName"`
Port int64 `json:"port"`
}
// redis
type ChangeRedisPass struct {
@@ -256,6 +266,13 @@ type DatabaseOption struct {
Address string `json:"address"`
}
type DatabaseItem struct {
ID uint `json:"id"`
From string `json:"from"`
Database string `json:"database"`
Name string `json:"name"`
}
type DatabaseCreate struct {
Name string `json:"name" validate:"required,max=256"`
Type string `json:"type" validate:"required"`
+85
View File
@@ -0,0 +1,85 @@
package dto
import "time"
type PostgresqlDBSearch struct {
PageInfo
Info string `json:"info"`
Database string `json:"database" validate:"required"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
}
type PostgresqlDBInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
From string `json:"from"`
PostgresqlName string `json:"postgresqlName"`
Format string `json:"format"`
Username string `json:"username"`
Password string `json:"password"`
SuperUser bool `json:"superUser"`
IsDelete bool `json:"isDelete"`
Description string `json:"description"`
}
type PostgresqlOption struct {
ID uint `json:"id"`
From string `json:"from"`
Type string `json:"type"`
Database string `json:"database"`
Name string `json:"name"`
}
type PostgresqlDBCreate struct {
Name string `json:"name" validate:"required"`
From string `json:"from" validate:"required,oneof=local remote"`
Database string `json:"database" validate:"required"`
Format string `json:"format"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
SuperUser bool `json:"superUser"`
Description string `json:"description"`
}
type PostgresqlBindUser struct {
Name string `json:"name" validate:"required"`
Database string `json:"database" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
SuperUser bool `json:"superUser"`
}
type PostgresqlPrivileges struct {
Name string `json:"name" validate:"required"`
Database string `json:"database" validate:"required"`
Username string `json:"username" validate:"required"`
SuperUser bool `json:"superUser"`
}
type PostgresqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=postgresql"`
Database string `json:"database" validate:"required"`
}
type PostgresqlDBDeleteCheck struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=postgresql"`
Database string `json:"database" validate:"required"`
}
type PostgresqlDBDelete struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=postgresql"`
Database string `json:"database" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
}
type PostgresqlConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=postgresql mariadb"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
+1 -1
View File
@@ -16,7 +16,7 @@ type RuleSearch struct {
}
type FirewallOperation struct {
Operation string `json:"operation" validate:"required,oneof=start stop disablePing enablePing"`
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
}
type PortRuleOperate struct {
-1
View File
@@ -27,7 +27,6 @@ type ImagePull struct {
}
type ImageTag struct {
RepoID uint `json:"repoID"`
SourceID string `json:"sourceID" validate:"required"`
TargetName string `json:"targetName" validate:"required"`
}
+2
View File
@@ -32,6 +32,7 @@ type AppContainerConfig struct {
EditCompose bool `json:"editCompose"`
DockerCompose string `json:"dockerCompose"`
HostMode bool `json:"hostMode"`
PullImage bool `json:"pullImage"`
}
type AppInstalledSearch struct {
@@ -67,6 +68,7 @@ type AppInstalledOperate struct {
DeleteBackup bool `json:"deleteBackup"`
DeleteDB bool `json:"deleteDB"`
Backup bool `json:"backup"`
PullImage bool `json:"pullImage"`
}
type AppInstalledUpdate struct {
+2 -2
View File
@@ -22,7 +22,7 @@ type FileCreate struct {
Path string `json:"path" validate:"required"`
Content string `json:"content"`
IsDir bool `json:"isDir"`
Mode int64 `json:"mode" validate:"required"`
Mode int64 `json:"mode"`
IsLink bool `json:"isLink"`
IsSymlink bool `json:"isSymlink"`
LinkPath string `json:"linkPath"`
@@ -64,7 +64,7 @@ type FileDeCompress struct {
type FileEdit struct {
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Content string `json:"content"`
}
type FileRename struct {
+22
View File
@@ -0,0 +1,22 @@
package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type PHPExtensionsSearch struct {
dto.PageInfo
All bool `json:"all"`
}
type PHPExtensionsCreate struct {
Name string `json:"name" validate:"required"`
Extensions string `json:"extensions" validate:"required"`
}
type PHPExtensionsUpdate struct {
ID uint `json:"id" validate:"required"`
Extensions string `json:"extensions" validate:"required"`
}
type PHPExtensionsDelete struct {
ID uint `json:"id" validate:"required"`
}
+6 -18
View File
@@ -69,24 +69,12 @@ type WebsiteOp struct {
Operate string `json:"operate"`
}
type WebsiteWafReq struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
Rule string `json:"rule" validate:"required"`
}
type WebsiteRedirectUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
Enable bool `json:"enable"`
}
type WebsiteWafUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
Enable bool `json:"enable"`
}
type WebsiteRecover struct {
WebsiteName string `json:"websiteName" validate:"required"`
Type string `json:"type" validate:"required"`
@@ -207,12 +195,12 @@ type WebsiteRedirectReq struct {
WebsiteID uint `json:"websiteId" validate:"required"`
}
type WebsiteWafFileUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Content string `json:"content" validate:"required"`
Type string `json:"type" validate:"required,oneof=cc ip_white ip_block url_white url_block cookie_block args_check post_check ua_check file_ext_block user_agent"`
}
type WebsiteCommonReq struct {
ID uint `json:"id" validate:"required"`
}
type WafWebsite struct {
Key string `json:"key"`
Domains []string `json:"domains"`
Host []string `json:"host"`
}
+2 -1
View File
@@ -32,7 +32,8 @@ type WebsiteSSLRenew struct {
}
type WebsiteSSLApply struct {
ID uint `json:"ID" validate:"required"`
ID uint `json:"ID" validate:"required"`
SkipDNSCheck bool `json:"SkipDNSCheck"`
}
type WebsiteAcmeAccountCreate struct {
+7 -4
View File
@@ -1,9 +1,10 @@
package response
import (
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
@@ -72,9 +73,11 @@ type AppInstalledDTO struct {
}
type DatabaseConn struct {
Password string `json:"password"`
ServiceName string `json:"serviceName"`
Port int64 `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
ContainerName string `json:"containerName"`
ServiceName string `json:"serviceName"`
Port int64 `json:"port"`
}
type AppService struct {
@@ -0,0 +1,7 @@
package response
import "github.com/1Panel-dev/1Panel/backend/app/model"
type PHPExtensionsDTO struct {
model.PHPExtensions
}
+6 -5
View File
@@ -13,6 +13,12 @@ type WebsiteDTO struct {
RuntimeName string `json:"runtimeName"`
}
type WebsiteOption struct {
ID uint `json:"id"`
PrimaryDomain string `json:"primaryDomain"`
Alias string `json:"alias"`
}
type WebsitePreInstallCheck struct {
Name string `json:"name"`
Status string `json:"status"`
@@ -25,11 +31,6 @@ type WebsiteNginxConfig struct {
Params []NginxParam `json:"params"`
}
type WebsiteWafConfig struct {
Enable bool `json:"enable"`
Content string `json:"content"`
}
type WebsiteHTTPS struct {
Enable bool `json:"enable"`
HttpConfig string `json:"httpConfig"`
+19 -12
View File
@@ -19,6 +19,7 @@ type SettingInfo struct {
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`
DefaultNetwork string `json:"defaultNetwork"`
LastCleanTime string `json:"lastCleanTime"`
@@ -52,6 +53,10 @@ type SettingInfo struct {
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
FileRecycleBin string `json:"fileRecycleBin"`
SnapshotIgnore string `json:"snapshotIgnore"`
XpackHideMenu string `json:"xpackHideMenu"`
NoAuthSetting string `json:"noAuthSetting"`
}
type SettingUpdate struct {
@@ -60,7 +65,7 @@ type SettingUpdate struct {
}
type SSLUpdate struct {
SSLType string `json:"sslType" validate:"required,oneof=self select import-paste import-local"`
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
Cert string `json:"cert"`
@@ -99,9 +104,10 @@ type SnapshotStatus struct {
}
type SnapshotCreate struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive WebDAV"`
Description string `json:"description" validate:"max=256"`
ID uint `json:"id"`
From string `json:"from" validate:"required"`
DefaultDownload string `json:"defaultDownload" validate:"required"`
Description string `json:"description" validate:"max=256"`
}
type SnapshotRecover struct {
IsNew bool `json:"isNew"`
@@ -114,14 +120,15 @@ type SnapshotImport struct {
Description string `json:"description" validate:"max=256"`
}
type SnapshotInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description" validate:"max=256"`
From string `json:"from"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
Version string `json:"version"`
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description" validate:"max=256"`
From string `json:"from"`
DefaultDownload string `json:"defaultDownload"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
Version string `json:"version"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`
-20
View File
@@ -44,26 +44,6 @@ type SSHLog struct {
FailedCount int `json:"failedCount"`
}
type SearchForAnalysis struct {
PageInfo
OrderBy string `json:"orderBy" validate:"required,oneof=Success Failed"`
}
type AnalysisRes struct {
Total int64 `json:"total"`
Items []SSHLogAnalysis `json:"items"`
SuccessfulCount int `json:"successfulCount"`
FailedCount int `json:"failedCount"`
}
type SSHLogAnalysis struct {
Address string `json:"address"`
Area string `json:"area"`
SuccessfulCount int `json:"successfulCount"`
FailedCount int `json:"failedCount"`
Status string `json:"status"`
}
type SSHHistory struct {
Date time.Time `json:"date"`
DateStr string `json:"dateStr"`
+2
View File
@@ -12,6 +12,8 @@ type BackupAccount struct {
type BackupRecord struct {
BaseModel
From string `gorm:"type:varchar(64)" json:"from"`
CronjobID uint `gorm:"type:decimal" json:"cronjobID"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Name string `gorm:"type:varchar(64);not null" json:"name"`
DetailName string `gorm:"type:varchar(256)" json:"detailName"`
+15 -15
View File
@@ -5,32 +5,32 @@ import "time"
type Cronjob struct {
BaseModel
Name string `gorm:"type:varchar(64);not null;unique" json:"name"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
SpecType string `gorm:"type:varchar(64);not null" json:"specType"`
Spec string `gorm:"type:varchar(64);not null" json:"spec"`
Week uint64 `gorm:"type:decimal" json:"week"`
Day uint64 `gorm:"type:decimal" json:"day"`
Hour uint64 `gorm:"type:decimal" json:"hour"`
Minute uint64 `gorm:"type:decimal" json:"minute"`
Second uint64 `gorm:"type:decimal" json:"second"`
Name string `gorm:"type:varchar(64);not null" json:"name"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Spec string `gorm:"type:varchar(64);not null" json:"spec"`
Command string `gorm:"type:varchar(64)" json:"command"`
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
Script string `gorm:"longtext" json:"script"`
Website string `gorm:"type:varchar(64)" json:"website"`
AppID string `gorm:"type:varchar(64)" json:"appID"`
DBType string `gorm:"type:varchar(64)" json:"dbType"`
DBName string `gorm:"type:varchar(64)" json:"dbName"`
URL string `gorm:"type:varchar(256)" json:"url"`
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
ExclusionRules string `gorm:"longtext" json:"exclusionRules"`
KeepLocal bool `gorm:"type:varchar(64)" json:"keepLocal"`
TargetDirID uint64 `gorm:"type:decimal" json:"targetDirID"`
RetainCopies uint64 `gorm:"type:decimal" json:"retainCopies"`
// 已废弃
KeepLocal bool `gorm:"type:varchar(64)" json:"keepLocal"`
TargetDirID uint64 `gorm:"type:decimal" json:"targetDirID"`
Status string `gorm:"type:varchar(64)" json:"status"`
EntryID uint64 `gorm:"type:decimal" json:"entryID"`
Records []JobRecords `json:"records"`
BackupAccounts string `gorm:"type:varchar(64)" json:"backupAccounts"`
DefaultDownload string `gorm:"type:varchar(64)" json:"defaultDownload"`
RetainCopies uint64 `gorm:"type:decimal" json:"retainCopies"`
Status string `gorm:"type:varchar(64)" json:"status"`
EntryIDs string `gorm:"type:varchar(64)" json:"entryIDs"`
Records []JobRecords `json:"records"`
}
type JobRecords struct {
+1
View File
@@ -9,5 +9,6 @@ type DatabaseMysql struct {
Username string `json:"username" gorm:"type:varchar(256);not null"`
Password string `json:"password" gorm:"type:varchar(256);not null"`
Permission string `json:"permission" gorm:"type:varchar(256);not null"`
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+14
View File
@@ -0,0 +1,14 @@
package model
type DatabasePostgresql struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(256);not null"`
From string `json:"from" gorm:"type:varchar(256);not null;default:local"`
PostgresqlName string `json:"postgresqlName" gorm:"type:varchar(64);not null"`
Format string `json:"format" gorm:"type:varchar(64);not null"`
Username string `json:"username" gorm:"type:varchar(256);not null"`
Password string `json:"password" gorm:"type:varchar(256);not null"`
SuperUser bool `json:"superUser" gorm:"type:varchar(64)"`
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+7
View File
@@ -0,0 +1,7 @@
package model
type PHPExtensions struct {
BaseModel
Name string ` json:"name" gorm:"not null"`
Extensions string `json:"extensions" gorm:"not null"`
}
+7 -6
View File
@@ -2,12 +2,13 @@ package model
type Snapshot struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null;unique"`
Description string `json:"description" gorm:"type:varchar(256)"`
From string `json:"from"`
Status string `json:"status" gorm:"type:varchar(64)"`
Message string `json:"message" gorm:"type:varchar(256)"`
Version string `json:"version" gorm:"type:varchar(256)"`
Name string `json:"name" gorm:"type:varchar(64);not null;unique"`
Description string `json:"description" gorm:"type:varchar(256)"`
From string `json:"from"`
DefaultDownload string `json:"defaultDownload" gorm:"type:varchar(64)"`
Status string `json:"status" gorm:"type:varchar(64)"`
Message string `json:"message" gorm:"type:varchar(256)"`
Version string `json:"version" gorm:"type:varchar(256)"`
InterruptStep string `json:"interruptStep" gorm:"type:varchar(64)"`
RecoverStatus string `json:"recoverStatus" gorm:"type:varchar(64)"`
+10
View File
@@ -17,6 +17,7 @@ type IAppRepo interface {
OrderByRecommend() DBOption
GetRecommend() DBOption
WithResource(resource string) DBOption
WithLikeName(name string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.App, error)
GetFirst(opts ...DBOption) (model.App, error)
GetBy(opts ...DBOption) ([]model.App, error)
@@ -31,6 +32,15 @@ func NewIAppRepo() IAppRepo {
return &AppRepo{}
}
func (a AppRepo) WithLikeName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 {
return g
}
return g.Where("name like ? or short_desc_zh like ? or short_desc_en like ?", "%"+name+"%", "%"+name+"%", "%"+name+"%")
}
}
func (a AppRepo) WithKey(key string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("key = ?", key)
+5 -1
View File
@@ -3,6 +3,7 @@ package repo
import (
"context"
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/constant"
"gorm.io/gorm/clause"
@@ -170,6 +171,7 @@ type RootInfo struct {
Env string `json:"env"`
Key string `json:"key"`
Version string `json:"version"`
AppPath string `json:"app_path"`
}
func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error) {
@@ -205,7 +207,7 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
if ok {
info.Password = password
}
case "mongodb", "postgresql":
case "mongodb", constant.AppPostgresql:
user, ok := envMap["PANEL_DB_ROOT_USER"].(string)
if ok {
info.UserName = user
@@ -230,5 +232,7 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
info.Param = appInstall.Param
info.Version = appInstall.Version
info.Key = app.Key
appInstall.App = app
info.AppPath = appInstall.GetAppPath()
return &info, nil
}
+13
View File
@@ -2,6 +2,7 @@ package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
@@ -19,9 +20,11 @@ type IBackupRepo interface {
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
DeleteRecord(ctx context.Context, opts ...DBOption) error
UpdateRecord(record *model.BackupRecord) error
WithByDetailName(detailName string) DBOption
WithByFileName(fileName string) DBOption
WithByType(backupType string) DBOption
WithByCronID(cronjobID uint) DBOption
}
func NewIBackupRepo() IBackupRepo {
@@ -105,6 +108,10 @@ func (u *BackupRepo) CreateRecord(record *model.BackupRecord) error {
return global.DB.Create(record).Error
}
func (u *BackupRepo) UpdateRecord(record *model.BackupRecord) error {
return global.DB.Save(record).Error
}
func (u *BackupRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.BackupAccount{}).Where("id = ?", id).Updates(vars).Error
}
@@ -120,3 +127,9 @@ func (u *BackupRepo) Delete(opts ...DBOption) error {
func (u *BackupRepo) DeleteRecord(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.BackupRecord{}).Error
}
func (u *BackupRepo) WithByCronID(cronjobID uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("cronjob_id = ?", cronjobID)
}
}
+15 -3
View File
@@ -20,7 +20,8 @@ type ICronjobRepo interface {
Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error)
Create(cronjob *model.Cronjob) error
WithByJobID(id int) DBOption
WithByBackupID(id uint) DBOption
WithByDbName(name string) DBOption
WithByDefaultDownload(account string) DBOption
WithByRecordDropID(id int) DBOption
WithByRecordFile(file string) DBOption
Save(id uint, cronjob model.Cronjob) error
@@ -28,6 +29,7 @@ type ICronjobRepo interface {
Delete(opts ...DBOption) error
DeleteRecord(opts ...DBOption) error
StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords
UpdateRecords(id uint, vars map[string]interface{}) error
EndRecords(record model.JobRecords, status, message, records string)
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
}
@@ -116,9 +118,15 @@ func (c *CronjobRepo) WithByJobID(id int) DBOption {
}
}
func (c *CronjobRepo) WithByBackupID(id uint) DBOption {
func (c *CronjobRepo) WithByDbName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("target_dir_id = ?", id)
return g.Where("db_name = ?", name)
}
}
func (c *CronjobRepo) WithByDefaultDownload(account string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("default_download = ?", account)
}
}
@@ -164,6 +172,10 @@ func (u *CronjobRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Cronjob{}).Where("id = ?", id).Updates(vars).Error
}
func (u *CronjobRepo) UpdateRecords(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.JobRecords{}).Where("id = ?", id).Updates(vars).Error
}
func (u *CronjobRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
+123
View File
@@ -0,0 +1,123 @@
package repo
import (
"context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"gorm.io/gorm"
)
type PostgresqlRepo struct{}
type IPostgresqlRepo interface {
Get(opts ...DBOption) (model.DatabasePostgresql, error)
WithByPostgresqlName(postgresqlName string) DBOption
WithByFrom(from string) DBOption
List(opts ...DBOption) ([]model.DatabasePostgresql, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.DatabasePostgresql, error)
Create(ctx context.Context, postgresql *model.DatabasePostgresql) error
Delete(ctx context.Context, opts ...DBOption) error
Update(id uint, vars map[string]interface{}) error
DeleteLocal(ctx context.Context) error
}
func NewIPostgresqlRepo() IPostgresqlRepo {
return &PostgresqlRepo{}
}
func (u *PostgresqlRepo) Get(opts ...DBOption) (model.DatabasePostgresql, error) {
var postgresql model.DatabasePostgresql
db := global.DB
for _, opt := range opts {
db = opt(db)
}
if err := db.First(&postgresql).Error; err != nil {
return postgresql, err
}
pass, err := encrypt.StringDecrypt(postgresql.Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresql.Name, err)
}
postgresql.Password = pass
return postgresql, err
}
func (u *PostgresqlRepo) List(opts ...DBOption) ([]model.DatabasePostgresql, error) {
var postgresqls []model.DatabasePostgresql
db := global.DB.Model(&model.DatabasePostgresql{})
for _, opt := range opts {
db = opt(db)
}
if err := db.Find(&postgresqls).Error; err != nil {
return postgresqls, err
}
for i := 0; i < len(postgresqls); i++ {
pass, err := encrypt.StringDecrypt(postgresqls[i].Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresqls[i].Name, err)
}
postgresqls[i].Password = pass
}
return postgresqls, nil
}
func (u *PostgresqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.DatabasePostgresql, error) {
var postgresqls []model.DatabasePostgresql
db := global.DB.Model(&model.DatabasePostgresql{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
if err := db.Limit(size).Offset(size * (page - 1)).Find(&postgresqls).Error; err != nil {
return count, postgresqls, err
}
for i := 0; i < len(postgresqls); i++ {
pass, err := encrypt.StringDecrypt(postgresqls[i].Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresqls[i].Name, err)
}
postgresqls[i].Password = pass
}
return count, postgresqls, nil
}
func (u *PostgresqlRepo) Create(ctx context.Context, postgresql *model.DatabasePostgresql) error {
pass, err := encrypt.StringEncrypt(postgresql.Password)
if err != nil {
return fmt.Errorf("decrypt database db %s password failed, err: %v", postgresql.Name, err)
}
postgresql.Password = pass
return getTx(ctx).Create(postgresql).Error
}
func (u *PostgresqlRepo) Delete(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.DatabasePostgresql{}).Error
}
func (u *PostgresqlRepo) DeleteLocal(ctx context.Context) error {
return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabasePostgresql{}).Error
}
func (u *PostgresqlRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.DatabasePostgresql{}).Where("id = ?", id).Updates(vars).Error
}
func (u *PostgresqlRepo) WithByPostgresqlName(postgresqlName string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("postgresql_name = ?", postgresqlName)
}
}
func (u *PostgresqlRepo) WithByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(from) != 0 {
return g.Where("`from` = ?", from)
}
return g
}
}
+59
View File
@@ -0,0 +1,59 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type PHPExtensionsRepo struct {
}
type IPHPExtensionsRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.PHPExtensions, error)
Save(extension *model.PHPExtensions) error
Create(extension *model.PHPExtensions) error
GetFirst(opts ...DBOption) (model.PHPExtensions, error)
DeleteBy(opts ...DBOption) error
List() ([]model.PHPExtensions, error)
}
func NewIPHPExtensionsRepo() IPHPExtensionsRepo {
return &PHPExtensionsRepo{}
}
func (p *PHPExtensionsRepo) Page(page, size int, opts ...DBOption) (int64, []model.PHPExtensions, error) {
var (
phpExtensions []model.PHPExtensions
)
db := getDb(opts...).Model(&model.PHPExtensions{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&phpExtensions).Error
return count, phpExtensions, err
}
func (p *PHPExtensionsRepo) List() ([]model.PHPExtensions, error) {
var (
phpExtensions []model.PHPExtensions
)
err := getDb().Model(&model.PHPExtensions{}).Find(&phpExtensions).Error
return phpExtensions, err
}
func (p *PHPExtensionsRepo) Save(extension *model.PHPExtensions) error {
return getDb().Save(&extension).Error
}
func (p *PHPExtensionsRepo) Create(extension *model.PHPExtensions) error {
return getDb().Create(&extension).Error
}
func (p *PHPExtensionsRepo) GetFirst(opts ...DBOption) (model.PHPExtensions, error) {
var extension model.PHPExtensions
db := getDb(opts...).Model(&model.PHPExtensions{})
err := db.First(&extension).Error
return extension, err
}
func (p *PHPExtensionsRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.PHPExtensions{}).Error
}
+6 -6
View File
@@ -68,20 +68,20 @@ func (u *SettingRepo) Update(key, value string) error {
}
func (u *SettingRepo) CreateMonitorBase(model model.MonitorBase) error {
return global.DB.Create(&model).Error
return global.MonitorDB.Create(&model).Error
}
func (u *SettingRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
return global.DB.CreateInBatches(ioList, len(ioList)).Error
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
}
func (u *SettingRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error {
return global.DB.CreateInBatches(ioList, len(ioList)).Error
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
}
func (u *SettingRepo) DelMonitorBase(timeForDelete time.Time) error {
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
}
func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
}
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
}
+9 -2
View File
@@ -18,6 +18,7 @@ type IWebsiteRepo interface {
WithDefaultServer() DBOption
WithDomainLike(domain string) DBOption
WithRuntimeID(runtimeID uint) DBOption
WithIDs(ids []uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
List(opts ...DBOption) ([]model.Website, error)
GetFirst(opts ...DBOption) (model.Website, error)
@@ -36,9 +37,15 @@ func NewIWebsiteRepo() IWebsiteRepo {
type WebsiteRepo struct {
}
func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption {
func (w *WebsiteRepo) WithAppInstallId(appInstallID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallId)
return db.Where("app_install_id = ?", appInstallID)
}
}
func (w *WebsiteRepo) WithIDs(ids []uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id in (?)", ids)
}
}
+6
View File
@@ -12,6 +12,7 @@ type IAcmeAccountRepo interface {
Save(account model.WebsiteAcmeAccount) error
DeleteBy(opts ...DBOption) error
WithEmail(email string) DBOption
WithType(acType string) DBOption
}
func NewIAcmeAccountRepo() IAcmeAccountRepo {
@@ -26,6 +27,11 @@ func (w *WebsiteAcmeAccountRepo) WithEmail(email string) DBOption {
return db.Where("email = ?", email)
}
}
func (w *WebsiteAcmeAccountRepo) WithType(acType string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("type = ?", acType)
}
}
func (w *WebsiteAcmeAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteAcmeAccount, error) {
var accounts []model.WebsiteAcmeAccount
+6
View File
@@ -14,6 +14,7 @@ type IWebsiteDomainRepo interface {
WithWebsiteId(websiteId uint) DBOption
WithPort(port int) DBOption
WithDomain(domain string) DBOption
WithDomainLike(domain string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error)
GetFirst(opts ...DBOption) (model.WebsiteDomain, error)
GetBy(opts ...DBOption) ([]model.WebsiteDomain, error)
@@ -44,6 +45,11 @@ func (w WebsiteDomainRepo) WithDomain(domain string) DBOption {
return db.Where("domain = ?", domain)
}
}
func (w WebsiteDomainRepo) WithDomainLike(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("domain like ?", "%"+domain+"%")
}
}
func (w WebsiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) {
var domains []model.WebsiteDomain
db := getDb(opts...).Model(&model.WebsiteDomain{})
+3 -3
View File
@@ -53,7 +53,7 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
var opts []repo.DBOption
opts = append(opts, appRepo.OrderByRecommend())
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
opts = append(opts, appRepo.WithLikeName(req.Name))
}
if req.Type != "" {
opts = append(opts, appRepo.WithType(req.Type))
@@ -397,7 +397,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
index++
}
if app.Limit == 0 && appInstall.Name != serviceName {
if app.Limit == 0 && appInstall.Name != serviceName && len(servicesMap) == 1 {
servicesMap[appInstall.Name] = servicesMap[serviceName]
delete(servicesMap, serviceName)
serviceName = appInstall.Name
@@ -459,7 +459,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
if err = runScript(appInstall, "init"); err != nil {
return
}
upApp(appInstall)
upApp(appInstall, req.PullImage)
}()
go updateToolApp(appInstall)
return
+93 -88
View File
@@ -12,8 +12,6 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"gopkg.in/yaml.v3"
@@ -152,7 +150,7 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
return res, nil
}
if err = syncByID(appInstall.ID); err != nil {
if err = syncAppInstallStatus(&appInstall); err != nil {
return nil, err
}
@@ -184,9 +182,11 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp
if err != nil {
return data, nil
}
data.Username = app.UserName
data.Password = app.Password
data.ServiceName = app.ServiceName
data.Port = app.Port
data.ContainerName = app.ContainerName
return data, nil
}
@@ -240,28 +240,28 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
if err != nil {
return handleErr(install, err, out)
}
return syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Stop:
out, err := compose.Stop(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Restart:
out, err := compose.Restart(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncByID(install.ID)
return syncAppInstallStatus(&install)
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 syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Upgrade:
return upgradeInstall(install.ID, req.DetailId, req.Backup)
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
case constant.Reload:
return opNginx(install.ContainerName, constant.NginxReload)
default:
@@ -411,7 +411,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
return err
}
for _, i := range allList {
if i.Status == constant.Installing || i.Status == constant.Upgrading {
if i.Status == constant.Installing || i.Status == constant.Upgrading || i.Status == constant.Rebuilding {
if systemInit {
i.Status = constant.Error
i.Message = "1Panel restart causes the task to terminate"
@@ -420,7 +420,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
continue
}
if !systemInit {
if err := syncByID(i.ID); err != nil {
if err = syncAppInstallStatus(&i); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
@@ -431,6 +431,9 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
func (a *AppInstallService) GetServices(key string) ([]response.AppService, error) {
var res []response.AppService
if DatabaseKeys[key] > 0 {
if key == constant.AppPostgres {
key = constant.AppPostgresql
}
dbs, _ := databaseRepo.GetList(commonRepo.WithByType(key))
if len(dbs) == 0 {
return res, nil
@@ -524,7 +527,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
return nil
}
if err := updateInstallInfoInDB(req.Key, req.Name, "port", true, strconv.FormatInt(req.Port, 10)); err != nil {
if err := updateInstallInfoInDB(req.Key, req.Name, "port", strconv.FormatInt(req.Port, 10)); err != nil {
return nil
}
@@ -636,10 +639,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
for _, form := range appForm.FormFields {
if v, ok := envs[form.EnvKey]; ok {
appParam := response.AppParam{
Edit: false,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
Edit: false,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
Multiple: form.Multiple,
}
if form.Edit {
appParam.Edit = true
@@ -651,10 +655,20 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
appParam.ShowValue = appInstall.Name
} else if form.Type == "select" {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
if form.Multiple {
if v == "" {
appParam.Value = []string{}
} else {
if str, ok := v.(string); ok {
appParam.Value = strings.Split(str, ",")
}
}
} else {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
}
}
}
appParam.Values = form.Values
@@ -662,14 +676,15 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
params = append(params, appParam)
} else {
params = append(params, response.AppParam{
Edit: form.Edit,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
LabelZh: form.LabelZh,
LabelEn: form.LabelEn,
Value: form.Default,
Values: form.Values,
Edit: form.Edit,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
LabelZh: form.LabelZh,
LabelEn: form.LabelEn,
Value: form.Default,
Values: form.Values,
Multiple: form.Multiple,
})
}
}
@@ -685,15 +700,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
return &res, nil
}
func syncByID(installID uint) error {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
if err != nil {
return err
}
if appInstall.Status == constant.Installing {
func syncAppInstallStatus(appInstall *model.AppInstall) error {
if appInstall.Status == constant.Installing || appInstall.Status == constant.Rebuilding || appInstall.Status == constant.Upgrading {
return nil
}
containerNames, err := getContainerNames(appInstall)
containerNames, err := getContainerNames(*appInstall)
if err != nil {
return err
}
@@ -705,75 +716,69 @@ func syncByID(installID uint) error {
if err != nil {
return err
}
var (
errorContainers []string
notFoundContainers []string
runningContainers []string
exitedContainers []string
runningCount int
exitedCount int
pausedCount int
exitedContainerNames []string
total = len(containerNames)
count = len(containers)
)
for _, n := range containers {
switch n.State {
foundNames := make(map[string]bool)
for _, con := range containers {
foundNames[con.Names[0]] = true
switch con.State {
case "exited":
exitedContainers = append(exitedContainers, n.Names[0])
exitedContainerNames = append(exitedContainerNames, strings.TrimPrefix(con.Names[0], "/"))
exitedCount++
case "running":
runningContainers = append(runningContainers, n.Names[0])
default:
errorContainers = append(errorContainers, n.Names[0])
runningCount++
case "paused":
pausedCount++
}
}
var notFoundNames []string
for _, name := range containerNames {
exist := false
for _, container := range containers {
if common.ExistWithStrArray(name, container.Names) {
exist = true
break
}
}
if !exist {
notFoundContainers = append(notFoundContainers, name)
if !foundNames["/"+name] {
notFoundNames = append(notFoundNames, name)
}
}
containerCount := len(containers)
errCount := len(errorContainers)
notFoundCount := len(notFoundContainers)
existedCount := len(exitedContainers)
normalCount := len(containerNames)
runningCount := len(runningContainers)
if containerCount == 0 {
appInstall.Status = constant.Error
appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
appInstall.Status = constant.Running
return appInstallRepo.Save(context.Background(), &appInstall)
}
if existedCount == normalCount {
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
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == normalCount {
appInstall.Status = constant.Error
}
if runningCount < normalCount {
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)
var errMsg string
if errCount > 0 {
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
}
if notFoundCount > 0 {
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
}
appInstall.Message = errMsg
return appInstallRepo.Save(context.Background(), &appInstall)
return nil
}
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
func updateInstallInfoInDB(appKey, appName, param string, value interface{}) error {
if param != "password" && param != "port" && param != "user-password" {
return nil
}
@@ -781,7 +786,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
if err != nil {
return nil
}
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, appKey, appInstall.Name)
envPath := fmt.Sprintf("%s/%s/.env", appInstall.AppPath, appInstall.Name)
lineBytes, err := os.ReadFile(envPath)
if err != nil {
return err
@@ -790,7 +795,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
envKey := ""
switch param {
case "password":
if appKey == "mysql" || appKey == "mariadb" {
if appKey == "mysql" || appKey == "mariadb" || appKey == "postgresql" {
envKey = "PANEL_DB_ROOT_PASSWORD="
} else {
envKey = "PANEL_REDIS_ROOT_PASSWORD="
+213 -103
View File
@@ -5,6 +5,8 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/docker/docker/api/types/container"
"math"
"net/http"
"os"
@@ -37,7 +39,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/compose"
composeV2 "github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
dockerTypes "github.com/docker/docker/api/types"
"github.com/pkg/errors"
)
@@ -110,12 +111,13 @@ func checkPortExist(port int) error {
}
var DatabaseKeys = map[string]uint{
"mysql": 3306,
"mariadb": 3306,
"postgresql": 5432,
"mongodb": 27017,
"redis": 6379,
"memcached": 11211,
constant.AppMysql: 3306,
constant.AppMariaDB: 3306,
constant.AppPostgresql: 5432,
constant.AppPostgres: 5432,
constant.AppMongodb: 27017,
constant.AppRedis: 6379,
constant.AppMemcached: 11211,
}
var ToolKeys = map[string]uint{
@@ -154,7 +156,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
}
switch app.Key {
case "mysql", "mariadb", "postgresql", "mongodb":
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb:
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
if password != "" {
database.Password = password.(string)
@@ -176,7 +178,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
}
}
case "redis":
case constant.AppRedis:
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.RedisAuthParam{
@@ -233,28 +235,55 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
}
var resourceId uint
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
iMysqlRepo := repo.NewIMysqlRepo()
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
resourceId = oldMysqlDb.ID
if oldMysqlDb.ID > 0 {
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
return buserr.New(constant.ErrDbUserNotValid)
switch database.Type {
case constant.AppPostgresql, constant.AppPostgres:
iPostgresqlRepo := repo.NewIPostgresqlRepo()
oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal))
resourceId = oldPostgresqlDb.ID
if oldPostgresqlDb.ID > 0 {
if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password {
return buserr.New(constant.ErrDbUserNotValid)
}
} else {
var createPostgresql dto.PostgresqlDBCreate
createPostgresql.Name = dbConfig.DbName
createPostgresql.Username = dbConfig.DbUser
createPostgresql.Database = database.Name
createPostgresql.Format = "UTF8"
createPostgresql.Password = dbConfig.Password
createPostgresql.From = database.From
createPostgresql.SuperUser = true
pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql)
if err != nil {
return err
}
resourceId = pgdb.ID
}
} else {
var createMysql dto.MysqlDBCreate
createMysql.Name = dbConfig.DbName
createMysql.Username = dbConfig.DbUser
createMysql.Database = database.Name
createMysql.Format = "utf8mb4"
createMysql.Permission = "%"
createMysql.Password = dbConfig.Password
createMysql.From = database.From
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
if err != nil {
return err
case constant.AppMysql, constant.AppMariaDB:
iMysqlRepo := repo.NewIMysqlRepo()
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
resourceId = oldMysqlDb.ID
if oldMysqlDb.ID > 0 {
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
return buserr.New(constant.ErrDbUserNotValid)
}
} else {
var createMysql dto.MysqlDBCreate
createMysql.Name = dbConfig.DbName
createMysql.Username = dbConfig.DbUser
createMysql.Database = database.Name
createMysql.Format = "utf8mb4"
createMysql.Permission = "%"
createMysql.Password = dbConfig.Password
createMysql.From = database.From
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
if err != nil {
return err
}
resourceId = mysqldb.ID
}
resourceId = mysqldb.ID
}
}
var installResource model.AppInstallResource
installResource.ResourceId = resourceId
@@ -326,8 +355,8 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
websiteApp, _ := appRepo.GetFirst(commonRepo.WithByID(websiteAppInstall.AppId))
if websiteApp.Type == constant.RuntimePHP {
go func() {
_, _ = compose.Down(install.GetComposePath())
_ = op.DeleteDir(install.GetPath())
_, _ = compose.Down(websiteAppInstall.GetComposePath())
_ = op.DeleteDir(websiteAppInstall.GetPath())
}()
_ = appInstallRepo.Delete(ctx, websiteAppInstall)
}
@@ -336,8 +365,11 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
}
_ = websiteRepo.DeleteAll(ctx)
_ = websiteDomainRepo.DeleteAll(ctx)
xpack.RemoveTamper("")
case constant.AppMysql, constant.AppMariaDB:
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
case constant.AppPostgresql:
_ = postgresqlRepo.Delete(ctx, postgresqlRepo.WithByPostgresqlName(install.Name))
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
@@ -364,32 +396,51 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return nil
}
for _, re := range resources {
mysqlService := NewIMysqlService()
if (re.Key == constant.AppMysql || re.Key == constant.AppMariaDB) && deleteDB {
database, _ := mysqlRepo.Get(commonRepo.WithByID(re.ResourceId))
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
continue
}
if err := mysqlService.Delete(ctx, dto.MysqlDBDelete{
ID: database.ID,
ForceDelete: forceDelete,
DeleteBackup: deleteBackup,
Type: re.Key,
Database: database.MysqlName,
}); err != nil && !forceDelete {
return err
if deleteDB {
switch re.Key {
case constant.AppMysql, constant.AppMariaDB:
mysqlService := NewIMysqlService()
database, _ := mysqlRepo.Get(commonRepo.WithByID(re.ResourceId))
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
continue
}
if err := mysqlService.Delete(ctx, dto.MysqlDBDelete{
ID: database.ID,
ForceDelete: forceDelete,
DeleteBackup: deleteBackup,
Type: re.Key,
Database: database.MysqlName,
}); err != nil && !forceDelete {
return err
}
case constant.AppPostgresql:
pgsqlService := NewIPostgresqlService()
database, _ := postgresqlRepo.Get(commonRepo.WithByID(re.ResourceId))
if reflect.DeepEqual(database, model.DatabasePostgresql{}) {
continue
}
if err := pgsqlService.Delete(ctx, dto.PostgresqlDBDelete{
ID: database.ID,
ForceDelete: forceDelete,
DeleteBackup: deleteBackup,
Type: re.Key,
Database: database.PostgresqlName,
}); err != nil {
return err
}
}
}
}
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func upgradeInstall(installId uint, detailId uint, backup bool) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
if err != nil {
return err
}
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailId))
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailID))
if err != nil {
return err
}
@@ -443,23 +494,6 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
}
images, err := composeV2.GetDockerComposeImages([]byte(detail.DockerCompose))
if err != nil {
upErr = err
return
}
dockerCli, err := composeV2.NewClient()
if err != nil {
upErr = err
return
}
for _, image := range images {
if err = dockerCli.PullImage(image, true); err != nil {
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return
}
}
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
stdout, _ := command.CombinedOutput()
if stdout != nil {
@@ -524,7 +558,7 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
install.DockerCompose = string(composeByte)
install.Version = detail.Version
install.AppDetailId = detailId
install.AppDetailId = detailID
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
@@ -543,6 +577,31 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
return
}
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
}
for _, image := range images {
if err = dockerCli.PullImage(image, true); err != nil {
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return
}
}
}
if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil {
return
}
@@ -630,8 +689,14 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
envParams[k] = strconv.Itoa(int(t))
case int:
envParams[k] = strconv.Itoa(t)
default:
envParams[k] = t.(string)
case []interface{}:
strArray := make([]string, len(t))
for i := range t {
strArray[i] = strings.ToLower(fmt.Sprintf("%v", t[i]))
}
envParams[k] = strings.Join(strArray, ",")
case map[string]interface{}:
handleMap(t, envParams)
}
}
}
@@ -764,7 +829,7 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
if err != nil {
return false, err
}
var options dockerTypes.ContainerListOptions
var options container.ListOptions
list, err := client.ContainerList(context.Background(), options)
if err != nil {
return false, err
@@ -784,39 +849,36 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
return false, nil
}
func upApp(appInstall *model.AppInstall) {
func upApp(appInstall *model.AppInstall, pullImages bool) {
upProject := func(appInstall *model.AppInstall) (err error) {
if err == nil {
var (
out string
errMsg string
)
if appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
}
out, err = compose.Up(appInstall.GetComposePath())
var (
out string
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
return
} else {
return
}
out, err = compose.Up(appInstall.GetComposePath())
if err != nil {
if out != "" {
appInstall.Message = errMsg + out
}
return err
}
return
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
@@ -839,11 +901,12 @@ func rebuildApp(appInstall model.AppInstall) error {
_ = handleErr(appInstall, err, out)
return
}
out, err = compose.Up(dockerComposePath)
out, err = compose.Up(appInstall.GetComposePath())
if err != nil {
_ = handleErr(appInstall, err, out)
return
}
appInstall.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &appInstall)
}()
@@ -931,6 +994,43 @@ func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
if err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
var appParam dto.AppForm
if err = json.Unmarshal(dataJson, &appParam); err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
for _, formField := range appParam.FormFields {
if strings.Contains(formField.EnvKey, " ") {
return buserr.WithName(constant.ErrAppParamKey, formField.EnvKey)
}
}
var dataMap map[string]interface{}
err = yaml.Unmarshal(paramByte, &dataMap)
if err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
}
formFieldsInterface, ok := additionalProperties["formFields"]
if ok {
formFields, ok := formFieldsInterface.([]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "formFields")
}
for _, item := range formFields {
field := item.(map[string]interface{})
for key, value := range field {
if value == nil {
return buserr.WithName(constant.ErrAppParamKey, key)
}
}
}
}
appDetail.Params = string(dataJson)
return nil
}
@@ -982,8 +1082,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
if out != "" {
install.Message = out
reErr = errors.New(out)
install.Status = constant.Error
}
install.Status = constant.Error
_ = appInstallRepo.Save(context.Background(), &install)
return reErr
}
@@ -994,6 +1094,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
continue
}
if err := syncAppInstallStatus(&installed); err != nil {
global.LOG.Error("sync app install status error : ", err)
}
installDTO := response.AppInstalledDTO{
AppInstall: installed,
Path: installed.GetPath(),
@@ -1008,7 +1112,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
}
var versions []string
for _, detail := range details {
if detail.IgnoreUpgrade {
if detail.IgnoreUpgrade || installed.Version == "latest" {
continue
}
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
@@ -1123,14 +1227,20 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
return buserr.New(constant.ErrFileParse)
}
serviceValue := service.(map[string]interface{})
deploy := map[string]interface{}{
"resources": map[string]interface{}{
"limits": map[string]interface{}{
"cpus": "${CPUS}",
"memory": "${MEMORY_LIMIT}",
},
},
deploy := map[string]interface{}{}
if de, ok := serviceValue["deploy"]; ok {
deploy = de.(map[string]interface{})
}
resource := map[string]interface{}{}
if res, ok := deploy["resources"]; ok {
resource = res.(map[string]interface{})
}
resource["limits"] = map[string]interface{}{
"cpus": "${CPUS}",
"memory": "${MEMORY_LIMIT}",
}
deploy["resources"] = resource
serviceValue["deploy"] = deploy
ports, ok := serviceValue["ports"].([]interface{})
+23 -5
View File
@@ -1,6 +1,7 @@
package service
import (
"crypto/hmac"
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -19,6 +20,7 @@ type AuthService struct{}
type IAuthService interface {
CheckIsSafety(code string) (string, error)
GetResponsePage() (string, error)
VerifyCode(code string) (bool, error)
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
@@ -42,7 +44,7 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
if err != nil {
return nil, constant.ErrAuth
}
if info.Password != pass || nameSetting.Value != info.Name {
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
@@ -78,7 +80,7 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
if err != nil {
return nil, err
}
if info.Password != pass || nameSetting.Value != info.Name {
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
@@ -109,6 +111,10 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
if err != nil {
return nil, err
}
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return nil, err
}
lifeTime, err := strconv.Atoi(setting.Value)
if err != nil {
return nil, err
@@ -129,7 +135,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
sessionUser, err := global.SESSION.Get(sID)
if err != nil {
sID = uuid.New().String()
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", true)
err := global.SESSION.Set(sID, sessionUser, lifeTime)
if err != nil {
return nil, err
@@ -144,9 +150,13 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
}
func (u *AuthService) LogOut(c *gin.Context) error {
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
if sID != "" {
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", true)
err := global.SESSION.Delete(sID)
if err != nil {
return err
@@ -160,7 +170,7 @@ func (u *AuthService) VerifyCode(code string) (bool, error) {
if err != nil {
return false, err
}
return setting.Value == code, nil
return hmac.Equal([]byte(setting.Value), []byte(code)), nil
}
func (u *AuthService) CheckIsSafety(code string) (string, error) {
@@ -176,3 +186,11 @@ func (u *AuthService) CheckIsSafety(code string) (string, error) {
}
return "unpass", nil
}
func (u *AuthService) GetResponsePage() (string, error) {
pageCode, err := settingRepo.Get(settingRepo.WithByKey("NoAuthSetting"))
if err != nil {
return "", err
}
return pageCode.Value, nil
}
+200 -103
View File
@@ -6,12 +6,12 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"sort"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
@@ -19,6 +19,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -29,7 +30,8 @@ type BackupService struct{}
type IBackupService interface {
List() ([]dto.BackupInfo, error)
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
LoadOneDriveInfo() (string, error)
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
LoadOneDriveInfo() (dto.OneDriveInfo, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
@@ -38,11 +40,14 @@ type IBackupService interface {
BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
ListFiles(req dto.BackupSearchFile) ([]string, error)
ListFiles(req dto.BackupSearchFile) []string
MysqlBackup(db dto.CommonBackup) error
PostgresqlBackup(db dto.CommonBackup) error
MysqlRecover(db dto.CommonRecover) error
PostgresqlRecover(db dto.CommonRecover) error
MysqlRecoverByUpload(req dto.CommonRecover) error
PostgresqlRecoverByUpload(req dto.CommonRecover) error
RedisBackup() error
RedisRecover(db dto.CommonRecover) error
@@ -52,6 +57,8 @@ type IBackupService interface {
AppBackup(db dto.CommonBackup) error
AppRecover(req dto.CommonRecover) error
Run()
}
func NewIBackupService() IBackupService {
@@ -81,63 +88,72 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
commonRepo.WithByType(search.Type),
backupRepo.WithByDetailName(search.DetailName),
)
var datas []dto.BackupRecords
clientMap := make(map[string]loadSizeHelper)
for i := 0; i < len(records); i++ {
var item dto.BackupRecords
if err := copier.Copy(&item, &records[i]); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
itemPath := path.Join(records[i].FileDir, records[i].FileName)
if records[i].Source == "LOCAL" {
fileInfo, _ := os.Stat(itemPath)
item.Size = fileInfo.Size()
datas = append(datas, item)
continue
}
if _, ok := clientMap[records[i].Source]; !ok {
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
if err != nil {
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
return total, datas, err
}
client, err := u.NewClient(&backup)
if err != nil {
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
return total, datas, err
}
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
datas = append(datas, item)
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client}
continue
}
item.Size, _ = clientMap[records[i].Source].client.Size(path.Join(clientMap[records[i].Source].backupPath, itemPath))
datas = append(datas, item)
if err != nil {
return 0, nil, err
}
datas, err := u.loadRecordSize(records)
sort.Slice(datas, func(i, j int) bool {
return datas[i].CreatedAt.After(datas[j].CreatedAt)
})
return total, datas, err
}
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
total, records, err := backupRepo.PageRecord(
search.Page, search.PageSize,
commonRepo.WithOrderBy("created_at desc"),
backupRepo.WithByCronID(search.CronjobID),
)
if err != nil {
return 0, nil, err
}
datas, err := u.loadRecordSize(records)
sort.Slice(datas, func(i, j int) bool {
return datas[i].CreatedAt.After(datas[j].CreatedAt)
})
return total, datas, err
}
type loadSizeHelper struct {
isOk bool
backupPath string
client cloud_storage.CloudStorageClient
}
func (u *BackupService) LoadOneDriveInfo() (string, error) {
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
func (u *BackupService) LoadOneDriveInfo() (dto.OneDriveInfo, error) {
var data dto.OneDriveInfo
data.RedirectUri = constant.OneDriveRedirectURI
clientID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
if err != nil {
return "", err
return data, err
}
idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value)
idItem, err := base64.StdEncoding.DecodeString(clientID.Value)
if err != nil {
return "", err
return data, err
}
return string(idItem), err
data.ClientID = string(idItem)
clientSecret, err := settingRepo.Get(settingRepo.WithByKey("OneDriveSc"))
if err != nil {
return data, err
}
secretItem, err := base64.StdEncoding.DecodeString(clientSecret.Value)
if err != nil {
return data, err
}
data.ClientSecret = string(secretItem)
return data, err
}
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
if info.Source == "LOCAL" {
return info.FileDir + "/" + info.FileName, nil
localDir, err := loadLocalDir()
if err != nil {
return "", err
}
return path.Join(localDir, info.FileDir, info.FileName), nil
}
backup, _ := backupRepo.Get(commonRepo.WithByType(info.Source))
if backup.ID == 0 {
@@ -200,6 +216,9 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
}
}
if backup.Type == constant.OneDrive {
StartRefreshOneDriveToken()
}
if err := backupRepo.Create(&backup); err != nil {
return err
}
@@ -227,7 +246,14 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
}
func (u *BackupService) Delete(id uint) error {
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByBackupID(id))
backup, _ := backupRepo.Get(commonRepo.WithByID(id))
if backup.ID == 0 {
return constant.ErrRecordNotFound
}
if backup.Type == constant.OneDrive {
global.Cron.Remove(global.OneDriveCronID)
}
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDefaultDownload(backup.Type))
if len(cronjobs) != 0 {
return buserr.New(constant.ErrBackupInUsed)
}
@@ -240,22 +266,18 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
return err
}
for _, record := range records {
if record.Source == "LOCAL" {
if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil {
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
}
} else {
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
return err
}
client, err := u.NewClient(&backupAccount)
if err != nil {
return err
}
if _, err = client.Delete(record.FileDir + record.FileName); err != nil {
global.LOG.Errorf("remove file %s from %s failed, err: %v", record.FileDir+record.FileName, record.Source, err)
}
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)
}
}
return backupRepo.DeleteRecord(context.Background(), commonRepo.WithIdsIn(ids))
@@ -308,27 +330,29 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
if backup.Type == "LOCAL" {
if dir, ok := varMap["dir"]; ok {
if dirStr, isStr := dir.(string); isStr {
if strings.HasSuffix(dirStr, "/") {
if strings.HasSuffix(dirStr, "/") && dirStr != "/" {
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
}
if err := copyDir(oldDir, dirStr); err != nil {
_ = backupRepo.Update(req.ID, (map[string]interface{}{"vars": oldVars}))
return err
}
global.CONF.System.Backup = dirStr
}
}
}
return nil
}
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
func (u *BackupService) ListFiles(req dto.BackupSearchFile) []string {
var datas []string
backup, err := backupRepo.Get(backupRepo.WithByType(req.Type))
if err != nil {
return nil, err
return datas
}
client, err := u.NewClient(&backup)
if err != nil {
return nil, err
return datas
}
prefix := "system_snapshot"
if len(backup.BackupPath) != 0 {
@@ -336,15 +360,15 @@ func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
}
files, err := client.ListObjects(prefix)
if err != nil {
return nil, err
global.LOG.Debugf("load files from %s failed, err: %v", req.Type, err)
return datas
}
var datas []string
for _, file := range files {
if len(file) != 0 {
datas = append(datas, path.Base(file))
}
}
return datas, nil
return datas
}
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
@@ -352,9 +376,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err
}
if backup.Type == "LOCAL" {
return nil, errors.New("not support")
}
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp, constant.WebDAV:
@@ -382,6 +403,15 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
if err := copier.Copy(&item, &account); err != nil {
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
}
if account.Type == constant.OneDrive {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
return dto.BackupInfo{Type: accountType}
}
delete(varMap, "refresh_token")
itemVars, _ := json.Marshal(varMap)
item.Vars = string(itemVars)
}
return item
}
}
@@ -393,48 +423,68 @@ 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)
}
data := url.Values{}
data.Set("client_id", global.CONF.System.OneDriveID)
data.Set("client_secret", global.CONF.System.OneDriveSc)
data.Set("grant_type", "authorization_code")
data.Set("code", varMap["code"].(string))
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
token, refreshToken, err := client.RefreshToken("authorization_code", varMap)
if err != nil {
return fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request for access token failed, err: %v", err)
return err
}
delete(varMap, "code")
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()
token := map[string]interface{}{}
if err := json.Unmarshal(respBody, &token); err != nil {
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := token["refresh_token"].(string)
if !ok {
return errors.New("no such access token in response")
}
backup.Credential = token
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
varMap["refresh_token"] = refreshToken
itemVars, err := json.Marshal(varMap)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
backup.Credential = accessToken
backup.Vars = string(itemVars)
return nil
}
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupRecords, error) {
var datas []dto.BackupRecords
clientMap := make(map[string]loadSizeHelper)
var wg sync.WaitGroup
for i := 0; i < len(records); i++ {
var item dto.BackupRecords
if err := copier.Copy(&item, &records[i]); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
itemPath := path.Join(records[i].FileDir, records[i].FileName)
if _, ok := clientMap[records[i].Source]; !ok {
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
if err != nil {
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
clientMap[records[i].Source] = loadSizeHelper{}
datas = append(datas, item)
continue
}
client, err := u.NewClient(&backup)
if err != nil {
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
clientMap[records[i].Source] = loadSizeHelper{}
datas = append(datas, item)
continue
}
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
datas = append(datas, item)
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
continue
}
if clientMap[records[i].Source].isOk {
wg.Add(1)
go func(index int) {
item.Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
datas = append(datas, item)
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
return datas, nil
}
func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
@@ -516,3 +566,50 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
return client.Upload(fileItem, targetPath)
}
func StartRefreshOneDriveToken() {
service := NewIBackupService()
oneDriveCronID, err := global.Cron.AddJob("0 * * * *", service)
if err != nil {
global.LOG.Errorf("can not add OneDrive corn job: %s", err.Error())
return
}
global.OneDriveCronID = oneDriveCronID
}
func (u *BackupService) Run() {
var backupItem model.BackupAccount
_ = global.DB.Where("`type` = ?", "OneDrive").First(&backupItem)
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)
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
if err != nil {
varMap["refresh_status"] = constant.StatusFailed
varMap["refresh_msg"] = err.Error()
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
return
}
varMap["refresh_token"] = refreshToken
varsItem, _ := json.Marshal(varMap)
_ = global.DB.Model(&model.BackupAccount{}).
Where("id = ?", backupItem.ID).
Updates(map[string]interface{}{
"credential": token,
"vars": varsItem,
}).Error
global.LOG.Info("Successfully refreshed OneDrive token.")
}
+39 -9
View File
@@ -10,10 +10,13 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"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/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
@@ -33,10 +36,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
return err
}
timeNow := time.Now().Format("20060102150405")
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
backupDir := path.Join(localDir, itemDir)
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", req.Name, req.DetailName))
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
return err
}
@@ -47,7 +50,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
DetailName: req.DetailName,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: backupDir,
FileDir: itemDir,
FileName: fileName,
}
@@ -70,7 +73,7 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(req.File) {
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
return buserr.WithName("ErrFileNotFound", req.File)
}
if _, err := compose.Down(install.GetComposePath()); err != nil {
return err
@@ -106,12 +109,21 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
for _, resource := range resources {
if resource.Key == "mysql" || resource.Key == "mariadb" {
switch resource.Key {
case constant.AppMysql, constant.AppMariaDB:
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
}
case constant.AppPostgresql:
db, err := postgresqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handlePostgresqlBackup(db.PostgresqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
}
}
@@ -190,7 +202,21 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
return err
}
}
if database.Type == "mysql" || database.Type == "mariadb" {
switch database.Type {
case constant.AppPostgresql:
db, err := postgresqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handlePostgresqlRecover(dto.CommonRecover{
Name: database.Name,
DetailName: db.Name,
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
}, true); err != nil {
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
return err
}
case constant.AppMysql, constant.AppMariaDB:
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
@@ -234,7 +260,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
_ = fileOp.DeleteDir(backPath)
if len(newEnvFile) != 0 {
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
envPath := fmt.Sprintf("%s/%s/.env", install.GetAppPath(), install.Name)
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
@@ -278,6 +304,10 @@ func reCreateDB(dbID uint, database model.Database, oldEnv string) (*model.Datab
Password: oldPassword,
Permission: "%",
})
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDbName(fmt.Sprintf("%v", dbID)))
for _, job := range cronjobs {
_ = cronjobRepo.Update(job.ID, map[string]interface{}{"db_name": fmt.Sprintf("%v", createDB.ID)})
}
if err != nil {
return nil, envMap, err
}
+20 -9
View File
@@ -8,12 +8,14 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
"github.com/pkg/errors"
)
func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
@@ -23,10 +25,11 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
}
timeNow := time.Now().Format("20060102150405")
targetDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName))
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
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))
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
if err := handleMysqlBackup(req.Name, req.Type, req.DetailName, targetDir, fileName); err != nil {
return err
}
@@ -36,7 +39,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
DetailName: req.DetailName,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: targetDir,
FileDir: itemDir,
FileName: fileName,
}
if err := backupRepo.CreateRecord(record); err != nil {
@@ -97,18 +100,20 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
return nil
}
func handleMysqlBackup(database, dbName, targetDir, fileName string) error {
func handleMysqlBackup(database, dbType, dbName, targetDir, fileName string) error {
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(database))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(database)
cli, version, err := LoadMysqlClientByFrom(database)
if err != nil {
return err
}
backupInfo := client.BackupInfo{
Name: dbName,
Type: dbType,
Version: version,
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
@@ -125,13 +130,13 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
isOk := false
fileOp := files.NewFileOp()
if !fileOp.Stat(req.File) {
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
return buserr.WithName("ErrFileNotFound", req.File)
}
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(req.Name)
cli, version, err := LoadMysqlClientByFrom(req.Name)
if err != nil {
return err
}
@@ -140,6 +145,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
@@ -153,6 +160,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
global.LOG.Info("recover failed, start to rollback now")
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
SourceFile: rollbackFile,
@@ -169,6 +178,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
}
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
SourceFile: req.File,
+177
View File
@@ -0,0 +1,177 @@
package service
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/common"
pgclient "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
)
func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
localDir, err := loadLocalDir()
if err != nil {
return err
}
timeNow := time.Now().Format("20060102150405")
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))
if err := handlePostgresqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
return err
}
record := &model.BackupRecord{
Type: req.Type,
Name: req.Name,
DetailName: req.DetailName,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: itemDir,
FileName: fileName,
}
if err := backupRepo.CreateRecord(record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
}
return nil
}
func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
if err := handlePostgresqlRecover(req, false); err != nil {
return err
}
return nil
}
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")
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 {
_ = os.RemoveAll(dstDir)
return err
}
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
hasTestSql := false
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && info.Name() == "test.sql" {
hasTestSql = true
file = path
fileName = "test.sql"
}
return nil
})
if !hasTestSql {
_ = os.RemoveAll(dstDir)
return fmt.Errorf("no such file named test.sql in %s", fileName)
}
defer func() {
_ = os.RemoveAll(dstDir)
}()
}
req.File = path.Dir(file) + "/" + fileName
if err := handlePostgresqlRecover(req, false); err != nil {
return err
}
global.LOG.Info("recover from uploads successful!")
return nil
}
func handlePostgresqlBackup(database, dbName, targetDir, fileName string) error {
cli, err := LoadPostgresqlClientByFrom(database)
if err != nil {
return err
}
defer cli.Close()
backupInfo := pgclient.BackupInfo{
Name: dbName,
TargetDir: targetDir,
FileName: fileName,
Timeout: 300,
}
if err := cli.Backup(backupInfo); err != nil {
return err
}
return nil
}
func handlePostgresqlRecover(req dto.CommonRecover, isRollback bool) error {
isOk := false
fileOp := files.NewFileOp()
if !fileOp.Stat(req.File) {
return buserr.WithName("ErrFileNotFound", req.File)
}
dbInfo, err := postgresqlRepo.Get(commonRepo.WithByName(req.DetailName), postgresqlRepo.WithByPostgresqlName(req.Name))
if err != nil {
return err
}
cli, err := LoadPostgresqlClientByFrom(req.Name)
if err != nil {
return err
}
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")))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
Timeout: 300,
}); err != nil {
return fmt.Errorf("backup postgresql db %s for rollback before recover failed, err: %v", req.DetailName, err)
}
defer func() {
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
SourceFile: rollbackFile,
Timeout: 300,
}); err != nil {
global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
}
global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
}
}()
}
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
SourceFile: req.File,
Username: dbInfo.Username,
Timeout: 300,
}); err != nil {
return err
}
isOk = true
return nil
}
+6 -4
View File
@@ -14,6 +14,7 @@ import (
"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/compose"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
@@ -34,7 +35,7 @@ func (u *BackupService) RedisBackup() error {
}
global.LOG.Infof("appendonly in redis conf is %s", appendonly)
timeNow := time.Now().Format("20060102150405")
timeNow := time.Now().Format("20060102150405") + common.RandStrAndNum(5)
fileName := fmt.Sprintf("%s.rdb", timeNow)
if appendonly == "yes" {
if strings.HasPrefix(redisInfo.Version, "6.") {
@@ -43,7 +44,8 @@ func (u *BackupService) RedisBackup() error {
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
}
}
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
itemDir := fmt.Sprintf("database/redis/%s", redisInfo.Name)
backupDir := path.Join(localDir, itemDir)
if err := handleRedisBackup(redisInfo, backupDir, fileName); err != nil {
return err
}
@@ -51,7 +53,7 @@ func (u *BackupService) RedisBackup() error {
Type: "redis",
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: backupDir,
FileDir: itemDir,
FileName: fileName,
}
if err := backupRepo.CreateRecord(record); err != nil {
@@ -111,7 +113,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, backupDir, fileName string) err
func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback bool) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(recoverFile) {
return fmt.Errorf("%s file is not exist", recoverFile)
return buserr.WithName("ErrFileNotFound", recoverFile)
}
appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly")
+22 -19
View File
@@ -15,6 +15,7 @@ import (
"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/compose"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
@@ -25,14 +26,15 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
if err != nil {
return err
}
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
if err != nil {
return err
}
timeNow := time.Now().Format("20060102150405")
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", req.Name))
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow)
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 {
return err
}
@@ -40,10 +42,10 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
record := &model.BackupRecord{
Type: "website",
Name: website.PrimaryDomain,
DetailName: "",
DetailName: req.DetailName,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: backupDir,
FileDir: itemDir,
FileName: fileName,
}
if err := backupRepo.CreateRecord(record); err != nil {
@@ -54,13 +56,13 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
}
func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(req.Name))
if err != nil {
return err
}
fileOp := files.NewFileOp()
if !fileOp.Stat(req.File) {
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
return buserr.WithName("ErrFileNotFound", req.File)
}
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
if err != nil {
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 {
@@ -79,15 +81,6 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
_ = os.RemoveAll(tmpPath)
}()
temPathWithName := tmpPath + "/" + website.Alias
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
}
if website.Type == constant.Deployment {
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
}
}
var oldWebsite model.Website
websiteJson, err := os.ReadFile(tmpPath + "/website.json")
if err != nil {
@@ -101,6 +94,16 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
return err
}
temPathWithName := tmpPath + "/" + website.Alias
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
}
if website.Type == constant.Deployment {
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
}
}
isOk := false
if !isRollback {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format("20060102150405")))
+4 -4
View File
@@ -23,7 +23,7 @@ func NewICommandService() ICommandService {
}
func (u *CommandService) List() ([]dto.CommandInfo, error) {
commands, err := commandRepo.GetList()
commands, err := commandRepo.GetList(commonRepo.WithOrderBy("name"))
if err != nil {
return nil, constant.ErrRecordNotFound
}
@@ -39,7 +39,7 @@ func (u *CommandService) List() ([]dto.CommandInfo, error) {
}
func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) {
cmdList, err := commandRepo.GetList()
cmdList, err := commandRepo.GetList(commonRepo.WithOrderBy("name"))
if err != nil {
return nil, err
}
@@ -65,11 +65,11 @@ func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) {
}
func (u *CommandService) SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) {
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID))
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
if err != nil {
return 0, nil, err
}
groups, _ := groupRepo.GetList(commonRepo.WithByType("command"))
groups, _ := groupRepo.GetList(commonRepo.WithByType("command"), commonRepo.WithOrderBy("name"))
var dtoCommands []dto.CommandInfo
for _, command := range commands {
var item dto.CommandInfo
+152 -97
View File
@@ -15,6 +15,9 @@ import (
"sync"
"syscall"
"time"
"unicode/utf8"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
@@ -26,6 +29,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/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/client"
@@ -84,15 +88,28 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
if err != nil {
return 0, nil, err
}
options := types.ContainerListOptions{All: true}
options := container.ListOptions{
All: true,
}
if len(req.Filters) != 0 {
options.Filters = filters.NewArgs()
options.Filters.Add("label", req.Filters)
}
list, err = client.ContainerList(context.Background(), options)
containers, err := client.ContainerList(context.Background(), options)
if err != nil {
return 0, nil, err
}
if req.ExcludeAppStore {
for _, item := range containers {
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
continue
}
list = append(list, item)
}
} else {
list = containers
}
if len(req.Name) != 0 {
length, count := len(list), 0
for count < length {
@@ -162,7 +179,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
}
ports := loadContainerPort(item.Ports)
backDatas[i] = dto.ContainerInfo{
info := dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
@@ -174,6 +191,16 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
install, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(info.Name))
if install.ID > 0 {
info.AppInstallName = install.Name
info.AppName = install.App.Name
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(install.ID))
for _, website := range websites {
info.Websites = append(info.Websites, website.PrimaryDomain)
}
}
backDatas[i] = info
if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 {
networks := make([]string, 0, len(item.NetworkSettings.Networks))
for key := range item.NetworkSettings.Networks {
@@ -192,7 +219,7 @@ func (u *ContainerService) List() ([]string, error) {
if err != nil {
return nil, err
}
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
return nil, err
}
@@ -213,7 +240,7 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
if err != nil {
return nil, err
}
list, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
return nil, err
}
@@ -313,7 +340,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
data := dto.ResourceLimit{
CPU: cpuCounts,
Memory: int(memoryInfo.Total),
Memory: memoryInfo.Total,
}
return &data, nil
}
@@ -352,14 +379,14 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
return err
}
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
@@ -386,6 +413,18 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
break
}
}
networkSettings := oldContainer.NetworkSettings
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
data.Ipv4 = ipv4Address
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
data.Ipv6 = ipv6Address
} else {
data.Ipv4 = bridgeNetworkSettings.IPAddress
}
data.Cmd = oldContainer.Config.Cmd
data.OpenStdin = oldContainer.Config.OpenStdin
data.Tty = oldContainer.Config.Tty
@@ -411,14 +450,14 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.Privileged = oldContainer.HostConfig.Privileged
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name)
if oldContainer.HostConfig.NanoCPUs != 0 {
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
}
if oldContainer.HostConfig.Memory != 0 {
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
}
data.Volumes = loadVolumeBinds(oldContainer.HostConfig.Binds)
data.Volumes = loadVolumeBinds(oldContainer.Mounts)
return &data, nil
}
@@ -447,7 +486,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
}
}
if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil {
if err := client.ContainerRemove(ctx, req.ContainerID, container.RemoveOptions{Force: true}); err != nil {
return err
}
@@ -458,13 +497,13 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("update container failed, err: %v", err)
}
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
return fmt.Errorf("update successful but start failed, err: %v", err)
}
@@ -499,18 +538,18 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
break
}
}
if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil {
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil {
return err
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("upgrade container failed, err: %v", err)
}
global.LOG.Infof("upgrade container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
}
@@ -542,7 +581,7 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
global.LOG.Infof("start container %s operation %s", item, req.Operation)
switch req.Operation {
case constant.ContainerOpStart:
err = client.ContainerStart(ctx, item, types.ContainerStartOptions{})
err = client.ContainerStart(ctx, item, container.StartOptions{})
case constant.ContainerOpStop:
err = client.ContainerStop(ctx, item, container.StopOptions{})
case constant.ContainerOpRestart:
@@ -554,7 +593,7 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
case constant.ContainerOpUnpause:
err = client.ContainerUnpause(ctx, item)
case constant.ContainerOpRemove:
err = client.ContainerRemove(ctx, item, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
err = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true})
}
}
return err
@@ -588,7 +627,7 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
_ = os.Remove(file)
}
if err := client.ContainerStart(ctx, containerItem.ID, types.ContainerStartOptions{}); err != nil {
if err := client.ContainerStart(ctx, containerItem.ID, container.StartOptions{}); err != nil {
return err
}
return nil
@@ -599,70 +638,83 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
if cmd.CheckIllegal(container, since, tail) {
return buserr.New(constant.ErrCmdIllegal)
}
command := fmt.Sprintf("docker logs %s", container)
commandName := "docker"
commandArg := []string{"logs", container}
if containerType == "compose" {
command = fmt.Sprintf("docker-compose -f %s logs", container)
commandName = "docker-compose"
commandArg = []string{"-f", container, "logs"}
}
if tail != "0" {
command += " --tail " + tail
commandArg = append(commandArg, "--tail")
commandArg = append(commandArg, tail)
}
if since != "all" {
command += " --since " + since
commandArg = append(commandArg, "--since")
commandArg = append(commandArg, since)
}
if follow {
command += " -f"
commandArg = append(commandArg, "-f")
}
command += " 2>&1"
cmd := exec.Command("bash", "-c", command)
if !follow {
cmd := exec.Command(commandName, commandArg...)
cmd.Stderr = cmd.Stdout
stdout, _ := cmd.CombinedOutput()
if !utf8.Valid(stdout) {
return errors.New("invalid utf8")
}
if err := wsConn.WriteMessage(websocket.TextMessage, stdout); err != nil {
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
}
return nil
}
cmd := exec.Command(commandName, commandArg...)
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}
defer func() {
_ = cmd.Process.Signal(syscall.SIGTERM)
_ = cmd.Wait()
}()
var exitCh chan struct{}
if follow {
go func() {
_, wsData, _ := wsConn.ReadMessage()
if string(wsData) == "close conn" {
exitCh <- struct{}{}
}
}()
return err
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
return err
}
exitCh := make(chan struct{})
go func() {
_, wsData, _ := wsConn.ReadMessage()
if string(wsData) == "close conn" {
_ = cmd.Process.Signal(syscall.SIGTERM)
exitCh <- struct{}{}
}
}()
buffer := make([]byte, 1024)
for {
select {
case <-exitCh:
return nil
default:
n, err := stdout.Read(buffer)
if err != nil {
if err == io.EOF {
return err
go func() {
buffer := make([]byte, 1024)
for {
select {
case <-exitCh:
return
default:
n, err := stdout.Read(buffer)
if err != nil {
if err == io.EOF {
return
}
global.LOG.Errorf("read bytes from log failed, err: %v", err)
continue
}
if !utf8.Valid(buffer[:n]) {
continue
}
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
return
}
global.LOG.Errorf("read bytes from log failed, err: %v", err)
continue
}
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
return err
}
}
}
}()
_ = cmd.Wait()
return nil
}
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
@@ -693,7 +745,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
data.Cache = float64(cache) / 1024 / 1024
}
data.Memory = data.Memory - data.Cache
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
data.ShotTime = stats.Read
return &data, nil
@@ -706,7 +757,7 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
if err != nil {
return ""
}
options := types.ContainerListOptions{All: true}
options := container.ListOptions{All: true}
options.Filters = filters.NewArgs()
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name))
containers, err := cli.ContainerList(context.Background(), options)
@@ -761,7 +812,7 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
memUsage := float64(memStats.Usage)
memLimit := float64(memStats.Limit)
if memUsage > 0.0 && memLimit > 0.0 {
memPercent = (memUsage / memLimit) * 100.0
@@ -865,6 +916,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
data.MemoryCache = stats.MemoryStats.Stats["cache"]
data.MemoryUsage = stats.MemoryStats.Usage
data.MemoryLimit = stats.MemoryStats.Limit
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
return data
}
@@ -946,8 +998,21 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
case "host", "none", "bridge":
hostConf.NetworkMode = container.NetworkMode(req.Network)
}
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
if req.Ipv4 != "" || req.Ipv6 != "" {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{
req.Network: {
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: req.Ipv4,
IPv6Address: req.Ipv6,
},
}}
} else {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
}
} else {
if req.Ipv4 != "" || req.Ipv6 != "" {
return nil, nil, nil, fmt.Errorf("Please set up the network")
}
networkConf = network.NetworkingConfig{}
}
@@ -955,7 +1020,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
hostConf.AutoRemove = req.AutoRemove
hostConf.CPUShares = req.CPUShares
hostConf.PublishAllPorts = req.PublishAllPorts
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
@@ -964,10 +1029,19 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
hostConf.MemorySwap = 0
hostConf.PortBindings = portMap
hostConf.Binds = []string{}
hostConf.Mounts = []mount.Mount{}
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
config.Volumes[volume.ContainerDir] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
if volume.Type == "volume" {
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{
Type: mount.Type(volume.Type),
Source: volume.SourceDir,
Target: volume.ContainerDir,
})
config.Volumes[volume.ContainerDir] = struct{}{}
} else {
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
}
}
return &config, &hostConf, &networkConf, nil
}
@@ -988,46 +1062,27 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
global.LOG.Errorf("recreate after container update failed, err: %v", err)
return
}
if err := client.ContainerStart(ctx, oldContainer.ID, types.ContainerStartOptions{}); err != nil {
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")
}
func loadVolumeBinds(binds []string) []dto.VolumeHelper {
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
var datas []dto.VolumeHelper
for _, bind := range binds {
parts := strings.Split(bind, ":")
var volumeItem dto.VolumeHelper
if len(parts) > 3 {
continue
volumeItem.Type = string(bind.Type)
if bind.Type == "volume" {
volumeItem.SourceDir = bind.Name
} else {
volumeItem.SourceDir = bind.Source
}
volumeItem.SourceDir = parts[0]
if len(parts) == 1 {
volumeItem.ContainerDir = parts[0]
volumeItem.ContainerDir = bind.Destination
volumeItem.Mode = "ro"
if bind.RW {
volumeItem.Mode = "rw"
}
if len(parts) == 2 {
switch parts[1] {
case "r", "ro":
volumeItem.ContainerDir = parts[0]
volumeItem.Mode = "ro"
case "rw":
volumeItem.ContainerDir = parts[0]
volumeItem.Mode = "rw"
default:
volumeItem.ContainerDir = parts[1]
volumeItem.Mode = "rw"
}
}
if len(parts) == 3 {
volumeItem.ContainerDir = parts[1]
if parts[2] == "r" {
volumeItem.Mode = "ro"
} else {
volumeItem.Mode = parts[2]
}
}
datas = append(datas, volumeItem)
}
return datas
+2 -2
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"errors"
"fmt"
"github.com/docker/docker/api/types/container"
"io"
"os"
"os/exec"
@@ -20,7 +21,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"golang.org/x/net/context"
)
@@ -40,7 +40,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
return 0, nil, err
}
options := types.ContainerListOptions{All: true}
options := container.ListOptions{All: true}
options.Filters = filters.NewArgs()
options.Filters.Add("label", composeProjectLabel)
+75 -86
View File
@@ -5,11 +5,12 @@ import (
"fmt"
"os"
"path"
"strconv"
"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/jinzhu/copier"
@@ -28,10 +29,10 @@ type ICronjobService interface {
UpdateStatus(id uint, status string) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob) (int, error)
StartJob(cronjob *model.Cronjob) (string, error)
CleanRecord(req dto.CronjobClean) error
LoadRecordLog(req dto.OperateByID) (string, error)
LoadRecordLog(req dto.OperateByID) string
}
func NewICronjobService() ICronjobService {
@@ -46,14 +47,6 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
if err := copier.Copy(&item, &cronjob); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if hasBackup(item.Type) {
backup, _ := backupRepo.Get(commonRepo.WithByID(uint(item.TargetDirID)))
if len(backup.Type) != 0 {
item.TargetDir = backup.Type
}
} else {
item.TargetDir = "-"
}
record, _ := cronjobRepo.RecordFirst(cronjob.ID)
if record.ID != 0 {
item.LastRecordTime = record.StartTime.Format("2006-01-02 15:04:05")
@@ -83,19 +76,19 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
if err != nil {
return "", err
return ""
}
if _, err := os.Stat(record.Records); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
return ""
}
content, err := os.ReadFile(record.Records)
if err != nil {
return "", err
return ""
}
return string(content), nil
return string(content)
}
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
@@ -103,24 +96,23 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if err != nil {
return err
}
if req.CleanData && hasBackup(cronjob.Type) {
cronjob.RetainCopies = 0
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return err
}
if backup.Type != "LOCAL" {
localDir, err := loadLocalDir()
if req.CleanData {
if hasBackup(cronjob.Type) {
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
cronjob.RetainCopies = 0
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
} else {
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
u.removeExpiredLog(cronjob)
}
}
if req.IsDelete {
records, _ := backupRepo.ListRecord(backupRepo.WithByCronID(cronjob.ID))
for _, records := range records {
records.CronjobID = 0
_ = backupRepo.UpdateRecord(&records)
}
}
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
@@ -184,29 +176,40 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
cronjob.Status = constant.StatusEnable
cronjob.Spec = loadSpec(cronjob)
global.LOG.Infof("create cronjob %s successful, spec: %s", cronjob.Name, cronjob.Spec)
entryID, err := u.StartJob(&cronjob)
spec := cronjob.Spec
entryIDs, err := u.StartJob(&cronjob)
if err != nil {
return err
}
cronjob.EntryID = uint64(entryID)
cronjob.Spec = spec
cronjob.EntryIDs = entryIDs
if err := cronjobRepo.Create(&cronjob); err != nil {
return err
}
return nil
}
func (u *CronjobService) StartJob(cronjob *model.Cronjob) (int, error) {
if cronjob.EntryID != 0 {
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
func (u *CronjobService) StartJob(cronjob *model.Cronjob) (string, error) {
if len(cronjob.EntryIDs) != 0 {
ids := strings.Split(cronjob.EntryIDs, ",")
for _, id := range ids {
idItem, _ := strconv.Atoi(id)
global.Cron.Remove(cron.EntryID(idItem))
}
}
entryID, err := u.AddCronJob(cronjob)
if err != nil {
return 0, err
specs := strings.Split(cronjob.Spec, ",")
var ids []string
for _, spec := range specs {
cronjob.Spec = spec
entryID, err := u.AddCronJob(cronjob)
if err != nil {
return "", err
}
ids = append(ids, fmt.Sprintf("%v", entryID))
}
return entryID, nil
return strings.Join(ids, ","), nil
}
func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
@@ -215,9 +218,13 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
if cronjob.ID == 0 {
return errors.New("find cronjob in db failed")
}
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData}); err != nil {
ids := strings.Split(cronjob.EntryIDs, ",")
for _, id := range ids {
idItem, _ := strconv.Atoi(id)
global.Cron.Remove(cron.EntryID(idItem))
}
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, IsDelete: true}); err != nil {
return err
}
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
@@ -238,37 +245,38 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
return constant.ErrRecordNotFound
}
upMap := make(map[string]interface{})
cronjob.EntryID = cronModel.EntryID
cronjob.EntryIDs = cronModel.EntryIDs
cronjob.Type = cronModel.Type
cronjob.Spec = loadSpec(cronjob)
spec := cronjob.Spec
if cronModel.Status == constant.StatusEnable {
newEntryID, err := u.StartJob(&cronjob)
newEntryIDs, err := u.StartJob(&cronjob)
if err != nil {
return err
}
upMap["entry_id"] = newEntryID
upMap["entry_ids"] = newEntryIDs
} else {
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
ids := strings.Split(cronjob.EntryIDs, ",")
for _, id := range ids {
idItem, _ := strconv.Atoi(id)
global.Cron.Remove(cron.EntryID(idItem))
}
}
upMap["name"] = req.Name
upMap["spec"] = cronjob.Spec
upMap["spec"] = spec
upMap["script"] = req.Script
upMap["command"] = req.Command
upMap["container_name"] = req.ContainerName
upMap["spec_type"] = req.SpecType
upMap["week"] = req.Week
upMap["day"] = req.Day
upMap["hour"] = req.Hour
upMap["minute"] = req.Minute
upMap["second"] = req.Second
upMap["app_id"] = req.AppID
upMap["website"] = req.Website
upMap["exclusion_rules"] = req.ExclusionRules
upMap["db_type"] = req.DBType
upMap["db_name"] = req.DBName
upMap["url"] = req.URL
upMap["source_dir"] = req.SourceDir
upMap["keep_local"] = req.KeepLocal
upMap["target_dir_id"] = req.TargetDirID
upMap["backup_accounts"] = req.BackupAccounts
upMap["default_download"] = req.DefaultDownload
upMap["retain_copies"] = req.RetainCopies
return cronjobRepo.Update(id, upMap)
}
@@ -279,19 +287,23 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
return errors.WithMessage(constant.ErrRecordNotFound, "record not found")
}
var (
entryID int
err error
entryIDs string
err error
)
if status == constant.StatusEnable {
entryID, err = u.StartJob(&cronjob)
entryIDs, err = u.StartJob(&cronjob)
if err != nil {
return err
}
} else {
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
ids := strings.Split(cronjob.EntryIDs, ",")
for _, id := range ids {
idItem, _ := strconv.Atoi(id)
global.Cron.Remove(cron.EntryID(idItem))
}
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
}
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_id": entryID})
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_ids": entryIDs})
}
func (u *CronjobService) AddCronJob(cronjob *model.Cronjob) (int, error) {
@@ -327,26 +339,3 @@ func mkdirAndWriteFile(cronjob *model.Cronjob, startTime time.Time, msg []byte)
write.Flush()
return path, nil
}
func loadSpec(cronjob model.Cronjob) string {
switch cronjob.SpecType {
case "perMonth":
return fmt.Sprintf("%v %v %v * *", cronjob.Minute, cronjob.Hour, cronjob.Day)
case "perWeek":
return fmt.Sprintf("%v %v * * %v", cronjob.Minute, cronjob.Hour, cronjob.Week)
case "perNDay":
return fmt.Sprintf("%v %v */%v * *", cronjob.Minute, cronjob.Hour, cronjob.Day)
case "perDay":
return fmt.Sprintf("%v %v * * *", cronjob.Minute, cronjob.Hour)
case "perNHour":
return fmt.Sprintf("%v */%v * * *", cronjob.Minute, cronjob.Hour)
case "perHour":
return fmt.Sprintf("%v * * * *", cronjob.Minute)
case "perNMinute":
return fmt.Sprintf("@every %vm", cronjob.Minute)
case "perNSecond":
return fmt.Sprintf("@every %vs", cronjob.Second)
default:
return ""
}
}
+386
View File
@@ -0,0 +1,386 @@
package service
import (
"fmt"
"os"
"path"
"strconv"
"strings"
"time"
"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/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
)
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error {
var apps []model.AppInstall
if cronjob.AppID == "all" {
apps, _ = appInstallRepo.ListBy()
} else {
itemID, _ := (strconv.Atoi(cronjob.AppID))
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
if err != nil {
return err
}
apps = append(apps, app)
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
for _, app := range apps {
var record model.BackupRecord
record.From = "cronjob"
record.Type = "app"
record.CronjobID = cronjob.ID
record.Name = app.App.Key
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); err != nil {
return err
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
}
return nil
}
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time) error {
webs := loadWebsForJob(cronjob)
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
for _, web := range webs {
var record model.BackupRecord
record.From = "cronjob"
record.Type = "website"
record.CronjobID = cronjob.ID
record.Name = web.PrimaryDomain
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); err != nil {
return err
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
}
return nil
}
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time) error {
dbs := loadDbsForJob(cronjob)
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
for _, dbInfo := range dbs {
var record model.BackupRecord
record.From = "cronjob"
record.Type = dbInfo.DBType
record.CronjobID = cronjob.ID
record.Name = dbInfo.Database
record.DetailName = dbInfo.Name
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))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if err := handleMysqlBackup(dbInfo.Database, dbInfo.DBType, dbInfo.Name, backupDir, record.FileName); err != nil {
return err
}
} else {
if err := handlePostgresqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
return err
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
}
return nil
}
func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.Time) error {
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405")+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 {
return err
}
var record model.BackupRecord
record.From = "cronjob"
record.Type = "directory"
record.CronjobID = cronjob.ID
record.Name = cronjob.Name
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, fileName))
if err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
record.FileName = fileName
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
return nil
}
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time) error {
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
nameItem := startTime.Format("20060102150405") + 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 {
return err
}
var record model.BackupRecord
record.From = "cronjob"
record.Type = "log"
record.CronjobID = cronjob.ID
record.Name = cronjob.Name
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(path.Dir(backupDir), fileName))
if err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
record.FileName = fileName
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
return nil
}
func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, startTime time.Time, logPath string) error {
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
}
var record model.BackupRecord
record.From = "cronjob"
record.Type = "directory"
record.CronjobID = cronjob.ID
record.Name = cronjob.Name
record.Source, record.BackupType = loadRecordPath(cronjob, accountMap)
record.FileDir = "system_snapshot"
req := dto.SnapshotCreate{
From: record.BackupType,
DefaultDownload: cronjob.DefaultDownload,
}
name, err := NewISnapshotService().HandleSnapshot(true, logPath, req, startTime.Format("20060102150405")+common.RandStrAndNum(5))
if err != nil {
return err
}
record.FileName = name + ".tar.gz"
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
return nil
}
type databaseHelper struct {
DBType string
Database string
Name string
}
func loadDbsForJob(cronjob model.Cronjob) []databaseHelper {
var dbs []databaseHelper
if cronjob.DBName == "all" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
mysqlItems, _ := mysqlRepo.List()
for _, mysql := range mysqlItems {
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: mysql.MysqlName,
Name: mysql.Name,
})
}
} else {
pgItems, _ := postgresqlRepo.List()
for _, pg := range pgItems {
dbs = append(dbs, databaseHelper{
DBType: cronjob.DBType,
Database: pg.PostgresqlName,
Name: pg.Name,
})
}
}
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,
})
}
return dbs
}
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
var weblist []model.Website
if 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)
}
return weblist
}
func loadRecordPath(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper) (string, string) {
source := accountMap[fmt.Sprintf("%v", cronjob.DefaultDownload)].backType
targets := strings.Split(cronjob.BackupAccounts, ",")
var itemAccounts []string
for _, target := range targets {
if len(target) == 0 {
continue
}
if len(accountMap[target].backType) != 0 {
itemAccounts = append(itemAccounts, accountMap[target].backType)
}
}
backupType := strings.Join(itemAccounts, ",")
return source, backupType
}
func handleBackupLogs(targetDir, fileName string) error {
websites, err := websiteRepo.List()
if err != nil {
return err
}
if len(websites) != 0 {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
webItem := path.Join(nginxInstall.GetPath(), "www/sites")
for _, website := range websites {
dirItem := path.Join(targetDir, "website", website.Alias)
if _, err := os.Stat(dirItem); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dirItem, os.ModePerm); err != nil {
return err
}
}
itemDir := path.Join(webItem, website.Alias, "log")
logFiles, _ := os.ReadDir(itemDir)
if len(logFiles) != 0 {
for i := 0; i < len(logFiles); i++ {
if !logFiles[i].IsDir() {
_ = common.CopyFile(path.Join(itemDir, logFiles[i].Name()), dirItem)
}
}
}
itemDir2 := path.Join(global.CONF.System.Backup, "log/website", website.Alias)
logFiles2, _ := os.ReadDir(itemDir2)
if len(logFiles2) != 0 {
for i := 0; i < len(logFiles2); i++ {
if !logFiles2[i].IsDir() {
_ = common.CopyFile(path.Join(itemDir2, logFiles2[i].Name()), dirItem)
}
}
}
}
global.LOG.Debug("backup website log successful!")
}
systemLogDir := path.Join(global.CONF.System.BaseDir, "1panel/log")
systemDir := path.Join(targetDir, "system")
if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(systemDir, os.ModePerm); err != nil {
return err
}
}
systemLogFiles, _ := os.ReadDir(systemLogDir)
if len(systemLogFiles) != 0 {
for i := 0; i < len(systemLogFiles); i++ {
if !systemLogFiles[i].IsDir() {
_ = common.CopyFile(path.Join(systemLogDir, systemLogFiles[i].Name()), systemDir)
}
}
}
global.LOG.Debug("backup system log successful!")
loginLogFiles, _ := os.ReadDir("/var/log")
loginDir := path.Join(targetDir, "login")
if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(loginDir, os.ModePerm); err != nil {
return err
}
}
if len(loginLogFiles) != 0 {
for i := 0; i < len(loginLogFiles); i++ {
if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) {
_ = common.CopyFile(path.Join("/var/log", loginLogFiles[i].Name()), loginDir)
}
}
}
global.LOG.Debug("backup ssh log successful!")
if err := handleTar(targetDir, path.Dir(targetDir), fileName, ""); err != nil {
return err
}
defer func() {
_ = os.RemoveAll(targetDir)
}()
return nil
}
+205 -510
View File
@@ -2,16 +2,18 @@ package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
@@ -33,48 +35,62 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
if len(cronjob.Script) == 0 {
return
}
record.Records = u.generateLogsPath(*cronjob, record.StartTime)
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
script := cronjob.Script
if len(cronjob.ContainerName) != 0 {
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("docker exec %s %s", cronjob.ContainerName, cronjob.Script))
} else {
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
command := "sh"
if len(cronjob.Command) != 0 {
command = cronjob.Command
}
script = fmt.Sprintf("docker exec %s %s -c \"%s\"", cronjob.ContainerName, command, strings.ReplaceAll(cronjob.Script, "\"", "\\\""))
}
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "snapshot":
messageItem := ""
messageItem, record.File, err = u.handleSnapshot(cronjob, record.StartTime)
message = []byte(messageItem)
err = u.handleShell(cronjob.Type, cronjob.Name, script, record.Records)
u.removeExpiredLog(*cronjob)
case "curl":
if len(cronjob.URL) == 0 {
return
}
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL))
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
record.Records = u.generateLogsPath(*cronjob, record.StartTime)
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL), record.Records)
u.removeExpiredLog(*cronjob)
case "ntp":
err = u.handleNtpSync()
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "website", "database", "app":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "directory":
if len(cronjob.SourceDir) == 0 {
return
}
record.File, err = u.handleBackup(cronjob, record.StartTime)
u.removeExpiredLog(*cronjob)
case "cutWebsiteLog":
record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime)
if err != nil {
global.LOG.Errorf("cut website log file failed, err: %v", err)
}
var messageItem []string
messageItem, record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime)
message = []byte(strings.Join(messageItem, "\n"))
case "clean":
messageItem := ""
messageItem, err = u.handleSystemClean()
message = []byte(messageItem)
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
u.removeExpiredLog(*cronjob)
case "website":
err = u.handleWebsite(*cronjob, record.StartTime)
case "app":
err = u.handleApp(*cronjob, record.StartTime)
case "database":
err = u.handleDatabase(*cronjob, record.StartTime)
case "directory":
if len(cronjob.SourceDir) == 0 {
return
}
err = u.handleDirectory(*cronjob, record.StartTime)
case "log":
record.File, err = u.handleSystemLog(*cronjob, record.StartTime)
err = u.handleSystemLog(*cronjob, record.StartTime)
case "snapshot":
record.Records = u.generateLogsPath(*cronjob, record.StartTime)
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleSnapshot(*cronjob, record.StartTime, record.Records)
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
if len(message) != 0 {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
return
}
if len(message) != 0 {
@@ -87,18 +103,17 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
}()
}
func (u *CronjobService) handleShell(cronType, cornName, script string) ([]byte, error) {
func (u *CronjobService) handleShell(cronType, cornName, script, logPath string) error {
handleDir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronType, cornName)
if _, err := os.Stat(handleDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(handleDir, os.ModePerm); err != nil {
return nil, err
return err
}
}
stdout, err := cmd.ExecCronjobWithTimeOut(script, handleDir, 24*time.Hour)
if err != nil {
return []byte(stdout), err
if err := cmd.ExecCronjobWithTimeOut(script, handleDir, logPath, 24*time.Hour); err != nil {
return err
}
return []byte(stdout), nil
return nil
}
func (u *CronjobService) handleNtpSync() error {
@@ -116,98 +131,6 @@ func (u *CronjobService) handleNtpSync() error {
return nil
}
func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return "", err
}
localDir, err := loadLocalDir()
if err != nil {
return "", err
}
global.LOG.Infof("start to backup %s %s to %s", cronjob.Type, cronjob.Name, backup.Type)
switch cronjob.Type {
case "database":
paths, err := u.handleDatabase(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
case "app":
paths, err := u.handleApp(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
case "website":
paths, err := u.handleWebsite(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
default:
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
backupDir := path.Join(localDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
itemFileDir := fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
return "", err
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
}()
}
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
if len(backup.BackupPath) != 0 {
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
}
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
return "", err
}
}
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
if backup.Type == "LOCAL" || cronjob.KeepLocal {
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
} else {
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
}
}
}
func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) <= int(cronjob.RetainCopies) {
return
}
for i := int(cronjob.RetainCopies); i < len(records); i++ {
if len(records[i].File) != 0 {
files := strings.Split(records[i].File, ",")
for _, file := range files {
_ = os.Remove(file)
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
if backType == "LOCAL" {
continue
}
fileItem := file
if cronjob.KeepLocal {
if len(backupPath) != 0 {
fileItem = path.Join(strings.TrimPrefix(backupPath, "/") + strings.TrimPrefix(file, localDir+"/"))
} else {
fileItem = strings.TrimPrefix(file, localDir+"/")
}
}
if cronjob.Type == "snapshot" {
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(path.Base(fileItem), ".tar.gz")))
}
_, _ = backClient.Delete(fileItem)
}
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
}
}
func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
@@ -265,433 +188,205 @@ func handleUnTar(sourceFile, targetDir string) error {
return nil
}
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var dbs []model.DatabaseMysql
if cronjob.DBName == "all" {
dbs, err = mysqlRepo.List()
if err != nil {
return paths, err
}
} else {
itemID, _ := (strconv.Atoi(cronjob.DBName))
dbs, err = mysqlRepo.List(commonRepo.WithByID(uint(itemID)))
if err != nil {
return paths, err
}
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return paths, err
}
}
for _, dbInfo := range dbs {
var record model.BackupRecord
database, _ := databaseRepo.Get(commonRepo.WithByName(dbInfo.MysqlName))
record.Type = database.Type
record.Source = "LOCAL"
record.BackupType = backup.Type
record.Name = dbInfo.MysqlName
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", database.Type, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405"))
if err = handleMysqlBackup(dbInfo.MysqlName, dbInfo.Name, backupDir, record.FileName); err != nil {
return paths, err
}
record.DetailName = dbInfo.Name
record.FileDir = backupDir
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = itemFileDir
}
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return paths, err
}
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
}
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) (string, error) {
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) ([]string, string, error) {
var (
websites []string
err error
filePaths []string
msgs []string
)
if cronjob.Website == "all" {
websites, _ = NewIWebsiteService().GetWebsiteOptions()
if len(websites) == 0 {
return "", nil
}
} else {
websites = append(websites, cronjob.Website)
}
websites := loadWebsForJob(*cronjob)
nginx, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return "", nil
return msgs, "", nil
}
baseDir := path.Join(nginx.GetPath(), "www", "sites")
fileOp := files.NewFileOp()
var wg sync.WaitGroup
wg.Add(len(websites))
for _, websiteName := range websites {
name := websiteName
go func() {
website, _ := websiteRepo.GetFirst(websiteRepo.WithDomain(name))
if website.ID == 0 {
wg.Done()
return
}
websiteLogDir := path.Join(baseDir, website.Alias, "log")
srcAccessLogPath := path.Join(websiteLogDir, "access.log")
srcErrorLogPath := path.Join(websiteLogDir, "error.log")
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.Alias)
if !fileOp.Stat(dstLogDir) {
_ = os.MkdirAll(dstLogDir, 0755)
}
for _, website := range websites {
websiteLogDir := path.Join(baseDir, website.Alias, "log")
srcAccessLogPath := path.Join(websiteLogDir, "access.log")
srcErrorLogPath := path.Join(websiteLogDir, "error.log")
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.Alias)
if !fileOp.Stat(dstLogDir) {
_ = os.MkdirAll(dstLogDir, 0755)
}
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
filePaths = append(filePaths, path.Join(dstLogDir, dstName))
if err = fileOp.Compress([]string{srcAccessLogPath, srcErrorLogPath}, dstLogDir, dstName, files.Gz); err != nil {
global.LOG.Errorf("There was an error in compressing the website[%s] access.log, err: %v", website.PrimaryDomain, err)
} else {
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755)
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
}
global.LOG.Infof("The website[%s] log file was successfully rotated in the directory [%s]", website.PrimaryDomain, dstLogDir)
var record model.BackupRecord
record.Type = "cutWebsiteLog"
record.Name = cronjob.Website
record.Source = "LOCAL"
record.BackupType = "LOCAL"
record.FileDir = dstLogDir
record.FileName = dstName
if err = backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
}
wg.Done()
}()
}
wg.Wait()
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
return strings.Join(filePaths, ","), nil
}
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
dstFilePath := path.Join(dstLogDir, dstName)
filePaths = append(filePaths, dstFilePath)
func (u *CronjobService) handleApp(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var applist []model.AppInstall
if cronjob.AppID == "all" {
applist, err = appInstallRepo.ListBy()
if err != nil {
return paths, err
}
} else {
itemID, _ := (strconv.Atoi(cronjob.AppID))
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
if err != nil {
return paths, err
}
applist = append(applist, app)
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return paths, err
}
}
for _, app := range applist {
var record model.BackupRecord
record.Type = "app"
record.Name = app.App.Key
record.DetailName = app.Name
record.Source = "LOCAL"
record.BackupType = backup.Type
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
record.FileDir = backupDir
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
}
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405"))
if err := handleAppBackup(&app, backupDir, record.FileName); err != nil {
return paths, err
}
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return paths, err
}
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err = backupLogFile(dstFilePath, websiteLogDir, fileOp); err != nil {
websiteErr := buserr.WithNameAndErr("ErrCutWebsiteLog", website.PrimaryDomain, err)
err = websiteErr
msgs = append(msgs, websiteErr.Error())
global.LOG.Error(websiteErr.Error())
continue
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755)
_ = 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.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
u.removeExpiredLog(*cronjob)
return msgs, strings.Join(filePaths, ","), err
}
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var weblist []string
if cronjob.Website == "all" {
weblist, err = NewIWebsiteService().GetWebsiteOptions()
if err != nil {
return paths, err
func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error {
if err := cmd.ExecCmd(fmt.Sprintf("tar -czf %s -C %s %s", dstFilePath, websiteLogDir, strings.Join([]string{"access.log", "error.log"}, " "))); err != nil {
dstDir := path.Dir(dstFilePath)
if err = fileOp.Copy(path.Join(websiteLogDir, "access.log"), dstDir); err != nil {
return err
}
} else {
weblist = append(weblist, cronjob.Website)
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return paths, err
if err = fileOp.Copy(path.Join(websiteLogDir, "error.log"), dstDir); err != nil {
return err
}
}
for _, websiteItem := range weblist {
var record model.BackupRecord
record.Type = "website"
record.Name = cronjob.Website
record.Source = "LOCAL"
record.BackupType = backup.Type
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(websiteItem))
if err != nil {
return paths, err
}
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.PrimaryDomain))
record.FileDir = backupDir
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
}
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil {
return paths, err
}
record.Name = website.PrimaryDomain
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return paths, err
}
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemFileDir = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), itemFileDir)
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
if err = cmd.ExecCmd(fmt.Sprintf("tar -czf %s -C %s %s", dstFilePath, dstDir, strings.Join([]string{"access.log", "error.log"}, " "))); err != nil {
return err
}
_ = fileOp.DeleteFile(path.Join(dstDir, "access.log"))
_ = fileOp.DeleteFile(path.Join(dstDir, "error.log"))
return nil
}
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}
func (u *CronjobService) handleSnapshot(cronjob *model.Cronjob, startTime time.Time) (string, string, error) {
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return "", "", err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", "", err
}
req := dto.SnapshotCreate{
From: backup.Type,
}
message, name, err := NewISnapshotService().HandleSnapshot(true, req, startTime.Format("20060102150405"))
if err != nil {
return message, "", err
}
path := path.Join(strings.TrimPrefix(backup.BackupPath, "/"), "system_snapshot", name+".tar.gz")
u.HandleRmExpired(backup.Type, backup.BackupPath, "", cronjob, client)
return message, path, nil
return nil
}
func (u *CronjobService) handleSystemClean() (string, error) {
return NewIDeviceService().CleanForCronjob()
}
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time) (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
func loadClientMap(backupAccounts string) (map[string]cronjobUploadHelper, error) {
clients := make(map[string]cronjobUploadHelper)
accounts, err := backupRepo.List()
if err != nil {
return "", err
return nil, err
}
pathItem := path.Join(global.CONF.System.BaseDir, "1panel/tmp/log", startTime.Format("20060102150405"))
websites, err := websiteRepo.List()
if err != nil {
return "", err
}
if len(websites) != 0 {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return "", err
targets := strings.Split(backupAccounts, ",")
for _, target := range targets {
if len(target) == 0 {
continue
}
webItem := path.Join(nginxInstall.GetPath(), "www/sites")
for _, website := range websites {
dirItem := path.Join(pathItem, "website", website.Alias)
if _, err := os.Stat(dirItem); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dirItem, os.ModePerm); err != nil {
return "", err
for _, account := range accounts {
if target == account.Type {
client, err := NewIBackupService().NewClient(&account)
if err != nil {
return nil, err
}
}
itemDir := path.Join(webItem, website.Alias, "log")
logFiles, _ := os.ReadDir(itemDir)
if len(logFiles) != 0 {
for i := 0; i < len(logFiles); i++ {
if !logFiles[i].IsDir() {
_ = cpBinary([]string{path.Join(itemDir, logFiles[i].Name())}, dirItem)
pathItem := account.BackupPath
if account.BackupPath != "/" {
pathItem = strings.TrimPrefix(account.BackupPath, "/")
}
if target == constant.Local {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
return nil, err
}
if _, ok := varMap["dir"]; !ok {
return nil, errors.New("load local backup dir failed")
}
baseDir, ok := varMap["dir"].(string)
if ok {
pathItem = baseDir
} else {
return nil, fmt.Errorf("error type dir: %T", varMap["dir"])
}
}
}
itemDir2 := path.Join(global.CONF.System.Backup, "log/website", website.Alias)
logFiles2, _ := os.ReadDir(itemDir2)
if len(logFiles2) != 0 {
for i := 0; i < len(logFiles2); i++ {
if !logFiles2[i].IsDir() {
_ = cpBinary([]string{path.Join(itemDir2, logFiles2[i].Name())}, dirItem)
}
clients[target] = cronjobUploadHelper{
client: client,
backupPath: pathItem,
backType: account.Type,
}
}
}
global.LOG.Debug("backup website log successful!")
}
return clients, nil
}
systemLogDir := path.Join(global.CONF.System.BaseDir, "1panel/log")
systemDir := path.Join(pathItem, "system")
if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(systemDir, os.ModePerm); err != nil {
return "", err
}
}
systemLogFiles, _ := os.ReadDir(systemLogDir)
if len(systemLogFiles) != 0 {
for i := 0; i < len(systemLogFiles); i++ {
if !systemLogFiles[i].IsDir() {
_ = cpBinary([]string{path.Join(systemLogDir, systemLogFiles[i].Name())}, systemDir)
}
}
}
global.LOG.Debug("backup system log successful!")
type cronjobUploadHelper struct {
backupPath string
backType string
client cloud_storage.CloudStorageClient
}
loginLogFiles, _ := os.ReadDir("/var/log")
loginDir := path.Join(pathItem, "login")
if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(loginDir, os.ModePerm); err != nil {
return "", err
}
}
if len(loginLogFiles) != 0 {
for i := 0; i < len(loginLogFiles); i++ {
if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) {
_ = cpBinary([]string{path.Join("/var/log", loginLogFiles[i].Name())}, loginDir)
}
}
}
global.LOG.Debug("backup ssh log successful!")
fileName := fmt.Sprintf("system_log_%s.tar.gz", startTime.Format("20060102150405"))
targetDir := path.Dir(pathItem)
if err := handleTar(pathItem, targetDir, fileName, ""); err != nil {
return "", err
}
func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap map[string]cronjobUploadHelper, file string) (string, error) {
defer func() {
os.RemoveAll(pathItem)
os.RemoveAll(path.Join(targetDir, fileName))
_ = os.Remove(file)
}()
accounts := strings.Split(cronjob.BackupAccounts, ",")
cloudSrc := strings.TrimPrefix(file, global.CONF.System.TmpDir+"/")
for _, account := range accounts {
if len(account) != 0 {
global.LOG.Debugf("start upload file to %s, dir: %s", account, path.Join(accountMap[account].backupPath, cloudSrc))
if _, err := accountMap[account].client.Upload(file, path.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
return "", err
}
global.LOG.Debugf("upload successful!")
}
}
return cloudSrc, nil
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
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))
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
if record.ID != 0 {
opts = append(opts, backupRepo.WithByType(record.Type))
opts = append(opts, commonRepo.WithByName(record.Name))
opts = append(opts, backupRepo.WithByDetailName(record.DetailName))
}
records, _ := backupRepo.ListRecord(opts...)
if len(records) <= int(cronjob.RetainCopies) {
return
}
for i := int(cronjob.RetainCopies); i < len(records); i++ {
accounts := strings.Split(cronjob.BackupAccounts, ",")
if cronjob.Type == "snapshot" {
for _, account := range accounts {
if len(account) != 0 {
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
}
}
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
} else {
for _, account := range accounts {
if len(account) != 0 {
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
}
}
}
_ = backupRepo.DeleteRecord(context.Background(), commonRepo.WithByID(records[i].ID))
}
}
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
}
for i := int(cronjob.RetainCopies); i < len(records); i++ {
if len(records[i].File) != 0 {
files := strings.Split(records[i].File, ",")
for _, file := range files {
_ = os.Remove(file)
}
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
}
}
func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.Time) string {
dir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronjob.Type, cronjob.Name)
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(dir, os.ModePerm)
}
targetPath := "log/" + fileName
if len(backup.BackupPath) != 0 {
targetPath = strings.TrimPrefix(backup.BackupPath, "/") + "/log/" + fileName
}
if _, err = client.Upload(path.Join(targetDir, fileName), targetPath); err != nil {
return "", err
}
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, client)
return targetPath, nil
path := fmt.Sprintf("%s/%s.log", dir, startTime.Format("20060102150405"))
return path
}
func hasBackup(cronjobType string) bool {
+47 -1
View File
@@ -11,6 +11,8 @@ import (
"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"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
@@ -22,6 +24,7 @@ import (
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
@@ -49,6 +52,27 @@ func (u *DashboardService) Restart(operation string) error {
return nil
}
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
var baseInfo dto.OsInfo
hostInfo, err := host.Info()
if err != nil {
return nil, err
}
baseInfo.OS = hostInfo.OS
baseInfo.Platform = hostInfo.Platform
baseInfo.PlatformFamily = hostInfo.PlatformFamily
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
if baseInfo.KernelArch == "armv7l" {
baseInfo.KernelArch = "armv7"
}
if baseInfo.KernelArch == "x86_64" {
baseInfo.KernelArch = "amd64"
}
return &baseInfo, nil
}
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase
hostInfo, err := host.Info()
@@ -132,6 +156,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
currentInfo.DiskData = loadDiskInfo()
currentInfo.GPUData = loadGPUInfo()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
@@ -183,7 +208,10 @@ func loadDiskInfo() []dto.DiskInfo {
var datas []dto.DiskInfo
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second)
if err != nil {
return datas
stdout, err = cmd.ExecWithTimeOut("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 1*time.Second)
if err != nil {
return datas
}
}
lines := strings.Split(stdout, "\n")
@@ -264,3 +292,21 @@ func loadDiskInfo() []dto.DiskInfo {
})
return datas
}
func loadGPUInfo() []dto.GPUInfo {
list := xpack.LoadGpuInfo()
if len(list) == 0 {
return nil
}
var data []dto.GPUInfo
for _, gpu := range list {
var dataItem dto.GPUInfo
if err := copier.Copy(&dataItem, &gpu); err != nil {
continue
}
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
data = append(data, dataItem)
}
return data
}
+133 -48
View File
@@ -6,6 +6,9 @@ import (
"os"
"path"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
client2 "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -28,6 +31,7 @@ type IDatabaseService interface {
DeleteCheck(id uint) ([]string, error)
Delete(req dto.DatabaseDelete) error
List(dbType string) ([]dto.DatabaseOption, error)
LoadItems(dbType string) ([]dto.DatabaseItem, error)
}
func NewIDatabaseService() IDatabaseService {
@@ -77,24 +81,66 @@ func (u *DatabaseService) List(dbType string) ([]dto.DatabaseOption, error) {
return datas, err
}
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
}); err != nil {
return false
func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType))
var datas []dto.DatabaseItem
for _, db := range dbs {
if dbType == "postgresql" {
items, _ := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(db.Name))
for _, item := range items {
var dItem dto.DatabaseItem
if err := copier.Copy(&dItem, &item); err != nil {
continue
}
dItem.Database = db.Name
datas = append(datas, dItem)
}
} else {
items, _ := mysqlRepo.List(mysqlRepo.WithByMysqlName(db.Name))
for _, item := range items {
var dItem dto.DatabaseItem
if err := copier.Copy(&dItem, &item); err != nil {
continue
}
dItem.Database = db.Name
datas = append(datas, dItem)
}
}
}
return true
return datas, err
}
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
switch req.Type {
case constant.AppPostgresql:
_, err := postgresql.NewPostgresqlClient(client2.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 6,
})
return err == nil
case "mysql", "mariadb":
_, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
})
return err == nil
}
return false
}
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
@@ -105,22 +151,39 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
}
return constant.ErrRecordExist
}
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
switch req.Type {
case constant.AppPostgresql:
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 6,
}); err != nil {
return err
}
case "mysql", "mariadb":
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
}); err != nil {
return err
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
}); err != nil {
return err
}
default:
return errors.New("database type not supported")
}
if err := copier.Copy(&db, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
@@ -170,30 +233,51 @@ func (u *DatabaseService) Delete(req dto.DatabaseDelete) error {
return err
}
if db.From != "local" {
if err := mysqlRepo.Delete(context.Background(), mysqlRepo.WithByMysqlName(db.Name)); err != nil && !req.ForceDelete {
return err
if db.Type == "mysql" || db.Type == "mariadb" {
if err := mysqlRepo.Delete(context.Background(), mysqlRepo.WithByMysqlName(db.Name)); err != nil && !req.ForceDelete {
return err
}
} else {
if err := postgresqlRepo.Delete(context.Background(), postgresqlRepo.WithByPostgresqlName(db.Name)); err != nil && !req.ForceDelete {
return err
}
}
}
return nil
}
func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
switch req.Type {
case constant.AppPostgresql:
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 300,
}); err != nil {
return err
}
case "mysql", "mariadb":
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
SSL: req.SSL,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
RootCert: req.RootCert,
SkipVerify: req.SkipVerify,
Timeout: 300,
}); err != nil {
return err
SSL: req.SSL,
RootCert: req.RootCert,
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 300,
}); err != nil {
return err
}
default:
return errors.New("database type not supported")
}
pass, err := encrypt.StringEncrypt(req.Password)
@@ -208,6 +292,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
upMap["port"] = req.Port
upMap["username"] = req.Username
upMap["password"] = pass
upMap["description"] = req.Description
upMap["ssl"] = req.SSL
upMap["client_key"] = req.ClientKey
upMap["client_cert"] = req.ClientCert
+94
View File
@@ -0,0 +1,94 @@
package service
import (
"bufio"
"fmt"
"os"
"path"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
)
type DBCommonService struct{}
type IDBCommonService interface {
LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error)
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
UpdateConfByFile(req dto.DBConfUpdateByFile) error
}
func NewIDBCommonService() IDBCommonService {
return &DBCommonService{}
}
func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) {
var data dto.DBBaseInfo
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
data.ContainerName = app.ContainerName
data.Name = app.Name
data.Port = int64(app.Port)
return &data, nil
}
func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
filePath := ""
switch req.Type {
case "mysql-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name))
case "mariadb-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/conf/my.cnf", req.Name))
case "postgresql-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
case "redis-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "mysql-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(content), nil
}
func (u *DBCommonService) UpdateConfByFile(req dto.DBConfUpdateByFile) error {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
path := ""
switch req.Type {
case constant.AppMariaDB, constant.AppMysql:
path = fmt.Sprintf("%s/%s/%s/conf/my.cnf", constant.AppInstallDir, req.Type, app.Name)
case constant.AppPostgresql:
path = fmt.Sprintf("%s/%s/%s/data/postgresql.conf", constant.AppInstallDir, req.Type, app.Name)
case constant.AppRedis:
path = fmt.Sprintf("%s/%s/%s/conf/redis.conf", constant.AppInstallDir, req.Type, app.Name)
}
file, err := os.OpenFile(path, 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()
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, req.Type, app.Name)); err != nil {
return err
}
return nil
}
+46 -66
View File
@@ -1,7 +1,6 @@
package service
import (
"bufio"
"context"
"encoding/json"
"fmt"
@@ -35,21 +34,18 @@ type IMysqlService interface {
SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error)
ListDBOption() ([]dto.MysqlOption, error)
Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error)
BindUser(req dto.BindUser) error
LoadFromRemote(req dto.MysqlLoadDB) error
ChangeAccess(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(req dto.MysqlVariablesUpdate) error
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error)
Delete(ctx context.Context, req dto.MysqlDBDelete) error
LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error)
LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error)
LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error)
LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error)
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
}
func NewIMysqlService() IMysqlService {
@@ -144,6 +140,42 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
return &createItem, nil
}
func (u *MysqlService) BindUser(req dto.BindUser) error {
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
if err != nil {
return err
}
cli, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
if err := cli.CreateUser(client.CreateInfo{
Name: dbItem.Name,
Format: dbItem.Format,
Username: req.Username,
Password: req.Password,
Permission: req.Permission,
Version: version,
Timeout: 300,
}, false); err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return fmt.Errorf("decrypt database db password failed, err: %v", err)
}
if err := mysqlRepo.Update(dbItem.ID, map[string]interface{}{
"username": req.Username,
"password": pass,
"permission": req.Permission,
}); err != nil {
return err
}
return nil
}
func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
client, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
@@ -158,11 +190,13 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
if err != nil {
return err
}
deleteList := databases
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.MysqlName, data.MysqlName) {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) {
hasOld = true
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
}
@@ -176,6 +210,9 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
}
}
}
for _, delItem := range deleteList {
_ = mysqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
}
return nil
}
@@ -310,7 +347,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
}
global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
return err
}
}
@@ -323,7 +360,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
return nil
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
return nil
@@ -369,26 +406,6 @@ func (u *MysqlService) ChangeAccess(req dto.ChangeDBInfo) error {
return nil
}
func (u *MysqlService) UpdateConfByFile(req dto.MysqlConfUpdateByFile) error {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
path := fmt.Sprintf("%s/%s/%s/conf/my.cnf", constant.AppInstallDir, req.Type, app.Name)
file, err := os.OpenFile(path, 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()
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, req.Type, app.Name)); err != nil {
return err
}
return nil
}
func (u *MysqlService) UpdateVariables(req dto.MysqlVariablesUpdate) error {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
@@ -434,19 +451,6 @@ func (u *MysqlService) UpdateVariables(req dto.MysqlVariablesUpdate) error {
return nil
}
func (u *MysqlService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) {
var data dto.DBBaseInfo
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
data.ContainerName = app.ContainerName
data.Name = app.Name
data.Port = int64(app.Port)
return &data, nil
}
func (u *MysqlService) LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error) {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
@@ -528,30 +532,6 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
return &info, nil
}
func (u *MysqlService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
filePath := ""
switch req.Type {
case "mysql-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name))
case "mariadb-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/conf/my.cnf", req.Name))
case "redis-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "mysql-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(content), nil
}
func executeSqlForMaps(containerName, password, command string) (map[string]string, error) {
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
+418
View File
@@ -0,0 +1,418 @@
package service
import (
"context"
"fmt"
"os"
"path"
"strings"
"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/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
"github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
type PostgresqlService struct{}
type IPostgresqlService interface {
SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error)
ListDBOption() ([]dto.PostgresqlOption, error)
BindUser(req dto.PostgresqlBindUser) error
Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error)
LoadFromRemote(database string) error
ChangePrivileges(req dto.PostgresqlPrivileges) error
ChangePassword(info dto.ChangeDBInfo) error
UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error)
Delete(ctx context.Context, req dto.PostgresqlDBDelete) error
}
func NewIPostgresqlService() IPostgresqlService {
return &PostgresqlService{}
}
func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) {
total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize,
postgresqlRepo.WithByPostgresqlName(search.Database),
commonRepo.WithLikeName(search.Info),
commonRepo.WithOrderRuleBy(search.OrderBy, search.Order),
)
var dtoPostgresqls []dto.PostgresqlDBInfo
for _, pg := range postgresqls {
var item dto.PostgresqlDBInfo
if err := copier.Copy(&item, &pg); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtoPostgresqls = append(dtoPostgresqls, item)
}
return total, dtoPostgresqls, err
}
func (u *PostgresqlService) ListDBOption() ([]dto.PostgresqlOption, error) {
postgresqls, err := postgresqlRepo.List()
if err != nil {
return nil, err
}
databases, err := databaseRepo.GetList(databaseRepo.WithTypeList("postgresql,mariadb"))
if err != nil {
return nil, err
}
var dbs []dto.PostgresqlOption
for _, pg := range postgresqls {
var item dto.PostgresqlOption
if err := copier.Copy(&item, &pg); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.Database = pg.PostgresqlName
for _, database := range databases {
if database.Name == item.Database {
item.Type = database.Type
}
}
dbs = append(dbs, item)
}
return dbs, err
}
func (u *PostgresqlService) BindUser(req dto.PostgresqlBindUser) error {
if cmd.CheckIllegal(req.Name, req.Username, req.Password) {
return buserr.New(constant.ErrCmdIllegal)
}
dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(req.Database), commonRepo.WithByName(req.Name))
if err != nil {
return err
}
pgClient, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
if err := pgClient.CreateUser(client.CreateInfo{
Name: req.Name,
Username: req.Username,
Password: req.Password,
SuperUser: req.SuperUser,
Timeout: 300,
}, false); err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return fmt.Errorf("decrypt database db password failed, err: %v", err)
}
if err := postgresqlRepo.Update(dbItem.ID, map[string]interface{}{
"username": req.Username,
"password": pass,
"super_user": req.SuperUser,
}); err != nil {
return err
}
return nil
}
func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error) {
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format) {
return nil, buserr.New(constant.ErrCmdIllegal)
}
pgsql, _ := postgresqlRepo.Get(commonRepo.WithByName(req.Name), postgresqlRepo.WithByPostgresqlName(req.Database), databaseRepo.WithByFrom(req.From))
if pgsql.ID != 0 {
return nil, constant.ErrRecordExist
}
var createItem model.DatabasePostgresql
if err := copier.Copy(&createItem, &req); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if req.From == "local" && req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
}
cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return nil, err
}
defer cli.Close()
createItem.PostgresqlName = req.Database
defer cli.Close()
if err := cli.Create(client.CreateInfo{
Name: req.Name,
Username: req.Username,
Password: req.Password,
SuperUser: req.SuperUser,
Timeout: 300,
}); err != nil {
return nil, err
}
global.LOG.Infof("create database %s successful!", req.Name)
if err := postgresqlRepo.Create(ctx, &createItem); err != nil {
return nil, err
}
return &createItem, nil
}
func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, error) {
var (
dbInfo client.DBInfo
err error
)
dbInfo.Timeout = 300
databaseItem, err := databaseRepo.Get(commonRepo.WithByName(database))
if err != nil {
return nil, err
}
dbInfo.From = databaseItem.From
dbInfo.Database = database
if dbInfo.From != "local" {
dbInfo.Address = databaseItem.Address
dbInfo.Port = databaseItem.Port
dbInfo.Username = databaseItem.Username
dbInfo.Password = databaseItem.Password
} else {
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
if err != nil {
return nil, err
}
dbInfo.From = "local"
dbInfo.Address = app.ContainerName
dbInfo.Username = app.UserName
dbInfo.Password = app.Password
dbInfo.Port = uint(app.Port)
}
cli, err := postgresql.NewPostgresqlClient(dbInfo)
if err != nil {
return nil, err
}
return cli, nil
}
func (u *PostgresqlService) LoadFromRemote(database string) error {
client, err := LoadPostgresqlClientByFrom(database)
if err != nil {
return err
}
defer client.Close()
databases, err := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(database))
if err != nil {
return err
}
datas, err := client.SyncDB()
if err != nil {
return err
}
deleteList := databases
for _, data := range datas {
hasOld := false
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) {
hasOld = true
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
}
if !hasOld {
var createItem model.DatabasePostgresql
if err := copier.Copy(&createItem, &data); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if err := postgresqlRepo.Create(context.Background(), &createItem); err != nil {
return err
}
}
}
for _, delItem := range deleteList {
_ = postgresqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
}
return nil
}
func (u *PostgresqlService) UpdateDescription(req dto.UpdateDescription) error {
return postgresqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
}
func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error) {
var appInUsed []string
db, err := postgresqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return appInUsed, err
}
if db.From == "local" {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return appInUsed, err
}
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
}
}
} else {
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
}
}
}
return appInUsed, nil
}
func (u *PostgresqlService) Delete(ctx context.Context, req dto.PostgresqlDBDelete) error {
db, err := postgresqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil && !req.ForceDelete {
return err
}
cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
if err := cli.Delete(client.DeleteInfo{
Name: db.Name,
Username: db.Username,
ForceDelete: req.ForceDelete,
Timeout: 300,
}); err != nil && !req.ForceDelete {
return err
}
if req.DeleteBackup {
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/database/%s/%s/%s", req.Type, req.Database, db.Name))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
localDir, err := loadLocalDir()
if err != nil && !req.ForceDelete {
return err
}
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.PostgresqlName, db.Name))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(req.Type), commonRepo.WithByName(req.Database), backupRepo.WithByDetailName(db.Name))
global.LOG.Infof("delete database %s-%s backups successful", req.Database, db.Name)
}
_ = postgresqlRepo.Delete(ctx, commonRepo.WithByID(db.ID))
return nil
}
func (u *PostgresqlService) ChangePrivileges(req dto.PostgresqlPrivileges) error {
if cmd.CheckIllegal(req.Database, req.Username) {
return buserr.New(constant.ErrCmdIllegal)
}
dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(req.Database), commonRepo.WithByName(req.Name))
if err != nil {
return err
}
cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
if err := cli.ChangePrivileges(client.Privileges{Username: req.Username, SuperUser: req.SuperUser, Timeout: 300}); err != nil {
return err
}
if err := postgresqlRepo.Update(dbItem.ID, map[string]interface{}{
"super_user": req.SuperUser,
}); err != nil {
return err
}
return nil
}
func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
if cmd.CheckIllegal(req.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
var (
postgresqlData model.DatabasePostgresql
passwordInfo client.PasswordChangeInfo
)
passwordInfo.Password = req.Value
passwordInfo.Timeout = 300
if req.ID != 0 {
postgresqlData, err = postgresqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
passwordInfo.Username = postgresqlData.Username
} else {
dbItem, err := databaseRepo.Get(commonRepo.WithByType(req.Type), commonRepo.WithByFrom(req.From))
if err != nil {
return err
}
passwordInfo.Username = dbItem.Username
}
if err := cli.ChangePassword(passwordInfo); err != nil {
return err
}
if req.ID != 0 {
var appRess []model.AppInstallResource
if req.From == "local" {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(postgresqlData.ID))
} else {
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(postgresqlData.ID))
}
for _, appRes := range appRess {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
if err != nil {
return err
}
appModel, err := appRepo.GetFirst(commonRepo.WithByID(appInstall.AppId))
if err != nil {
return err
}
global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
return err
}
}
global.LOG.Info("execute password change sql successful")
pass, err := encrypt.StringEncrypt(req.Value)
if err != nil {
return fmt.Errorf("decrypt database db password failed, err: %v", err)
}
_ = postgresqlRepo.Update(postgresqlData.ID, map[string]interface{}{"password": pass})
return nil
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
return nil
}
+25 -6
View File
@@ -44,7 +44,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
confs = append(confs, redisConfig{key: "timeout", value: req.Timeout})
confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients})
confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory})
if err := confSet(redisInfo.Name, confs); err != nil {
if err := confSet(redisInfo.Name, "", confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", constant.AppInstallDir, redisInfo.Name)); err != nil {
@@ -55,10 +55,10 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
if err := updateInstallInfoInDB("redis", "", "password", true, req.Value); err != nil {
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil {
return err
}
if err := updateInstallInfoInDB("redis-commander", "", "password", true, req.Value); err != nil {
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
return err
}
@@ -78,7 +78,7 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly})
confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync})
}
if err := confSet(redisInfo.Name, confs); err != nil {
if err := confSet(redisInfo.Name, req.Type, confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", constant.AppInstallDir, redisInfo.Name)); err != nil {
@@ -213,7 +213,7 @@ type redisConfig struct {
value string
}
func confSet(redisName string, changeConf []redisConfig) error {
func confSet(redisName string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", constant.AppInstallDir, redisName)
lineBytes, err := os.ReadFile(path)
if err != nil {
@@ -221,19 +221,38 @@ func confSet(redisName string, changeConf []redisConfig) error {
}
files := strings.Split(string(lineBytes), "\n")
startIndex, endIndex := 0, 0
startIndex, endIndex, emptyLine := 0, 0, 0
var newFiles []string
for i := 0; i < len(files); i++ {
if files[i] == "# Redis configuration rewrite by 1Panel" {
startIndex = i
newFiles = append(newFiles, files[i])
continue
}
if files[i] == "# End Redis configuration rewrite by 1Panel" {
endIndex = i
break
}
if startIndex == 0 && strings.HasPrefix(files[i], "save ") {
newFiles = append(newFiles, "# "+files[i])
continue
}
if startIndex != 0 && endIndex == 0 && (len(files[i]) == 0 || (updateType == "rbd" && strings.HasPrefix(files[i], "save "))) {
emptyLine++
continue
}
newFiles = append(newFiles, files[i])
}
endIndex = endIndex - emptyLine
for _, item := range changeConf {
if item.key == "save" {
saveVal := strings.Split(item.value, ",")
for i := 0; i < len(saveVal); i++ {
newFiles = append(newFiles, "save "+saveVal[i])
}
continue
}
isExist := false
for i := startIndex; i < endIndex; i++ {
if strings.HasPrefix(newFiles[i], item.key) || strings.HasPrefix(newFiles[i], "# "+item.key) {
+1 -1
View File
@@ -353,7 +353,7 @@ func loadHosts() []dto.HostHelper {
lines := strings.Split(string(hostConf), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 2 {
if len(parts) < 2 || strings.HasPrefix(strings.TrimPrefix(line, " "), "#") {
continue
}
list = append(list, dto.HostHelper{IP: parts[0], Host: strings.Join(parts[1:], " ")})
+1 -1
View File
@@ -181,7 +181,7 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
} else {
daemonMap["iptables"] = false
}
case "Dirver":
case "Driver":
if opts, ok := daemonMap["exec-opts"]; ok {
if optsValue, isArray := opts.([]interface{}); isArray {
for i := 0; i < len(optsValue); i++ {
+5 -3
View File
@@ -12,8 +12,9 @@ var (
appInstallRepo = repo.NewIAppInstallRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
mysqlRepo = repo.NewIMysqlRepo()
databaseRepo = repo.NewIDatabaseRepo()
mysqlRepo = repo.NewIMysqlRepo()
postgresqlRepo = repo.NewIPostgresqlRepo()
databaseRepo = repo.NewIDatabaseRepo()
imageRepoRepo = repo.NewIImageRepoRepo()
composeRepo = repo.NewIComposeTemplateRepo()
@@ -37,7 +38,8 @@ var (
logRepo = repo.NewILogRepo()
snapshotRepo = repo.NewISnapshotRepo()
runtimeRepo = repo.NewIRunTimeRepo()
runtimeRepo = repo.NewIRunTimeRepo()
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
favoriteRepo = repo.NewIFavoriteRepo()
)
+1 -1
View File
@@ -107,7 +107,7 @@ func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error {
if req.Value == "firewallcmd-ipset" || req.Value == "ufw" {
itemName := "ufw"
if req.Value == "firewallcmd-ipset" {
itemName = "firewallcmd"
itemName = "firewalld"
}
client, err := firewall.NewFirewallClient()
if err != nil {
+22 -9
View File
@@ -116,22 +116,32 @@ func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, e
}
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)
}
if op.IsDir {
return fo.CreateDir(op.Path, fs.FileMode(op.Mode))
} else {
if op.IsLink {
if !fo.Stat(op.LinkPath) {
return buserr.New(constant.ErrLinkPathNotFound)
}
return fo.LinkFile(op.LinkPath, op.Path, op.IsSymlink)
mode := op.Mode
if mode == 0 {
fileInfo, err := os.Stat(filepath.Dir(op.Path))
if err == nil {
mode = int64(fileInfo.Mode().Perm())
} else {
return fo.CreateFile(op.Path)
mode = 0755
}
}
if op.IsDir {
return fo.CreateDirWithMode(op.Path, fs.FileMode(mode))
}
if op.IsLink {
if !fo.Stat(op.LinkPath) {
return buserr.New(constant.ErrLinkPathNotFound)
}
return fo.LinkFile(op.LinkPath, op.Path, op.IsSymlink)
}
return fo.CreateFileWithMode(op.Path, fs.FileMode(mode))
}
func (f *FileService) Delete(op request.FileDelete) error {
@@ -236,6 +246,9 @@ func (f *FileService) SaveContent(edit request.FileEdit) error {
}
func (f *FileService) ChangeName(req request.FileRename) error {
if files.IsInvalidChar(req.NewName) {
return buserr.New("ErrInvalidChar")
}
fo := files.NewFileOp()
return fo.Rename(req.OldName, req.NewName)
}
+6 -18
View File
@@ -196,6 +196,12 @@ func (u *FirewallService) OperateFirewall(operation string) error {
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "restart":
if err := client.Restart(); err != nil {
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "disablePing":
return u.updatePingStatus("0")
case "enablePing":
@@ -582,24 +588,6 @@ func (u *FirewallService) addAddressRecord(req dto.AddrRuleOperate) error {
return nil
}
func listIpRules(strategy string) ([]string, error) {
client, err := firewall.NewFirewallClient()
if err != nil {
return nil, err
}
addrs, err := client.ListAddress()
if err != nil {
return nil, err
}
var rules []string
for _, addr := range addrs {
if addr.Strategy == strategy {
rules = append(rules, addr.Address)
}
}
return rules, nil
}
func checkPortUsed(ports, proto string, apps []portOfApp) string {
var portList []int
if strings.Contains(ports, "-") || strings.Contains(ports, ",") {

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