Compare commits

...

238 Commits

Author SHA1 Message Date
耗子 637fb83398 feat: 发布v2.3.20 2024-11-26 04:15:29 +08:00
耗子 ff67ace165 feat: 添加CLI命令 2024-11-26 04:06:04 +08:00
renovate[bot] c1580afadf chore(deps): Update all dependencies (#390)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 03:52:29 +08:00
耗子 9a2e68e24e feat: 数据库管理基本完成 2024-11-26 03:50:54 +08:00
耗子 3fbaecbe9e feat: 数据库用户管理2 2024-11-26 03:19:33 +08:00
耗子 a2c96993d3 feat: update node 22 2024-11-26 02:40:02 +08:00
耗子 2208ff1c29 fix: build 2024-11-26 02:27:24 +08:00
耗子 146c9aadbd feat: update eslint to v9 2024-11-26 02:19:20 +08:00
耗子 929219e487 feat: update eslint to v9 2024-11-26 02:18:45 +08:00
耗子 7aca6587af fix: 文件名称检查支持中文 2024-11-26 01:18:08 +08:00
renovate[bot] a8575bcda9 chore(deps): Lock file maintenance (#388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 22:10:09 +08:00
renovate[bot] bdaa3c005b chore(deps): Update module github.com/urfave/cli/v3 to v3.0.0-alpha9.5 (#389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 22:09:50 +08:00
renovate[bot] e6fe1e48f7 chore(deps): Update all dependencies (#387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 22:07:01 +08:00
耗子 33cb41cf42 feat: 优化数据库信息获取 2024-11-25 03:18:36 +08:00
耗子 0df2a9107d feat: 数据库用户接口 2024-11-25 03:01:02 +08:00
耗子 7f61793b36 feat: php 8.4 2024-11-25 01:45:19 +08:00
耗子 7d9de56a82 feat: 重构数据库1 2024-11-24 20:46:25 +08:00
renovate[bot] 18148b15b0 chore(deps): Update all dependencies (#385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-23 06:31:26 +00:00
耗子 ad989337b3 docs: update README 2024-11-23 03:07:20 +08:00
耗子 81d32420c0 docs: update README 2024-11-23 02:55:15 +08:00
耗子 5538f7498b fix: lock typescript to fix build 2024-11-23 02:47:19 +08:00
耗子 371d2b3ac0 Merge remote-tracking branch 'origin/main' 2024-11-23 02:37:39 +08:00
renovate[bot] ff17aee372 chore(deps): Update all dependencies (#383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-23 02:37:31 +08:00
耗子 9e1d525b20 feat: 数据库权限 2024-11-23 01:53:08 +08:00
renovate[bot] f6526c15fa chore(deps): Update all dependencies (#382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-22 23:48:18 +08:00
耗子 be3ae54efe fix: lint 2024-11-22 23:29:49 +08:00
耗子 214b804062 feat: 数据库同步及fail2ban重构 2024-11-22 22:46:26 +08:00
耗子 e1f074f116 feat: 防火墙正确处理多ip规则 2024-11-22 19:52:02 +08:00
耗子 a96bbf3dfb feat: 数据库管理提交1 2024-11-22 02:58:31 +08:00
renovate[bot] 39d2eee676 chore(deps): Update dependency cronstrue to v2.52.0 (#381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-22 02:19:32 +08:00
renovate[bot] 8c17b295a2 chore(deps): Update all dependencies (#380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 12:30:55 +00:00
renovate[bot] 452ef92d8f chore(deps): Update dependency marked to v15.0.2 (#379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 04:33:59 +00:00
耗子 df49e167a9 feat: 优化php状态使用json接口获取 2024-11-20 20:26:18 +08:00
耗子 f3aa240aa9 Merge remote-tracking branch 'origin/main' 2024-11-20 20:24:22 +08:00
renovate[bot] d81f4df8b6 chore(deps): Update module github.com/go-resty/resty/v2 to v2.16.1 (#378)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:42:04 +08:00
renovate[bot] 4c2f9f1bbc chore(deps): Update dependency @types/node to v22.9.1 (#377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 21:49:41 +00:00
renovate[bot] e2080b0603 chore(deps): Update dependency @iconify/json to v2.2.274 (#376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 18:55:05 +00:00
renovate[bot] 53eb005531 chore(deps): Update dependency md-editor-v3 to v5.0.2 (#375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 13:21:45 +00:00
renovate[bot] 9ae7b5201c chore(deps): Update dependency unplugin-auto-import to v0.18.5 (#374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 04:05:51 +00:00
耗子 119411a3ce fix: 优化样式 2024-11-19 01:56:17 +08:00
耗子 112fc30437 feat: 阻止删除关键目录 2024-11-19 01:26:06 +08:00
renovate[bot] dea96649d7 chore(deps): Update module github.com/bddjr/hlfhr to v1.2.4 (#373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 01:03:03 +08:00
renovate[bot] 2d136e40c5 chore(deps): Update dependency marked to v15.0.1 (#372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 16:55:27 +08:00
耗子 11e4d05181 Merge remote-tracking branch 'origin/main' 2024-11-18 14:13:16 +08:00
耗子 50381dff0c feat: 防跨站使用网站目录而不是运行目录 2024-11-18 14:13:09 +08:00
renovate[bot] 8e40d9a75d chore(deps): Lock file maintenance (#371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 03:39:07 +00:00
耗子 8251a9a70e Merge remote-tracking branch 'origin/main' 2024-11-18 03:54:54 +08:00
耗子 0297ccbf28 feat: 优化商店缓存更新防止挤爆接口 2024-11-18 03:54:48 +08:00
renovate[bot] a9113f3e4c chore(deps): Update module github.com/bddjr/hlfhr to v1.2.2 (#370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 02:30:58 +08:00
耗子 17918c0f1f fix: build 2024-11-18 00:26:59 +08:00
耗子 9509cfa4e2 Merge remote-tracking branch 'origin/main' 2024-11-18 00:18:34 +08:00
耗子 1e32f9addc feat: 修改部分菜单名称 2024-11-18 00:18:20 +08:00
renovate[bot] abe2e1fd3d chore(deps): Update module github.com/bddjr/hlfhr to v1.2.0 (#369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-17 21:37:19 +08:00
耗子 cbf9bdc926 fix: 证书为空时页面js报错 2024-11-17 19:47:58 +08:00
renovate[bot] ab4a94c32d chore(deps): Update module github.com/go-playground/validator/v10 to v10.23.0 (#368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-17 03:30:56 +08:00
耗子 0ba29c7c40 feat: 防跨站避免覆盖修改 2024-11-16 21:40:00 +08:00
耗子 e99c71b8e1 feat: 文件删除显示文件名 2024-11-16 20:58:28 +08:00
renovate[bot] b95b035991 chore(deps): Update dependency @iconify/json to v2.2.273 (#367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-16 12:02:08 +00:00
renovate[bot] 5959cab965 chore(deps): Update github.com/tufanbarisyildirim/gonginx digest to 128af6d (#366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 22:05:01 +00:00
renovate[bot] d4668f2a7e chore(deps): Update all dependencies (#365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-16 00:21:53 +08:00
耗子 e1fb498aa5 docs: 更新 star 历史 2024-11-15 21:57:54 +08:00
renovate[bot] 6413d6462c chore(deps): Update all dependencies (#364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 16:07:50 +08:00
renovate[bot] 3f9fce7841 chore(deps): Update dependency sass to v1.81.0 (#363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 06:38:30 +00:00
renovate[bot] 58d100e636 chore(deps): Update codecov/codecov-action action to v5 (#362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 00:14:11 +00:00
renovate[bot] 98d57b6c23 chore(deps): Update dependency @vue-js-cron/naive-ui to v2.0.8 (#361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 21:34:49 +00:00
耗子 3a8c7784f5 chore: 更新描述 2024-11-14 19:34:10 +08:00
renovate[bot] 02bfddcf03 chore(deps): Update all dependencies (#360)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 10:59:43 +00:00
renovate[bot] 55750796b3 chore(deps): Update dependency @vitejs/plugin-vue to v5.2.0 (#359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 22:35:09 +08:00
renovate[bot] 692f8ca030 chore(deps): Update dependency md-editor-v3 to v5 (#358)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 12:08:00 +00:00
renovate[bot] fbe99ea4dd chore(deps): Update dependency sass to v1.80.7 (#357)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 13:36:15 +08:00
耗子 d34438d90e Merge remote-tracking branch 'origin/main' 2024-11-13 12:01:54 +08:00
耗子 47e114d43e feat: 修改面板端口前检查占用 2024-11-13 12:01:47 +08:00
renovate[bot] 0b9bba45d7 chore(deps): Update dependency @iconify/json to v2.2.271 (#356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-12 23:31:09 +08:00
耗子 00ea2f40e2 fix: 创建网站未创建postgres数据库 2024-11-12 19:39:47 +08:00
耗子 5183653dbb fix: 创建网站未保存备注 2024-11-12 19:24:44 +08:00
renovate[bot] 9f7d8e413e chore(deps): Update all dependencies (#355)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-12 10:47:29 +00:00
耗子 4450d2c8d1 fix: test 2024-11-12 12:56:17 +08:00
耗子 b5f7458344 fix: 带 i 属性的文件无法保存 2024-11-12 03:14:53 +08:00
耗子 da44f1a8fe feat: 发布v2.3.19 2024-11-12 02:50:24 +08:00
耗子 0317aa1401 feat: redis添加清空 2024-11-12 02:46:54 +08:00
renovate[bot] 90fc48f478 chore(deps): Update dependency vite to v5.4.11 (#354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 14:21:13 +00:00
renovate[bot] a8178cbcd6 chore(deps): Update all dependencies (#353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 15:12:12 +08:00
耗子 91ecd04c27 feat: 优化登录中间件使用白名单 2024-11-11 14:28:00 +08:00
耗子 6743ac9621 fix: 防止首页报错 2024-11-11 14:19:36 +08:00
耗子 5606d980be fix: cli下修改端口后自动放行 2024-11-11 14:13:18 +08:00
renovate[bot] 34300431a4 chore(deps): Lock file maintenance (#352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 08:25:44 +08:00
耗子 6f0f4c69ef feat: 初步完成redis操作方法 2024-11-11 02:22:23 +08:00
耗子 5fba45e3a4 feat: 初步完成redis操作方法 2024-11-11 02:22:17 +08:00
耗子 bb140ade7b feat: 优化app路由加载 2024-11-11 01:07:37 +08:00
耗子 1316c50a57 feat: php支持设置为cli默认版本 2024-11-11 01:04:09 +08:00
耗子 f14b4f5c54 Merge remote-tracking branch 'origin/main' 2024-11-11 00:47:48 +08:00
耗子 b08eb6bf1f feat: 优化目录输入自动清理多余的斜杠和空格 2024-11-11 00:47:09 +08:00
renovate[bot] 7f5869f805 chore(deps): Update dependency @vue/tsconfig to ^0.6.0 (#351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 00:38:26 +08:00
耗子 ecadaa9bbc fix: lint 2024-11-10 18:37:44 +08:00
耗子 60b9350625 feat: 优化删除和写入支持自动去除ia属性 2024-11-10 18:25:01 +08:00
耗子 d64f099f8e feat: 发布v2.3.18 2024-11-10 08:37:38 +08:00
耗子 294a78cd95 feat: 优化删除检查 2024-11-10 03:26:08 +08:00
renovate[bot] 8294a1a74e chore(deps): Update module github.com/urfave/cli/v3 to v3.0.0-alpha9.3 (#349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-10 02:28:09 +08:00
renovate[bot] c403efd762 chore(deps): Update dependency @iconify/json to v2.2.270 (#348)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-09 18:02:04 +08:00
耗子 cf8dfca4c0 Merge remote-tracking branch 'origin/main' 2024-11-09 18:00:15 +08:00
耗子 e552caa38f feat: 修改ip获取地址 2024-11-09 18:00:09 +08:00
renovate[bot] ddff13990a chore(deps): Update dependency marked to v15 (#347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-09 06:58:36 +00:00
renovate[bot] 27f79ea7a3 chore(deps): Update dependency pinia-plugin-persistedstate to v4.1.3 (#346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-09 01:32:12 +00:00
耗子 04d570197a feat: 添加redis驱动 2024-11-09 02:41:47 +08:00
renovate[bot] 8e74428023 chore(deps): Update all dependencies (#345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-09 01:10:12 +08:00
renovate[bot] f4c09324b7 chore(deps): Update dependency md-editor-v3 to v4.21.3 (#344)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 11:08:41 +00:00
renovate[bot] ee6a12fac2 chore(deps): Update module golang.org/x/crypto to v0.29.0 (#343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 03:10:34 +00:00
renovate[bot] ba429068ad chore(deps): Update dependency marked to v14.1.4 (#342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 21:48:01 +00:00
renovate[bot] 711ccb4504 chore(deps): Update dependency @iconify/json to v2.2.269 (#341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 19:09:13 +08:00
耗子 7d6e078e20 chore: 添加新合作 2024-11-07 16:58:49 +08:00
耗子 d3a7c9e29c feat: 数据库密码可以为空(本地认证) 2024-11-07 02:37:29 +08:00
耗子 10d22bfbc0 feat: 初始化数据库服务 2024-11-07 02:27:17 +08:00
耗子 724d05d980 feat: 优化数据库模型 2024-11-07 02:13:24 +08:00
耗子 690e7f0391 feat: 重命名安全为数据库 2024-11-07 01:12:38 +08:00
耗子 a28fc592d1 feat: 优化数据库模型 2024-11-07 00:52:12 +08:00
耗子 ff7e76ac2b feat: 初始化数据库条目模型 2024-11-07 00:49:11 +08:00
耗子 f2eb3a180c feat: 对部分密码进行加密 2024-11-07 00:25:09 +08:00
renovate[bot] 020a4ad870 chore(deps): Update all dependencies (#340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-06 22:16:57 +08:00
renovate[bot] b5e7cfc069 chore(deps): Update all dependencies (#339)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 21:31:06 +08:00
renovate[bot] 52f8a6f045 chore(deps): Update dependency @types/node to v22.9.0 (#338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 05:13:50 +00:00
耗子 3831d47a23 feat(文件): 路径切换后清空已选择 2024-11-05 02:57:30 +08:00
耗子 3a579f2346 feat: 运行等待任务前标记先前运行中的任务为失败 2024-11-04 17:59:13 +08:00
耗子 e85e8dffb3 fix: lint 2024-11-04 17:48:46 +08:00
耗子 e18d40b0ab feat: 防止任务重复提交 2024-11-04 17:47:39 +08:00
renovate[bot] c308b8e42c chore(deps): Update all dependencies (#336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 12:40:20 +08:00
renovate[bot] cafded526f chore(deps): Lock file maintenance (#335)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 04:24:39 +00:00
renovate[bot] 99aa6c99d8 chore(deps): Update dependency pinia to v2.2.6 (#334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 01:11:37 +00:00
耗子 e0daee4766 feat: 移动部分方法到utils 2024-11-04 03:04:34 +08:00
耗子 bf8c832b32 feat: 去掉重复的方法 2024-11-04 02:33:15 +08:00
耗子 2d35cbc176 fix: lint 2024-11-03 16:46:05 +08:00
renovate[bot] 6b51f58856 chore(deps): Update dependency @types/node to v22.8.7 (#330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 07:25:33 +00:00
renovate[bot] f58c680ced chore(deps): Update dependency @iconify/json to v2.2.267 (#329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 19:49:40 +00:00
耗子 9c6d0b5837 feat: php拓展安装添加确认弹窗 2024-11-02 21:28:29 +08:00
耗子 d069cbec75 feat: 负载条不允许超过100% 2024-11-02 21:22:35 +08:00
耗子 9b252aa8f6 docs: 更新文档 2024-11-02 20:02:43 +08:00
耗子 a52d06e3cb feat: 添加memcached php拓展 2024-11-02 19:26:38 +08:00
耗子 c25c78bfef feat: 发布v2.3.17 2024-11-02 18:32:54 +08:00
耗子 567f3ab693 fix: lint 2024-11-02 18:18:29 +08:00
耗子 aa162e12db feat: 支持memcached 2024-11-02 17:48:52 +08:00
renovate[bot] 7169b90c1d chore(deps): Update dependency sass to v1.80.6 (#328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 03:07:42 +00:00
耗子 5e1001f10f docs: update README 2024-11-02 03:36:33 +08:00
耗子 8e26589c80 feat: 提升最低TLS到1.3 2024-11-02 03:34:21 +08:00
耗子 27ec65650d feat: 替换go默认的logger 2024-11-02 01:12:17 +08:00
耗子 e2345a8285 workflow: 合并codecov与test 2024-11-02 00:43:01 +08:00
耗子 4ebe20d7f8 workflow: issue lock修改为手动触发 2024-11-02 00:37:02 +08:00
renovate[bot] 8f5872b284 chore(deps): Update dependency alova to v3.2.0 (#327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 00:34:24 +08:00
耗子 75c6522453 fix: lint 2024-11-01 23:07:51 +08:00
耗子 ff34cc72cc fix: lint 2024-11-01 21:54:06 +08:00
耗子 e2fb1080da Merge remote-tracking branch 'origin/main' 2024-11-01 21:42:27 +08:00
耗子 b96c6ad899 feat: php支持74和80 2024-11-01 21:42:20 +08:00
renovate[bot] c1a4fb0904 chore(deps): Update all dependencies (#326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 09:46:35 +08:00
renovate[bot] 5c50da27c5 chore(deps): Update dependency @vue-js-cron/naive-ui to v2.0.7 (#325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-31 18:48:45 +00:00
耗子 336a9bc675 fix: 文件管理bug 2024-10-31 21:49:58 +08:00
耗子 ea3bf37687 fix: 7z list 2024-10-31 18:38:03 +08:00
耗子 79e09f9cb7 chore: debug 2024-10-31 18:32:08 +08:00
耗子 d73c988073 chore: debug 2024-10-31 18:32:01 +08:00
耗子 9641ae7f34 chore: debug 2024-10-31 18:31:56 +08:00
耗子 fd2c89a480 chore: debug 2024-10-31 18:27:01 +08:00
耗子 526f192200 feat(文件): 解压时只设置压缩包内文件的权限 2024-10-31 18:20:54 +08:00
renovate[bot] 6423953aeb chore(deps): Update dependency @types/node to v22.8.5 (#324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-31 17:07:19 +08:00
耗子 1091ec8a23 Merge remote-tracking branch 'origin/main' 2024-10-31 12:35:38 +08:00
耗子 2cbc641b2d feat(文件): 优化解压,保存时的权限设置 2024-10-31 12:35:31 +08:00
renovate[bot] f4d23211e3 chore(deps): Update dependency @vueuse/core to v11.2.0 (#323)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-31 01:16:52 +00:00
耗子 1b6086bb0b docs: 更新bug模板 2024-10-31 04:11:13 +08:00
耗子 f399e9fcec fix: 应用更新按钮错位 2024-10-31 04:11:03 +08:00
renovate[bot] 17cf1ada0e chore(deps): Update all dependencies (#321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-31 03:43:36 +08:00
耗子 85bd71aefe feat: 发布v2.3.16 2024-10-31 03:34:02 +08:00
耗子 f01afacf34 feat: 优化docker兼容 2024-10-31 03:28:41 +08:00
耗子 52e8830605 feat: docker 管理器 2024-10-31 01:23:46 +08:00
耗子 e90c6f4f7b fix: 计划任务修改后未刷新 2024-10-30 20:38:35 +08:00
renovate[bot] eb94ef89b3 chore(deps): Update dependency sass to v1.80.5 (#318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-30 01:42:39 +00:00
renovate[bot] adc6dccaae chore(deps): Update all dependencies (#317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 18:30:35 +00:00
renovate[bot] 855b2e87f9 chore(deps): Update dependency @types/node to v22 (#316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 11:26:03 +08:00
renovate[bot] 3c601b808f chore(deps): Update dependency @types/node to v20.17.2 (#315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 02:17:04 +00:00
renovate[bot] 8652501e95 chore(deps): Lock file maintenance (#313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 00:22:35 +08:00
renovate[bot] 43b67f67be chore(deps): Update dependency md-editor-v3 to v4.21.2 (#312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 00:20:13 +08:00
renovate[bot] 4fff0b84d8 chore(deps): migrate config renovate.json (#314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 00:03:12 +08:00
renovate[bot] fd73982f86 chore(deps): Update dependency cronstrue to v2.51.0 (#311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 23:52:24 +08:00
renovate[bot] 10191ae5d6 chore(deps): Update dependency eslint-plugin-vue to v9.30.0 (#309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 23:52:14 +08:00
耗子 b162f71d47 chore: update renovate config 2024-10-28 23:56:07 +08:00
耗子 a9f34a5783 feat: 实验性支持alova 2024-10-28 23:44:48 +08:00
耗子 1e4285b516 fix: mysql数据库为空时无法备份 2024-10-28 21:10:49 +08:00
耗子 44c370a119 feat(#305): 使用slog记录所有日志 2024-10-28 20:18:25 +08:00
renovate[bot] a1c0b4a399 chore(deps): Update dependency @iconify/json to v2.2.265 (#308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 15:18:03 +08:00
renovate[bot] 805f6c0002 chore(deps): Lock file maintenance (#307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 05:10:06 +00:00
renovate[bot] 5066446053 chore(deps): Lock file maintenance (#306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 03:09:55 +00:00
耗子 0c31641ebd fix: lint 2024-10-28 03:36:35 +08:00
耗子 343d451752 feat: 所有shell操作全部使用shell包 2024-10-28 03:35:20 +08:00
耗子 12ecf0707e feat: 规范压缩解压 2024-10-28 03:21:22 +08:00
耗子 899ce1fa18 feat: 优化压缩解压错误输出 2024-10-28 02:56:53 +08:00
耗子 9ef56f6ede feat: 适配面板备份 2024-10-28 02:43:21 +08:00
耗子 4b3ebc082c feat: 合并gz和tgz 2024-10-28 02:05:36 +08:00
耗子 fb3c8ccc85 fix: test 2024-10-28 02:01:34 +08:00
耗子 f620a8737e fix: test 2024-10-28 01:55:30 +08:00
耗子 d0d5b237ef fix: test 2024-10-28 01:52:37 +08:00
耗子 11f5adc5a4 fix: test 2024-10-28 01:44:31 +08:00
耗子 6927a0256c fix: test 2024-10-28 01:42:21 +08:00
耗子 dbc78343dc fix: test 2024-10-28 01:39:58 +08:00
耗子 d8347310ea fix: test 2024-10-28 01:36:27 +08:00
耗子 8d6b3fe95e fix: test 2024-10-28 01:28:28 +08:00
耗子 036e01795e fix: test 2024-10-28 01:25:22 +08:00
耗子 2d7faaa653 fix: test 2024-10-28 01:22:56 +08:00
耗子 5921a88c75 fix: test 2024-10-28 01:20:41 +08:00
耗子 d6f4b8736b fix: 尝试修复压缩 2024-10-28 01:20:03 +08:00
耗子 227f10751c feat: 避免压缩包多一层目录 2024-10-28 01:02:04 +08:00
耗子 54cc556d17 feat: 重构压缩解压 2024-10-28 00:10:24 +08:00
耗子 0d8a26ccb6 fix: lint 2024-10-27 23:07:06 +08:00
耗子 eb18b1080a refactor: 使用api获取容器信息 2024-10-27 22:52:42 +08:00
renovate[bot] ffe7b95b9c chore(deps): Update module github.com/bddjr/hlfhr to v1.1.3 (#303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-27 19:24:58 +08:00
耗子 ca097cac1e feat: 移除docker包 2024-10-27 19:23:22 +08:00
耗子 f3a578380a feat: 容器网络使用cli实现 2024-10-27 18:51:53 +08:00
耗子 beb271b3b9 feat: 容器镜像使用cli实现 2024-10-27 18:12:21 +08:00
耗子 310316c8e4 feat: 优化容器结构体 2024-10-27 17:45:36 +08:00
耗子 7521b0f788 feat: 使用cli实现容器管理 2024-10-27 17:33:34 +08:00
耗子 2783e2b28e feat: 删除容器一批无用的接口 2024-10-27 16:35:34 +08:00
耗子 cf26063287 feat: 优化去掉go-yaml依赖 2024-10-27 16:23:52 +08:00
耗子 2a61f8939f fix: PHP关闭状态下获取负载报错 2024-10-27 04:37:38 +08:00
耗子 513a298541 feat: 发布v2.3.15 2024-10-27 04:19:48 +08:00
renovate[bot] 1cbc53b311 chore(deps): Update dependency vue-tsc to v2.1.8 (#302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-27 04:18:37 +08:00
耗子 aed35990bc feat: 支持签发自签名证书 2024-10-27 04:17:58 +08:00
耗子 9179543b7f feat: 支持重签面板HTTPS证书 2024-10-27 04:09:33 +08:00
耗子 f40ad8bdf9 feat: 手动签发证书 2024-10-27 03:17:21 +08:00
耗子 56ae7ebfa0 feat: 支持上传本地证书 2024-10-27 02:35:04 +08:00
耗子 cf5f7bd866 feat: 证书支持同时部署多个网站 2024-10-27 01:46:58 +08:00
耗子 bd6496c1cb feat: 网站支持选择证书填入 2024-10-27 01:22:47 +08:00
耗子 3c75c1c49d feat: 文件名支持缩略显示 2024-10-27 01:08:43 +08:00
耗子 2b0f8a57bb fix: lint 2024-10-27 01:00:13 +08:00
耗子 658b6a79eb feat: 文件操作支持覆盖 2024-10-27 01:00:02 +08:00
耗子 42dd00c5ea feat: 标记的文件可以取消 2024-10-27 00:17:32 +08:00
耗子 8fa5cec568 Merge remote-tracking branch 'origin/main' 2024-10-27 00:02:35 +08:00
耗子 2af4b9569d feat: 不允许把目录复制到原目录中 2024-10-27 00:02:29 +08:00
renovate[bot] 408b9ce19b chore(deps): Update dependency @iconify/json to v2.2.264 (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-26 23:30:44 +08:00
耗子 b2845fba15 feat: http自动跳转https 2024-10-26 21:26:14 +08:00
耗子 ba819a5a9c fix: 修复监控单位错误 2024-10-26 20:07:33 +08:00
耗子 bf1359e3d6 fix: 禁用resty的日志输出 2024-10-26 16:33:53 +08:00
耗子 2970aaaa47 feat: 一键签发证书支持自动同步域名 2024-10-26 13:13:29 +08:00
耗子 a65bcf582a feat: PHP网站默认开启防跨站 2024-10-26 05:06:10 +08:00
耗子 145387b3df fix: Alt-Svc头删除不正确 2024-10-26 04:59:28 +08:00
270 changed files with 9695 additions and 5837 deletions
+7 -8
View File
@@ -19,9 +19,7 @@ body:
options:
- label: 我已经搜索了现有的 Issues, Discussions 和 Google (I've searched the existing Issues, Discussions and Google)
required: true
- label: 我已经阅读了 README 中除版本记录以外的所有内容 (I've read everything in the README except the version notes)
required: true
- label: 我在提问题之前至少花费了 5 分钟来思考和准备 (I spend at least 5 minutes thinking and preparing before asking a question)
- label: 我已经阅读了文档中除版本记录以外的所有内容 (I've read everything in the documentation except the version notes)
required: true
- label: 我没有改动过面板及系统的任何文件 (I have not changed any files of the panel and system)
required: false
@@ -37,9 +35,10 @@ body:
请提供安装面板的操作系统。
Please provide the operating system on which the panel is installed.
options:
- RockyLinux
- AlmaLinux
- Debian
- Ubuntu
- AlmaLinux
- RockyLinux
- Other
validations:
required: true
@@ -50,17 +49,17 @@ body:
description: |
请提供操作系统的版本号。
Please provide the version number of the operating system.
placeholder: "9.0"
placeholder: "12"
validations:
required: true
- type: input
id: version
attributes:
label: 耗子面板版本 (HaoZi Panel Version)
label: 耗子面板版本 (Rat Panel Version)
description: |
请提供面板的版本号。
Please provide the version number of the panel.
placeholder: "2.0.0"
placeholder: "2.3.0"
validations:
required: true
- type: textarea
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

-26
View File
@@ -1,26 +0,0 @@
name: Codecov
on:
push:
branches:
- main
pull_request:
jobs:
codecov:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
cache: true
go-version: 'stable'
- name: Install dependencies
run: go mod tidy
- name: Run tests with coverage
run: sudo go test -v -coverprofile="coverage.out" ./...
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.out
token: ${{ secrets.CODECOV }}
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: 'pnpm'
cache-dependency-path: web/pnpm-lock.yaml
- name: Build frontend
-3
View File
@@ -3,9 +3,6 @@ on:
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
push:
branches:
- main
issues:
types: [ opened ]
permissions:
+1 -1
View File
@@ -72,7 +72,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: 'pnpm'
cache-dependency-path: web/pnpm-lock.yaml
- name: Run pnpm lint
+6 -1
View File
@@ -21,4 +21,9 @@ jobs:
run: |
cp config.example.yml config.yml
- name: Run tests
run: sudo go test ./...
run: sudo go test -v -coverprofile="coverage.out" ./...
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v5
with:
file: ./coverage.out
token: ${{ secrets.CODECOV }}
+27 -20
View File
@@ -4,10 +4,6 @@
<h1 align="center" style="font-size: 40px">耗子面板</h1>
<p align="center">
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
<a href="https://github.com/TheTNB/panel/actions"><img src="https://github.com/TheTNB/panel/actions/workflows/test.yml/badge.svg"></a>
@@ -16,9 +12,9 @@
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
</p>
耗子面板是使用 Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板
耗子面板是新一代企业级全能服务器运维管理面板。简单轻量,高效运维
QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | 论坛:[tom.moe](https://tom.moe) | 赞助:[爱发电](https://afdian.com/a/TheTNB)
**赞助:[爱发电](https://afdian.com/a/TheTNB)** | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | 论坛:[tom.moe](https://tom.moe)
## 优势
@@ -63,10 +59,7 @@ QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制
随着系统版本的不断更新,我们亦可能会终止部分过于老旧的系统的支持,以保证面板的健壮性。
## 安装面板
> **Warning**
> 安装面板前,您需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,我们不建议 0 基础的用户安装和使用耗子面板。
## 挂载硬盘
如果您的服务器有未挂载的数据盘,可在安装前以`root`用户登录服务器运行以下命令自动挂载,面板安装后不支持跨目录迁移。
@@ -74,9 +67,12 @@ QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制
curl -fsLm 10 -o auto_mount.sh https://dl.cdn.haozi.net/panel/auto_mount.sh && bash auto_mount.sh
```
准备就绪后,请以`root`用户登录服务器,运行以下命令安装面板
## 安装面板
**当前 v2.3.x 为测试版本,欢迎帮助我们测试在不同操作系统下的兼容性。**
> **Warning**
> 安装面板前,您需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,我们不建议 0 基础的用户安装和使用耗子面板。
`root`用户登录服务器,运行以下命令安装面板:
```shell
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
@@ -140,6 +136,12 @@ panel-cli
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
</a>
### 合作伙伴
<a href="https://1ms.run">
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
</a>
### 赞助商们
<p align="center">
@@ -152,18 +154,23 @@ panel-cli
这个项目的存在要归功于所有做出贡献的人,参与贡献请先查看贡献代码部分。
<a href="https://github.com/TheTNB/panel/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
</a>
<p align="center">
<a href="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors?repo_id=572922963&limit=30" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=dark" width="655" height="auto">
<img alt="Active Contributors of TheTNB/panel - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=light" width="655" height="auto">
</picture>
</a>
</p>
## Star 历史
<a href="https://star-history.com/#TheTNB/panel&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
</picture>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
</picture>
</a>
## 免责声明
+27 -20
View File
@@ -4,10 +4,6 @@
<h1 align="center" style="font-size: 40px">Rat Panel</h1>
<p align="center">
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
<a href="https://github.com/TheTNB/panel/actions"><img src="https://github.com/TheTNB/panel/actions/workflows/test.yml/badge.svg"></a>
@@ -16,9 +12,9 @@
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
</p>
The Rat Panel is an open source lightweight Linux server operation and maintenance management panel developed using Golang and Vue.
Rat Panel is a new generation of enterprise-level all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | Forum: [tom.moe](https://tom.moe) | Sponsor: [Open Collective](https://opencollective.com/tnb)
**Sponsor: [Open Collective](https://opencollective.com/tnb)** | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6) | Forum: [tom.moe](https://tom.moe)
## Advantages
@@ -63,10 +59,7 @@ For other systems not listed in the table below, you can try to install them by
As system versions are constantly updated, we may also terminate support for some older systems to ensure the robustness of the panel.
## Install Panel
> **Warning**
> Before installing the panel, you need to understand the basic knowledge of the LNMP environment and how to deal with common LNMP environment problems. We are not recommended for users with zero basic knowledge to install and use Rat Panel.
## Mount Disk
If your server has an unmounted data disk, you can run the following command as the `root` user to automatically mount it before installation. The panel does not support cross-directory migration after installation.
@@ -74,9 +67,12 @@ If your server has an unmounted data disk, you can run the following command as
curl -fsLm 10 -o auto_mount.sh https://dl.cdn.haozi.net/panel/auto_mount.sh && bash auto_mount.sh
```
After you are ready, log in to the server as the `root` user and run the following command to install the panel:
## Install Panel
**Current v2.3.x is a test version, welcome to help us test compatibility on different operating systems.**
> **Warning**
> Before installing the panel, you need to understand the basic knowledge of the LNMP environment and how to deal with common LNMP environment problems. We are not recommended for users with zero basic knowledge to install and use Rat Panel.
Login to the server as the `root` user and run the following command to install the panel:
```shell
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
@@ -140,6 +136,12 @@ If the Rat Panel is helpful to you, welcome to [sponsor us](https://opencollecti
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
</a>
### Partners
<a href="https://1ms.run">
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
</a>
### Sponsors
<p align="center">
@@ -152,18 +154,23 @@ If the Rat Panel is helpful to you, welcome to [sponsor us](https://opencollecti
This project owes its existence to all those who have contributed. To contribute, please check the contributed code section first.
<a href="https://github.com/TheTNB/panel/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
</a>
<p align="center">
<a href="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors?repo_id=572922963&limit=30" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=dark" width="655" height="auto">
<img alt="Active Contributors of TheTNB/panel - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=572922963&limit=30&image_size=auto&color_scheme=light" width="655" height="auto">
</picture>
</a>
</p>
## Star History
<a href="https://star-history.com/#TheTNB/panel&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
</picture>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
</picture>
</a>
## Disclaimer
-2
View File
@@ -1,5 +1,3 @@
# 警告:未经指导私自修改此配置文件可能导致严重后果
# Warning: Modifying this configuration file without guidance may cause serious consequences
app:
debug: false
key: a-long-string-with-32-characters
+26 -62
View File
@@ -3,127 +3,91 @@ module github.com/TheTNB/panel
go 1.23
require (
github.com/bddjr/hlfhr v1.2.4
github.com/beevik/ntp v1.4.3
github.com/docker/docker v27.3.1+incompatible
github.com/docker/go-connections v0.5.0
github.com/creack/pty v1.1.24
github.com/expr-lang/expr v1.16.9
github.com/glebarez/sqlite v1.11.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-gormigrate/gormigrate/v2 v2.1.3
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.22.1
github.com/go-playground/validator/v10 v10.23.0
github.com/go-rat/chix v1.1.4
github.com/go-rat/gormstore v1.0.6
github.com/go-rat/sessions v1.0.11
github.com/go-rat/utils v1.0.3
github.com/go-resty/resty/v2 v2.15.3
github.com/go-rat/utils v1.0.7
github.com/go-resty/resty/v2 v2.16.2
github.com/go-sql-driver/mysql v1.8.1
github.com/goccy/go-yaml v1.12.0
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
github.com/gomodule/redigo v1.9.2
github.com/gorilla/websocket v1.5.3
github.com/hashicorp/go-version v1.7.0
github.com/klauspost/compress v1.17.11
github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/file v1.1.2
github.com/knadh/koanf/v2 v2.1.1
github.com/knadh/koanf/v2 v2.1.2
github.com/lib/pq v1.10.9
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1
github.com/libdns/huaweicloud v0.2.2
github.com/libdns/libdns v0.2.2
github.com/libdns/tencentcloud v1.0.0
github.com/libdns/tencentcloud v1.1.0
github.com/mholt/acmez/v2 v2.0.3
github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/orandin/slog-gorm v1.4.0
github.com/robfig/cron/v3 v3.0.1
github.com/samber/do/v2 v2.0.0-beta.7
github.com/samber/lo v1.47.0
github.com/sethvargo/go-limiter v1.0.0
github.com/shirou/gopsutil v2.21.11+incompatible
github.com/spf13/cast v1.7.0
github.com/stretchr/testify v1.9.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8
github.com/urfave/cli/v3 v3.0.0-alpha9.1
github.com/stretchr/testify v1.10.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20241115180907-128af6df1765
github.com/urfave/cli/v3 v3.0.0-alpha9.5
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.28.0
golang.org/x/net v0.30.0
go.uber.org/zap/exp v0.3.0
golang.org/x/crypto v0.29.0
golang.org/x/net v0.31.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.12
moul.io/zapgorm2 v1.3.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/bodgit/plumbing v1.2.0 // indirect
github.com/bodgit/sevenzip v1.3.0 // indirect
github.com/bodgit/windows v1.0.0 // indirect
github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-rat/securecookie v1.0.1 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gofiber/schema v1.2.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jaevor/go-nanoid v1.4.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/samber/go-type-to-string v1.4.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1033 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1033 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
go.opentelemetry.io/otel v1.30.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
go.opentelemetry.io/otel/sdk v1.30.0 // indirect
go.opentelemetry.io/otel/trace v1.30.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.60.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
+58 -398
View File
@@ -1,99 +1,36 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/TheTNB/acmez/v2 v2.0.0-20241025203320-cc718c4c870b h1:1iVxCglpEJ/kq48YcujkWq8SeOacMuMmuekMmGyOSPA=
github.com/TheTNB/acmez/v2 v2.0.0-20241025203320-cc718c4c870b/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bddjr/hlfhr v1.2.4 h1:1KZcqvzeWJdB20REsfKuCsdnRAeVC113cONlhswypek=
github.com/bddjr/hlfhr v1.2.4/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7 h1:AjCMD0AEGHHbcIpvRcrI8rMA+Us5F4uUt3B3KSpiYN8=
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7/go.mod h1:Gz/Ng6WCvYc1Q2smuPvow9kszfyH47wtnpZzvdD2juA=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -102,8 +39,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-rat/chix v1.1.4 h1:YVDwF4vrlkm5hmlDGWKoiWPgP98pJooBvhsto8UWq+o=
github.com/go-rat/chix v1.1.4/go.mod h1:eKUTG7GXqMS278PH0QZi8yMWZddvMfxqWu7mverM6kQ=
github.com/go-rat/gormstore v1.0.6 h1:YxmaY6n3JoTk6o9RXoZ1+cQ6HQL+ayf/6d8D76ydCAc=
@@ -112,99 +49,46 @@ github.com/go-rat/securecookie v1.0.1 h1:HW0fpKmB+FjJzXTw8ABOwBJ+XrPmRBSZqHhmrv8
github.com/go-rat/securecookie v1.0.1/go.mod h1:tP/ObWYyjmcpabQ7WTon/i2lBSip/Aolliw2llXuPDU=
github.com/go-rat/sessions v1.0.11 h1:2eTUvhEBtymrAtwv71LlGHNylz+Ad2SgY3jg/sSUboA=
github.com/go-rat/sessions v1.0.11/go.mod h1:Ray/GCbuhm4U9xpjFFSCfOTCEn91puEhAXX5creHE9g=
github.com/go-rat/utils v1.0.3 h1:SqH/O0KYq4SBv8naSAjJA4hC45/d8NLNrTCONfqjbFM=
github.com/go-rat/utils v1.0.3/go.mod h1:4WNPlrF57KmeGZN3HOeBgdBVLJL3xgba4QRP/t6pKrE=
github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8=
github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-rat/utils v1.0.7 h1:hZUe+n1W/tvF3j+oP/TeAHX3AF98SjVB1FuF11zkCAI=
github.com/go-rat/utils v1.0.7/go.mod h1:b0S/vd2bXWvPUQpw8z6RmJrUG7G7BkMuNSrsz6JRBHs=
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a h1:BAyyIK6rc6Tq9vT2KVlPJXD8EVv/LpaFwn4fgfLwtHg=
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a/go.mod h1:bgk4Ij/0OQ89UeoFFAQrSNhbbr4rKJ0fwWfo7wc+TCc=
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jaevor/go-nanoid v1.4.0 h1:mPz0oi3CrQyEtRxeRq927HHtZCJAAtZ7zdy7vOkrvWs=
github.com/jaevor/go-nanoid v1.4.0/go.mod h1:GIpPtsvl3eSBsjjIEFQdzzgpi50+Bo1Luk+aYlbJzlc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -224,231 +108,99 @@ github.com/libdns/huaweicloud v0.2.2/go.mod h1:F9vNIca+Cq1ZJiMCSkHQUdvXEGY6UbLoa
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/tencentcloud v1.0.0 h1:u4LXnYu/lu/9P5W+MCVPeSDnwI+6w+DxYhQ1wSnQOuU=
github.com/libdns/tencentcloud v1.0.0/go.mod h1:NlCgPumzUsZWSOo1+Q/Hfh8G6TNRAaTUeWQdg6LbtUI=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/libdns/tencentcloud v1.1.0 h1:UvrU9HR3Cc87vIkj/FrTGkaZigGcRyZFdLCYqKDac1c=
github.com/libdns/tencentcloud v1.1.0/go.mod h1:PuP62hTGHSI3OqSUsrzuvsCWMD+9BlPtVjKasOrAtJ4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU=
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/samber/do/v2 v2.0.0-beta.7 h1:tmdLOVSCbTA6uGWLU5poi/nZvMRh5QxXFJ9vHytU+Jk=
github.com/samber/do/v2 v2.0.0-beta.7/go.mod h1:+LpV3vu4L81Q1JMZNSkMvSkW9lt4e5eJoXoZHkeBS4c=
github.com/samber/go-type-to-string v1.4.0 h1:KXphToZgiFdnJQxryU25brhlh/CqY/cwJVeX2rfmow0=
github.com/samber/go-type-to-string v1.4.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
github.com/shirou/gopsutil v2.21.11+incompatible h1:lOGOyCG67a5dv2hq5Z1BLDUqqKp3HkbjPcz5j6XMS0U=
github.com/shirou/gopsutil v2.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597 h1:C0GHdLTfikLVoEzfhgPfrZ7LwlG0xiCmk6iwNKE+xs0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1033 h1:g263/dapUpOAZJa1Y9x07WgfOl7Yy+FM5Mrf4WyttS8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1033/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1033 h1:bRCo+X6i/6B+5Q/G4+rexcMRxpSq+9XS/8+Usn6OcOA=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1033/go.mod h1:ta9FOsQP/SvQAhDD6IhVhYWyY+eboPEbPWq7zpiNwZA=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8 h1:7yw1yHkylDcu/CwY4hEEe8MycDLo7B9LjcqwhoL8NrM=
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v3 v3.0.0-alpha9.1 h1:1fJU+bltkwN8lF4Sni/X0i1d8XwPIrS82ivZ8qsp/q4=
github.com/urfave/cli/v3 v3.0.0-alpha9.1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/tufanbarisyildirim/gonginx v0.0.0-20241115180907-128af6df1765 h1:nnw6RpVhgrUvV6BLysdedhxaALJY1JqRFr3S85XXpF4=
github.com/tufanbarisyildirim/gonginx v0.0.0-20241115180907-128af6df1765/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
github.com/urfave/cli/v3 v3.0.0-alpha9.5 h1:Y693/B4H1nO6s5Xxns2AVLWNZ650HCF7AjThgvkP0bw=
github.com/urfave/cli/v3 v3.0.0-alpha9.5/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8=
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU=
go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -457,133 +209,46 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
@@ -608,8 +273,3 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
moul.io/zapgorm2 v1.3.0 h1:+CzUTMIcnafd0d/BvBce8T4uPn6DQnpIrz64cyixlkk=
moul.io/zapgorm2 v1.3.0/go.mod h1:nPVy6U9goFKHR4s+zfSo1xVFaoU7Qgd5DoCdOfzoCqs=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+6 -2
View File
@@ -1,13 +1,15 @@
package app
import (
"log/slog"
"github.com/go-chi/chi/v5"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
"github.com/go-rat/sessions"
"github.com/go-rat/utils/crypt"
"github.com/knadh/koanf/v2"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/TheTNB/panel/pkg/queue"
@@ -22,7 +24,8 @@ var (
Session *sessions.Manager
Cron *cron.Cron
Queue *queue.Queue
Logger *zap.Logger
Logger *slog.Logger
Crypter crypt.Crypter
)
// 定义面板状态常量
@@ -36,6 +39,7 @@ const (
// 面板全局变量
var (
Key string
Root string
Version string
Locale string
+7 -8
View File
@@ -2,6 +2,7 @@ package benchmark
import (
"bytes"
"compress/gzip"
"crypto/aes"
"crypto/cipher"
cryptorand "crypto/rand"
@@ -20,8 +21,6 @@ import (
"sync"
"time"
"github.com/klauspost/compress/zstd"
"github.com/TheTNB/panel/internal/service"
)
@@ -351,9 +350,9 @@ func (s *Service) compressionTestTask(numThreads int) {
end = len(data)
}
var buf bytes.Buffer
zw, _ := zstd.NewWriter(&buf)
_, _ = zw.Write(data[start:end])
_ = zw.Close()
w := gzip.NewWriter(&buf)
_, _ = w.Write(data[start:end])
_ = w.Close()
compressedChunks[i] = buf
}(i)
}
@@ -365,15 +364,15 @@ func (s *Service) compressionTestTask(numThreads int) {
wg.Add(1)
go func(i int) {
defer wg.Done()
zr, err := zstd.NewReader(&compressedChunks[i])
r, err := gzip.NewReader(&compressedChunks[i])
if err != nil {
return
}
_, err = io.Copy(io.Discard, zr)
_, err = io.Copy(io.Discard, r)
if err != nil {
return
}
zr.Close()
_ = r.Close()
}(i)
}
+19
View File
@@ -0,0 +1,19 @@
package docker
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "docker",
Route: func(r chi.Router) {
service := NewService()
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
},
})
}
+5
View File
@@ -0,0 +1,5 @@
package docker
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
+45
View File
@@ -0,0 +1,45 @@
package docker
import (
"net/http"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/etc/docker/daemon.json")
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, config)
}
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write("/etc/docker/daemon.json", req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("docker"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
+2 -16
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/go-rat/chix"
"github.com/go-rat/utils/str"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/app"
@@ -14,9 +15,7 @@ import (
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type Service struct {
@@ -55,7 +54,6 @@ func (s *Service) List(w http.ResponseWriter, r *http.Request) {
continue
}
jailEnabled := strings.Contains(jailRaw, "enabled = true")
jailLogPath := regexp.MustCompile(`logpath = (.*)`).FindStringSubmatch(jailRaw)
jailMaxRetry := regexp.MustCompile(`maxretry = (.*)`).FindStringSubmatch(jailRaw)
jailFindTime := regexp.MustCompile(`findtime = (.*)`).FindStringSubmatch(jailRaw)
jailBanTime := regexp.MustCompile(`bantime = (.*)`).FindStringSubmatch(jailRaw)
@@ -63,7 +61,6 @@ func (s *Service) List(w http.ResponseWriter, r *http.Request) {
jails = append(jails, Jail{
Name: jailName,
Enabled: jailEnabled,
LogPath: jailLogPath[1],
MaxRetry: cast.ToInt(jailMaxRetry[1]),
FindTime: cast.ToInt(jailFindTime[1]),
BanTime: cast.ToInt(jailBanTime[1]),
@@ -128,7 +125,6 @@ port = ` + ports + `
maxretry = ` + jailMaxRetry + `
findtime = ` + jailFindTime + `
bantime = ` + jailBanTime + `
action = %(action_mwl)s
logpath = ` + app.Root + `/wwwlogs/` + website.Name + `.log
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
`
@@ -158,25 +154,17 @@ ignoreregex =
}
case "service":
var logPath string
var filter string
var port string
var err error
switch jailName {
case "ssh":
if os.IsDebian() || os.IsUbuntu() {
logPath = "/var/log/auth.log"
} else {
logPath = "/var/log/secure"
}
filter = "sshd"
port, err = shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
case "mysql":
logPath = app.Root + "/server/mysql/mysql-error.log"
filter = "mysqld-auth"
port, err = shell.Execf("cat %s/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'", app.Root)
case "pure-ftpd":
logPath = "/var/log/messages"
filter = "pure-ftpd"
port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
default:
@@ -197,8 +185,6 @@ port = ` + port + `
maxretry = ` + jailMaxRetry + `
findtime = ` + jailFindTime + `
bantime = ` + jailBanTime + `
action = %(action_mwl)s
logpath = ` + logPath + `
# ` + jailName + `-END
`
raw += rule
@@ -208,7 +194,7 @@ logpath = ` + logPath + `
}
}
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载配置失败")
return
}
-1
View File
@@ -3,7 +3,6 @@ package fail2ban
type Jail struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
LogPath string `json:"log_path"`
MaxRetry int `json:"max_retry"`
FindTime int `json:"find_time"`
BanTime int `json:"ban_time"`
+2
View File
@@ -4,9 +4,11 @@ import (
"github.com/go-chi/chi/v5"
_ "github.com/TheTNB/panel/internal/apps/benchmark"
_ "github.com/TheTNB/panel/internal/apps/docker"
_ "github.com/TheTNB/panel/internal/apps/fail2ban"
_ "github.com/TheTNB/panel/internal/apps/frp"
_ "github.com/TheTNB/panel/internal/apps/gitea"
_ "github.com/TheTNB/panel/internal/apps/memcached"
_ "github.com/TheTNB/panel/internal/apps/mysql"
_ "github.com/TheTNB/panel/internal/apps/nginx"
_ "github.com/TheTNB/panel/internal/apps/php"
+20
View File
@@ -0,0 +1,20 @@
package memcached
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "memcached",
Route: func(r chi.Router) {
service := NewService()
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
},
})
}
+5
View File
@@ -0,0 +1,5 @@
package memcached
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}
+97
View File
@@ -0,0 +1,97 @@
package memcached
import (
"bufio"
"net"
"net/http"
"regexp"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("memcached")
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get Memcached status: %v", err)
return
}
if !status {
service.Success(w, []types.NV{})
return
}
conn, err := net.Dial("tcp", "127.0.0.1:11211")
if err != nil {
service.Success(w, []types.NV{})
return
}
defer conn.Close()
_, err = conn.Write([]byte("stats\nquit\n"))
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to write to Memcached: %v", err)
return
}
data := make([]types.NV, 0)
re := regexp.MustCompile(`STAT\s(\S+)\s(\S+)`)
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := scanner.Text()
if matches := re.FindStringSubmatch(line); len(matches) == 3 {
data = append(data, types.NV{
Name: matches[1],
Value: matches[2],
})
}
if line == "END" {
break
}
}
if err = scanner.Err(); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to read from Memcached: %v", err)
return
}
service.Success(w, data)
}
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/etc/systemd/system/memcached.service")
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, config)
}
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write("/etc/systemd/system/memcached.service", req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("memcached"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
-1
View File
@@ -15,7 +15,6 @@ func init() {
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/errorLog", service.ErrorLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
+4 -9
View File
@@ -15,8 +15,8 @@ import (
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
@@ -123,7 +123,7 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
if len(matches) > 1 {
d := map[string]string{"name": expression.name, "value": matches[1]}
if expression.name == "发送" || expression.name == "接收" {
d["value"] = str.FormatBytes(cast.ToFloat64(matches[1]))
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
}
load = append(load, d)
@@ -142,14 +142,9 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
service.Success(w, load)
}
// ErrorLog 获取错误日志
func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
service.Success(w, fmt.Sprintf("%s/server/mysql/mysql-error.log", app.Root))
}
// ClearErrorLog 清空错误日志
func (s *Service) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
if _, err := shell.Execf("echo '' > %s/server/mysql/mysql-error.log", app.Root); err != nil {
if err := systemctl.LogsClear("mysqld"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -204,7 +199,7 @@ func (s *Service) SetRootPassword(w http.ResponseWriter, r *http.Request) {
return
}
} else {
if err = mysql.UserPassword("root", req.Password); err != nil {
if err = mysql.UserPassword("root", req.Password, "localhost"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
+2 -2
View File
@@ -13,8 +13,8 @@ import (
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
@@ -95,7 +95,7 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
service.Error(w, http.StatusInternalServerError, "获取负载失败")
return
}
mem := str.FormatBytes(cast.ToFloat64(out))
mem := tools.FormatBytes(cast.ToFloat64(out))
data = append(data, types.NV{
Name: "内存占用",
Value: mem,
+24 -72
View File
@@ -1,6 +1,8 @@
package php
import (
"fmt"
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
@@ -8,76 +10,26 @@ import (
)
func init() {
apploader.Register(&types.App{
Slug: "php80",
Route: func(r chi.Router) {
service := NewService(80)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
apploader.Register(&types.App{
Slug: "php81",
Route: func(r chi.Router) {
service := NewService(81)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
apploader.Register(&types.App{
Slug: "php82",
Route: func(r chi.Router) {
service := NewService(82)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
apploader.Register(&types.App{
Slug: "php83",
Route: func(r chi.Router) {
service := NewService(83)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
php := []uint{74, 80, 81, 82, 83, 84}
for _, version := range php {
apploader.Register(&types.App{
Slug: fmt.Sprintf("php%d", version),
Route: func(r chi.Router) {
service := NewService(version)
r.Post("/setCli", service.SetCli)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/load", service.Load)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
}
}
+65 -65
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
"net/url"
"regexp"
"slices"
"strings"
"time"
@@ -33,6 +32,15 @@ func NewService(version uint) *Service {
}
}
func (s *Service) SetCli(w http.ResponseWriter, r *http.Request) {
if _, err := shell.Execf("ln -sf %s/server/php/%d/bin/php /usr/bin/php", app.Root, s.version); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, s.version))
if err != nil {
@@ -84,26 +92,25 @@ func (s *Service) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {
}
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
var raw map[string]any
client := resty.New().SetTimeout(10 * time.Second)
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d", s.version))
if err != nil || !resp.IsSuccess() {
service.Error(w, http.StatusInternalServerError, "获取负载状态失败:%v", err)
_, err := client.R().SetResult(&raw).Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d?json", s.version))
if err != nil {
service.Success(w, []types.NV{})
return
}
raw := resp.String()
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
regexKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
rawKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
loads := make([]types.NV, len(dataKeys))
loads := make([]types.NV, 0)
for i := range dataKeys {
loads[i].Name = dataKeys[i]
r := regexp.MustCompile(fmt.Sprintf("%s:\\s+(.*)", regexKeys[i]))
match := r.FindStringSubmatch(raw)
if len(match) > 1 {
loads[i].Value = strings.TrimSpace(match[1])
v, ok := raw[rawKeys[i]]
if ok {
loads = append(loads, types.NV{
Name: dataKeys[i],
Value: cast.ToString(v),
})
}
}
@@ -138,17 +145,6 @@ func (s *Service) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
func (s *Service) ExtensionList(w http.ResponseWriter, r *http.Request) {
extensions := s.getExtensions()
// ionCube 只支持 PHP 8.3 以下版本
if cast.ToUint(s.version) < 83 {
extensions = append(extensions, Extension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的 PHP 加密解密工具。",
Installed: false,
})
}
raw, err := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, s.version)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
@@ -182,10 +178,10 @@ func (s *Service) InstallExtension(w http.ResponseWriter, r *http.Request) {
return
}
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/%s.sh" | bash -s -- "install" "%d" >> /tmp/%s.log 2>&1`, url.QueryEscape(req.Slug), s.version, req.Slug)
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'install' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
officials := []string{"fileinfo", "exif", "imap", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
if slices.Contains(officials, req.Slug) {
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/official.sh" | bash -s -- "install" "%d" "%s" >> /tmp/%s.log 2>&1`, s.version, req.Slug, req.Slug)
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'install' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
}
task := new(biz.Task)
@@ -213,10 +209,10 @@ func (s *Service) UninstallExtension(w http.ResponseWriter, r *http.Request) {
return
}
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/%s.sh" | bash -s -- "uninstall" "%d" >> /tmp/%s.log 2>&1`, url.QueryEscape(req.Slug), s.version, req.Slug)
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'uninstall' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
officials := []string{"fileinfo", "exif", "imap", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
if slices.Contains(officials, req.Slug) {
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 "https://dl.cdn.haozi.net/panel/php_exts/official.sh" | bash -s -- "uninstall" "%d" "%s" >> /tmp/%s.log 2>&1`, s.version, req.Slug, req.Slug)
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'uninstall' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
}
task := new(biz.Task)
@@ -237,137 +233,141 @@ func (s *Service) getExtensions() []Extension {
{
Name: "fileinfo",
Slug: "fileinfo",
Description: "Fileinfo 是一个用于识别文件类型的库",
Description: "Fileinfo 是一个用于识别文件类型的库",
},
{
Name: "OPcache",
Slug: "Zend OPcache",
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销",
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销",
},
{
Name: "PhpRedis",
Name: "Redis",
Slug: "redis",
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据",
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据",
},
{
Name: "Memcached",
Slug: "memcached",
Description: "Memcached 使用 libmemcached 库连接 Memcached 服务器",
},
{
Name: "ImageMagick",
Slug: "imagick",
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件",
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件",
},
{
Name: "exif",
Slug: "exif",
Description: "通过 exif 扩展,您可以操作图像元数据",
Description: "通过 exif 扩展,您可以操作图像元数据",
},
{
Name: "pdo_pgsql",
Slug: "pdo_pgsql",
Description: "(需先安装PostgreSQLpdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问",
Description: "pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问(需先安装PostgreSQL",
},
{
Name: "imap",
Slug: "imap",
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件",
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件",
},
{
Name: "zip",
Slug: "zip",
Description: "Zip 是一个用于处理 ZIP 文件的库",
Description: "Zip 是一个用于处理 ZIP 文件的库",
},
{
Name: "bz2",
Slug: "bz2",
Description: "Bzip2 是一个用于压缩和解压缩文件的库",
Description: "Bzip2 是一个用于压缩和解压缩文件的库",
},
{
Name: "readline",
Slug: "readline",
Description: "Readline 是一个库,它提供了一种用于处理文本的接口",
Description: "Readline 是一个库,它提供了一种用于处理文本的接口",
},
{
Name: "snmp",
Slug: "snmp",
Description: "SNMP 是一种用于网络管理的协议",
Description: "SNMP 是一种用于网络管理的协议",
},
{
Name: "ldap",
Slug: "ldap",
Description: "LDAP 是一种用于访问目录服务的协议",
Description: "LDAP 是一种用于访问目录服务的协议",
},
{
Name: "enchant",
Slug: "enchant",
Description: "Enchant 是一个拼写检查库",
Description: "Enchant 是一个拼写检查库",
},
{
Name: "pspell",
Slug: "pspell",
Description: "Pspell 是一个拼写检查库",
Description: "Pspell 是一个拼写检查库",
},
{
Name: "calendar",
Slug: "calendar",
Description: "Calendar 是一个用于处理日期的库",
Description: "Calendar 是一个用于处理日期的库",
},
{
Name: "gmp",
Slug: "gmp",
Description: "GMP 是一个用于处理大整数的库",
Description: "GMP 是一个用于处理大整数的库",
},
{
Name: "sysvmsg",
Slug: "sysvmsg",
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库",
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库",
},
{
Name: "sysvsem",
Slug: "sysvsem",
Description: "Sysvsem 是一个用于处理 System V 信号量的库",
Description: "Sysvsem 是一个用于处理 System V 信号量的库",
},
{
Name: "sysvshm",
Slug: "sysvshm",
Description: "Sysvshm 是一个用于处理 System V 共享内存的库",
Description: "Sysvshm 是一个用于处理 System V 共享内存的库",
},
{
Name: "xsl",
Slug: "xsl",
Description: "XSL 是一个用于处理 XML 文档的库",
Description: "XSL 是一个用于处理 XML 文档的库",
},
{
Name: "intl",
Slug: "intl",
Description: "Intl 是一个用于处理国际化和本地化的库",
Description: "Intl 是一个用于处理国际化和本地化的库",
},
{
Name: "gettext",
Slug: "gettext",
Description: "Gettext 是一个用于处理多语言的库",
Description: "Gettext 是一个用于处理多语言的库",
},
{
Name: "igbinary",
Slug: "igbinary",
Description: "Igbinary 是一个用于序列化和反序列化数据的库",
Description: "Igbinary 是一个用于序列化和反序列化数据的库",
},
{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的 PHP 加密解密工具(需在 OPcache 之后安装)",
},
{
Name: "Swoole",
Slug: "swoole",
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
},
{
Name: "Swow",
Slug: "Swow",
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
},
}
// ionCube 只支持 PHP 8.3 以下版本
if cast.ToUint(s.version) < 83 {
// Swow 不支持 PHP 8.0 以下版本
if cast.ToUint(s.version) >= 80 {
extensions = append(extensions, Extension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的 PHP 加密解密工具。",
Installed: false,
Name: "Swow",
Slug: "Swow",
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
})
}
+1 -1
View File
@@ -16,5 +16,5 @@ type ChangePassword struct {
}
type UpdatePort struct {
Port uint `form:"port" json:"port" validate:"required"`
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
}
+4 -4
View File
@@ -7,11 +7,11 @@ import (
"strings"
"github.com/go-rat/chix"
"github.com/go-rat/utils/str"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
)
@@ -114,11 +114,11 @@ secrets file = /etc/rsyncd.secrets
# ` + req.Name + `-END
`
if err = io.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
if err = io.WriteAppend("/etc/rsyncd.conf", conf, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret)); err != nil {
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -216,7 +216,7 @@ secrets file = /etc/rsyncd.secrets
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret)); err != nil {
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
+5 -5
View File
@@ -14,7 +14,7 @@ import (
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/ntp"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
@@ -77,7 +77,7 @@ func (s *Service) GetSWAP(w http.ResponseWriter, r *http.Request) {
}
size = file.Size() / 1024 / 1024
total = str.FormatBytes(float64(file.Size()))
total = tools.FormatBytes(float64(file.Size()))
} else {
size = 0
total = "0.00 B"
@@ -91,8 +91,8 @@ func (s *Service) GetSWAP(w http.ResponseWriter, r *http.Request) {
match := regexp.MustCompile(`Swap:\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
if len(match) >= 4 {
used = str.FormatBytes(cast.ToFloat64(match[2]) * 1024)
free = str.FormatBytes(cast.ToFloat64(match[3]) * 1024)
used = tools.FormatBytes(cast.ToFloat64(match[2]) * 1024)
free = tools.FormatBytes(cast.ToFloat64(match[3]) * 1024)
}
service.Success(w, chix.M{
@@ -134,7 +134,7 @@ func (s *Service) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
return
}
if cast.ToInt64(free)*1024 < req.Size*1024*1024 {
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", str.FormatBytes(cast.ToFloat64(free)))
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", tools.FormatBytes(cast.ToFloat64(free)))
return
}
+4 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/acme"
"github.com/TheTNB/panel/pkg/types"
)
type Cert struct {
@@ -27,14 +28,16 @@ type Cert struct {
}
type CertRepo interface {
List(page, limit uint) ([]*Cert, int64, error)
List(page, limit uint) ([]*types.CertList, int64, error)
Get(id uint) (*Cert, error)
GetByWebsite(WebsiteID uint) (*Cert, error)
Upload(req *request.CertUpload) (*Cert, error)
Create(req *request.CertCreate) (*Cert, error)
Update(req *request.CertUpdate) error
Delete(id uint) error
ObtainAuto(id uint) (*acme.Certificate, error)
ObtainManual(id uint) (*acme.Certificate, error)
ObtainSelfSigned(id uint) error
Renew(id uint) (*acme.Certificate, error)
ManualDNS(id uint) ([]acme.DNSRecord, error)
Deploy(ID, WebsiteID uint) error
+2 -8
View File
@@ -1,15 +1,13 @@
package biz
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerRepo interface {
ListAll() ([]types.Container, error)
ListByNames(names []string) ([]types.Container, error)
ListByName(name string) ([]types.Container, error)
Create(req *request.ContainerCreate) (string, error)
Remove(id string) error
Start(id string) error
@@ -17,12 +15,8 @@ type ContainerRepo interface {
Restart(id string) error
Pause(id string) error
Unpause(id string) error
Inspect(id string) (types.ContainerJSON, error)
Kill(id string) error
Rename(id string, newName string) error
Stats(id string) (container.StatsResponseReader, error)
Exist(name string) (bool, error)
Update(id string, config container.UpdateConfig) error
Logs(id string) (string, error)
Prune() error
}
+2 -6
View File
@@ -1,17 +1,13 @@
package biz
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerImageRepo interface {
List() ([]image.Summary, error)
Exist(id string) (bool, error)
List() ([]types.ContainerImage, error)
Pull(req *request.ContainerImagePull) error
Remove(id string) error
Prune() error
Inspect(id string) (types.ImageInspect, error)
}
+2 -7
View File
@@ -1,18 +1,13 @@
package biz
import (
"github.com/docker/docker/api/types/network"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerNetworkRepo interface {
List() ([]network.Inspect, error)
List() ([]types.ContainerNetwork, error)
Create(req *request.ContainerNetworkCreate) (string, error)
Remove(id string) error
Exist(name string) (bool, error)
Inspect(id string) (network.Inspect, error)
Connect(networkID string, containerID string) error
Disconnect(networkID string, containerID string) error
Prune() error
}
+3 -6
View File
@@ -1,16 +1,13 @@
package biz
import (
"github.com/docker/docker/api/types/volume"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerVolumeRepo interface {
List() ([]*volume.Volume, error)
Create(req *request.ContainerVolumeCreate) (volume.Volume, error)
Exist(name string) (bool, error)
Inspect(id string) (volume.Volume, error)
List() ([]types.ContainerVolume, error)
Create(req *request.ContainerVolumeCreate) (string, error)
Remove(id string) error
Prune() error
}
+24 -11
View File
@@ -1,16 +1,29 @@
package biz
import "time"
import (
"github.com/TheTNB/panel/internal/http/request"
)
type DatabaseType string
const (
DatabaseTypeMysql DatabaseType = "mysql"
DatabaseTypePostgresql DatabaseType = "postgresql"
DatabaseTypeMongoDB DatabaseType = "mongodb"
DatabaseSQLite DatabaseType = "sqlite"
DatabaseTypeRedis DatabaseType = "redis"
)
type Database struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;unique" json:"name"`
Type string `gorm:"not null" json:"type"`
Host string `gorm:"not null" json:"host"`
Port int `gorm:"not null" json:"port"`
Username string `gorm:"not null" json:"username"`
Password string `gorm:"not null" json:"password"`
Remark string `gorm:"not null" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Type DatabaseType `json:"type"`
Name string `json:"name"`
Server string `json:"server"`
ServerID uint `json:"server_id"`
Encoding string `json:"encoding"`
}
type DatabaseRepo interface {
List(page, limit uint) ([]*Database, int64, error)
Create(req *request.DatabaseCreate) error
Delete(serverID uint, name string) error
}
+61
View File
@@ -0,0 +1,61 @@
package biz
import (
"time"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/http/request"
)
type DatabaseServerStatus string
const (
DatabaseServerStatusValid DatabaseServerStatus = "valid"
DatabaseServerStatusInvalid DatabaseServerStatus = "invalid"
)
type DatabaseServer struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;unique" json:"name"`
Type DatabaseType `gorm:"not null" json:"type"`
Host string `gorm:"not null" json:"host"`
Port uint `gorm:"not null" json:"port"`
Username string `gorm:"not null" json:"username"`
Password string `gorm:"not null" json:"password"`
Status DatabaseServerStatus `gorm:"-:all" json:"status"`
Remark string `gorm:"not null" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (r *DatabaseServer) BeforeSave(tx *gorm.DB) error {
var err error
r.Password, err = app.Crypter.Encrypt([]byte(r.Password))
if err != nil {
return err
}
return nil
}
func (r *DatabaseServer) AfterFind(tx *gorm.DB) error {
password, err := app.Crypter.Decrypt(r.Password)
if err == nil {
r.Password = string(password)
}
return nil
}
type DatabaseServerRepo interface {
Count() (int64, error)
List(page, limit uint) ([]*DatabaseServer, int64, error)
Get(id uint) (*DatabaseServer, error)
Create(req *request.DatabaseServerCreate) error
Update(req *request.DatabaseServerUpdate) error
Delete(id uint) error
Sync(id uint) error
}
+62
View File
@@ -0,0 +1,62 @@
package biz
import (
"time"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/http/request"
)
type DatabaseUserStatus string
const (
DatabaseUserStatusValid DatabaseUserStatus = "valid"
DatabaseUserStatusInvalid DatabaseUserStatus = "invalid"
)
type DatabaseUser struct {
ID uint `gorm:"primaryKey" json:"id"`
ServerID uint `gorm:"not null" json:"server_id"`
Username string `gorm:"not null" json:"username"`
Password string `gorm:"not null" json:"password"`
Host string `gorm:"not null" json:"host"` // 仅 mysql
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
Privileges map[string][]string `gorm:"-:all" json:"privileges"` // 仅显示
Remark string `gorm:"not null" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Server *DatabaseServer `gorm:"foreignKey:ServerID;references:ID" json:"server"`
}
func (r *DatabaseUser) BeforeSave(tx *gorm.DB) error {
var err error
r.Password, err = app.Crypter.Encrypt([]byte(r.Password))
if err != nil {
return err
}
return nil
}
func (r *DatabaseUser) AfterFind(tx *gorm.DB) error {
password, err := app.Crypter.Decrypt(r.Password)
if err == nil {
r.Password = string(password)
}
return nil
}
type DatabaseUserRepo interface {
Count() (int64, error)
List(page, limit uint) ([]*DatabaseUser, int64, error)
Get(id uint) (*DatabaseUser, error)
Create(req *request.DatabaseUserCreate) error
Update(req *request.DatabaseUserUpdate) error
Delete(id uint) error
DeleteByServerID(serverID uint) error
}
-4
View File
@@ -1,4 +0,0 @@
package biz
type FirewallRepo interface {
}
+31
View File
@@ -3,6 +3,9 @@ package biz
import (
"time"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/ssh"
)
@@ -18,6 +21,34 @@ type SSH struct {
UpdatedAt time.Time `json:"updated_at"`
}
func (r *SSH) BeforeSave(tx *gorm.DB) error {
var err error
r.Config.Key, err = app.Crypter.Encrypt([]byte(r.Config.Key))
if err != nil {
return err
}
r.Config.Password, err = app.Crypter.Encrypt([]byte(r.Config.Password))
if err != nil {
return err
}
return nil
}
func (r *SSH) AfterFind(tx *gorm.DB) error {
key, err := app.Crypter.Decrypt(r.Config.Key)
if err == nil {
r.Config.Key = string(key)
}
password, err := app.Crypter.Decrypt(r.Config.Password)
if err == nil {
r.Config.Password = string(password)
}
return nil
}
type SSHRepo interface {
List(page, limit uint) ([]*SSH, int64, error)
Get(id uint) (*SSH, error)
+1
View File
@@ -18,6 +18,7 @@ func boot() {
initLogger()
initOrm()
runMigrate()
bootCrypter()
}
func BootWeb() {
+6 -1
View File
@@ -25,8 +25,13 @@ func initConf() {
}
func initGlobal() {
app.Key = app.Conf.MustString("app.key")
if len(app.Key) != 32 {
log.Fatalf("app key must be 32 characters")
}
app.Root = app.Conf.MustString("app.root")
app.Version = "2.3.14"
app.Version = "2.3.20"
app.Locale = app.Conf.MustString("app.locale")
// 初始化时区
+18
View File
@@ -0,0 +1,18 @@
package bootstrap
import (
"log"
"github.com/go-rat/utils/crypt"
"github.com/TheTNB/panel/internal/app"
)
func bootCrypter() {
crypter, err := crypt.NewXChacha20Poly1305([]byte(app.Key))
if err != nil {
log.Fatalf("failed to create crypter: %v", err)
}
app.Crypter = crypter
}
+2 -11
View File
@@ -6,25 +6,16 @@ import (
"github.com/glebarez/sqlite"
"github.com/go-gormigrate/gormigrate/v2"
slogGorm "github.com/orandin/slog-gorm"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"moul.io/zapgorm2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/migration"
)
func initOrm() {
logLevel := logger.Error
if app.Conf.Bool("database.debug") {
logLevel = logger.Info
}
zapLogger := zapgorm2.New(app.Logger)
zapLogger.LogMode(logLevel)
zapLogger.SetAsDefault()
db, err := gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
Logger: zapLogger,
Logger: slogGorm.New(slogGorm.WithHandler(app.Logger.Handler())),
SkipDefaultTransaction: true,
DisableForeignKeyConstraintWhenMigrating: true,
})
+7 -5
View File
@@ -7,10 +7,10 @@ import (
"net/http"
"path/filepath"
"github.com/bddjr/hlfhr"
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/apps"
"github.com/TheTNB/panel/internal/http/middleware"
"github.com/TheTNB/panel/internal/route"
)
@@ -24,17 +24,19 @@ func initHttp() {
// add route
route.Http(app.Http)
route.Ws(app.Http)
apps.Boot(app.Http)
srv := &http.Server{
srv := hlfhr.New(&http.Server{
Addr: fmt.Sprintf(":%d", app.Conf.MustInt("http.port")),
Handler: http.AllowQuerySemicolons(app.Http),
MaxHeaderBytes: 2048 << 20,
}
})
srv.HttpOnHttpsPortErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hlfhr.RedirectToHttps(w, r, http.StatusTemporaryRedirect)
})
if app.Conf.Bool("http.tls") {
srv.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS13,
}
cert := filepath.Join(app.Root, "panel/storage/cert.pem")
+11 -11
View File
@@ -1,10 +1,12 @@
package bootstrap
import (
"log/slog"
"path/filepath"
"time"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
@@ -13,16 +15,15 @@ import (
func initLogger() {
writeSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: filepath.Join(app.Root, "panel/storage/logs/app.log"),
MaxSize: 10,
MaxBackups: 10,
MaxAge: 30,
Compress: true,
Filename: filepath.Join(app.Root, "panel/storage/logs/app.log"),
MaxSize: 10,
MaxAge: 30,
Compress: true,
})
level := zapcore.InfoLevel
level := zap.InfoLevel
if app.Conf.Bool("app.debug") {
level = zapcore.DebugLevel
level = zap.DebugLevel
}
config := zap.NewProductionEncoderConfig()
@@ -34,8 +35,7 @@ func initLogger() {
)
logger := zap.New(core)
defer func(logger *zap.Logger) {
_ = logger.Sync()
}(logger)
app.Logger = logger
zap.ReplaceGlobals(logger)
app.Logger = slog.New(zapslog.NewHandler(logger.Core()))
slog.SetDefault(app.Logger)
}
+1 -1
View File
@@ -8,6 +8,6 @@ import (
)
func initQueue() {
app.Queue = queue.New(40)
app.Queue = queue.New(100)
go app.Queue.Run(context.Background())
}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func initSession() {
// initialize session manager
manager, err := sessions.NewManager(&sessions.ManagerOptions{
Key: app.Conf.String("app.key"),
Key: app.Key,
Lifetime: 120,
GcInterval: 30,
DisableDefaultDriver: true,
+13 -18
View File
@@ -7,7 +7,9 @@ import (
"slices"
"github.com/expr-lang/expr"
"github.com/go-rat/utils/collect"
"github.com/hashicorp/go-version"
"github.com/samber/do/v2"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/app"
@@ -15,25 +17,18 @@ import (
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type appRepo struct {
cacheRepo biz.CacheRepo
taskRepo biz.TaskRepo
api *api.API
api *api.API
}
func NewAppRepo() biz.AppRepo {
return &appRepo{
cacheRepo: NewCacheRepo(),
taskRepo: NewTaskRepo(),
api: api.NewAPI(app.Version),
}
return do.MustInvoke[biz.AppRepo](injector)
}
func (r *appRepo) All() api.Apps {
cached, err := r.cacheRepo.Get(biz.CacheKeyApps)
cached, err := NewCacheRepo().Get(biz.CacheKeyApps)
if err != nil {
return nil
}
@@ -65,7 +60,7 @@ func (r *appRepo) UpdateExist(slug string) bool {
for channel := range slices.Values(item.Channels) {
if channel.Slug == installed.Channel {
current := str.FirstElement(channel.Subs)
current := collect.First(channel.Subs)
if current != nil && current.Version != installed.Version {
return true
}
@@ -109,7 +104,7 @@ func (r *appRepo) GetHomeShow() ([]map[string]string, error) {
return nil, err
}
var filtered []map[string]string
filtered := make([]map[string]string, 0)
for item := range slices.Values(apps) {
loaded, err := r.Get(item.Slug)
if err != nil {
@@ -168,7 +163,7 @@ func (r *appRepo) Install(channel, slug string) error {
}
shellUrl = ch.Install
shellChannel = ch.Slug
shellVersion = str.FirstElement(ch.Subs).Version
shellVersion = collect.First(ch.Subs).Version
break
}
}
@@ -190,7 +185,7 @@ func (r *appRepo) Install(channel, slug string) error {
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
task.Log = "/tmp/" + item.Slug + ".log"
return r.taskRepo.Push(task)
return NewTaskRepo().Push(task)
}
func (r *appRepo) UnInstall(slug string) error {
@@ -245,7 +240,7 @@ func (r *appRepo) UnInstall(slug string) error {
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
task.Log = "/tmp/" + item.Slug + ".log"
return r.taskRepo.Push(task)
return NewTaskRepo().Push(task)
}
func (r *appRepo) Update(slug string) error {
@@ -278,7 +273,7 @@ func (r *appRepo) Update(slug string) error {
}
shellUrl = ch.Update
shellChannel = ch.Slug
shellVersion = str.FirstElement(ch.Subs).Version
shellVersion = collect.First(ch.Subs).Version
break
}
}
@@ -300,7 +295,7 @@ func (r *appRepo) Update(slug string) error {
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
task.Log = "/tmp/" + item.Slug + ".log"
return r.taskRepo.Push(task)
return NewTaskRepo().Push(task)
}
func (r *appRepo) UpdateShow(slug string, show bool) error {
@@ -331,7 +326,7 @@ func (r *appRepo) UpdateCache() error {
return err
}
return r.cacheRepo.Set(biz.CacheKeyApps, string(encoded))
return NewCacheRepo().Set(biz.CacheKeyApps, string(encoded))
}
func (r *appRepo) preCheck(app *api.App) error {
+43 -51
View File
@@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/samber/do/v2"
"github.com/shirou/gopsutil/disk"
"github.com/TheTNB/panel/internal/app"
@@ -16,20 +17,14 @@ import (
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
type backupRepo struct {
setting biz.SettingRepo
website biz.WebsiteRepo
}
type backupRepo struct{}
func NewBackupRepo() biz.BackupRepo {
return &backupRepo{
setting: NewSettingRepo(),
website: NewWebsiteRepo(),
}
return do.MustInvoke[biz.BackupRepo](injector)
}
// List 备份列表
@@ -53,7 +48,7 @@ func (r *backupRepo) List(typ biz.BackupType) ([]*types.BackupFile, error) {
list = append(list, &types.BackupFile{
Name: file.Name(),
Path: filepath.Join(path, file.Name()),
Size: str.FormatBytes(float64(info.Size())),
Size: tools.FormatBytes(float64(info.Size())),
Time: info.ModTime(),
})
}
@@ -134,7 +129,7 @@ func (r *backupRepo) CutoffLog(path, target string) error {
}
to := filepath.Join(path, fmt.Sprintf("%s_%s.zip", time.Now().Format("20060102150405"), filepath.Base(target)))
if err := io.Compress([]string{target}, to, io.Zip); err != nil {
if err := io.Compress(filepath.Dir(target), []string{filepath.Base(target)}, to); err != nil {
return err
}
@@ -197,7 +192,7 @@ func (r *backupRepo) ClearExpired(path, prefix string, save int) error {
// GetPath 获取备份路径
func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) {
backupPath, err := r.setting.Get(biz.SettingKeyBackupPath)
backupPath, err := NewSettingRepo().Get(biz.SettingKeyBackupPath)
if err != nil {
return "", err
}
@@ -217,7 +212,7 @@ func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) {
// createWebsite 创建网站备份
func (r *backupRepo) createWebsite(to string, name string) error {
website, err := r.website.GetByName(name)
website, err := NewWebsiteRepo().GetByName(name)
if err != nil {
return err
}
@@ -226,18 +221,9 @@ func (r *backupRepo) createWebsite(to string, name string) error {
return err
}
var paths []string
dirs, err := io.ReadDir(website.Path)
if err != nil {
return err
}
for _, item := range dirs {
paths = append(paths, filepath.Join(website.Path, item.Name()))
}
start := time.Now()
backup := filepath.Join(to, fmt.Sprintf("%s_%s.zip", website.Name, time.Now().Format("20060102150405")))
if err = io.Compress(paths, backup, io.Zip); err != nil {
if err = io.Compress(website.Path, nil, backup); err != nil {
return err
}
@@ -250,7 +236,7 @@ func (r *backupRepo) createWebsite(to string, name string) error {
// createMySQL 创建 MySQL 备份
func (r *backupRepo) createMySQL(to string, name string) error {
rootPassword, err := r.setting.Get(biz.SettingKeyMySQLRootPassword)
rootPassword, err := NewSettingRepo().Get(biz.SettingKeyMySQLRootPassword)
if err != nil {
return err
}
@@ -281,7 +267,7 @@ func (r *backupRepo) createMySQL(to string, name string) error {
return err
}
if err = io.Compress([]string{backup}, backup+".zip", io.Zip); err != nil {
if err = io.Compress(filepath.Dir(backup), []string{filepath.Base(backup)}, backup+".zip"); err != nil {
return err
}
if err = io.Remove(backup); err != nil {
@@ -297,7 +283,7 @@ func (r *backupRepo) createMySQL(to string, name string) error {
// createPostgres 创建 PostgreSQL 备份
func (r *backupRepo) createPostgres(to string, name string) error {
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), 5432)
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
if err != nil {
return err
}
@@ -318,7 +304,7 @@ func (r *backupRepo) createPostgres(to string, name string) error {
return err
}
if err = io.Compress([]string{backup}, backup+".zip", io.Zip); err != nil {
if err = io.Compress(filepath.Dir(backup), []string{filepath.Base(backup)}, backup+".zip"); err != nil {
return err
}
if err = io.Remove(backup); err != nil {
@@ -341,14 +327,27 @@ func (r *backupRepo) createPanel(to string) error {
}
start := time.Now()
if err := io.Compress([]string{
filepath.Join(app.Root, "panel"),
"/usr/local/sbin/panel-cli",
"/usr/local/etc/panel/config.yml",
}, backup, io.Zip); err != nil {
temp, err := io.TempDir("panel-backup")
if err != nil {
return err
}
if err := io.Chmod(backup, 0600); err != nil {
if err = io.Cp(filepath.Join(app.Root, "panel"), temp); err != nil {
return err
}
if err = io.Cp("/usr/local/sbin/panel-cli", temp); err != nil {
return err
}
if err = io.Cp("/usr/local/etc/panel/config.yml", temp); err != nil {
return err
}
_ = io.Chmod(temp, 0600)
if err = io.Compress(temp, nil, backup); err != nil {
return err
}
if err = io.Chmod(backup, 0600); err != nil {
return err
}
@@ -356,7 +355,8 @@ func (r *backupRepo) createPanel(to string) error {
fmt.Printf("|-备份耗时:%s\n", time.Since(start).String())
fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup))
}
return nil
return io.Remove(temp)
}
// restoreWebsite 恢复网站备份
@@ -365,11 +365,7 @@ func (r *backupRepo) restoreWebsite(backup, target string) error {
return errors.New("备份文件不存在")
}
website, err := r.website.GetByName(target)
if err != nil {
return err
}
format, err := io.FormatArchiveByPath(backup)
website, err := NewWebsiteRepo().GetByName(target)
if err != nil {
return err
}
@@ -377,7 +373,7 @@ func (r *backupRepo) restoreWebsite(backup, target string) error {
if err = io.Remove(website.Path); err != nil {
return err
}
if err = io.UnCompress(backup, website.Path, format); err != nil {
if err = io.UnCompress(backup, website.Path); err != nil {
return err
}
if err = io.Chmod(website.Path, 0755); err != nil {
@@ -396,7 +392,7 @@ func (r *backupRepo) restoreMySQL(backup, target string) error {
return errors.New("备份文件不存在")
}
rootPassword, err := r.setting.Get(biz.SettingKeyMySQLRootPassword)
rootPassword, err := NewSettingRepo().Get(biz.SettingKeyMySQLRootPassword)
if err != nil {
return err
}
@@ -435,7 +431,7 @@ func (r *backupRepo) restorePostgres(backup, target string) error {
return errors.New("备份文件不存在")
}
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), 5432)
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
if err != nil {
return err
}
@@ -477,9 +473,9 @@ func (r *backupRepo) preCheckPath(to, path string) error {
}
if app.IsCli {
fmt.Printf("|-目标大小:%s\n", str.FormatBytes(float64(size)))
fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size)))
fmt.Printf("|-目标文件数:%d\n", files)
fmt.Printf("|-备份目录可用空间:%s\n", str.FormatBytes(float64(usage.Free)))
fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free)))
fmt.Printf("|-备份目录可用Inode%d\n", usage.InodesFree)
}
@@ -503,8 +499,8 @@ func (r *backupRepo) preCheckDB(to string, size int64) error {
}
if app.IsCli {
fmt.Printf("|-目标大小:%s\n", str.FormatBytes(float64(size)))
fmt.Printf("|-备份目录可用空间:%s\n", str.FormatBytes(float64(usage.Free)))
fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size)))
fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free)))
fmt.Printf("|-备份目录可用Inode%d\n", usage.InodesFree)
}
@@ -522,11 +518,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
return "", err
}
format, err := io.FormatArchiveByPath(backup)
if err != nil {
return "", err
}
if err = io.UnCompress(backup, temp, format); err != nil {
if err = io.UnCompress(backup, temp); err != nil {
return "", err
}
+2 -1
View File
@@ -3,6 +3,7 @@ package data
import (
"errors"
"github.com/samber/do/v2"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
@@ -12,7 +13,7 @@ import (
type cacheRepo struct{}
func NewCacheRepo() biz.CacheRepo {
return &cacheRepo{}
return do.MustInvoke[biz.CacheRepo](injector)
}
func (r *cacheRepo) Get(key biz.CacheKey, defaultValue ...string) (string, error) {
+97 -12
View File
@@ -4,16 +4,21 @@ import (
"context"
"errors"
"fmt"
"slices"
"strings"
"time"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/acme"
pkgcert "github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type certRepo struct {
@@ -21,14 +26,40 @@ type certRepo struct {
}
func NewCertRepo() biz.CertRepo {
return &certRepo{}
return do.MustInvoke[biz.CertRepo](injector)
}
func (r *certRepo) List(page, limit uint) ([]*biz.Cert, int64, error) {
func (r *certRepo) List(page, limit uint) ([]*types.CertList, int64, error) {
var certs []*biz.Cert
var total int64
err := app.Orm.Model(&biz.Cert{}).Preload("Website").Preload("Account").Preload("DNS").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&certs).Error
return certs, total, err
list := make([]*types.CertList, 0)
for cert := range slices.Values(certs) {
item := &types.CertList{
ID: cert.ID,
AccountID: cert.AccountID,
WebsiteID: cert.WebsiteID,
DNSID: cert.DNSID,
Type: cert.Type,
Domains: cert.Domains,
AutoRenew: cert.AutoRenew,
Cert: cert.Cert,
Key: cert.Key,
CreatedAt: cert.CreatedAt,
UpdatedAt: cert.UpdatedAt,
}
if decode, err := pkgcert.ParseCert(cert.Cert); err == nil {
item.NotBefore = decode.NotBefore
item.NotAfter = decode.NotAfter
item.Issuer = decode.Issuer.CommonName
item.OCSPServer = decode.OCSPServer
item.DNSNames = decode.DNSNames
}
list = append(list, item)
}
return list, total, err
}
func (r *certRepo) Get(id uint) (*biz.Cert, error) {
@@ -43,6 +74,28 @@ func (r *certRepo) GetByWebsite(WebsiteID uint) (*biz.Cert, error) {
return cert, err
}
func (r *certRepo) Upload(req *request.CertUpload) (*biz.Cert, error) {
info, err := pkgcert.ParseCert(req.Cert)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %v", err)
}
if _, err = pkgcert.ParseKey(req.Key); err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}
cert := &biz.Cert{
Type: "upload",
Domains: info.DNSNames,
Cert: req.Cert,
Key: req.Key,
}
if err = app.Orm.Create(cert).Error; err != nil {
return nil, err
}
return cert, nil
}
func (r *certRepo) Create(req *request.CertCreate) (*biz.Cert, error) {
cert := &biz.Cert{
AccountID: req.AccountID,
@@ -59,11 +112,19 @@ func (r *certRepo) Create(req *request.CertCreate) (*biz.Cert, error) {
}
func (r *certRepo) Update(req *request.CertUpdate) error {
return app.Orm.Model(&biz.Cert{}).Where("id = ?", req.ID).Updates(&biz.Cert{
info, err := pkgcert.ParseCert(req.Cert)
if err == nil && req.Type == "upload" {
req.Domains = info.DNSNames
}
return app.Orm.Model(&biz.Cert{}).Where("id = ?", req.ID).Select("*").Updates(&biz.Cert{
ID: req.ID,
AccountID: req.AccountID,
WebsiteID: req.WebsiteID,
DNSID: req.DNSID,
Type: req.Type,
Cert: req.Cert,
Key: req.Key,
Domains: req.Domains,
AutoRenew: req.AutoRenew,
}).Error
@@ -88,11 +149,11 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
} else {
if cert.Website == nil {
return nil, errors.New("该证书没有关联网站,无法自动签发")
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
} else {
for _, domain := range cert.Domains {
if strings.Contains(domain, "*") {
return nil, errors.New("通配符域名无法使用 HTTP 验证")
return nil, errors.New("wildcard domains cannot use HTTP verification")
}
}
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
@@ -126,7 +187,7 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
}
if r.client == nil {
return nil, errors.New("请重新获取 DNS 解析记录")
return nil, errors.New("please retry the manual obtain operation")
}
ssl, err := r.client.ObtainCertificateManual()
@@ -148,6 +209,30 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
return &ssl, nil
}
func (r *certRepo) ObtainSelfSigned(id uint) error {
cert, err := r.Get(id)
if err != nil {
return err
}
crt, key, err := pkgcert.GenerateSelfSigned(cert.Domains)
if err != nil {
return err
}
cert.Cert = string(crt)
cert.Key = string(key)
if err = app.Orm.Save(cert).Error; err != nil {
return err
}
if cert.Website != nil {
return r.Deploy(cert.ID, cert.WebsiteID)
}
return nil
}
func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
cert, err := r.Get(id)
if err != nil {
@@ -160,18 +245,18 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
}
if cert.CertURL == "" {
return nil, errors.New("该证书没有签发成功,无法续签")
return nil, errors.New("this certificate has not been signed successfully and cannot be renewed")
}
if cert.DNS != nil {
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
} else {
if cert.Website == nil {
return nil, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发")
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
} else {
for _, domain := range cert.Domains {
if strings.Contains(domain, "*") {
return nil, errors.New("通配符域名无法使用 HTTP 验证")
return nil, errors.New("wildcard domains cannot use HTTP verification")
}
}
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
@@ -231,7 +316,7 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
}
if cert.Cert == "" || cert.Key == "" {
return errors.New("该证书没有签发成功,无法部署")
return errors.New("this certificate has not been signed successfully and cannot be deployed")
}
website, err := NewWebsiteRepo().Get(WebsiteID)
@@ -255,7 +340,7 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
if cert.Account == nil {
return nil, errors.New("该证书没有关联账号,无法签发")
return nil, errors.New("this certificate is not associated with an ACME account and cannot be signed")
}
var ca string
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/go-resty/resty/v2"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
@@ -18,7 +19,7 @@ import (
type certAccountRepo struct{}
func NewCertAccountRepo() biz.CertAccountRepo {
return &certAccountRepo{}
return do.MustInvoke[biz.CertAccountRepo](injector)
}
func (r certAccountRepo) List(page, limit uint) ([]*biz.CertAccount, int64, error) {
+3 -1
View File
@@ -1,6 +1,8 @@
package data
import (
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
@@ -9,7 +11,7 @@ import (
type certDNSRepo struct{}
func NewCertDNSRepo() biz.CertDNSRepo {
return &certDNSRepo{}
return do.MustInvoke[biz.CertDNSRepo](injector)
}
func (r certDNSRepo) List(page, limit uint) ([]*biz.CertDNS, int64, error) {
+130 -156
View File
@@ -1,235 +1,209 @@
package data
import (
"context"
"fmt"
"io"
"strconv"
"slices"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/go-resty/resty/v2"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
paneltypes "github.com/TheTNB/panel/pkg/types"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
"github.com/TheTNB/panel/pkg/types/docker/container"
)
type containerRepo struct {
client *client.Client
client *resty.Client
}
func NewContainerRepo(sock ...string) biz.ContainerRepo {
if len(sock) == 0 {
sock = append(sock, "/run/podman/podman.sock")
}
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
return &containerRepo{
client: cli,
}
func NewContainerRepo() biz.ContainerRepo {
return do.MustInvoke[biz.ContainerRepo](injector)
}
// ListAll 列出所有容器
func (r *containerRepo) ListAll() ([]types.Container, error) {
containers, err := r.client.ContainerList(context.Background(), container.ListOptions{
All: true,
})
var resp []container.Container
_, err := r.client.R().SetResult(&resp).SetQueryParam("all", "true").Get("/containers/json")
if err != nil {
return nil, err
}
var containers []types.Container
for _, item := range resp {
ports := make([]types.ContainerPort, 0)
for _, port := range item.Ports {
ports = append(ports, types.ContainerPort{
ContainerStart: uint(port.PrivatePort),
ContainerEnd: uint(port.PublicPort),
HostStart: uint(port.PublicPort),
HostEnd: uint(port.PublicPort),
Protocol: port.Type,
Host: port.IP,
})
}
if len(item.Names) == 0 {
item.Names = append(item.Names, "")
}
containers = append(containers, types.Container{
ID: item.ID,
Name: strings.TrimPrefix(item.Names[0], "/"), // https://github.com/moby/moby/issues/7519
Image: item.Image,
ImageID: item.ImageID,
Command: item.Command,
CreatedAt: time.Unix(item.Created, 0),
State: item.State,
Status: item.Status,
Ports: ports,
Labels: types.MapToKV(item.Labels),
})
}
slices.SortFunc(containers, func(a types.Container, b types.Container) int {
return strings.Compare(a.Name, b.Name)
})
return containers, nil
}
// ListByNames 根据名称列出容器
func (r *containerRepo) ListByNames(names []string) ([]types.Container, error) {
var options container.ListOptions
options.All = true
if len(names) > 0 {
var array []filters.KeyValuePair
for _, n := range names {
array = append(array, filters.Arg("name", n))
}
options.Filters = filters.NewArgs(array...)
}
containers, err := r.client.ContainerList(context.Background(), options)
// ListByName 根据名称搜索容器
func (r *containerRepo) ListByName(names string) ([]types.Container, error) {
containers, err := r.ListAll()
if err != nil {
return nil, err
}
containers = slices.DeleteFunc(containers, func(item types.Container) bool {
return !strings.Contains(item.Name, names)
})
return containers, nil
}
// Create 创建容器
func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
var hostConf container.HostConfig
var networkConf network.NetworkingConfig
portMap := make(nat.PortMap)
for _, port := range req.Ports {
if port.ContainerStart-port.ContainerEnd != port.HostStart-port.HostEnd {
return "", fmt.Errorf("容器端口和主机端口数量不匹配(容器: %d 主机: %d)", port.ContainerStart-port.ContainerEnd, port.HostStart-port.HostEnd)
}
if port.ContainerStart > port.ContainerEnd || port.HostStart > port.HostEnd || port.ContainerStart < 1 || port.HostStart < 1 {
return "", fmt.Errorf("端口范围不正确")
}
count := 0
for host := port.HostStart; host <= port.HostEnd; host++ {
bindItem := nat.PortBinding{HostPort: strconv.Itoa(host), HostIP: port.Host}
portMap[nat.Port(fmt.Sprintf("%d/%s", port.ContainerStart+count, port.Protocol))] = []nat.PortBinding{bindItem}
count++
}
}
exposed := make(nat.PortSet)
for port := range portMap {
exposed[port] = struct{}{}
}
if req.Network != "" {
switch req.Network {
case "host", "none", "bridge":
hostConf.NetworkMode = container.NetworkMode(req.Network)
}
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("docker run -d --name %s", req.Name))
if req.PublishAllPorts {
sb.WriteString(" -P")
} else {
networkConf = network.NetworkingConfig{}
for _, port := range req.Ports {
sb.WriteString(" -p ")
if port.Host != "" {
sb.WriteString(fmt.Sprintf("%s:", port.Host))
}
if port.HostStart == port.HostEnd || port.ContainerStart == port.ContainerEnd {
sb.WriteString(fmt.Sprintf("%d:%d/%s", port.HostStart, port.ContainerStart, port.Protocol))
} else {
sb.WriteString(fmt.Sprintf("%d-%d:%d-%d/%s", port.HostStart, port.HostEnd, port.ContainerStart, port.ContainerEnd, port.Protocol))
}
}
}
if req.Network != "" {
sb.WriteString(fmt.Sprintf(" --network %s", req.Network))
}
for _, volume := range req.Volumes {
sb.WriteString(fmt.Sprintf(" -v %s:%s:%s", volume.Host, volume.Container, volume.Mode))
}
for _, label := range req.Labels {
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
}
for _, env := range req.Env {
sb.WriteString(fmt.Sprintf(" -e %s=%s", env.Key, env.Value))
}
if len(req.Entrypoint) > 0 {
sb.WriteString(fmt.Sprintf(" --entrypoint '%s'", strings.Join(req.Entrypoint, " ")))
}
if len(req.Command) > 0 {
sb.WriteString(fmt.Sprintf(" '%s'", strings.Join(req.Command, " ")))
}
if req.RestartPolicy != "" {
sb.WriteString(fmt.Sprintf(" --restart %s", req.RestartPolicy))
}
if req.AutoRemove {
sb.WriteString(" --rm")
}
if req.Privileged {
sb.WriteString(" --privileged")
}
if req.OpenStdin {
sb.WriteString(" -i")
}
if req.Tty {
sb.WriteString(" -t")
}
if req.CPUShares > 0 {
sb.WriteString(fmt.Sprintf(" --cpu-shares %d", req.CPUShares))
}
if req.CPUs > 0 {
sb.WriteString(fmt.Sprintf(" --cpus %d", req.CPUs))
}
if req.Memory > 0 {
sb.WriteString(fmt.Sprintf(" --memory %d", req.Memory))
}
hostConf.Privileged = req.Privileged
hostConf.AutoRemove = req.AutoRemove
hostConf.CPUShares = req.CPUShares
hostConf.PublishAllPorts = req.PublishAllPorts
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
hostConf.NanoCPUs = req.CPUs * 1000000000
hostConf.Memory = req.Memory * 1024 * 1024
hostConf.MemorySwap = 0
hostConf.PortBindings = portMap
hostConf.Binds = []string{}
volumes := make(map[string]struct{})
for _, v := range req.Volumes {
volumes[v.Container] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", v.Host, v.Container, v.Mode))
}
resp, err := r.client.ContainerCreate(context.Background(), &container.Config{
Image: req.Image,
Env: paneltypes.KVToSlice(req.Env),
Entrypoint: req.Entrypoint,
Cmd: req.Command,
Labels: paneltypes.KVToMap(req.Labels),
ExposedPorts: exposed,
OpenStdin: req.OpenStdin,
Tty: req.Tty,
Volumes: volumes,
}, &hostConf, &networkConf, nil, req.Name)
if err != nil {
return "", err
}
return resp.ID, err
sb.WriteString(" %s bash")
return shell.ExecfWithTTY(sb.String(), req.Image)
}
// Remove 移除容器
func (r *containerRepo) Remove(id string) error {
return r.client.ContainerRemove(context.Background(), id, container.RemoveOptions{
Force: true,
})
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rm -f %s", id)
return err
}
// Start 启动容器
func (r *containerRepo) Start(id string) error {
return r.client.ContainerStart(context.Background(), id, container.StartOptions{})
_, err := shell.ExecfWithTimeout(120*time.Second, "docker start %s", id)
return err
}
// Stop 停止容器
func (r *containerRepo) Stop(id string) error {
return r.client.ContainerStop(context.Background(), id, container.StopOptions{})
_, err := shell.ExecfWithTimeout(120*time.Second, "docker stop %s", id)
return err
}
// Restart 重启容器
func (r *containerRepo) Restart(id string) error {
return r.client.ContainerRestart(context.Background(), id, container.StopOptions{})
_, err := shell.ExecfWithTimeout(120*time.Second, "docker restart %s", id)
return err
}
// Pause 暂停容器
func (r *containerRepo) Pause(id string) error {
return r.client.ContainerPause(context.Background(), id)
_, err := shell.ExecfWithTimeout(120*time.Second, "docker pause %s", id)
return err
}
// Unpause 恢复容器
func (r *containerRepo) Unpause(id string) error {
return r.client.ContainerUnpause(context.Background(), id)
}
// Inspect 查看容器
func (r *containerRepo) Inspect(id string) (types.ContainerJSON, error) {
return r.client.ContainerInspect(context.Background(), id)
_, err := shell.ExecfWithTimeout(120*time.Second, "docker unpause %s", id)
return err
}
// Kill 杀死容器
func (r *containerRepo) Kill(id string) error {
return r.client.ContainerKill(context.Background(), id, "KILL")
_, err := shell.ExecfWithTimeout(120*time.Second, "docker kill %s", id)
return err
}
// Rename 重命名容器
func (r *containerRepo) Rename(id string, newName string) error {
return r.client.ContainerRename(context.Background(), id, newName)
}
// Stats 查看容器状态
func (r *containerRepo) Stats(id string) (container.StatsResponseReader, error) {
return r.client.ContainerStats(context.Background(), id, false)
}
// Exist 判断容器是否存在
func (r *containerRepo) Exist(name string) (bool, error) {
var options container.ListOptions
options.Filters = filters.NewArgs(filters.Arg("name", name))
containers, err := r.client.ContainerList(context.Background(), options)
if err != nil {
return false, err
}
return len(containers) > 0, nil
}
// Update 更新容器
func (r *containerRepo) Update(id string, config container.UpdateConfig) error {
_, err := r.client.ContainerUpdate(context.Background(), id, config)
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rename %s %s", id, newName)
return err
}
// Logs 查看容器日志
func (r *containerRepo) Logs(id string) (string, error) {
options := container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
}
reader, err := r.client.ContainerLogs(context.Background(), id, options)
if err != nil {
return "", err
}
defer reader.Close()
data, err := io.ReadAll(reader)
if err != nil {
return "", err
}
return string(data), nil
return shell.ExecfWithTimeout(120*time.Second, "docker logs %s", id)
}
// Prune 清理未使用的容器
func (r *containerRepo) Prune() error {
_, err := r.client.ContainersPrune(context.Background(), filters.NewArgs())
_, err := shell.ExecfWithTimeout(120*time.Second, "docker container prune -f")
return err
}
+47 -57
View File
@@ -1,97 +1,87 @@
package data
import (
"context"
"encoding/base64"
"encoding/json"
"io"
"fmt"
"slices"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/client"
"github.com/go-resty/resty/v2"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
"github.com/TheTNB/panel/pkg/types/docker/image"
)
type containerImageRepo struct {
client *client.Client
client *resty.Client
}
func NewContainerImageRepo(sock ...string) biz.ContainerImageRepo {
if len(sock) == 0 {
sock = append(sock, "/run/podman/podman.sock")
}
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
return &containerImageRepo{
client: cli,
}
func NewContainerImageRepo() biz.ContainerImageRepo {
return do.MustInvoke[biz.ContainerImageRepo](injector)
}
// List 列出镜像
func (r *containerImageRepo) List() ([]image.Summary, error) {
return r.client.ImageList(context.Background(), image.ListOptions{
All: true,
})
}
// Exist 判断镜像是否存在
func (r *containerImageRepo) Exist(id string) (bool, error) {
var options image.ListOptions
options.Filters = filters.NewArgs(filters.Arg("reference", id))
images, err := r.client.ImageList(context.Background(), options)
func (r *containerImageRepo) List() ([]types.ContainerImage, error) {
var resp []image.Image
_, err := r.client.R().SetResult(&resp).SetQueryParam("all", "true").Get("/images/json")
if err != nil {
return false, err
return nil, err
}
return len(images) > 0, nil
var images []types.ContainerImage
for _, item := range resp {
images = append(images, types.ContainerImage{
ID: item.ID,
Containers: item.Containers,
RepoTags: item.RepoTags,
RepoDigests: item.RepoDigests,
Size: tools.FormatBytes(float64(item.Size)),
Labels: types.MapToKV(item.Labels),
CreatedAt: time.Unix(item.Created, 0),
})
}
slices.SortFunc(images, func(a types.ContainerImage, b types.ContainerImage) int {
return strings.Compare(a.ID, b.ID)
})
return images, nil
}
// Pull 拉取镜像
func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
options := image.PullOptions{}
var sb strings.Builder
if req.Auth {
authConfig := registry.AuthConfig{
Username: req.Username,
Password: req.Password,
sb.WriteString(fmt.Sprintf("docker login -u %s -p %s", req.Username, req.Password))
if _, err := shell.Exec(sb.String()); err != nil {
return fmt.Errorf("login failed: %w", err)
}
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
return err
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
options.RegistryAuth = authStr
sb.Reset()
}
out, err := r.client.ImagePull(context.Background(), req.Name, options)
if err != nil {
sb.WriteString(fmt.Sprintf("docker pull %s", req.Name))
if _, err := shell.Exec(sb.String()); err != nil {
return err
}
defer out.Close()
_, err = io.Copy(io.Discard, out)
return err
return nil
}
// Remove 删除镜像
func (r *containerImageRepo) Remove(id string) error {
_, err := r.client.ImageRemove(context.Background(), id, image.RemoveOptions{
Force: true,
PruneChildren: true,
})
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rmi %s", id)
return err
}
// Prune 清理未使用的镜像
func (r *containerImageRepo) Prune() error {
_, err := r.client.ImagesPrune(context.Background(), filters.NewArgs())
_, err := shell.ExecfWithTimeout(120*time.Second, "docker image prune -f")
return err
}
// Inspect 查看镜像
func (r *containerImageRepo) Inspect(id string) (types.ImageInspect, error) {
img, _, err := r.client.ImageInspectWithRaw(context.Background(), id)
return img, err
}
+80 -70
View File
@@ -1,104 +1,114 @@
package data
import (
"context"
"fmt"
"slices"
"strings"
"time"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/go-resty/resty/v2"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
"github.com/TheTNB/panel/pkg/types/docker/network"
)
type containerNetworkRepo struct {
client *client.Client
client *resty.Client
}
func NewContainerNetworkRepo(sock ...string) biz.ContainerNetworkRepo {
if len(sock) == 0 {
sock = append(sock, "/run/podman/podman.sock")
}
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
return &containerNetworkRepo{
client: cli,
}
func NewContainerNetworkRepo() biz.ContainerNetworkRepo {
return do.MustInvoke[biz.ContainerNetworkRepo](injector)
}
// List 列出网络
func (r *containerNetworkRepo) List() ([]network.Inspect, error) {
return r.client.NetworkList(context.Background(), network.ListOptions{})
func (r *containerNetworkRepo) List() ([]types.ContainerNetwork, error) {
var resp []network.Network
_, err := r.client.R().SetResult(&resp).Get("/networks")
if err != nil {
return nil, err
}
var networks []types.ContainerNetwork
for _, item := range resp {
ipamConfigs := make([]types.ContainerNetworkIPAMConfig, 0)
for _, ipam := range item.IPAM.Config {
ipamConfigs = append(ipamConfigs, types.ContainerNetworkIPAMConfig{
Subnet: ipam.Subnet,
IPRange: ipam.IPRange,
Gateway: ipam.Gateway,
AuxAddress: ipam.AuxAddress,
})
}
networks = append(networks, types.ContainerNetwork{
ID: item.ID,
Name: item.Name,
Driver: item.Driver,
IPv6: item.EnableIPv6,
Internal: item.Internal,
Attachable: item.Attachable,
Ingress: item.Ingress,
Scope: item.Scope,
CreatedAt: item.Created,
IPAM: types.ContainerNetworkIPAM{
Driver: item.IPAM.Driver,
Options: types.MapToKV(item.IPAM.Options),
Config: ipamConfigs,
},
Options: types.MapToKV(item.Options),
Labels: types.MapToKV(item.Labels),
})
}
slices.SortFunc(networks, func(a types.ContainerNetwork, b types.ContainerNetwork) int {
return strings.Compare(a.Name, b.Name)
})
return networks, nil
}
// Create 创建网络
func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (string, error) {
var ipamConfigs []network.IPAMConfig
if req.Ipv4.Enabled {
ipamConfigs = append(ipamConfigs, network.IPAMConfig{
Subnet: req.Ipv4.Subnet,
Gateway: req.Ipv4.Gateway,
IPRange: req.Ipv4.IPRange,
})
}
if req.Ipv6.Enabled {
ipamConfigs = append(ipamConfigs, network.IPAMConfig{
Subnet: req.Ipv6.Subnet,
Gateway: req.Ipv6.Gateway,
IPRange: req.Ipv6.IPRange,
})
}
var sb strings.Builder
options := network.CreateOptions{
EnableIPv6: &req.Ipv6.Enabled,
Driver: req.Driver,
Options: types.KVToMap(req.Options),
Labels: types.KVToMap(req.Labels),
}
if len(ipamConfigs) > 0 {
options.IPAM = &network.IPAM{
Config: ipamConfigs,
sb.WriteString(fmt.Sprintf("docker network create --driver %s", req.Driver))
sb.WriteString(fmt.Sprintf(" %s", req.Name))
if req.Ipv4.Enabled {
sb.WriteString(fmt.Sprintf(" --subnet %s", req.Ipv4.Subnet))
sb.WriteString(fmt.Sprintf(" --gateway %s", req.Ipv4.Gateway))
if req.Ipv4.IPRange != "" {
sb.WriteString(fmt.Sprintf(" --ip-range %s", req.Ipv4.IPRange))
}
}
if req.Ipv6.Enabled {
sb.WriteString(fmt.Sprintf(" --subnet %s", req.Ipv6.Subnet))
sb.WriteString(fmt.Sprintf(" --gateway %s", req.Ipv6.Gateway))
if req.Ipv6.IPRange != "" {
sb.WriteString(fmt.Sprintf(" --ip-range %s", req.Ipv6.IPRange))
}
}
for _, label := range req.Labels {
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
}
for _, option := range req.Options {
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
}
resp, err := r.client.NetworkCreate(context.Background(), req.Name, options)
return resp.ID, err
return shell.Exec(sb.String())
}
// Remove 删除网络
func (r *containerNetworkRepo) Remove(id string) error {
return r.client.NetworkRemove(context.Background(), id)
}
// Exist 判断网络是否存在
func (r *containerNetworkRepo) Exist(name string) (bool, error) {
var options network.ListOptions
options.Filters = filters.NewArgs(filters.Arg("name", name))
networks, err := r.client.NetworkList(context.Background(), options)
if err != nil {
return false, err
}
return len(networks) > 0, nil
}
// Inspect 查看网络
func (r *containerNetworkRepo) Inspect(id string) (network.Inspect, error) {
return r.client.NetworkInspect(context.Background(), id, network.InspectOptions{})
}
// Connect 连接网络
func (r *containerNetworkRepo) Connect(networkID string, containerID string) error {
return r.client.NetworkConnect(context.Background(), networkID, containerID, nil)
}
// Disconnect 断开网络
func (r *containerNetworkRepo) Disconnect(networkID string, containerID string) error {
return r.client.NetworkDisconnect(context.Background(), networkID, containerID, true)
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network rm -f %s", id)
return err
}
// Prune 清理未使用的网络
func (r *containerNetworkRepo) Prune() error {
_, err := r.client.NetworksPrune(context.Background(), filters.NewArgs())
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network prune -f")
return err
}
+52 -38
View File
@@ -1,74 +1,88 @@
package data
import (
"context"
"fmt"
"slices"
"strings"
"time"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/go-resty/resty/v2"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
"github.com/TheTNB/panel/pkg/types/docker/volume"
)
type containerVolumeRepo struct {
client *client.Client
client *resty.Client
}
func NewContainerVolumeRepo(sock ...string) biz.ContainerVolumeRepo {
if len(sock) == 0 {
sock = append(sock, "/run/podman/podman.sock")
}
cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation())
return &containerVolumeRepo{
client: cli,
}
func NewContainerVolumeRepo() biz.ContainerVolumeRepo {
return do.MustInvoke[biz.ContainerVolumeRepo](injector)
}
// List 列出存储卷
func (r *containerVolumeRepo) List() ([]*volume.Volume, error) {
volumes, err := r.client.VolumeList(context.Background(), volume.ListOptions{})
func (r *containerVolumeRepo) List() ([]types.ContainerVolume, error) {
var resp volume.ListResponse
_, err := r.client.R().SetResult(&resp).Get("/volumes")
if err != nil {
return nil, err
}
return volumes.Volumes, err
var volumes []types.ContainerVolume
for _, item := range resp.Volumes {
volumes = append(volumes, types.ContainerVolume{
Name: item.Name,
Driver: item.Driver,
Scope: item.Scope,
MountPoint: item.Mountpoint,
CreatedAt: item.CreatedAt,
Labels: types.MapToKV(item.Labels),
Options: types.MapToKV(item.Options),
RefCount: item.UsageData.RefCount,
Size: tools.FormatBytes(float64(item.UsageData.Size)),
})
}
slices.SortFunc(volumes, func(a types.ContainerVolume, b types.ContainerVolume) int {
return strings.Compare(a.Name, b.Name)
})
return volumes, nil
}
// Create 创建存储卷
func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (volume.Volume, error) {
return r.client.VolumeCreate(context.Background(), volume.CreateOptions{
Name: req.Name,
Driver: req.Driver,
DriverOpts: types.KVToMap(req.Options),
Labels: types.KVToMap(req.Labels),
})
}
func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string, error) {
var sb strings.Builder
sb.WriteString("docker volume create")
sb.WriteString(fmt.Sprintf(" %s", req.Name))
// Exist 判断存储卷是否存在
func (r *containerVolumeRepo) Exist(id string) (bool, error) {
var options volume.ListOptions
options.Filters = filters.NewArgs(filters.Arg("name", id))
volumes, err := r.client.VolumeList(context.Background(), options)
if err != nil {
return false, err
if req.Driver != "" {
sb.WriteString(fmt.Sprintf(" --driver %s", req.Driver))
}
for _, label := range req.Labels {
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
}
return len(volumes.Volumes) > 0, nil
}
for _, option := range req.Options {
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
}
// Inspect 查看存储卷
func (r *containerVolumeRepo) Inspect(id string) (volume.Volume, error) {
return r.client.VolumeInspect(context.Background(), id)
return shell.Exec(sb.String())
}
// Remove 删除存储卷
func (r *containerVolumeRepo) Remove(id string) error {
return r.client.VolumeRemove(context.Background(), id, true)
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume rm -f %s", id)
return err
}
// Prune 清理未使用的存储卷
func (r *containerVolumeRepo) Prune() error {
_, err := r.client.VolumesPrune(context.Background(), filters.NewArgs())
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume prune -f")
return err
}
+6 -8
View File
@@ -7,24 +7,22 @@ import (
"strconv"
"time"
"github.com/go-rat/utils/str"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
)
type cronRepo struct {
settingRepo biz.SettingRepo
}
type cronRepo struct{}
func NewCronRepo() biz.CronRepo {
return &cronRepo{
settingRepo: NewSettingRepo(),
}
return do.MustInvoke[biz.CronRepo](injector)
}
func (r *cronRepo) Count() (int64, error) {
@@ -99,7 +97,7 @@ panel-cli cutoff clear -t website -f %s -s %d -p %s
if !io.Exists(shellLogDir) {
return errors.New("计划任务日志目录不存在")
}
shellFile := strconv.Itoa(int(time.Now().Unix())) + str.RandomString(16)
shellFile := strconv.Itoa(int(time.Now().Unix())) + str.Random(16)
if err := io.Write(filepath.Join(shellDir, shellFile+".sh"), script, 0700); err != nil {
return errors.New(err.Error())
}
+136
View File
@@ -0,0 +1,136 @@
package data
import (
"fmt"
"slices"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/db"
)
type databaseRepo struct{}
func NewDatabaseRepo() biz.DatabaseRepo {
return do.MustInvoke[biz.DatabaseRepo](injector)
}
func (r databaseRepo) List(page, limit uint) ([]*biz.Database, int64, error) {
var databaseServer []*biz.DatabaseServer
if err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Find(&databaseServer).Error; err != nil {
return nil, 0, err
}
database := make([]*biz.Database, 0)
for _, server := range databaseServer {
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err == nil {
if databases, err := mysql.Databases(); err == nil {
for item := range slices.Values(databases) {
database = append(database, &biz.Database{
Type: biz.DatabaseTypeMysql,
Name: item.Name,
Server: server.Name,
ServerID: server.ID,
Encoding: item.CharSet,
})
}
}
}
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err == nil {
if databases, err := postgres.Databases(); err == nil {
for item := range slices.Values(databases) {
database = append(database, &biz.Database{
Type: biz.DatabaseTypePostgresql,
Name: item.Name,
Server: server.Name,
ServerID: server.ID,
Encoding: item.Encoding,
})
}
}
}
}
}
return database[(page-1)*limit:], int64(len(database)), nil
}
func (r databaseRepo) Create(req *request.DatabaseCreate) error {
server, err := NewDatabaseServerRepo().Get(req.ServerID)
if err != nil {
return err
}
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err != nil {
return err
}
if req.CreateUser {
if err = mysql.UserCreate(req.Username, req.Password, req.Host); err != nil {
return err
}
}
if err = mysql.DatabaseCreate(req.Name); err != nil {
return err
}
if req.Username != "" {
if err = mysql.PrivilegesGrant(req.Username, req.Name, req.Host); err != nil {
return err
}
}
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {
return err
}
if req.CreateUser {
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
return err
}
}
if err = postgres.DatabaseCreate(req.Name); err != nil {
return err
}
if req.Username != "" {
if err = postgres.PrivilegesGrant(req.Username, req.Name); err != nil {
return err
}
}
}
return nil
}
func (r databaseRepo) Delete(serverID uint, name string) error {
server, err := NewDatabaseServerRepo().Get(serverID)
if err != nil {
return err
}
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err != nil {
return err
}
return mysql.DatabaseDrop(name)
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {
return err
}
return postgres.DatabaseDrop(name)
}
return nil
}
+188
View File
@@ -0,0 +1,188 @@
package data
import (
"fmt"
"log/slog"
"slices"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/db"
)
type databaseServerRepo struct{}
func NewDatabaseServerRepo() biz.DatabaseServerRepo {
return do.MustInvoke[biz.DatabaseServerRepo](injector)
}
func (r databaseServerRepo) Count() (int64, error) {
var count int64
if err := app.Orm.Model(&biz.DatabaseServer{}).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (r databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64, error) {
var databaseServer []*biz.DatabaseServer
var total int64
err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&databaseServer).Error
for server := range slices.Values(databaseServer) {
r.checkServer(server)
}
return databaseServer, total, err
}
func (r databaseServerRepo) Get(id uint) (*biz.DatabaseServer, error) {
databaseServer := new(biz.DatabaseServer)
if err := app.Orm.Where("id = ?", id).First(databaseServer).Error; err != nil {
return nil, err
}
r.checkServer(databaseServer)
return databaseServer, nil
}
func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
databaseServer := &biz.DatabaseServer{
Name: req.Name,
Type: biz.DatabaseType(req.Type),
Host: req.Host,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Remark: req.Remark,
}
if !r.checkServer(databaseServer) {
return fmt.Errorf("check server connection failed")
}
return app.Orm.Create(databaseServer).Error
}
func (r databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
server, err := r.Get(req.ID)
if err != nil {
return err
}
server.Name = req.Name
server.Host = req.Host
server.Port = req.Port
server.Username = req.Username
server.Password = req.Password
server.Remark = req.Remark
if !r.checkServer(server) {
return fmt.Errorf("check server connection failed")
}
return app.Orm.Save(server).Error
}
func (r databaseServerRepo) Delete(id uint) error {
// 删除服务器下的所有用户
if err := NewDatabaseUserRepo().DeleteByServerID(id); err != nil {
return err
}
return app.Orm.Where("id = ?", id).Delete(&biz.DatabaseServer{}).Error
}
func (r databaseServerRepo) Sync(id uint) error {
server, err := r.Get(id)
if err != nil {
return err
}
users := make([]*biz.DatabaseUser, 0)
if err = app.Orm.Where("server_id = ?", id).Find(&users).Error; err != nil {
return err
}
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err != nil {
return err
}
allUsers, err := mysql.Users()
if err != nil {
return err
}
for user := range slices.Values(allUsers) {
if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool {
return a.Username == user.User && a.Host == user.Host
}) && !slices.Contains([]string{"root", "mysql.sys", "mysql.session", "mysql.infoschema"}, user.User) {
newUser := &biz.DatabaseUser{
ServerID: id,
Username: user.User,
Host: user.Host,
Remark: fmt.Sprintf("sync from server %s", server.Name),
}
if err = app.Orm.Create(newUser).Error; err != nil {
app.Logger.Warn("sync database user failed", slog.Any("err", err))
}
}
}
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {
return err
}
allUsers, err := postgres.Users()
if err != nil {
return err
}
for user := range slices.Values(allUsers) {
if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool {
return a.Username == user.Role
}) && !slices.Contains([]string{"postgres"}, user.Role) {
newUser := &biz.DatabaseUser{
ServerID: id,
Username: user.Role,
Remark: fmt.Sprintf("sync from server %s", server.Name),
}
app.Orm.Create(newUser)
}
}
}
return nil
}
// checkServer 检查服务器连接
func (r databaseServerRepo) checkServer(server *biz.DatabaseServer) bool {
switch server.Type {
case biz.DatabaseTypeMysql:
_, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err == nil {
server.Status = biz.DatabaseServerStatusValid
return true
}
case biz.DatabaseTypePostgresql:
_, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err == nil {
server.Status = biz.DatabaseServerStatusValid
return true
}
case biz.DatabaseTypeRedis:
_, err := db.NewRedis(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err == nil {
server.Status = biz.DatabaseServerStatusValid
return true
}
}
server.Status = biz.DatabaseServerStatusInvalid
return false
}
+214
View File
@@ -0,0 +1,214 @@
package data
import (
"fmt"
"slices"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/db"
)
type databaseUserRepo struct{}
func NewDatabaseUserRepo() biz.DatabaseUserRepo {
return do.MustInvoke[biz.DatabaseUserRepo](injector)
}
func (r databaseUserRepo) Count() (int64, error) {
var count int64
if err := app.Orm.Model(&biz.DatabaseUser{}).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (r databaseUserRepo) List(page, limit uint) ([]*biz.DatabaseUser, int64, error) {
var user []*biz.DatabaseUser
var total int64
err := app.Orm.Model(&biz.DatabaseUser{}).Preload("Server").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&user).Error
for u := range slices.Values(user) {
r.fillUser(u)
}
return user, total, err
}
func (r databaseUserRepo) Get(id uint) (*biz.DatabaseUser, error) {
user := new(biz.DatabaseUser)
if err := app.Orm.Preload("Server").Where("id = ?", id).First(user).Error; err != nil {
return nil, err
}
r.fillUser(user)
return user, nil
}
func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
server, err := NewDatabaseServerRepo().Get(req.ServerID)
if err != nil {
return err
}
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err != nil {
return err
}
if err = mysql.UserCreate(req.Username, req.Password, req.Host); err != nil {
return err
}
for name := range slices.Values(req.Privileges) {
if err = mysql.PrivilegesGrant(req.Username, name, req.Host); err != nil {
return err
}
}
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {
return err
}
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
return err
}
for name := range slices.Values(req.Privileges) {
if err = postgres.PrivilegesGrant(req.Username, name); err != nil {
return err
}
}
}
user := &biz.DatabaseUser{
ServerID: req.ServerID,
Username: req.Username,
Password: req.Password,
Host: req.Host,
Remark: req.Remark,
}
return app.Orm.Create(user).Error
}
func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
user, err := r.Get(req.ID)
if err != nil {
return err
}
server, err := NewDatabaseServerRepo().Get(user.ServerID)
if err != nil {
return err
}
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err != nil {
return err
}
if req.Password != "" {
if err = mysql.UserPassword(user.Username, req.Password, user.Host); err != nil {
return err
}
}
for name := range slices.Values(req.Privileges) {
if err = mysql.PrivilegesGrant(user.Username, name, user.Host); err != nil {
return err
}
}
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {
return err
}
if req.Password != "" {
if err = postgres.UserPassword(user.Username, req.Password); err != nil {
return err
}
}
for name := range slices.Values(req.Privileges) {
if err = postgres.PrivilegesGrant(user.Username, name); err != nil {
return err
}
}
}
user.Password = req.Password
user.Remark = req.Remark
return app.Orm.Save(user).Error
}
func (r databaseUserRepo) Delete(id uint) error {
user, err := r.Get(id)
if err != nil {
return err
}
server, err := NewDatabaseServerRepo().Get(user.ServerID)
if err != nil {
return err
}
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err != nil {
return err
}
_ = mysql.UserDrop(user.Username)
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {
return err
}
_ = postgres.DatabaseDrop(user.Username)
}
return app.Orm.Where("id = ?", id).Delete(&biz.DatabaseUser{}).Error
}
func (r databaseUserRepo) DeleteByServerID(serverID uint) error {
return app.Orm.Where("server_id = ?", serverID).Delete(&biz.DatabaseUser{}).Error
}
func (r databaseUserRepo) fillUser(user *biz.DatabaseUser) {
server, err := NewDatabaseServerRepo().Get(user.ServerID)
if err == nil {
switch server.Type {
case biz.DatabaseTypeMysql:
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
if err == nil {
privileges, _ := mysql.UserPrivileges(user.Username, user.Host)
user.Privileges = privileges
}
if _, err := db.NewMySQL(user.Username, user.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)); err == nil {
user.Status = biz.DatabaseUserStatusValid
} else {
user.Status = biz.DatabaseUserStatusInvalid
}
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err == nil {
privileges, _ := postgres.UserPrivileges(user.Username)
user.Privileges = privileges
}
if _, err := db.NewPostgres(user.Username, user.Password, server.Host, server.Port); err == nil {
user.Status = biz.DatabaseUserStatusValid
} else {
user.Status = biz.DatabaseUserStatusInvalid
}
}
}
// 初始化,防止 nil
if user.Privileges == nil {
user.Privileges = make(map[string][]string)
}
}
+120
View File
@@ -0,0 +1,120 @@
package data
import (
"context"
"net"
"net/http"
"time"
"github.com/go-rat/utils/hash"
"github.com/go-resty/resty/v2"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/os"
)
var injector = do.New()
func init() {
do.Provide(injector, func(i do.Injector) (biz.AppRepo, error) {
return &appRepo{
api: api.NewAPI(app.Version),
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.BackupRepo, error) {
return &backupRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.CacheRepo, error) {
return &cacheRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.CertRepo, error) {
return &certRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.CertAccountRepo, error) {
return &certAccountRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.CertDNSRepo, error) {
return &certDNSRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.ContainerRepo, error) {
return &containerRepo{
client: getDockerClient("/var/run/docker.sock"),
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.ContainerImageRepo, error) {
return &containerImageRepo{
client: getDockerClient("/var/run/docker.sock"),
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.ContainerNetworkRepo, error) {
return &containerNetworkRepo{
client: getDockerClient("/var/run/docker.sock"),
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.ContainerVolumeRepo, error) {
return &containerVolumeRepo{
client: getDockerClient("/var/run/docker.sock"),
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.CronRepo, error) {
return &cronRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.DatabaseServerRepo, error) {
return &databaseServerRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.DatabaseUserRepo, error) {
return &databaseUserRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.DatabaseRepo, error) {
return &databaseRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.MonitorRepo, error) {
return &monitorRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.SafeRepo, error) {
var ssh string
if os.IsRHEL() {
ssh = "sshd"
} else {
ssh = "ssh"
}
return &safeRepo{
ssh: ssh,
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.SettingRepo, error) {
return &settingRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.SSHRepo, error) {
return &sshRepo{}, nil
})
do.Provide(injector, func(i do.Injector) (biz.TaskRepo, error) {
task := &taskRepo{}
task.DispatchWaiting()
return task, nil
})
do.Provide(injector, func(i do.Injector) (biz.UserRepo, error) {
return &userRepo{
hasher: hash.NewArgon2id(),
}, nil
})
do.Provide(injector, func(i do.Injector) (biz.WebsiteRepo, error) {
return &websiteRepo{}, nil
})
}
func getDockerClient(sock string) *resty.Client {
client := resty.New()
client.SetTimeout(1 * time.Minute)
client.SetRetryCount(2)
client.SetTransport(&http.Transport{
DialContext: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", sock)
},
})
client.SetBaseURL("http://d/v1.40")
return client
}
+9 -10
View File
@@ -4,6 +4,7 @@ import (
"errors"
"time"
"github.com/samber/do/v2"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/app"
@@ -11,22 +12,19 @@ import (
"github.com/TheTNB/panel/internal/http/request"
)
type monitorRepo struct {
settingRepo biz.SettingRepo
}
type monitorRepo struct{}
func NewMonitorRepo() biz.MonitorRepo {
return &monitorRepo{
settingRepo: NewSettingRepo(),
}
return do.MustInvoke[biz.MonitorRepo](injector)
}
func (r monitorRepo) GetSetting() (*request.MonitorSetting, error) {
monitor, err := r.settingRepo.Get(biz.SettingKeyMonitor)
repo := NewSettingRepo()
monitor, err := repo.Get(biz.SettingKeyMonitor)
if err != nil {
return nil, err
}
monitorDays, err := r.settingRepo.Get(biz.SettingKeyMonitorDays)
monitorDays, err := repo.Get(biz.SettingKeyMonitorDays)
if err != nil {
return nil, err
}
@@ -39,10 +37,11 @@ func (r monitorRepo) GetSetting() (*request.MonitorSetting, error) {
}
func (r monitorRepo) UpdateSetting(setting *request.MonitorSetting) error {
if err := r.settingRepo.Set(biz.SettingKeyMonitor, cast.ToString(setting.Enabled)); err != nil {
repo := NewSettingRepo()
if err := repo.Set(biz.SettingKeyMonitor, cast.ToString(setting.Enabled)); err != nil {
return err
}
if err := r.settingRepo.Set(biz.SettingKeyMonitorDays, cast.ToString(setting.Days)); err != nil {
if err := repo.Set(biz.SettingKeyMonitorDays, cast.ToString(setting.Days)); err != nil {
return err
}
+2 -11
View File
@@ -4,11 +4,11 @@ import (
"fmt"
"strings"
"github.com/samber/do/v2"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/pkg/firewall"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
@@ -18,16 +18,7 @@ type safeRepo struct {
}
func NewSafeRepo() biz.SafeRepo {
var ssh string
if os.IsRHEL() {
ssh = "sshd"
} else {
ssh = "ssh"
}
return &safeRepo{
ssh: ssh,
}
return do.MustInvoke[biz.SafeRepo](injector)
}
func (r *safeRepo) GetSSH() (uint, bool, error) {
+33 -22
View File
@@ -8,28 +8,27 @@ import (
"slices"
"github.com/go-rat/utils/hash"
"github.com/goccy/go-yaml"
"github.com/samber/do/v2"
"github.com/spf13/cast"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/firewall"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
type settingRepo struct {
taskRepo biz.TaskRepo
}
type settingRepo struct{}
func NewSettingRepo() biz.SettingRepo {
return &settingRepo{
taskRepo: NewTaskRepo(),
}
return do.MustInvoke[biz.SettingRepo](injector)
}
func (r *settingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, error) {
@@ -108,7 +107,7 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
return nil, err
}
cert, err := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
crt, err := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
if err != nil {
return nil, err
}
@@ -126,9 +125,9 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
BackupPath: backupPath,
Username: user.Username,
Email: user.Email,
Port: app.Conf.Int("http.port"),
Port: uint(app.Conf.Int("http.port")),
HTTPS: app.Conf.Bool("http.tls"),
Cert: cert,
Cert: crt,
Key: key,
}, nil
}
@@ -168,16 +167,23 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
}
// 下面是需要需要重启的设置
task := NewTaskRepo()
// 面板HTTPS
restartFlag := false
oldCert, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem"))
oldKey, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.key"))
if oldCert != setting.Cert || oldKey != setting.Key {
if r.taskRepo.HasRunningTask() {
if task.HasRunningTask() {
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
}
restartFlag = true
}
if _, err := cert.ParseCert(setting.Cert); err != nil {
return false, fmt.Errorf("failed to parse certificate: %w", err)
}
if _, err := cert.ParseKey(setting.Key); err != nil {
return false, fmt.Errorf("failed to parse private key: %w", err)
}
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil {
return false, err
}
@@ -187,15 +193,20 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
// 面板主配置
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return false, err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return false, err
}
if setting.Port != config.HTTP.Port {
if os.TCPPortInUse(setting.Port) {
return false, errors.New("端口已被占用")
}
}
config.App.Locale = setting.Locale
config.HTTP.Port = setting.Port
config.HTTP.Entrance = setting.Entrance
@@ -214,12 +225,12 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
return false, err
}
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return false, err
}
if raw != string(encoded) {
if r.taskRepo.HasRunningTask() {
if task.HasRunningTask() {
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
}
restartFlag = true
@@ -291,7 +302,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
}
return fmt.Errorf("备份面板失败:%w", err)
}
if err := io.Compress([]string{filepath.Join(app.Root, "panel/storage")}, "/tmp/panel-storage.zip", io.Zip); err != nil {
if err := io.Compress(filepath.Join(app.Root, "panel/storage"), nil, "/tmp/panel-storage.zip"); err != nil {
if app.IsCli {
fmt.Println("|-备份面板数据失败:", err)
}
@@ -311,7 +322,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
if app.IsCli {
fmt.Println("|-解压新版本...")
}
if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel"), io.Zip); err != nil {
if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel")); err != nil {
return fmt.Errorf("解压失败:%w", err)
}
if !io.Exists(filepath.Join(app.Root, "panel", "web")) {
@@ -321,7 +332,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
if app.IsCli {
fmt.Println("|-恢复面板数据...")
}
if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel"), io.Zip); err != nil {
if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel", "storage")); err != nil {
return fmt.Errorf("恢复面板数据失败:%w", err)
}
if !io.Exists(filepath.Join(app.Root, "panel/storage/app.db")) {
@@ -392,7 +403,7 @@ func (r *settingRepo) FixPanel() error {
return fmt.Errorf("清理临时文件失败:%w", err)
}
if app.IsCli {
fmt.Println("已清理临时文件,请运行 panel-cli update 更新面板")
fmt.Println("|-已清理临时文件,请运行 panel-cli update 更新面板")
}
return nil
}
@@ -422,7 +433,7 @@ func (r *settingRepo) FixPanel() error {
if err = io.Remove("/tmp/panel-fix"); err != nil {
return fmt.Errorf("清理临时目录失败:%w", err)
}
if err = io.UnCompress(latest.Path, "/tmp/panel-fix", io.Zip); err != nil {
if err = io.UnCompress(latest.Path, "/tmp/panel-fix"); err != nil {
return fmt.Errorf("解压备份文件失败:%w", err)
}
@@ -449,12 +460,12 @@ func (r *settingRepo) FixPanel() error {
}
}
// tmp目录下如果有storage备份,则解压回去
// tmp 目录下如果有 storage 备份,则解压回去
if app.IsCli {
fmt.Println("|-恢复面板数据...")
}
if io.Exists("/tmp/panel-storage.zip") {
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel"), io.Zip); err != nil {
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel")); err != nil {
return fmt.Errorf("恢复面板数据失败:%w", err)
}
if err = io.Remove("/tmp/panel-storage.zip"); err != nil {
+5 -7
View File
@@ -3,20 +3,18 @@ package data
import (
"fmt"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
pkgssh "github.com/TheTNB/panel/pkg/ssh"
)
type sshRepo struct {
settingRepo biz.SettingRepo
}
type sshRepo struct{}
func NewSSHRepo() biz.SSHRepo {
return &sshRepo{
settingRepo: NewSettingRepo(),
}
return do.MustInvoke[biz.SSHRepo](injector)
}
func (r *sshRepo) List(page, limit uint) ([]*biz.SSH, int64, error) {
@@ -81,7 +79,7 @@ func (r *sshRepo) Update(req *request.SSHUpdate) error {
Remark: req.Remark,
}
return app.Orm.Model(ssh).Updates(ssh).Error
return app.Orm.Model(ssh).Where("id = ?", req.ID).Select("*").Updates(ssh).Error
}
func (r *sshRepo) Delete(id uint) error {
+20 -9
View File
@@ -1,23 +1,20 @@
package data
import (
"sync"
"fmt"
"log/slog"
"go.uber.org/zap"
"github.com/samber/do/v2"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/queuejob"
)
var taskDispatchOnce sync.Once
type taskRepo struct{}
func NewTaskRepo() biz.TaskRepo {
task := &taskRepo{}
taskDispatchOnce.Do(task.DispatchWaiting)
return &taskRepo{}
return do.MustInvoke[biz.TaskRepo](injector)
}
func (r *taskRepo) HasRunningTask() bool {
@@ -48,9 +45,18 @@ func (r *taskRepo) UpdateStatus(id uint, status biz.TaskStatus) error {
}
func (r *taskRepo) Push(task *biz.Task) error {
var count int64
if err := app.Orm.Model(&biz.Task{}).Where("shell = ? and (status = ? or status = ?)", task.Shell, biz.TaskStatusWaiting, biz.TaskStatusRunning).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return fmt.Errorf("duplicate submission, please wait for the previous task to end")
}
if err := app.Orm.Create(task).Error; err != nil {
return err
}
return app.Queue.Push(queuejob.NewProcessTask(r), []any{
task.ID,
})
@@ -62,9 +68,14 @@ func (r *taskRepo) DispatchWaiting() {
return
}
if err := app.Orm.Model(&biz.Task{}).Where("status = ?", biz.TaskStatusRunning).Update("status", biz.TaskStatusFailed).Error; err != nil {
app.Logger.Warn("failed to mark running tasks as failed", slog.Any("err", err))
return
}
var tasks []biz.Task
if err := app.Orm.Where("status = ?", biz.TaskStatusWaiting).Find(&tasks).Error; err != nil {
app.Logger.Error("获取待处理任务失败", zap.Error(err))
app.Logger.Warn("failed to get pending tasks", slog.Any("err", err))
return
}
@@ -72,7 +83,7 @@ func (r *taskRepo) DispatchWaiting() {
if err := app.Queue.Push(queuejob.NewProcessTask(r), []any{
task.ID,
}); err != nil {
app.Logger.Error("推送任务失败", zap.Error(err))
app.Logger.Warn("failed to push task", slog.Any("err", err))
return
}
}
+2 -3
View File
@@ -4,6 +4,7 @@ import (
"errors"
"github.com/go-rat/utils/hash"
"github.com/samber/do/v2"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
@@ -15,9 +16,7 @@ type userRepo struct {
}
func NewUserRepo() biz.UserRepo {
return &userRepo{
hasher: hash.NewArgon2id(),
}
return do.MustInvoke[biz.UserRepo](injector)
}
func (r *userRepo) Create(username, password string) (*biz.User, error) {
+54 -28
View File
@@ -7,7 +7,9 @@ import (
"path/filepath"
"slices"
"strings"
"time"
"github.com/samber/do/v2"
"github.com/samber/lo"
"github.com/spf13/cast"
@@ -29,7 +31,7 @@ import (
type websiteRepo struct{}
func NewWebsiteRepo() biz.WebsiteRepo {
return &websiteRepo{}
return do.MustInvoke[biz.WebsiteRepo](injector)
}
func (r *websiteRepo) UpdateDefaultConfig(req *request.WebsiteDefaultConfig) error {
@@ -137,8 +139,8 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
setting.SSLCertificateKey = key
// 解析证书信息
if decode, err := cert.ParseCert(crt); err == nil {
setting.SSLNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
setting.SSLNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
setting.SSLNotBefore = decode.NotBefore.Format(time.DateTime)
setting.SSLNotAfter = decode.NotAfter.Format(time.DateTime)
setting.SSLIssuer = decode.Issuer.CommonName
setting.SSLOCSPServer = decode.OCSPServer
setting.SSLDNSNames = decode.DNSNames
@@ -271,12 +273,24 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
return nil, err
}
// PHP 网站默认开启防跨站
if req.PHP > 0 {
userIni := filepath.Join(req.Path, ".user.ini")
if !io.Exists(userIni) {
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Path), 0644); err != nil {
return nil, err
}
}
_, _ = shell.Execf(`chattr +i '%s'`, userIni)
}
// 创建面板网站
w := &biz.Website{
Name: req.Name,
Status: true,
Path: req.Path,
Https: false,
Remark: req.Remark,
}
if err = app.Orm.Create(w).Error; err != nil {
return nil, err
@@ -294,24 +308,30 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
if err != nil {
return nil, err
}
if err = mysql.UserCreate(req.DBUser, req.DBPassword, "localhost"); err != nil {
return nil, err
}
if err = mysql.DatabaseCreate(req.DBName); err != nil {
return nil, err
}
if err = mysql.UserCreate(req.DBUser, req.DBPassword); err != nil {
return nil, err
}
if err = mysql.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
if err = mysql.PrivilegesGrant(req.DBUser, req.DBName, "localhost"); err != nil {
return nil, err
}
}
if req.DB && req.DBType == "postgresql" {
_, _ = shell.Execf(`echo "CREATE DATABASE '%s';" | su - postgres -c "psql"`, req.DBName)
_, _ = shell.Execf(`echo "CREATE USER '%s' WITH PASSWORD '%s';" | su - postgres -c "psql"`, req.DBUser, req.DBPassword)
_, _ = shell.Execf(`echo "ALTER DATABASE '%s' OWNER TO '%s';" | su - postgres -c "psql"`, req.DBName, req.DBUser)
_, _ = shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE '%s' TO '%s';" | su - postgres -c "psql"`, req.DBName, req.DBUser)
userConfig := "host " + req.DBName + " " + req.DBUser + " 127.0.0.1/32 scram-sha-256"
_, _ = shell.Execf(`echo "`+userConfig+`" >> %s/server/postgresql/data/pg_hba.conf`, app.Root)
_ = systemctl.Reload("postgresql")
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
if err != nil {
return nil, err
}
if err = postgres.UserCreate(req.DBUser, req.DBPassword); err != nil {
return nil, err
}
if err = postgres.DatabaseCreate(req.DBName); err != nil {
return nil, err
}
if err = postgres.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
return nil, err
}
}
return w, nil
@@ -417,11 +437,6 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
if err = p.SetOCSP(req.OCSP); err != nil {
return err
}
if quic {
if err = p.SetAltSvc(`'h3=":$server_port"; ma=2592000'`); err != nil {
return err
}
}
} else {
if err = p.ClearSetHTTPS(); err != nil {
return err
@@ -435,6 +450,12 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
if err = p.SetOCSP(false); err != nil {
return err
}
}
if quic {
if err = p.SetAltSvc(`'h3=":$server_port"; ma=2592000'`); err != nil {
return err
}
} else {
if err = p.SetAltSvc(``); err != nil {
return err
}
@@ -445,14 +466,14 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
}
userIni := filepath.Join(req.Root, ".user.ini")
if req.OpenBasedir {
_, _ = shell.Execf(`chattr -i '%s'`, userIni)
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Root), 0644); err != nil {
return err
if !io.Exists(userIni) {
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Path), 0644); err != nil {
return err
}
}
_, _ = shell.Execf(`chattr +i '%s'`, userIni)
} else {
if io.Exists(userIni) {
_, _ = shell.Execf(`chattr -i '%s'`, userIni)
if err = io.Remove(userIni); err != nil {
return err
}
@@ -503,13 +524,14 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
if err != nil {
return err
}
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
if err == nil {
_ = mysql.DatabaseDrop(website.Name)
if mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix"); err == nil {
_ = mysql.UserDrop(website.Name)
_ = mysql.DatabaseDrop(website.Name)
}
if postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432); err == nil {
_ = postgres.UserDrop(website.Name)
_ = postgres.DatabaseDrop(website.Name)
}
_, _ = shell.Execf(`echo "DROP DATABASE IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
_, _ = shell.Execf(`echo "DROP USER IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
}
if err := app.Orm.Delete(website).Error; err != nil {
@@ -708,6 +730,10 @@ func (r *websiteRepo) ObtainCert(ctx context.Context, id uint) error {
return err
}
}
newCert.Domains = website.Domains
if err = app.Orm.Save(newCert).Error; err != nil {
return err
}
_, err = cRepo.ObtainAuto(newCert.ID)
if err != nil {
+9 -3
View File
@@ -1,10 +1,12 @@
package middleware
import (
"log/slog"
"net/http"
"github.com/go-chi/chi/v5/middleware"
sessionmiddleware "github.com/go-rat/sessions/middleware"
"github.com/golang-cz/httplog"
"github.com/TheTNB/panel/internal/app"
)
@@ -16,11 +18,15 @@ func GlobalMiddleware() []func(http.Handler) http.Handler {
//middleware.SupressNotFound(app.Http),// bug https://github.com/go-chi/chi/pull/940
middleware.CleanPath,
middleware.StripSlashes,
middleware.Logger,
middleware.Recoverer,
middleware.Compress(5),
Entrance,
httplog.RequestLogger(app.Logger, &httplog.Options{
Level: slog.LevelInfo,
LogRequestHeaders: []string{"User-Agent"},
}),
middleware.Recoverer,
Status,
Entrance,
MustLogin,
MustInstall,
}
}
+16
View File
@@ -3,6 +3,8 @@ package middleware
import (
"context"
"net/http"
"slices"
"strings"
"github.com/go-rat/chix"
"github.com/spf13/cast"
@@ -12,6 +14,14 @@ import (
// MustLogin 确保已登录
func MustLogin(next http.Handler) http.Handler {
// 白名单
whiteList := []string{
"/api/user/login",
"/api/user/logout",
"/api/user/isLogin",
"/api/dashboard/panel",
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sess, err := app.Session.GetSession(r)
if err != nil {
@@ -22,6 +32,12 @@ func MustLogin(next http.Handler) http.Handler {
})
}
// 对白名单和非 API 请求放行
if slices.Contains(whiteList, r.URL.Path) || !strings.HasPrefix(r.URL.Path, "/api") {
next.ServeHTTP(w, r)
return
}
if sess.Missing("user_id") {
render := chix.NewRender(w)
render.Status(http.StatusUnauthorized)
+8 -1
View File
@@ -1,5 +1,10 @@
package request
type CertUpload struct {
Cert string `form:"cert" json:"cert" validate:"required"`
Key string `form:"key" json:"key" validate:"required"`
}
type CertCreate struct {
Type string `form:"type" json:"type" validate:"required,oneof=P256 P384 2048 3072 4096"`
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
@@ -11,8 +16,10 @@ type CertCreate struct {
type CertUpdate struct {
ID uint `form:"id" json:"id" validate:"required,exists=certs id"`
Type string `form:"type" json:"type" validate:"required,oneof=P256 P384 2048 3072 4096"`
Type string `form:"type" json:"type" validate:"required,oneof=upload P256 P384 2048 3072 4096"`
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
Cert string `form:"cert" json:"cert"`
Key string `form:"key" json:"key"`
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
AccountID uint `form:"account_id" json:"account_id"`
DNSID uint `form:"dns_id" json:"dns_id"`
+18 -18
View File
@@ -12,22 +12,22 @@ type ContainerRename struct {
}
type ContainerCreate struct {
Name string `form:"name" json:"name" validate:"required"`
Image string `form:"image" json:"image" validate:"required"`
Ports []types.ContainerPort `form:"ports" json:"ports"`
Network string `form:"network" json:"network"`
Volumes []types.ContainerVolume `form:"volumes" json:"volumes"`
Labels []types.KV `form:"labels" json:"labels"`
Env []types.KV `form:"env" json:"env"`
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
Command []string `form:"command" json:"command"`
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
Privileged bool `form:"privileged" json:"privileged"`
OpenStdin bool `form:"openStdin" json:"open_stdin"`
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
Tty bool `form:"tty" json:"tty"`
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
CPUs int64 `form:"cpus" json:"cpus"`
Memory int64 `form:"memory" json:"memory"`
Name string `form:"name" json:"name" validate:"required"`
Image string `form:"image" json:"image" validate:"required"`
Ports []types.ContainerPort `form:"ports" json:"ports"`
Network string `form:"network" json:"network"`
Volumes []types.ContainerContainerVolume `form:"volumes" json:"volumes"`
Labels []types.KV `form:"labels" json:"labels"`
Env []types.KV `form:"env" json:"env"`
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
Command []string `form:"command" json:"command"`
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
Privileged bool `form:"privileged" json:"privileged"`
OpenStdin bool `form:"openStdin" json:"open_stdin"`
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
Tty bool `form:"tty" json:"tty"`
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
CPUs int64 `form:"cpus" json:"cpus"`
Memory int64 `form:"memory" json:"memory"`
}
+6 -11
View File
@@ -7,15 +7,10 @@ type ContainerNetworkID struct {
}
type ContainerNetworkCreate struct {
Name string `form:"name" json:"name" validate:"required"`
Driver string `form:"driver" json:"driver"`
Ipv4 types.ContainerNetwork `form:"ipv4" json:"ipv4"`
Ipv6 types.ContainerNetwork `form:"ipv6" json:"ipv6"`
Labels []types.KV `form:"labels" json:"labels"`
Options []types.KV `form:"options" json:"options"`
}
type ContainerNetworkConnect struct {
Network string `form:"network" json:"network" validate:"required"`
Container string `form:"container" json:"container" validate:"required"`
Name string `form:"name" json:"name" validate:"required"`
Driver string `form:"driver" json:"driver"`
Ipv4 types.ContainerContainerNetwork `form:"ipv4" json:"ipv4"`
Ipv6 types.ContainerContainerNetwork `form:"ipv6" json:"ipv6"`
Labels []types.KV `form:"labels" json:"labels"`
Options []types.KV `form:"options" json:"options"`
}
+16
View File
@@ -0,0 +1,16 @@
package request
type DatabaseCreate struct {
ServerID uint `form:"server_id" json:"server_id" validate:"required,exists=database_servers id"`
Name string `form:"name" json:"name" validate:"required"`
CreateUser bool `form:"create_user" json:"create_user"`
Username string `form:"username" json:"username" validate:"required_if=CreateUser true"`
Password string `form:"password" json:"password" validate:"required_if=CreateUser true"`
Host string `form:"host" json:"host"`
Remark string `form:"remark" json:"remark"`
}
type DatabaseDelete struct {
ServerID uint `form:"server_id" json:"server_id" validate:"required,exists=database_servers id"`
Name string `form:"name" json:"name" validate:"required"`
}
+21
View File
@@ -0,0 +1,21 @@
package request
type DatabaseServerCreate struct {
Name string `form:"name" json:"name" validate:"required,not_exists=database_servers name"`
Type string `form:"type" json:"type" validate:"required,oneof=mysql postgresql redis"`
Host string `form:"host" json:"host" validate:"required"`
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
Remark string `form:"remark" json:"remark"`
}
type DatabaseServerUpdate struct {
ID uint `form:"id" json:"id" validate:"required,exists=database_servers id"`
Name string `form:"name" json:"name" validate:"required,not_exists=database_servers name"`
Host string `form:"host" json:"host" validate:"required"`
Port uint `form:"port" json:"port" validate:"required,number,gte=1,lte=65535"`
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
Remark string `form:"remark" json:"remark"`
}
+17
View File
@@ -0,0 +1,17 @@
package request
type DatabaseUserCreate struct {
ServerID uint `form:"server_id" json:"server_id" validate:"required,exists=database_servers id"`
Username string `form:"username" json:"username" validate:"required"`
Password string `form:"password" json:"password" validate:"required"`
Host string `form:"host" json:"host"`
Privileges []string `form:"privileges" json:"privileges"`
Remark string `form:"remark" json:"remark"`
}
type DatabaseUserUpdate struct {
ID uint `form:"id" json:"id" validate:"required,exists=database_users id"`
Password string `form:"password" json:"password"`
Privileges []string `form:"privileges" json:"privileges"`
Remark string `form:"remark" json:"remark"`
}
+2 -7
View File
@@ -25,13 +25,7 @@ type FileSave struct {
Content string `form:"content" json:"content"`
}
type FileMove struct {
Source string `form:"source" json:"source" validate:"required"`
Target string `form:"target" json:"target" validate:"required"`
Force bool `form:"force" json:"force"`
}
type FileCopy struct {
type FileControl struct {
Source string `form:"source" json:"source" validate:"required"`
Target string `form:"target" json:"target" validate:"required"`
Force bool `form:"force" json:"force"`
@@ -50,6 +44,7 @@ type FilePermission struct {
}
type FileCompress struct {
Dir string `form:"dir" json:"dir" validate:"required"`
Paths []string `form:"paths" json:"paths" validate:"min=1,dive,required"`
File string `form:"file" json:"file" validate:"required"`
}
+1 -1
View File
@@ -10,7 +10,7 @@ type PanelSetting struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"password"`
Email string `json:"email" validate:"required"`
Port int `json:"port" validate:"required,number,gte=1,lte=65535"`
Port uint `json:"port" validate:"required,number,gte=1,lte=65535"`
HTTPS bool `json:"https"`
Cert string `json:"cert" validate:"required"`
Key string `json:"key" validate:"required"`
+1
View File
@@ -18,6 +18,7 @@ type WebsiteCreate struct {
DBName string `form:"db_name" json:"db_name"`
DBUser string `form:"db_user" json:"db_user"`
DBPassword string `form:"db_password" json:"db_password" validate:"password"`
Remark string `form:"remark" json:"remark"`
}
type WebsiteDelete struct {
+4 -5
View File
@@ -1,10 +1,9 @@
package job
import (
"log/slog"
"time"
"go.uber.org/zap"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
@@ -29,12 +28,12 @@ func (r *CertRenew) Run() {
var certs []biz.Cert
if err := app.Orm.Preload("Website").Preload("Account").Preload("DNS").Find(&certs).Error; err != nil {
app.Logger.Error("获取证书失败", zap.Error(err))
app.Logger.Warn("获取证书失败", slog.Any("err", err))
return
}
for _, cert := range certs {
if !cert.AutoRenew {
if cert.Type == "upload" || !cert.AutoRenew {
continue
}
@@ -51,7 +50,7 @@ func (r *CertRenew) Run() {
_, err = r.certRepo.Renew(cert.ID)
if err != nil {
app.Logger.Error("续签证书失败", zap.Error(err))
app.Logger.Warn("续签证书失败", slog.Any("err", err))
}
}
}
+4 -4
View File
@@ -1,10 +1,10 @@
package job
import (
"log/slog"
"time"
"github.com/spf13/cast"
"go.uber.org/zap"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
@@ -48,7 +48,7 @@ func (r *Monitoring) Run() {
}
if err = app.Orm.Create(&biz.Monitor{Info: info}).Error; err != nil {
app.Logger.Error("记录系统监控失败", zap.Error(err))
app.Logger.Warn("记录系统监控失败", slog.Any("err", err))
return
}
@@ -61,8 +61,8 @@ func (r *Monitoring) Run() {
if day <= 0 || app.Status != app.StatusNormal {
return
}
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format("2006-01-02 15:04:05")).Delete(&biz.Monitor{}).Error; err != nil {
app.Logger.Error("删除过期系统监控失败", zap.Error(err))
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format(time.DateTime)).Delete(&biz.Monitor{}).Error; err != nil {
app.Logger.Warn("删除过期系统监控失败", slog.Any("err", err))
return
}
}
+13 -10
View File
@@ -1,10 +1,11 @@
package job
import (
"log/slog"
"math/rand/v2"
"runtime"
"runtime/debug"
"go.uber.org/zap"
"time"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
@@ -32,32 +33,34 @@ func (r *PanelTask) Run() {
// 优化数据库
if err := app.Orm.Exec("VACUUM").Error; err != nil {
app.Status = app.StatusFailed
app.Logger.Error("优化面板数据库失败", zap.Error(err))
app.Logger.Warn("优化面板数据库失败", slog.Any("err", err))
}
if err := app.Orm.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
app.Status = app.StatusFailed
app.Logger.Error("优化面板数据库失败", zap.Error(err))
app.Logger.Warn("优化面板数据库失败", slog.Any("err", err))
}
// 备份面板
if err := r.backupRepo.Create(biz.BackupTypePanel, ""); err != nil {
app.Logger.Error("备份面板失败", zap.Error(err))
app.Logger.Warn("备份面板失败", slog.Any("err", err))
}
// 清理备份
path, err := r.backupRepo.GetPath("panel")
if err == nil {
if err = r.backupRepo.ClearExpired(path, "panel_", 10); err != nil {
app.Logger.Error("清理面板备份失败", zap.Error(err))
app.Logger.Warn("清理面板备份失败", slog.Any("err", err))
}
}
// 更新商店缓存
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
if err = r.appRepo.UpdateCache(); err != nil {
app.Logger.Error("更新商店缓存失败", zap.Error(err))
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() {
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
if err = r.appRepo.UpdateCache(); err != nil {
app.Logger.Warn("更新商店缓存失败", slog.Any("err", err))
}
}
}
})
// 回收内存
runtime.GC()
+25 -2
View File
@@ -17,7 +17,6 @@ func init() {
&biz.CertDNS{},
&biz.CertAccount{},
&biz.Cron{},
&biz.Database{},
&biz.Monitor{},
&biz.App{},
&biz.Setting{},
@@ -32,7 +31,6 @@ func init() {
&biz.CertDNS{},
&biz.CertAccount{},
&biz.Cron{},
&biz.Database{},
&biz.Monitor{},
&biz.App{},
&biz.Setting{},
@@ -55,4 +53,29 @@ func init() {
)
},
})
Migrations = append(Migrations, &gormigrate.Migration{
ID: "20241107-database",
Migrate: func(tx *gorm.DB) error {
return nil
},
Rollback: func(tx *gorm.DB) error {
return nil
},
})
Migrations = append(Migrations, &gormigrate.Migration{
ID: "20241124-database",
Migrate: func(tx *gorm.DB) error {
_ = tx.Migrator().DropTable("databases")
return tx.AutoMigrate(
&biz.DatabaseServer{},
&biz.DatabaseUser{},
)
},
Rollback: func(tx *gorm.DB) error {
return tx.Migrator().DropTable(
&biz.DatabaseServer{},
&biz.DatabaseUser{},
)
},
})
}
+4 -1
View File
@@ -2,7 +2,9 @@ package queuejob
import (
"errors"
"log/slog"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/pkg/shell"
)
@@ -36,7 +38,7 @@ func (r *ProcessTask) Handle(args ...any) error {
return err
}
if _, err = shell.Execf(task.Shell); err != nil { // nolint: govet
if _, err = shell.Exec(task.Shell); err != nil {
return err
}
@@ -48,5 +50,6 @@ func (r *ProcessTask) Handle(args ...any) error {
}
func (r *ProcessTask) ErrHandle(err error) {
app.Logger.Warn("background task failed", slog.Any("task_id", r.taskID), slog.Any("err", err))
_ = r.taskRepo.UpdateStatus(r.taskID, biz.TaskStatusFailed)
}
+50
View File
@@ -74,6 +74,11 @@ func Cli() []*cli.Command {
Usage: "关闭HTTPS",
Action: cliService.HTTPSOff,
},
{
Name: "generate",
Usage: "生成HTTPS证书",
Action: cliService.HTTPSGenerate,
},
},
},
{
@@ -168,6 +173,51 @@ func Cli() []*cli.Command {
},
},
},
{
Name: "database",
Usage: "数据库管理",
Commands: []*cli.Command{
{
Name: "add-server",
Usage: "添加数据库服务器",
Action: cliService.DatabaseAddServer,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "type",
Usage: "服务器类型",
Required: true,
},
&cli.StringFlag{
Name: "name",
Usage: "服务器名称",
Required: true,
},
&cli.StringFlag{
Name: "host",
Usage: "服务器地址",
Required: true,
},
&cli.UintFlag{
Name: "port",
Usage: "服务器端口",
Required: true,
},
&cli.StringFlag{
Name: "username",
Usage: "服务器用户名",
},
&cli.StringFlag{
Name: "password",
Usage: "服务器密码",
},
&cli.StringFlag{
Name: "remark",
Usage: "服务器备注",
},
},
},
},
},
{
Name: "backup",
Usage: "数据备份",
+44 -36
View File
@@ -8,6 +8,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/internal/apps"
"github.com/TheTNB/panel/internal/embed"
"github.com/TheTNB/panel/internal/http/middleware"
"github.com/TheTNB/panel/internal/service"
@@ -20,25 +21,24 @@ func Http(r chi.Router) {
r.With(middleware.Throttle(5, time.Minute)).Post("/login", user.Login)
r.Post("/logout", user.Logout)
r.Get("/isLogin", user.IsLogin)
r.With(middleware.MustLogin).Get("/info", user.Info)
r.Get("/info", user.Info)
})
r.Route("/dashboard", func(r chi.Router) {
dashboard := service.NewDashboardService()
r.Get("/panel", dashboard.Panel)
r.With(middleware.MustLogin).Get("/homeApps", dashboard.HomeApps)
r.With(middleware.MustLogin).Post("/current", dashboard.Current)
r.With(middleware.MustLogin).Get("/systemInfo", dashboard.SystemInfo)
r.With(middleware.MustLogin).Get("/countInfo", dashboard.CountInfo)
r.With(middleware.MustLogin).Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
r.With(middleware.MustLogin).Get("/checkUpdate", dashboard.CheckUpdate)
r.With(middleware.MustLogin).Get("/updateInfo", dashboard.UpdateInfo)
r.With(middleware.MustLogin).Post("/update", dashboard.Update)
r.With(middleware.MustLogin).Post("/restart", dashboard.Restart)
r.Get("/homeApps", dashboard.HomeApps)
r.Post("/current", dashboard.Current)
r.Get("/systemInfo", dashboard.SystemInfo)
r.Get("/countInfo", dashboard.CountInfo)
r.Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
r.Get("/checkUpdate", dashboard.CheckUpdate)
r.Get("/updateInfo", dashboard.UpdateInfo)
r.Post("/update", dashboard.Update)
r.Post("/restart", dashboard.Restart)
})
r.Route("/task", func(r chi.Router) {
r.Use(middleware.MustLogin)
task := service.NewTaskService()
r.Get("/status", task.Status)
r.Get("/", task.List)
@@ -47,7 +47,6 @@ func Http(r chi.Router) {
})
r.Route("/website", func(r chi.Router) {
r.Use(middleware.MustLogin)
website := service.NewWebsiteService()
r.Get("/defaultConfig", website.GetDefaultConfig)
r.Post("/defaultConfig", website.UpdateDefaultConfig)
@@ -63,8 +62,31 @@ func Http(r chi.Router) {
r.Post("/{id}/obtainCert", website.ObtainCert)
})
r.Route("/database", func(r chi.Router) {
database := service.NewDatabaseService()
r.Get("/", database.List)
r.Post("/", database.Create)
r.Delete("/", database.Delete)
})
r.Route("/databaseServer", func(r chi.Router) {
server := service.NewDatabaseServerService()
r.Get("/", server.List)
r.Post("/", server.Create)
r.Put("/{id}", server.Update)
r.Delete("/{id}", server.Delete)
r.Post("/{id}/sync", server.Sync)
})
r.Route("/databaseUser", func(r chi.Router) {
user := service.NewDatabaseUserService()
r.Get("/", user.List)
r.Post("/", user.Create)
r.Put("/{id}", user.Update)
r.Delete("/{id}", user.Delete)
})
r.Route("/backup", func(r chi.Router) {
r.Use(middleware.MustLogin)
backup := service.NewBackupService()
r.Get("/{type}", backup.List)
r.Post("/{type}", backup.Create)
@@ -74,7 +96,6 @@ func Http(r chi.Router) {
})
r.Route("/cert", func(r chi.Router) {
r.Use(middleware.MustLogin)
cert := service.NewCertService()
r.Get("/caProviders", cert.CAProviders)
r.Get("/dnsProviders", cert.DNSProviders)
@@ -82,10 +103,13 @@ func Http(r chi.Router) {
r.Route("/cert", func(r chi.Router) {
r.Get("/", cert.List)
r.Post("/", cert.Create)
r.Post("/upload", cert.Upload)
r.Put("/{id}", cert.Update)
r.Get("/{id}", cert.Get)
r.Delete("/{id}", cert.Delete)
r.Post("/{id}/obtain", cert.Obtain)
r.Post("/{id}/obtainAuto", cert.ObtainAuto)
r.Post("/{id}/obtainManual", cert.ObtainManual)
r.Post("/{id}/obtainSelfSigned", cert.ObtainSelfSigned)
r.Post("/{id}/renew", cert.Renew)
r.Post("/{id}/manualDNS", cert.ManualDNS)
r.Post("/{id}/deploy", cert.Deploy)
@@ -109,7 +133,6 @@ func Http(r chi.Router) {
})
r.Route("/app", func(r chi.Router) {
r.Use(middleware.MustLogin)
app := service.NewAppService()
r.Get("/list", app.List)
r.Post("/install", app.Install)
@@ -121,7 +144,6 @@ func Http(r chi.Router) {
})
r.Route("/cron", func(r chi.Router) {
r.Use(middleware.MustLogin)
cron := service.NewCronService()
r.Get("/", cron.List)
r.Post("/", cron.Create)
@@ -132,7 +154,6 @@ func Http(r chi.Router) {
})
r.Route("/safe", func(r chi.Router) {
r.Use(middleware.MustLogin)
safe := service.NewSafeService()
r.Get("/ssh", safe.GetSSH)
r.Post("/ssh", safe.UpdateSSH)
@@ -141,7 +162,6 @@ func Http(r chi.Router) {
})
r.Route("/firewall", func(r chi.Router) {
r.Use(middleware.MustLogin)
firewall := service.NewFirewallService()
r.Get("/status", firewall.GetStatus)
r.Post("/status", firewall.UpdateStatus)
@@ -157,7 +177,6 @@ func Http(r chi.Router) {
})
r.Route("/ssh", func(r chi.Router) {
r.Use(middleware.MustLogin)
ssh := service.NewSSHService()
r.Get("/", ssh.List)
r.Post("/", ssh.Create)
@@ -167,7 +186,6 @@ func Http(r chi.Router) {
})
r.Route("/container", func(r chi.Router) {
r.Use(middleware.MustLogin)
r.Route("/container", func(r chi.Router) {
container := service.NewContainerService()
r.Get("/", container.List)
@@ -179,11 +197,8 @@ func Http(r chi.Router) {
r.Post("/{id}/restart", container.Restart)
r.Post("/{id}/pause", container.Pause)
r.Post("/{id}/unpause", container.Unpause)
r.Get("/{id}/inspect", container.Inspect)
r.Post("/{id}/kill", container.Kill)
r.Post("/{id}/rename", container.Rename)
r.Get("/{id}/stats", container.Stats)
r.Get("/{id}/exist", container.Exist)
r.Get("/{id}/logs", container.Logs)
r.Post("/prune", container.Prune)
})
@@ -192,40 +207,32 @@ func Http(r chi.Router) {
r.Get("/", containerNetwork.List)
r.Post("/", containerNetwork.Create)
r.Delete("/{id}", containerNetwork.Remove)
r.Get("/{id}/exist", containerNetwork.Exist)
r.Get("/{id}/inspect", containerNetwork.Inspect)
r.Post("/{network}/connect", containerNetwork.Connect)
r.Post("/{network}/disconnect", containerNetwork.Disconnect)
r.Post("/prune", containerNetwork.Prune)
})
r.Route("/image", func(r chi.Router) {
containerImage := service.NewContainerImageService()
r.Get("/", containerImage.List)
r.Get("/{id}/exist", containerImage.Exist)
r.Post("/", containerImage.Pull)
r.Delete("/{id}", containerImage.Remove)
r.Get("/{id}", containerImage.Inspect)
r.Post("/prune", containerImage.Prune)
})
r.Route("/volume", func(r chi.Router) {
containerVolume := service.NewContainerVolumeService()
r.Get("/", containerVolume.List)
r.Post("/", containerVolume.Create)
r.Get("/{id}/exist", containerVolume.Exist)
r.Delete("/{id}", containerVolume.Remove)
r.Get("/{id}", containerVolume.Inspect)
r.Post("/prune", containerVolume.Prune)
})
})
r.Route("/file", func(r chi.Router) {
r.Use(middleware.MustLogin)
file := service.NewFileService()
r.Post("/create", file.Create)
r.Get("/content", file.Content)
r.Post("/save", file.Save)
r.Post("/delete", file.Delete)
r.Post("/upload", file.Upload)
r.Post("/exist", file.Exist)
r.Post("/move", file.Move)
r.Post("/copy", file.Copy)
r.Get("/download", file.Download)
@@ -239,7 +246,6 @@ func Http(r chi.Router) {
})
r.Route("/monitor", func(r chi.Router) {
r.Use(middleware.MustLogin)
monitor := service.NewMonitorService()
r.Get("/setting", monitor.GetSetting)
r.Post("/setting", monitor.UpdateSetting)
@@ -248,14 +254,12 @@ func Http(r chi.Router) {
})
r.Route("/setting", func(r chi.Router) {
r.Use(middleware.MustLogin)
setting := service.NewSettingService()
r.Get("/", setting.Get)
r.Post("/", setting.Update)
})
r.Route("/systemctl", func(r chi.Router) {
r.Use(middleware.MustLogin)
systemctl := service.NewSystemctlService()
r.Get("/status", systemctl.Status)
r.Get("/isEnabled", systemctl.IsEnabled)
@@ -266,6 +270,10 @@ func Http(r chi.Router) {
r.Post("/start", systemctl.Start)
r.Post("/stop", systemctl.Stop)
})
r.Route("/apps", func(r chi.Router) {
apps.Boot(r)
})
})
r.NotFound(func(writer http.ResponseWriter, request *http.Request) {
+41 -7
View File
@@ -115,6 +115,22 @@ func (s *CertService) List(w http.ResponseWriter, r *http.Request) {
})
}
func (s *CertService) Upload(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.CertUpload](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
cert, err := s.certRepo.Upload(req)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, cert)
}
func (s *CertService) Create(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.CertCreate](r)
if err != nil {
@@ -178,26 +194,44 @@ func (s *CertService) Delete(w http.ResponseWriter, r *http.Request) {
Success(w, nil)
}
func (s *CertService) Obtain(w http.ResponseWriter, r *http.Request) {
func (s *CertService) ObtainAuto(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
cert, err := s.certRepo.Get(req.ID)
if err != nil {
if _, err = s.certRepo.ObtainAuto(req.ID); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if cert.DNS != nil || cert.Website != nil {
_, err = s.certRepo.ObtainAuto(req.ID)
} else {
_, err = s.certRepo.ObtainManual(req.ID)
Success(w, nil)
}
func (s *CertService) ObtainManual(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if _, err = s.certRepo.ObtainManual(req.ID); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *CertService) ObtainSelfSigned(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.certRepo.ObtainSelfSigned(req.ID); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
+123 -50
View File
@@ -7,10 +7,12 @@ import (
"path/filepath"
"time"
"github.com/go-rat/utils/collect"
"github.com/go-rat/utils/hash"
"github.com/goccy/go-yaml"
"github.com/go-rat/utils/str"
"github.com/spf13/cast"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
"github.com/TheTNB/panel/internal/app"
@@ -18,35 +20,39 @@ import (
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/firewall"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/ntp"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
type CliService struct {
hr string
api *api.API
appRepo biz.AppRepo
userRepo biz.UserRepo
settingRepo biz.SettingRepo
backupRepo biz.BackupRepo
websiteRepo biz.WebsiteRepo
hash hash.Hasher
hr string
api *api.API
appRepo biz.AppRepo
userRepo biz.UserRepo
settingRepo biz.SettingRepo
backupRepo biz.BackupRepo
websiteRepo biz.WebsiteRepo
databaseServerRepo biz.DatabaseServerRepo
hash hash.Hasher
}
func NewCliService() *CliService {
return &CliService{
hr: `+----------------------------------------------------`,
api: api.NewAPI(app.Version),
appRepo: data.NewAppRepo(),
userRepo: data.NewUserRepo(),
settingRepo: data.NewSettingRepo(),
backupRepo: data.NewBackupRepo(),
websiteRepo: data.NewWebsiteRepo(),
hash: hash.NewArgon2id(),
hr: `+----------------------------------------------------`,
api: api.NewAPI(app.Version),
appRepo: data.NewAppRepo(),
userRepo: data.NewUserRepo(),
settingRepo: data.NewSettingRepo(),
backupRepo: data.NewBackupRepo(),
websiteRepo: data.NewWebsiteRepo(),
databaseServerRepo: data.NewDatabaseServerRepo(),
hash: hash.NewArgon2id(),
}
}
@@ -83,7 +89,7 @@ func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("获取最新版本失败:%v", err)
}
download := str.FirstElement(panel.Downloads)
download := collect.First(panel.Downloads)
if download == nil {
return fmt.Errorf("下载地址为空")
}
@@ -102,15 +108,15 @@ func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("获取管理员信息失败:%v", err)
}
password := str.RandomString(16)
password := str.Random(16)
hashed, err := s.hash.Make(password)
if err != nil {
return fmt.Errorf("密码生成失败:%v", err)
}
user.Username = str.RandomString(8)
user.Username = str.Random(8)
user.Password = hashed
if user.Email == "" {
user.Email = str.RandomString(8) + "@example.com"
user.Email = str.Random(8) + "@example.com"
}
if err = app.Orm.Save(user).Error; err != nil {
@@ -168,7 +174,7 @@ func (s *CliService) UserList(ctx context.Context, cmd *cli.Command) error {
}
for _, user := range users {
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format("2006-01-02 15:04:05"))
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format(time.DateTime))
}
return nil
@@ -236,18 +242,17 @@ func (s *CliService) UserPassword(ctx context.Context, cmd *cli.Command) error {
func (s *CliService) HTTPSOn(ctx context.Context, cmd *cli.Command) error {
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return err
}
config.HTTP.TLS = true
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return err
}
@@ -262,18 +267,17 @@ func (s *CliService) HTTPSOn(ctx context.Context, cmd *cli.Command) error {
func (s *CliService) HTTPSOff(ctx context.Context, cmd *cli.Command) error {
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return err
}
config.HTTP.TLS = false
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return err
}
@@ -286,20 +290,50 @@ func (s *CliService) HTTPSOff(ctx context.Context, cmd *cli.Command) error {
return s.Restart(ctx, cmd)
}
func (s *CliService) HTTPSGenerate(ctx context.Context, cmd *cli.Command) error {
var names []string
if lv4, err := tools.GetLocalIPv4(); err == nil {
names = append(names, lv4)
}
if lv6, err := tools.GetLocalIPv6(); err == nil {
names = append(names, lv6)
}
if rv4, err := tools.GetPublicIPv4(); err == nil {
names = append(names, rv4)
}
if rv6, err := tools.GetPublicIPv6(); err == nil {
names = append(names, rv6)
}
crt, key, err := cert.GenerateSelfSigned(names)
if err != nil {
return err
}
if err = io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), string(crt), 0644); err != nil {
return err
}
if err = io.Write(filepath.Join(app.Root, "panel/storage/cert.key"), string(key), 0644); err != nil {
return err
}
fmt.Println("已生成HTTPS证书")
return s.Restart(ctx, cmd)
}
func (s *CliService) EntranceOn(ctx context.Context, cmd *cli.Command) error {
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return err
}
config.HTTP.Entrance = "/" + str.RandomString(6)
config.HTTP.Entrance = "/" + str.Random(6)
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return err
}
@@ -315,18 +349,17 @@ func (s *CliService) EntranceOn(ctx context.Context, cmd *cli.Command) error {
func (s *CliService) EntranceOff(ctx context.Context, cmd *cli.Command) error {
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return err
}
config.HTTP.Entrance = "/"
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return err
}
@@ -340,24 +373,42 @@ func (s *CliService) EntranceOff(ctx context.Context, cmd *cli.Command) error {
}
func (s *CliService) Port(ctx context.Context, cmd *cli.Command) error {
port := cast.ToInt(cmd.Args().First())
port := cast.ToUint(cmd.Args().First())
if port < 1 || port > 65535 {
return fmt.Errorf("端口范围错误")
}
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return err
}
if port != config.HTTP.Port {
if os.TCPPortInUse(port) {
return errors.New("端口已被占用")
}
}
config.HTTP.Port = port
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return err
}
// 放行端口
fw := firewall.NewFirewall()
err = fw.Port(firewall.FireInfo{
Type: firewall.TypeNormal,
PortStart: uint(config.HTTP.Port),
PortEnd: uint(config.HTTP.Port),
Direction: firewall.DirectionIn,
Strategy: firewall.StrategyAccept,
}, firewall.OperationAdd)
if err != nil {
return err
}
@@ -430,6 +481,25 @@ func (s *CliService) WebsiteWrite(ctx context.Context, cmd *cli.Command) error {
return nil
}
func (s *CliService) DatabaseAddServer(ctx context.Context, cmd *cli.Command) error {
req := &request.DatabaseServerCreate{
Type: cmd.String("type"),
Name: cmd.String("name"),
Host: cmd.String("host"),
Port: uint(cmd.Uint("port")),
Username: cmd.String("username"),
Password: cmd.String("password"),
Remark: cmd.String("remark"),
}
if err := s.databaseServerRepo.Create(req); err != nil {
return err
}
fmt.Printf("数据库服务器 %s 添加成功\n", cmd.String("name"))
return nil
}
func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error {
fmt.Println(s.hr)
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
@@ -437,7 +507,7 @@ func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error
fmt.Println("|-备份类型:网站")
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
if err := s.backupRepo.Create(biz.BackupTypeWebsite, cmd.String("name"), cmd.String("path")); err != nil {
return fmt.Errorf("|-备份失败:%v", err)
return fmt.Errorf("备份失败:%v", err)
}
fmt.Println(s.hr)
fmt.Printf("☆ 备份成功 [%s]\n", time.Now().Format(time.DateTime))
@@ -453,7 +523,7 @@ func (s *CliService) BackupDatabase(ctx context.Context, cmd *cli.Command) error
fmt.Printf("|-数据库:%s\n", cmd.String("type"))
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
if err := s.backupRepo.Create(biz.BackupType(cmd.String("type")), cmd.String("name"), cmd.String("path")); err != nil {
return fmt.Errorf("|-备份失败:%v", err)
return fmt.Errorf("备份失败:%v", err)
}
fmt.Println(s.hr)
fmt.Printf("☆ 备份成功 [%s]\n", time.Now().Format(time.DateTime))
@@ -467,7 +537,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error {
fmt.Println(s.hr)
fmt.Println("|-备份类型:面板")
if err := s.backupRepo.Create(biz.BackupTypePanel, "", cmd.String("path")); err != nil {
return fmt.Errorf("|-备份失败:%v", err)
return fmt.Errorf("备份失败:%v", err)
}
fmt.Println(s.hr)
fmt.Printf("☆ 备份成功 [%s]\n", time.Now().Format(time.DateTime))
@@ -491,7 +561,7 @@ func (s *CliService) BackupClear(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("|-清理目标:%s\n", cmd.String("file"))
fmt.Printf("|-保留份数:%d\n", cmd.Int("save"))
if err = s.backupRepo.ClearExpired(path, cmd.String("file"), int(cmd.Int("save"))); err != nil {
return fmt.Errorf("|-清理失败:%v", err)
return fmt.Errorf("清理失败:%v", err)
}
fmt.Println(s.hr)
fmt.Printf("☆ 清理成功 [%s]\n", time.Now().Format(time.DateTime))
@@ -728,7 +798,7 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("初始化失败:%v", err)
}
value, err := hash.NewArgon2id().Make(str.RandomString(32))
value, err := hash.NewArgon2id().Make(str.Random(32))
if err != nil {
return fmt.Errorf("初始化失败:%v", err)
}
@@ -739,20 +809,23 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("初始化失败:%v", err)
}
if err = s.HTTPSGenerate(ctx, cmd); err != nil {
return fmt.Errorf("初始化失败:%v", err)
}
config := new(types.PanelConfig)
cm := yaml.CommentMap{}
raw, err := io.Read("/usr/local/etc/panel/config.yml")
if err != nil {
return err
}
if err = yaml.UnmarshalWithOptions([]byte(raw), config, yaml.CommentToMap(cm)); err != nil {
if err = yaml.Unmarshal([]byte(raw), config); err != nil {
return err
}
config.App.Key = str.RandomString(32)
config.HTTP.Entrance = "/" + str.RandomString(6)
config.App.Key = str.Random(32)
config.HTTP.Entrance = "/" + str.Random(6)
encoded, err := yaml.MarshalWithOptions(config, yaml.WithComment(cm))
encoded, err := yaml.Marshal(config)
if err != nil {
return err
}
+2 -72
View File
@@ -2,8 +2,6 @@ package service
import (
"net/http"
"strings"
"time"
"github.com/go-rat/chix"
@@ -29,35 +27,15 @@ func (s *ContainerService) List(w http.ResponseWriter, r *http.Request) {
}
paged, total := Paginate(r, containers)
items := make([]any, 0)
for _, item := range paged {
var name string
if len(item.Names) > 0 {
name = item.Names[0]
}
items = append(items, map[string]any{
"id": item.ID,
"name": strings.TrimLeft(name, "/"),
"image": item.Image,
"image_id": item.ImageID,
"command": item.Command,
"created": time.Unix(item.Created, 0).Format(time.DateTime),
"ports": item.Ports,
"labels": item.Labels,
"state": item.State,
"status": item.Status,
})
}
Success(w, chix.M{
"total": total,
"items": items,
"items": paged,
})
}
func (s *ContainerService) Search(w http.ResponseWriter, r *http.Request) {
name := strings.Fields(r.FormValue("name"))
containers, err := s.containerRepo.ListByNames(name)
containers, err := s.containerRepo.ListByName(r.FormValue("name"))
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
@@ -175,22 +153,6 @@ func (s *ContainerService) Unpause(w http.ResponseWriter, r *http.Request) {
Success(w, nil)
}
func (s *ContainerService) Inspect(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
container, err := s.containerRepo.Inspect(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, container)
}
func (s *ContainerService) Kill(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerID](r)
if err != nil {
@@ -221,38 +183,6 @@ func (s *ContainerService) Rename(w http.ResponseWriter, r *http.Request) {
Success(w, nil)
}
func (s *ContainerService) Stats(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
stats, err := s.containerRepo.Stats(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, stats)
}
func (s *ContainerService) Exist(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
exist, err := s.containerRepo.Exist(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, exist)
}
func (s *ContainerService) Logs(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerID](r)
if err != nil {
+1 -48
View File
@@ -2,14 +2,12 @@ package service
import (
"net/http"
"time"
"github.com/go-rat/chix"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/str"
)
type ContainerImageService struct {
@@ -31,41 +29,12 @@ func (s *ContainerImageService) List(w http.ResponseWriter, r *http.Request) {
paged, total := Paginate(r, images)
items := make([]any, 0)
for _, item := range paged {
items = append(items, map[string]any{
"id": item.ID,
"created": time.Unix(item.Created, 0).Format(time.DateTime),
"containers": item.Containers,
"size": str.FormatBytes(float64(item.Size)),
"labels": item.Labels,
"repo_tags": item.RepoTags,
"repo_digests": item.RepoDigests,
})
}
Success(w, chix.M{
"total": total,
"items": items,
"items": paged,
})
}
func (s *ContainerImageService) Exist(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerImageID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
exist, err := s.containerImageRepo.Exist(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, exist)
}
func (s *ContainerImageService) Pull(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerImagePull](r)
if err != nil {
@@ -96,22 +65,6 @@ func (s *ContainerImageService) Remove(w http.ResponseWriter, r *http.Request) {
Success(w, nil)
}
func (s *ContainerImageService) Inspect(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerImageID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
inspect, err := s.containerImageRepo.Inspect(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, inspect)
}
func (s *ContainerImageService) Prune(w http.ResponseWriter, r *http.Request) {
if err := s.containerImageRepo.Prune(); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
+1 -95
View File
@@ -2,7 +2,6 @@ package service
import (
"net/http"
"time"
"github.com/go-rat/chix"
@@ -30,40 +29,9 @@ func (s *ContainerNetworkService) List(w http.ResponseWriter, r *http.Request) {
paged, total := Paginate(r, networks)
items := make([]any, 0)
for _, item := range paged {
var ipamConfig []any
for _, v := range item.IPAM.Config {
ipamConfig = append(ipamConfig, map[string]any{
"subnet": v.Subnet,
"gateway": v.Gateway,
"ip_range": v.IPRange,
"aux_address": v.AuxAddress,
})
}
items = append(items, map[string]any{
"id": item.ID,
"name": item.Name,
"driver": item.Driver,
"ipv6": item.EnableIPv6,
"scope": item.Scope,
"internal": item.Internal,
"attachable": item.Attachable,
"ingress": item.Ingress,
"labels": item.Labels,
"options": item.Options,
"ipam": map[string]any{
"config": ipamConfig,
"driver": item.IPAM.Driver,
"options": item.IPAM.Options,
},
"created": item.Created.Format(time.DateTime),
})
}
Success(w, chix.M{
"total": total,
"items": items,
"items": paged,
})
}
@@ -98,68 +66,6 @@ func (s *ContainerNetworkService) Remove(w http.ResponseWriter, r *http.Request)
Success(w, nil)
}
func (s *ContainerNetworkService) Exist(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerNetworkID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
exist, err := s.containerNetworkRepo.Exist(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, exist)
}
func (s *ContainerNetworkService) Inspect(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerNetworkID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
network, err := s.containerNetworkRepo.Inspect(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, network)
}
func (s *ContainerNetworkService) Connect(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerNetworkConnect](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerNetworkRepo.Connect(req.Network, req.Container); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *ContainerNetworkService) Disconnect(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerNetworkConnect](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerNetworkRepo.Disconnect(req.Network, req.Container); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *ContainerNetworkService) Prune(w http.ResponseWriter, r *http.Request) {
if err := s.containerNetworkRepo.Prune(); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
+3 -58
View File
@@ -8,7 +8,6 @@ import (
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/str"
)
type ContainerVolumeService struct {
@@ -30,31 +29,9 @@ func (s *ContainerVolumeService) List(w http.ResponseWriter, r *http.Request) {
paged, total := Paginate(r, volumes)
items := make([]any, 0)
for _, item := range paged {
var usage any
if item.UsageData != nil {
usage = map[string]any{
"ref_count": item.UsageData.RefCount,
"size": str.FormatBytes(float64(item.UsageData.Size)),
}
}
items = append(items, map[string]any{
"id": item.Name,
"created": item.CreatedAt,
"driver": item.Driver,
"mount": item.Mountpoint,
"labels": item.Labels,
"options": item.Options,
"scope": item.Scope,
"status": item.Status,
"usage": usage,
})
}
Success(w, chix.M{
"total": total,
"items": items,
"items": paged,
})
}
@@ -65,32 +42,16 @@ func (s *ContainerVolumeService) Create(w http.ResponseWriter, r *http.Request)
return
}
volume, err := s.containerVolumeRepo.Create(req)
name, err := s.containerVolumeRepo.Create(req)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, volume.Name)
Success(w, name)
}
func (s *ContainerVolumeService) Exist(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerVolumeID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
exist, err := s.containerVolumeRepo.Exist(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, exist)
}
func (s *ContainerVolumeService) Remove(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerVolumeID](r)
if err != nil {
@@ -106,22 +67,6 @@ func (s *ContainerVolumeService) Remove(w http.ResponseWriter, r *http.Request)
Success(w, nil)
}
func (s *ContainerVolumeService) Inspect(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerVolumeID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
volume, err := s.containerVolumeRepo.Inspect(req.ID)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, volume)
}
func (s *ContainerVolumeService) Prune(w http.ResponseWriter, r *http.Request) {
if err := s.containerVolumeRepo.Prune(); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
+5 -5
View File
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/go-rat/chix"
"github.com/go-rat/utils/collect"
"github.com/hashicorp/go-version"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
@@ -20,7 +21,6 @@ import (
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/types"
)
@@ -52,8 +52,8 @@ func (s *DashboardService) Panel(w http.ResponseWriter, r *http.Request) {
}
Success(w, chix.M{
"name": name,
"language": app.Conf.MustString("app.locale"),
"name": name,
"locale": app.Conf.MustString("app.locale"),
})
}
@@ -140,7 +140,7 @@ func (s *DashboardService) CountInfo(w http.ResponseWriter, r *http.Request) {
}
}
if postgresqlInstalled {
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), 5432)
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
if err == nil {
defer postgres.Close()
databases, err := postgres.Databases()
@@ -295,7 +295,7 @@ func (s *DashboardService) Update(w http.ResponseWriter, r *http.Request) {
return
}
download := str.FirstElement(panel.Downloads)
download := collect.First(panel.Downloads)
if download == nil {
Error(w, http.StatusInternalServerError, "获取下载链接失败")
return
+70
View File
@@ -0,0 +1,70 @@
package service
import (
"net/http"
"github.com/go-rat/chix"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
)
type Database struct {
databaseRepo biz.DatabaseRepo
}
func NewDatabaseService() *Database {
return &Database{
databaseRepo: data.NewDatabaseRepo(),
}
}
func (s *Database) List(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.Paginate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
databases, total, err := s.databaseRepo.List(req.Page, req.Limit)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, chix.M{
"total": total,
"items": databases,
})
}
func (s *Database) Create(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.DatabaseCreate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseRepo.Create(req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *Database) Delete(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.DatabaseDelete](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseRepo.Delete(req.ServerID, req.Name); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
+100
View File
@@ -0,0 +1,100 @@
package service
import (
"net/http"
"github.com/go-rat/chix"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
)
type DatabaseServer struct {
databaseServerRepo biz.DatabaseServerRepo
}
func NewDatabaseServerService() *DatabaseServer {
return &DatabaseServer{
databaseServerRepo: data.NewDatabaseServerRepo(),
}
}
func (s *DatabaseServer) List(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.Paginate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
servers, total, err := s.databaseServerRepo.List(req.Page, req.Limit)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, chix.M{
"total": total,
"items": servers,
})
}
func (s *DatabaseServer) Create(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.DatabaseServerCreate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseServerRepo.Create(req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *DatabaseServer) Update(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.DatabaseServerUpdate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseServerRepo.Update(req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *DatabaseServer) Delete(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseServerRepo.Delete(req.ID); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *DatabaseServer) Sync(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseServerRepo.Sync(req.ID); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
+85
View File
@@ -0,0 +1,85 @@
package service
import (
"net/http"
"github.com/go-rat/chix"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
)
type DatabaseUser struct {
databaseUserRepo biz.DatabaseUserRepo
}
func NewDatabaseUserService() *DatabaseUser {
return &DatabaseUser{
databaseUserRepo: data.NewDatabaseUserRepo(),
}
}
func (s *DatabaseUser) List(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.Paginate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
users, total, err := s.databaseUserRepo.List(req.Page, req.Limit)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, chix.M{
"total": total,
"items": users,
})
}
func (s *DatabaseUser) Create(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.DatabaseUserCreate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseUserRepo.Create(req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *DatabaseUser) Update(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.DatabaseUserUpdate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseUserRepo.Update(req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *DatabaseUser) Delete(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ID](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.databaseUserRepo.Delete(req.ID); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
+83 -44
View File
@@ -17,13 +17,14 @@ import (
"github.com/go-rat/chix"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/tools"
)
type FileService struct {
@@ -72,11 +73,11 @@ func (s *FileService) Content(w http.ResponseWriter, r *http.Request) {
return
}
if fileInfo.IsDir() {
Error(w, http.StatusInternalServerError, "目标路径不是文件")
Error(w, http.StatusInternalServerError, "target is a directory")
return
}
if fileInfo.Size() > 10*1024*1024 {
Error(w, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
Error(w, http.StatusInternalServerError, "file is too large, please download it")
return
}
@@ -107,7 +108,6 @@ func (s *FileService) Save(w http.ResponseWriter, r *http.Request) {
return
}
s.setPermission(req.Path, 0755, "www", "www")
Success(w, nil)
}
@@ -118,6 +118,12 @@ func (s *FileService) Delete(w http.ResponseWriter, r *http.Request) {
return
}
banned := []string{"/", app.Root, filepath.Join(app.Root, "server"), filepath.Join(app.Root, "panel")}
if slices.Contains(banned, req.Path) {
Error(w, http.StatusForbidden, "please don't do this")
return
}
if err = io.Remove(req.Path); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
@@ -135,17 +141,17 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
path := r.FormValue("path")
_, handler, err := r.FormFile("file")
if err != nil {
Error(w, http.StatusInternalServerError, "上传文件失败:%v", err)
Error(w, http.StatusInternalServerError, "upload file error: %v", err)
return
}
if io.Exists(path) {
Error(w, http.StatusForbidden, "目标路径 %s 已存在", path)
Error(w, http.StatusForbidden, "target path %s already exists", path)
return
}
if !io.Exists(filepath.Dir(path)) {
if err = io.Mkdir(filepath.Dir(path), 0755); err != nil {
Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err)
Error(w, http.StatusInternalServerError, "create directory error: %v", err)
return
}
}
@@ -153,12 +159,12 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
src, _ := handler.Open()
out, err := stdos.OpenFile(path, stdos.O_CREATE|stdos.O_RDWR|stdos.O_TRUNC, 0644)
if err != nil {
Error(w, http.StatusInternalServerError, "打开文件失败:%v", err)
Error(w, http.StatusInternalServerError, "open file error: %v", err)
return
}
if _, err = stdio.Copy(out, src); err != nil {
Error(w, http.StatusInternalServerError, "写入文件失败:%v", err)
Error(w, http.StatusInternalServerError, "write file error: %v", err)
return
}
@@ -167,41 +173,77 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
Success(w, nil)
}
func (s *FileService) Exist(w http.ResponseWriter, r *http.Request) {
binder := chix.NewBind(r)
defer binder.Release()
var paths []string
if err := binder.Body(&paths); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
var results []bool
for item := range slices.Values(paths) {
results = append(results, io.Exists(item))
}
Success(w, results)
}
func (s *FileService) Move(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.FileMove](r)
if err != nil {
binder := chix.NewBind(r)
defer binder.Release()
var req []request.FileControl
if err := binder.Body(&req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if io.Exists(req.Target) && !req.Force {
Error(w, http.StatusForbidden, "目标路径 %s 已存在", req.Target)
return
}
for item := range slices.Values(req) {
if io.Exists(item.Target) && !item.Force {
continue
}
if err = io.Mv(req.Source, req.Target); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
return
}
if err := io.Mv(item.Source, item.Target); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
Success(w, nil)
}
func (s *FileService) Copy(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.FileCopy](r)
if err != nil {
binder := chix.NewBind(r)
defer binder.Release()
var req []request.FileControl
if err := binder.Body(&req); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if io.Exists(req.Target) && !req.Force {
Error(w, http.StatusForbidden, "目标路径 %s 已存在", req.Target)
return
}
for item := range slices.Values(req) {
if io.Exists(item.Target) && !item.Force {
continue
}
if err = io.Cp(req.Source, req.Target); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
return
}
if err := io.Cp(item.Source, item.Target); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
Success(w, nil)
@@ -220,7 +262,7 @@ func (s *FileService) Download(w http.ResponseWriter, r *http.Request) {
return
}
if info.IsDir() {
Error(w, http.StatusInternalServerError, "不能下载目录")
Error(w, http.StatusInternalServerError, "can't download a directory")
return
}
@@ -266,7 +308,7 @@ func (s *FileService) Info(w http.ResponseWriter, r *http.Request) {
Success(w, chix.M{
"name": info.Name(),
"size": str.FormatBytes(float64(info.Size())),
"size": tools.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"dir": info.IsDir(),
@@ -307,13 +349,7 @@ func (s *FileService) Compress(w http.ResponseWriter, r *http.Request) {
return
}
format, err := io.FormatArchiveByPath(req.File)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Compress(req.Paths, req.File, format); err != nil {
if err = io.Compress(req.Dir, req.Paths, req.File); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -329,18 +365,21 @@ func (s *FileService) UnCompress(w http.ResponseWriter, r *http.Request) {
return
}
format, err := io.FormatArchiveByPath(req.File)
if err = io.UnCompress(req.File, req.Path); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
list, err := io.ListCompress(req.File)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.UnCompress(req.File, req.Path, format); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
for item := range slices.Values(list) {
s.setPermission(filepath.Join(req.Path, item), 0755, "www", "www")
}
s.setPermission(req.Path, 0755, "www", "www")
Success(w, nil)
}
@@ -409,7 +448,7 @@ func (s *FileService) List(w http.ResponseWriter, r *http.Request) {
// formatDir 格式化目录信息
func (s *FileService) formatDir(base string, entries []stdos.DirEntry) []any {
var paths []any
for _, file := range entries {
for file := range slices.Values(entries) {
info, err := file.Info()
if err != nil {
continue
@@ -419,7 +458,7 @@ func (s *FileService) formatDir(base string, entries []stdos.DirEntry) []any {
paths = append(paths, map[string]any{
"name": info.Name(),
"full": filepath.Join(base, info.Name()),
"size": str.FormatBytes(float64(info.Size())),
"size": tools.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"owner": os.GetUser(stat.Uid),
@@ -445,7 +484,7 @@ func (s *FileService) formatInfo(infos map[string]stdos.FileInfo) []map[string]a
paths = append(paths, map[string]any{
"name": info.Name(),
"full": path,
"size": str.FormatBytes(float64(info.Size())),
"size": tools.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"owner": os.GetUser(stat.Uid),

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