472 Commits
v1.2.1 ... dev

Author SHA1 Message Date
ssongliu
3e6cd1cab1 feat: 远程数据库增加连接性测试 (#1928)
Refs #1924
2023-08-11 14:20:15 +00:00
ssongliu
98df3806f5 fix: 优化容器清理提示信息 (#1927) 2023-08-11 14:18:17 +00:00
ssongliu
18e8af6234 fix: 容器终端断开连接时,保持抽屉打开 (#1926) 2023-08-11 08:08:14 +00:00
zhengkunwang
5bbda8f842 fix: 解决应用升级镜像拉取失败导致应用服务异常的问题 (#1921) 2023-08-11 07:52:13 +00:00
zhengkunwang
85f8c1e634 feat: 守护进程增加操作列 (#1919) 2023-08-11 07:50:13 +00:00
ssongliu
85c935ee46 fix: 容器创建启动命令提示信息修改 (#1917) 2023-08-11 06:04:12 +00:00
ssongliu
b4033471e7 fix: 修改表格分页偶发的数据丢失问题 (#1911)
Refs #1834
2023-08-10 14:36:13 +00:00
ssongliu
8dca519068 feat: 远程数据库地址支持域名 (#1909) 2023-08-10 14:34:23 +00:00
ssongliu
39d8b0d98c fix: 数据库获取时,忽略重名数据库 (#1906) 2023-08-10 06:06:13 +00:00
zhengkunwang
278a562320 feat: 修改应用安装数据库名的默认值 (#1904) 2023-08-10 04:48:12 +00:00
zhengkunwang
c0d8578466 fix: 解决 PHP 运行环境网站恢复失败的问题 (#1903) 2023-08-10 02:42:12 +00:00
ssongliu
b8480e4928 fix: 解决计划任务编辑错误 (#1902) 2023-08-10 02:40:12 +00:00
ssongliu
1ff39d7b85 fix: 解决 ssh 登录日志分页错误 (#1901) 2023-08-10 02:38:16 +00:00
ssongliu
b1f817f09b fix: 限制数据库名称大小写 (#1899) 2023-08-10 02:36:20 +00:00
ssongliu
018fb85bc3 fix: 计划任务创建样式调整 (#1897) 2023-08-09 10:12:12 +00:00
zhengkunwang
bb7102d543 feat: 编辑运行环境增加提示 (#1896) 2023-08-09 10:04:12 +00:00
zhengkunwang
1cccfeff22 feat: 修改应用商店的分页 (#1895) 2023-08-09 10:00:13 +00:00
ssongliu
6c3a5b0ec7 fix: 修改数据库名称长度提示信息 (#1894) 2023-08-09 09:20:12 +00:00
ssongliu
3fffb3c8ec fix: 备份账号高度调整 (#1893) 2023-08-09 08:40:12 +00:00
ssongliu
a51d2d15de fix: 快照恢复前先清空对应文件夹 (#1892) 2023-08-09 08:38:13 +00:00
wangdan-fit2cloud
1c0d0d46a7 Pr@dev@dan (#1891)
#### 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.
2023-08-09 08:32:12 +00:00
zhengkunwang
65b8d47310 style: 修改 Supervisor 初始化的提示文字 (#1890) 2023-08-09 07:42:12 +00:00
ssongliu
ca586bb766 fix: 快照前停止计划任务执行 (#1888) 2023-08-09 06:24:12 +00:00
ssongliu
25ccadea9e fix: 解决计划任务备份远程数据库详情显示错误的问题 (#1886) 2023-08-09 06:14:12 +00:00
zhengkunwang
87e9662af4 fix: 解决关闭高级设置之后没有提示端口放开的问题 (#1887) 2023-08-09 06:08:12 +00:00
zhengkunwang
fe705a25ea feat: 优化网站反向代理地址填写 (#1885) 2023-08-09 03:40:12 +00:00
ssongliu
7e5cdbf953 fix: 解决编排创建日志获取失败的问题 (#1883) 2023-08-09 03:18:12 +00:00
zhengkunwang
e262f4fa40 fix: 解决创建PHP运行环境报错的问题 (#1882) 2023-08-08 14:32:11 +00:00
ssongliu
f4d5b5437e fix: 修改部分字段校验 (#1881) 2023-08-08 14:30:16 +00:00
zhengkunwang
ce258cf157 fix: 解决 Mysql 恢复失败的问题 (#1874) 2023-08-08 09:06:12 +00:00
zhengkunwang
381233a8a5 feat: 修改 Supervisor 的判断 (#1876) 2023-08-08 09:00:12 +00:00
ssongliu
0e8a4eaf2e fix: 解决同步快照名称加载错误的问题 (#1875) 2023-08-08 08:44:12 +00:00
zhengkunwang
0c4400d6f7 feat: 进程守护状态增加信息列 (#1873) 2023-08-08 08:38:12 +00:00
ssongliu
075ae253a1 fix: 国际化问题修改 (#1872) 2023-08-08 06:48:11 +00:00
zhengkunwang
fc57256197 style: 修改网站提示 (#1871) 2023-08-08 06:44:12 +00:00
zhengkunwang
e0f15ca783 feat: 已安装应用增加 https 端口跳转 (#1870) 2023-08-08 06:12:11 +00:00
ssongliu
1931e8800a fix: 优化远程数据库备份方式 (#1869) 2023-08-08 06:08:11 +00:00
zhengkunwang
fb4a5491eb fix: 解决 sftpgo 安装失败的问题 (#1866) 2023-08-08 06:06:20 +00:00
zhengkunwang
cd77c672bc feat: 守护进程一些字段增加校验 (#1865) 2023-08-08 06:04:19 +00:00
ssongliu
9b02e88e3c fix: 防火墙与进程守护加载样式调整 (#1864) 2023-08-07 14:46:27 +00:00
ssongliu
2e73857b42 fix: 解决同步快照路径错误的问题 (#1863) 2023-08-07 07:50:28 +00:00
ssongliu
7721395748 fix: 修改快照版本判断 (#1862) 2023-08-07 07:10:26 +00:00
ssongliu
c326606401 fix: 优化英文状态下,备份恢复按钮换行的问题 (#1860) 2023-08-07 03:52:11 +00:00
zhengkunwang
c9ea6f6c8d fix: 解决应用安装有远程同名数据库报错的问题 (#1861) 2023-08-07 03:50:11 +00:00
zhengkunwang
8c2d3e432d fix: 解决 redis 页面命令行执行命令提示未认证的问题 (#1858) 2023-08-06 14:28:03 +00:00
ssongliu
20a47afb8c fix: mysql 数据库连接信息样式调整 (#1857) 2023-08-06 13:02:00 +00:00
zhengkunwang
07d5c580a6 fix: 解决网站路径重定向不保留URL参数不生效的问题 (#1855) 2023-08-06 09:50:01 +00:00
zhengkunwang
480b8acd66 feat:404重定向取消设置是否保留 URL 参数 (#1854) 2023-08-06 09:48:04 +00:00
zhengkunwang
ac7e47bdf1 style: 修改初始化 Supervisor 的提示文字 (#1838) 2023-08-04 10:22:01 +00:00
ssongliu
a516ba9d63 fix: 解决创建数据库用户重复的问题 (#1837) 2023-08-04 10:20:04 +00:00
zhengkunwang
5f357db1e1 fix: 解决重定向切换 404 没有消除名称字段校验结果的问题 (#1841) 2023-08-04 09:46:00 +00:00
zhengkunwang
5944f67823 fix: 解决文件上传可以拖入文件夹的问题 (#1840)
Refs https://github.com/1Panel-dev/1Panel/issues/1833
2023-08-04 09:44:04 +00:00
zhengkunwang
aa1e548ddf feat: 网站反向代理增加协议选择 (#1832) 2023-08-04 09:42:09 +00:00
ssongliu
80e845f320 fix: 解决数据库同步后备份路径错误的问题 (#1830) 2023-08-04 06:10:43 +00:00
zhengkunwang
d4e6232664 fix: 解决项目启动失败的问题 (#1829) 2023-08-03 14:13:59 +00:00
ssongliu
43e1ec0244 fix: 解决应用备份失败的问题 (#1828) 2023-08-03 10:40:23 +00:00
zhengkunwang
e30546102e fix: 解决应用同步可能出现的多个同名应用的问题 (#1827) 2023-08-03 10:37:57 +00:00
zhengkunwang
70319aca45 feat: 应用关联 redis 时自动填充密码 (#1826) 2023-08-03 10:36:01 +00:00
ssongliu
7c6f8ff7c4 feat: 创建远程数据库支持 localhost 权限 (#1825) 2023-08-03 17:32:47 +08:00
zhengkunwang
7eb96a35df fix: 解决应用升级无法编辑最新配置的问题 (#1824) 2023-08-03 08:51:28 +00:00
ssongliu
e52e6ddaa2 fix: 解决数据库日志监听未刷新的问题 (#1823) 2023-08-03 08:49:32 +00:00
zhengkunwang
40d3392520 fix: 解决 cloudreve 升级数据丢失的问题 (#1822) 2023-08-03 08:47:44 +00:00
ssongliu
b83147220a fix: 分页排序样式调整 (#1821) 2023-08-03 08:21:27 +00:00
ssongliu
a031d3ba41 fix: 计划任务数据库选项增加前缀 (#1820) 2023-08-03 08:19:30 +00:00
zhengkunwang
df770460d6 feat: 应用安装适配 sftpgo pgadmin4 (#1819) 2023-08-03 07:25:26 +00:00
zhengkunwang
0f6e14d2f9 feat: Supervisor 增加修改配置功能 (#1818) 2023-08-03 07:23:32 +00:00
ssongliu
b0320a4ebc feat: 数据库从服务器获取增加提示信息 (#1817) 2023-08-03 06:45:27 +00:00
ssongliu
e4462c06fe fix: 应用恢复后自动同步数据库 (#1816) 2023-08-03 02:42:13 +00:00
ssongliu
c7e7629d85 fix: 修改数据库密码校验规则 (#1815) 2023-08-03 02:40:17 +00:00
zhengkunwang
d34e7492e1 fix: 修复网站配置文件读取漏洞 (#1814) 2023-08-02 14:38:32 +00:00
ssongliu
f6b84d384e fix: 解决越权下载文件的问题 (#1813) 2023-08-02 14:36:37 +00:00
zhengkunwang
202a2ad7ae feat: Supervisor 适配 Debian 操作系统 (#1811) 2023-08-02 10:29:50 +00:00
ssongliu
6f6c836d9a fix: 解决打开越权读取文件的问题 (#1810) 2023-08-02 08:47:30 +00:00
zhengkunwang
01d5bd047f fix: 解决安装应用 数据库密码字段包含&$提示错误的问题 (#1809)
Refs https://github.com/1Panel-dev/1Panel/issues/1793
2023-08-02 03:38:16 +00:00
ssongliu
f000f8b46c fix: 回滚目录切换 (#1808) 2023-08-01 14:05:14 +00:00
zhengkunwang
ae8b6b8c05 style: 修改网站防盗链页面样式 (#1807) 2023-08-01 14:03:15 +00:00
zhengkunwang
b7d09d0201 fix: 解决带数据库应用安装失败的问题 (#1806) 2023-08-01 10:44:54 +00:00
ssongliu
67bba4bc1d fix: 快照容器部分修改为仅备份已安装应用数据 (#1805) 2023-08-01 10:40:54 +00:00
zhengkunwang
a0a26f237b fix: 解决重定向设置重定向到首页但是编辑显示错误的问题 (#1803) 2023-08-01 10:25:12 +00:00
zhengkunwang
a84d8dd828 fix: 解决文件压缩无法选择文件夹的问题 (#1802) 2023-08-01 10:22:58 +00:00
zhengkunwang
39abd4341d feat: 增加守护进程管理 (#1800) 2023-08-01 09:31:42 +00:00
^薄荷布丁^
b31de5e637 修正翻译错误 (#1797) 2023-08-01 12:53:14 +08:00
ssongliu
09653f9c06 fix: 修改远程数据库依赖 (#1794) 2023-08-01 03:20:16 +00:00
ssongliu
e0ca9507de fix: 修改监控默认采集间隔和保存时间 (#1795) 2023-07-31 14:06:37 +00:00
zhengkunwang
b59ccc52ae feat: 增加进程守护管理 (#1786)
增加 Supervisor 状态读取 初始化 启动 重启 设置 日志 功能
Refs https://github.com/1Panel-dev/1Panel/issues/1754
Refs https://github.com/1Panel-dev/1Panel/issues/1409
Refs https://github.com/1Panel-dev/1Panel/issues/1388
Refs https://github.com/1Panel-dev/1Panel/issues/379
Refs https://github.com/1Panel-dev/1Panel/issues/353
Refs https://github.com/1Panel-dev/1Panel/issues/331
2023-07-31 03:28:41 +00:00
ssongliu
f9c8aa0484 fix: 解决 ssh 登录日志年份获取失败的问题 (#1785) 2023-07-28 10:27:20 +00:00
ssongliu
5629dbff8e fix: 解决手动挂载的挂载卷在编辑时不显示的问题 (#1783)
Refs #1781
2023-07-28 10:26:02 +00:00
ssongliu
ed0923fd28 fix: 面板设置支持设置服务器域名 (#1778) 2023-07-28 10:23:01 +00:00
ssongliu
489efb7ac1 fix: 创建抽屉隐藏名称显示 (#1777) 2023-07-28 10:21:06 +00:00
Eric_Lee
6a0b4a1176 perf: 移除不必要的 ssh session 连接 (#1780)
* perf: 移除不必要的 ssh session 连接

---------

Co-authored-by: Eric <xplv@126.com>
2023-07-28 11:04:03 +08:00
ssongliu
7a67377aa9 fix: 兼容安装目录设置为根目录的情况 2023-07-27 16:42:12 +08:00
ssongliu
40aaa1ceb0 feat: 数据库实现远程服务器获取功能 (#1775) 2023-07-27 08:07:27 +00:00
ssongliu
e83e592e0a feat: 实现远程数据备份恢复功能 (#1774) 2023-07-27 06:32:23 +00:00
ssongliu
87a7cf3aca fix: 解决容器端口冲突导致创建失败的问题 (#1770) 2023-07-27 05:58:13 +00:00
longyuan
2a20ff0b79 fix DatabaseMysql 新增的 From字段 不允许为空 但未设置默认值 导致已存在数据库的1panel升级时无法正常启动 (#1772)
Co-authored-by: 邵传波 <shaocb@mail.unitid.cn>
2023-07-27 13:58:08 +08:00
zhengkunwang
1a6c45c526 feat: 文件列表记录最后一次位置 (#1753)
Refs https://github.com/1Panel-dev/1Panel/issues/1254
2023-07-25 09:26:12 +00:00
zhengkunwang
4d368a4de8 feat: 登录页增加语言选项 (#1752)
Refs https://github.com/1Panel-dev/1Panel/issues/1728
2023-07-25 09:24:15 +00:00
zhengkunwang
b05e5736a6 feat: 升级应用增加备份选项 (#1750)
Refs https://github.com/1Panel-dev/1Panel/issues/1742
2023-07-25 09:22:20 +00:00
zhengkunwang
3b9f92e03f feat: PHP 网站增加版本切换 (#1748)
Refs https://github.com/1Panel-dev/1Panel/issues/1586
Refs https://github.com/1Panel-dev/1Panel/issues/1406
Refs https://github.com/1Panel-dev/1Panel/issues/974
Refs https://github.com/1Panel-dev/1Panel/issues/671
2023-07-25 09:20:25 +00:00
ssongliu
da4c264908 feat: 增加远程数据库提示信息 (#1745) 2023-07-25 09:08:13 +00:00
ssongliu
bbd649188a feat: 实现本地远程数据库切换 2023-07-24 14:58:55 +08:00
ssongliu
cd742b0933 feat: 远程数据库增加默认数据 2023-07-24 14:58:55 +08:00
ssongliu
7f79f5f031 feat: 实现远程数据库改密、授权功能 2023-07-24 14:58:55 +08:00
ssongliu
cb7351a9fb feat: 实现远程数据库增删改查 2023-07-24 14:58:55 +08:00
ssongliu
bd5dc56b66 feat: Mysql 数据库增删、改密、改权限方法封装 2023-07-24 14:58:55 +08:00
zhengkunwang
34e8d88a53 feat: 优化网站 IPV6 设置 (#1732)
Refs https://github.com/1Panel-dev/1Panel/issues/1404
2023-07-22 14:04:17 +00:00
zhengkunwang
c14e192bee feat: 网站增加重定向功能 (#1731)
Refs https://github.com/1Panel-dev/1Panel/issues/1654
Refs https://github.com/1Panel-dev/1Panel/issues/1387
Refs https://github.com/1Panel-dev/1Panel/issues/986
2023-07-22 14:02:21 +00:00
wangdan-fit2cloud
d31eacd049 fix: 移动端文件列表UI异常问题修复 (#1730)
#### 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.
2023-07-21 06:30:13 +00:00
wangdan-fit2cloud
f556038425 Pr@dev@dan (#1721)
#### 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.
2023-07-20 10:13:55 +00:00
dependabot[bot]
03ed4c8071 build(deps-dev): bump word-wrap from 1.2.3 to 1.2.4 in /frontend (#1726)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 17:59:51 +08:00
zhengkunwang
86ba53d6ec style: 应用商店样式修改 (#1711) 2023-07-18 08:10:01 +00:00
ssongliu
0ca559f1e1 fix: 快速命令增加提示信息 (#1710) 2023-07-18 07:34:00 +00:00
ssongliu
1fd31fffd6 fix: 优化首页样式 (#1709) 2023-07-18 07:26:02 +00:00
wangdan-fit2cloud
c62c19a49e fix: 修复首页高度对齐问题 (#1708)
#### 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.
2023-07-18 06:28:35 +00:00
zhengkunwang
aa8816f3ec feat: 网站已安装页面增加分页 (#1707) 2023-07-18 06:22:36 +00:00
zhengkunwang
920f1112fa feat: php 网站禁用函数支持输入下划线 (#1705) 2023-07-18 06:20:39 +00:00
ssongliu
4c2302dc94 fix: 国际化内容补全 (#1696) 2023-07-17 11:24:24 +00:00
ssongliu
efebb26b5e fix: 解决容器创建端口选择 ipv6 校验问题 (#1695) 2023-07-17 08:38:21 +00:00
ssongliu
22fe2a6d51 fix: 本地连接失败时,打开添加主机抽屉 (#1691) 2023-07-17 08:36:24 +00:00
ssongliu
e17b80cff4 fix: 解决部分接口命令注入问题 (#1690) 2023-07-17 08:34:29 +00:00
wangdan-fit2cloud
1d6f1b0ef3 fix: 优化首页对齐问题 (#1689)
#### 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.
2023-07-17 08:32:34 +00:00
ssongliu
dac0c81d96 fix: 解决主机密钥无法正常连接终端的问题 (#1675) 2023-07-14 14:40:12 +00:00
wangdan-fit2cloud
e555dcb903 fix: 优化echart线状图表 (#1673)
#### 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.
2023-07-14 14:38:16 +00:00
ssongliu
15c391e763 fix: 解决概览页左右高度不一致的问题 (#1671) 2023-07-14 08:22:11 +00:00
ssongliu
3b3584714c fix: 修改部分国际化内容 (#1669) 2023-07-14 07:18:11 +00:00
zhengkunwang
61a0244cfe fix: 修改部分国际化内容 (#1670) 2023-07-14 07:08:10 +00:00
wangdan-fit2cloud
73a61933c5 Pr@dev@dan (#1668)
#### 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.
2023-07-14 06:34:10 +00:00
zhengkunwang
72c7407b0b style: 文件列表增加操作列宽度 (#1667) 2023-07-14 06:22:10 +00:00
zhengkunwang
6538282a7b fix: 解决网站停止之后再启动运行目录错误的问题 (#1666)
Refs https://github.com/1Panel-dev/1Panel/issues/1665
2023-07-14 06:20:19 +00:00
ssongliu
87326e3292 fix: 解决监控数据异常导致溢出的问题 2023-07-14 11:55:33 +08:00
zhengkunwang
6eadb116c2 feat: 修复当网站主域名与网站目录不一致时,定时任务切割网站日志失败的问题 (#1659) 2023-07-14 03:50:10 +00:00
zhengkunwang
c9ffd2564e feat: 基本名称名称长度限制增加到 128 (#1658) 2023-07-14 03:48:11 +00:00
wangdan-fit2cloud
cd6e5f7905 fix: 优化暗色背景下禁用按钮样式 (#1663)
#### 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.
2023-07-14 03:38:10 +00:00
Mystery0 M
b96e988f20 fix: 修复当网站主域名与网站目录不一致时,定时任务切割网站日志失败 (#1653)
* fix: 网站日志切割定时任务执行失败时打印失败原因,方便排查问题

* fix: 修复SiteDir与主域名不匹配时,切割网站日志失败
2023-07-13 22:25:58 +08:00
wzrove
30f7fa6afa fix: 优化监控页面echart资源释放 (#1651)
#### What this PR does / why we need it?

#1598 监控页面echart资源 组件卸载未释放

#### Summary of your change

优化监控页面echart资源释放

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

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

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

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

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

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

* feat: 容器日志增加全屏按钮 #1176
2023-06-12 15:44:15 +08:00
zhengkunwang223
11ae05163c fix: 解决旧版本安装的应用升级之后状态异常的问题 (#1327) 2023-06-09 15:40:11 +00:00
wangdan-fit2cloud
0275716ff5 feat: 优化页面兼容移动端问题 (#1320)
#### 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.
2023-06-09 15:38:15 +00:00
ssongliu
e9bdca5279 fix: 解决终端全屏及缩放的菜单显示问题 (#1316) 2023-06-09 08:56:11 +00:00
ssongliu
cd84116a03 fix: 解决终端意外退出全屏导致菜单无法显示的问题 (#1314) 2023-06-09 08:02:12 +00:00
wangdan-fit2cloud
457effae85 fix: 终端全屏问题 (#1313)
#### 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.
2023-06-09 07:52:10 +00:00
zhengkunwang223
9361c358f1 style: 修改安装应用提示语句 (#1308) 2023-06-09 03:48:11 +00:00
ssongliu
39f952e460 fix: 优化 ssh 登录日志查询速度 (#1307) 2023-06-09 03:44:12 +00:00
ssongliu
7c600a357c fix: 解决应用备份或升级失败的问题 (#1306) 2023-06-09 03:42:11 +00:00
吴小白
8f036a0c3d feat: 上传文件到 OSS (#1304) 2023-06-09 10:47:09 +08:00
ssongliu
1184ae2b52 fix: 容器日志切割样式修改 (#1303) 2023-06-09 02:30:11 +00:00
zhengkunwang223
de7ef19034 fix: 解决系统启动时同步应用导致的报错 (#1301) 2023-06-08 15:20:11 +00:00
ssongliu
e91b7caf4f fix: 解决 mysql 日志清空错误的问题 (#1298) 2023-06-08 13:50:11 +00:00
zhengkunwang223
033b3c10c0 feat: 应用详情适配夜间模式 (#1297) 2023-06-08 11:00:11 +00:00
ssongliu
97774c88d5 fix: 解决 sftp 上传大文件失败的问题 (#1294) 2023-06-08 10:58:15 +00:00
zhengkunwang223
95e1c73ced feat: 删除无用的文件 (#1289) 2023-06-08 03:36:10 +00:00
zhengkunwang223
d2a067db77 feat: 修改前端打包方式 (#1288) 2023-06-08 03:14:10 +00:00
ssongliu
586dee9e70 fix: 解决计划任务报告未显示执行周期的问题 (#1287) 2023-06-08 03:12:15 +00:00
ssongliu
6a717b2517 fix: 兼容 mysql 5.7.42 (#1286) 2023-06-08 03:00:12 +00:00
zhengkunwang223
203af988fa fix: 修复启动 panic 问题 (#1285) 2023-06-08 02:58:10 +00:00
zhengkunwang223
911166152a fix: 解决 MYSQL 不显示升级的问题 (#1283) 2023-06-07 11:09:23 +00:00
ssongliu
b3ecddb20f feat: 计划任务创建增加第三方备份账号跳转 (#1282) 2023-06-07 10:23:24 +00:00
zhengkunwang223
a41d4e55aa feat: 删除域名的时候同步删除 ipv6 的端口监听 (#1281) 2023-06-07 10:21:24 +00:00
zhengkunwang223
c7a3ef7d72 style: 修改 logo 路径 (#1279) 2023-06-07 08:31:25 +00:00
zhengkunwang223
b7495a63e9 fix: 解决网站导入自签证书报错的问题 (#1278) 2023-06-07 08:09:25 +00:00
ssongliu
6b3093aa09 fix: 计划任务执行周期校验修改 (#1277) 2023-06-07 07:59:28 +00:00
zhengkunwang223
8f8369c989 feat: 创建 DNS 账号禁止输入空格 (#1276) 2023-06-07 07:03:25 +00:00
zhengkunwang223
f79293be69 feat: 修改一些提示信息 (#1274) 2023-06-07 06:55:24 +00:00
ssongliu
4631da2212 feat: 版本更新检查错误信息优化 (#1273) 2023-06-07 06:53:24 +00:00
ssongliu
e2a7c51de0 fix: 备份适配 redis 版本 (#1271) 2023-06-07 02:49:22 +00:00
ssongliu
633e26b1de fix: 工作目录切换逻辑修改 (#1269) 2023-06-06 10:29:22 +00:00
zhengkunwang223
08992f83b5 fix: 解决导入 cloudflare 证书域名显示错误的问题 (#1268)
Refs https://github.com/1Panel-dev/1Panel/issues/1184
2023-06-06 08:29:22 +00:00
ssongliu
921e886e71 fix: 应用默认配置路径修改 (#1267) 2023-06-06 08:03:22 +00:00
ssongliu
9fc4cad80e fix: 操作日志内容修改 (#1266) 2023-06-06 03:33:21 +00:00
ssongliu
4fdb81642a fix: 修改 ssh 登录日志排序规则 (#1264) 2023-06-06 03:31:26 +00:00
dependabot[bot]
056c771d2e build(deps-dev): bump vite from 2.9.15 to 2.9.16 in /frontend (#1265)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 2.9.15 to 2.9.16.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v2.9.16/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v2.9.16/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>
2023-06-06 11:25:17 +08:00
ssongliu
84183bdfeb fix: 容器详情抽屉高度自适应 (#1262) 2023-06-05 10:37:21 +00:00
ssongliu
4e786fee31 fix: 日志拦截解压方式判断修改 (#1260) 2023-06-05 10:35:25 +00:00
zhengkunwang223
9f4e5050dd feat: acme 账号增加提示信息 (#1259) 2023-06-05 10:33:29 +00:00
ssongliu
bb49a610a2 fix: 计划任务排除规则改为换行切割 (#1255) 2023-06-05 10:31:26 +00:00
&nbsp
5e3e580f51 perf: 浏览器失去焦点停止查询、节省开销 (#1256) 2023-06-05 17:52:53 +08:00
ssongliu
75fa498a7c fix: 恢复密码过期跳转 (#1252) 2023-06-05 07:01:21 +00:00
zhengkunwang223
05e5f06cf1 feat: 修改应用容器名称增加校验 (#1251) 2023-06-05 06:59:26 +00:00
ssongliu
3a17f4f29f fix: 面板设置提示信息修改 (#1249) 2023-06-05 05:47:21 +00:00
zhengkunwang223
5e7524e4f8 fix: 解决本地应用升级失败的问题 (#1248) 2023-06-05 05:45:26 +00:00
zhengkunwang223
b2e17d4c42 feat: 防盗链响应增加状态码判断 (#1242) 2023-06-03 05:21:20 +00:00
dependabot[bot]
b88303d36a build(deps): bump @antfu/utils, unplugin-auto-import and unplugin-vue-components (#1243)
Bumps [@antfu/utils](https://github.com/antfu/utils) to 0.7.4 and updates ancestor dependencies [@antfu/utils](https://github.com/antfu/utils), [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) and [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components). These dependencies need to be updated together.


Updates `@antfu/utils` from 0.4.0 to 0.7.4
- [Release notes](https://github.com/antfu/utils/releases)
- [Commits](https://github.com/antfu/utils/compare/v0.4.0...v0.7.4)

Updates `unplugin-auto-import` from 0.6.9 to 0.16.4
- [Release notes](https://github.com/antfu/unplugin-auto-import/releases)
- [Commits](https://github.com/antfu/unplugin-auto-import/compare/v0.6.9...v0.16.4)

Updates `unplugin-vue-components` from 0.17.21 to 0.25.0
- [Release notes](https://github.com/antfu/unplugin-vue-components/releases)
- [Commits](https://github.com/antfu/unplugin-vue-components/compare/v0.17.21...v0.25.0)

---
updated-dependencies:
- dependency-name: "@antfu/utils"
  dependency-type: indirect
- dependency-name: unplugin-auto-import
  dependency-type: direct:development
- dependency-name: unplugin-vue-components
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-03 13:20:54 +08:00
zhengkunwang223
5a5b0e3a1b fix: 解决同步应用之后旧版本被删除的问题 (#1241) 2023-06-03 05:19:23 +00:00
ssongliu
e504e55a34 feat: 容器日志切割增加回显 (#1239) 2023-06-02 09:49:19 +00:00
zhengkunwang223
b6758ff92d feat: 安装应用自动填写名称 (#1238) 2023-06-02 09:47:23 +00:00
zhengkunwang223
957499e4d7 fix: 解决应用内存限制回显失败的问题 (#1237) 2023-06-02 09:45:24 +00:00
wanghe-fit2cloud
3773d64aa7 chore: 删除 docker.sh 脚本引用 2023-06-02 17:43:45 +08:00
ssongliu
d89f823bef fix: 版本升级适配 armv7 (#1236) 2023-06-02 09:03:20 +00:00
zhengkunwang223
a8b7c3d8c5 feat: 增加一些提示信息 (#1234) 2023-06-02 06:31:19 +00:00
zhengkunwang223
9a45ce3110 feat: 限制已废弃应用升级和备份 (#1233) 2023-06-02 06:25:19 +00:00
zhengkunwang223
3ad3b180af feat: 增加应用商店定时同步 (#1231) 2023-06-02 06:23:24 +00:00
ssongliu
6919ce7b5c fix: 概览页磁盘大小自适应 (#1232) 2023-06-02 06:19:19 +00:00
zhengkunwang223
e7a9c3814b fix: 解决打开防盗链报错的问题 (#1230) 2023-06-02 05:53:25 +00:00
ssongliu
488eb319a1 fix: 优化监控页空数据显示 (#1229) 2023-06-02 03:50:27 +00:00
zhengkunwang223
4f650cad9d fix: 解决PHP上传限制清空之后可以上传的问题 (#1227) 2023-06-02 03:12:28 +00:00
dependabot[bot]
ce9c2e6d3c build(deps): bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#1228)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.0...v1.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 11:10:40 +08:00
zhengkunwang223
90a5e741fd fix: 解决本地应用升级失败的问题 (#1226) 2023-06-02 02:26:27 +00:00
ssongliu
bfedd02d1d fix: 解决容器配置失败的问题 (#1225) 2023-06-02 02:24:29 +00:00
ssongliu
0187d2f5c1 fix: ssh 登录日志时区修改 (#1223) 2023-06-01 14:04:57 +00:00
ssongliu
7d968348f5 fix: ssh 登录日志归属地获取方式修改 (#1221) 2023-06-01 10:27:53 +00:00
zhengkunwang223
317017a2b4 fix: 解决应用升级失败的问题 (#1220) 2023-06-01 09:05:14 +00:00
ssongliu
35ca52620c feat: 计划任务报告页增加计划任务名称显示 (#1219) 2023-06-01 09:03:21 +00:00
ssongliu
01ed60fcb7 fix: 解决编辑已停止计划任务导致任务执行的问题 (#1218) 2023-06-01 07:05:14 +00:00
zhengkunwang223
80599a3576 feat: 删除 AppStoreVersion 参数 (#1217) 2023-06-01 06:47:13 +00:00
zhengkunwang223
d40b2734a9 feat: 减少 badger 的内存占用 (#1216) 2023-06-01 06:45:18 +00:00
zhengkunwang223
6abd313bd2 fix: 解决解压 GBK 格式的 zip 文件中文乱码的问题 (#1214)
Refs https://github.com/1Panel-dev/1Panel/issues/1209
2023-06-01 05:51:11 +00:00
wanghe
88f573e2a6 Update .goreleaser.yaml 2023-06-01 10:58:03 +08:00
ssongliu
eb55e16465 fix: 拦截中间件数据改为实时获取 (#1213) 2023-06-01 02:38:11 +00:00
ssongliu
bb48964cca fix: 优化未启用安全入口跳转逻辑 (#1210) 2023-06-01 02:14:11 +00:00
zhengkunwang223
cbdf1ad7b7 fix: 解决应用无法升级的问题 (#1208) 2023-05-31 15:27:03 +00:00
zhengkunwang223
df77ac4318 style: 网站设置页面手机端适配 (#1207) 2023-05-31 15:25:07 +00:00
zhengkunwang223
b3caa343d2 feat: 编辑应用增加编辑 docker-compose.yml (#1206) 2023-05-31 07:59:00 +00:00
wanghe-fit2cloud
49f4f316f8 refactor: goreleaser 增加 dodocker.sh 2023-05-31 15:44:18 +08:00
zhengkunwang223
2207711400 feat: 编辑应用 cpu 和内存限制增加校验 (#1205) 2023-05-31 07:41:02 +00:00
zhengkunwang223
763f450f43 feat: 创建应用容器名称增加校验 (#1204) 2023-05-31 07:39:04 +00:00
zhengkunwang223
0fa11213bb feat: 本地应用同步增加日志 (#1203) 2023-05-31 06:51:01 +00:00
wangdan-fit2cloud
c743775a1e feat: 移动端样式优化 (#1202)
#### 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.
2023-05-31 06:05:01 +00:00
ssongliu
b854aa3bfe fix: 默认时区获取方式修改 (#1200) 2023-05-31 06:03:04 +00:00
zhengkunwang223
8dfba82a70 fix: 解决备份文件过大导致下载超时问题 (#1199)
Refs https://github.com/1Panel-dev/1Panel/issues/1191
2023-05-31 06:01:01 +00:00
zhengkunwang223
824051e89e feat: 限制删除运行环境中使用的应用版本 (#1197) 2023-05-30 13:54:58 +00:00
wanghe
ab45cc27d5 Update .goreleaser.yaml (#1196)
#### What this PR does / why we need it?

#### Summary of your change

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

- [x] Made sure tests are passing and test coverage is added if needed.
- [x] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [x] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-05-30 08:52:57 +00:00
zhengkunwang223
627e467fdf fix: 解决申请证书 多个域名下显示异常的问题 (#1195)
Refs https://github.com/1Panel-dev/1Panel/issues/996
2023-05-30 08:48:58 +00:00
zhengkunwang223
ca3b4b02e0 fix: 解决网站的默认文档不能设置为子目录的文件的问题 (#1194)
Refs https://github.com/1Panel-dev/1Panel/issues/720
2023-05-30 08:24:57 +00:00
ssongliu
34ac685e65 fix: 面板设置输入增加校验及提示信息 (#1193) 2023-05-30 07:46:58 +00:00
ssongliu
7a66e71215 fix: 统一修改代码拼写错误 (#1192) 2023-05-30 07:30:57 +00:00
zhengkunwang223
1e96a1ae84 fix: 解决 halo 用户名设置为大写之后导致安装失败的问题 (#1190)
Refs https://github.com/1Panel-dev/1Panel/issues/1161
2023-05-30 07:04:57 +00:00
zhengkunwang223
ce0342e235 fix: 解决 nexus 安装失败的问题 (#1189)
Refs https://github.com/1Panel-dev/1Panel/issues/1179
2023-05-30 06:46:57 +00:00
zhengkunwang223
a48ea497b0 feat: 网站增加 IPV6 监听设置 (#1188) 2023-05-30 06:22:57 +00:00
ssongliu
4b3c9419f3 fix: 镜像仓库校验判断修改 (#1187) 2023-05-30 05:56:57 +00:00
zhengkunwang223
f51b09dc40 style: 修改日志审计-网站日志样式 (#1185) 2023-05-30 03:02:56 +00:00
zhengkunwang223
800f9e2d38 feat: 优化大文件下载 (#1183)
Refs https://github.com/1Panel-dev/1Panel/issues/1165
2023-05-29 16:00:56 +00:00
ssongliu
b5093e4d93 fix: 补全缺失的组件引用 (#1181) 2023-05-29 10:10:56 +00:00
ssongliu
10684f9aac fix: 解决浏览器关闭后重新打开不需要登录验证的问题 (#1180) 2023-05-29 09:32:55 +00:00
ssongliu
7c0327c1b8 fix: 修复概览页数据加载失败的问题 (#1178) 2023-05-29 07:10:40 +00:00
ssongliu
9f5a03419e fix: 面板设置界面样式统一 (#1177) 2023-05-29 15:08:54 +08:00
ssongliu
ec843f2396 feat: docker 配置界面样式统一 (#1173) 2023-05-29 11:24:28 +08:00
zhengkunwang223
2d6925ac4f feat: 文件列表增加批量上传功能 (#1168) 2023-05-27 23:15:30 +08:00
zhengkunwang223
7cbaa4f63d fix: 解决高级设置CPU和内存限制删除之后安装报错的问题 (#1167) 2023-05-27 23:13:47 +08:00
ssongliu
ebb9e7a2a0 feat: 概览界面针对磁盘显示优化 2023-05-26 16:49:03 +08:00
吴小白
84e44127b3 feat: 更新 npm 依赖 (#1154) 2023-05-26 13:44:27 +08:00
吴小白
0b66bb5eff perf: 更新 arm 文件名规则 (#1153) 2023-05-26 13:43:58 +08:00
zhengkunwang223
e70ec0e978 feat: 网站增加防盗链设置 (#1151)
Refs https://github.com/1Panel-dev/1Panel/issues/1149
2023-05-26 02:40:21 +00:00
吴小白
d62f566bb3 build(deps): bump github.com/shirou/gopsutil/v3 from v3.23.1 to v3.23.4 (#1152) 2023-05-26 10:40:17 +08:00
吴小白
4e1c3be03a perf: 默认构建 armv7 (#1148) 2023-05-25 21:42:31 +08:00
ssongliu
792e96e95f feat: 优化监控界面无数据时显示样式 (#1147) 2023-05-25 10:50:18 +00:00
ssongliu
c1acd8f5f0 feat: 监控增加数据采集间隔设置 (#1146) 2023-05-25 10:02:17 +00:00
吴小白
d64e1713fb fix: 修正 actions 构建错误 (#1145) 2023-05-25 16:46:39 +08:00
吴小白
3e6fefe1b7 feat: 支持更多 linux 架构 (#1144) 2023-05-25 15:48:00 +08:00
ssongliu
48e3ef3e73 fix: 修改计划任务报告样式 (#1142) 2023-05-25 07:12:16 +00:00
zhengkunwang223
c76c24e102 feat: 适配 Linux 多架构打包 (#1143) 2023-05-25 07:06:16 +00:00
zhengkunwang223
4b25dafb92 feat: 修改同步应用列表方式 (#1141) 2023-05-25 06:58:16 +00:00
ssongliu
c8237d59be fix: 计划任务删除弹窗优化 (#1140) 2023-05-25 02:48:10 +00:00
wangdan-fit2cloud
c2629e3945 feat: 调整网站设置页面 (#1139)
#### 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.
2023-05-25 02:22:10 +00:00
吴小白
b5864ca3a3 修复: 修正 goreleaser 构建失败 (#1136) 2023-05-24 21:40:51 +08:00
zhengkunwang223
b84c983727 feat: 网站日志增加清空功能 (#1134) 2023-05-24 13:40:35 +00:00
zhengkunwang223
aa3e8783ae feat: PHP 网站增加配置上传限制功能 (#1133) 2023-05-24 10:38:50 +00:00
ssongliu
f65f0d86aa feat: 计划任务增加每隔 n 秒执行 (#1132) 2023-05-24 10:36:41 +00:00
zhengkunwang223
93b03c7212 style: 删除无用代码 2023-05-24 17:26:14 +08:00
ssongliu
c96b999b2c feat: 计划任务增加时间同步选项 (#1128) 2023-05-24 09:25:35 +00:00
吴小白
da155fadf2 feat: 使用 goreleaser 构建包 (#829)
* feat: 使用 goreleaser 构建包

Signed-off-by: 吴小白 <296015668@qq.com>
2023-05-24 16:46:28 +08:00
ssongliu
c69a1c8ec2 fix: 计划任务脚本执行设置执行路径 (#1127) 2023-05-24 08:39:36 +00:00
wangdan-fit2cloud
83ac2f1ff7 feat: 兼容移动端 (#1126)
#### 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.
2023-05-24 08:36:54 +00:00
zhengkunwang223
f77972fa38 feat: PHP 网站增加禁用函数设置 (#1125)
Refs https://github.com/1Panel-dev/1Panel/issues/663
2023-05-24 08:31:18 +00:00
ssongliu
d7c08295f8 fix: 解决容器进程异常退出的问题 (#1124) 2023-05-24 07:45:12 +00:00
ssongliu
be6b7157f4 fix: 系统时间同步样式修改 (#1123) 2023-05-24 07:43:21 +00:00
zhengkunwang223
b979c2574c feat: 网站密码访问限制没有用户的情况下启用 (#1120)
Refs https://github.com/1Panel-dev/1Panel/issues/1113
2023-05-24 06:17:11 +00:00
wangdan-fit2cloud
f7c76c012f feat: 应用商店兼容移动端 (#1119)
#### 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.
2023-05-23 13:40:03 +00:00
zhengkunwang223
13abe8cb66 feat: 网站 HTTPS 配置增加阻止 IP 访问 TLS 握手 (#1118)
Refs https://github.com/1Panel-dev/1Panel/issues/849
2023-05-23 11:02:02 +00:00
ssongliu
7596099aa1 feat: 增加容器、镜像、网络、存储卷清理功能 (#1117) 2023-05-23 11:00:06 +00:00
zhengkunwang223
626782102a feat: 计划任务增加网站日志切割 (#1116)
Refs https://github.com/1Panel-dev/1Panel/issues/495
2023-05-23 10:54:05 +00:00
zhengkunwang223
3e068a0020 feat: 日志审计增加网站日志 (#1114) 2023-05-23 07:47:43 +00:00
ssongliu
09af1e9cdf fix: 解决代码合并冲突 (#1112) 2023-05-23 07:45:47 +00:00
ssongliu
5ac9298db0 feat: 增加容器日志清理功能 (#1111) 2023-05-23 07:43:51 +00:00
wangdan-fit2cloud
0d084861e0 feat: 概览兼容移动端 (#1109)
#### 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.
2023-05-23 05:51:43 +00:00
zhengkunwang223
c052887d58 feat: 应用商店支持多容器的本地应用 (#1108)
Refs https://github.com/1Panel-dev/1Panel/issues/635
2023-05-23 02:31:45 +00:00
zhengkunwang223
f5cd45438b feat: 应用列表增加已安装提示 (#1107)
Refs https://github.com/1Panel-dev/1Panel/issues/972
2023-05-22 13:39:42 +00:00
ssongliu
a0a1cc410f feat: 日志菜单样式调整 (#1106) 2023-05-22 13:29:43 +00:00
zhengkunwang223
74cfc11a37 feat: 创建应用增加 compose 文件配置功能 (#1105)
Refs https://github.com/1Panel-dev/1Panel/issues/301
2023-05-22 11:19:40 +00:00
ssongliu
53600900f2 feat: 时间同步增加时区、同步地址自定义设置 (#1102) 2023-05-22 09:45:39 +00:00
zhengkunwang223
ce19107c95 feat: 已安装应用增加打开安装目录功能 (#1100) 2023-05-22 09:10:51 +00:00
zhengkunwang223
7c56ed7b16 feat: 取消网站 ipv6 的默认设置 (#1099) 2023-05-22 03:03:19 +00:00
ssongliu
152cc76e3f feat: 1pctl reset 命令改为多级 (#1098) 2023-05-22 03:01:47 +00:00
ssongliu
8fd4060562 feat: 实现域名绑定与授权 ip 功能 (#1089) 2023-05-19 13:47:46 +00:00
zhengkunwang223
c2f5908a9d fix: 解决首页控制台报错的问题 (#1087) 2023-05-19 09:50:12 +00:00
zhengkunwang223
57aa2aba74 fix: 解决伪静态页面光标丢失的问题 (#1086) 2023-05-19 07:53:10 +00:00
zhengkunwang223
d851aeed45 fix: 解决搜索子目录模糊查询失败的问题 (#1085)
Refs https://github.com/1Panel-dev/1Panel/issues/1049
2023-05-19 07:51:09 +00:00
zhengkunwang223
03d338d6c9 fix: 解决打开编辑文件页面确认按钮可能看不到的问题 (#1084)
Refs https://github.com/1Panel-dev/1Panel/issues/1042
2023-05-19 06:25:09 +00:00
zhengkunwang223
a74cdcc061 fix: 解决反向代理 1Panel 并启用 auth_basic 的时候无法登录的问题 (#1083)
Refs https://github.com/1Panel-dev/1Panel/issues/1082
2023-05-19 04:21:06 +00:00
xin.yang
4fca9aeb48 修复首页推荐应用处在窄窗口时显示异常的问题 2023-05-18 19:54:38 +08:00
zhengkunwang223
205e32dde8 feat: 创建运行环境逻辑修改 (#1079) 2023-05-18 11:08:20 +00:00
zhengkunwang223
d065558865 feat: 增加创建本地应用默认文件指令 (#1076) 2023-05-18 11:06:23 +00:00
ssongliu
950c6b9d08 feat: 容器增加日志切割 (#1077) 2023-05-18 10:38:19 +00:00
zhengkunwang223
d20d5946f2 feat: 优化应用升级逻辑 (#1075) 2023-05-18 08:48:19 +00:00
ssongliu
72bc99bddc fix: docker 状态判断逻辑修改 (#1073) 2023-05-18 16:46:13 +08:00
ssongliu
8c4016792b feat: 增加 ssh 相关接口文档 2023-05-18 16:45:54 +08:00
ssongliu
2975cf6d5a feat: sshd 支持重启、停止等操作,增加状态条 2023-05-18 16:45:54 +08:00
ssongliu
73d93d6104 feat: 面板设置中冗余系统日志 2023-05-18 16:45:54 +08:00
ssongliu
7452cc19e0 feat: 配置修改后重启,适配 SELinux 策略 2023-05-18 16:45:54 +08:00
ssongliu
efd545882f feat: 增加 ssh 登录日志功能 2023-05-18 16:45:54 +08:00
ssongliu
b19cdd9339 fix: 生成 ssh 密钥加密文件 2023-05-18 16:45:54 +08:00
ssongliu
da54794aca feat: 完成 ssh 配置前端界面实现 2023-05-18 16:45:54 +08:00
zhengkunwang223
a8df6d0668 feat: 升级 distribution 版本到 v2.8.2 (#1067) 2023-05-17 15:08:09 +00:00
zhengkunwang223
1f65d2243e feat: 创建一键部署网站和运行环境增加高级设置 (#1064) 2023-05-17 13:48:09 +00:00
zhengkunwang223
36db30471c feat: 编辑应用增加高级设置 (#1062) 2023-05-17 07:46:29 +00:00
zhengkunwang223
80e22ffc82 feat: 创建应用增加高级设置 (#1060) 2023-05-17 05:46:28 +00:00
zhengkunwang223
872581fa4b feat: 修改本地应用同步逻辑 2023-05-16 17:45:19 +08:00
zhengkunwang223
aeabed70db feat: 应用商店对接远程应用服务 2023-05-16 17:45:19 +08:00
zhengkunwang223
d443103e2c style: 修改应用商店样式 2023-05-16 17:45:19 +08:00
ssongliu
c1332235a0 fix: 右侧抽屉宽度调整 (#1047) 2023-05-15 02:36:47 +00:00
ssongliu
212c8634ea fix: 容器配置界面样式调整 (#1045) 2023-05-15 02:22:47 +00:00
zhengkunwang223
b966ab3e11 fix: 解决创建数据库超时问题 (#1037) 2023-05-14 01:34:31 +00:00
zhengkunwang223
25c2246782 fix: 解决默认网站开启 https 443 端口没有设置 default_server 的问题 (#1034) 2023-05-14 01:04:31 +00:00
ssongliu
2ae1db4730 fix: 解决 ssl 启用和端口修改跳转问题 (#1030) 2023-05-13 12:20:30 +00:00
ssongliu
43652b2a54 fix: 解决数据库上传备份恢复失败的问题 (#1027) 2023-05-13 07:32:28 +00:00
ssongliu
574a504ec3 fix: 增加初始化逻辑 (#1026) 2023-05-13 04:20:29 +00:00
zhengkunwang223
52a8331c78 fix: 解决网站设置 https 情况下,设置默认网站失败的问题 (#1023) 2023-05-12 15:08:28 +00:00
ssongliu
afa9eecf35 fix: 补全 swagger 文档 (#1022) 2023-05-12 22:30:50 +08:00
ssongliu
46e13d754f fix: 解决计划任务未勾选同步到本地造成的备份恢复问题 (#1021) 2023-05-12 22:30:26 +08:00
zhengkunwang223
0157326d61 fix: 限制应用失败情况下启动应用 (#1020) 2023-05-12 22:30:02 +08:00
ssongliu
7d08875f95 fix: 解决数据库用户指定多个 ip 无效的问题 (#1019) 2023-05-12 17:48:31 +08:00
zhengkunwang223
bd2a49e299 fix: 解决网站设置切换到 HTTPS 控制台报错的问题 (#1016)
fix https://github.com/1Panel-dev/1Panel/issues/672
2023-05-12 07:08:23 +00:00
ssongliu
06f1d03b93 fix: 端口校验正则表达式修改 (#1009) 2023-05-12 03:36:23 +00:00
Mystery0 M
4c39955f2f fix: 获取网站日志时,先检查一下日志文件大小,超过10M时直接报错 (#1011)
#### What this PR does / why we need it?

修复网站日志文件较大时,在界面上查看日志会导致系统短时间卡住的问题

相关Issue https://github.com/1Panel-dev/1Panel/issues/495
该提交可优化这个Issue的问题

#### Summary of your change

在获取文件内容之前,通过os.Stat获取文件的信息,并对文件大小进行判断,如果过大(目前是>10MB),则抛出错误中止流程

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

- [ ] Made sure tests are passing and test coverage is added if needed.
- [x] 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.
2023-05-12 02:54:23 +00:00
maninhill
56f92310f8 Update README.md (#1005) 2023-05-11 23:27:01 +08:00
maninhill
cb47806fd6 Update README.md 2023-05-11 21:28:57 +08:00
maninhill
16ae193e9f Update README.md 2023-05-11 15:33:17 +08:00
fossabot
8770939328 Add license scan report and status
Signed off by: fossabot <badges@fossa.com>
2023-05-11 15:33:17 +08:00
ssongliu
d5bff6473e fix: 解决安全入口跳转失败的问题 (#995) 2023-05-10 15:28:18 +00:00
ssongliu
a1c0408aae fix: 解决安全入口情况下升级后页面无法打开的问题 (#994) 2023-05-10 15:16:48 +00:00
520 changed files with 39661 additions and 17767 deletions

34
.github/workflows/build-test.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
on:
pull_request:
branches:
- dev
push:
branches:
- dev
name: Build Test
jobs:
build-linux-binary:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18.14'
- name: Build Web
id: build_frontend
run: |
cd frontend && npm install && npm run build:pro
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.20.x'
- name: Build Server
uses: goreleaser/goreleaser-action@v4
with:
args: release --snapshot --clean

66
.github/workflows/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Create Release And Upload assets
jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18.14'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.20.x'
- name: Build Release
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: release --skip-publish --clean
- name: Upload Assets
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
body: |
# 一、安装和升级
## 1.1 一键安装
```sh
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
```
## 1.2 在线升级
登录 1Panel Web 控制台,在页面右下角点击 **【检查更新】** 进行在线升级。
>更多信息请查阅在线文档https://1panel.cn/docs/
# 二、更新日志
files: |
dist/*.tar.gz
dist/checksums.txt
- name: Setup OSSUTIL
uses: yizhoumo/setup-ossutil@v1
with:
endpoint: ${{ secrets.OSS_ENDPOINT }}
access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
ossutil-version: '1.7.14'
- name: Upload Assets to OSS
run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force

12
.gitignore vendored
View File

@@ -23,3 +23,15 @@ cmd/server/web/assets
cmd/server/web/monacoeditorwork
cmd/server/web/index.html
frontend/auto-imports.d.ts
frontend/components.d.ts
.history/
dist/
1pctl
1panel.service
install.sh
quick_start.sh
cmd/server/web/.DS_Store
cmd/server/.DS_Store
cmd/server/fileList.txt
.fileList.txt

58
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,58 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# - export NODE_OPTIONS="--max-old-space-size=8192"
# - make build_web
- chmod +x ./script.sh
- ./script.sh
- sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl
- go mod tidy
builds:
- main: ./cmd/server/main.go
binary: 1panel
flags:
- -trimpath
ldflags:
- -w -s
env:
- CGO_ENABLED=0
goos:
- linux
goarm:
- 7
goarch:
- amd64
- arm64
- arm
- ppc64le
- s390x
archives:
- format: tar.gz
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
wrap_in_directory: true
files:
- 1pctl
- 1panel.service
- install.sh
- README.md
- LICENSE
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
release:
draft: true
mode: append
extra_files:
- glob: dist/*.tar.gz
- glob: dist/checksums.txt
name_template: "Release {{.Tag}}"
# The lines beneath this are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3"
}

View File

@@ -10,20 +10,25 @@ WEB_PATH=$(BASE_PAH)/frontend
SERVER_PATH=$(BASE_PAH)/backend
MAIN= $(BASE_PAH)/cmd/server/main.go
APP_NAME=1panel
ASSERT_PATH= $(BASE_PAH)/cmd/server/web/assets
clean_assets:
rm -rf $(ASSERT_PATH)
upx_bin:
upx $(BUILD_PATH)/$(APP_NAME)
build_frontend:
cd $(WEB_PATH) && npm install && npm run build:dev
cd $(WEB_PATH) && npm install && npm run build:pro
build_backend_on_linux:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags 'osusergo,netgo' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_darwin:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_archlinux:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_frontend build_backend_on_linux
build_all: build_frontend build_backend_on_linux
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin

View File

@@ -6,6 +6,7 @@
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel.svg?type=shield" alt="FOSSA Status"></a>
</p>
------------------------------
@@ -58,6 +59,10 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date)
## 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.

View File

@@ -5,6 +5,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/gin-gonic/gin"
)
@@ -39,6 +40,15 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"}
func (b *BaseApi) SyncApp(c *gin.Context) {
go appService.SyncAppListFromLocal()
res, err := appService.GetAppUpdate()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if !res.CanUpdate {
helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsUpToDate"))
return
}
go func() {
global.LOG.Infof("sync app list start ...")
if err := appService.SyncAppListFromRemote(); err != nil {
@@ -120,6 +130,22 @@ func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
helper.SuccessWithData(c, appDetailDTO)
}
// @Tags App
// @Summary Get Ignore App
// @Description 获取忽略的应用版本
// @Accept json
// @Success 200 {object} response.IgnoredApp
// @Security ApiKeyAuth
// @Router /apps/ingored [get]
func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
res, err := appService.GetIgnoredApp()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags App
// @Summary Install app
// @Description 安装应用
@@ -128,7 +154,7 @@ func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
// @Success 200 {object} model.AppInstall
// @Security ApiKeyAuth
// @Router /apps/install [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"name","input_value":"name","isList":false,"db":"app_installs","output_colume":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_colume":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"}
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[{"input_column":"name","input_value":"name","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"}
func (b *BaseApi) InstallApp(c *gin.Context) {
var req request.AppInstallCreate
if err := c.ShouldBindJSON(&req); err != nil {

View File

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

View File

@@ -103,7 +103,12 @@ func (b *BaseApi) Captcha(c *gin.Context) {
// @Router /auth/issafety [get]
func (b *BaseApi) CheckIsSafety(c *gin.Context) {
code := c.DefaultQuery("code", "")
helper.SuccessWithData(c, authService.CheckIsSafety(code))
status, err := authService.CheckIsSafety(code)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, status)
}
// @Tags Auth
@@ -115,6 +120,20 @@ func (b *BaseApi) CheckIsDemo(c *gin.Context) {
helper.SuccessWithData(c, global.CONF.System.IsDemo)
}
// @Tags Auth
// @Summary Load System Language
// @Description 获取系统语言设置
// @Success 200
// @Router /auth/language [get]
func (b *BaseApi) GetLanguage(c *gin.Context) {
settingInfo, err := settingService.GetSettingInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, settingInfo.Language)
}
func saveLoginLogs(c *gin.Context, err error) {
var logs model.LoginLog
if err != nil {

View File

@@ -2,6 +2,8 @@ package v1
import (
"encoding/base64"
"fmt"
"path"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -58,7 +60,7 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
// @Description 获取 bucket 列表
// @Accept json
// @Param request body dto.ForBuckets true "request"
// @Success 200 {anrry} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /settings/backup/search [post]
func (b *BaseApi) ListBuckets(c *gin.Context) {
@@ -96,6 +98,22 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
helper.SuccessWithData(c, buckets)
}
// @Tags Backup Account
// @Summary Load OneDrive info
// @Description 获取 OneDrive 信息
// @Accept json
// @Success 200 string clientID
// @Security ApiKeyAuth
// @Router /settings/backup/onedrive [get]
func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
clientID, err := backupService.LoadOneDriveInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, clientID)
}
// @Tags Backup Account
// @Summary Delete backup account
// @Description 删除备份账号
@@ -104,7 +122,7 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":true,"db":"backup_accounts","output_colume":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":true,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
func (b *BaseApi) DeleteBackup(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
@@ -186,7 +204,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/backup/record/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"backup_records","output_colume":"file_name","output_value":"files"}],"formatZH":"删除备份记录 [files]","formatEN":"delete backup records [files]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"backup_records","output_column":"file_name","output_value":"files"}],"formatZH":"删除备份记录 [files]","formatEN":"delete backup records [files]"}
func (b *BaseApi) DeleteBackupRecord(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {
@@ -251,7 +269,7 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
// @Tags Backup Account
// @Summary List backup accounts
// @Description 获取备份账号列表
// @Success 200 {anrry} dto.BackupInfo
// @Success 200 {array} dto.BackupInfo
// @Security ApiKeyAuth
// @Router /settings/backup/search [get]
func (b *BaseApi) ListBackup(c *gin.Context) {
@@ -269,7 +287,7 @@ func (b *BaseApi) ListBackup(c *gin.Context) {
// @Description 获取备份账号内文件列表
// @Accept json
// @Param request body dto.BackupSearchFile true "request"
// @Success 200 {anrry} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /settings/backup/search/files [post]
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
@@ -356,6 +374,14 @@ 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
}
switch req.Type {
case "mysql":
if err := backupService.MysqlRecover(req); err != nil {

View File

@@ -85,7 +85,7 @@ func (b *BaseApi) ListCommand(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/command/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"commands","output_colume":"name","output_value":"names"}],"formatZH":"删除快捷命令 [names]","formatEN":"delete quick command [names]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"commands","output_column":"name","output_value":"names"}],"formatZH":"删除快捷命令 [names]","formatEN":"delete quick command [names]"}
func (b *BaseApi) DeleteCommand(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -66,7 +66,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) {
// @Summary List compose templates
// @Description 获取容器编排模版列表
// @Produce json
// @Success 200 {anrry} dto.ComposeTemplateInfo
// @Success 200 {array} dto.ComposeTemplateInfo
// @Security ApiKeyAuth
// @Router /containers/template [get]
func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
@@ -87,7 +87,7 @@ func (b *BaseApi) ListComposeTemplate(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/template/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"compose_templates","output_colume":"name","output_value":"names"}],"formatZH":"删除 compose 模版 [names]","formatEN":"delete compose template [names]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"compose_templates","output_column":"name","output_value":"names"}],"formatZH":"删除 compose 模版 [names]","formatEN":"delete compose template [names]"}
func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {
@@ -114,7 +114,7 @@ func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/template/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"compose_templates","output_colume":"name","output_value":"name"}],"formatZH":"更新 compose 模版 [name]","formatEN":"update compose template information [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"compose_templates","output_column":"name","output_value":"name"}],"formatZH":"更新 compose 模版 [name]","formatEN":"update compose template information [name]"}
func (b *BaseApi) UpdateComposeTemplate(c *gin.Context) {
var req dto.ComposeTemplateUpdate
if err := c.ShouldBindJSON(&req); err != nil {

View File

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

View File

@@ -94,6 +94,28 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
})
}
// @Tags Cronjob
// @Summary Load Cronjob record log
// @Description 获取计划任务记录日志
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjob/record/log [post]
func (b *BaseApi) LoadRecordLog(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := cronjobService.LoadRecordLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Cronjob
// @Summary Clean job records
// @Description 清空计划任务记录
@@ -102,7 +124,7 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/records/clean [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"清空计划任务记录 [name]","formatEN":"clean cronjob [name] records"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"清空计划任务记录 [name]","formatEN":"clean cronjob [name] records"}
func (b *BaseApi) CleanRecord(c *gin.Context) {
var req dto.CronjobClean
if err := c.ShouldBindJSON(&req); err != nil {
@@ -126,7 +148,7 @@ func (b *BaseApi) CleanRecord(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"cronjobs","output_colume":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"cronjobs","output_column":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"}
func (b *BaseApi) DeleteCronjob(c *gin.Context) {
var req dto.CronjobBatchDelete
if err := c.ShouldBindJSON(&req); err != nil {
@@ -153,7 +175,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"}
func (b *BaseApi) UpdateCronjob(c *gin.Context) {
var req dto.CronjobUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -180,7 +202,7 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/status [post]
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"修改计划任务 [name] 状态为 [status]","formatEN":"change the status of cronjob [name] to [status]."}
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"修改计划任务 [name] 状态为 [status]","formatEN":"change the status of cronjob [name] to [status]."}
func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) {
var req dto.CronjobUpdateStatus
if err := c.ShouldBindJSON(&req); err != nil {
@@ -207,7 +229,7 @@ func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/download [post]
// @x-panel-log {"bodyKeys":["recordID"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"recordID","isList":false,"db":"job_records","output_colume":"file","output_value":"file"}],"formatZH":"下载计划任务记录 [file]","formatEN":"download the cronjob record [file]"}
// @x-panel-log {"bodyKeys":["recordID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"recordID","isList":false,"db":"job_records","output_column":"file","output_value":"file"}],"formatZH":"下载计划任务记录 [file]","formatEN":"download the cronjob record [file]"}
func (b *BaseApi) TargetDownload(c *gin.Context) {
var req dto.CronjobDownload
if err := c.ShouldBindJSON(&req); err != nil {
@@ -224,7 +246,8 @@ func (b *BaseApi) TargetDownload(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, filePath)
c.File(filePath)
}
// @Tags Cronjob
@@ -235,7 +258,7 @@ func (b *BaseApi) TargetDownload(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/handle [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"手动执行计划任务 [name]","formatEN":"manually execute the cronjob [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"手动执行计划任务 [name]","formatEN":"manually execute the cronjob [name]"}
func (b *BaseApi) HandleOnce(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -54,7 +54,7 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/description/update [post]
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"}
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"}
func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) {
var req dto.UpdateDescription
if err := c.ShouldBindJSON(&req); err != nil {
@@ -80,7 +80,7 @@ func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/change/password [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"}
func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
var req dto.ChangeDBInfo
if err := c.ShouldBindJSON(&req); err != nil {
@@ -115,7 +115,7 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/change/access [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"}
func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
var req dto.ChangeDBInfo
if err := c.ShouldBindJSON(&req); err != nil {
@@ -188,12 +188,12 @@ func (b *BaseApi) UpdateMysqlConfByFile(c *gin.Context) {
// @Summary Page mysql databases
// @Description 获取 mysql 数据库列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Param request body dto.MysqlDBSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/search [post]
func (b *BaseApi) SearchMysql(c *gin.Context) {
var req dto.SearchWithPage
var req dto.MysqlDBSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@@ -216,11 +216,11 @@ func (b *BaseApi) SearchMysql(c *gin.Context) {
// @Description 获取 mysql 数据库列表
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200 {anrry} string
// @Success 200 {array} dto.MysqlOption
// @Security ApiKeyAuth
// @Router /databases/options [get]
func (b *BaseApi) ListDBName(c *gin.Context) {
list, err := mysqlService.ListDBName()
list, err := mysqlService.ListDBOption()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -229,12 +229,31 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
helper.SuccessWithData(c, list)
}
// @Tags Database Mysql
// @Summary Load mysql database from remote
// @Description 从服务器获取
// @Security ApiKeyAuth
// @Router /databases/load/:from [get]
func (b *BaseApi) LoadDBFromRemote(c *gin.Context) {
from, err := helper.GetStrParamByKey(c, "from")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := mysqlService.LoadFromRemote(from); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Check before delete mysql database
// @Description Mysql 数据库删除前检查
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200 {anrry} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /databases/del/check [post]
func (b *BaseApi) DeleteCheckMysql(c *gin.Context) {
@@ -264,7 +283,7 @@ func (b *BaseApi) DeleteCheckMysql(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"删除 mysql 数据库 [name]","formatEN":"delete mysql database [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"删除 mysql 数据库 [name]","formatEN":"delete mysql database [name]"}
func (b *BaseApi) DeleteMysql(c *gin.Context) {
var req dto.MysqlDBDelete
if err := c.ShouldBindJSON(&req); err != nil {
@@ -302,6 +321,28 @@ func (b *BaseApi) LoadBaseinfo(c *gin.Context) {
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 := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
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 远程访问权限

View File

@@ -58,13 +58,13 @@ func (b *BaseApi) LoadDaemonJson(c *gin.Context) {
// @Summary Update docker daemon.json
// @Description 修改 docker 配置信息
// @Accept json
// @Param request body dto.DaemonJsonConf true "request"
// @Param request body dto.SettingUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/daemonjson/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 配置","formatEN":"Updated the docker daemon.json configuration"}
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 配置 [key]=>[value]","formatEN":"Updated the docker daemon.json configuration [key]=>[value]"}
func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
var req dto.DaemonJsonConf
var req dto.SettingUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@@ -78,6 +78,30 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Container Docker
// @Summary Update docker daemon.json log option
// @Description 修改 docker 日志配置
// @Accept json
// @Param request body dto.LogOption true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/daemonjson/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 日志配置","formatEN":"Updated the docker daemon.json log option"}
func (b *BaseApi) UpdateLogOption(c *gin.Context) {
var req dto.LogOption
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := dockerService.UpdateLogOption(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Container Docker
// @Summary Update docker daemon.json by upload file
// @Description 上传替换 docker 配置文件

View File

@@ -21,14 +21,16 @@ var (
imageService = service.NewIImageService()
dockerService = service.NewIDockerService()
mysqlService = service.NewIMysqlService()
redisService = service.NewIRedisService()
mysqlService = service.NewIMysqlService()
remoteDBService = service.NewIRemoteDBService()
redisService = service.NewIRedisService()
cronjobService = service.NewICronjobService()
hostService = service.NewIHostService()
groupService = service.NewIGroupService()
fileService = service.NewIFileService()
hostService = service.NewIHostService()
groupService = service.NewIGroupService()
fileService = service.NewIFileService()
sshService = service.NewISSHService()
firewallService = service.NewIFirewallService()
settingService = service.NewISettingService()
@@ -48,4 +50,7 @@ var (
upgradeService = service.NewIUpgradeService()
runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService()
hostToolService = service.NewIHostToolService()
)

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
@@ -51,7 +52,7 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
// @Description 分页获取上传文件
// @Accept json
// @Param request body request.SearchUploadWithPage true "request"
// @Success 200 {anrry} response.FileInfo
// @Success 200 {array} response.FileInfo
// @Security ApiKeyAuth
// @Router /files/upload/search [post]
func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
@@ -80,7 +81,7 @@ func (b *BaseApi) SearchUploadWithPage(c *gin.Context) {
// @Description 加载文件树
// @Accept json
// @Param request body request.FileOption true "request"
// @Success 200 {anrry} response.FileTree
// @Success 200 {array} response.FileTree
// @Security ApiKeyAuth
// @Router /files/tree [post]
func (b *BaseApi) GetFileTree(c *gin.Context) {
@@ -454,39 +455,93 @@ func (b *BaseApi) MoveFile(c *gin.Context) {
// @Router /files/download [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"}
func (b *BaseApi) Download(c *gin.Context) {
var req request.FileDownload
filePath := c.Query("path")
file, err := os.Open(filePath)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
}
info, _ := file.Stat()
c.Header("Content-Length", strconv.FormatInt(info.Size(), 10))
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
}
// @Tags File
// @Summary Chunk Download file
// @Description 分片下载下载文件
// @Accept json
// @Param request body request.FileDownload true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/chunkdownload [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"}
func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
var req request.FileChunkDownload
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
filePath, err := fileService.FileDownload(req)
fileOp := files.NewFileOp()
if !fileOp.Stat(req.Path) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrPathNotFound, nil)
return
}
filePath := req.Path
fstFile, err := fileOp.OpenFile(filePath)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.File(filePath)
}
info, err := fstFile.Stat()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if info.IsDir() {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileDownloadDir, err)
return
}
// @Tags File
// @Summary Download file with path
// @Description 下载指定文件
// @Accept json
// @Param request body dto.FilePath true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/download/bypath [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [path]","formatEN":"Download file [path]"}
func (b *BaseApi) DownloadFile(c *gin.Context) {
var req dto.FilePath
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", req.Name))
c.Writer.Header().Set("Content-Type", "application/octet-stream")
c.Writer.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10))
c.Writer.Header().Set("Accept-Ranges", "bytes")
if c.Request.Header.Get("Range") != "" {
rangeHeader := c.Request.Header.Get("Range")
rangeArr := strings.Split(rangeHeader, "=")[1]
rangeParts := strings.Split(rangeArr, "-")
startPos, _ := strconv.ParseInt(rangeParts[0], 10, 64)
var endPos int64
if rangeParts[1] == "" {
endPos = info.Size() - 1
} else {
endPos, _ = strconv.ParseInt(rangeParts[1], 10, 64)
}
c.Writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, info.Size()))
c.Writer.WriteHeader(http.StatusPartialContent)
buffer := make([]byte, 1024*1024)
file, err := os.Open(filePath)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
defer file.Close()
_, _ = file.Seek(startPos, 0)
reader := io.LimitReader(file, endPos-startPos+1)
_, err = io.CopyBuffer(c.Writer, reader, buffer)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
} else {
c.File(filePath)
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
c.File(req.Path)
}
// @Tags File
@@ -512,33 +567,6 @@ func (b *BaseApi) Size(c *gin.Context) {
helper.SuccessWithData(c, res)
}
// @Tags File
// @Summary Read file
// @Description 读取文件
// @Accept json
// @Param request body dto.FilePath true "request"
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Router /files/loadfile [post]
func (b *BaseApi) LoadFromFile(c *gin.Context) {
var req dto.FilePath
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := os.ReadFile(req.Path)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, string(content))
}
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
if _, err := os.Stat(path.Dir(dstDir)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(dstDir), os.ModePerm); err != nil {
@@ -575,6 +603,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int)
// @Security ApiKeyAuth
// @Router /files/chunkupload [post]
func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
var err error
fileForm, err := c.FormFile("chunk")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
@@ -585,19 +614,16 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
chunkCount, err := strconv.Atoi(c.PostForm("chunkCount"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
fileOp := files.NewFileOp()
tmpDir := path.Join(global.CONF.System.TmpDir, "upload")
if !fileOp.Stat(tmpDir) {
@@ -606,37 +632,50 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
return
}
}
filename := c.PostForm("filename")
fileDir := filepath.Join(tmpDir, filename)
_ = os.MkdirAll(fileDir, 0755)
if chunkIndex == 0 {
if fileOp.Stat(fileDir) {
_ = fileOp.DeleteDir(fileDir)
}
_ = os.MkdirAll(fileDir, 0755)
}
filePath := filepath.Join(fileDir, filename)
emptyFile, err := os.Create(filePath)
defer func() {
if err != nil {
_ = os.Remove(fileDir)
}
}()
var (
emptyFile *os.File
chunkData []byte
)
emptyFile, err = os.Create(filePath)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
defer emptyFile.Close()
chunkData, err := io.ReadAll(uploadFile)
chunkData, err = io.ReadAll(uploadFile)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrFileUpload, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err))
return
}
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
err = os.WriteFile(chunkPath, chunkData, 0644)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileUpload, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err))
return
}
if chunkIndex+1 == chunkCount {
err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileUpload, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err))
return
}
helper.SuccessWithData(c, true)
@@ -651,19 +690,12 @@ var wsUpgrade = websocket.Upgrader{
},
}
var WsManager = websocket2.Manager{
Group: make(map[string]*websocket2.Client),
Register: make(chan *websocket2.Client, 128),
UnRegister: make(chan *websocket2.Client, 128),
ClientCount: 0,
}
func (b *BaseApi) Ws(c *gin.Context) {
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
wsClient := websocket2.NewWsClient("wsClient", ws)
wsClient := websocket2.NewWsClient("fileClient", ws)
go wsClient.Read()
go wsClient.Write()
}

View File

@@ -138,7 +138,7 @@ func (b *BaseApi) OperateIPRule(c *gin.Context) {
// @Param request body dto.BatchRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/ip [post]
// @Router /hosts/firewall/batch [post]
func (b *BaseApi) BatchOperateRule(c *gin.Context) {
var req dto.BatchRuleOperate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -149,7 +149,7 @@ func (b *BaseApi) BatchOperateRule(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := firewallService.BacthOperateRule(req); err != nil {
if err := firewallService.BatchOperateRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@@ -188,7 +188,7 @@ func (b *BaseApi) UpdatePortRule(c *gin.Context) {
// @Param request body dto.AddrRuleUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/update/ip [post]
// @Router /hosts/firewall/update/addr [post]
func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
var req dto.AddrRuleUpdate
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -42,7 +42,7 @@ func (b *BaseApi) CreateGroup(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /groups/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"groups","output_colume":"name","output_value":"name"},{"input_colume":"id","input_value":"id","isList":false,"db":"groups","output_colume":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"}
func (b *BaseApi) DeleteGroup(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
@@ -92,7 +92,7 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) {
// @Description 查询系统组
// @Accept json
// @Param request body dto.GroupSearch true "request"
// @Success 200 {anrry} dto.GroupInfo
// @Success 200 {array} dto.GroupInfo
// @Security ApiKeyAuth
// @Router /groups/search [post]
func (b *BaseApi) ListGroup(c *gin.Context) {

View File

@@ -1,13 +1,11 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/gin-gonic/gin"
)
@@ -30,22 +28,6 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMode == "password" && 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 req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PrivateKey = string(privateKey)
}
host, err := hostService.Create(req)
if err != nil {
@@ -102,7 +84,7 @@ func (b *BaseApi) TestByID(c *gin.Context) {
// @Description 加载主机树
// @Accept json
// @Param request body dto.SearchForTree true "request"
// @Success 200 {anrry} dto.HostTree
// @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth
// @Router /hosts/tree [post]
func (b *BaseApi) HostTree(c *gin.Context) {
@@ -126,7 +108,7 @@ func (b *BaseApi) HostTree(c *gin.Context) {
// @Description 获取主机列表分页
// @Accept json
// @Param request body dto.SearchHostWithPage true "request"
// @Success 200 {anrry} dto.HostTree
// @Success 200 {array} dto.HostTree
// @Security ApiKeyAuth
// @Router /hosts/search [post]
func (b *BaseApi) SearchHost(c *gin.Context) {
@@ -148,33 +130,6 @@ func (b *BaseApi) SearchHost(c *gin.Context) {
})
}
// @Tags Host
// @Summary Load host info
// @Description 加载主机信息
// @Accept json
// @Param id path integer true "request"
// @Success 200 {object} dto.HostInfo
// @Security ApiKeyAuth
// @Router /hosts/:id [get]
func (b *BaseApi) GetHostInfo(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
host, err := hostService.GetHostInfo(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
var hostDto dto.HostInfo
if err := copier.Copy(&hostDto, host); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, hostDto)
}
// @Tags Host
// @Summary Delete host
// @Description 删除主机
@@ -183,7 +138,7 @@ func (b *BaseApi) GetHostInfo(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"hosts","output_colume":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"}
func (b *BaseApi) DeleteHost(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {
@@ -221,21 +176,30 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
var err error
if len(req.Password) != 0 && req.AuthMode == "password" {
req.Password, err = hostService.EncryptHost(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = string(password)
req.PrivateKey = ""
req.PassPhrase = ""
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PrivateKey = string(privateKey)
if len(req.PassPhrase) != 0 {
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
}
req.Password = ""
}
upMap := make(map[string]interface{})
@@ -246,10 +210,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
upMap["user"] = req.User
upMap["auth_mode"] = req.AuthMode
upMap["remember_password"] = req.RememberPassword
if len(req.Password) != 0 {
if req.AuthMode == "password" {
upMap["password"] = req.Password
}
if len(req.PrivateKey) != 0 {
upMap["private_key"] = ""
upMap["pass_phrase"] = ""
} else {
upMap["password"] = ""
upMap["private_key"] = req.PrivateKey
upMap["pass_phrase"] = req.PassPhrase
}
@@ -269,7 +235,7 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/update/group [post]
// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"hosts","output_colume":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"}
// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"}
func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
var req dto.ChangeHostGroup
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -0,0 +1,213 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
// @Tags Host tool
// @Summary Get tool
// @Description 获取主机工具状态
// @Accept json
// @Param request body request.HostToolReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool [post]
func (b *BaseApi) GetToolStatus(c *gin.Context) {
var req request.HostToolReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
config, err := hostToolService.GetToolStatus(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, config)
}
// @Tags Host tool
// @Summary Create Host tool Config
// @Description 创建主机工具配置
// @Accept json
// @Param request body request.HostToolCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/create [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
func (b *BaseApi) InitToolConfig(c *gin.Context) {
var req request.HostToolCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := hostToolService.CreateToolConfig(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host tool
// @Summary Operate tool
// @Description 操作主机工具
// @Accept json
// @Param request body request.HostToolReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/operate [post]
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
func (b *BaseApi) OperateTool(c *gin.Context) {
var req request.HostToolReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := hostToolService.OperateTool(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host tool
// @Summary Get tool config
// @Description 操作主机工具配置文件
// @Accept json
// @Param request body request.HostToolConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/config [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
var req request.HostToolConfig
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
config, err := hostToolService.OperateToolConfig(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, config)
}
// @Tags Host tool
// @Summary Get tool
// @Description 获取主机工具日志
// @Accept json
// @Param request body request.HostToolLogReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/log [post]
func (b *BaseApi) GetToolLog(c *gin.Context) {
var req request.HostToolLogReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
logContent, err := hostToolService.GetToolLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, logContent)
}
// @Tags Host tool
// @Summary Create Supervisor process
// @Description 操作守护进程
// @Accept json
// @Param request body request.SupervisorProcessConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
func (b *BaseApi) OperateProcess(c *gin.Context) {
var req request.SupervisorProcessConfig
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := hostToolService.OperateSupervisorProcess(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host tool
// @Summary Get Supervisor process config
// @Description 获取 Supervisor 进程配置
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process [get]
func (b *BaseApi) GetProcess(c *gin.Context) {
configs, err := hostToolService.GetSupervisorProcessConfig()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, configs)
}
// @Tags Host tool
// @Summary Get Supervisor process config
// @Description 操作 Supervisor 进程文件
// @Accept json
// @Param request body request.SupervisorProcessFileReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process/file [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
func (b *BaseApi) GetProcessFile(c *gin.Context) {
var req request.SupervisorProcessFileReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := hostToolService.OperateSupervisorProcessFile(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}

View File

@@ -44,7 +44,7 @@ func (b *BaseApi) SearchImage(c *gin.Context) {
// @Summary List images
// @Description 获取镜像列表
// @Produce json
// @Success 200 {anrry} dto.Options
// @Success 200 {array} dto.Options
// @Security ApiKeyAuth
// @Router /containers/image [get]
func (b *BaseApi) ListImage(c *gin.Context) {
@@ -93,7 +93,7 @@ func (b *BaseApi) ImageBuild(c *gin.Context) {
// @Success 200 {string} log
// @Security ApiKeyAuth
// @Router /containers/image/pull [post]
// @x-panel-log {"bodyKeys":["repoID","imageName"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"repoID","isList":false,"db":"image_repos","output_colume":"name","output_value":"reponame"}],"formatZH":"镜像拉取 [reponame][imageName]","formatEN":"image pull [reponame][imageName]"}
// @x-panel-log {"bodyKeys":["repoID","imageName"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"镜像拉取 [reponame][imageName]","formatEN":"image pull [reponame][imageName]"}
func (b *BaseApi) ImagePull(c *gin.Context) {
var req dto.ImagePull
if err := c.ShouldBindJSON(&req); err != nil {
@@ -122,7 +122,7 @@ func (b *BaseApi) ImagePull(c *gin.Context) {
// @Success 200 {string} log
// @Security ApiKeyAuth
// @Router /containers/image/push [post]
// @x-panel-log {"bodyKeys":["repoID","tagName","name"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"repoID","isList":false,"db":"image_repos","output_colume":"name","output_value":"reponame"}],"formatZH":"[tagName] 推送到 [reponame][name]","formatEN":"push [tagName] to [reponame][name]"}
// @x-panel-log {"bodyKeys":["repoID","tagName","name"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"[tagName] 推送到 [reponame][name]","formatEN":"push [tagName] to [reponame][name]"}
func (b *BaseApi) ImagePush(c *gin.Context) {
var req dto.ImagePush
if err := c.ShouldBindJSON(&req); err != nil {
@@ -207,7 +207,7 @@ func (b *BaseApi) ImageSave(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/image/tag [post]
// @x-panel-log {"bodyKeys":["repoID","targetName"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"repoID","isList":false,"db":"image_repos","output_colume":"name","output_value":"reponame"}],"formatZH":"tag 镜像 [reponame][targetName]","formatEN":"tag image [reponame][targetName]"}
// @x-panel-log {"bodyKeys":["repoID","targetName"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"tag 镜像 [reponame][targetName]","formatEN":"tag image [reponame][targetName]"}
func (b *BaseApi) ImageTag(c *gin.Context) {
var req dto.ImageTag
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -44,7 +44,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) {
// @Summary List image repos
// @Description 获取镜像仓库列表
// @Produce json
// @Success 200 {anrry} dto.ImageRepoOption
// @Success 200 {array} dto.ImageRepoOption
// @Security ApiKeyAuth
// @Router /containers/repo [get]
func (b *BaseApi) ListRepo(c *gin.Context) {
@@ -119,7 +119,7 @@ func (b *BaseApi) CreateRepo(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/repo/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"image_repos","output_colume":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"image_repos","output_column":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"}
func (b *BaseApi) DeleteRepo(c *gin.Context) {
var req dto.ImageRepoDelete
if err := c.ShouldBindJSON(&req); err != nil {
@@ -147,7 +147,7 @@ func (b *BaseApi) DeleteRepo(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/repo/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_colume":"name","output_value":"name"}],"formatZH":"更新镜像仓库 [name]","formatEN":"update image repo information [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"更新镜像仓库 [name]","formatEN":"update image repo information [name]"}
func (b *BaseApi) UpdateRepo(c *gin.Context) {
var req dto.ImageRepoUpdate
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -89,3 +89,19 @@ func (b *BaseApi) CleanLogs(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Logs
// @Summary Load system logs
// @Description 获取系统日志
// @Success 200
// @Security ApiKeyAuth
// @Router /logs/system [get]
func (b *BaseApi) GetSystemLogs(c *gin.Context) {
data, err := logService.LoadSystemLog()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}

View File

@@ -27,7 +27,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) {
// @Description 获取部分 OpenResty 配置信息
// @Accept json
// @Param request body request.NginxScopeReq true "request"
// @Success 200 {anrry} response.NginxParam
// @Success 200 {array} response.NginxParam
// @Security ApiKeyAuth
// @Router /openResty/scope [post]
func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
@@ -53,7 +53,7 @@ func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /openResty/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新 nginx 配置 [domain]","formatEN":"Update nginx conf [domain]"}
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"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
if err := c.ShouldBindJSON(&req); err != nil {

View File

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

View File

@@ -0,0 +1,180 @@
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/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
// @Tags Database
// @Summary Create remote database
// @Description 创建远程数据库
// @Accept json
// @Param request body dto.RemoteDBCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/remote [post]
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建远程数据库 [name][type]","formatEN":"create remote database [name][type]"}
func (b *BaseApi) CreateRemoteDB(c *gin.Context) {
var req dto.RemoteDBCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := remoteDBService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database
// @Summary Check remote database
// @Description 检测远程数据库连接性
// @Accept json
// @Param request body dto.RemoteDBCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/remote/check [post]
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"检测远程数据库 [name][type] 连接性","formatEN":"check if remote database [name][type] is connectable"}
func (b *BaseApi) CheckeRemoteDB(c *gin.Context) {
var req dto.RemoteDBCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
helper.SuccessWithData(c, remoteDBService.CheckeRemoteDB(req))
}
// @Tags Database
// @Summary Page remote databases
// @Description 获取远程数据库列表分页
// @Accept json
// @Param request body dto.RemoteDBSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/remote/search [post]
func (b *BaseApi) SearchRemoteDB(c *gin.Context) {
var req dto.RemoteDBSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := remoteDBService.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
// @Summary List remote databases
// @Description 获取远程数据库列表
// @Success 200 {array} dto.RemoteDBOption
// @Security ApiKeyAuth
// @Router /databases/remote/list/:type [get]
func (b *BaseApi) ListRemoteDB(c *gin.Context) {
dbType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
list, err := remoteDBService.List(dbType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Database
// @Summary Get remote databases
// @Description 获取远程数据库
// @Success 200 {object} dto.RemoteDBInfo
// @Security ApiKeyAuth
// @Router /databases/remote/:name [get]
func (b *BaseApi) GetRemoteDB(c *gin.Context) {
name, err := helper.GetStrParamByKey(c, "name")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := remoteDBService.Get(name)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Database
// @Summary Delete remote database
// @Description 删除远程数据库
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/remote/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"databases","output_column":"name","output_value":"names"}],"formatZH":"删除远程数据库 [names]","formatEN":"delete remote database [names]"}
func (b *BaseApi) DeleteRemoteDB(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := remoteDBService.Delete(req.ID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database
// @Summary Update remote database
// @Description 更新远程数据库
// @Accept json
// @Param request body dto.RemoteDBUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/remote/update [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新远程数据库 [name]","formatEN":"update remote database [name]"}
func (b *BaseApi) UpdateRemoteDB(c *gin.Context) {
var req dto.RemoteDBUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := remoteDBService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@@ -2,14 +2,16 @@ package v1
import (
"errors"
"time"
"fmt"
"os"
"path"
"strconv"
"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/global"
"github.com/1Panel-dev/1Panel/backend/utils/mfa"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/gin-gonic/gin"
)
@@ -134,6 +136,22 @@ func (b *BaseApi) LoadFromCert(c *gin.Context) {
helper.SuccessWithData(c, info)
}
// @Tags System Setting
// @Summary Download system cert
// @Description 下载证书
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/ssl/download [post]
func (b *BaseApi) DownloadSSL(c *gin.Context) {
pathItem := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
if _, err := os.Stat(pathItem); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.File(pathItem)
}
// @Tags System Setting
// @Summary Update system port
// @Description 更新系统端口
@@ -189,26 +207,40 @@ func (b *BaseApi) HandlePasswordExpired(c *gin.Context) {
}
// @Tags System Setting
// @Summary Sync system time
// @Description 系统时间同步
// @Success 200 {string} ntime
// @Summary Load time zone options
// @Description 加载系统可用时区
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/time/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步","formatEN":"sync system time"}
func (b *BaseApi) SyncTime(c *gin.Context) {
ntime, err := ntp.GetRemoteTime()
// @Router /settings/time/option [get]
func (b *BaseApi) LoadTimeZone(c *gin.Context) {
zones, err := settingService.LoadTimeZone()
if err != nil {
helper.SuccessWithData(c, time.Now().Format("2006-01-02 15:04:05 MST -0700"))
return
}
ts := ntime.Format("2006-01-02 15:04:05")
if err := ntp.UpdateSystemDate(ts); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, zones)
}
helper.SuccessWithData(c, ntime.Format("2006-01-02 15:04:05 MST -0700"))
// @Tags System Setting
// @Summary Sync system time
// @Description 系统时间同步
// @Accept json
// @Param request body dto.SyncTime true "request"
// @Success 200 {string} ntime
// @Security ApiKeyAuth
// @Router /settings/time/sync [post]
// @x-panel-log {"bodyKeys":["ntpSite"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步[ntpSite]","formatEN":"sync system time [ntpSite]"}
func (b *BaseApi) SyncTime(c *gin.Context) {
var req dto.SyncTime
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := settingService.SyncTime(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
@@ -248,11 +280,23 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) {
// @Tags System Setting
// @Summary Load mfa info
// @Description 获取 mfa 信息
// @Param interval path string true "request"
// @Success 200 {object} mfa.Otp
// @Security ApiKeyAuth
// @Router /settings/mfa [get]
// @Router /settings/mfa/:interval [get]
func (b *BaseApi) GetMFA(c *gin.Context) {
otp, err := mfa.GetOtp("admin")
intervalStr, ok := c.Params.Get("interval")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error interval in path"))
return
}
interval, err := strconv.Atoi(intervalStr)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("type conversion failed, err: %v", err))
return
}
otp, err := mfa.GetOtp("admin", interval)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -276,12 +320,17 @@ func (b *BaseApi) MFABind(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
success := mfa.ValidCode(req.Code, req.Secret)
success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
if !success {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid"))
return
}
if err := settingService.Update("MFAInterval", req.Interval); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err := settingService.Update("MFAStatus", "enable"); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

View File

@@ -68,7 +68,7 @@ func (b *BaseApi) ImportSnapshot(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/description/update [post]
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"快照 [name] 描述信息修改 [description]","formatEN":"The description of the snapshot [name] is modified => [description]"}
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"快照 [name] 描述信息修改 [description]","formatEN":"The description of the snapshot [name] is modified => [description]"}
func (b *BaseApi) UpdateSnapDescription(c *gin.Context) {
var req dto.UpdateDescription
if err := c.ShouldBindJSON(&req); err != nil {
@@ -119,7 +119,7 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/recover [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 恢复","formatEN":"Recover from system backup [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 恢复","formatEN":"Recover from system backup [name]"}
func (b *BaseApi) RecoverSnapshot(c *gin.Context) {
var req dto.SnapshotRecover
if err := c.ShouldBindJSON(&req); err != nil {
@@ -146,7 +146,7 @@ func (b *BaseApi) RecoverSnapshot(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/rollback [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 回滚","formatEN":"Rollback from system backup [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 回滚","formatEN":"Rollback from system backup [name]"}
func (b *BaseApi) RollbackSnapshot(c *gin.Context) {
var req dto.SnapshotRecover
if err := c.ShouldBindJSON(&req); err != nil {
@@ -173,7 +173,7 @@ func (b *BaseApi) RollbackSnapshot(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"删除系统快照 [name]","formatEN":"Delete system backup [name]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"删除系统快照 [name]","formatEN":"Delete system backup [name]"}
func (b *BaseApi) DeleteSnapshot(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {

200
backend/app/api/v1/ssh.go Normal file
View File

@@ -0,0 +1,200 @@
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/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
// @Tags SSH
// @Summary Load host ssh setting info
// @Description 加载 SSH 配置信息
// @Success 200 {object} dto.SSHInfo
// @Security ApiKeyAuth
// @Router /host/ssh/search [post]
func (b *BaseApi) GetSSHInfo(c *gin.Context) {
info, err := sshService.GetSSHInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, info)
}
// @Tags SSH
// @Summary Operate ssh
// @Description 修改 SSH 服务状态
// @Accept json
// @Param request body dto.Operate true "request"
// @Security ApiKeyAuth
// @Router /host/ssh/operate [post]
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"}
func (b *BaseApi) OperateSSH(c *gin.Context) {
var req dto.Operate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := sshService.OperateSSH(req.Operation); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags SSH
// @Summary Update host ssh setting
// @Description 更新 SSH 配置
// @Accept json
// @Param request body dto.SettingUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/ssh/update [post]
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"}
func (b *BaseApi) UpdateSSH(c *gin.Context) {
var req dto.SettingUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := sshService.Update(req.Key, req.Value); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags SSH
// @Summary Update host ssh setting by file
// @Description 上传文件更新 SSH 配置
// @Accept json
// @Param request body dto.SSHConf true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/conffile/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
var req dto.SSHConf
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := sshService.UpdateByFile(req.File); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags SSH
// @Summary Generate host ssh secret
// @Description 生成 ssh 密钥
// @Accept json
// @Param request body dto.GenerateSSH true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/ssh/generate [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
func (b *BaseApi) GenerateSSH(c *gin.Context) {
var req dto.GenerateSSH
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := sshService.GenerateSSH(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags SSH
// @Summary Load host ssh secret
// @Description 获取 ssh 密钥
// @Accept json
// @Param request body dto.GenerateLoad true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/ssh/secret [post]
func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
var req dto.GenerateLoad
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := sshService.LoadSSHSecret(req.EncryptionMode)
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 登录日志
// @Accept json
// @Param request body dto.SearchSSHLog true "request"
// @Success 200 {object} dto.SSHLog
// @Security ApiKeyAuth
// @Router /host/ssh/logs [post]
func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
var req dto.SearchSSHLog
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := sshService.LoadLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags SSH
// @Summary Load host ssh conf
// @Description 获取 ssh 配置文件
// @Success 200
// @Security ApiKeyAuth
// @Router /host/ssh/conf [get]
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
data, err := sshService.LoadSSHConf()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}

View File

@@ -5,12 +5,10 @@ import (
"encoding/json"
"fmt"
"net/http"
"os/exec"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/copier"
@@ -22,24 +20,27 @@ import (
)
func (b *BaseApi) WsSsh(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
id, err := strconv.Atoi(c.Query("id"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
return
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
host, err := hostService.GetHostInfo(uint(id))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
return
}
var connInfo ssh.ConnInfo
@@ -49,24 +50,12 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
client, err := connInfo.NewClient()
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
}
defer client.Close()
ssConn, err := connInfo.NewSshConn(cols, rows)
if wshandleError(wsConn, err) {
return
}
defer ssConn.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
if wshandleError(wsConn, err) {
return
@@ -85,38 +74,36 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
}
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
redisConf, err := redisService.LoadConf()
if err != nil {
global.LOG.Errorf("load redis container failed, err: %v", err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
redisConf, err := redisService.LoadConf()
if wshandleError(wsConn, errors.WithMessage(err, "load redis container failed")) {
return
}
defer wsConn.Close()
commands := "redis-cli"
if len(redisConf.Requirepass) != 0 {
commands = fmt.Sprintf("redis-cli -a %s --no-auth-warning", redisConf.Requirepass)
}
pidMap := loadMapFromDockerTop(redisConf.ContainerName)
slave, err := terminal.NewCommand(fmt.Sprintf("docker exec -it %s %s", redisConf.ContainerName, commands))
if wshandleError(wsConn, err) {
return
}
defer killBash(redisConf.ContainerName, commands)
defer killBash(redisConf.ContainerName, commands, pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
@@ -137,24 +124,6 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
}
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
containerID := c.Query("containerid")
command := c.Query("command")
user := c.Query("user")
if len(command) == 0 || len(containerID) == 0 {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error param of command or containerID"))
return
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
@@ -162,11 +131,33 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
}
defer wsConn.Close()
cmds := fmt.Sprintf("docker exec %s %s", containerID, command)
if len(user) != 0 {
cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command)
containerID := c.Query("containerid")
command := c.Query("command")
user := c.Query("user")
if len(command) == 0 || len(containerID) == 0 {
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
return
}
}
stdout, err := cmd.Exec(cmds)
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
cmds := []string{"exec", containerID, command}
if len(user) != 0 {
cmds = []string{"exec", "-u", user, containerID, command}
}
if cmd.CheckIllegal(user, containerID, command) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
}
stdout, err := cmd.ExecWithCheck("docker", cmds...)
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
return
}
@@ -175,11 +166,12 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
if len(user) != 0 {
commands = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command)
}
pidMap := loadMapFromDockerTop(containerID)
slave, err := terminal.NewCommand(commands)
if wshandleError(wsConn, err) {
return
}
defer killBash(containerID, command)
defer killBash(containerID, command, pidMap)
defer slave.Close()
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave)
@@ -219,13 +211,40 @@ func wshandleError(ws *websocket.Conn, err error) bool {
return false
}
func killBash(containerID, comm string) {
sudo := ""
if cmd.HasNoPasswordSudo() {
sudo = "sudo"
func loadMapFromDockerTop(containerID string) map[string]string {
pidMap := make(map[string]string)
sudo := cmd.SudoHandleCmd()
stdout, err := cmd.Execf("%s docker top %s -eo pid,command ", sudo, containerID)
if err != nil {
return pidMap
}
lines := strings.Split(stdout, "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) != 2 {
continue
}
pidMap[parts[0]] = parts[1]
}
return pidMap
}
func killBash(containerID, comm string, pidMap map[string]string) {
sudo := cmd.SudoHandleCmd()
newPidMap := loadMapFromDockerTop(containerID)
for pid, command := range newPidMap {
isOld := false
for pid2 := range pidMap {
if pid == pid2 {
isOld = true
break
}
}
if !isOld && command == comm {
_, _ = cmd.Execf("%s kill -9 %s", sudo, pid)
}
}
command := exec.Command("sh", "-c", fmt.Sprintf("%s kill -9 $(docker top %s -eo pid,command | grep '%s' | awk '{print $1}')", sudo, containerID, comm))
_, _ = command.CombinedOutput()
}
var upGrader = websocket.Upgrader{

View File

@@ -36,7 +36,7 @@ func (b *BaseApi) PageWebsite(c *gin.Context) {
// @Tags Website
// @Summary List websites
// @Description 获取网站列表
// @Success 200 {anrry} response.WebsiteDTO
// @Success 200 {array} response.WebsiteDTO
// @Security ApiKeyAuth
// @Router /websites/list [get]
func (b *BaseApi) GetWebsites(c *gin.Context) {
@@ -51,7 +51,7 @@ func (b *BaseApi) GetWebsites(c *gin.Context) {
// @Tags Website
// @Summary List website names
// @Description 获取网站列表
// @Success 200 {anrry} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /websites/options [get]
func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
@@ -95,7 +95,7 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/operate [post]
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[operate] 网站 [domain]","formatEN":"[operate] website [domain]"}
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[operate] 网站 [domain]","formatEN":"[operate] website [domain]"}
func (b *BaseApi) OpWebsite(c *gin.Context) {
var req request.WebsiteOp
if err := c.ShouldBindJSON(&req); err != nil {
@@ -118,7 +118,7 @@ func (b *BaseApi) OpWebsite(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain]","formatEN":"Delete website [domain]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain]","formatEN":"Delete website [domain]"}
func (b *BaseApi) DeleteWebsite(c *gin.Context) {
var req request.WebsiteDelete
if err := c.ShouldBindJSON(&req); err != nil {
@@ -207,7 +207,7 @@ func (b *BaseApi) GetWebsiteNginx(c *gin.Context) {
// @Description 通过网站 id 查询域名
// @Accept json
// @Param websiteId path integer true "request"
// @Success 200 {anrry} model.WebsiteDomain
// @Success 200 {array} model.WebsiteDomain
// @Security ApiKeyAuth
// @Router /websites/domains/:websiteId [get]
func (b *BaseApi) GetWebDomains(c *gin.Context) {
@@ -232,7 +232,7 @@ func (b *BaseApi) GetWebDomains(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/domains/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_domains","output_colume":"domain","output_value":"domain"}],"formatZH":"删除域名 [domain]","formatEN":"Delete domain [domain]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_domains","output_column":"domain","output_value":"domain"}],"formatZH":"删除域名 [domain]","formatEN":"Delete domain [domain]"}
func (b *BaseApi) DeleteWebDomain(c *gin.Context) {
var req request.WebsiteDomainDelete
if err := c.ShouldBindJSON(&req); err != nil {
@@ -300,7 +300,7 @@ func (b *BaseApi) GetNginxConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/config/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"nginx 配置修改 [domain]","formatEN":"Nginx conf update [domain]"}
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"nginx 配置修改 [domain]","formatEN":"Nginx conf update [domain]"}
func (b *BaseApi) UpdateNginxConfig(c *gin.Context) {
var req request.NginxConfigUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -344,7 +344,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) {
// @Success 200 {object} response.WebsiteHTTPS
// @Security ApiKeyAuth
// @Router /websites/:id/https [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"}
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"}
func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) {
var req request.WebsiteHTTPSOp
if err := c.ShouldBindJSON(&req); err != nil {
@@ -367,7 +367,7 @@ func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) {
// @Description 网站创建前检查
// @Accept json
// @Param request body request.WebsiteInstallCheckReq true "request"
// @Success 200 {anrry} request.WebsitePreInstallCheck
// @Success 200 {array} response.WebsitePreInstallCheck
// @Security ApiKeyAuth
// @Router /websites/check [post]
func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
@@ -414,7 +414,7 @@ func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/waf/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置修改 [domain]","formatEN":"WAF conf update [domain]"}
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"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 := c.ShouldBindJSON(&req); err != nil {
@@ -428,6 +428,28 @@ func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
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":[],"BeforeFuntions":[{"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 := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
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 配置
@@ -436,7 +458,7 @@ func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/nginx/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[domain] Nginx 配置修改","formatEN":"[domain] Nginx conf update"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] Nginx 配置修改","formatEN":"[domain] Nginx conf update"}
func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) {
var req request.WebsiteNginxUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -458,7 +480,7 @@ func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) {
// @Success 200 {object} response.WebsiteLog
// @Security ApiKeyAuth
// @Router /websites/log [post]
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[domain][operate] 日志","formatEN":"[domain][operate] logs"}
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain][operate] 日志","formatEN":"[domain][operate] logs"}
func (b *BaseApi) OpWebsiteLog(c *gin.Context) {
var req request.WebsiteLogReq
if err := c.ShouldBindJSON(&req); err != nil {
@@ -481,7 +503,7 @@ func (b *BaseApi) OpWebsiteLog(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/default/server [post]
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"修改默认 server => [domain]","formatEN":"Change default server => [domain]"}
// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改默认 server => [domain]","formatEN":"Change default server => [domain]"}
func (b *BaseApi) ChangeDefaultServer(c *gin.Context) {
var req request.WebsiteDefaultUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -525,7 +547,7 @@ func (b *BaseApi) GetWebsitePHPConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/php/config [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[domain] PHP 配置修改","formatEN":"[domain] PHP conf update"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] PHP 配置修改","formatEN":"[domain] PHP conf update"}
func (b *BaseApi) UpdateWebsitePHPConfig(c *gin.Context) {
var req request.WebsitePHPConfigUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -541,13 +563,13 @@ func (b *BaseApi) UpdateWebsitePHPConfig(c *gin.Context) {
// @Tags Website PHP
// @Summary Update php conf
// @Description 更新 php 配置
// @Description 更新 php 配置文件
// @Accept json
// @Param request body request.WebsitePHPFileUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/php/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"php 配置修改 [domain]","formatEN":"Nginx conf update [domain]"}
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 配置修改 [domain]","formatEN":"Nginx conf update [domain]"}
func (b *BaseApi) UpdatePHPFile(c *gin.Context) {
var req request.WebsitePHPFileUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -561,6 +583,28 @@ func (b *BaseApi) UpdatePHPFile(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Website PHP
// @Summary Update php version
// @Description 变更 php 版本
// @Accept json
// @Param request body request.WebsitePHPVersionReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/php/version [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 版本变更 [domain]","formatEN":"php version update [domain]"}
func (b *BaseApi) ChangePHPVersion(c *gin.Context) {
var req request.WebsitePHPVersionReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.ChangePHPVersion(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get rewrite conf
// @Description 获取伪静态配置
@@ -591,7 +635,7 @@ func (b *BaseApi) GetRewriteConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/rewrite/update [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteID","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"伪静态配置修改 [domain]","formatEN":"Nginx conf rewrite update [domain]"}
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"伪静态配置修改 [domain]","formatEN":"Nginx conf rewrite update [domain]"}
func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) {
var req request.NginxRewriteUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -613,7 +657,7 @@ func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dir/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录","formatEN":"Update domain [domain] dir"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录","formatEN":"Update domain [domain] dir"}
func (b *BaseApi) UpdateSiteDir(c *gin.Context) {
var req request.WebsiteUpdateDir
if err := c.ShouldBindJSON(&req); err != nil {
@@ -635,7 +679,7 @@ func (b *BaseApi) UpdateSiteDir(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dir/permission [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录权限","formatEN":"Update domain [domain] dir permission"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录权限","formatEN":"Update domain [domain] dir permission"}
func (b *BaseApi) UpdateSiteDirPermission(c *gin.Context) {
var req request.WebsiteUpdateDirPermission
if err := c.ShouldBindJSON(&req); err != nil {
@@ -679,7 +723,7 @@ func (b *BaseApi) GetProxyConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/proxies/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 ","formatEN":"Update domain [domain] proxy config"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 ","formatEN":"Update domain [domain] proxy config"}
func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
var req request.WebsiteProxyConfig
if err := c.ShouldBindJSON(&req); err != nil {
@@ -702,7 +746,7 @@ func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/proxy/file [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteID","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"}
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"}
func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) {
var req request.NginxProxyUpdate
if err := c.ShouldBindJSON(&req); err != nil {
@@ -758,3 +802,113 @@ func (b *BaseApi) UpdateAuthConfig(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get AntiLeech conf
// @Description 获取防盗链配置
// @Accept json
// @Param request body request.NginxCommonReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/leech [post]
func (b *BaseApi) GetAntiLeech(c *gin.Context) {
var req request.NginxCommonReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.GetAntiLeech(req.WebsiteID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Update AntiLeech
// @Description 更新防盗链配置
// @Accept json
// @Param request body request.NginxAntiLeechUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/leech/update [post]
func (b *BaseApi) UpdateAntiLeech(c *gin.Context) {
var req request.NginxAntiLeechUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateAntiLeech(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Update redirect conf
// @Description 修改重定向配置
// @Accept json
// @Param request body request.NginxRedirectReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/redirect/update [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 重定向理配置 ","formatEN":"Update domain [domain] redirect config"}
func (b *BaseApi) UpdateRedirectConfig(c *gin.Context) {
var req request.NginxRedirectReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := websiteService.OperateRedirect(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get redirect conf
// @Description 获取重定向配置
// @Accept json
// @Param request body request.WebsiteProxyReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/redirect [post]
func (b *BaseApi) GetRedirectConfig(c *gin.Context) {
var req request.WebsiteRedirectReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.GetRedirect(req.WebsiteID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Update redirect file
// @Description 更新重定向文件
// @Accept json
// @Param request body request.NginxRedirectUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/redirect/file [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新重定向文件 [domain]","formatEN":"Nginx conf redirect file update [domain]"}
func (b *BaseApi) UpdateRedirectConfigFile(c *gin.Context) {
var req request.NginxRedirectUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateRedirectFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@@ -64,7 +64,7 @@ func (b *BaseApi) CreateWebsiteAcmeAccount(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/acme/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_acme_accounts","output_colume":"email","output_value":"email"}],"formatZH":"删除网站 acme [email]","formatEN":"Delete website acme [email]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_acme_accounts","output_column":"email","output_value":"email"}],"formatZH":"删除网站 acme [email]","formatEN":"Delete website acme [email]"}
func (b *BaseApi) DeleteWebsiteAcmeAccount(c *gin.Context) {
var req request.WebsiteResourceReq
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -85,7 +85,7 @@ func (b *BaseApi) UpdateWebsiteDnsAccount(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dns/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_dns_accounts","output_colume":"name","output_value":"name"}],"formatZH":"删除网站 dns [name]","formatEN":"Delete website dns [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_dns_accounts","output_column":"name","output_value":"name"}],"formatZH":"删除网站 dns [name]","formatEN":"Delete website dns [name]"}
func (b *BaseApi) DeleteWebsiteDnsAccount(c *gin.Context) {
var req request.WebsiteResourceReq
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -35,7 +35,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
Items: accounts,
})
} else {
list, err := websiteSSLService.Search()
list, err := websiteSSLService.Search(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -75,7 +75,7 @@ func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ssl/renew [post]
// @x-panel-log {"bodyKeys":["SSLId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"SSLId","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"重置 ssl [domain]","formatEN":"Renew ssl [domain]"}
// @x-panel-log {"bodyKeys":["SSLId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"SSLId","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"重置 ssl [domain]","formatEN":"Renew ssl [domain]"}
func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) {
var req request.WebsiteSSLRenew
if err := c.ShouldBindJSON(&req); err != nil {
@@ -94,7 +94,7 @@ func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) {
// @Description 解析网站 ssl
// @Accept json
// @Param request body request.WebsiteDNSReq true "request"
// @Success 200 {anrry} response.WebsiteDNSRes
// @Success 200 {array} response.WebsiteDNSRes
// @Security ApiKeyAuth
// @Router /websites/ssl/resolve [post]
func (b *BaseApi) GetDNSResolve(c *gin.Context) {
@@ -119,7 +119,7 @@ func (b *BaseApi) GetDNSResolve(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ssl/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"删除 ssl [domain]","formatEN":"Delete ssl [domain]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除 ssl [domain]","formatEN":"Delete ssl [domain]"}
func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) {
var req request.WebsiteResourceReq
if err := c.ShouldBindJSON(&req); err != nil {
@@ -185,7 +185,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ssl/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新证书设置 [domain]","formatEN":"Update ssl config [domain]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新证书设置 [domain]","formatEN":"Update ssl config [domain]"}
func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) {
var req request.WebsiteSSLUpdate
if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -1,7 +1,7 @@
package dto
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type AppDatabase struct {
@@ -15,6 +15,10 @@ type AuthParam struct {
RootPassword string `json:"PANEL_DB_ROOT_PASSWORD"`
}
type RedisAuthParam struct {
RootPassword string `json:"PANEL_REDIS_ROOT_PASSWORD"`
}
type ContainerExec struct {
ContainerName string `json:"containerName"`
DbParam AppDatabase `json:"dbParam"`
@@ -32,19 +36,47 @@ type AppVersion struct {
}
type AppList struct {
Version string `json:"version"`
Tags []Tag `json:"tags"`
Items []AppDefine `json:"items"`
Valid bool `json:"valid"`
Violations []string `json:"violations"`
LastModified int `json:"lastModified"`
Apps []AppDefine `json:"apps"`
Extra ExtraProperties `json:"additionalProperties"`
}
type AppDefine struct {
Key string `json:"key"`
Icon string `json:"icon"`
Name string `json:"name"`
ReadMe string `json:"readMe"`
LastModified int `json:"lastModified"`
AppProperty AppProperty `json:"additionalProperties"`
Versions []AppConfigVersion `json:"versions"`
}
type LocalAppAppDefine struct {
AppProperty model.App `json:"additionalProperties" yaml:"additionalProperties"`
}
type LocalAppParam struct {
AppParams LocalAppInstallDefine `json:"additionalProperties" yaml:"additionalProperties"`
}
type LocalAppInstallDefine struct {
FormFields interface{} `json:"formFields" yaml:"formFields"`
}
type ExtraProperties struct {
Tags []Tag `json:"tags"`
}
type AppProperty struct {
Name string `json:"name"`
Type string `json:"type"`
Tags []string `json:"tags"`
Versions []string `json:"versions"`
ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"`
Type string `json:"type"`
Key string `json:"key"`
Required []string `json:"Required"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit"`
@@ -54,9 +86,12 @@ type AppDefine struct {
Document string `json:"document"`
}
func (define AppDefine) GetRequired() string {
by, _ := json.Marshal(define.Required)
return string(by)
type AppConfigVersion struct {
Name string `json:"name"`
LastModified int `json:"lastModified"`
DownloadUrl string `json:"downloadUrl"`
DownloadCallBackUrl string `json:"downloadCallBackUrl"`
AppForm interface{} `json:"additionalProperties"`
}
type Tag struct {
@@ -79,6 +114,7 @@ type AppFormFields struct {
Edit bool `json:"edit"`
Rule string `json:"rule"`
Multiple bool `json:"multiple"`
Child interface{} `json:"child"`
Values []AppFormValue `json:"values"`
}

View File

@@ -12,8 +12,9 @@ type UserLoginInfo struct {
}
type MfaCredential struct {
Secret string `json:"secret"`
Code string `json:"code"`
Secret string `json:"secret"`
Code string `json:"code"`
Interval string `json:"interval"`
}
type Login struct {
@@ -23,6 +24,7 @@ type Login struct {
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod"`
Language string `json:"language"`
}
type MFALogin struct {

View File

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

View File

@@ -2,7 +2,9 @@ package dto
type SearchWithPage struct {
PageInfo
Info string `json:"info"`
Info string `json:"info"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
}
type PageInfo struct {
@@ -23,6 +25,10 @@ type OperateByID struct {
ID uint `json:"id" validate:"required"`
}
type Operate struct {
Operation string `json:"operation" validate:"required"`
}
type BatchDeleteReq struct {
Ids []uint `json:"ids" validate:"required"`
}

View File

@@ -5,6 +5,8 @@ import "time"
type PageContainer struct {
PageInfo
Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
Filters string `json:"filters"`
}
@@ -22,22 +24,29 @@ type ContainerInfo struct {
State string `json:"state"`
RunTime string `json:"runTime"`
CPUPercent float64 `json:"cpuPercent"`
MemoryPercent float64 `json:"memoryPercent"`
Ports []string `json:"ports"`
Ports []string `json:"ports"`
IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"`
}
type ContainerCreate struct {
type ResourceLimit struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
}
type ContainerOperate struct {
ContainerID string `json:"containerID"`
ForcePull bool `json:"forcePull"`
Name string `json:"name"`
Image string `json:"image"`
Network string `json:"network"`
PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"`
Cmd []string `json:"cmd"`
NanoCPUs int64 `json:"nanoCPUs"`
Memory int64 `json:"memory"`
CPUShares int64 `json:"cpuShares"`
NanoCPUs float64 `json:"nanoCPUs"`
Memory float64 `json:"memory"`
AutoRemove bool `json:"autoRemove"`
Volumes []VolumeHelper `json:"volumes"`
Labels []string `json:"labels"`
@@ -45,7 +54,19 @@ type ContainerCreate struct {
RestartPolicy string `json:"restartPolicy"`
}
type ContainterStats struct {
type ContainerUpgrade struct {
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
ForcePull bool `json:"forcePull"`
}
type ContainerListStats struct {
ContainerID string `json:"containerID"`
CPUPercent float64 `json:"cpuPercent"`
MemoryPercent float64 `json:"memoryPercent"`
}
type ContainerStats struct {
CPUPercent float64 `json:"cpuPercent"`
Memory float64 `json:"memory"`
Cache float64 `json:"cache"`
@@ -69,17 +90,22 @@ type PortHelper struct {
Protocol string `json:"protocol"`
}
type ContainerLog struct {
ContainerID string `json:"containerID" validate:"required"`
Mode string `json:"mode" validate:"required"`
}
type ContainerOperation struct {
Name string `json:"name" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause rename remove"`
NewName string `json:"newName"`
}
type ContainerPrune struct {
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
WithTagAll bool `json:"withTagAll"`
}
type ContainerPruneReport struct {
DeletedNumber int `json:"deletedNumber"`
SpaceReclaimed int `json:"spaceReclaimed"`
}
type Network struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -91,7 +117,7 @@ type Network struct {
CreatedAt time.Time `json:"createdAt"`
Attachable bool `json:"attachable"`
}
type NetworkCreat struct {
type NetworkCreate struct {
Name string `json:"name"`
Driver string `json:"driver"`
Options []string `json:"options"`
@@ -108,7 +134,7 @@ type Volume struct {
Mountpoint string `json:"mountpoint"`
CreatedAt time.Time `json:"createdAt"`
}
type VolumeCreat struct {
type VolumeCreate struct {
Name string `json:"name"`
Driver string `json:"driver"`
Options []string `json:"options"`

View File

@@ -6,12 +6,14 @@ 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=7,min=1"`
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"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -26,12 +28,14 @@ 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=7,min=1"`
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"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -71,8 +75,10 @@ type CronjobInfo struct {
Day int `json:"day"`
Hour int `json:"hour"`
Minute int `json:"minute"`
Second int `json:"second"`
Script string `json:"script"`
ContainerName string `json:"containerName"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -83,7 +89,7 @@ type CronjobInfo struct {
TargetDirID int `json:"targetDirID"`
RetainCopies int `json:"retainCopies"`
LastRecrodTime string `json:"lastRecrodTime"`
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
}

View File

@@ -2,10 +2,20 @@ package dto
import "time"
type MysqlDBSearch struct {
PageInfo
Info string `json:"info"`
From string `json:"from"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
}
type MysqlDBInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
From string `json:"from"`
MysqlName string `json:"mysqlName"`
Format string `json:"format"`
Username string `json:"username"`
Password string `json:"password"`
@@ -14,8 +24,15 @@ type MysqlDBInfo struct {
Description string `json:"description"`
}
type MysqlOption struct {
ID uint `json:"id"`
Name string `json:"name"`
From string `json:"from"`
}
type MysqlDBCreate struct {
Name string `json:"name" validate:"required"`
From string `json:"from" validate:"required"`
Format string `json:"format" validate:"required,oneof=utf8mb4 utf8 gbk big5"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
@@ -100,6 +117,7 @@ type MysqlConfUpdateByFile struct {
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from"`
Value string `json:"value" validate:"required"`
}

View File

@@ -13,10 +13,16 @@ type DaemonJsonConf struct {
LiveRestore bool `json:"liveRestore"`
IPTables bool `json:"iptables"`
CgroupDriver string `json:"cgroupDriver"`
LogMaxSize string `json:"logMaxSize"`
LogMaxFile string `json:"logMaxFile"`
}
type LogOption struct {
LogMaxSize string `json:"logMaxSize"`
LogMaxFile string `json:"logMaxFile"`
}
type DockerOperation struct {
StopSocket bool `json:"stopSocket"`
StopService bool `json:"stopService"`
Operation string `json:"operation" validate:"required,oneof=start restart stop"`
Operation string `json:"operation" validate:"required,oneof=start restart stop"`
}

View File

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

View File

@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
Name string `json:"name" validate:"required"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Auth bool `json:"auth"`
}
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
ID uint `json:"id"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Auth bool `json:"auth"`
}

View File

@@ -0,0 +1,52 @@
package dto
import "time"
type RemoteDBSearch struct {
PageInfo
Info string `json:"info"`
Type string `json:"type"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
}
type RemoteDBInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name" validate:"max=256"`
From string `json:"from"`
Version string `json:"version"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
}
type RemoteDBOption struct {
ID uint `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
}
type RemoteDBCreate struct {
Name string `json:"name" validate:"required,max=256"`
Type string `json:"type" validate:"required,oneof=mysql"`
From string `json:"from" validate:"required,oneof=local remote"`
Version string `json:"version" validate:"required"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Description string `json:"description"`
}
type RemoteDBUpdate struct {
ID uint `json:"id"`
Version string `json:"version" validate:"required"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Description string `json:"description"`
}

View File

@@ -18,6 +18,18 @@ type AppInstallCreate struct {
Params map[string]interface{} `json:"params"`
Name string `json:"name" validate:"required"`
Services map[string]string `json:"services"`
AppContainerConfig
}
type AppContainerConfig struct {
Advanced bool `json:"advanced"`
CpuQuota float64 `json:"cpuQuota"`
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
EditCompose bool `json:"editCompose"`
DockerCompose string `json:"dockerCompose"`
}
type AppInstalledSearch struct {
@@ -46,11 +58,18 @@ type AppInstalledOperate struct {
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
DeleteDB bool `json:"deleteDB"`
Backup bool `json:"backup"`
}
type AppInstalledUpdate struct {
InstallId uint `json:"installId" validate:"required"`
Params map[string]interface{} `json:"params" validate:"required"`
AppContainerConfig
}
type AppInstalledIgnoreUpgrade struct {
DetailID uint `json:"detailID" validate:"required"`
Operate string `json:"operate" validate:"required,oneof=cancel ignore"`
}
type PortUpdate struct {

View File

@@ -82,6 +82,11 @@ type FileDownload struct {
Compress bool `json:"compress" validate:"required"`
}
type FileChunkDownload struct {
Path string `json:"path" validate:"required"`
Name string `json:"name" validate:"required"`
}
type DirSizeReq struct {
Path string `json:"path" validate:"required"`
}

View File

@@ -0,0 +1,41 @@
package request
type HostToolReq struct {
Type string `json:"type" validate:"required,oneof=supervisord"`
Operate string `json:"operate" validate:"oneof=status restart start stop"`
}
type HostToolCreate struct {
Type string `json:"type" validate:"required"`
SupervisorConfig
}
type SupervisorConfig struct {
ConfigPath string `json:"configPath"`
ServiceName string `json:"serviceName"`
}
type HostToolLogReq struct {
Type string `json:"type" validate:"required,oneof=supervisord"`
}
type HostToolConfig struct {
Type string `json:"type" validate:"required,oneof=supervisord"`
Operate string `json:"operate" validate:"oneof=get set"`
Content string `json:"content"`
}
type SupervisorProcessConfig struct {
Name string `json:"name"`
Operate string `json:"operate"`
Command string `json:"command"`
User string `json:"user"`
Dir string `json:"dir"`
Numprocs string `json:"numprocs"`
}
type SupervisorProcessFileReq struct {
Name string `json:"name" validate:"required"`
Operate string `json:"operate" validate:"required,oneof=get clear update" `
Content string `json:"content"`
File string `json:"file" validate:"required,oneof=out.log err.log config"`
}

View File

@@ -3,9 +3,8 @@ package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type NginxConfigFileUpdate struct {
Content string `json:"content" validate:"required"`
FilePath string `json:"filePath" validate:"required"`
Backup bool `json:"backup" validate:"required"`
Content string `json:"content" validate:"required"`
Backup bool `json:"backup" validate:"required"`
}
type NginxScopeReq struct {
@@ -48,3 +47,41 @@ type NginxAuthUpdate struct {
type NginxAuthReq struct {
WebsiteID uint `json:"websiteID" validate:"required"`
}
type NginxCommonReq struct {
WebsiteID uint `json:"websiteID" validate:"required"`
}
type NginxAntiLeechUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Extends string `json:"extends" validate:"required"`
Return string `json:"return" validate:"required"`
Enable bool `json:"enable" validate:"required"`
ServerNames []string `json:"serverNames"`
Cache bool `json:"cache"`
CacheTime int `json:"cacheTime"`
CacheUint string `json:"cacheUint"`
NoneRef bool `json:"noneRef"`
LogEnable bool `json:"logEnable"`
Blocked bool `json:"blocked"`
}
type NginxRedirectReq struct {
Name string `json:"name" validate:"required"`
WebsiteID uint `json:"websiteID" validate:"required"`
Domains []string `json:"domains"`
KeepPath bool `json:"keepPath" validate:"required"`
Enable bool `json:"enable" validate:"required"`
Type string `json:"type" validate:"required"`
Redirect string `json:"redirect" validate:"required"`
Path string `json:"path"`
Target string `json:"target" validate:"required"`
Operate string `json:"operate" validate:"required"`
RedirectRoot bool `json:"redirectRoot"`
}
type NginxRedirectUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Content string `json:"content" validate:"required"`
Name string `json:"name" validate:"required"`
}

View File

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

View File

@@ -7,6 +7,8 @@ import (
type WebsiteSearch struct {
dto.PageInfo
Name string `json:"name"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
WebsiteGroupID uint `json:"websiteGroupId"`
}
@@ -18,6 +20,7 @@ type WebsiteCreate struct {
OtherDomains string `json:"otherDomains"`
Proxy string `json:"proxy"`
WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"`
IPV6 bool `json:"IPV6"`
AppType string `json:"appType" validate:"oneof=new installed"`
AppInstall NewAppInstall `json:"appInstall"`
@@ -37,6 +40,8 @@ type NewAppInstall struct {
Name string `json:"name"`
AppDetailId uint `json:"appDetailID"`
Params map[string]interface{} `json:"params"`
AppContainerConfig
}
type WebsiteInstallCheckReq struct {
@@ -49,6 +54,7 @@ type WebsiteUpdate struct {
Remark string `json:"remark"`
WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"`
ExpireDate string `json:"expireDate"`
IPV6 bool `json:"IPV6"`
}
type WebsiteDelete struct {
@@ -69,6 +75,12 @@ type WebsiteWafReq struct {
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" validate:"required"`
}
type WebsiteWafUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
@@ -109,15 +121,18 @@ type WebsiteDomainDelete struct {
}
type WebsiteHTTPSOp struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
}
type WebsiteNginxUpdate struct {
@@ -136,8 +151,11 @@ type WebsiteDefaultUpdate struct {
}
type WebsitePHPConfigUpdate struct {
ID uint `json:"id" validate:"required"`
Params map[string]string `json:"params" validate:"required"`
ID uint `json:"id" validate:"required"`
Params map[string]string `json:"params"`
Scope string `json:"scope" validate:"required"`
DisableFunctions []string `json:"disableFunctions"`
UploadMaxSize string `json:"uploadMaxSize"`
}
type WebsitePHPFileUpdate struct {
@@ -146,6 +164,12 @@ type WebsitePHPFileUpdate struct {
Content string `json:"content" validate:"required"`
}
type WebsitePHPVersionReq struct {
WebsiteID uint `json:"websiteID" validate:"required"`
RuntimeID uint `json:"runtimeID" validate:"required"`
RetainConfig bool `json:"retainConfig" validate:"required"`
}
type WebsiteUpdateDir struct {
ID uint `json:"id" validate:"required"`
SiteDir string `json:"siteDir" validate:"required"`
@@ -177,3 +201,13 @@ type WebsiteProxyConfig struct {
type WebsiteProxyReq struct {
ID uint `json:"id" validate:"required"`
}
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"`
}

View File

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

View File

@@ -1,6 +1,7 @@
package response
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
@@ -12,15 +13,15 @@ type AppRes struct {
}
type AppUpdateRes struct {
Version string `json:"version"`
CanUpdate bool `json:"canUpdate"`
DownloadPath string `json:"downloadPath"`
CanUpdate bool `json:"canUpdate"`
AppStoreLastModified int `json:"appStoreLastModified"`
}
type AppDTO struct {
model.App
Versions []string `json:"versions"`
Tags []model.Tag `json:"tags"`
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Tags []model.Tag `json:"tags"`
}
type TagDTO struct {
@@ -47,6 +48,13 @@ type AppDetailDTO struct {
Image string `json:"image"`
}
type IgnoredApp struct {
Icon string `json:"icon"`
Name string `json:"name"`
Version string `json:"version"`
DetailID uint `json:"detailID"`
}
type AppInstalledDTO struct {
model.AppInstall
Total int `json:"total"`
@@ -54,6 +62,7 @@ type AppInstalledDTO struct {
AppName string `json:"appName"`
Icon string `json:"icon"`
CanUpdate bool `json:"canUpdate"`
Path string `json:"path"`
}
type DatabaseConn struct {
@@ -81,3 +90,8 @@ type AppParam struct {
Required bool `json:"required"`
Multiple bool `json:"multiple"`
}
type AppConfig struct {
Params []AppParam `json:"params"`
request.AppContainerConfig
}

View File

@@ -0,0 +1,41 @@
package response
type HostToolRes struct {
Type string `json:"type"`
Config interface{} `json:"config"`
}
type Supervisor struct {
ConfigPath string `json:"configPath"`
IncludeDir string `json:"includeDir"`
LogPath string `json:"logPath"`
IsExist bool `json:"isExist"`
Init bool `json:"init"`
Msg string `json:"msg"`
Version string `json:"version"`
Status string `json:"status"`
CtlExist bool `json:"ctlExist"`
ServiceName string `json:"serviceName"`
}
type HostToolConfig struct {
Content string `json:"content"`
}
type SupervisorProcessConfig struct {
Name string `json:"name"`
Command string `json:"command"`
User string `json:"user"`
Dir string `json:"dir"`
Numprocs string `json:"numprocs"`
Msg string `json:"msg"`
Status []ProcessStatus `json:"status"`
}
type ProcessStatus struct {
Name string `json:"name"`
Status string `json:"status"`
PID string `json:"PID"`
Uptime string `json:"uptime"`
Msg string `json:"msg"`
}

View File

@@ -21,3 +21,35 @@ type NginxAuthRes struct {
Enable bool `json:"enable"`
Items []dto.NginxAuth `json:"items"`
}
type NginxAntiLeechRes struct {
Enable bool `json:"enable"`
Extends string `json:"extends"`
Return string `json:"return"`
ServerNames []string `json:"serverNames"`
Cache bool `json:"cache"`
CacheTime int `json:"cacheTime"`
CacheUint string `json:"cacheUint"`
NoneRef bool `json:"noneRef"`
LogEnable bool `json:"logEnable"`
Blocked bool `json:"blocked"`
}
type NginxRedirectConfig struct {
WebsiteID uint `json:"websiteID"`
Name string `json:"name"`
Domains []string `json:"domains"`
KeepPath bool `json:"keepPath"`
Enable bool `json:"enable"`
Type string `json:"type"`
Redirect string `json:"redirect"`
Path string `json:"path"`
Target string `json:"target"`
FilePath string `json:"filePath"`
Content string `json:"content"`
RedirectRoot bool `json:"redirectRoot"`
}
type NginxFile struct {
Content string `json:"content"`
}

View File

@@ -26,9 +26,8 @@ type WebsiteNginxConfig struct {
}
type WebsiteWafConfig struct {
Enable bool `json:"enable"`
FilePath string `json:"filePath"`
Content string `json:"content"`
Enable bool `json:"enable"`
Content string `json:"content"`
}
type WebsiteHTTPS struct {
@@ -45,7 +44,9 @@ type WebsiteLog struct {
}
type PHPConfig struct {
Params map[string]string `json:"params"`
Params map[string]string `json:"params"`
DisableFunctions []string `json:"disableFunctions"`
UploadMaxSize string `json:"uploadMaxSize"`
}
type NginxRewriteRes struct {

View File

@@ -5,10 +5,13 @@ import "time"
type SettingInfo struct {
UserName string `json:"userName"`
Email string `json:"email"`
SystemIP string `json:"systemIP"`
SystemVersion string `json:"systemVersion"`
SessionTimeout string `json:"sessionTimeout"`
LocalTime string `json:"localTime"`
TimeZone string `json:"timeZone"`
NtpSite string `json:"ntpSite"`
Port string `json:"port"`
PanelName string `json:"panelName"`
@@ -18,14 +21,18 @@ type SettingInfo struct {
ServerPort string `json:"serverPort"`
SSL string `json:"ssl"`
SSLType string `json:"sslType"`
BindDomain string `json:"bindDomain"`
AllowIPs string `json:"allowIPs"`
SecurityEntrance string `json:"securityEntrance"`
ExpirationDays string `json:"expirationDays"`
ExpirationTime string `json:"expirationTime"`
ComplexityVerification string `json:"complexityVerification"`
MFAStatus string `json:"mfaStatus"`
MFASecret string `json:"mfaSecret"`
MFAInterval string `json:"mfaInterval"`
MonitorStatus string `json:"monitorStatus"`
MonitorInterval string `json:"monitorInterval"`
MonitorStoreDays string `json:"monitorStoreDays"`
MessageType string `json:"messageType"`
@@ -33,7 +40,8 @@ type SettingInfo struct {
WeChatVars string `json:"weChatVars"`
DingVars string `json:"dingVars"`
AppStoreVersion string `json:"appStoreVersion"`
AppStoreVersion string `json:"appStoreVersion"`
AppStoreLastModified string `json:"appStoreLastModified"`
}
type SettingUpdate struct {
@@ -68,7 +76,7 @@ type PortUpdate struct {
}
type SnapshotCreate struct {
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO"`
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"`
Description string `json:"description" validate:"max=256"`
}
type SnapshotRecover struct {
@@ -106,6 +114,10 @@ type UpgradeInfo struct {
ReleaseNote string `json:"releaseNote"`
}
type SyncTime struct {
NtpSite string `json:"ntpSite"`
}
type Upgrade struct {
Version string `json:"version"`
}

49
backend/app/dto/ssh.go Normal file
View File

@@ -0,0 +1,49 @@
package dto
import "time"
type SSHInfo struct {
Status string `json:"status"`
Message string `json:"message"`
Port string `json:"port"`
ListenAddress string `json:"listenAddress"`
PasswordAuthentication string `json:"passwordAuthentication"`
PubkeyAuthentication string `json:"pubkeyAuthentication"`
PermitRootLogin string `json:"permitRootLogin"`
UseDNS string `json:"useDNS"`
}
type GenerateSSH struct {
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
Password string `json:"password"`
}
type GenerateLoad struct {
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
}
type SSHConf struct {
File string `json:"file"`
}
type SearchSSHLog struct {
PageInfo
Info string `json:"info"`
Status string `json:"Status" validate:"required,oneof=Success Failed All"`
}
type SSHLog struct {
Logs []SSHHistory `json:"logs"`
TotalCount int `json:"totalCount"`
SuccessfulCount int `json:"successfulCount"`
FailedCount int `json:"failedCount"`
}
type SSHHistory struct {
Date time.Time `json:"date"`
DateStr string `json:"dateStr"`
Area string `json:"area"`
User string `json:"user"`
AuthMode string `json:"authMode"`
Address string `json:"address"`
Port string `json:"port"`
Status string `json:"status"`
Message string `json:"message"`
}

View File

@@ -2,22 +2,25 @@ package model
type App struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null"`
Key string `json:"key" gorm:"type:varchar(64);not null;uniqueIndex"`
ShortDescZh string `json:"shortDescZh" gorm:"type:longtext;"`
ShortDescEn string `json:"shortDescEn" gorm:"type:longtext;"`
Icon string `json:"icon" gorm:"type:longtext;"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
Required string `json:"required" gorm:"type:varchar(64);not null"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit" gorm:"type:Integer;not null"`
Website string `json:"website" gorm:"type:varchar(64);not null"`
Github string `json:"github" gorm:"type:varchar(64);not null"`
Document string `json:"document" gorm:"type:varchar(64);not null"`
Recommend int `json:"recommend" gorm:"type:Integer;not null"`
Resource string `json:"resource" gorm:"type:varchar;not null;default:remote"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"-" gorm:"-"`
AppTags []AppTag `json:"-" gorm:"-:migration"`
Name string `json:"name" gorm:"type:varchar(64);not null"`
Key string `json:"key" gorm:"type:varchar(64);not null;"`
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"`
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"`
Icon string `json:"icon" gorm:"type:longtext;"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
Required string `json:"required" gorm:"type:varchar(64);"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit" gorm:"type:Integer;not null"`
Website string `json:"website" gorm:"type:varchar(64);not null"`
Github string `json:"github" gorm:"type:varchar(64);not null"`
Document string `json:"document" gorm:"type:varchar(64);not null"`
Recommend int `json:"recommend" gorm:"type:Integer;not null"`
Resource string `json:"resource" gorm:"type:varchar;not null;default:remote"`
ReadMe string `json:"readMe" gorm:"type:varchar;"`
LastModified int `json:"lastModified" gorm:"type:Integer;"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"tags" yaml:"tags" gorm:"-"`
AppTags []AppTag `json:"-" gorm:"-:migration"`
}

View File

@@ -2,11 +2,15 @@ package model
type AppDetail struct {
BaseModel
AppId uint `json:"appId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Params string `json:"-" gorm:"type:longtext;"`
DockerCompose string `json:"-" gorm:"type:longtext;not null"`
Readme string `json:"readme" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
LastVersion string `json:"lastVersion" gorm:"type:varchar(64);"`
AppId uint `json:"appId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Params string `json:"-" gorm:"type:longtext;"`
DockerCompose string `json:"dockerCompose" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
LastVersion string `json:"lastVersion" gorm:"type:varchar(64);"`
LastModified int `json:"lastModified" gorm:"type:integer;"`
DownloadUrl string `json:"downloadUrl" gorm:"type:varchar;"`
DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"`
Update bool `json:"update"`
IgnoreUpgrade bool `json:"ignoreUpgrade"`
}

View File

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

View File

@@ -13,7 +13,9 @@ type Cronjob struct {
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"`
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
Script string `gorm:"longtext" json:"script"`
Website string `gorm:"type:varchar(64)" json:"website"`
DBName string `gorm:"type:varchar(64)" json:"dbName"`

View File

@@ -3,6 +3,7 @@ package model
type DatabaseMysql struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(256);not null"`
From string `json:"from" gorm:"type:varchar(256);not null;default:local"`
MysqlName string `json:"mysqlName" 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"`

View File

@@ -0,0 +1,14 @@
package model
type RemoteDB struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
From string `json:"from" gorm:"type:varchar(64);not null"`
Address string `json:"address" gorm:"type:varchar(64);not null"`
Port uint `json:"port" gorm:"type:decimal;not null"`
Username string `json:"username" gorm:"type:varchar(64)"`
Password string `json:"password" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}

View File

@@ -19,6 +19,7 @@ type Website struct {
ErrorLog bool `json:"errorLog"`
AccessLog bool `json:"accessLog"`
DefaultServer bool `json:"defaultServer"`
IPV6 bool `json:"IPV6"`
Rewrite string `gorm:"type:varchar" json:"rewrite"`
WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"`

View File

@@ -24,6 +24,7 @@ type IAppRepo interface {
GetByKey(ctx context.Context, key string) (model.App, error)
Create(ctx context.Context, app *model.App) error
Save(ctx context.Context, app *model.App) error
BatchDelete(ctx context.Context, apps []model.App) error
}
func NewIAppRepo() IAppRepo {
@@ -106,3 +107,7 @@ func (a AppRepo) Create(ctx context.Context, app *model.App) error {
func (a AppRepo) Save(ctx context.Context, app *model.App) error {
return getTx(ctx).Omit(clause.Associations).Save(app).Error
}
func (a AppRepo) BatchDelete(ctx context.Context, apps []model.App) error {
return getTx(ctx).Omit(clause.Associations).Delete(&apps).Error
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type AppDetailRepo struct {
@@ -12,12 +13,14 @@ type AppDetailRepo struct {
type IAppDetailRepo interface {
WithVersion(version string) DBOption
WithAppId(id uint) DBOption
WithIgnored() DBOption
GetFirst(opts ...DBOption) (model.AppDetail, error)
Update(ctx context.Context, detail model.AppDetail) error
BatchCreate(ctx context.Context, details []model.AppDetail) error
DeleteByAppIds(ctx context.Context, appIds []uint) error
GetBy(opts ...DBOption) ([]model.AppDetail, error)
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
BatchDelete(ctx context.Context, appDetails []model.AppDetail) error
}
func NewIAppDetailRepo() IAppDetailRepo {
@@ -29,12 +32,19 @@ func (a AppDetailRepo) WithVersion(version string) DBOption {
return g.Where("version = ?", version)
}
}
func (a AppDetailRepo) WithAppId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_id = ?", id)
}
}
func (a AppDetailRepo) WithIgnored() DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("ignore_upgrade = 1")
}
}
func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) {
var detail model.AppDetail
err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error
@@ -66,3 +76,7 @@ func (a AppDetailRepo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOpti
}
return db.Updates(&maps).Error
}
func (a AppDetailRepo) BatchDelete(ctx context.Context, appDetails []model.AppDetail) error {
return getTx(ctx).Omit(clause.Associations).Delete(&appDetails).Error
}

View File

@@ -19,8 +19,10 @@ type IAppInstallRepo interface {
WithAppIdsIn(appIds []uint) DBOption
WithStatus(status string) DBOption
WithServiceName(serviceName string) DBOption
WithContainerName(containerName string) DBOption
WithPort(port int) DBOption
WithIdNotInWebsite() DBOption
WithIDNotIs(id uint) DBOption
ListBy(opts ...DBOption) ([]model.AppInstall, error)
GetFirst(opts ...DBOption) (model.AppInstall, error)
Create(ctx context.Context, install *model.AppInstall) error
@@ -55,6 +57,12 @@ func (a *AppInstallRepo) WithAppId(appId uint) DBOption {
}
}
func (a *AppInstallRepo) WithIDNotIs(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id != ?", id)
}
}
func (a *AppInstallRepo) WithAppIdsIn(appIds []uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_id in (?)", appIds)
@@ -73,6 +81,12 @@ func (a *AppInstallRepo) WithServiceName(serviceName string) DBOption {
}
}
func (a *AppInstallRepo) WithContainerName(containerName string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("container_name = ?", containerName)
}
}
func (a *AppInstallRepo) WithPort(port int) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("https_port = ? or http_port = ?", port, port)
@@ -112,7 +126,7 @@ func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall)
}
func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error {
return getTx(ctx).Omit(clause.Associations).Save(&install).Error
return getTx(ctx).Save(&install).Error
}
func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error {
@@ -178,10 +192,19 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil {
return nil, err
}
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
switch app.Key {
case "mysql":
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "redis":
password, ok := envMap["PANEL_REDIS_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
}
userPassword, ok := envMap["PANEL_DB_USER_PASSWORD"].(string)
if ok {
info.UserPassword = userPassword

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,13 +13,14 @@ type MysqlRepo struct{}
type IMysqlRepo interface {
Get(opts ...DBOption) (model.DatabaseMysql, error)
WithByMysqlName(mysqlName string) DBOption
WithByFrom(from string) DBOption
List(opts ...DBOption) ([]model.DatabaseMysql, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.DatabaseMysql, error)
Create(ctx context.Context, mysql *model.DatabaseMysql) error
Delete(ctx context.Context, opts ...DBOption) error
Update(id uint, vars map[string]interface{}) error
UpdateDatabaseInfo(id uint, vars map[string]interface{}) error
DeleteAll(ctx context.Context) error
DeleteLocal(ctx context.Context) error
}
func NewIMysqlRepo() IMysqlRepo {
@@ -66,8 +67,8 @@ func (u *MysqlRepo) Delete(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.DatabaseMysql{}).Error
}
func (u *MysqlRepo) DeleteAll(ctx context.Context) error {
return getTx(ctx).Where("1 = 1").Delete(&model.DatabaseMysql{}).Error
func (u *MysqlRepo) DeleteLocal(ctx context.Context) error {
return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabaseMysql{}).Error
}
func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error {
@@ -86,3 +87,12 @@ func (u *MysqlRepo) WithByMysqlName(mysqlName string) DBOption {
return g.Where("mysql_name = ?", mysqlName)
}
}
func (u *MysqlRepo) WithByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(from) != 0 {
return g.Where("`from` = ?", from)
}
return g
}
}

View File

@@ -0,0 +1,84 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
type RemoteDBRepo struct{}
type IRemoteDBRepo interface {
GetList(opts ...DBOption) ([]model.RemoteDB, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.RemoteDB, error)
Create(database *model.RemoteDB) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.RemoteDB, error)
WithByFrom(from string) DBOption
WithoutByFrom(from string) DBOption
}
func NewIRemoteDBRepo() IRemoteDBRepo {
return &RemoteDBRepo{}
}
func (u *RemoteDBRepo) Get(opts ...DBOption) (model.RemoteDB, error) {
var database model.RemoteDB
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&database).Error
return database, err
}
func (u *RemoteDBRepo) Page(page, size int, opts ...DBOption) (int64, []model.RemoteDB, error) {
var users []model.RemoteDB
db := global.DB.Model(&model.RemoteDB{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
}
func (u *RemoteDBRepo) GetList(opts ...DBOption) ([]model.RemoteDB, error) {
var databases []model.RemoteDB
db := global.DB.Model(&model.RemoteDB{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&databases).Error
return databases, err
}
func (c *RemoteDBRepo) WithByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`from` == ?", from)
}
}
func (c *RemoteDBRepo) WithoutByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`from` != ?", from)
}
}
func (u *RemoteDBRepo) Create(database *model.RemoteDB) error {
return global.DB.Create(database).Error
}
func (u *RemoteDBRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.RemoteDB{}).Where("id = ?", id).Updates(vars).Error
}
func (u *RemoteDBRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.RemoteDB{}).Error
}

View File

@@ -14,6 +14,7 @@ type IRuntimeRepo interface {
WithImage(image string) DBOption
WithNotId(id uint) DBOption
WithStatus(status string) DBOption
WithDetailId(id uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error)
Create(ctx context.Context, runtime *model.Runtime) error
Save(runtime *model.Runtime) error
@@ -43,6 +44,12 @@ func (r *RuntimeRepo) WithImage(image string) DBOption {
}
}
func (r *RuntimeRepo) WithDetailId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_detail_id = ?", id)
}
}
func (r *RuntimeRepo) WithNotId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id != ?", id)

View File

@@ -1,6 +1,8 @@
package repo
import (
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
@@ -14,6 +16,13 @@ type ISettingRepo interface {
Create(key, value string) error
Update(key, value string) error
WithByKey(key string) DBOption
CreateMonitorBase(model model.MonitorBase) error
BatchCreateMonitorIO(ioList []model.MonitorIO) error
BatchCreateMonitorNet(ioList []model.MonitorNetwork) error
DelMonitorBase(timeForDelete time.Time) error
DelMonitorIO(timeForDelete time.Time) error
DelMonitorNet(timeForDelete time.Time) error
}
func NewISettingRepo() ISettingRepo {
@@ -57,3 +66,22 @@ func (c *SettingRepo) WithByKey(key string) DBOption {
func (u *SettingRepo) Update(key, value string) error {
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error
}
func (u *SettingRepo) CreateMonitorBase(model model.MonitorBase) error {
return global.DB.Create(&model).Error
}
func (u *SettingRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
return global.DB.CreateInBatches(ioList, len(ioList)).Error
}
func (u *SettingRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error {
return global.DB.CreateInBatches(ioList, len(ioList)).Error
}
func (u *SettingRepo) DelMonitorBase(timeForDelete time.Time) error {
return global.DB.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
}
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
}

View File

@@ -5,25 +5,26 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
"gopkg.in/yaml.v3"
"io"
"net/http"
"os"
"path"
"strconv"
"strings"
)
type AppService struct {
@@ -39,6 +40,7 @@ type IAppService interface {
GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal()
GetIgnoredApp() ([]response.IgnoredApp, error)
}
func NewIAppService() IAppService {
@@ -82,12 +84,16 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
return nil, err
}
var appDTOs []*response.AppDTO
for _, a := range apps {
for _, ap := range apps {
ap.ReadMe = ""
ap.Website = ""
ap.Document = ""
ap.Github = ""
appDTO := &response.AppDTO{
App: a,
App: ap,
}
appDTOs = append(appDTOs, appDTO)
appTags, err := appTagRepo.GetByAppId(a.ID)
appTags, err := appTagRepo.GetByAppId(ap.ID)
if err != nil {
continue
}
@@ -100,6 +106,8 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
continue
}
appDTO.Tags = tags
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID))
appDTO.Installed = len(installs) > 0
}
res.Items = appDTOs
res.Total = total
@@ -160,7 +168,13 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response.
return appDetailDTO, err
}
fileOp := files.NewFileOp()
buildPath := path.Join(constant.AppResourceDir, app.Key, "versions", detail.Version, "build")
versionPath := path.Join(constant.AppResourceDir, app.Resource, app.Key, detail.Version)
if !fileOp.Stat(versionPath) || detail.Update {
if err = downloadApp(app, detail, nil); err != nil {
return appDetailDTO, err
}
}
buildPath := path.Join(versionPath, "build")
paramsPath := path.Join(buildPath, "config.json")
if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist)
@@ -224,6 +238,27 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
return res, nil
}
func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) {
var res []response.IgnoredApp
details, _ := appDetailRepo.GetBy(appDetailRepo.WithIgnored())
if len(details) == 0 {
return res, nil
}
for _, detail := range details {
app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId))
if err != nil {
return nil, err
}
res = append(res, response.IgnoredApp{
Name: app.Name,
Version: detail.Version,
DetailID: detail.ID,
Icon: app.Icon,
})
}
return res, nil
}
func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
if err = docker.CreateDefaultDockerNetwork(); err != nil {
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
@@ -239,21 +274,30 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
appDetail model.AppDetail
app model.App
)
httpPort, err = checkPort("PANEL_APP_PORT_HTTP", req.Params)
if err != nil {
return
}
httpsPort, err = checkPort("PANEL_APP_PORT_HTTPS", req.Params)
if err != nil {
return
for key := range req.Params {
if !strings.Contains(key, "PANEL_APP_PORT") {
continue
}
var port int
if port, err = checkPort(key, req.Params); err == nil {
if key == "PANEL_APP_PORT_HTTP" {
httpPort = port
}
if key == "PANEL_APP_PORT_HTTPS" {
httpsPort = port
}
} else {
return
}
}
appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
if err != nil {
return
}
app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return nil, err
return
}
if err = checkRequiredAndLimit(app); err != nil {
return
@@ -270,35 +314,55 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
App: app,
}
composeMap := make(map[string]interface{})
if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil {
return
if req.EditCompose {
if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil {
return
}
} else {
if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil {
return
}
}
value, ok := composeMap["services"]
if !ok {
err = buserr.New("")
err = buserr.New(constant.ErrFileParse)
return
}
servicesMap := value.(map[string]interface{})
changeKeys := make(map[string]string, len(servicesMap))
containerName := constant.ContainerPrefix + app.Key + "-" + common.RandStr(4)
if req.Advanced && req.ContainerName != "" {
containerName = req.ContainerName
appInstalls, _ := appInstallRepo.ListBy(appInstallRepo.WithContainerName(containerName))
if len(appInstalls) > 0 {
err = buserr.New(constant.ErrContainerName)
return
}
containerExist := false
containerExist, err = checkContainerNameIsExist(req.ContainerName, appInstall.GetPath())
if err != nil {
return
}
if containerExist {
err = buserr.New(constant.ErrContainerName)
return
}
}
req.Params[constant.ContainerName] = containerName
appInstall.ContainerName = containerName
index := 0
for k := range servicesMap {
serviceName := k + "-" + common.RandStr(4)
changeKeys[k] = serviceName
containerName := constant.ContainerPrefix + k + "-" + common.RandStr(4)
appInstall.ServiceName = k
if index > 0 {
continue
}
req.Params["CONTAINER_NAME"] = containerName
appInstall.ServiceName = serviceName
appInstall.ContainerName = containerName
index++
}
for k, v := range changeKeys {
servicesMap[v] = servicesMap[k]
delete(servicesMap, k)
}
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
return
}
var (
composeByte []byte
paramByte []byte
@@ -318,34 +382,262 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
}
}()
if err = copyAppData(app.Key, appDetail.Version, req.Name, req.Params, app.Resource == constant.AppResourceLocal); err != nil {
return
}
fileOp := files.NewFileOp()
if err = fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil {
return
}
paramByte, err = json.Marshal(req.Params)
if err != nil {
return
}
appInstall.Env = string(paramByte)
if err = appInstallRepo.Create(ctx, appInstall); err != nil {
return
}
if err = createLink(ctx, app, appInstall, req.Params); err != nil {
return
}
if err = upAppPre(app, appInstall); err != nil {
return
}
go upApp(appInstall)
go func() {
if err = copyData(app, appDetail, appInstall, req); err != nil {
if appInstall.Status == constant.Installing {
appInstall.Status = constant.Error
appInstall.Message = err.Error()
}
_ = appInstallRepo.Save(context.Background(), appInstall)
return
}
if err = upAppPre(app, appInstall); err != nil {
return
}
upApp(appInstall)
}()
go updateToolApp(appInstall)
return
}
func (a AppService) SyncAppListFromLocal() {
fileOp := files.NewFileOp()
localAppDir := constant.LocalAppResourceDir
if !fileOp.Stat(localAppDir) {
return
}
var (
err error
dirEntries []os.DirEntry
localApps []model.App
)
defer func() {
if err != nil {
global.LOG.Errorf("sync app failed %v", err)
}
}()
global.LOG.Infof("start sync local apps...")
dirEntries, err = os.ReadDir(localAppDir)
if err != nil {
return
}
for _, dirEntry := range dirEntries {
if dirEntry.IsDir() {
appDir := path.Join(localAppDir, dirEntry.Name())
appDirEntries, err := os.ReadDir(appDir)
if err != nil {
global.LOG.Errorf(i18n.GetMsgWithMap("ErrAppDirNull", map[string]interface{}{"name": dirEntry.Name(), "err": err.Error()}))
continue
}
app, err := handleLocalApp(appDir)
if err != nil {
global.LOG.Errorf(i18n.GetMsgWithMap("LocalAppErr", map[string]interface{}{"name": dirEntry.Name(), "err": err.Error()}))
continue
}
var appDetails []model.AppDetail
for _, appDirEntry := range appDirEntries {
if appDirEntry.IsDir() {
appDetail := model.AppDetail{
Version: appDirEntry.Name(),
Status: constant.AppNormal,
}
versionDir := path.Join(appDir, appDirEntry.Name())
if err = handleLocalAppDetail(versionDir, &appDetail); err != nil {
global.LOG.Errorf(i18n.GetMsgWithMap("LocalAppVersionErr", map[string]interface{}{"name": app.Name, "version": appDetail.Version, "err": err.Error()}))
continue
}
appDetails = append(appDetails, appDetail)
}
}
if len(appDetails) > 0 {
app.Details = appDetails
localApps = append(localApps, *app)
} else {
global.LOG.Errorf(i18n.GetMsgWithMap("LocalAppVersionNull", map[string]interface{}{"name": app.Name}))
}
}
}
var (
newApps []model.App
deleteApps []model.App
updateApps []model.App
oldAppIds []uint
deleteAppIds []uint
deleteAppDetails []model.AppDetail
newAppDetails []model.AppDetail
updateDetails []model.AppDetail
appTags []*model.AppTag
)
oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal))
apps := make(map[string]model.App, len(oldApps))
for _, old := range oldApps {
old.Status = constant.AppTakeDown
apps[old.Key] = old
}
for _, app := range localApps {
if oldApp, ok := apps[app.Key]; ok {
app.ID = oldApp.ID
appDetails := make(map[string]model.AppDetail, len(oldApp.Details))
for _, old := range oldApp.Details {
old.Status = constant.AppTakeDown
appDetails[old.Version] = old
}
for i, newDetail := range app.Details {
version := newDetail.Version
newDetail.Status = constant.AppNormal
newDetail.AppId = app.ID
oldDetail, exist := appDetails[version]
if exist {
newDetail.ID = oldDetail.ID
delete(appDetails, version)
}
app.Details[i] = newDetail
}
for _, v := range appDetails {
app.Details = append(app.Details, v)
}
}
app.TagsKey = append(app.TagsKey, constant.AppResourceLocal)
apps[app.Key] = app
}
for _, app := range apps {
if app.ID == 0 {
newApps = append(newApps, app)
} else {
oldAppIds = append(oldAppIds, app.ID)
if app.Status == constant.AppTakeDown {
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID))
if len(installs) > 0 {
updateApps = append(updateApps, app)
continue
}
deleteAppIds = append(deleteAppIds, app.ID)
deleteApps = append(deleteApps, app)
deleteAppDetails = append(deleteAppDetails, app.Details...)
} else {
updateApps = append(updateApps, app)
}
}
}
tags, _ := tagRepo.All()
tagMap := make(map[string]uint, len(tags))
for _, tag := range tags {
tagMap[tag.Key] = tag.ID
}
tx, ctx := getTxAndContext()
defer tx.Rollback()
if len(newApps) > 0 {
if err = appRepo.BatchCreate(ctx, newApps); err != nil {
return
}
}
for _, update := range updateApps {
if err = appRepo.Save(ctx, &update); err != nil {
return
}
}
if len(deleteApps) > 0 {
if err = appRepo.BatchDelete(ctx, deleteApps); err != nil {
return
}
if err = appDetailRepo.DeleteByAppIds(ctx, deleteAppIds); err != nil {
return
}
}
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
return
}
for _, newApp := range newApps {
if newApp.ID > 0 {
for _, detail := range newApp.Details {
detail.AppId = newApp.ID
newAppDetails = append(newAppDetails, detail)
}
}
}
for _, update := range updateApps {
for _, detail := range update.Details {
if detail.ID == 0 {
detail.AppId = update.ID
newAppDetails = append(newAppDetails, detail)
} else {
if detail.Status == constant.AppNormal {
updateDetails = append(updateDetails, detail)
} else {
deleteAppDetails = append(deleteAppDetails, detail)
}
}
}
}
allApps := append(newApps, updateApps...)
for _, app := range allApps {
for _, t := range app.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
}
}
}
if len(newAppDetails) > 0 {
if err = appDetailRepo.BatchCreate(ctx, newAppDetails); err != nil {
return
}
}
for _, updateAppDetail := range updateDetails {
if err = appDetailRepo.Update(ctx, updateAppDetail); err != nil {
return
}
}
if len(deleteAppDetails) > 0 {
if err = appDetailRepo.BatchDelete(ctx, deleteAppDetails); err != nil {
return
}
}
if len(oldAppIds) > 0 {
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
return
}
}
if len(appTags) > 0 {
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil {
return
}
}
tx.Commit()
global.LOG.Infof("sync local apps success")
}
func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
res := &response.AppUpdateRes{
CanUpdate: false,
@@ -354,8 +646,8 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
if err != nil {
return nil, err
}
versionUrl := fmt.Sprintf("%s/%s/%s/appstore/apps.json", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion)
versionRes, err := http.Get(versionUrl)
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.System.AppRepo, global.CONF.System.Mode)
versionRes, err := http2.GetHttpRes(versionUrl)
if err != nil {
return nil, err
}
@@ -364,195 +656,75 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
if err != nil {
return nil, err
}
list := &dto.AppList{}
if err = json.Unmarshal(body, list); err != nil {
lastModifiedStr := string(body)
lastModified, err := strconv.Atoi(lastModifiedStr)
if err != nil {
return nil, err
}
res.Version = list.Version
if setting.AppStoreVersion == "" || common.CompareVersion(list.Version, setting.AppStoreVersion) {
appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified)
res.AppStoreLastModified = appStoreLastModified
if setting.AppStoreLastModified == "" || lastModified != appStoreLastModified {
res.CanUpdate = true
res.DownloadPath = fmt.Sprintf("%s/%s/%s/appstore/apps-%s.tar.gz", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion, list.Version)
return res, err
}
return res, nil
}
func (a AppService) SyncAppListFromLocal() {
func getAppFromRepo(downloadPath string) error {
downloadUrl := downloadPath
global.LOG.Infof("download file from %s", downloadUrl)
fileOp := files.NewFileOp()
appDir := constant.LocalAppResourceDir
listFile := path.Join(appDir, "list.json")
if !fileOp.Stat(listFile) {
return
packagePath := path.Join(constant.ResourceDir, path.Base(downloadUrl))
if err := fileOp.DownloadFile(downloadUrl, packagePath); err != nil {
return err
}
global.LOG.Infof("start sync local apps...")
content, err := fileOp.GetContent(listFile)
if err != nil {
global.LOG.Errorf("get list.json content failed %s", err.Error())
return
if err := fileOp.Decompress(packagePath, constant.ResourceDir, files.Zip); err != nil {
return err
}
list := &dto.AppList{}
if err := json.Unmarshal(content, list); err != nil {
global.LOG.Errorf("unmarshal list.json failed %s", err.Error())
return
}
oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal))
appsMap := getApps(oldApps, list.Items, true)
for _, l := range list.Items {
localKey := "local" + l.Key
app := appsMap[localKey]
icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png"))
if err != nil {
global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error())
continue
}
iconStr := base64.StdEncoding.EncodeToString(icon)
app.Icon = iconStr
app.TagsKey = append(l.Tags, "Local")
app.Recommend = 9999
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
detail := detailsMap[v]
detailPath := path.Join(appDir, l.Key, "versions", v)
if _, err := os.Stat(detailPath); err != nil {
global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error())
continue
}
readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md"))
if err != nil {
global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error())
}
detail.Readme = string(readmeStr)
dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml"))
if err != nil {
global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error())
continue
}
detail.DockerCompose = string(dockerComposeStr)
paramStr, err := os.ReadFile(path.Join(detailPath, "config.json"))
if err != nil {
global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error())
}
detail.Params = string(paramStr)
detailsMap[v] = detail
}
var newDetails []model.AppDetail
for _, v := range detailsMap {
newDetails = append(newDetails, v)
}
app.Details = newDetails
appsMap[localKey] = app
}
var (
addAppArray []model.App
updateArray []model.App
appIds []uint
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
updateArray = append(updateArray, v)
appIds = append(appIds, v.ID)
}
}
tx, ctx := getTxAndContext()
if len(addAppArray) > 0 {
if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
tx.Rollback()
return
}
}
for _, update := range updateArray {
if err := appRepo.Save(ctx, &update); err != nil {
tx.Rollback()
return
}
}
if err := appTagRepo.DeleteByAppIds(ctx, appIds); err != nil {
tx.Rollback()
return
}
apps := append(addAppArray, updateArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
appTags []*model.AppTag
)
tags, _ := tagRepo.All()
tagMap := make(map[string]uint, len(tags))
for _, app := range tags {
tagMap[app.Key] = app.ID
}
for _, a := range apps {
for _, t := range a.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: a.ID,
TagId: tagId,
})
}
}
for _, d := range a.Details {
d.AppId = a.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
if len(addDetails) > 0 {
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
tx.Rollback()
return
}
}
for _, u := range updateDetails {
if err := appDetailRepo.Update(ctx, u); err != nil {
tx.Rollback()
return
}
}
if len(appTags) > 0 {
if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
tx.Rollback()
return
}
}
tx.Commit()
global.LOG.Infof("sync local apps success")
defer func() {
_ = fileOp.DeleteFile(packagePath)
}()
return nil
}
func (a AppService) SyncAppListFromRemote() error {
func (a AppService) SyncAppListFromRemote() (err error) {
updateRes, err := a.GetAppUpdate()
if err != nil {
return err
}
if !updateRes.CanUpdate {
global.LOG.Infof("The latest version is [%s] The app store is already up to date", updateRes.Version)
return nil
return
}
if err := getAppFromRepo(updateRes.DownloadPath, updateRes.Version); err != nil {
global.LOG.Errorf("get app from oss error: %s", err.Error())
if err = getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.System.AppRepo, global.CONF.System.Mode)); err != nil {
return err
}
appDir := constant.AppResourceDir
listFile := path.Join(appDir, "list.json")
listFile := path.Join(constant.ResourceDir, "1panel.json")
content, err := os.ReadFile(listFile)
if err != nil {
return err
}
list := &dto.AppList{}
if err := json.Unmarshal(content, list); err != nil {
if err = json.Unmarshal(content, list); err != nil {
return
}
if err = NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
return err
}
defer func() {
if err != nil {
_ = NewISettingService().Update("AppStoreLastModified", strconv.Itoa(updateRes.AppStoreLastModified))
}
}()
var (
tags []*model.Tag
appTags []*model.AppTag
tags []*model.Tag
appTags []*model.AppTag
oldAppIds []uint
)
for _, t := range list.Tags {
for _, t := range list.Extra.Tags {
tags = append(tags, &model.Tag{
Key: t.Key,
Name: t.Name,
@@ -560,147 +732,190 @@ func (a AppService) SyncAppListFromRemote() error {
}
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
if err != nil {
return err
return
}
appsMap := getApps(oldApps, list.Items, false)
for _, l := range list.Items {
app := appsMap[l.Key]
icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png"))
for _, old := range oldApps {
oldAppIds = append(oldAppIds, old.ID)
}
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode)
appsMap := getApps(oldApps, list.Apps)
for _, l := range list.Apps {
app := appsMap[l.AppProperty.Key]
iconRes, err := http.Get(l.Icon)
if err != nil {
global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error())
continue
return err
}
iconStr := base64.StdEncoding.EncodeToString(icon)
body, err := io.ReadAll(iconRes.Body)
if err != nil {
return err
}
iconStr := base64.StdEncoding.EncodeToString(body)
app.Icon = iconStr
app.TagsKey = l.Tags
if l.Recommend > 0 {
app.Recommend = l.Recommend
app.TagsKey = l.AppProperty.Tags
if l.AppProperty.Recommend > 0 {
app.Recommend = l.AppProperty.Recommend
} else {
app.Recommend = 9999
}
app.ReadMe = l.ReadMe
app.LastModified = l.LastModified
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
detail := detailsMap[v]
detailPath := path.Join(appDir, l.Key, "versions", v)
if _, err := os.Stat(detailPath); err != nil {
global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error())
continue
}
readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md"))
version := v.Name
detail := detailsMap[version]
dockerComposeUrl := fmt.Sprintf("%s/%s/%s/%s", baseRemoteUrl, app.Key, version, "docker-compose.yml")
composeRes, err := http.Get(dockerComposeUrl)
if err != nil {
global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error())
return err
}
detail.Readme = string(readmeStr)
dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml"))
bodyContent, err := io.ReadAll(composeRes.Body)
if err != nil {
global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error())
continue
return err
}
detail.DockerCompose = string(dockerComposeStr)
paramStr, err := os.ReadFile(path.Join(detailPath, "config.json"))
if err != nil {
global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error())
}
detail.Params = string(paramStr)
detailsMap[v] = detail
detail.DockerCompose = string(bodyContent)
paramByte, _ := json.Marshal(v.AppForm)
detail.Params = string(paramByte)
detail.DownloadUrl = v.DownloadUrl
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
detail.Update = true
detail.LastModified = v.LastModified
detailsMap[version] = detail
}
var newDetails []model.AppDetail
for _, v := range detailsMap {
newDetails = append(newDetails, v)
for _, detail := range detailsMap {
newDetails = append(newDetails, detail)
}
app.Details = newDetails
appsMap[l.Key] = app
appsMap[l.AppProperty.Key] = app
}
var (
addAppArray []model.App
updateArray []model.App
tagMap = make(map[string]uint, len(tags))
addAppArray []model.App
updateAppArray []model.App
deleteAppArray []model.App
deleteIds []uint
tagMap = make(map[string]uint, len(tags))
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
updateArray = append(updateArray, v)
if v.Status == constant.AppTakeDown {
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(v.ID))
if len(installs) > 0 {
updateAppArray = append(updateAppArray, v)
continue
}
deleteAppArray = append(deleteAppArray, v)
deleteIds = append(deleteIds, v.ID)
} else {
updateAppArray = append(updateAppArray, v)
}
}
}
tx, ctx := getTxAndContext()
defer tx.Rollback()
if len(addAppArray) > 0 {
if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
tx.Rollback()
return err
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
return
}
}
if err := tagRepo.DeleteAll(ctx); err != nil {
tx.Rollback()
return err
if len(deleteAppArray) > 0 {
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
return
}
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
if err = tagRepo.DeleteAll(ctx); err != nil {
return
}
if len(tags) > 0 {
if err := tagRepo.BatchCreate(ctx, tags); err != nil {
tx.Rollback()
return err
if err = tagRepo.BatchCreate(ctx, tags); err != nil {
return
}
for _, t := range tags {
tagMap[t.Key] = t.ID
}
}
for _, update := range updateArray {
if err := appRepo.Save(ctx, &update); err != nil {
tx.Rollback()
return err
for _, update := range updateAppArray {
if err = appRepo.Save(ctx, &update); err != nil {
return
}
}
apps := append(addAppArray, updateArray...)
apps := append(addAppArray, updateAppArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
deleteDetails []model.AppDetail
)
for _, a := range apps {
for _, t := range a.TagsKey {
for _, app := range apps {
for _, t := range app.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: a.ID,
AppId: app.ID,
TagId: tagId,
})
}
}
for _, d := range a.Details {
d.AppId = a.ID
for _, d := range app.Details {
d.AppId = app.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
updateDetails = append(updateDetails, d)
if d.Status == constant.AppTakeDown {
runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithDetailId(d.ID))
if runtime != nil {
updateDetails = append(updateDetails, d)
continue
}
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithDetailIdsIn([]uint{d.ID}))
if len(installs) > 0 {
updateDetails = append(updateDetails, d)
continue
}
deleteDetails = append(deleteDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
}
if len(addDetails) > 0 {
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
tx.Rollback()
return err
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
return
}
}
if len(deleteDetails) > 0 {
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
return
}
}
for _, u := range updateDetails {
if err := appDetailRepo.Update(ctx, u); err != nil {
tx.Rollback()
return err
if err = appDetailRepo.Update(ctx, u); err != nil {
return
}
}
if err := appTagRepo.DeleteAll(ctx); err != nil {
tx.Rollback()
return err
if len(oldAppIds) > 0 {
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
return
}
}
if len(appTags) > 0 {
if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
tx.Rollback()
return err
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil {
return
}
}
tx.Commit()
return nil
return
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/i18n"
"math"
"os"
"path"
@@ -11,6 +12,9 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/backend/utils/env"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/joho/godotenv"
@@ -42,10 +46,11 @@ type IAppInstallService interface {
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
Operate(req request.AppInstalledOperate) error
Update(req request.AppInstalledUpdate) error
IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error
SyncAll(systemInit bool) error
GetServices(key string) ([]response.AppService, error)
GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
GetParams(id uint) ([]response.AppParam, error)
GetParams(id uint) (*response.AppConfig, error)
ChangeAppPort(req request.PortUpdate) error
GetDefaultConfigByKey(key string) (string, error)
DeleteCheck(installId uint) ([]dto.AppResource, error)
@@ -56,7 +61,12 @@ func NewIAppInstalledService() IAppInstallService {
}
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
var opts []repo.DBOption
var (
opts []repo.DBOption
total int64
installs []model.AppInstall
err error
)
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
@@ -82,15 +92,25 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
opts = append(opts, appInstallRepo.WithAppIdsIn(appIds))
}
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
if req.Update {
installs, err = appInstallRepo.ListBy(opts...)
if err != nil {
return 0, nil, err
}
} else {
total, installs, err = appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}
}
installDTOs, err := handleInstalled(installs, req.Update)
if err != nil {
return 0, nil, err
}
if req.Update {
total = int64(len(installDTOs))
}
return total, installDTOs, nil
}
@@ -183,6 +203,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
if err != nil {
return err
}
if !req.ForceDelete && !files.NewFileOp().Stat(install.GetPath()) {
return buserr.New(constant.ErrInstallDirNotFound)
}
dockerComposePath := install.GetComposePath()
switch req.Operate {
case constant.Rebuild:
@@ -213,7 +236,7 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
case constant.Sync:
return syncById(install.ID)
case constant.Upgrade:
return upgradeInstall(install.ID, req.DetailId)
return upgradeInstall(install.ID, req.DetailId, req.Backup)
default:
return errors.New("operate not support")
}
@@ -225,35 +248,68 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
return err
}
changePort := false
var (
oldPorts []int
newPorts []int
)
port, ok := req.Params["PANEL_APP_PORT_HTTP"]
if ok {
portN := int(math.Ceil(port.(float64)))
if portN != installed.HttpPort {
oldPorts = append(oldPorts, installed.HttpPort)
changePort = true
httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params)
if err != nil {
return err
}
installed.HttpPort = httpPort
newPorts = append(newPorts, httpPort)
}
}
ports, ok := req.Params["PANEL_APP_PORT_HTTPS"]
if ok {
portN := int(math.Ceil(ports.(float64)))
if portN != installed.HttpsPort {
oldPorts = append(oldPorts, installed.HttpsPort)
httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params)
if err != nil {
return err
}
installed.HttpsPort = httpsPort
newPorts = append(newPorts, httpsPort)
}
}
backupDockerCompose := installed.DockerCompose
if req.Advanced {
composeMap := make(map[string]interface{})
if req.EditCompose {
if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil {
return err
}
} else {
if err = yaml.Unmarshal([]byte(installed.DockerCompose), &composeMap); err != nil {
return err
}
}
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
return err
}
composeByte, err := yaml.Marshal(composeMap)
if err != nil {
return err
}
installed.DockerCompose = string(composeByte)
if req.ContainerName == "" {
req.Params[constant.ContainerName] = installed.ContainerName
} else {
req.Params[constant.ContainerName] = req.ContainerName
if installed.ContainerName != req.ContainerName {
exist, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(req.ContainerName), appInstallRepo.WithIDNotIs(installed.ID))
if exist.ID > 0 {
return buserr.New(constant.ErrContainerName)
}
containerExist, err := checkContainerNameIsExist(req.ContainerName, installed.GetPath())
if err != nil {
return err
}
if containerExist {
return buserr.New(constant.ErrContainerName)
}
installed.ContainerName = req.ContainerName
}
}
}
@@ -262,6 +318,7 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
if err != nil {
return err
}
backupEnvMaps := oldEnvMaps
handleMap(req.Params, oldEnvMaps)
paramByte, err := json.Marshal(oldEnvMaps)
if err != nil {
@@ -271,57 +328,74 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
if err := env.Write(oldEnvMaps, envPath); err != nil {
return err
}
_ = appInstallRepo.Save(context.Background(), &installed)
fileOp := files.NewFileOp()
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), 0755)
if err := rebuildApp(installed); err != nil {
_ = env.Write(backupEnvMaps, envPath)
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), 0755)
return err
}
installed.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &installed)
website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID))
if changePort && website.ID != 0 && website.Status == constant.Running {
nginxInstall, err := getNginxFull(&website)
if err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
config := nginxInstall.SiteConfig.Config
servers := config.FindServers()
if len(servers) == 0 {
return buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid"))
}
server := servers[0]
proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort)
server.UpdateRootProxy([]string{proxy})
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
}
if changePort {
go func() {
_ = OperateFirewallPort(oldPorts, newPorts)
nginxInstall, err := getNginxFull(&website)
if err != nil {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
}
config := nginxInstall.SiteConfig.Config
servers := config.FindServers()
if len(servers) == 0 {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")).Error())
return
}
server := servers[0]
proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort)
server.UpdateRootProxy([]string{proxy})
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
}
if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
}
}()
}
return nil
}
func (a *AppInstallService) IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error {
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.DetailID))
if err != nil {
return err
}
appDetail.IgnoreUpgrade = req.Operate == "ignore"
return appDetailRepo.Update(context.Background(), appDetail)
}
func (a *AppInstallService) SyncAll(systemInit bool) error {
allList, err := appInstallRepo.ListBy()
if err != nil {
return err
}
for _, i := range allList {
if i.Status == constant.Installing {
if i.Status == constant.Installing || i.Status == constant.Upgrading {
if systemInit {
i.Status = constant.Error
i.Message = "System restart causes application exception"
i.Message = "1Panel restart causes the task to terminate"
_ = appInstallRepo.Save(context.Background(), &i)
}
continue
}
if err := syncById(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
if !systemInit {
if err := syncById(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
}
return nil
@@ -366,6 +440,12 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
return versions, err
}
for _, detail := range details {
if detail.IgnoreUpgrade {
continue
}
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
continue
}
if common.CompareVersion(detail.Version, install.Version) {
versions = append(versions, dto.AppVersion{
Version: detail.Version,
@@ -401,10 +481,6 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
}
}
if err := OperateFirewallPort([]int{int(appInstall.Port)}, []int{int(req.Port)}); err != nil {
global.LOG.Errorf("allow firewall failed, err: %v", err)
}
return nil
}
@@ -452,7 +528,16 @@ func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
if err != nil {
return "", err
}
filePath := path.Join(constant.AppResourceDir, appInstall.App.Key, "versions", appInstall.Version, "conf")
fileOp := files.NewFileOp()
filePath := path.Join(constant.AppResourceDir, "remote", appInstall.App.Key, appInstall.Version, "conf")
if !fileOp.Stat(filePath) {
filePath = path.Join(constant.AppResourceDir, appInstall.App.Key, "versions", appInstall.Version, "conf")
}
if !fileOp.Stat(filePath) {
return "", buserr.New(constant.ErrPathNotFound)
}
if key == constant.AppMysql {
filePath = path.Join(filePath, "my.cnf")
}
@@ -469,11 +554,12 @@ func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
return string(contentByte), nil
}
func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) {
func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
var (
res []response.AppParam
params []response.AppParam
appForm dto.AppForm
envs = make(map[string]interface{})
res response.AppConfig
)
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
@@ -483,10 +569,10 @@ func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) {
if err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(detail.Params), &appForm); err != nil {
if err = json.Unmarshal([]byte(detail.Params), &appForm); err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
return nil, err
}
for _, form := range appForm.FormFields {
@@ -515,10 +601,29 @@ func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) {
}
appParam.Values = form.Values
}
res = append(res, appParam)
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,
})
}
}
return res, nil
config := getAppCommonConfig(envs)
config.DockerCompose = install.DockerCompose
res.Params = params
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
}
res.AppContainerConfig = config
return &res, nil
}
func syncById(installId uint) error {
@@ -529,12 +634,10 @@ func syncById(installId uint) error {
if appInstall.Status == constant.Installing {
return nil
}
containerNames, err := getContainerNames(appInstall)
if err != nil {
return err
}
cli, err := docker.NewClient()
if err != nil {
return err
@@ -560,16 +663,16 @@ func syncById(installId uint) error {
errorContainers = append(errorContainers, n.Names[0])
}
}
for _, old := range containerNames {
for _, name := range containerNames {
exist := false
for _, new := range containers {
if common.ExistWithStrArray(old, new.Names) {
for _, container := range containers {
if common.ExistWithStrArray(name, container.Names) {
exist = true
break
}
}
if !exist {
notFoundContainers = append(notFoundContainers, old)
notFoundContainers = append(notFoundContainers, name)
}
}
@@ -582,10 +685,10 @@ func syncById(installId uint) error {
if containerCount == 0 {
appInstall.Status = constant.Error
appInstall.Message = "container is not found"
appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == 0 && existedCount == 0 {
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
appInstall.Status = constant.Running
return appInstallRepo.Save(context.Background(), &appInstall)
}
@@ -600,22 +703,14 @@ func syncById(installId uint) error {
appInstall.Status = constant.UnHealthy
}
var errMsg strings.Builder
var errMsg string
if errCount > 0 {
errMsg.Write([]byte(string(rune(errCount)) + " error containers:"))
for _, e := range errorContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
}
if notFoundCount > 0 {
errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:"))
for _, e := range notFoundContainers {
errMsg.Write([]byte(e))
}
errMsg.Write([]byte("\n"))
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
}
appInstall.Message = errMsg.String()
appInstall.Message = errMsg
return appInstallRepo.Save(context.Background(), &appInstall)
}
@@ -636,7 +731,11 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
envKey := ""
switch param {
case "password":
envKey = "PANEL_DB_ROOT_PASSWORD="
if appKey == "mysql" {
envKey = "PANEL_DB_ROOT_PASSWORD="
} else {
envKey = "PANEL_REDIS_ROOT_PASSWORD="
}
case "port":
envKey = "PANEL_APP_PORT_HTTP="
case "user-password":
@@ -665,6 +764,10 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
if param == "password" {
oldVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
newVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", value)
if appKey == "redis" {
oldVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
newVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", value)
}
_ = appInstallRepo.BatchUpdateBy(map[string]interface{}{
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),

View File

@@ -2,19 +2,25 @@ package service
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/compose-spec/compose-go/types"
"github.com/subosito/gotenv"
"math"
"net/http"
"os"
"os/exec"
"path"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/utils/env"
@@ -29,6 +35,7 @@ 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"
)
@@ -42,7 +49,19 @@ var (
func checkPort(key string, params map[string]interface{}) (int, error) {
port, ok := params[key]
if ok {
portN := int(math.Ceil(port.(float64)))
portN := 0
var err error
switch p := port.(type) {
case string:
portN, err = strconv.Atoi(p)
if err != nil {
return portN, nil
}
case float64:
portN = int(math.Ceil(p))
case int:
portN = p
}
oldInstalled, _ := appInstallRepo.ListBy(appInstallRepo.WithPort(portN))
if len(oldInstalled) > 0 {
@@ -64,20 +83,33 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error {
var dbConfig dto.AppDatabase
if app.Type == "runtime" {
var authParam dto.AuthParam
paramByte, err := json.Marshal(params)
if err != nil {
return err
}
if err := json.Unmarshal(paramByte, &authParam); err != nil {
return err
}
if authParam.RootPassword != "" {
authByte, err := json.Marshal(authParam)
if err != nil {
return err
switch app.Key {
case "mysql", "mariadb", "postgresql":
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.AuthParam{
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
}
}
case "redis":
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.RedisAuthParam{
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
}
}
appInstall.Param = string(authByte)
}
}
if app.Type == "website" || app.Type == "tool" {
@@ -85,7 +117,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
if err != nil {
return err
}
if err := json.Unmarshal(paramByte, &dbConfig); err != nil {
if err = json.Unmarshal(paramByte, &dbConfig); err != nil {
return err
}
}
@@ -98,7 +130,7 @@ 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))
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 {
@@ -111,6 +143,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
createMysql.Format = "utf8mb4"
createMysql.Permission = "%"
createMysql.Password = dbConfig.Password
createMysql.From = "local"
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
if err != nil {
return err
@@ -167,15 +200,15 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key))
if install.App.Key == constant.AppMysql {
_ = mysqlRepo.DeleteAll(ctx)
_ = mysqlRepo.DeleteLocal(ctx)
}
uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name)
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/app/%s/%s", install.App.Key, install.Name))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
if deleteBackup {
localDir, _ := loadLocalDir()
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name)
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", install.App.Key, install.Name))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
@@ -209,7 +242,7 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func upgradeInstall(installId uint, detailId uint) error {
func upgradeInstall(installId uint, detailId uint, backup bool) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
return err
@@ -221,44 +254,139 @@ func upgradeInstall(installId uint, detailId uint) error {
if install.Version == detail.Version {
return errors.New("two version is same")
}
if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
return err
}
install.Status = constant.Upgrading
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Key, "versions", detail.Version)
if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(constant.ResourceDir, "localApps", strings.TrimPrefix(install.App.Key, "local"), "versions", detail.Version)
}
go func() {
if backup {
if err = NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
global.LOG.Errorf(i18n.GetMsgWithMap("ErrAppBackup", map[string]interface{}{"name": install.Name, "err": err.Error()}))
}
}
var upErr error
defer func() {
if upErr != nil {
install.Status = constant.UpgradeErr
install.Message = upErr.Error()
_ = appInstallRepo.Save(context.Background(), &install)
}
}()
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
stdout, err := cmd.CombinedOutput()
if err != nil {
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
if install.App.Resource == constant.AppResourceRemote {
if upErr = downloadApp(install.App, detail, &install); upErr != nil {
return
}
go func() {
_, _ = http.Get(detail.DownloadCallBackUrl)
}()
}
if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
}
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
stdout, _ := cmd.CombinedOutput()
if stdout != nil {
return errors.New(string(stdout))
global.LOG.Errorf("upgrade app [%s] [%s] cp file log : %s ", install.App.Key, install.Name, string(stdout))
}
return err
}
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
composeMap := make(map[string]interface{})
if upErr = yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); upErr != nil {
return
}
value, ok := composeMap["services"]
if !ok {
upErr = buserr.New(constant.ErrFileParse)
return
}
servicesMap := value.(map[string]interface{})
index := 0
oldServiceName := ""
for k := range servicesMap {
oldServiceName = k
index++
if index > 0 {
break
}
}
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
if install.ServiceName != oldServiceName {
delete(servicesMap, oldServiceName)
}
return err
}
install.DockerCompose = detail.DockerCompose
install.Version = detail.Version
install.AppDetailId = detailId
fileOp := files.NewFileOp()
if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil {
return err
}
if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
envs := make(map[string]interface{})
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
return
}
return err
}
config := getAppCommonConfig(envs)
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
envs[constant.ContainerName] = install.ContainerName
}
config.Advanced = true
if upErr = addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); upErr != nil {
return
}
paramByte, upErr := json.Marshal(envs)
if upErr != nil {
return
}
install.Env = string(paramByte)
composeByte, upErr := yaml.Marshal(composeMap)
if upErr != nil {
return
}
install.DockerCompose = string(composeByte)
install.Version = detail.Version
install.AppDetailId = detailId
images, err := getImages(install)
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 out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
return
}
return
}
fileOp := files.NewFileOp()
envParams := make(map[string]string, len(envs))
handleMap(envs, envParams)
if err = env.Write(envParams, install.GetEnvPath()); err != nil {
return
}
if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil {
return
}
if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
return
}
upErr = err
return
}
install.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &install)
}()
return appInstallRepo.Save(context.Background(), &install)
}
@@ -272,7 +400,6 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
return nil, err
}
containerMap := make(map[string]struct{})
containerMap[install.ContainerName] = struct{}{}
for _, service := range project.AllServices() {
if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" {
continue
@@ -283,9 +410,32 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
for k := range containerMap {
containerNames = append(containerNames, k)
}
if len(containerNames) == 0 {
containerNames = append(containerNames, install.ContainerName)
}
return containerNames, nil
}
func getImages(install model.AppInstall) ([]string, error) {
envStr, err := coverEnvJsonToStr(install.Env)
if err != nil {
return nil, err
}
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true)
if err != nil {
return nil, err
}
imagesMap := make(map[string]struct{})
for _, service := range project.AllServices() {
imagesMap[service.Image] = struct{}{}
}
var images []string
for k := range imagesMap {
images = append(images, k)
}
return images, nil
}
func coverEnvJsonToStr(envJson string) (string, error) {
envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(envJson), &envMap)
@@ -315,34 +465,6 @@ func checkRequiredAndLimit(app model.App) error {
if err := checkLimit(app); err != nil {
return err
}
if app.Required != "" {
var requiredArray []string
if err := json.Unmarshal([]byte(app.Required), &requiredArray); err != nil {
return err
}
for _, key := range requiredArray {
if key == "" {
continue
}
requireApp, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return err
}
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(requireApp.ID))
if err != nil {
return err
}
var detailIds []uint
for _, d := range details {
detailIds = append(detailIds, d.ID)
}
_, err = appInstallRepo.GetFirst(appInstallRepo.WithDetailIdsIn(detailIds))
if err != nil {
return buserr.WithDetail(constant.ErrAppRequired, requireApp.Name, nil)
}
}
}
return nil
}
@@ -359,24 +481,74 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
}
}
func copyAppData(key, version, installName string, params map[string]interface{}, isLocal bool) (err error) {
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall) (err error) {
appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
appDownloadDir := path.Join(appResourceDir, app.Key)
appVersionDir := path.Join(appDownloadDir, appDetail.Version)
fileOp := files.NewFileOp()
appResourceDir := constant.AppResourceDir
installAppDir := path.Join(constant.AppInstallDir, key)
appKey := key
if isLocal {
if !appDetail.Update && fileOp.Stat(appVersionDir) {
return
}
if !fileOp.Stat(appDownloadDir) {
_ = fileOp.CreateDir(appDownloadDir, 0755)
}
if !fileOp.Stat(appVersionDir) {
_ = fileOp.CreateDir(appVersionDir, 0755)
}
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz")
defer func() {
if err != nil {
if appInstall != nil {
appInstall.Status = constant.DownloadErr
appInstall.Message = err.Error()
}
}
}()
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
return
}
if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
return
}
_ = fileOp.DeleteFile(filePath)
appDetail.Update = false
_ = appDetailRepo.Update(context.Background(), appDetail)
return
}
func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) {
fileOp := files.NewFileOp()
appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
if app.Resource == constant.AppResourceRemote {
err = downloadApp(app, appDetail, appInstall)
if err != nil {
return
}
go func() {
_, _ = http.Get(appDetail.DownloadCallBackUrl)
}()
}
appKey := app.Key
installAppDir := path.Join(constant.AppInstallDir, app.Key)
if app.Resource == constant.AppResourceLocal {
appResourceDir = constant.LocalAppResourceDir
appKey = strings.TrimPrefix(key, "local")
appKey = strings.TrimPrefix(app.Key, "local")
installAppDir = path.Join(constant.LocalAppInstallDir, appKey)
}
resourceDir := path.Join(appResourceDir, appKey, "versions", version)
resourceDir := path.Join(appResourceDir, appKey, appDetail.Version)
if !fileOp.Stat(installAppDir) {
if err = fileOp.CreateDir(installAppDir, 0755); err != nil {
return
}
}
appDir := path.Join(installAppDir, installName)
appDir := path.Join(installAppDir, req.Name)
if fileOp.Stat(appDir) {
if err = fileOp.DeleteDir(appDir); err != nil {
return
@@ -385,62 +557,90 @@ func copyAppData(key, version, installName string, params map[string]interface{}
if err = fileOp.Copy(resourceDir, installAppDir); err != nil {
return
}
versionDir := path.Join(installAppDir, version)
versionDir := path.Join(installAppDir, appDetail.Version)
if err = fileOp.Rename(versionDir, appDir); err != nil {
return
}
envPath := path.Join(appDir, ".env")
envParams := make(map[string]string, len(params))
handleMap(params, envParams)
envParams := make(map[string]string, len(req.Params))
handleMap(req.Params, envParams)
if err = env.Write(envParams, envPath); err != nil {
return
}
if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(appInstall.DockerCompose), 0755); err != nil {
return err
}
return
}
// 处理文件夹权限等问题
func upAppPre(app model.App, appInstall *model.AppInstall) error {
if app.Key == "nexus" {
dataPath := path.Join(appInstall.GetPath(), "data")
if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil {
return err
}
dataPath := path.Join(appInstall.GetPath(), "data")
fileOp := files.NewFileOp()
switch app.Key {
case "nexus":
return fileOp.ChownR(dataPath, "200", "0", true)
case "sftpgo":
return fileOp.ChownR(dataPath, "1000", "1000", true)
case "pgadmin4":
return fileOp.ChownR(dataPath, "5050", "5050", true)
}
return nil
}
func getServiceFromInstall(appInstall *model.AppInstall) (service *composeV2.ComposeService, err error) {
var (
project *types.Project
envStr string
)
envStr, err = coverEnvJsonToStr(appInstall.Env)
func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
client, err := composeV2.NewDockerClient()
if err != nil {
return
return false, err
}
project, err = composeV2.GetComposeProject(appInstall.Name, appInstall.GetPath(), []byte(appInstall.DockerCompose), []byte(envStr), true)
var options dockerTypes.ContainerListOptions
list, err := client.ContainerList(context.Background(), options)
if err != nil {
return
return false, err
}
service, err = composeV2.NewComposeService()
if err != nil {
return
for _, container := range list {
if containerName == container.Names[0][1:] {
if workDir, ok := container.Labels[composeWorkdirLabel]; ok {
if workDir != appDir {
return true, nil
}
} else {
return true, nil
}
}
}
service.SetProject(project)
return
return false, nil
}
func upApp(appInstall *model.AppInstall) {
upProject := func(appInstall *model.AppInstall) (err error) {
if err == nil {
var composeService *composeV2.ComposeService
composeService, err = getServiceFromInstall(appInstall)
if err != nil {
return err
var (
out string
errMsg string
)
if appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
}
err = composeService.ComposeUp()
out, err = compose.Up(appInstall.GetComposePath())
if err != nil {
if out != "" {
appInstall.Message = errMsg + out
}
return err
}
return
@@ -450,7 +650,6 @@ func upApp(appInstall *model.AppInstall) {
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
appInstall.Message = err.Error()
} else {
appInstall.Status = constant.Running
}
@@ -473,21 +672,21 @@ func rebuildApp(appInstall model.AppInstall) error {
return syncById(appInstall.ID)
}
func getAppDetails(details []model.AppDetail, versions []string) map[string]model.AppDetail {
func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) map[string]model.AppDetail {
appDetails := make(map[string]model.AppDetail, len(details))
for _, old := range details {
old.Status = constant.AppTakeDown
appDetails[old.Version] = old
}
for _, v := range versions {
detail, ok := appDetails[v]
version := v.Name
detail, ok := appDetails[version]
if ok {
detail.Status = constant.AppNormal
appDetails[v] = detail
appDetails[version] = detail
} else {
appDetails[v] = model.AppDetail{
Version: v,
appDetails[version] = model.AppDetail{
Version: version,
Status: constant.AppNormal,
}
}
@@ -495,43 +694,110 @@ func getAppDetails(details []model.AppDetail, versions []string) map[string]mode
return appDetails
}
func getApps(oldApps []model.App, items []dto.AppDefine, isLocal bool) map[string]model.App {
func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
apps := make(map[string]model.App, len(oldApps))
for _, old := range oldApps {
old.Status = constant.AppTakeDown
apps[old.Key] = old
}
for _, item := range items {
key := item.Key
if isLocal {
key = "local" + key
}
config := item.AppProperty
key := config.Key
app, ok := apps[key]
if !ok {
app = model.App{}
}
if isLocal {
app.Resource = constant.AppResourceLocal
} else {
app.Resource = constant.AppResourceRemote
}
app.Resource = constant.AppResourceRemote
app.Name = item.Name
app.Limit = item.Limit
app.Limit = config.Limit
app.Key = key
app.ShortDescZh = item.ShortDescZh
app.ShortDescEn = item.ShortDescEn
app.Website = item.Website
app.Document = item.Document
app.Github = item.Github
app.Type = item.Type
app.CrossVersionUpdate = item.CrossVersionUpdate
app.Required = item.GetRequired()
app.ShortDescZh = config.ShortDescZh
app.ShortDescEn = config.ShortDescEn
app.Website = config.Website
app.Document = config.Document
app.Github = config.Github
app.Type = config.Type
app.CrossVersionUpdate = config.CrossVersionUpdate
app.Status = constant.AppNormal
app.LastModified = item.LastModified
app.ReadMe = item.ReadMe
apps[key] = app
}
return apps
}
func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
fileOp := files.NewFileOp()
dockerComposePath := path.Join(versionDir, "docker-compose.yml")
if !fileOp.Stat(dockerComposePath) {
return errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "docker-compose.yml"}))
}
dockerComposeByte, _ := fileOp.GetContent(dockerComposePath)
if dockerComposeByte == nil {
return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "docker-compose.yml"}))
}
appDetail.DockerCompose = string(dockerComposeByte)
paramPath := path.Join(versionDir, "data.yml")
if !fileOp.Stat(paramPath) {
return errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "data.yml"}))
}
paramByte, _ := fileOp.GetContent(paramPath)
if paramByte == nil {
return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml"}))
}
appParamConfig := dto.LocalAppParam{}
if err := yaml.Unmarshal(paramByte, &appParamConfig); err != nil {
return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml"}))
}
dataJson, err := json.Marshal(appParamConfig.AppParams)
if err != nil {
return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}))
}
appDetail.Params = string(dataJson)
return nil
}
func handleLocalApp(appDir string) (app *model.App, err error) {
fileOp := files.NewFileOp()
configYamlPath := path.Join(appDir, "data.yml")
if !fileOp.Stat(configYamlPath) {
err = errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "data.yml"}))
return
}
iconPath := path.Join(appDir, "logo.png")
if !fileOp.Stat(iconPath) {
err = errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "logo.png"}))
return
}
configYamlByte, err := fileOp.GetContent(configYamlPath)
if err != nil {
err = errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}))
return
}
localAppDefine := dto.LocalAppAppDefine{}
if err = yaml.Unmarshal(configYamlByte, &localAppDefine); err != nil {
err = errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}))
return
}
app = &localAppDefine.AppProperty
app.Resource = constant.AppResourceLocal
app.Status = constant.AppNormal
app.Recommend = 9999
app.TagsKey = append(app.TagsKey, "Local")
app.Key = "local" + app.Key
readMePath := path.Join(appDir, "README.md")
readMeByte, err := fileOp.GetContent(readMePath)
if err == nil {
app.ReadMe = string(readMeByte)
}
iconByte, _ := fileOp.GetContent(iconPath)
if iconByte != nil {
iconStr := base64.StdEncoding.EncodeToString(iconByte)
app.Icon = iconStr
}
return
}
func handleErr(install model.AppInstall, err error, out string) error {
reErr := err
install.Message = err.Error()
@@ -544,37 +810,15 @@ func handleErr(install model.AppInstall, err error, out string) error {
return reErr
}
func getAppFromRepo(downloadPath, version string) error {
downloadUrl := downloadPath
appDir := constant.AppResourceDir
global.LOG.Infof("download file from %s", downloadUrl)
fileOp := files.NewFileOp()
if _, err := fileOp.CopyAndBackup(appDir); err != nil {
return err
}
packagePath := path.Join(constant.ResourceDir, path.Base(downloadUrl))
if err := fileOp.DownloadFile(downloadUrl, packagePath); err != nil {
return err
}
if err := fileOp.Decompress(packagePath, constant.ResourceDir, files.TarGz); err != nil {
return err
}
_ = NewISettingService().Update("AppStoreVersion", version)
defer func() {
_ = fileOp.DeleteFile(packagePath)
}()
return nil
}
func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) {
var res []response.AppInstalledDTO
for _, installed := range appInstallList {
if updated && installed.App.Type == "php" {
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
continue
}
installDTO := response.AppInstalledDTO{
AppInstall: installed,
Path: installed.GetPath(),
}
app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId))
if err != nil {
@@ -586,9 +830,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
}
var versions []string
for _, detail := range details {
if detail.IgnoreUpgrade {
continue
}
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
continue
}
versions = append(versions, detail.Version)
}
versions = common.GetSortedVersions(versions)
if len(versions) == 0 {
continue
}
lastVersion := versions[0]
if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate
@@ -667,3 +920,108 @@ func updateToolApp(installed *model.AppInstall) {
return
}
}
func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName string, req request.AppContainerConfig, params map[string]interface{}) error {
services, serviceValid := composeMap["services"].(map[string]interface{})
if !serviceValid {
return buserr.New(constant.ErrFileParse)
}
service, serviceExist := services[serviceName]
if !serviceExist {
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}",
},
},
}
serviceValue["deploy"] = deploy
ports, ok := serviceValue["ports"].([]interface{})
if ok {
for i, port := range ports {
portStr, portOK := port.(string)
if !portOK {
continue
}
portArray := strings.Split(portStr, ":")
if len(portArray) == 2 {
portArray = append([]string{"${HOST_IP}"}, portArray...)
}
ports[i] = strings.Join(portArray, ":")
}
serviceValue["ports"] = ports
}
params[constant.CPUS] = "0"
params[constant.MemoryLimit] = "0"
if req.Advanced {
if req.CpuQuota > 0 {
params[constant.CPUS] = req.CpuQuota
}
if req.MemoryLimit > 0 {
params[constant.MemoryLimit] = strconv.FormatFloat(req.MemoryLimit, 'f', -1, 32) + req.MemoryUnit
}
}
_, portExist := serviceValue["ports"].([]interface{})
if portExist {
allowHost := "127.0.0.1"
if req.Advanced && req.AllowPort {
allowHost = "0.0.0.0"
}
params[constant.HostIP] = allowHost
}
services[serviceName] = serviceValue
return nil
}
func getAppCommonConfig(envs map[string]interface{}) request.AppContainerConfig {
config := request.AppContainerConfig{}
if hostIp, ok := envs[constant.HostIP]; ok {
config.AllowPort = hostIp.(string) == "0.0.0.0"
} else {
config.AllowPort = true
}
if cpuCore, ok := envs[constant.CPUS]; ok {
numStr, ok := cpuCore.(string)
if ok {
num, err := strconv.ParseFloat(numStr, 64)
if err == nil {
config.CpuQuota = num
}
} else {
num64, flOk := cpuCore.(float64)
if flOk {
config.CpuQuota = num64
}
}
} else {
config.CpuQuota = 0
}
if memLimit, ok := envs[constant.MemoryLimit]; ok {
re := regexp.MustCompile(`(\d+)([A-Za-z]+)`)
matches := re.FindStringSubmatch(memLimit.(string))
if len(matches) == 3 {
num, err := strconv.ParseFloat(matches[1], 64)
if err == nil {
unit := matches[2]
config.MemoryLimit = num
config.MemoryUnit = unit
}
}
} else {
config.MemoryLimit = 0
config.MemoryUnit = "M"
}
if containerName, ok := envs[constant.ContainerName]; ok {
config.ContainerName = containerName.(string)
}
return config
}

View File

@@ -17,7 +17,7 @@ import (
type AuthService struct{}
type IAuthService interface {
CheckIsSafety(code string) bool
CheckIsSafety(code string) (string, error)
VerifyCode(code string) (bool, error)
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
@@ -33,11 +33,11 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo,
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
passwrodSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
pass, err := encrypt.StringDecrypt(passwrodSetting.Value)
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
return nil, constant.ErrAuth
}
@@ -48,10 +48,12 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo,
if err != nil {
return nil, err
}
if err = settingRepo.Update("Language", info.Language); err != nil {
return nil, err
}
if mfa.Value == "enable" {
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, nil
}
return u.generateSession(c, info.Name, info.AuthMethod)
}
@@ -60,11 +62,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
passwrodSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
pass, err := encrypt.StringDecrypt(passwrodSetting.Value)
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
return nil, err
}
@@ -76,7 +78,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi
if err != nil {
return nil, err
}
success := mfa.ValidCode(info.Code, mfaSecret.Value)
mfaInterval, err := settingRepo.Get(settingRepo.WithByKey("MFAInterval"))
if err != nil {
return nil, err
}
success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value)
if !success {
return nil, constant.ErrAuth
}
@@ -109,7 +115,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, 604800, "", "", false, false)
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
err := global.SESSION.Set(sID, sessionUser, lifeTime)
if err != nil {
return nil, err
@@ -143,13 +149,16 @@ func (u *AuthService) VerifyCode(code string) (bool, error) {
return setting.Value == code, nil
}
func (u *AuthService) CheckIsSafety(code string) bool {
func (u *AuthService) CheckIsSafety(code string) (string, error) {
status, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
return false
return "", err
}
if len(status.Value) == 0 {
return true
return "disable", nil
}
return status.Value == code
if status.Value == code {
return "pass", nil
}
return "unpass", nil
}

View File

@@ -2,9 +2,14 @@ package service
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -23,6 +28,7 @@ type BackupService struct{}
type IBackupService interface {
List() ([]dto.BackupInfo, error)
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
LoadOneDriveInfo() (string, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
@@ -31,7 +37,7 @@ type IBackupService interface {
BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
ListFiles(req dto.BackupSearchFile) ([]interface{}, error)
ListFiles(req dto.BackupSearchFile) ([]string, error)
MysqlBackup(db dto.CommonBackup) error
MysqlRecover(db dto.CommonRecover) error
@@ -61,6 +67,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
dtobas = append(dtobas, u.loadByType("MINIO", ops))
dtobas = append(dtobas, u.loadByType("COS", ops))
dtobas = append(dtobas, u.loadByType("KODO", ops))
dtobas = append(dtobas, u.loadByType("OneDrive", ops))
return dtobas, err
}
@@ -83,6 +90,18 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [
return total, dtobas, err
}
func (u *BackupService) LoadOneDriveInfo() (string, error) {
OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
if err != nil {
return "", err
}
idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value)
if err != nil {
return "", err
}
return string(idItem), err
}
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
if info.Source == "LOCAL" {
return info.FileDir + "/" + info.FileName, nil
@@ -95,7 +114,6 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", err
}
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
@@ -104,20 +122,27 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
case constant.OneDrive:
varMap["accessToken"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil {
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
}
tempPath := fmt.Sprintf("%sdownload%s", constant.DataDir, info.FileDir)
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(tempPath, os.ModePerm); err != nil {
global.LOG.Errorf("mkdir %s failed, err: %v", tempPath, err)
targetPath := fmt.Sprintf("%s/download/%s/%s", constant.DataDir, info.FileDir, info.FileName)
if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil {
global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err)
}
}
targetPath := tempPath + info.FileName
if _, err = os.Stat(targetPath); err != nil && os.IsNotExist(err) {
isOK, err := backClient.Download(info.FileName, targetPath)
srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName)
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
srcPath = itemPath + srcPath
}
if exist, _ := backClient.Exist(srcPath); exist {
isOK, err := backClient.Download(srcPath, targetPath)
if !isOK {
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
}
@@ -133,6 +158,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error {
if err := copier.Copy(&backup, &backupDto); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if backupDto.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
}
if err := backupRepo.Create(&backup); err != nil {
return err
}
@@ -144,7 +175,6 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil {
return nil, err
}
varMap["type"] = backupDto.Type
switch backupDto.Type {
case constant.Sftp:
varMap["username"] = backupDto.AccessKey
@@ -153,7 +183,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
varMap["accessKey"] = backupDto.AccessKey
varMap["secretKey"] = backupDto.Credential
}
client, err := cloud_storage.NewCloudStorageClient(varMap)
client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
if err != nil {
return nil, err
}
@@ -179,7 +209,7 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
}
} else {
backupAccount, err := backupRepo.Get(commonRepo.WithByName(record.Source))
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
return err
}
@@ -213,7 +243,17 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
upMap := make(map[string]interface{})
upMap["bucket"] = req.Bucket
upMap["credential"] = req.Credential
upMap["backup_path"] = req.BackupPath
upMap["vars"] = req.Vars
backup.Vars = req.Vars
if req.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
upMap["credential"] = backup.Credential
upMap["vars"] = backup.Vars
}
if err := backupRepo.Update(req.ID, upMap); err != nil {
return err
}
@@ -233,7 +273,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
return nil
}
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, error) {
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
backup, err := backupRepo.Get(backupRepo.WithByType(req.Type))
if err != nil {
return nil, err
@@ -242,7 +282,21 @@ func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, erro
if err != nil {
return nil, err
}
return client.ListObjects("system_snapshot/")
prefix := "system_snapshot"
if len(backup.BackupPath) != 0 {
prefix = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), prefix)
}
files, err := client.ListObjects(prefix)
if err != nil {
return nil, err
}
var datas []string
for _, file := range files {
if len(file) != 0 {
datas = append(datas, path.Base(file))
}
}
return datas, nil
}
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
@@ -250,7 +304,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err
}
varMap["type"] = backup.Type
if backup.Type == "LOCAL" {
return nil, errors.New("not support")
}
@@ -262,9 +315,11 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
case constant.OneDrive:
varMap["accessToken"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil {
return nil, err
}
@@ -285,6 +340,53 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
return dto.BackupInfo{Type: accountType}
}
func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
}
data := url.Values{}
data.Set("client_id", global.CONF.System.OneDriveID)
data.Set("client_secret", global.CONF.System.OneDriveSc)
data.Set("grant_type", "authorization_code")
data.Set("code", varMap["code"].(string))
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
if err != nil {
return fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request for access token failed, err: %v", err)
}
delete(varMap, "code")
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()
token := map[string]interface{}{}
if err := json.Unmarshal(respBody, &token); err != nil {
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := token["refresh_token"].(string)
if !ok {
return errors.New("no such access token in response")
}
itemVars, err := json.Marshal(varMap)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
backup.Credential = accessToken
backup.Vars = string(itemVars)
return nil
}
func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {

View File

@@ -34,7 +34,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName)
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", req.Name, req.DetailName))
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
@@ -99,23 +99,21 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
return err
}
appPath := fmt.Sprintf("%s/%s", install.GetPath(), install.Name)
appPath := install.GetPath()
if err := handleTar(appPath, tmpDir, "app.tar.gz", ""); err != nil {
return err
}
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && resource.ResourceId != 0 {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handleMysqlBackup(mysqlInfo, tmpDir, db.Name, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
for _, resource := range resources {
if resource.Key == "mysql" {
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 {
return err
}
}
}
@@ -148,12 +146,12 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
if err := json.Unmarshal(appjson, &oldInstall); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name || oldInstall.Version != install.Version || oldInstall.ID != install.ID {
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name {
return errors.New("the current backup file does not match the application")
}
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/app/%s_%s.tar.gz", global.CONF.System.BaseDir, install.Name, time.Now().Format("20060102150405"))
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format("20060102150405")))
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
}
@@ -172,32 +170,104 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}()
}
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && install.App.Key != "mysql" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handleMysqlRecover(mysqlInfo, tmpPath, db.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
return err
newEnvFile := ""
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
for _, resource := range resources {
if resource.Key == "mysql" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env)
if err != nil {
return err
}
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
if err != nil {
return err
}
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
if err := handleMysqlRecover(dto.CommonRecover{
Name: newDB.MysqlName,
DetailName: newDB.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
}
}
}
appDir := install.GetPath()
backPath := fmt.Sprintf("%s_bak", appDir)
_ = fileOp.Rename(appDir, backPath)
_ = fileOp.CreateDir(appDir, 0755)
if err := handleUnTar(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key)); err != nil {
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
_ = fileOp.DeleteDir(appDir)
_ = fileOp.Rename(backPath, appDir)
return err
}
_ = fileOp.DeleteDir(backPath)
oldInstall.Status = constant.Running
if err := appInstallRepo.Save(context.Background(), install); err != nil {
if len(newEnvFile) != 0 {
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
_, _ = file.WriteString(newEnvFile)
}
oldInstall.ID = install.ID
oldInstall.Status = constant.StatusRunning
oldInstall.AppId = install.AppId
oldInstall.AppDetailId = install.AppDetailId
oldInstall.App.ID = install.AppId
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
global.LOG.Errorf("save db app install failed, err: %v", err)
return err
}
isOk = true
return nil
}
func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
mysqlService := NewIMysqlService()
ctx := context.Background()
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, DeleteBackup: true, ForceDelete: true})
envMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
return nil, envMap, err
}
oldName, _ := envMap["PANEL_DB_NAME"].(string)
oldUser, _ := envMap["PANEL_DB_USER"].(string)
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
Name: oldName,
From: "local",
Format: "utf8mb4",
Username: oldUser,
Password: oldPassword,
Permission: "%",
})
if err != nil {
return nil, envMap, err
}
return createDB, envMap, nil
}

View File

@@ -1,10 +1,8 @@
package service
import (
"compress/gzip"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
@@ -12,9 +10,9 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
"github.com/pkg/errors"
)
@@ -23,24 +21,22 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
if err != nil {
return err
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
timeNow := time.Now().Format("20060102150405")
targetDir := path.Join(localDir, fmt.Sprintf("database/mysql/%s/%s", req.Name, req.DetailName))
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
return err
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, req.Name, req.DetailName)
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
if err := handleMysqlBackup(app, backupDir, req.DetailName, fileName); err != nil {
return err
}
record := &model.BackupRecord{
Type: "mysql",
Name: app.Name,
Name: req.Name,
DetailName: req.DetailName,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: backupDir,
FileDir: targetDir,
FileName: fileName,
}
if err := backupRepo.CreateRecord(record); err != nil {
@@ -50,26 +46,13 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
}
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
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))
}
global.LOG.Infof("recover database %s-%s from backup file %s", req.Name, req.DetailName, req.File)
if err := handleMysqlRecover(app, path.Dir(req.File), req.DetailName, path.Base(req.File), false); err != nil {
if err := handleMysqlRecover(req, false); err != nil {
return err
}
return nil
}
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return err
}
file := req.File
fileName := path.Base(req.File)
if strings.HasSuffix(fileName, ".tar.gz") {
@@ -106,77 +89,92 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
}()
}
if err := handleMysqlRecover(app, path.Dir(file), req.DetailName, fileName, false); err != nil {
req.File = path.Dir(file) + "/" + fileName
if err := handleMysqlRecover(req, false); err != nil {
return err
}
global.LOG.Info("recover from uploads successful!")
return nil
}
func handleMysqlBackup(app *repo.RootInfo, backupDir, dbName, fileName string) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(backupDir) {
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
}
func handleMysqlBackup(name, dbName, targetDir, fileName string) error {
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(name))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(dbInfo.From)
if err != nil {
return err
}
outfile, _ := os.OpenFile(backupDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755)
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", backupDir+"/"+fileName)
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName)
gzipCmd := exec.Command("gzip", "-cf")
gzipCmd.Stdin, _ = cmd.StdoutPipe()
gzipCmd.Stdout = outfile
_ = gzipCmd.Start()
_ = cmd.Run()
_ = gzipCmd.Wait()
backupInfo := client.BackupInfo{
Name: dbName,
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
Timeout: 300,
}
if err := cli.Backup(backupInfo); err != nil {
return err
}
return nil
}
func handleMysqlRecover(mysqlInfo *repo.RootInfo, recoverDir, dbName, fileName string, isRollback bool) error {
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))
}
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(dbInfo.From)
if err != nil {
return err
}
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/database/%s_%s.sql.gz", global.CONF.System.BaseDir, mysqlInfo.Name, time.Now().Format("20060102150405"))
if err := handleMysqlBackup(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", mysqlInfo.Name, err)
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/mysql/%s_%s.sql.gz", req.DetailName, time.Now().Format("20060102150405")))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
Format: dbInfo.Format,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
Timeout: 300,
}); err != nil {
return fmt.Errorf("backup mysql 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 := handleMysqlRecover(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile), true); err != nil {
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", dbName, rollbackFile, err)
return
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Format: dbInfo.Format,
SourceFile: rollbackFile,
Timeout: 300,
}); err != nil {
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
}
global.LOG.Infof("rollback mysql db %s from %s successful", dbName, rollbackFile)
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
}
}()
}
file := recoverDir + "/" + fileName
fi, _ := os.Open(file)
defer fi.Close()
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, dbName)
if strings.HasSuffix(fileName, ".gz") {
gzipFile, err := os.Open(file)
if err != nil {
return err
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
return err
}
defer gzipReader.Close()
cmd.Stdin = gzipReader
} else {
cmd.Stdin = fi
}
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Format: dbInfo.Format,
SourceFile: req.File,
Timeout: 300,
}); err != nil {
return err
}
isOk = true
return nil

View File

@@ -37,13 +37,13 @@ func (u *BackupService) RedisBackup() error {
timeNow := time.Now().Format("20060102150405")
fileName := fmt.Sprintf("%s.rdb", timeNow)
if appendonly == "yes" {
if redisInfo.Version == "6.0.16" {
if strings.HasPrefix(redisInfo.Version, "6.") {
fileName = fmt.Sprintf("%s.aof", timeNow)
} else {
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
}
}
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
if err := handleRedisBackup(redisInfo, backupDir, fileName); err != nil {
return err
}
@@ -120,10 +120,10 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
}
if appendonly == "yes" {
if redisInfo.Version == "6.0.16" && !strings.HasSuffix(recoverFile, ".aof") {
if strings.HasPrefix(redisInfo.Version, "6.") && !strings.HasSuffix(recoverFile, ".aof") {
return buserr.New(constant.ErrTypeOfRedis)
}
if redisInfo.Version == "7.0.5" && !strings.HasSuffix(recoverFile, ".tar.gz") {
if strings.HasPrefix(redisInfo.Version, "7.") && !strings.HasSuffix(recoverFile, ".tar.gz") {
return buserr.New(constant.ErrTypeOfRedis)
}
} else {
@@ -137,13 +137,13 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
if !isRollback {
suffix := "rdb"
if appendonly == "yes" {
if redisInfo.Version == "6.0.16" {
if strings.HasPrefix(redisInfo.Version, "6.") {
suffix = "aof"
} else {
suffix = "tar.gz"
}
}
rollbackFile := fmt.Sprintf("%s/original/database/redis/%s_%s.%s", global.CONF.System.BaseDir, redisInfo.Name, time.Now().Format("20060102150405"), suffix)
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format("20060102150405"), suffix))
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
}
@@ -165,14 +165,14 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
return err
}
if appendonly == "yes" && redisInfo.Version == "7.0.5" {
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data", constant.AppInstallDir, "redis", redisInfo.Name)
if err := handleUnTar(recoverFile, redisDataDir); err != nil {
return err
}
} else {
itemName := "dump.rdb"
if appendonly == "yes" && redisInfo.Version == "6.0.16" {
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "6.") {
itemName = "appendonly.aof"
}
input, err := os.ReadFile(recoverFile)

View File

@@ -11,6 +11,7 @@ import (
"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"
@@ -30,7 +31,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/website/%s", localDir, req.Name)
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", req.Name))
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow)
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
return err
@@ -80,11 +81,11 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
temPathWithName := tmpPath + "/" + website.Alias
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
return errors.New("the wrong recovery package does not have .conf or .web.tar.gz files")
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
}
if website.Type == constant.Deployment {
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
return errors.New("the wrong recovery package does not have .app.tar.gz files")
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
}
}
var oldWebsite model.Website
@@ -95,13 +96,14 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type || oldWebsite.ID != website.ID {
return errors.New("the current backup file does not match the application")
if err := checkValidOfWebsite(&oldWebsite, website); err != nil {
return err
}
isOk := false
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/website/%s_%s.tar.gz", global.CONF.System.BaseDir, website.Alias, time.Now().Format("20060102150405"))
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format("20060102150405")))
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
}
@@ -155,6 +157,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
return errors.New(string(stdout))
}
oldWebsite.ID = website.ID
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
global.LOG.Errorf("handle save website data failed, err: %v", err)
return err
@@ -212,3 +215,26 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err
return nil
}
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type {
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil)
}
if oldWebsite.AppInstallID != 0 {
_, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "app", nil)
}
}
if oldWebsite.RuntimeID != 0 {
if _, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)); err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "runtime", nil)
}
}
if oldWebsite.WebsiteSSLID != 0 {
if _, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID)); err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "ssl", nil)
}
}
return nil
}

View File

@@ -3,10 +3,12 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -17,6 +19,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
@@ -25,30 +28,44 @@ import (
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/gorilla/websocket"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
)
type ContainerService struct{}
type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
List() ([]string, error)
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
ListNetwork() ([]dto.Options, error)
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) (string, error)
ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerCreate) error
ContainerCreate(req dto.ContainerOperate) error
ContainerUpdate(req dto.ContainerOperate) error
ContainerUpgrade(req dto.ContainerUpgrade) error
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
ContainerListStats() ([]dto.ContainerListStats, error)
LoadResouceLimit() (*dto.ResourceLimit, error)
ContainerLogClean(req dto.OperationWithName) error
ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(param dto.ContainerLog) (string, error)
ContainerStats(id string) (*dto.ContainterStats, error)
ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error
ContainerStats(id string) (*dto.ContainerStats, error)
Inspect(req dto.InspectReq) (string, error)
DeleteNetwork(req dto.BatchDelete) error
CreateNetwork(req dto.NetworkCreat) error
CreateNetwork(req dto.NetworkCreate) error
DeleteVolume(req dto.BatchDelete) error
CreateVolume(req dto.VolumeCreat) error
CreateVolume(req dto.VolumeCreate) error
TestCompose(req dto.ComposeCreate) (bool, error)
ComposeUpdate(req dto.ComposeUpdate) error
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
LoadContainerLogs(req dto.OperationWithNameAndType) string
}
func NewIContainerService() IContainerService {
@@ -57,9 +74,8 @@ func NewIContainerService() IContainerService {
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
var (
records []types.Container
list []types.Container
backDatas []dto.ContainerInfo
records []types.Container
list []types.Container
)
client, err := docker.NewDockerClient()
if err != nil {
@@ -75,19 +91,40 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
return 0, nil, err
}
if len(req.Name) != 0 {
lenth, count := len(list), 0
for count < lenth {
length, count := len(list), 0
for count < length {
if !strings.Contains(list[count].Names[0][1:], req.Name) {
list = append(list[:count], list[(count+1):]...)
lenth--
length--
} else {
count++
}
}
}
sort.Slice(list, func(i, j int) bool {
return list[i].Created > list[j].Created
})
switch req.OrderBy {
case "name":
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Names[0][1:] < list[j].Names[0][1:]
}
return list[i].Names[0][1:] > list[j].Names[0][1:]
})
case "state":
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].State < list[j].State
}
return list[i].State > list[j].State
})
default:
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Created < list[j].Created
}
return list[i].Created > list[j].Created
})
}
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
records = make([]types.Container, 0)
@@ -98,49 +135,87 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
records = list[start:end]
}
var wg sync.WaitGroup
wg.Add(len(records))
for _, container := range records {
go func(item types.Container) {
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
backDatas := make([]dto.ContainerInfo, len(records))
for i := 0; i < len(records); i++ {
item := records[i]
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
var ports []string
for _, port := range item.Ports {
if port.IP == "::" || port.PublicPort == 0 {
continue
}
ports = append(ports, fmt.Sprintf("%v:%v/%s", port.PublicPort, port.PrivatePort, port.Type))
var ports []string
for _, port := range item.Ports {
itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type)
if port.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)
}
cpu, mem := loadCpuAndMem(client, item.ID)
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
CPUPercent: cpu,
MemoryPercent: mem,
Ports: ports,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
})
wg.Done()
}(container)
ports = append(ports, itemPortStr)
}
backDatas[i] = dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
Ports: ports,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
}
wg.Wait()
return int64(total), backDatas, nil
}
func (u *ContainerService) List() ([]string, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
var datas []string
for _, container := range containers {
for _, name := range container.Names {
if len(name) != 0 {
datas = append(datas, strings.TrimPrefix(name, "/"))
}
}
}
return datas, nil
}
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
list, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
var datas []dto.ContainerListStats
var wg sync.WaitGroup
wg.Add(len(list))
for i := 0; i < len(list); i++ {
go func(item types.Container) {
cpu, mem := loadCpuAndMem(client, item.ID)
datas = append(datas, dto.ContainerListStats{CPUPercent: cpu, MemoryPercent: mem, ContainerID: item.ID})
wg.Done()
}(list[i])
}
wg.Wait()
return datas, nil
}
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
client, err := docker.NewDockerClient()
if err != nil {
@@ -165,63 +240,97 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
return string(bytes), nil
}
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
portMap, err := checkPortStats(req.ExposedPorts)
func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) {
report := dto.ContainerPruneReport{}
client, err := docker.NewDockerClient()
if err != nil {
return err
return report, err
}
pruneFilters := filters.NewArgs()
if req.WithTagAll {
pruneFilters.Add("dangling", "false")
if req.PruneType != "image" {
pruneFilters.Add("until", "24h")
}
}
switch req.PruneType {
case "container":
rep, err := client.ContainersPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.ContainersDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
case "image":
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.ImagesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
case "network":
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.NetworksDeleted)
case "volume":
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.VolumesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
}
return report, nil
}
func (u *ContainerService) LoadResouceLimit() (*dto.ResourceLimit, error) {
cpuCounts, err := cpu.Counts(true)
if err != nil {
return nil, fmt.Errorf("load cpu limit failed, err: %v", err)
}
memoryInfo, err := mem.VirtualMemory()
if err != nil {
return nil, fmt.Errorf("load memory limit failed, err: %v", err)
}
data := dto.ResourceLimit{
CPU: cpuCounts,
Memory: int(memoryInfo.Total),
}
return &data, nil
}
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
ctx := context.Background()
newContainer, _ := client.ContainerInspect(ctx, req.Name)
if newContainer.ContainerJSONBase != nil {
return buserr.New(constant.ErrContainerName)
}
exposeds := make(nat.PortSet)
for port := range portMap {
exposeds[port] = struct{}{}
}
config := &container.Config{
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
ExposedPorts: exposeds,
}
hostConf := &container.HostConfig{
AutoRemove: req.AutoRemove,
PublishAllPorts: req.PublishAllPorts,
RestartPolicy: container.RestartPolicy{Name: req.RestartPolicy},
}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
if req.NanoCPUs != 0 {
hostConf.NanoCPUs = req.NanoCPUs * 1000000000
}
if req.Memory != 0 {
hostConf.Memory = req.Memory
}
if len(req.ExposedPorts) != 0 {
hostConf.PortBindings = portMap
}
if len(req.Volumes) != 0 {
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
config.Volumes[volume.ContainerDir] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
}
var config container.Config
var hostConf container.HostConfig
var networkConf network.NetworkingConfig
if err := loadConfigInfo(req, &config, &hostConf, &networkConf); err != nil {
return err
}
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
ctx := context.Background()
if !checkImageExist(client, req.Image) {
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
return err
if !req.ForcePull {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
}
}
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
container, err := client.ContainerCreate(ctx, &config, &hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return err
@@ -234,6 +343,156 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
return nil
}
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
ctx := context.Background()
oldContainer, err := client.ContainerInspect(ctx, req.Name)
if err != nil {
return nil, err
}
var data dto.ContainerOperate
data.ContainerID = oldContainer.ID
data.Name = strings.ReplaceAll(oldContainer.Name, "/", "")
data.Image = oldContainer.Config.Image
if oldContainer.NetworkSettings != nil {
for network := range oldContainer.NetworkSettings.Networks {
data.Network = network
break
}
}
data.Cmd = oldContainer.Config.Cmd
data.Env = oldContainer.Config.Env
data.CPUShares = oldContainer.HostConfig.CPUShares
for key, val := range oldContainer.Config.Labels {
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
}
for key, val := range oldContainer.HostConfig.PortBindings {
var itemPort dto.PortHelper
if !strings.Contains(string(key), "/") {
continue
}
itemPort.ContainerPort = strings.Split(string(key), "/")[0]
itemPort.Protocol = strings.Split(string(key), "/")[1]
for _, binds := range val {
itemPort.HostIP = binds.HostIP
itemPort.HostPort = binds.HostPort
data.ExposedPorts = append(data.ExposedPorts, itemPort)
}
}
data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
if oldContainer.HostConfig.NanoCPUs != 0 {
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
}
if oldContainer.HostConfig.Memory != 0 {
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
}
data.Volumes = loadVolumeBinds(oldContainer.HostConfig.Binds)
return &data, nil
}
func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
ctx := context.Background()
newContainer, _ := client.ContainerInspect(ctx, req.Name)
if newContainer.ContainerJSONBase != nil && newContainer.ID != req.ContainerID {
return buserr.New(constant.ErrContainerName)
}
oldContainer, err := client.ContainerInspect(ctx, req.ContainerID)
if err != nil {
return err
}
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
}
}
if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil {
return err
}
config := oldContainer.Config
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return err
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("update contianer failed, err: %v", err)
}
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("update successful but start failed, err: %v", err)
}
return nil
}
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
ctx := context.Background()
oldContainer, err := client.ContainerInspect(ctx, req.Name)
if err != nil {
return err
}
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
}
}
config := oldContainer.Config
config.Image = req.Image
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if oldContainer.NetworkSettings != nil {
for networkKey := range oldContainer.NetworkSettings.Networks {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
}
if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil {
return err
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("upgrade contianer 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 {
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
}
return nil
}
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
var err error
ctx := context.Background()
@@ -256,6 +515,10 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
case constant.ContainerOpUnpause:
err = client.ContainerUnpause(ctx, req.Name)
case constant.ContainerOpRename:
newContainer, _ := client.ContainerInspect(ctx, req.NewName)
if newContainer.ContainerJSONBase != nil {
return buserr.New(constant.ErrContainerName)
}
err = client.ContainerRename(ctx, req.Name, req.NewName)
case constant.ContainerOpRemove:
err = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
@@ -263,19 +526,75 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
return err
}
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
cmd := exec.Command("docker", "logs", req.ContainerID)
if req.Mode != "all" {
cmd = exec.Command("docker", "logs", req.ContainerID, "--since", req.Mode)
}
stdout, err := cmd.CombinedOutput()
func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
client, err := docker.NewDockerClient()
if err != nil {
return "", errors.New(string(stdout))
return err
}
return string(stdout), nil
container, err := client.ContainerInspect(context.Background(), req.Name)
if err != nil {
return err
}
file, err := os.OpenFile(container.LogPath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer file.Close()
if err = file.Truncate(0); err != nil {
return err
}
_, _ = file.Seek(0, 0)
files, _ := filepath.Glob(fmt.Sprintf("%s.*", container.LogPath))
for _, file := range files {
_ = os.Remove(file)
}
return nil
}
func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, error) {
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error {
if cmd.CheckIllegal(container, since, tail) {
return buserr.New(constant.ErrCmdIllegal)
}
command := fmt.Sprintf("docker logs %s", container)
if tail != "0" {
command += " -n " + tail
}
if since != "all" {
command += " --since " + since
}
if follow {
command += " -f"
}
command += " 2>&1"
cmd := exec.Command("bash", "-c", command)
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}
buffer := make([]byte, 1024)
for {
n, err := stdout.Read(buffer)
if err != nil {
if err == io.EOF {
break
}
global.LOG.Errorf("read bytes from container log failed, err: %v", err)
continue
}
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
global.LOG.Errorf("send message with container log to ws failed, err: %v", err)
break
}
}
return nil
}
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
@@ -296,7 +615,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
}
var data dto.ContainterStats
var data dto.ContainerStats
data.CPUPercent = calculateCPUPercentUnix(stats)
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
@@ -309,6 +628,48 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
return &data, nil
}
func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) string {
filePath := ""
switch req.Type {
case "image-pull", "image-push", "image-build":
filePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
case "compose-detail", "compose-create":
client, err := docker.NewDockerClient()
if err != nil {
return ""
}
options := types.ContainerListOptions{All: true}
options.Filters = filters.NewArgs()
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name))
containers, err := client.ContainerList(context.Background(), options)
if err != nil {
return ""
}
for _, container := range containers {
config := container.Labels[composeConfigLabel]
workdir := container.Labels[composeWorkdirLabel]
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
filePath = config
break
} else {
filePath = workdir
break
}
}
if req.Type == "compose-create" {
filePath = path.Join(path.Dir(filePath), "compose.log")
}
}
if _, err := os.Stat(filePath); err != nil {
return ""
}
content, err := os.ReadFile(filePath)
if err != nil {
return ""
}
return string(content)
}
func stringsToMap(list []string) map[string]string {
var lableMap = make(map[string]string)
for _, label := range list {
@@ -460,3 +821,103 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
}
return portMap, nil
}
func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig) error {
portMap, err := checkPortStats(req.ExposedPorts)
if err != nil {
return err
}
exposeds := make(nat.PortSet)
for port := range portMap {
exposeds[port] = struct{}{}
}
config.Image = req.Image
config.Cmd = req.Cmd
config.Env = req.Env
config.Labels = stringsToMap(req.Labels)
config.ExposedPorts = exposeds
if len(req.Network) != 0 {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
} else {
networkConf = &network.NetworkingConfig{}
}
hostConf.AutoRemove = req.AutoRemove
hostConf.CPUShares = req.CPUShares
hostConf.PublishAllPorts = req.PublishAllPorts
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000)
hostConf.Memory = int64(req.Memory * 1024 * 1024)
hostConf.PortBindings = portMap
hostConf.Binds = []string{}
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
config.Volumes[volume.ContainerDir] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
}
return nil
}
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *types.NetworkSettings) {
ctx := context.Background()
var oldNetworkConf network.NetworkingConfig
if networkConf != nil {
for networkKey := range networkConf.Networks {
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
}
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
if err != nil {
global.LOG.Errorf("recreate after container update failed, err: %v", err)
return
}
if err := client.ContainerStart(ctx, oldContainer.ID, types.ContainerStartOptions{}); err != nil {
global.LOG.Errorf("restart after container update failed, err: %v", err)
}
}
func loadVolumeBinds(binds []string) []dto.VolumeHelper {
var datas []dto.VolumeHelper
for _, bind := range binds {
parts := strings.Split(bind, ":")
var volumeItem dto.VolumeHelper
if len(parts) > 3 {
continue
}
volumeItem.SourceDir = parts[0]
if len(parts) == 1 {
volumeItem.ContainerDir = parts[0]
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
}

View File

@@ -14,8 +14,10 @@ import (
"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/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
@@ -92,7 +94,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
}
for _, item := range composeCreatedByLocal {
if err := composeRepo.DeleteRecord(commonRepo.WithByName(item.Name)); err != nil {
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
global.LOG.Error(err)
}
}
@@ -101,11 +103,11 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
records = append(records, value)
}
if len(req.Info) != 0 {
lenth, count := len(records), 0
for count < lenth {
length, count := len(records), 0
for count < length {
if !strings.Contains(records[count].Name, req.Info) {
records = append(records[:count], records[(count+1):]...)
lenth--
length--
} else {
count++
}
@@ -127,6 +129,13 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if cmd.CheckIllegal(req.Path) {
return false, buserr.New(constant.ErrCmdIllegal)
}
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
if composeItem.ID != 0 {
return false, constant.ErrRecordExist
}
if err := u.loadPath(&req); err != nil {
return false, err
}
@@ -139,13 +148,16 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
}
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
if cmd.CheckIllegal(req.Name, req.Path) {
return "", buserr.New(constant.ErrCmdIllegal)
}
if err := u.loadPath(&req); err != nil {
return "", err
}
global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name)
if req.From == "path" {
req.Name = path.Base(strings.ReplaceAll(req.Path, "/"+path.Base(req.Path), ""))
req.Name = path.Base(path.Dir(req.Path))
}
logName := path.Dir(req.Path) + "/compose.log"
file, err := os.OpenFile(logName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
@@ -169,10 +181,13 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
_, _ = file.WriteString("docker-compose up successful!")
}()
return logName, nil
return req.Name, nil
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if cmd.CheckIllegal(req.Path, req.Operation) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
@@ -191,6 +206,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
}
func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
if cmd.CheckIllegal(req.Name, req.Path) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}

View File

@@ -24,11 +24,11 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface
return 0, nil, err
}
if len(req.Info) != 0 {
lenth, count := len(list), 0
for count < lenth {
length, count := len(list), 0
for count < length {
if !strings.Contains(list[count].Name, req.Info) {
list = append(list[:count], list[(count+1):]...)
lenth--
length--
} else {
count++
}
@@ -75,6 +75,26 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface
return int64(total), data, nil
}
func (u *ContainerService) ListNetwork() ([]dto.Options, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{})
if err != nil {
return nil, err
}
var datas []dto.Options
for _, item := range list {
datas = append(datas, dto.Options{Option: item.Name})
}
sort.Slice(datas, func(i, j int) bool {
return datas[i].Option < datas[j].Option
})
return datas, nil
}
func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
client, err := docker.NewDockerClient()
if err != nil {
@@ -90,7 +110,7 @@ func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
}
return nil
}
func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error {
func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
client, err := docker.NewDockerClient()
if err != nil {
return err

View File

@@ -24,11 +24,11 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
return 0, nil, err
}
if len(req.Info) != 0 {
lenth, count := len(list.Volumes), 0
for count < lenth {
length, count := len(list.Volumes), 0
for count < length {
if !strings.Contains(list.Volumes[count].Name, req.Info) {
list.Volumes = append(list.Volumes[:count], list.Volumes[(count+1):]...)
lenth--
length--
} else {
count++
}
@@ -80,13 +80,16 @@ func (u *ContainerService) ListVolume() ([]dto.Options, error) {
if err != nil {
return nil, err
}
var data []dto.Options
var datas []dto.Options
for _, item := range list.Volumes {
data = append(data, dto.Options{
datas = append(datas, dto.Options{
Option: item.Name,
})
}
return data, nil
sort.Slice(datas, func(i, j int) bool {
return datas[i].Option < datas[j].Option
})
return datas, nil
}
func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
client, err := docker.NewDockerClient()
@@ -103,7 +106,7 @@ func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
}
return nil
}
func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error {
func (u *ContainerService) CreateVolume(req dto.VolumeCreate) error {
client, err := docker.NewDockerClient()
if err != nil {
return err

View File

@@ -9,6 +9,7 @@ import (
"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"
@@ -29,6 +30,8 @@ type ICronjobService interface {
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob) (int, error)
CleanRecord(req dto.CronjobClean) error
LoadRecordLog(req dto.OperateByID) (string, error)
}
func NewICronjobService() ICronjobService {
@@ -36,7 +39,7 @@ func NewICronjobService() ICronjobService {
}
func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info))
total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
var dtoCronjobs []dto.CronjobInfo
for _, cronjob := range cronjobs {
var item dto.CronjobInfo
@@ -53,9 +56,9 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
}
record, _ := cronjobRepo.RecordFirst(cronjob.ID)
if record.ID != 0 {
item.LastRecrodTime = record.StartTime.Format("2006-01-02 15:04:05")
item.LastRecordTime = record.StartTime.Format("2006-01-02 15:04:05")
} else {
item.LastRecrodTime = "-"
item.LastRecordTime = "-"
}
dtoCronjobs = append(dtoCronjobs, item)
}
@@ -80,12 +83,27 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
if err != nil {
return "", err
}
if _, err := os.Stat(record.Records); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
content, err := os.ReadFile(record.Records)
if err != nil {
return "", err
}
return string(content), nil
}
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
cronjob, err := cronjobRepo.Get(commonRepo.WithByID(req.CronjobID))
if err != nil {
return err
}
if req.CleanData && cronjob.Type != "shell" && cronjob.Type != "curl" {
if req.CleanData && (cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory") {
cronjob.RetainCopies = 0
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
@@ -100,9 +118,9 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if err != nil {
return err
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
} else {
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
}
}
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
@@ -123,29 +141,27 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
if record.ID == 0 {
return "", constant.ErrRecordNotFound
}
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(record.CronjobID))
if cronjob.ID == 0 {
return "", constant.ErrRecordNotFound
}
backup, _ := backupRepo.Get(commonRepo.WithByID(down.BackupAccountID))
if cronjob.ID == 0 {
if backup.ID == 0 {
return "", constant.ErrRecordNotFound
}
if backup.Type == "LOCAL" || record.FromLocal {
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
return "", constant.ErrRecordNotFound
return "", err
}
return record.File, nil
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, record.File)
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", constant.ErrRecordNotFound
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", err
}
}
return tempPath, nil
}
@@ -190,7 +206,6 @@ func (u *CronjobService) StartJob(cronjob *model.Cronjob) (int, error) {
if err != nil {
return 0, err
}
global.LOG.Debug(global.Cron.Entries())
return entryID, nil
}
@@ -222,24 +237,30 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
if err != nil {
return constant.ErrRecordNotFound
}
upMap := make(map[string]interface{})
cronjob.EntryID = cronModel.EntryID
cronjob.Type = cronModel.Type
cronjob.Spec = loadSpec(cronjob)
newEntryID, err := u.StartJob(&cronjob)
if err != nil {
return err
if cronModel.Status == constant.StatusEnable {
newEntryID, err := u.StartJob(&cronjob)
if err != nil {
return err
}
upMap["entry_id"] = newEntryID
} else {
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
}
upMap := make(map[string]interface{})
upMap["entry_id"] = newEntryID
upMap["name"] = req.Name
upMap["spec"] = cronjob.Spec
upMap["script"] = req.Script
upMap["container_name"] = req.ContainerName
upMap["spec_type"] = req.SpecType
upMap["week"] = req.Week
upMap["day"] = req.Day
upMap["hour"] = req.Hour
upMap["minute"] = req.Minute
upMap["second"] = req.Second
upMap["website"] = req.Website
upMap["exclusion_rules"] = req.ExclusionRules
upMap["db_name"] = req.DBName
@@ -322,6 +343,8 @@ func loadSpec(cronjob model.Cronjob) string {
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 ""
}

View File

@@ -5,15 +5,18 @@ import (
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"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"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/pkg/errors"
)
@@ -29,31 +32,35 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
if len(cronjob.Script) == 0 {
return
}
stdout, errExec := cmd.ExecCronjobWithTimeOut(cronjob.Script, 5*time.Minute)
if errExec != nil {
err = errExec
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)
}
message = []byte(stdout)
u.HandleRmExpired("LOCAL", "", cronjob, nil)
case "website":
record.File, err = u.HandleBackup(cronjob, record.StartTime)
case "database":
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.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "curl":
if len(cronjob.URL) == 0 {
return
}
stdout, errCurl := cmd.ExecWithTimeOut("curl "+cronjob.URL, 5*time.Minute)
if err != nil {
err = errCurl
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL))
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "ntp":
err = u.handleNtpSync()
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "website":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "database":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "directory":
if len(cronjob.SourceDir) == 0 {
return
}
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "cutWebsiteLog":
record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime)
if err != nil {
global.LOG.Errorf("cut website log file failed, err: %v", err)
}
message = []byte(stdout)
u.HandleRmExpired("LOCAL", "", cronjob, nil)
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
@@ -69,7 +76,36 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
}()
}
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
func (u *CronjobService) handleShell(cronType, cornName, script string) ([]byte, 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
}
}
stdout, err := cmd.ExecCronjobWithTimeOut(script, handleDir, 24*time.Hour)
if err != nil {
return nil, err
}
return []byte(stdout), nil
}
func (u *CronjobService) handleNtpSync() error {
ntpServer, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
if err != nil {
return err
}
ntime, err := ntp.GetRemoteTime(ntpServer.Value)
if err != nil {
return err
}
if err := ntp.UpdateSystemTime(ntime.Format("2006-01-02 15:04:05")); err != nil {
return err
}
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
@@ -82,18 +118,14 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
switch cronjob.Type {
case "database":
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return "", err
}
paths, err := u.handleDatabase(*cronjob, app, backup, startTime)
paths, err := u.handleDatabase(*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 := fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
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 {
@@ -110,37 +142,55 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
if err != nil {
return "", err
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
return "", err
}
}
u.HandleRmExpired(backup.Type, localDir, cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
if backup.Type == "LOCAL" || cronjob.KeepLocal {
return fmt.Sprintf("%s/%s/%s/%s", localDir, cronjob.Type, cronjob.Name, fileName), nil
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
} else {
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
}
return fmt.Sprintf("%s/%s/%s", cronjob.Type, cronjob.Name, fileName), nil
}
}
func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) > int(cronjob.RetainCopies) {
for i := int(cronjob.RetainCopies); i < len(records); i++ {
if len(records) <= 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 {
if backType != "LOCAL" {
_, _ = backClient.Delete(strings.ReplaceAll(file, localDir+"/", ""))
_ = os.Remove(file)
} else {
_ = os.Remove(file)
}
_ = os.Remove(file)
_ = backupRepo.DeleteRecord(context.TODO(), backupRepo.WithByFileName(path.Base(file)))
}
if backType == "LOCAL" {
continue
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
fileItem := file
if cronjob.KeepLocal {
if len(backupPath) != 0 {
itemPath := strings.TrimPrefix(backupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
fileItem = itemPath + strings.TrimPrefix(file, localDir+"/")
} else {
fileItem = strings.TrimPrefix(file, localDir+"/")
}
}
_, _ = backClient.Delete(fileItem)
}
}
_ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].Records)
}
}
@@ -151,18 +201,21 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
}
}
excludes := strings.Split(exclusionRules, ";")
excludes := strings.Split(exclusionRules, ",")
excludeRules := ""
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
}
excludeRules += (" --exclude " + exclude)
excludeRules += " --exclude " + exclude
}
path := ""
if strings.Contains(sourceDir, "/") {
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
aheadDir := strings.ReplaceAll(sourceDir, itemDir, "")
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
if len(aheadDir) == 0 {
aheadDir = "/"
}
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
} else {
path = sourceDir
@@ -170,7 +223,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
commands := fmt.Sprintf("tar zcvf %s %s %s", targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
@@ -187,7 +240,7 @@ func handleUnTar(sourceFile, targetDir string) error {
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 5*time.Minute)
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
@@ -195,22 +248,25 @@ func handleUnTar(sourceFile, targetDir string) error {
return nil
}
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInfo, backup model.BackupAccount, startTime time.Time) ([]string, error) {
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 dblist []string
var dbs []model.DatabaseMysql
if cronjob.DBName == "all" {
mysqlService := NewIMysqlService()
dblist, err = mysqlService.ListDBName()
dbs, err = mysqlRepo.List()
if err != nil {
return paths, err
}
} else {
dblist = append(dblist, cronjob.DBName)
itemID, _ := (strconv.Atoi(cronjob.DBName))
dbs, err = mysqlRepo.List(commonRepo.WithByID(uint(itemID)))
if err != nil {
return paths, err
}
}
var client cloud_storage.CloudStorageClient
@@ -221,27 +277,27 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
}
}
for _, dbName := range dblist {
for _, dbInfo := range dbs {
var record model.BackupRecord
record.Type = "mysql"
record.Name = app.Name
record.Source = "LOCAL"
record.BackupType = backup.Type
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, dbName)
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
if err = handleMysqlBackup(app, backupDir, dbName, record.FileName); err != nil {
record.Name = dbInfo.MysqlName
backupDir := path.Join(localDir, fmt.Sprintf("database/mysql/%s/%s", 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 = dbName
record.DetailName = dbInfo.Name
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = itemFileDir
}
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
@@ -253,15 +309,91 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) (string, error) {
var (
websites []string
err error
filePaths []string
)
if cronjob.Website == "all" {
websites, _ = NewIWebsiteService().GetWebsiteOptions()
if len(websites) == 0 {
return "", nil
}
} else {
websites = append(websites, cronjob.Website)
}
nginx, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return "", 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)
}
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
}
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
@@ -297,15 +429,14 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
if err != nil {
return paths, err
}
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.PrimaryDomain))
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
}
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil {
return paths, err
}
@@ -320,11 +451,21 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"
@@ -17,8 +18,11 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/mysql"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -27,12 +31,13 @@ import (
type MysqlService struct{}
type IMysqlService interface {
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
ListDBName() ([]string, error)
SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error)
ListDBOption() ([]dto.MysqlOption, error)
Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error)
LoadFromRemote(from string) error
ChangeAccess(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
UpdateVariables(updates []dto.MysqlVariablesUpdate) error
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(id uint) ([]string, error)
@@ -41,14 +46,20 @@ type IMysqlService interface {
LoadVariables() (*dto.MysqlVariables, error)
LoadBaseInfo() (*dto.DBBaseInfo, error)
LoadRemoteAccess() (bool, error)
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
}
func NewIMysqlService() IMysqlService {
return &MysqlService{}
}
func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info))
func (u *MysqlService) SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error) {
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize,
mysqlRepo.WithByFrom(search.From),
commonRepo.WithLikeName(search.Info),
commonRepo.WithOrderRuleBy(search.OrderBy, search.Order),
)
var dtoMysqls []dto.MysqlDBInfo
for _, mysql := range mysqls {
var item dto.MysqlDBInfo
@@ -60,70 +71,115 @@ func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interfa
return total, dtoMysqls, err
}
func (u *MysqlService) ListDBName() ([]string, error) {
func (u *MysqlService) ListDBOption() ([]dto.MysqlOption, error) {
mysqls, err := mysqlRepo.List()
var dbNames []string
var dbs []dto.MysqlOption
for _, mysql := range mysqls {
dbNames = append(dbNames, mysql.Name)
var item dto.MysqlOption
if err := copier.Copy(&item, &mysql); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dbs = append(dbs, item)
}
return dbNames, err
}
var formatMap = map[string]string{
"utf8": "utf8_general_ci",
"utf8mb4": "utf8mb4_general_ci",
"gbk": "gbk_chinese_ci",
"big5": "big5_chinese_ci",
return dbs, err
}
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
if req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Permission) {
return nil, buserr.New(constant.ErrCmdIllegal)
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return nil, err
}
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name))
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name), remoteDBRepo.WithByFrom(req.From))
if mysql.ID != 0 {
return nil, constant.ErrRecordExist
}
if err := copier.Copy(&mysql, &req); err != nil {
var createItem model.DatabaseMysql
if err := copier.Copy(&createItem, &req); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", req.Name, req.Format, formatMap[req.Format])
if err := excSQL(app.ContainerName, app.Password, createSql); err != nil {
if strings.Contains(err.Error(), "ERROR 1007") {
return nil, buserr.New(constant.ErrDatabaseIsExist)
}
if req.From == "local" && req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
}
cli, version, err := LoadMysqlClientByFrom(req.From)
if err != nil {
return nil, err
}
tmpPermission := req.Permission
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("create user '%s'@'%s' identified by '%s';", req.Username, tmpPermission, req.Password)); err != nil {
_ = excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", req.Name))
if strings.Contains(err.Error(), "ERROR 1396") {
return nil, buserr.New(constant.ErrUserIsExist)
if req.From == "local" {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return nil, err
}
return nil, err
createItem.MysqlName = app.Name
} else {
createItem.MysqlName = req.From
}
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to '%s'@'%s'", req.Name, req.Username, tmpPermission)
if req.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to '%s'@'%s'", mysql.Username, tmpPermission)
}
if app.Version == "5.7.39" {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
}
if err := excSQL(app.ContainerName, app.Password, grantStr); err != nil {
defer cli.Close()
if err := cli.Create(client.CreateInfo{
Name: req.Name,
Format: req.Format,
Username: req.Username,
Password: req.Password,
Permission: req.Permission,
Version: version,
Timeout: 300,
}); err != nil {
return nil, err
}
global.LOG.Infof("create database %s successful!", req.Name)
mysql.MysqlName = app.Name
if err := mysqlRepo.Create(ctx, &mysql); err != nil {
if err := mysqlRepo.Create(ctx, &createItem); err != nil {
return nil, err
}
return &mysql, nil
return &createItem, nil
}
func (u *MysqlService) LoadFromRemote(from string) error {
client, version, err := LoadMysqlClientByFrom(from)
if err != nil {
return err
}
mysqlName := from
if from == "local" {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return err
}
mysqlName = app.Name
}
databases, err := mysqlRepo.List(remoteDBRepo.WithByFrom(from))
if err != nil {
return err
}
datas, err := client.SyncDB(version)
if err != nil {
return err
}
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) {
hasOld = true
break
}
}
if !hasOld {
var createItem model.DatabaseMysql
if err := copier.Copy(&createItem, &data); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
createItem.MysqlName = mysqlName
if err := mysqlRepo.Create(context.Background(), &createItem); err != nil {
return err
}
}
}
return nil
}
func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
@@ -132,46 +188,62 @@ func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
func (u *MysqlService) DeleteCheck(id uint) ([]string, error) {
var appInUsed []string
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return appInUsed, err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(id))
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)
if db.From == "local" {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
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 *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error {
db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil && !req.ForceDelete {
return err
}
cli, version, err := LoadMysqlClientByFrom(db.From)
if err != nil {
return err
}
defer cli.Close()
if err := cli.Delete(client.DeleteInfo{
Name: db.Name,
Version: version,
Username: db.Username,
Permission: db.Permission,
Timeout: 300,
}); err != nil {
return err
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil && !req.ForceDelete {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil && !req.ForceDelete {
return err
}
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
return err
}
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
return err
}
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
uploadDir := fmt.Sprintf("%s/1panel/uploads/database/mysql/%s/%s", global.CONF.System.BaseDir, app.Name, db.Name)
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/database/mysql/%s/%s", app.Name, db.Name))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
@@ -180,7 +252,7 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
if err != nil && !req.ForceDelete {
return err
}
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, db.MysqlName, db.Name)
backupDir := path.Join(localDir, fmt.Sprintf("database/mysql/%s/%s", db.MysqlName, db.Name))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
@@ -193,27 +265,48 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
}
func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
var (
mysql model.DatabaseMysql
err error
)
if info.ID != 0 {
mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
if err != nil {
return err
}
if cmd.CheckIllegal(info.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
cli, version, err := LoadMysqlClientByFrom(info.From)
if err != nil {
return err
}
defer cli.Close()
var (
mysqlData model.DatabaseMysql
passwordInfo client.PasswordChangeInfo
)
passwordInfo.Password = info.Value
passwordInfo.Timeout = 300
passwordInfo.Version = version
passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value)
if app.Version != "5.7.39" {
passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value)
}
if info.ID != 0 {
appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysql.ID))
mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
if err != nil {
return err
}
passwordInfo.Name = mysqlData.Name
passwordInfo.Username = mysqlData.Username
passwordInfo.Permission = mysqlData.Permission
} else {
passwordInfo.Username = "root"
}
if err := cli.ChangePassword(passwordInfo); err != nil {
return err
}
if info.ID != 0 {
var appRess []model.AppInstallResource
if info.From == "local" {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return err
}
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.ID))
} else {
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(mysqlData.ID))
}
for _, appRes := range appRess {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
if err != nil {
@@ -229,29 +322,11 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
return err
}
}
if err := excuteSql(app.ContainerName, app.Password, passwordChangeCMD); err != nil {
return err
}
global.LOG.Info("excute password change sql successful")
_ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"password": info.Value})
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": info.Value})
return nil
}
hosts, err := excuteSqlForRows(app.ContainerName, app.Password, "select host from mysql.user where user='root';")
if err != nil {
return err
}
for _, host := range hosts {
if host == "%" || host == "localhost" {
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value)
if app.Version != "5.7.39" {
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value)
}
if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil {
return err
}
}
}
if err := updateInstallInfoInDB("mysql", "", "password", false, info.Value); err != nil {
return err
}
@@ -262,56 +337,41 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
}
func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
var (
mysql model.DatabaseMysql
err error
)
if info.ID != 0 {
mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
if err != nil {
return err
}
if cmd.CheckIllegal(info.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
cli, version, err := LoadMysqlClientByFrom(info.From)
if err != nil {
return err
}
if info.ID == 0 {
mysql.Name = "*"
mysql.Username = "root"
mysql.Permission = "%"
mysql.Password = app.Password
}
defer cli.Close()
var (
mysqlData model.DatabaseMysql
accessInfo client.AccessChangeInfo
)
accessInfo.Permission = info.Value
accessInfo.Timeout = 300
accessInfo.Version = version
if info.Value != mysql.Permission {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, mysql.Permission)); err != nil {
if info.ID != 0 {
mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
if err != nil {
return err
}
if info.ID == 0 {
return nil
}
accessInfo.Name = mysqlData.Name
accessInfo.Username = mysqlData.Username
accessInfo.Password = mysqlData.Password
accessInfo.OldPermission = mysqlData.Permission
} else {
accessInfo.Username = "root"
}
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("create user if not exists '%s'@'%s' identified by '%s';", mysql.Username, info.Value, mysql.Password)); err != nil {
if err := cli.ChangeAccess(accessInfo); err != nil {
return err
}
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to '%s'@'%s'", mysql.Name, mysql.Username, info.Value)
if mysql.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to '%s'@'%s'", mysql.Username, info.Value)
}
if app.Version == "5.7.39" {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, mysql.Password)
}
if err := excuteSql(app.ContainerName, app.Password, grantStr); err != nil {
return err
}
if err := excuteSql(app.ContainerName, app.Password, "flush privileges"); err != nil {
return err
}
if info.ID == 0 {
return nil
}
_ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"permission": info.Value})
if mysqlData.ID != 0 {
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"permission": info.Value})
}
return nil
}
@@ -336,7 +396,7 @@ func (u *MysqlService) UpdateConfByFile(info dto.MysqlConfUpdateByFile) error {
return nil
}
func (u *MysqlService) UpdateVariables(updatas []dto.MysqlVariablesUpdate) error {
func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return err
@@ -351,8 +411,8 @@ func (u *MysqlService) UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
files = strings.Split(string(lineBytes), "\n")
group := "[mysqld]"
for _, info := range updatas {
if app.Version != "5.7.39" {
for _, info := range updates {
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
if info.Param == "query_cache_size" {
continue
}
@@ -475,6 +535,26 @@ func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
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 "redis-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/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 excuteSqlForMaps(containerName, password, command string) (map[string]string, error) {
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
@@ -504,31 +584,6 @@ func excuteSqlForRows(containerName, password, command string) ([]string, error)
return strings.Split(stdStr, "\n"), nil
}
func excuteSql(containerName, password, command string) error {
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
}
func excSQL(containerName, password, command string) error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return buserr.WithDetail(constant.ErrExecTimeOut, containerName, nil)
}
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
}
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
isOn := false
hasGroup := false
@@ -569,3 +624,41 @@ func updateMyCnf(oldFiles []string, group string, param string, value interface{
}
return newFiles
}
func LoadMysqlClientByFrom(from string) (mysql.MysqlClient, string, error) {
var (
dbInfo client.DBInfo
version string
err error
)
dbInfo.From = from
dbInfo.Timeout = 300
if from != "local" {
databaseItem, err := remoteDBRepo.Get(commonRepo.WithByName(from))
if err != nil {
return nil, "", err
}
dbInfo.Address = databaseItem.Address
dbInfo.Port = databaseItem.Port
dbInfo.Username = databaseItem.Username
dbInfo.Password = databaseItem.Password
version = databaseItem.Version
} else {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return nil, "", err
}
dbInfo.Address = app.ContainerName
dbInfo.Username = "root"
dbInfo.Password = app.Password
version = app.Version
}
cli, err := mysql.NewMysqlClient(dbInfo)
if err != nil {
return nil, "", err
}
return cli, version, nil
}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
@@ -163,7 +164,7 @@ func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interf
if err != nil {
return 0, nil, err
}
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil

View File

@@ -4,6 +4,7 @@ import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
@@ -18,7 +19,8 @@ import (
type DockerService struct{}
type IDockerService interface {
UpdateConf(req dto.DaemonJsonConf) error
UpdateConf(req dto.SettingUpdate) error
UpdateLogOption(req dto.LogOption) error
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
LoadDockerStatus() string
LoadDockerConf() *dto.DaemonJsonConf
@@ -30,46 +32,54 @@ func NewIDockerService() IDockerService {
}
type daemonJsonItem struct {
Status string `json:"status"`
Mirrors []string `json:"registry-mirrors"`
Registries []string `json:"insecure-registries"`
LiveRestore bool `json:"live-restore"`
IPTables bool `json:"iptables"`
ExecOpts []string `json:"exec-opts"`
Status string `json:"status"`
Mirrors []string `json:"registry-mirrors"`
Registries []string `json:"insecure-registries"`
LiveRestore bool `json:"live-restore"`
IPTables bool `json:"iptables"`
ExecOpts []string `json:"exec-opts"`
LogOption logOption `json:"log-opts"`
}
type logOption struct {
LogMaxSize string `json:"max-size"`
LogMaxFile string `json:"max-file"`
}
func (u *DockerService) LoadDockerStatus() string {
status := constant.StatusRunning
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) != "active\n" || err != nil {
status = constant.Stopped
client, err := docker.NewDockerClient()
if err != nil {
return constant.Stopped
}
if _, err := client.Ping(context.Background()); err != nil {
return constant.Stopped
}
return status
return constant.StatusRunning
}
func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
ctx := context.Background()
var data dto.DaemonJsonConf
data.IPTables = true
data.Status = constant.StatusRunning
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) != "active\n" || err != nil {
data.Version = "-"
client, err := docker.NewDockerClient()
if err != nil {
data.Status = constant.Stopped
} else {
if _, err := client.Ping(ctx); err != nil {
data.Status = constant.Stopped
}
itemVersion, err := client.ServerVersion(ctx)
if err == nil {
data.Version = itemVersion.Version
}
}
data.IsSwarm = false
stdout2, _ := cmd.Exec("docker info | grep Swarm")
if string(stdout2) == " Swarm: active\n" {
data.IsSwarm = true
}
data.Version = "-"
client, err := docker.NewDockerClient()
if err == nil {
ctx := context.Background()
itemVersion, err := client.ServerVersion(ctx)
if err == nil {
data.Version = itemVersion.Version
}
}
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
return &data
}
@@ -78,18 +88,19 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
return &data
}
var conf daemonJsonItem
deamonMap := make(map[string]interface{})
if err := json.Unmarshal(file, &deamonMap); err != nil {
daemonMap := make(map[string]interface{})
if err := json.Unmarshal(file, &daemonMap); err != nil {
return &data
}
arr, err := json.Marshal(deamonMap)
arr, err := json.Marshal(daemonMap)
if err != nil {
return &data
}
if err := json.Unmarshal(arr, &conf); err != nil {
fmt.Println(err)
return &data
}
if _, ok := deamonMap["iptables"]; !ok {
if _, ok := daemonMap["iptables"]; !ok {
conf.IPTables = true
}
data.CgroupDriver = "cgroupfs"
@@ -99,6 +110,8 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
break
}
}
data.LogMaxSize = conf.LogOption.LogMaxSize
data.LogMaxFile = conf.LogOption.LogMaxFile
data.Mirrors = conf.Mirrors
data.Registries = conf.Registries
data.IPTables = conf.IPTables
@@ -106,7 +119,7 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
return &data
}
func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
@@ -118,50 +131,98 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
if err != nil {
return err
}
deamonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &deamonMap)
daemonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &daemonMap)
if len(req.Registries) == 0 {
delete(deamonMap, "insecure-registries")
} else {
deamonMap["insecure-registries"] = req.Registries
}
if len(req.Mirrors) == 0 {
delete(deamonMap, "registry-mirrors")
} else {
deamonMap["registry-mirrors"] = req.Mirrors
}
if !req.LiveRestore {
delete(deamonMap, "live-restore")
} else {
deamonMap["live-restore"] = req.LiveRestore
}
if req.IPTables {
delete(deamonMap, "iptables")
} else {
deamonMap["iptables"] = false
}
if opts, ok := deamonMap["exec-opts"]; ok {
if optsValue, isArray := opts.([]interface{}); isArray {
for i := 0; i < len(optsValue); i++ {
if opt, isStr := optsValue[i].(string); isStr {
if strings.HasPrefix(opt, "native.cgroupdriver=") {
optsValue[i] = "native.cgroupdriver=" + req.CgroupDriver
break
switch req.Key {
case "Registries":
req.Value = strings.TrimSuffix(req.Value, ",")
if len(req.Value) == 0 {
delete(daemonMap, "insecure-registries")
} else {
daemonMap["insecure-registries"] = strings.Split(req.Value, ",")
}
case "Mirrors":
req.Value = strings.TrimSuffix(req.Value, ",")
if len(req.Value) == 0 {
delete(daemonMap, "registry-mirrors")
} else {
daemonMap["registry-mirrors"] = strings.Split(req.Value, ",")
}
case "LogOption":
if req.Value == "disable" {
delete(daemonMap, "log-opts")
}
case "LiveRestore":
if req.Value == "disable" {
delete(daemonMap, "live-restore")
} else {
daemonMap["live-restore"] = true
}
case "IPtables":
if req.Value == "enable" {
delete(daemonMap, "iptables")
} else {
daemonMap["iptables"] = false
}
case "Dirver":
if opts, ok := daemonMap["exec-opts"]; ok {
if optsValue, isArray := opts.([]interface{}); isArray {
for i := 0; i < len(optsValue); i++ {
if opt, isStr := optsValue[i].(string); isStr {
if strings.HasPrefix(opt, "native.cgroupdriver=") {
optsValue[i] = "native.cgroupdriver=" + req.Value
break
}
}
}
}
}
} else {
if req.CgroupDriver == "systemd" {
deamonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"}
} else {
if req.Value == "systemd" {
daemonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"}
}
}
}
if len(deamonMap) == 0 {
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
return nil
}
newJson, err := json.MarshalIndent(deamonMap, "", "\t")
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
}
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
}
_, _ = os.Create(constant.DaemonJsonPath)
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
}
daemonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &daemonMap)
changeLogOption(daemonMap, req.LogMaxFile, req.LogMaxSize)
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
}
@@ -206,10 +267,7 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
if req.Operation == "stop" {
service = "docker.service"
if req.StopSocket {
service = "docker.socket"
}
service = "docker.socket"
}
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
if err != nil {
@@ -217,3 +275,52 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
}
return nil
}
func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize string) {
if opts, ok := daemonMap["log-opts"]; ok {
if len(logMaxFile) != 0 || len(logMaxSize) != 0 {
daemonMap["log-driver"] = "json-file"
}
optsMap, isMap := opts.(map[string]interface{})
if isMap {
if len(logMaxFile) != 0 {
optsMap["max-file"] = logMaxFile
} else {
delete(optsMap, "max-file")
}
if len(logMaxSize) != 0 {
optsMap["max-size"] = logMaxSize
} else {
delete(optsMap, "max-size")
}
if len(optsMap) == 0 {
delete(daemonMap, "log-opts")
}
} else {
optsMap := make(map[string]interface{})
if len(logMaxFile) != 0 {
optsMap["max-file"] = logMaxFile
}
if len(logMaxSize) != 0 {
optsMap["max-size"] = logMaxSize
}
if len(optsMap) != 0 {
daemonMap["log-opts"] = optsMap
}
}
} else {
if len(logMaxFile) != 0 || len(logMaxSize) != 0 {
daemonMap["log-driver"] = "json-file"
}
optsMap := make(map[string]interface{})
if len(logMaxFile) != 0 {
optsMap["max-file"] = logMaxFile
}
if len(logMaxSize) != 0 {
optsMap["max-size"] = logMaxSize
}
if len(optsMap) != 0 {
daemonMap["log-opts"] = optsMap
}
}
}

View File

@@ -12,7 +12,8 @@ var (
appInstallRepo = repo.NewIAppInstallRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
mysqlRepo = repo.NewIMysqlRepo()
mysqlRepo = repo.NewIMysqlRepo()
remoteDBRepo = repo.NewIRemoteDBRepo()
imageRepoRepo = repo.NewIImageRepoRepo()
composeRepo = repo.NewIComposeTemplateRepo()

View File

@@ -27,7 +27,7 @@ type IFirewallService interface {
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
UpdatePortRule(req dto.PortRuleUpdate) error
UpdateAddrRule(req dto.AddrRuleUpdate) error
BacthOperateRule(req dto.BatchRuleOperate) error
BatchOperateRule(req dto.BatchRuleOperate) error
}
func NewIFirewallService() IFirewallService {
@@ -276,7 +276,7 @@ func (u *FirewallService) UpdateAddrRule(req dto.AddrRuleUpdate) error {
return client.Reload()
}
func (u *FirewallService) BacthOperateRule(req dto.BatchRuleOperate) error {
func (u *FirewallService) BatchOperateRule(req dto.BatchRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
@@ -304,7 +304,6 @@ func OperateFirewallPort(oldPorts, newPorts []int) error {
return err
}
for _, port := range newPorts {
if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
@@ -368,18 +367,16 @@ func (u *FirewallService) pingStatus() string {
if _, err := os.Stat("/etc/sysctl.conf"); err != nil {
return constant.StatusNone
}
commond := "cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= "
if cmd.HasNoPasswordSudo() {
commond = "sudo cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= "
}
stdout, _ := cmd.Exec(commond)
sudo := cmd.SudoHandleCmd()
command := fmt.Sprintf("%s cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= ", sudo)
stdout, _ := cmd.Exec(command)
if stdout == "net/ipv4/icmp_echo_ignore_all=1\n" {
return constant.StatusEnable
}
return constant.StatusDisable
}
func (u *FirewallService) updatePingStatus(enabel string) error {
func (u *FirewallService) updatePingStatus(enable string) error {
lineBytes, err := os.ReadFile(confPath)
if err != nil {
return err
@@ -389,14 +386,14 @@ func (u *FirewallService) updatePingStatus(enabel string) error {
hasLine := false
for _, line := range files {
if strings.Contains(line, "net/ipv4/icmp_echo_ignore_all") || strings.HasPrefix(line, "net/ipv4/icmp_echo_ignore_all") {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enabel)
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable)
hasLine = true
} else {
newFiles = append(newFiles, line)
}
}
if !hasLine {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enabel)
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable)
}
file, err := os.OpenFile(confPath, os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
@@ -408,11 +405,9 @@ func (u *FirewallService) updatePingStatus(enabel string) error {
return err
}
commond := "sysctl -p"
if cmd.HasNoPasswordSudo() {
commond = "sudo sysctl -p"
}
stdout, err := cmd.Exec(commond)
sudo := cmd.SudoHandleCmd()
command := fmt.Sprintf("%s sysctl -p", sudo)
stdout, err := cmd.Exec(command)
if err != nil {
return fmt.Errorf("update ping status failed, err: %v", stdout)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/ssh"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -23,6 +24,8 @@ type IHostService interface {
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
Update(id uint, upMap map[string]interface{}) error
Delete(id []uint) error
EncryptHost(itemVal string) (string, error)
}
func NewIHostService() IHostService {
@@ -89,8 +92,25 @@ func (u *HostService) TestLocalConn(id uint) bool {
if err := copier.Copy(&connInfo, &host); err != nil {
return false
}
connInfo.PrivateKey = []byte(host.PrivateKey)
if len(host.Password) != 0 {
host.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return false
}
connInfo.Password = host.Password
}
if len(host.PrivateKey) != 0 {
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return false
}
connInfo.PrivateKey = []byte(host.PrivateKey)
}
if len(host.PassPhrase) != 0 {
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return false
}
connInfo.PassPhrase = []byte(host.PassPhrase)
}
client, err := connInfo.NewClient()
@@ -107,6 +127,25 @@ func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
if err != nil {
return nil, constant.ErrRecordNotFound
}
if len(host.Password) != 0 {
host.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return nil, err
}
}
if len(host.PrivateKey) != 0 {
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return nil, err
}
}
if len(host.PassPhrase) != 0 {
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return nil, err
}
}
return &host, err
}
@@ -127,6 +166,25 @@ func (u *HostService) SearchWithPage(search dto.SearchHostWithPage) (int64, inte
item.Password = ""
item.PrivateKey = ""
item.PassPhrase = ""
} else {
if len(host.Password) != 0 {
item.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return 0, nil, err
}
}
if len(host.PrivateKey) != 0 {
item.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return 0, nil, err
}
}
if len(host.PassPhrase) != 0 {
item.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return 0, nil, err
}
}
}
dtoHosts = append(dtoHosts, item)
}
@@ -164,6 +222,28 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
}
func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
var err error
if len(req.Password) != 0 && req.AuthMode == "password" {
req.Password, err = u.EncryptHost(req.Password)
if err != nil {
return nil, err
}
req.PrivateKey = ""
req.PassPhrase = ""
}
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
req.PrivateKey, err = u.EncryptHost(req.PrivateKey)
if err != nil {
return nil, err
}
if len(req.PassPhrase) != 0 {
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
return nil, err
}
}
req.Password = ""
}
var host model.Host
if err := copier.Copy(&host, &req); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
@@ -234,3 +314,12 @@ func (u *HostService) Delete(ids []uint) error {
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
return hostRepo.Update(id, upMap)
}
func (u *HostService) EncryptHost(itemVal string) (string, error) {
privateKey, err := base64.StdEncoding.DecodeString(itemVal)
if err != nil {
return "", err
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
return keyItem, err
}

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