Compare commits

...

154 Commits

Author SHA1 Message Date
renovate[bot] f4e31c4902 chore(deps): Lock file maintenance (#638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 22:27:14 +08:00
renovate[bot] b2d382c2af chore(deps): Update module github.com/tnb-labs/acmez/v3 to v3.0.0-20250328123917-f204dcee9d09 (#639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 22:25:48 +08:00
耗子 5d0ad333c9 Merge branch 'main' of https://github.com/tnb-labs/panel 2025-03-28 22:24:13 +08:00
耗子 ccf05b8c42 feat: 新增minio 2025-03-28 22:23:05 +08:00
renovate[bot] 684c1b7140 chore(deps): Update module golang.org/x/net to v0.38.0 (#637)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 01:52:42 +00:00
renovate[bot] 15e12d083d chore(deps): Update non-major dependencies (#636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 15:10:51 +08:00
renovate[bot] f6937bb4cb chore(deps): Update dependency cronstrue to v2.57.0 (#635)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 04:01:53 +08:00
耗子 8683a85640 feat: 备份支持上传 2025-03-27 03:59:47 +08:00
耗子 7ee6dd4d0e Merge remote-tracking branch 'origin/main' 2025-03-27 03:20:49 +08:00
耗子 1c7c6074a1 feat: 优化备份与恢复体验 2025-03-27 03:20:40 +08:00
devhaozi af865b62a5 chore: update mocks 2025-03-26 18:56:30 +00:00
耗子 69b430d7cb feat(#108): 容器编排 2025-03-27 02:55:45 +08:00
devhaozi 0956385284 chore: update mocks 2025-03-26 18:23:27 +00:00
耗子 46cf1fa7da feat: 优化编排请求体 2025-03-27 02:22:43 +08:00
renovate[bot] ac9b42ca4d chore(deps): Update pnpm to v10.7.0 (#634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 23:56:06 +08:00
renovate[bot] 264335429a chore(deps): Update dependency unplugin-auto-import to v19.1.2 (#633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 23:52:51 +08:00
renovate[bot] 323e2bf95a chore(deps): Update non-major dependencies (#632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 23:12:45 +00:00
renovate[bot] 6b413d2962 chore(deps): Update module github.com/mholt/acmez/v3 to v3.1.1 (#631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 06:43:44 +00:00
renovate[bot] 83e0e33207 chore(deps): Update non-major dependencies (#630)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 22:08:51 +00:00
renovate[bot] 827df91241 chore(deps): Lock file maintenance (#629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 06:32:21 +00:00
renovate[bot] f78f414c77 chore(deps): Update dependency md-editor-v3 to v5.4.4 (#628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 14:55:33 +08:00
renovate[bot] dfb73d1730 chore(deps): Update non-major dependencies (#627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 01:22:59 +00:00
renovate[bot] 92c1d136c2 chore(deps): Update non-major dependencies (#626)
* chore(deps): Update non-major dependencies

* chore: update mocks

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 16:05:14 +00:00
耗子 45428f3e0b feat: 支持更新编排文件 2025-03-21 03:48:51 +08:00
耗子 139679ed0f feat: 支持更新编排文件 2025-03-21 03:21:44 +08:00
耗子 eed834bd5f feat: 支持更新编排文件 2025-03-21 03:20:08 +08:00
耗子 422c92cca5 feat: 创建compose支持env 2025-03-21 03:18:35 +08:00
耗子 28d699c886 fix: compose中的错误 2025-03-21 02:14:48 +08:00
耗子 f4533c9662 feat: 网站日志备份后重载Nginx 2025-03-20 19:23:41 +08:00
devhaozi ad71a8075b chore: update mocks 2025-03-19 18:52:30 +00:00
耗子 7d39015d97 feat: compose接口 2025-03-20 02:49:18 +08:00
renovate[bot] c3498aa5c5 chore(deps): Update pnpm to v10.6.5 (#624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 16:39:33 +00:00
renovate[bot] 2c3bb5bf91 chore(deps): Update non-major dependencies (#623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 10:19:09 +00:00
renovate[bot] 3c2b6e6488 chore(deps): Update dependency sass to v1.86.0 (#622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 23:10:52 +00:00
耗子 dff0a61182 feat(#528): 证书支持部署脚本 2025-03-18 03:18:39 +08:00
耗子 d081f1e312 feat(#570): 为部分应用添加运行日志 2025-03-18 02:55:28 +08:00
耗子 46809c8d5b fix: build 2025-03-18 02:45:42 +08:00
耗子 f1246ee779 fix(#612): 优化复制功能 2025-03-18 02:41:06 +08:00
renovate[bot] 03168a5652 chore(deps): Update dependency @vitejs/plugin-vue to v5.2.3 (#621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 18:31:06 +00:00
renovate[bot] 0e43d25430 chore(deps): Update dependency md-editor-v3 to v5.4.2 (#620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 11:14:40 +00:00
renovate[bot] 46a9446007 chore(deps): Lock file maintenance (#619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 03:43:57 +00:00
renovate[bot] 3761223230 chore(deps): Update dependency @vitejs/plugin-vue to v5.2.2 (#618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 03:43:36 +00:00
renovate[bot] 836b34e59d chore(deps): Update module github.com/expr-lang/expr to v1.17.0 (#617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-16 10:20:11 +00:00
renovate[bot] 3dc1106012 chore(deps): Update dependency @guolao/vue-monaco-editor to v1.5.5 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 10:56:09 +00:00
renovate[bot] 3eb8bf793f chore(deps): Update dependency @iconify/json to v2.2.317 (#615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 19:37:18 +00:00
renovate[bot] cd823c4506 chore(deps): Update module github.com/go-rat/utils to v1.1.4 (#614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 12:05:54 +00:00
renovate[bot] 36d45ee66f chore(deps): Update non-major dependencies (#611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 06:33:53 +00:00
renovate[bot] 068d36de3d chore(deps): Update module github.com/ncruces/go-sqlite3 to v0.24.1 (#609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 01:44:22 +00:00
renovate[bot] e3607b9bc7 chore(deps): Update non-major dependencies (#608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 22:43:43 +00:00
耗子 76a2ff8e43 Merge remote-tracking branch 'origin/main' 2025-03-12 23:08:57 +08:00
耗子 b3e3da6833 feat: 优化关闭数据库连接 2025-03-12 23:08:46 +08:00
dependabot[bot] fa6c574743 chore(deps): bump @babel/helpers (#607)
Bumps the npm_and_yarn group with 1 update in the /web directory: [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers).


Updates `@babel/helpers` from 7.26.9 to 7.26.10
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 22:01:00 +08:00
耗子 e7625a77a8 feat: 优化SQLite配置防止数据库损坏 2025-03-12 21:58:06 +08:00
耗子 e1b3c80771 Merge branch 'main' of https://github.com/tnb-labs/panel 2025-03-12 21:57:53 +08:00
耗子 3e9228cb65 feat: 优化SQLite配置防止数据库损坏 2025-03-12 21:57:06 +08:00
renovate[bot] e8b5bb266f chore(deps): Update non-major dependencies (#606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 11:04:01 +00:00
耗子 a6d27f261b fix: 容器主机端口显示错误 2025-03-12 03:05:07 +08:00
renovate[bot] 1a85571fca chore(deps): Update dependency @rushstack/eslint-patch to v1.11.0 (#605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-11 02:38:42 +00:00
renovate[bot] 9b4f308367 chore(deps): Update module github.com/go-rat/utils to v1.1.0 (#604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 22:10:38 +00:00
renovate[bot] aabf49d041 chore(deps): Update module github.com/tnb-labs/acmez/v3 to v3.0.0-20250310074100-3f4cd473d1db (#603)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 09:56:11 +00:00
renovate[bot] 0d22b53b36 chore(deps): Update dependency @vueuse/core to v13 (#601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 15:46:39 +08:00
renovate[bot] 8d9c25c195 chore(deps): Update dependency @iconify/json to v2.2.315 (#602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 07:42:28 +00:00
耗子 bfd4fe41d3 ci: fix mockery 2025-03-10 15:38:17 +08:00
renovate[bot] 8400705053 chore(deps): Lock file maintenance (#600)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 03:11:47 +00:00
renovate[bot] b3e72acad8 chore(deps): Update dependency @types/node to v22.13.10 (#599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 10:42:33 +00:00
renovate[bot] 33884c4c67 chore(deps): Update dependency eslint to v9.22.0 (#598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 22:35:54 +00:00
renovate[bot] 45a939571d chore(deps): Update dependency alova to v3.2.10 (#597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 14:24:15 +00:00
renovate[bot] ac4f2a8531 chore(deps): Update non-major dependencies (#596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 07:07:55 +00:00
renovate[bot] 7c09b017c9 chore(deps): Update non-major dependencies (#595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 11:05:41 +00:00
renovate[bot] 02c207d0b8 chore(deps): Update module golang.org/x/net to v0.37.0 (#594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 22:08:03 +00:00
renovate[bot] 6d23f59d6a chore(deps): Update module golang.org/x/crypto to v0.36.0 (#593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 19:17:35 +00:00
耗子 4c55552dec fix(#592): 某些情况下计划任务创建失败 2025-03-06 01:21:48 +08:00
renovate[bot] 5ba24d11f6 chore(deps): Update dependency @vueuse/core to v12.8.2 (#591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 11:56:15 +00:00
renovate[bot] bcf557c47e chore(deps): Update dependency eslint-plugin-vue to v10 (#590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 15:57:39 +08:00
renovate[bot] cac55a4f79 chore(deps): Update dependency @vueuse/core to v12.8.0 (#589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 06:58:44 +00:00
renovate[bot] 0d26edf6f0 chore(deps): Update dependency eslint-plugin-vue to v9.33.0 (#588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 03:09:56 +00:00
renovate[bot] 7a39ef706c chore(deps): Update module golang.org/x/net to v0.36.0 (#587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 23:17:00 +00:00
renovate[bot] f5c37c0402 chore(deps): Update dependency @iconify/json to v2.2.313 (#586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 12:03:56 +00:00
renovate[bot] 16d571e4ea chore(deps): Update module github.com/go-rat/chix to v1.1.5 (#585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 22:44:10 +00:00
renovate[bot] b4b75aa9c5 chore(deps): Update dependency @types/node to v22.13.9 (#584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 19:33:49 +00:00
renovate[bot] 6ed7784a89 chore(deps): Update dependency cronstrue to v2.56.0 (#583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 15:34:59 +00:00
renovate[bot] d2c7c1109d chore(deps): Lock file maintenance (#582)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 02:58:02 +00:00
renovate[bot] 079a7f7c07 chore(deps): Update dependency prettier to v3.5.3 (#581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 02:57:19 +00:00
renovate[bot] 923c6a478c chore(deps): Update dependency vue-tsc to v2.2.8 (#580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-02 12:45:26 +00:00
renovate[bot] 9e0f65cf52 chore(deps): Lock file maintenance (#579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-02 12:45:07 +00:00
renovate[bot] eab8184b6b chore(deps): Update non-major dependencies (#578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-02 09:42:37 +00:00
renovate[bot] 3972a17bd1 chore(deps): Update non-major dependencies (#577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-01 10:27:29 +00:00
renovate[bot] 34b7b8a10a chore(deps): Update dependency @types/node to v22.13.7 (#576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-01 01:42:19 +00:00
renovate[bot] a4da49d8ae chore(deps): Update dependency @types/node to v22.13.6 (#575)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 21:52:59 +00:00
renovate[bot] f8a989fbfc chore(deps): Update non-major dependencies (#574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 19:37:18 +00:00
renovate[bot] 4497b8fa82 chore(deps): Update dependency vite-plugin-static-copy to v2.3.0 (#573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 14:52:48 +00:00
耗子 0bb7ccd121 Merge remote-tracking branch 'origin/main' 2025-02-28 16:06:20 +08:00
耗子 b90227017c fix: ssh连接成功后即立即调整窗口大小 2025-02-28 16:04:20 +08:00
renovate[bot] 75d941060f chore(deps): Update dependency alova to v3.2.9 (#572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-28 06:17:59 +00:00
renovate[bot] b37be8aae9 chore(deps): Update dependency @iconify/json to v2.2.311 (#571)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 07:17:35 +00:00
renovate[bot] f5a471768b chore(deps): Update non-major dependencies (#569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 03:43:49 +00:00
renovate[bot] f6ad6b23cb chore(deps): Update github.com/tufanbarisyildirim/gonginx digest to c03497d (#568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 22:47:05 +00:00
renovate[bot] f5b9d3e912 chore(deps): Update dependency vite to v6.2.0 (#567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 05:51:07 +00:00
renovate[bot] 9619c3f55b chore(deps): Update dependency sass to v1.85.1 (#566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 02:38:56 +00:00
renovate[bot] 8c08ebb041 chore(deps): Update module golang.org/x/crypto to v0.35.0 (#565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 18:52:49 +00:00
renovate[bot] 0567319671 chore(deps): Update module github.com/ncruces/go-sqlite3 to v0.23.3 (#564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 15:34:06 +00:00
renovate[bot] 760c3be9c7 chore(deps): Update dependency @iconify/json to v2.2.310 (#563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 09:25:24 +00:00
耗子 1084968e99 chore: update ubuntu 2025-02-24 17:21:04 +08:00
renovate[bot] 188c2ffce0 chore(deps): Lock file maintenance (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 03:10:59 +00:00
renovate[bot] f6296e7704 chore(deps): Update dependency vue-tsc to v2.2.4 (#561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-23 14:38:04 +00:00
renovate[bot] bd149f6524 chore(deps): Update dependency prettier to v3.5.2 (#560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 06:51:11 +00:00
renovate[bot] 989beb3642 chore(deps): Update module golang.org/x/crypto to v0.34.0 (#559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 01:33:27 +00:00
renovate[bot] 5d4d94a5d8 chore(deps): Update non-major dependencies (#558)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 22:06:00 +00:00
renovate[bot] 41eca93f12 chore(deps): Update dependency md-editor-v3 to v5.3.2 (#557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 11:18:40 +00:00
renovate[bot] 946def76c5 chore(deps): Lock file maintenance (#556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 16:09:27 +00:00
renovate[bot] 24768a4078 chore(deps): Update non-major dependencies (#555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 16:08:52 +00:00
renovate[bot] 7751117dc6 chore(deps): Update dependency md-editor-v3 to v5.3.0 (#554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 09:56:51 +00:00
renovate[bot] f9f286335d chore(deps): Update unocss monorepo to v66 (#550)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 15:47:23 +08:00
renovate[bot] 1d50da79bb chore(deps): Update dependency vite to v6.1.1 (#553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 12:51:55 +00:00
renovate[bot] 7e78b6eaa3 chore(deps): Update dependency unplugin-vue-components to v28.4.0 (#552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 06:18:58 +00:00
renovate[bot] a098c988a7 chore(deps): Update dependency tsx to v4.19.3 (#551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 01:36:51 +00:00
renovate[bot] 0b04f32aed chore(deps): Update non-major dependencies (#549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 12:18:21 +00:00
renovate[bot] e14c1db060 chore(deps): Update non-major dependencies (#548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 06:41:47 +00:00
renovate[bot] e109e16610 chore(deps): Update dependency md-editor-v3 to v5.2.3 (#547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 05:47:45 +00:00
renovate[bot] cd3f2b5951 chore(deps): Lock file maintenance (#546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 02:39:56 +00:00
耗子 fe28e84bf7 Merge remote-tracking branch 'origin/main' 2025-02-16 16:08:51 +08:00
耗子 fe4e3850c9 feat: test new sqlite driver 2025-02-16 16:07:22 +08:00
renovate[bot] 2a070e14c0 chore(deps): Update dependency vue-tsc to v2.2.2 (#545)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-15 18:32:15 +00:00
renovate[bot] 3984f85d57 chore(deps): Update non-major dependencies (#544)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-15 10:36:05 +00:00
耗子 dcd4eb1e49 Merge remote-tracking branch 'origin/main' 2025-02-15 02:48:35 +08:00
耗子 bc62fbadbb fix: redis info 2025-02-15 02:47:50 +08:00
renovate[bot] d17ef0bfda chore(deps): Update non-major dependencies (#543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 18:07:07 +00:00
renovate[bot] 622d08f6f4 chore(deps): Update dependency @vueuse/core to v12.6.1 (#542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 06:44:38 +00:00
renovate[bot] 2a2fbbf287 chore(deps): Update dependency sass to v1.85.0 (#541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 03:27:12 +00:00
renovate[bot] 77fa6ec58f chore(deps): Update dependency @types/node to v22.13.4 (#540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 23:44:36 +00:00
renovate[bot] fae4894cea chore(deps): Update dependency prettier to v3.5.1 (#539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 16:12:53 +00:00
耗子 b4195459c5 Merge remote-tracking branch 'origin/main' 2025-02-13 17:22:38 +08:00
耗子 5a40cd3910 fix: 优化备份与清理命令输出 2025-02-13 17:21:37 +08:00
renovate[bot] c6971004f5 chore(deps): Update non-major dependencies (#538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 06:27:58 +00:00
renovate[bot] e6ff46ccac chore(deps): Update dependency pinia to v3.0.1 (#537)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 18:36:52 +00:00
耗子 a91d0b79a7 fix: 优化命令参数格式化 2025-02-13 00:19:19 +08:00
耗子 f0a9aceb13 Merge remote-tracking branch 'origin/main' 2025-02-13 00:00:03 +08:00
耗子 c251f73895 feat: 提升go1.24 2025-02-12 23:59:56 +08:00
renovate[bot] 79b8e58412 chore(deps): Update dependency vite-plugin-vue-devtools to v7.7.2 (#536)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 15:56:51 +00:00
耗子 73be25dcf7 feat: 提升go1.24 2025-02-12 23:52:13 +08:00
耗子 fff4d3eae2 fix: MySQL不支持重载 2025-02-12 03:07:25 +08:00
耗子 e614e97312 fix: 修复编辑器无法加载文件内容 2025-02-12 03:02:17 +08:00
耗子 7ac448d2c4 feat: 远程文件下载后自动修改为www用户 2025-02-12 02:51:11 +08:00
耗子 08f5c88cee feat: 优化首页弹窗位置 2025-02-12 02:38:17 +08:00
renovate[bot] 779d734dad chore(deps): Update dependency eslint to v9.20.1 (#535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 17:42:25 +00:00
renovate[bot] c3ca61a665 chore(deps): Update dependency pinia to v3 (#534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 01:37:59 +08:00
耗子 832367eb2a Merge remote-tracking branch 'origin/main' 2025-02-12 01:36:38 +08:00
耗子 929a799f68 feat: 优化session cookie 2025-02-12 01:36:31 +08:00
renovate[bot] f1cdb19a59 chore(deps): Update non-major dependencies (#533)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 12:18:25 +00:00
renovate[bot] 6f588efea7 chore(deps): Lock file maintenance (#532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 00:30:22 +00:00
renovate[bot] 333e8ae39f chore(deps): Update dependency marked to v15.0.7 (#531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 00:29:49 +00:00
renovate[bot] 12dc49968a chore(deps): Update dependency @vue/eslint-config-typescript to v14.4.0 (#530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-09 23:53:25 +00:00
耗子 960fc78518 feat: 修正批量删除后的列表刷新 2025-02-10 00:15:20 +08:00
耗子 447c393b46 chore: 优化文案 2025-02-09 23:47:30 +08:00
93 changed files with 3378 additions and 2274 deletions
+2 -3
View File
@@ -6,14 +6,13 @@ on:
pull_request:
jobs:
frontend:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
run_install: true
package_json_file: web/package.json
- name: Setup Node.js
@@ -35,7 +34,7 @@ jobs:
path: web/dist/
backend:
needs: frontend
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
strategy:
matrix:
goarch: [ amd64, arm64 ]
+1 -2
View File
@@ -7,7 +7,7 @@ permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -21,7 +21,6 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
run_install: true
package_json_file: web/package.json
- name: Setup Node.js
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: ☢️ Bug
if: github.event.label.name == '☢️ Bug'
+3 -4
View File
@@ -9,7 +9,7 @@ permissions:
jobs:
golangci:
name: golanci-lint
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -25,7 +25,7 @@ jobs:
version: latest
args: --timeout=30m ./...
govulncheck:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -39,14 +39,13 @@ jobs:
- name: Run Govulncheck
run: govulncheck ./...
frontend:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
run_install: true
package_json_file: web/package.json
- name: Setup Node.js
+5
View File
@@ -10,6 +10,11 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
cache: true
go-version: 'stable'
- name: Install Mockery
run: |
go install github.com/vektra/mockery/v2@latest
+1 -1
View File
@@ -6,7 +6,7 @@ on:
pull_request:
jobs:
unit:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
+1 -1
View File
@@ -15,7 +15,7 @@
</div>
企业级全能服务器运维管理面板。简单轻量,高效运维。
新一代全能服务器运维管理面板。简单轻量,高效运维。
官网:[panel.haozi.net](https://panel.haozi.net) | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
+1 -1
View File
@@ -15,7 +15,7 @@
</div>
Enterprise-level all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
A new generation of all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
Website: [panel.haozi.net](https://panel.haozi.net) | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
+3 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/tnb-labs/panel/internal/apps/frp"
"github.com/tnb-labs/panel/internal/apps/gitea"
"github.com/tnb-labs/panel/internal/apps/memcached"
"github.com/tnb-labs/panel/internal/apps/minio"
"github.com/tnb-labs/panel/internal/apps/mysql"
"github.com/tnb-labs/panel/internal/apps/nginx"
"github.com/tnb-labs/panel/internal/apps/php74"
@@ -76,6 +77,7 @@ func initCli() (*app.Cli, error) {
fail2banApp := fail2ban.NewApp(websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
minioApp := minio.NewApp()
memcachedApp := memcached.NewApp()
mysqlApp := mysql.NewApp(settingRepo)
nginxApp := nginx.NewApp()
@@ -94,7 +96,7 @@ func initCli() (*app.Cli, error) {
s3fsApp := s3fs.NewApp(settingRepo)
supervisorApp := supervisor.NewApp()
toolboxApp := toolbox.NewApp()
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, minioApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
appCli := app.NewCli(command, gormigrate, loader)
return appCli, nil
}
+6 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/tnb-labs/panel/internal/apps/frp"
"github.com/tnb-labs/panel/internal/apps/gitea"
"github.com/tnb-labs/panel/internal/apps/memcached"
"github.com/tnb-labs/panel/internal/apps/minio"
"github.com/tnb-labs/panel/internal/apps/mysql"
"github.com/tnb-labs/panel/internal/apps/nginx"
"github.com/tnb-labs/panel/internal/apps/php74"
@@ -97,6 +98,8 @@ func initWeb() (*app.Web, error) {
sshService := service.NewSSHService(sshRepo)
containerRepo := data.NewContainerRepo()
containerService := service.NewContainerService(containerRepo)
containerComposeRepo := data.NewContainerComposeRepo()
containerComposeService := service.NewContainerComposeService(containerComposeRepo)
containerNetworkRepo := data.NewContainerNetworkRepo()
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
containerImageRepo := data.NewContainerImageRepo()
@@ -113,6 +116,7 @@ func initWeb() (*app.Web, error) {
fail2banApp := fail2ban.NewApp(websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
minioApp := minio.NewApp()
memcachedApp := memcached.NewApp()
mysqlApp := mysql.NewApp(settingRepo)
nginxApp := nginx.NewApp()
@@ -131,8 +135,8 @@ func initWeb() (*app.Web, error) {
s3fsApp := s3fs.NewApp(settingRepo)
supervisorApp := supervisor.NewApp()
toolboxApp := toolbox.NewApp()
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, minioApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
wsService := service.NewWsService(koanf, sshRepo)
ws := route.NewWs(wsService)
mux, err := bootstrap.NewRouter(middlewares, http, ws)
+27 -37
View File
@@ -1,21 +1,20 @@
module github.com/tnb-labs/panel
go 1.23
go 1.24.0
require (
github.com/bddjr/hlfhr v1.3.8
github.com/beevik/ntp v1.4.3
github.com/creack/pty v1.1.24
github.com/expr-lang/expr v1.16.9
github.com/glebarez/sqlite v1.11.0
github.com/expr-lang/expr v1.17.2
github.com/go-chi/chi/v5 v5.2.1
github.com/go-gormigrate/gormigrate/v2 v2.1.3
github.com/go-rat/chix v1.1.4
github.com/go-rat/chix v1.1.5
github.com/go-rat/gormstore v1.0.6
github.com/go-rat/sessions v1.0.11
github.com/go-rat/utils v1.0.9
github.com/go-rat/sessions v1.1.0
github.com/go-rat/utils v1.1.4
github.com/go-resty/resty/v2 v2.16.5
github.com/go-sql-driver/mysql v1.8.1
github.com/go-sql-driver/mysql v1.9.1
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
github.com/gomodule/redigo v1.9.2
github.com/google/wire v0.6.0
@@ -27,22 +26,24 @@ require (
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/cloudflare v0.1.3
github.com/libdns/huaweicloud v0.3.1
github.com/libdns/libdns v0.2.2
github.com/libdns/libdns v0.2.3
github.com/libdns/tencentcloud v1.2.0
github.com/mholt/acmez/v3 v3.0.1
github.com/mholt/acmez/v3 v3.1.1
github.com/ncruces/go-sqlite3 v0.24.1
github.com/ncruces/go-sqlite3/gormlite v0.24.0
github.com/orandin/slog-gorm v1.4.0
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.49.1
github.com/sethvargo/go-limiter v1.0.0
github.com/shirou/gopsutil v2.21.11+incompatible
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cast v1.7.1
github.com/stretchr/testify v1.10.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6
github.com/urfave/cli/v3 v3.0.0-beta1
golang.org/x/crypto v0.33.0
golang.org/x/net v0.34.0
golang.org/x/crypto v0.36.0
golang.org/x/net v0.38.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.12
@@ -51,42 +52,31 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-rat/securecookie v1.0.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-rat/securecookie v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gofiber/schema v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gofiber/schema v1.3.0 // indirect
github.com/gookit/filter v1.2.2 // indirect
github.com/gookit/goutil v0.6.18 // 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/knadh/koanf/maps v0.1.1 // 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/ncruces/go-strftime v0.1.9 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
modernc.org/libc v1.60.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.32.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
)
replace (
github.com/libdns/tencentcloud => github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504
github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1
)
replace github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250328123917-f204dcee9d09
+57 -92
View File
@@ -8,46 +8,39 @@ 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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504 h1:Wk7RQyamGDfAz+5hZIT/GIm6ay5cORztgYVXOJTobKY=
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
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/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/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso=
github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
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.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
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-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-rat/chix v1.1.4 h1:YVDwF4vrlkm5hmlDGWKoiWPgP98pJooBvhsto8UWq+o=
github.com/go-rat/chix v1.1.4/go.mod h1:eKUTG7GXqMS278PH0QZi8yMWZddvMfxqWu7mverM6kQ=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-rat/chix v1.1.5 h1:nv+NHODd9o3AwK9JhEaUsuMCAc7cvw4bh39yTrVuo1M=
github.com/go-rat/chix v1.1.5/go.mod h1:YQP0YT0x/U3Xx5uQe2jfyMiS/bA732Nunnii6MG0wos=
github.com/go-rat/gormstore v1.0.6 h1:YxmaY6n3JoTk6o9RXoZ1+cQ6HQL+ayf/6d8D76ydCAc=
github.com/go-rat/gormstore v1.0.6/go.mod h1:xG/n+Du8baWf7ptiafiHLWBZWVXrYIPC42CsoEbfr4M=
github.com/go-rat/securecookie v1.0.1 h1:HW0fpKmB+FjJzXTw8ABOwBJ+XrPmRBSZqHhmrv86lBo=
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.9 h1:16zq6CpeULJgoW+TaJZ+gS4EPl0tQYAcuAdx3/GNCNs=
github.com/go-rat/utils v1.0.9/go.mod h1:LhzbrdsTHIo6Rjlz27Nh8edkugmoxbZjN87iV+ch+5M=
github.com/go-rat/securecookie v1.1.0 h1:bvwE9n6X3a4eazOIevEvZghkvZ3OpY4BX1cnH/1/x0s=
github.com/go-rat/securecookie v1.1.0/go.mod h1:Z870TcL7ktAjRuhPeQXYuQcA6/DdzKwwqSwDQuPmufI=
github.com/go-rat/sessions v1.1.0 h1:5zCySVVxNE6xLymf5F2u4TQPKIM7kz1MuD4TwIb/7Do=
github.com/go-rat/sessions v1.1.0/go.mod h1:ofnsWh7JoAApzCfIGd60xEyT5HlJY+Nx8zpWcwUXSHc=
github.com/go-rat/utils v1.1.4 h1:tjlX6IWHhA/VvEBVbLGjzA96dcFSdMU5quetUHY2uqs=
github.com/go-rat/utils v1.1.4/go.mod h1:SUy4Xw44VkZvo2FaURgiIiJlL6Kukll85fVKMp/50+8=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
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-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
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/gofiber/schema v1.3.0 h1:K3F3wYzAY+aivfCCEHPufCthu5/13r/lzp1nuk6mr3Q=
github.com/gofiber/schema v1.3.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
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=
@@ -57,11 +50,7 @@ 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/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/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
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/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
@@ -98,29 +87,31 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
github.com/libdns/cloudflare v0.1.3 h1:XPFa2f3Mm/3FDNwl9Ki2bfAQJ0Cm5GQB0e8PQVy25Us=
github.com/libdns/cloudflare v0.1.3/go.mod h1:XbvSCSMcxspwpSialM3bq0LsS3/Houy9WYxW8Ok8b6M=
github.com/libdns/huaweicloud v0.3.1 h1:wro0zpG86JKL3QVEaV/xfHW/59rmqwMYhcbl16w9EYg=
github.com/libdns/huaweicloud v0.3.1/go.mod h1:6s5ZIwLUr2qKsQz2SRNo7uh8X5uJohxoBR95rtSpNcI=
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/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/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/tencentcloud v1.2.0 h1:SBbZ9gUZ+ba/p2d8NpiOcj+WZTdYqB5Fz7zypdbK3YU=
github.com/libdns/tencentcloud v1.2.0/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/go-sqlite3 v0.24.1 h1:qHlIz+dlH3Y0wCUErFZXon5hCvw1Kc9eEkZVYYi8p14=
github.com/ncruces/go-sqlite3 v0.24.1/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
github.com/ncruces/go-sqlite3/gormlite v0.24.0 h1:81sHeq3CCdhjoqAB650n5wEdRlLO9VBvosArskcN3+c=
github.com/ncruces/go-sqlite3/gormlite v0.24.0/go.mod h1:vXfVWdBfg7qOgqQqHpzUWl9LLswD0h+8mK4oouaV2oc=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
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/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/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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -129,24 +120,26 @@ github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
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/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.1051 h1:3mg0L9vv9eO8UN4Oa7vNawe6yUIuXf9D0Q79rUmnblo=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 h1:DMokC7T0UF8wMfT1kD+mX3M+hc2C06gmFvQ9gsfRPmI=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
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/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1 h1:7IhZYEiBWy3KfKi9wZkv4Um8QYYF2fYzQ+xNrOx/tHI=
github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a h1:bFMBmB409YhHd+B4yfM7JPYTCgxnQjqzIfFOiN4Tpsc=
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/tnb-labs/acmez/v3 v3.0.0-20250328123917-f204dcee9d09 h1:Rz/1C7lc0qCuyZY20oh6grqy/hjm9gcjIJB3yEZ4i30=
github.com/tnb-labs/acmez/v3 v3.0.0-20250328123917-f204dcee9d09/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6 h1:HmtcQ7w07RI2SdTKkPf+NM8R33B1oR9MjIZYzlBizwA=
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
@@ -158,16 +151,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -175,36 +166,36 @@ 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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
@@ -212,8 +203,8 @@ 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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=
@@ -222,8 +213,6 @@ 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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
@@ -236,27 +225,3 @@ 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=
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=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
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=
+2
View File
@@ -9,6 +9,7 @@ import (
"github.com/tnb-labs/panel/internal/apps/frp"
"github.com/tnb-labs/panel/internal/apps/gitea"
"github.com/tnb-labs/panel/internal/apps/memcached"
"github.com/tnb-labs/panel/internal/apps/minio"
"github.com/tnb-labs/panel/internal/apps/mysql"
"github.com/tnb-labs/panel/internal/apps/nginx"
"github.com/tnb-labs/panel/internal/apps/php74"
@@ -34,6 +35,7 @@ var ProviderSet = wire.NewSet(
fail2ban.NewApp,
frp.NewApp,
gitea.NewApp,
minio.NewApp,
memcached.NewApp,
mysql.NewApp,
nginx.NewApp,
+47
View File
@@ -0,0 +1,47 @@
package minio
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/tnb-labs/panel/internal/service"
"github.com/tnb-labs/panel/pkg/io"
"github.com/tnb-labs/panel/pkg/systemctl"
)
type App struct{}
func NewApp() *App {
return &App{}
}
func (s *App) Route(r chi.Router) {
r.Get("/env", s.GetEnv)
r.Post("/env", s.UpdateEnv)
}
func (s *App) GetEnv(w http.ResponseWriter, r *http.Request) {
env, _ := io.Read("/etc/default/minio")
service.Success(w, env)
}
func (s *App) UpdateEnv(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateEnv](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write("/etc/default/minio", req.Env, 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("minio"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
+5
View File
@@ -0,0 +1,5 @@
package minio
type UpdateEnv struct {
Env string `form:"env" json:"env" validate:"required"`
}
+1 -1
View File
@@ -46,7 +46,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
re := regexp.MustCompile(`requirepass\s+(.+)`)
re := regexp.MustCompile(`^requirepass\s+(.+)`)
matches := re.FindStringSubmatch(config)
if len(matches) == 2 {
withPassword = " -a " + matches[1]
+1
View File
@@ -19,6 +19,7 @@ type Cert struct {
CertURL string `gorm:"not null" json:"cert_url"` // 证书 URL (续签时使用)
Cert string `gorm:"not null" json:"cert"` // 证书内容
Key string `gorm:"not null" json:"key"` // 私钥内容
Script string `gorm:"not null" json:"script"` // 部署脚本
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
+13
View File
@@ -0,0 +1,13 @@
package biz
import "github.com/tnb-labs/panel/pkg/types"
type ContainerComposeRepo interface {
List() ([]types.ContainerCompose, error)
Get(name string) (string, []types.KV, error)
Create(name, compose string, envs []types.KV) error
Update(name, compose string, envs []types.KV) error
Up(name string, force bool) error
Down(name string) error
Remove(name string) error
}
+3 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/tnb-labs/panel/internal/apps/frp"
"github.com/tnb-labs/panel/internal/apps/gitea"
"github.com/tnb-labs/panel/internal/apps/memcached"
"github.com/tnb-labs/panel/internal/apps/minio"
"github.com/tnb-labs/panel/internal/apps/mysql"
"github.com/tnb-labs/panel/internal/apps/nginx"
"github.com/tnb-labs/panel/internal/apps/php74"
@@ -33,6 +34,7 @@ func NewLoader(
fail2ban *fail2ban.App,
frp *frp.App,
gitea *gitea.App,
minio *minio.App,
memcached *memcached.App,
mysql *mysql.App,
nginx *nginx.App,
@@ -53,6 +55,6 @@ func NewLoader(
toolbox *toolbox.App,
) *apploader.Loader {
loader := new(apploader.Loader)
loader.Add(benchmark, docker, fail2ban, frp, gitea, memcached, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor, toolbox)
loader.Add(benchmark, docker, fail2ban, frp, gitea, minio, memcached, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor, toolbox)
return loader
}
+3 -2
View File
@@ -4,9 +4,10 @@ import (
"log/slog"
"path/filepath"
"github.com/glebarez/sqlite"
"github.com/go-gormigrate/gormigrate/v2"
"github.com/knadh/koanf/v2"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/gormlite"
sloggorm "github.com/orandin/slog-gorm"
"gorm.io/gorm"
@@ -16,7 +17,7 @@ import (
func NewDB(conf *koanf.Koanf, log *slog.Logger) (*gorm.DB, error) {
// You can use any other database, like MySQL or PostgreSQL.
return gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
return gorm.Open(gormlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
Logger: sloggorm.New(sloggorm.WithHandler(log.Handler())),
SkipDefaultTransaction: true,
DisableForeignKeyConstraintWhenMigrating: true,
+1 -1
View File
@@ -38,7 +38,7 @@ func NewHttp(conf *koanf.Koanf, r *chi.Mux) (*hlfhr.Server, error) {
if conf.Bool("http.tls") {
srv.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS13,
MinVersion: tls.VersionTLS12,
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ func NewSession(conf *koanf.Koanf, db *gorm.DB) (*sessions.Manager, error) {
manager, err := sessions.NewManager(&sessions.ManagerOptions{
Key: conf.MustString("app.key"),
Lifetime: 120,
GcInterval: 30,
GcInterval: 5,
DisableDefaultDriver: true,
})
if err != nil {
+3
View File
@@ -833,6 +833,9 @@ func (r *backupRepo) UpdatePanel(version, url, checksum string) error {
_, _ = shell.Execf("systemctl daemon-reload")
_ = io.Remove("/tmp/panel-storage.zip")
_ = io.Remove(filepath.Join(app.Root, "panel/config.example.yml"))
if sqlDB, err := r.db.DB(); err == nil {
_ = sqlDB.Close()
}
tools.RestartPanel()
return nil
+44
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"os"
"slices"
"strings"
"time"
@@ -49,6 +50,7 @@ func (r *certRepo) List(page, limit uint) ([]*types.CertList, int64, error) {
AutoRenew: cert.AutoRenew,
Cert: cert.Cert,
Key: cert.Key,
Script: cert.Script,
CreatedAt: cert.CreatedAt,
UpdatedAt: cert.UpdatedAt,
}
@@ -131,6 +133,7 @@ func (r *certRepo) Update(req *request.CertUpdate) error {
Type: req.Type,
Cert: req.Cert,
Key: req.Key,
Script: req.Script,
Domains: req.Domains,
AutoRenew: req.AutoRenew,
}).Error
@@ -183,6 +186,10 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
return &ssl, r.Deploy(cert.ID, cert.WebsiteID)
}
if err = r.runScript(cert); err != nil {
return nil, err
}
return &ssl, nil
}
@@ -212,6 +219,10 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
return &ssl, r.Deploy(cert.ID, cert.WebsiteID)
}
if err = r.runScript(cert); err != nil {
return nil, err
}
return &ssl, nil
}
@@ -236,6 +247,10 @@ func (r *certRepo) ObtainSelfSigned(id uint) error {
return r.Deploy(cert.ID, cert.WebsiteID)
}
if err = r.runScript(cert); err != nil {
return err
}
return nil
}
@@ -344,6 +359,35 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
return nil
}
func (r *certRepo) runScript(cert *biz.Cert) error {
if cert.Script == "" {
return nil
}
f, err := os.CreateTemp("", "cert-deploy-*.sh")
if err != nil {
return err
}
// 替换变量
cert.Script = strings.ReplaceAll(cert.Script, "{cert}", cert.Cert)
cert.Script = strings.ReplaceAll(cert.Script, "{key}", cert.Key)
if _, err = f.WriteString(cert.Script); err != nil {
return err
}
if err = f.Chmod(0755); err != nil {
return err
}
if err = f.Close(); err != nil {
return err
}
defer os.Remove(f.Name())
_, err = shell.Execf("bash " + f.Name())
return err
}
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
if cert.Account == nil {
return nil, errors.New("this certificate is not associated with an ACME account and cannot be signed")
+1 -1
View File
@@ -39,7 +39,7 @@ func (r *containerRepo) ListAll() ([]types.Container, error) {
for _, port := range item.Ports {
ports = append(ports, types.ContainerPort{
ContainerStart: uint(port.PrivatePort),
ContainerEnd: uint(port.PublicPort),
ContainerEnd: uint(port.PrivatePort),
HostStart: uint(port.PublicPort),
HostEnd: uint(port.PublicPort),
Protocol: port.Type,
+160
View File
@@ -0,0 +1,160 @@
package data
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"time"
"github.com/tnb-labs/panel/internal/app"
"github.com/tnb-labs/panel/internal/biz"
"github.com/tnb-labs/panel/pkg/shell"
"github.com/tnb-labs/panel/pkg/types"
)
type containerComposeRepo struct{}
func NewContainerComposeRepo() biz.ContainerComposeRepo {
return &containerComposeRepo{}
}
// List 列出所有编排文件名
func (r *containerComposeRepo) List() ([]types.ContainerCompose, error) {
raw, err := shell.Execf("docker compose ls -a --format json")
if err != nil {
return nil, err
}
var composeRaws []types.ContainerComposeRaw
if err = json.Unmarshal([]byte(raw), &composeRaws); err != nil {
return nil, err
}
composeDir := filepath.Join(app.Root, "server", "compose")
entries, err := os.ReadDir(composeDir)
if err != nil {
return nil, err
}
var composes []types.ContainerCompose
index := make(map[string]int)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
path := filepath.Join(composeDir, entry.Name())
var createdAt time.Time
if info, err := entry.Info(); err == nil {
createdAt = info.ModTime()
}
composes = append(composes, types.ContainerCompose{
Name: entry.Name(),
Path: path,
Status: "unknown",
CreatedAt: createdAt,
})
index[entry.Name()] = len(composes) - 1
}
// 更新状态
for _, item := range composeRaws {
if i, ok := index[item.Name]; ok {
composes[i].Status = item.Status
}
}
return composes, nil
}
// Get 获取编排文件和环境变量内容
func (r *containerComposeRepo) Get(name string) (string, []types.KV, error) {
content, _ := os.ReadFile(filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml"))
env, _ := os.ReadFile(filepath.Join(app.Root, "server", "compose", name, ".env"))
var envs []types.KV
for _, line := range strings.Split(string(env), "\n") {
if line == "" {
continue
}
kv := strings.SplitN(line, "=", 2)
if len(kv) != 2 {
continue
}
envs = append(envs, types.KV{Key: kv[0], Value: kv[1]})
}
return string(content), envs, nil // 有意忽略错误,这样可以允许新建文件
}
// Create 创建编排文件
func (r *containerComposeRepo) Create(name, compose string, envs []types.KV) error {
dir := filepath.Join(app.Root, "server", "compose", name)
if err := os.MkdirAll(dir, 0644); err != nil {
return err
}
if err := os.WriteFile(filepath.Join(dir, "docker-compose.yml"), []byte(compose), 0644); err != nil {
return err
}
var sb strings.Builder
for _, kv := range envs {
sb.WriteString(kv.Key)
sb.WriteString("=")
sb.WriteString(kv.Value)
sb.WriteString("\n")
}
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(sb.String()), 0644); err != nil {
return err
}
return nil
}
// Update 更新编排文件
func (r *containerComposeRepo) Update(name, compose string, envs []types.KV) error {
dir := filepath.Join(app.Root, "server", "compose", name)
if err := os.WriteFile(filepath.Join(dir, "docker-compose.yml"), []byte(compose), 0644); err != nil {
return err
}
var sb strings.Builder
for _, kv := range envs {
sb.WriteString(kv.Key)
sb.WriteString("=")
sb.WriteString(kv.Value)
sb.WriteString("\n")
}
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(sb.String()), 0644); err != nil {
return err
}
return nil
}
// Up 启动编排
func (r *containerComposeRepo) Up(name string, force bool) error {
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
cmd := "docker compose -f %s up -d"
if force {
cmd += " --pull always" // 强制拉取镜像
}
_, err := shell.Execf(cmd, file)
return err
}
// Down 停止编排
func (r *containerComposeRepo) Down(name string) error {
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
_, err := shell.Execf("docker compose -f %s down", file)
return err
}
// Remove 删除编排
func (r *containerComposeRepo) Remove(name string) error {
dir := filepath.Join(app.Root, "server", "compose", name)
return os.RemoveAll(dir)
}
+1
View File
@@ -65,6 +65,7 @@ export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
panel-cli backup website -n '%s' -p '%s'
panel-cli backup clear -t website -f '%s' -s '%d' -p '%s'
systemctl reload nginx
`, req.Target, req.BackupPath, req.Target, req.Save, req.BackupPath)
}
if req.BackupType == "mysql" || req.BackupType == "postgres" {
+1
View File
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
NewCertAccountRepo,
NewCertDNSRepo,
NewContainerRepo,
NewContainerComposeRepo,
NewContainerImageRepo,
NewContainerNetworkRepo,
NewContainerVolumeRepo,
+2
View File
@@ -2,6 +2,8 @@ package embed
import "embed"
// TODO 移到pkg目录下
//go:embed all:frontend/*
var PublicFS embed.FS
+11 -4
View File
@@ -1,22 +1,29 @@
package request
import "mime/multipart"
type BackupList struct {
Type string `json:"type" form:"type" validate:"required|in:path,website,mysql,postgres,redis,panel"`
Type string `uri:"type" form:"type" validate:"required|in:path,website,mysql,postgres,redis,panel"`
}
type BackupCreate struct {
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
Target string `json:"target" form:"target" validate:"required"`
Path string `json:"path" form:"path"`
}
type BackupUpload struct {
Type string `uri:"type" form:"type"` // 校验没有必要,因为根本没经过验证器
File *multipart.FileHeader `form:"file"`
}
type BackupFile struct {
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
File string `json:"file" form:"file" validate:"required"`
}
type BackupRestore struct {
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
File string `json:"file" form:"file" validate:"required"`
Target string `json:"target" form:"target" validate:"required"`
}
+1
View File
@@ -20,6 +20,7 @@ type CertUpdate struct {
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
Cert string `form:"cert" json:"cert"`
Key string `form:"key" json:"key"`
Script string `form:"script" json:"script"`
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
AccountID uint `form:"account_id" json:"account_id"`
DNSID uint `form:"dns_id" json:"dns_id"`
@@ -0,0 +1,32 @@
package request
import "github.com/tnb-labs/panel/pkg/types"
type ContainerComposeGet struct {
Name string `uri:"name" validate:"required"`
}
type ContainerComposeCreate struct {
Name string `json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
Compose string `json:"compose" validate:"required"`
Envs []types.KV `json:"envs"`
}
type ContainerComposeUpdate struct {
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
Compose string `json:"compose" validate:"required"`
Envs []types.KV `json:"envs"`
}
type ContainerComposeUp struct {
Name string `uri:"name" validate:"required"`
Force bool `json:"force"`
}
type ContainerComposeDown struct {
Name string `uri:"name" validate:"required"`
}
type ContainerComposeRemove struct {
Name string `uri:"name" validate:"required"`
}
+1 -1
View File
@@ -7,7 +7,7 @@ type CronCreate struct {
Script string `form:"script" json:"script"`
BackupType string `form:"backup_type" json:"backup_type" validate:"requiredIf:Type,backup"`
BackupPath string `form:"backup_path" json:"backup_path"`
Target string `form:"target" json:"target" validate:"required"`
Target string `form:"target" json:"target" validate:"requiredIf:Type,backup,cutoff"`
Save int `form:"save" json:"save" validate:"required"`
}
+5
View File
@@ -48,6 +48,11 @@ func (r *PanelTask) Run() {
r.log.Warn("[Panel Task] failed to vacuum database", slog.Any("err", err))
return
}
if err := r.db.Exec("PRAGMA journal_mode=WAL;").Error; err != nil {
app.Status = app.StatusFailed
r.log.Warn("[Panel Task] failed to set database journal_mode to WAL", slog.Any("err", err))
return
}
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
app.Status = app.StatusFailed
r.log.Warn("[Panel Task] failed to wal checkpoint database", slog.Any("err", err))
+9
View File
@@ -69,4 +69,13 @@ func init() {
)
},
})
Migrations = append(Migrations, &gormigrate.Migration{
ID: "20250318-cert-script",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&biz.Cert{})
},
Rollback: func(tx *gorm.DB) error {
return tx.Migrator().DropColumn(&biz.Cert{}, "script")
},
})
}
+12
View File
@@ -33,6 +33,7 @@ type Http struct {
firewall *service.FirewallService
ssh *service.SSHService
container *service.ContainerService
containerCompose *service.ContainerComposeService
containerNetwork *service.ContainerNetworkService
containerImage *service.ContainerImageService
containerVolume *service.ContainerVolumeService
@@ -62,6 +63,7 @@ func NewHttp(
firewall *service.FirewallService,
ssh *service.SSHService,
container *service.ContainerService,
containerCompose *service.ContainerComposeService,
containerNetwork *service.ContainerNetworkService,
containerImage *service.ContainerImageService,
containerVolume *service.ContainerVolumeService,
@@ -90,6 +92,7 @@ func NewHttp(
firewall: firewall,
ssh: ssh,
container: container,
containerCompose: containerCompose,
containerNetwork: containerNetwork,
containerImage: containerImage,
containerVolume: containerVolume,
@@ -284,6 +287,15 @@ func (route *Http) Register(r *chi.Mux) {
r.Get("/{id}/logs", route.container.Logs)
r.Post("/prune", route.container.Prune)
})
r.Route("/compose", func(r chi.Router) {
r.Get("/", route.containerCompose.List)
r.Get("/{name}", route.containerCompose.Get)
r.Post("/", route.containerCompose.Create)
r.Put("/{name}", route.containerCompose.Update)
r.Post("/{name}/up", route.containerCompose.Up)
r.Post("/{name}/down", route.containerCompose.Down)
r.Delete("/{name}", route.containerCompose.Remove)
})
r.Route("/network", func(r chi.Router) {
r.Get("/", route.containerNetwork.List)
r.Post("/", route.containerNetwork.Create)
+22 -15
View File
@@ -5,6 +5,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"github.com/go-rat/chix"
@@ -55,38 +56,44 @@ func (s *BackupService) Create(w http.ResponseWriter, r *http.Request) {
}
func (s *BackupService) Upload(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(2 << 30); err != nil {
binder := chix.NewBind(r)
defer binder.Release()
req := new(request.BackupUpload)
if err := binder.MultipartForm(req, 2<<30); err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err := binder.URI(req); err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
_, handler, err := r.FormFile("file")
if err != nil {
Error(w, http.StatusInternalServerError, "上传文件失败:%v", err)
// 只允许上传 .sql .zip .tar .gz .tgz .bz2 .xz .7z
if !slices.Contains([]string{".sql", ".zip", ".tar", ".gz", ".tgz", ".bz2", ".xz", ".7z"}, filepath.Ext(req.File.Filename)) {
Error(w, http.StatusForbidden, "unsupported file type")
return
}
path, err := s.backupRepo.GetPath(biz.BackupType(r.FormValue("type")))
path, err := s.backupRepo.GetPath(biz.BackupType(req.Type))
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if !io.Exists(filepath.Dir(path)) {
if err = os.MkdirAll(filepath.Dir(path), 0755); err != nil {
Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err)
return
}
if io.Exists(filepath.Join(path, req.File.Filename)) {
Error(w, http.StatusForbidden, "target backup %s already exists", path)
return
}
src, _ := handler.Open()
out, err := os.OpenFile(filepath.Join(path, handler.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
src, _ := req.File.Open()
out, err := os.OpenFile(filepath.Join(path, req.File.Filename), os.O_CREATE|os.O_RDWR|os.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
}
+4 -4
View File
@@ -535,7 +535,7 @@ func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error
fmt.Println(s.hr)
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
fmt.Println(s.hr)
fmt.Println("|-备份类型:网站")
fmt.Println("|-备份类型:website")
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)
@@ -550,7 +550,7 @@ func (s *CliService) BackupDatabase(ctx context.Context, cmd *cli.Command) error
fmt.Println(s.hr)
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
fmt.Println(s.hr)
fmt.Println("|-备份类型:数据库")
fmt.Println("|-备份类型:database")
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 {
@@ -566,7 +566,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error {
fmt.Println(s.hr)
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
fmt.Println(s.hr)
fmt.Println("|-备份类型:面板")
fmt.Println("|-备份类型:panel")
if err := s.backupRepo.Create(biz.BackupTypePanel, "", cmd.String("path")); err != nil {
return fmt.Errorf("备份失败:%v", err)
}
@@ -613,7 +613,7 @@ func (s *CliService) CutoffWebsite(ctx context.Context, cmd *cli.Command) error
fmt.Println(s.hr)
fmt.Printf("★ 开始切割日志 [%s]\n", time.Now().Format(time.DateTime))
fmt.Println(s.hr)
fmt.Println("|-切割类型:网站")
fmt.Println("|-切割类型:website")
fmt.Printf("|-切割目标:%s\n", website.Name)
if err = s.backupRepo.CutoffLog(path, filepath.Join(app.Root, "wwwlogs", website.Name+".log")); err != nil {
return err
+129
View File
@@ -0,0 +1,129 @@
package service
import (
"net/http"
"github.com/go-rat/chix"
"github.com/tnb-labs/panel/internal/biz"
"github.com/tnb-labs/panel/internal/http/request"
)
type ContainerComposeService struct {
containerComposeRepo biz.ContainerComposeRepo
}
func NewContainerComposeService(containerCompose biz.ContainerComposeRepo) *ContainerComposeService {
return &ContainerComposeService{
containerComposeRepo: containerCompose,
}
}
func (s *ContainerComposeService) List(w http.ResponseWriter, r *http.Request) {
composes, err := s.containerComposeRepo.List()
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
paged, total := Paginate(r, composes)
Success(w, chix.M{
"total": total,
"items": paged,
})
}
func (s *ContainerComposeService) Get(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerComposeGet](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
compose, envs, err := s.containerComposeRepo.Get(req.Name)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, chix.M{
"compose": compose,
"envs": envs,
})
}
func (s *ContainerComposeService) Create(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerComposeCreate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerComposeRepo.Create(req.Name, req.Compose, req.Envs); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *ContainerComposeService) Update(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerComposeUpdate](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerComposeRepo.Update(req.Name, req.Compose, req.Envs); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *ContainerComposeService) Up(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerComposeUp](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerComposeRepo.Up(req.Name, req.Force); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *ContainerComposeService) Down(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerComposeDown](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerComposeRepo.Down(req.Name); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *ContainerComposeService) Remove(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.ContainerComposeRemove](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = s.containerComposeRepo.Remove(req.Name); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
+1 -1
View File
@@ -291,7 +291,7 @@ func (s *FileService) RemoteDownload(w http.ResponseWriter, r *http.Request) {
task := new(biz.Task)
task.Name = "下载远程文件"
task.Status = biz.TaskStatusWaiting
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s'`, timestamp, req.Path, req.URL)
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s' && chmod 0755 '%s' && chown www:www '%s'`, timestamp, req.Path, req.URL, req.Path, req.Path)
task.Log = fmt.Sprintf("/tmp/remote-download-%s.log", timestamp)
if err = s.taskRepo.Push(task); err != nil {
+1
View File
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
NewCertDNSService,
NewCliService,
NewContainerService,
NewContainerComposeService,
NewContainerImageService,
NewContainerNetworkService,
NewContainerVolumeService,
+1 -1
View File
@@ -93,7 +93,7 @@ func (s *WsService) Exec(w http.ResponseWriter, r *http.Request) {
}
ctx, cancel := context.WithCancel(context.Background())
out, err := shell.ExecfWithPipe(ctx, string(cmd)) // nolint: govet
out, err := shell.ExecfWithPipe(ctx, string(cmd))
if err != nil {
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "failed to run command"))
cancel()
+392
View File
@@ -0,0 +1,392 @@
// Code generated by mockery. DO NOT EDIT.
package biz
import (
mock "github.com/stretchr/testify/mock"
types "github.com/tnb-labs/panel/pkg/types"
)
// ContainerComposeRepo is an autogenerated mock type for the ContainerComposeRepo type
type ContainerComposeRepo struct {
mock.Mock
}
type ContainerComposeRepo_Expecter struct {
mock *mock.Mock
}
func (_m *ContainerComposeRepo) EXPECT() *ContainerComposeRepo_Expecter {
return &ContainerComposeRepo_Expecter{mock: &_m.Mock}
}
// Create provides a mock function with given fields: name, compose, envs
func (_m *ContainerComposeRepo) Create(name string, compose string, envs []types.KV) error {
ret := _m.Called(name, compose, envs)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, string, []types.KV) error); ok {
r0 = rf(name, compose, envs)
} else {
r0 = ret.Error(0)
}
return r0
}
// ContainerComposeRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
type ContainerComposeRepo_Create_Call struct {
*mock.Call
}
// Create is a helper method to define mock.On call
// - name string
// - compose string
// - envs []types.KV
func (_e *ContainerComposeRepo_Expecter) Create(name interface{}, compose interface{}, envs interface{}) *ContainerComposeRepo_Create_Call {
return &ContainerComposeRepo_Create_Call{Call: _e.mock.On("Create", name, compose, envs)}
}
func (_c *ContainerComposeRepo_Create_Call) Run(run func(name string, compose string, envs []types.KV)) *ContainerComposeRepo_Create_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].([]types.KV))
})
return _c
}
func (_c *ContainerComposeRepo_Create_Call) Return(_a0 error) *ContainerComposeRepo_Create_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ContainerComposeRepo_Create_Call) RunAndReturn(run func(string, string, []types.KV) error) *ContainerComposeRepo_Create_Call {
_c.Call.Return(run)
return _c
}
// Down provides a mock function with given fields: name
func (_m *ContainerComposeRepo) Down(name string) error {
ret := _m.Called(name)
if len(ret) == 0 {
panic("no return value specified for Down")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(name)
} else {
r0 = ret.Error(0)
}
return r0
}
// ContainerComposeRepo_Down_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Down'
type ContainerComposeRepo_Down_Call struct {
*mock.Call
}
// Down is a helper method to define mock.On call
// - name string
func (_e *ContainerComposeRepo_Expecter) Down(name interface{}) *ContainerComposeRepo_Down_Call {
return &ContainerComposeRepo_Down_Call{Call: _e.mock.On("Down", name)}
}
func (_c *ContainerComposeRepo_Down_Call) Run(run func(name string)) *ContainerComposeRepo_Down_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *ContainerComposeRepo_Down_Call) Return(_a0 error) *ContainerComposeRepo_Down_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ContainerComposeRepo_Down_Call) RunAndReturn(run func(string) error) *ContainerComposeRepo_Down_Call {
_c.Call.Return(run)
return _c
}
// Get provides a mock function with given fields: name
func (_m *ContainerComposeRepo) Get(name string) (string, []types.KV, error) {
ret := _m.Called(name)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 string
var r1 []types.KV
var r2 error
if rf, ok := ret.Get(0).(func(string) (string, []types.KV, error)); ok {
return rf(name)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) []types.KV); ok {
r1 = rf(name)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).([]types.KV)
}
}
if rf, ok := ret.Get(2).(func(string) error); ok {
r2 = rf(name)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ContainerComposeRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
type ContainerComposeRepo_Get_Call struct {
*mock.Call
}
// Get is a helper method to define mock.On call
// - name string
func (_e *ContainerComposeRepo_Expecter) Get(name interface{}) *ContainerComposeRepo_Get_Call {
return &ContainerComposeRepo_Get_Call{Call: _e.mock.On("Get", name)}
}
func (_c *ContainerComposeRepo_Get_Call) Run(run func(name string)) *ContainerComposeRepo_Get_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *ContainerComposeRepo_Get_Call) Return(_a0 string, _a1 []types.KV, _a2 error) *ContainerComposeRepo_Get_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *ContainerComposeRepo_Get_Call) RunAndReturn(run func(string) (string, []types.KV, error)) *ContainerComposeRepo_Get_Call {
_c.Call.Return(run)
return _c
}
// List provides a mock function with no fields
func (_m *ContainerComposeRepo) List() ([]types.ContainerCompose, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for List")
}
var r0 []types.ContainerCompose
var r1 error
if rf, ok := ret.Get(0).(func() ([]types.ContainerCompose, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []types.ContainerCompose); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]types.ContainerCompose)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ContainerComposeRepo_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
type ContainerComposeRepo_List_Call struct {
*mock.Call
}
// List is a helper method to define mock.On call
func (_e *ContainerComposeRepo_Expecter) List() *ContainerComposeRepo_List_Call {
return &ContainerComposeRepo_List_Call{Call: _e.mock.On("List")}
}
func (_c *ContainerComposeRepo_List_Call) Run(run func()) *ContainerComposeRepo_List_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *ContainerComposeRepo_List_Call) Return(_a0 []types.ContainerCompose, _a1 error) *ContainerComposeRepo_List_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *ContainerComposeRepo_List_Call) RunAndReturn(run func() ([]types.ContainerCompose, error)) *ContainerComposeRepo_List_Call {
_c.Call.Return(run)
return _c
}
// Remove provides a mock function with given fields: name
func (_m *ContainerComposeRepo) Remove(name string) error {
ret := _m.Called(name)
if len(ret) == 0 {
panic("no return value specified for Remove")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(name)
} else {
r0 = ret.Error(0)
}
return r0
}
// ContainerComposeRepo_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
type ContainerComposeRepo_Remove_Call struct {
*mock.Call
}
// Remove is a helper method to define mock.On call
// - name string
func (_e *ContainerComposeRepo_Expecter) Remove(name interface{}) *ContainerComposeRepo_Remove_Call {
return &ContainerComposeRepo_Remove_Call{Call: _e.mock.On("Remove", name)}
}
func (_c *ContainerComposeRepo_Remove_Call) Run(run func(name string)) *ContainerComposeRepo_Remove_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *ContainerComposeRepo_Remove_Call) Return(_a0 error) *ContainerComposeRepo_Remove_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ContainerComposeRepo_Remove_Call) RunAndReturn(run func(string) error) *ContainerComposeRepo_Remove_Call {
_c.Call.Return(run)
return _c
}
// Up provides a mock function with given fields: name, force
func (_m *ContainerComposeRepo) Up(name string, force bool) error {
ret := _m.Called(name, force)
if len(ret) == 0 {
panic("no return value specified for Up")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
r0 = rf(name, force)
} else {
r0 = ret.Error(0)
}
return r0
}
// ContainerComposeRepo_Up_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Up'
type ContainerComposeRepo_Up_Call struct {
*mock.Call
}
// Up is a helper method to define mock.On call
// - name string
// - force bool
func (_e *ContainerComposeRepo_Expecter) Up(name interface{}, force interface{}) *ContainerComposeRepo_Up_Call {
return &ContainerComposeRepo_Up_Call{Call: _e.mock.On("Up", name, force)}
}
func (_c *ContainerComposeRepo_Up_Call) Run(run func(name string, force bool)) *ContainerComposeRepo_Up_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(bool))
})
return _c
}
func (_c *ContainerComposeRepo_Up_Call) Return(_a0 error) *ContainerComposeRepo_Up_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ContainerComposeRepo_Up_Call) RunAndReturn(run func(string, bool) error) *ContainerComposeRepo_Up_Call {
_c.Call.Return(run)
return _c
}
// Update provides a mock function with given fields: name, compose, envs
func (_m *ContainerComposeRepo) Update(name string, compose string, envs []types.KV) error {
ret := _m.Called(name, compose, envs)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, string, []types.KV) error); ok {
r0 = rf(name, compose, envs)
} else {
r0 = ret.Error(0)
}
return r0
}
// ContainerComposeRepo_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
type ContainerComposeRepo_Update_Call struct {
*mock.Call
}
// Update is a helper method to define mock.On call
// - name string
// - compose string
// - envs []types.KV
func (_e *ContainerComposeRepo_Expecter) Update(name interface{}, compose interface{}, envs interface{}) *ContainerComposeRepo_Update_Call {
return &ContainerComposeRepo_Update_Call{Call: _e.mock.On("Update", name, compose, envs)}
}
func (_c *ContainerComposeRepo_Update_Call) Run(run func(name string, compose string, envs []types.KV)) *ContainerComposeRepo_Update_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].([]types.KV))
})
return _c
}
func (_c *ContainerComposeRepo_Update_Call) Return(_a0 error) *ContainerComposeRepo_Update_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ContainerComposeRepo_Update_Call) RunAndReturn(run func(string, string, []types.KV) error) *ContainerComposeRepo_Update_Call {
_c.Call.Return(run)
return _c
}
// NewContainerComposeRepo creates a new instance of ContainerComposeRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewContainerComposeRepo(t interface {
mock.TestingT
Cleanup(func())
}) *ContainerComposeRepo {
mock := &ContainerComposeRepo{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
-5
View File
@@ -120,8 +120,3 @@ func GetSymlink(path string) string {
}
return linkPath
}
// TempFile 创建临时文件
func TempFile(dir, prefix string) (*os.File, error) {
return os.CreateTemp(dir, prefix)
}
+37 -16
View File
@@ -37,16 +37,19 @@ func Execf(shell string, args ...any) (string, error) {
if !preCheckArg(args) {
return "", errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
cmd := exec.Command("bash", "-c", shell)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
}
return strings.TrimSpace(stdout.String()), nil
@@ -57,9 +60,12 @@ func ExecfAsync(shell string, args ...any) error {
if !preCheckArg(args) {
return errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
cmd := exec.Command("bash", "-c", shell)
err := cmd.Start()
if err != nil {
@@ -68,7 +74,7 @@ func ExecfAsync(shell string, args ...any) error {
go func() {
if err = cmd.Wait(); err != nil {
fmt.Println(fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(err.Error())))
fmt.Println(fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(err.Error())))
}
}()
@@ -80,9 +86,12 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
if !preCheckArg(args) {
return "", errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
cmd := exec.Command("bash", "-c", shell)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
@@ -90,7 +99,7 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
err := cmd.Start()
if err != nil {
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
}
done := make(chan error)
@@ -101,10 +110,10 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
select {
case <-time.After(timeout):
_ = cmd.Process.Kill()
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), "timeout")
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, "timeout")
case err = <-done:
if err != nil {
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
}
}
@@ -116,9 +125,12 @@ func ExecfWithOutput(shell string, args ...any) error {
if !preCheckArg(args) {
return errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
cmd := exec.Command("bash", "-c", shell)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -130,9 +142,12 @@ func ExecfWithPipe(ctx context.Context, shell string, args ...any) (out io.ReadC
if !preCheckArg(args) {
return nil, errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.CommandContext(ctx, "bash", "-c", fmt.Sprintf(shell, args...))
cmd := exec.CommandContext(ctx, "bash", "-c", shell)
out, err = cmd.StdoutPipe()
if err != nil {
@@ -149,9 +164,12 @@ func ExecfWithDir(dir, shell string, args ...any) (string, error) {
if !preCheckArg(args) {
return "", errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
cmd := exec.Command("bash", "-c", shell)
cmd.Dir = dir
var stdout, stderr bytes.Buffer
@@ -159,7 +177,7 @@ func ExecfWithDir(dir, shell string, args ...any) (string, error) {
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", shell, strings.TrimSpace(stderr.String()))
}
return strings.TrimSpace(stdout.String()), nil
@@ -170,9 +188,12 @@ func ExecfWithTTY(shell string, args ...any) (string, error) {
if !preCheckArg(args) {
return "", errors.New("command contains illegal characters")
}
if len(args) > 0 {
shell = fmt.Sprintf(shell, args...)
}
_ = os.Setenv("LC_ALL", "C")
cmd := exec.Command("bash", "-i", "-c", fmt.Sprintf(shell, args...))
cmd := exec.Command("bash", "-i", "-c", shell)
var out bytes.Buffer
var stderr bytes.Buffer
@@ -180,15 +201,15 @@ func ExecfWithTTY(shell string, args ...any) (string, error) {
f, err := pty.Start(cmd)
if err != nil {
return "", fmt.Errorf("run %s failed", fmt.Sprintf(shell, args...))
return "", fmt.Errorf("run %s failed", shell)
}
defer f.Close()
if _, err = io.Copy(&out, f); ptyError(err) != nil {
return "", fmt.Errorf("run %s failed, out: %s, err: %w", fmt.Sprintf(shell, args...), strings.TrimSpace(out.String()), err)
return "", fmt.Errorf("run %s failed, out: %s, err: %w", shell, strings.TrimSpace(out.String()), err)
}
if stderr.Len() > 0 {
return "", fmt.Errorf("run %s failed, out: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
return "", fmt.Errorf("run %s failed, out: %s", shell, strings.TrimSpace(stderr.String()))
}
return strings.TrimSpace(out.String()), nil
+1
View File
@@ -12,6 +12,7 @@ type CertList struct {
AutoRenew bool `json:"auto_renew"`
Cert string `json:"cert"`
Key string `json:"key"`
Script string `json:"script"`
NotBefore time.Time `json:"not_before"`
NotAfter time.Time `json:"not_after"`
Issuer string `json:"issuer"`
+17
View File
@@ -0,0 +1,17 @@
package types
import "time"
// ContainerComposeRaw docker compose ls 命令原始输出
type ContainerComposeRaw struct {
Name string `json:"Name"`
Status string `json:"Status"`
ConfigFiles string `json:"ConfigFiles"`
}
type ContainerCompose struct {
Name string `json:"name"`
Path string `json:"path"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
+9 -7
View File
@@ -27,8 +27,9 @@
"@eslint/eslintrc": "^3.2.0",
"@fontsource-variable/jetbrains-mono": "^5.1.1",
"@guolao/vue-monaco-editor": "^1.5.4",
"@vavt/copy2clipboard": "^1.0.1",
"@vue-js-cron/naive-ui": "^2.0.9",
"@vueuse/core": "^12.2.0",
"@vueuse/core": "^13.0.0",
"@xterm/addon-attach": "^0.11.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
@@ -45,7 +46,7 @@
"marked": "^15.0.4",
"mitt": "^3.0.1",
"node-forge": "^1.3.1",
"pinia": "^2.3.0",
"pinia": "^3.0.0",
"pinia-plugin-persistedstate": "^4.2.0",
"remove": "^0.1.5",
"vue": "^3.5.13",
@@ -62,7 +63,7 @@
"@types/luxon": "^3.4.2",
"@types/node": "^22.10.2",
"@types/node-forge": "^1.3.11",
"@unocss/eslint-config": "^65.0.0",
"@unocss/eslint-config": "^66.0.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^14.2.0",
@@ -70,7 +71,7 @@
"colord": "^2.9.3",
"cpx2": "^8.0.0",
"eslint": "^9.17.0",
"eslint-plugin-vue": "^9.32.0",
"eslint-plugin-vue": "^10.0.0",
"md-editor-v3": "^5.1.1",
"monaco-editor": "^0.52.2",
"naive-ui": "^2.40.4",
@@ -79,8 +80,8 @@
"prettier-plugin-organize-imports": "^4.1.0",
"sass": "^1.83.0",
"tsx": "^4.19.2",
"typescript": "5.7.3",
"unocss": "^65.0.0",
"typescript": "5.8.2",
"unocss": "^66.0.0",
"unplugin-auto-import": "^19.0.0",
"unplugin-icons": "^22.0.0",
"unplugin-vue-components": "^28.0.0",
@@ -90,5 +91,6 @@
"vite-plugin-static-copy": "^2.2.0",
"vite-plugin-vue-devtools": "^7.6.8",
"vue-tsc": "^2.2.0"
}
},
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}
+1421 -1828
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- vue-demi
+8
View File
@@ -0,0 +1,8 @@
import { http } from '@/utils'
export default {
// 获取环境变量
env: (): any => http.Get('/apps/minio/env'),
// 保存环境变量
saveEnv: (env: string): any => http.Post('/apps/minio/env', { env })
}
+1 -5
View File
@@ -8,11 +8,7 @@ export default {
create: (type: string, target: string, path: string): any =>
http.Post(`/backup/${type}`, { target, path }),
// 上传备份
upload: (type: string, formData: FormData): any => {
return http.Post(`/backup/${type}/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
},
upload: (type: string, formData: FormData): any => http.Post(`/backup/${type}/upload`, formData),
// 删除备份
delete: (type: string, file: string): any => http.Delete(`/backup/${type}/delete`, { file }),
// 恢复备份
+16
View File
@@ -27,6 +27,22 @@ export default {
containerLogs: (id: string): any => http.Get(`/container/container/${id}/logs`),
// 清理容器
containerPrune: (): any => http.Post(`/container/container/prune`),
// 获取编排列表
composeList: (page: number, limit: number): any =>
http.Get('/container/compose', { params: { page, limit } }),
// 获取编排
composeGet: (name: string): any => http.Get(`/container/compose/${name}`),
// 创建编排
composeCreate: (config: any): any => http.Post('/container/compose', config),
// 更新编排
composeUpdate: (name: string, config: any): any => http.Put(`/container/compose/${name}`, config),
// 删除编排
composeRemove: (name: string): any => http.Delete(`/container/compose/${name}`),
// 启动编排
composeUp: (name: string, force: boolean): any =>
http.Post(`/container/compose/${name}/up`, { force }),
// 停止编排
composeDown: (name: string): any => http.Post(`/container/compose/${name}/down`),
// 获取网络列表
networkList: (page: number, limit: number): any =>
http.Get(`/container/network`, { params: { page, limit } }),
+4 -4
View File
@@ -16,12 +16,12 @@ const props = defineProps({
})
const disabled = ref(false) // 在出现错误的情况下禁用保存
const data = ref('')
const content = ref('')
const get = () => {
useRequest(file.content(props.path))
.onSuccess(({ data }) => {
data.value = decodeBase64(data.content)
content.value = decodeBase64(data.content)
window.$message.success('获取成功')
})
.onError(() => {
@@ -34,7 +34,7 @@ const save = () => {
window.$message.error('当前状态下不可保存')
return
}
useRequest(file.save(props.path, data.value)).onSuccess(() => {
useRequest(file.save(props.path, content.value)).onSuccess(() => {
window.$message.success('保存成功')
})
}
@@ -51,7 +51,7 @@ defineExpose({
<template>
<Editor
v-model:value="data"
v-model:value="content"
:language="languageByPath(props.path)"
theme="vs-dark"
height="60vh"
+1 -1
View File
@@ -20,7 +20,7 @@ async function setupApp() {
const app = createApp(App)
app.use(VueMonacoEditorPlugin, {
paths: {
vs: '/assets/vs'
vs: window.location.origin + '/assets/vs'
},
'vs/nls': {
availableLanguages: { '*': 'zh-cn' }
+2 -1
View File
@@ -8,7 +8,8 @@ body {
height: 100%;
overflow: hidden;
background-color: #f2f2f2;
font-family: -apple-system, 'Noto Sans', 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial,
font-family:
-apple-system, 'Noto Sans', 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial,
'Liberation Sans', 'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC',
'Source Han Sans SC', 'Source Han Sans CN', 'Microsoft YaHei', 'Wenquanyi Micro Hei',
'WenQuanYi Zen Hei', 'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;
+3
View File
@@ -144,6 +144,9 @@ onMounted(() => {
/>
</n-space>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="docker" />
</n-tab-pane>
</n-tabs>
</common-page>
</template>
@@ -376,6 +376,9 @@ onMounted(() => {
/>
</n-card>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="fail2ban" />
</n-tab-pane>
</n-tabs>
</common-page>
<n-modal v-model:show="addJailModal" title="添加规则">
+3
View File
@@ -139,6 +139,9 @@ onMounted(() => {
/>
</n-space>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="gitea" />
</n-tab-pane>
</n-tabs>
</common-page>
</template>
@@ -171,6 +171,9 @@ onMounted(() => {
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="memcached" />
</n-tab-pane>
</n-tabs>
</common-page>
</template>
+143
View File
@@ -0,0 +1,143 @@
<script setup lang="ts">
defineOptions({
name: 'apps-minio-index'
})
import Editor from '@guolao/vue-monaco-editor'
import { NButton, NPopconfirm } from 'naive-ui'
import minio from '@/api/apps/minio'
import systemctl from '@/api/panel/systemctl'
const currentTab = ref('status')
const status = ref(false)
const isEnabled = ref(false)
const env = ref('')
const statusStr = computed(() => {
return status.value ? '正常运行中' : '已停止运行'
})
const getStatus = async () => {
status.value = await systemctl.status('minio')
}
const getIsEnabled = async () => {
isEnabled.value = await systemctl.isEnabled('minio')
}
const getEnv = async () => {
env.value = await minio.env()
}
const handleSaveEnv = () => {
useRequest(minio.saveEnv(env.value)).onSuccess(() => {
window.$message.success('保存成功')
})
}
const handleStart = async () => {
await systemctl.start('minio')
window.$message.success('启动成功')
await getStatus()
}
const handleStop = async () => {
await systemctl.stop('minio')
window.$message.success('停止成功')
await getStatus()
}
const handleRestart = async () => {
await systemctl.restart('minio')
window.$message.success('重启成功')
await getStatus()
}
const handleIsEnabled = async () => {
if (isEnabled.value) {
await systemctl.enable('minio')
window.$message.success('开启自启动成功')
} else {
await systemctl.disable('minio')
window.$message.success('禁用自启动成功')
}
await getIsEnabled()
}
onMounted(() => {
getStatus()
getIsEnabled()
getEnv()
})
</script>
<template>
<common-page show-footer>
<template #action>
<n-button v-if="currentTab == 'env'" class="ml-16" type="primary" @click="handleSaveEnv">
<TheIcon :size="18" icon="material-symbols:save-outline" />
保存
</n-button>
</template>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="status" tab="运行状态">
<n-card title="运行状态">
<template #header-extra>
<n-switch v-model:value="isEnabled" @update:value="handleIsEnabled">
<template #checked> 自启动开 </template>
<template #unchecked> 自启动关 </template>
</n-switch>
</template>
<n-space vertical>
<n-alert :type="status ? 'success' : 'error'">
{{ statusStr }}
</n-alert>
<n-space>
<n-button type="success" @click="handleStart">
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
启动
</n-button>
<n-popconfirm @positive-click="handleStop">
<template #trigger>
<n-button type="error">
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
停止
</n-button>
</template>
确定要停止 Minio
</n-popconfirm>
<n-button type="warning" @click="handleRestart">
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
重启
</n-button>
</n-space>
</n-space>
</n-card>
</n-tab-pane>
<n-tab-pane name="env" tab="环境变量">
<n-space vertical>
<n-alert type="warning">
此处修改的是 Minio 环境变量文件
/etc/default/minio
</n-alert>
<Editor
v-model:value="env"
language="ini"
theme="vs-dark"
height="60vh"
mt-8
:options="{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true
}"
/>
</n-space>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="minio" />
</n-tab-pane>
</n-tabs>
</common-page>
</template>
+23
View File
@@ -0,0 +1,23 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'minio',
path: '/apps/minio',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-minio-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'Minio',
icon: 'simple-icons:minio',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType
+1 -11
View File
@@ -104,12 +104,6 @@ const handleRestart = async () => {
await getStatus()
}
const handleReload = async () => {
await systemctl.reload('mysqld')
window.$message.success('重载成功')
await getStatus()
}
const handleSetRootPassword = async () => {
await mysql.setRootPassword(rootPassword.value)
window.$message.success('修改成功')
@@ -184,10 +178,6 @@ onMounted(() => {
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
重启
</n-button>
<n-button type="primary" @click="handleReload">
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
重载
</n-button>
</n-space>
</n-space>
</n-card>
@@ -232,7 +222,7 @@ onMounted(() => {
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="error-log" tab="错误日志">
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="mysqld" />
</n-tab-pane>
<n-tab-pane name="slow-log" tab="慢查询日志">
+3
View File
@@ -198,6 +198,9 @@ onMounted(() => {
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="nginx" />
</n-tab-pane>
<n-tab-pane name="error-log" tab="错误日志">
<realtime-log :path="errorLog" />
</n-tab-pane>
+3
View File
@@ -377,6 +377,9 @@ onMounted(() => {
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log :service="'php-fpm-' + version" />
</n-tab-pane>
<n-tab-pane name="error-log" tab="错误日志">
<realtime-log :path="errorLog" />
</n-tab-pane>
+3
View File
@@ -180,6 +180,9 @@ onMounted(() => {
/>
</n-space>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="podman" />
</n-tab-pane>
</n-tabs>
</common-page>
</template>
+4 -1
View File
@@ -228,7 +228,10 @@ onMounted(() => {
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="log" tab="日志">
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="postgresql" />
</n-tab-pane>
<n-tab-pane name="slow-log" tab="慢日志">
<realtime-log :path="log" />
</n-tab-pane>
</n-tabs>
@@ -281,6 +281,9 @@ onMounted(() => {
/>
</n-card>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="pure-ftpd" />
</n-tab-pane>
</n-tabs>
</common-page>
<n-modal v-model:show="addUserModal" title="创建用户">
+3
View File
@@ -171,6 +171,9 @@ onMounted(() => {
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="redis" />
</n-tab-pane>
</n-tabs>
</common-page>
</template>
+3
View File
@@ -335,6 +335,9 @@ onMounted(() => {
/>
</n-space>
</n-tab-pane>
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="rsyncd" />
</n-tab-pane>
</n-tabs>
</common-page>
<n-modal
+4 -1
View File
@@ -453,7 +453,10 @@ onUnmounted(() => {
/>
</n-space>
</n-tab-pane>
<n-tab-pane name="log" tab="日志">
<n-tab-pane name="run-log" tab="运行日志">
<realtime-log service="supervisor" />
</n-tab-pane>
<n-tab-pane name="log" tab="守护日志">
<realtime-log path="/var/log/supervisor/supervisord.log" />
</n-tab-pane>
</n-tabs>
+20 -54
View File
@@ -3,79 +3,45 @@ defineOptions({
name: 'backup-index'
})
import backup from '@/api/panel/backup'
import dashboard from '@/api/panel/dashboard'
import ListView from '@/views/backup/ListView.vue'
import { NButton, NInput } from 'naive-ui'
const currentTab = ref('website')
const createModal = ref(false)
const createModel = ref({
target: '',
path: ''
const { data: installedDbAndPhp } = useRequest(dashboard.installedDbAndPhp, {
initialData: {
db: [
{
label: '',
value: ''
}
]
}
})
const handleCreate = () => {
useRequest(
backup.create(currentTab.value, createModel.value.target, createModel.value.path)
).onSuccess(() => {
createModal.value = false
window.$bus.emit('backup:refresh')
window.$message.success('创建成功')
})
}
const mySQLInstalled = computed(() => {
return installedDbAndPhp.value.db.find((item: any) => item.value === 'mysql')
})
const postgreSQLInstalled = computed(() => {
return installedDbAndPhp.value.db.find((item: any) => item.value === 'postgresql')
})
</script>
<template>
<common-page show-footer>
<template #action>
<n-button type="primary" @click="createModal = true">
<TheIcon :size="18" icon="material-symbols:add" />
创建备份
</n-button>
</template>
<n-flex vertical>
<n-alert type="info">该页面预计下版本移除</n-alert>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="website" tab="网站">
<list-view v-model:type="currentTab" />
</n-tab-pane>
<n-tab-pane name="mysql" tab="MySQL">
<n-tab-pane v-if="mySQLInstalled" name="mysql" tab="MySQL">
<list-view v-model:type="currentTab" />
</n-tab-pane>
<n-tab-pane name="postgres" tab="PostgreSQL">
<n-tab-pane v-if="postgreSQLInstalled" name="postgres" tab="PostgreSQL">
<list-view v-model:type="currentTab" />
</n-tab-pane>
</n-tabs>
</n-flex>
</common-page>
<n-modal
v-model:show="createModal"
preset="card"
title="创建备份"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="createModal = false"
>
<n-form :model="createModel">
<n-form-item path="name" label="名称">
<n-input
v-model:value="createModel.target"
type="text"
@keydown.enter.prevent
placeholder="输入网站/数据库名称"
/>
</n-form-item>
<n-form-item path="path" label="目录">
<n-input
v-model:value="createModel.path"
type="text"
@keydown.enter.prevent
placeholder="留空使用默认路径"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
</n-modal>
</template>
+106 -25
View File
@@ -1,20 +1,34 @@
<script setup lang="ts">
import backup from '@/api/panel/backup'
import { renderIcon } from '@/utils'
import { MessageReactive, NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
import type { MessageReactive } from 'naive-ui'
import { NButton, NDataTable, NFlex, NInput, NPopconfirm } from 'naive-ui'
import app from '@/api/panel/app'
import website from '@/api/panel/website'
import { formatDateTime } from '@/utils'
import UploadModal from '@/views/backup/UploadModal.vue'
const type = defineModel<string>('type', { type: String, required: true })
let messageReactive: MessageReactive | null = null
const uploadModal = ref(false)
const createModal = ref(false)
const createModel = ref({
target: '',
path: ''
})
const restoreModal = ref(false)
const restoreModel = ref({
file: '',
target: ''
})
const websites = ref<any>([])
const columns: any = [
{
title: '文件名',
@@ -102,6 +116,16 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
}
)
const handleCreate = () => {
useRequest(backup.create(type.value, createModel.value.target, createModel.value.path)).onSuccess(
() => {
createModal.value = false
window.$bus.emit('backup:refresh')
window.$message.success('创建成功')
}
)
}
const handleRestore = () => {
messageReactive = window.$message.loading('恢复中...', {
duration: 0
@@ -125,10 +149,24 @@ const handleDelete = async (file: string) => {
}
onMounted(() => {
refresh()
window.$bus.on('backup:refresh', () => {
refresh()
useRequest(app.isInstalled('nginx')).onSuccess(({ data }) => {
if (data.installed) {
useRequest(website.list(1, 10000)).onSuccess(({ data }: { data: any }) => {
for (const item of data.items) {
websites.value.push({
label: item.name,
value: item.name
})
}
if (type.value === 'website') {
createModel.value.target = websites.value[0]?.value
restoreModel.value.target = websites.value[0]?.value
}
})
}
})
refresh()
window.$bus.on('backup:refresh', refresh)
})
onUnmounted(() => {
@@ -137,26 +175,65 @@ onUnmounted(() => {
</script>
<template>
<n-data-table
striped
remote
:scroll-x="1000"
:loading="loading"
:columns="columns"
:data="data"
:row-key="(row: any) => row.name"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="createModal = true"> 创建备份 </n-button>
<n-button type="primary" @click="uploadModal = true" ghost> 上传备份 </n-button>
</n-flex>
<n-data-table
striped
remote
:scroll-x="1000"
:loading="loading"
:columns="columns"
:data="data"
:row-key="(row: any) => row.name"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
</n-flex>
<n-modal
v-model:show="createModal"
preset="card"
title="创建备份"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="createModal = false"
>
<n-form :model="createModel">
<n-form-item v-if="type == 'website'" path="name" label="网站">
<n-select v-model:value="createModel.target" :options="websites" placeholder="选择网站" />
</n-form-item>
<n-form-item v-if="type != 'website'" path="name" label="数据库名">
<n-input
v-model:value="createModel.target"
type="text"
@keydown.enter.prevent
placeholder="输入数据库名称"
/>
</n-form-item>
<n-form-item path="path" label="保存目录">
<n-input
v-model:value="createModel.path"
type="text"
@keydown.enter.prevent
placeholder="留空使用默认路径"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
</n-modal>
<n-modal
v-model:show="restoreModal"
preset="card"
@@ -168,12 +245,16 @@ onUnmounted(() => {
@close="restoreModal = false"
>
<n-form :model="restoreModel">
<n-form-item path="name" label="恢复目标">
<n-form-item v-if="type == 'website'" path="name" label="网站">
<n-select v-model:value="restoreModel.target" :options="websites" placeholder="选择网站" />
</n-form-item>
<n-form-item v-if="type != 'website'" path="name" label="数据库">
<n-input v-model:value="restoreModel.target" type="text" @keydown.enter.prevent />
</n-form-item>
</n-form>
<n-button type="info" block @click="handleRestore">提交</n-button>
</n-modal>
<upload-modal v-model:show="uploadModal" v-model:type="type" />
</template>
<style scoped lang="scss"></style>
+55
View File
@@ -0,0 +1,55 @@
<script setup lang="ts">
import type { UploadCustomRequestOptions } from 'naive-ui'
import api from '@/api/panel/backup'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const type = defineModel<string>('type', { type: String, required: true })
const upload = ref<any>(null)
const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequestOptions) => {
const formData = new FormData()
formData.append('file', file.file as File)
const { uploading } = useRequest(api.upload(type.value, formData))
.onSuccess(() => {
onFinish()
window.$bus.emit('backup:refresh')
window.$message.success(`上传 ${file.name} 成功`)
})
.onError(() => {
onError()
})
.onComplete(() => {
stopWatch()
})
const stopWatch = watch(uploading, (progress) => {
onProgress({ percent: Math.ceil((progress.loaded / progress.total) * 100) })
})
}
</script>
<template>
<n-modal
v-model:show="show"
preset="card"
title="上传备份"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-flex vertical>
<n-upload ref="upload" multiple directory-dnd :custom-request="uploadRequest">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<the-icon :size="48" icon="bi:arrow-up-square" />
</div>
<NText text-18> 点击或者拖动文件到该区域来上传</NText>
<NP depth="3" m-10> 大文件建议使用 SFTP 等方式上传 </NP>
</n-upload-dragger>
</n-upload>
</n-flex>
</n-modal>
</template>
<style scoped lang="scss"></style>
+17 -6
View File
@@ -38,7 +38,8 @@ const updateModel = ref<any>({
website_id: null,
auto_renew: true,
cert: '',
key: ''
key: '',
script: ''
})
const updateModal = ref(false)
const updateCert = ref<any>()
@@ -278,6 +279,7 @@ const columns: any = [
updateModel.value.auto_renew = row.auto_renew
updateModel.value.cert = row.cert
updateModel.value.key = row.key
updateModel.value.script = row.script
updateModal.value = true
}
},
@@ -341,6 +343,7 @@ const handleUpdateCert = () => {
updateModel.value.auto_renew = true
updateModel.value.cert = ''
updateModel.value.key = ''
updateModel.value.script = ''
window.$message.success('更新成功')
})
}
@@ -354,6 +357,7 @@ const handleAutoRenewUpdate = (row: any) => {
updateModel.value.auto_renew = !row.auto_renew
updateModel.value.cert = row.cert
updateModel.value.key = row.key
updateModel.value.script = row.script
useRequest(cert.certUpdate(row.id, updateModel.value))
.onSuccess(() => {
refresh()
@@ -368,21 +372,20 @@ const handleAutoRenewUpdate = (row: any) => {
updateModel.value.auto_renew = true
updateModel.value.cert = ''
updateModel.value.key = ''
updateModel.value.script = ''
})
}
const handleDeployCert = async () => {
const promises = deployModel.value.websites.map((website: any) =>
useRequest(cert.deploy(deployModel.value.id, website)).onSuccess(() => {
window.$message.success(`部署网站 ${website.name} 成功`)
})
cert.deploy(deployModel.value.id, website)
)
await Promise.all(promises)
deployModal.value = false
deployModel.value.id = null
deployModel.value.websites = []
window.$message.success('部署成功')
}
const handleShowModalClose = () => {
@@ -437,7 +440,7 @@ onUnmounted(() => {
<n-space vertical>
<n-alert v-if="updateModel.type != 'upload'" type="info">
可以通过选择网站 / DNS 中的任意一项来自动签发和部署证书,也可以手动输入域名并设置 DNS
解析来签发证书
解析来签发证书,还可以填写部署脚本来自动部署证书。
</n-alert>
<n-form :model="updateModel">
<n-form-item v-if="updateModel.type != 'upload'" path="domains" label="域名">
@@ -496,6 +499,14 @@ onUnmounted(() => {
:autosize="{ minRows: 10, maxRows: 15 }"
/>
</n-form-item>
<n-form-item v-if="updateModel.type != 'upload'" path="key" label="部署脚本">
<n-input
v-model:value="updateModel.script"
type="textarea"
placeholder="脚本中的 {cert} {key} 会被替换为证书和私钥内容"
:autosize="{ minRows: 5, maxRows: 10 }"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleUpdateCert">提交</n-button>
</n-space>
+357
View File
@@ -0,0 +1,357 @@
<script setup lang="ts">
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
import container from '@/api/panel/container'
import { useFileStore } from '@/store'
import { formatDateTime } from '@/utils'
const fileStore = useFileStore()
const router = useRouter()
const forcePush = ref(false)
const createModel = ref({
name: '',
compose: '',
envs: []
})
const createModal = ref(false)
const updateModel = ref({
name: '',
compose: '',
envs: []
})
const updateModal = ref(false)
const columns: any = [
{
title: '名称',
key: 'name',
minWidth: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '目录',
key: 'path',
minWidth: 150,
resizable: true,
render(row: any) {
return h(
NTag,
{
class: 'cursor-pointer hover:opacity-60',
type: 'info',
onClick: () => {
fileStore.path = row.path
router.push({ name: 'file-index' })
}
},
{ default: () => row.path }
)
}
},
{
title: '状态',
key: 'status',
width: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '创建时间',
key: 'created_at',
width: 200,
resizable: true,
render(row: any) {
return formatDateTime(row.created_at)
}
},
{
title: '操作',
key: 'actions',
width: 280,
align: 'center',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
onClick: () => {
useRequest(container.composeGet(row.name)).onSuccess(({ data }: { data: any }) => {
updateModel.value = {
name: row.name,
compose: data.compose,
envs: data.envs
}
updateModal.value = true
})
}
},
{
default: () => '编辑'
}
),
h(
NPopconfirm,
{
showIcon: false,
onPositiveClick: () => {
const messageReactive = window.$message.loading('启动中...', {
duration: 0
})
useRequest(container.composeUp(row.name, forcePush.value))
.onSuccess(() => {
refresh()
forcePush.value = false
window.$message.success('启动成功')
})
.onComplete(() => {
messageReactive?.destroy()
})
}
},
{
default: () => {
return h(
NFlex,
{
vertical: true
},
{
default: () => [
h('strong', {}, { default: () => `确定启动编排 ${row.name} 吗?` }),
h(
NCheckbox,
{
checked: forcePush.value,
onUpdateChecked: (v) => (forcePush.value = v)
},
{ default: () => '强制拉取镜像' }
)
]
}
)
},
trigger: () => {
return h(
NButton,
{
style: 'margin-left: 15px;',
size: 'small',
type: 'success'
},
{
default: () => '启动'
}
)
}
}
),
h(
NPopconfirm,
{
onPositiveClick: () => {
useRequest(container.composeDown(row.name)).onSuccess(() => {
refresh()
window.$message.success('停止成功')
})
}
},
{
default: () => {
return `确定停止编排 ${row.name} 吗?`
},
trigger: () => {
return h(
NButton,
{
style: 'margin-left: 15px;',
size: 'small',
type: 'warning'
},
{
default: () => '停止'
}
)
}
}
),
h(
NPopconfirm,
{
onPositiveClick: () => {
useRequest(container.composeRemove(row.name)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
})
}
},
{
default: () => {
return `确定删除编排 ${row.name} 吗?`
},
trigger: () => {
return h(
NButton,
{
style: 'margin-left: 15px;',
size: 'small',
type: 'error'
},
{
default: () => '删除'
}
)
}
}
)
]
}
}
]
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => container.composeList(page, pageSize),
{
initialData: { total: 0, list: [] },
initialPageSize: 20,
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
const handleCreate = () => {
loading.value = true
useRequest(container.composeCreate(createModel.value))
.onSuccess(() => {
refresh()
window.$message.success('创建成功')
})
.onComplete(() => {
loading.value = false
createModal.value = false
createModel.value = {
name: '',
compose: '',
envs: []
}
})
}
const handleUpdate = () => {
loading.value = true
useRequest(container.composeUpdate(updateModel.value.name, updateModel.value))
.onSuccess(() => {
refresh()
window.$message.success('更新成功')
})
.onComplete(() => {
loading.value = false
updateModal.value = false
updateModel.value = {
name: '',
compose: '',
envs: []
}
})
}
onMounted(() => {
refresh()
})
</script>
<template>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="createModal = true">创建编排</n-button>
</n-flex>
<n-data-table
striped
remote
:loading="loading"
:scroll-x="1000"
:data="data"
:columns="columns"
:row-key="(row: any) => row.id"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
</n-flex>
<n-modal
v-model:show="createModal"
preset="card"
title="创建编排"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="createModel">
<n-form-item path="name" label="编排名">
<n-input v-model:value="createModel.name" type="text" />
</n-form-item>
<n-form-item path="compose" label="编排">
<n-input
v-model:value="createModel.compose"
type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"
/>
</n-form-item>
<n-form-item path="envs" label="环境变量">
<n-dynamic-input
v-model:value="createModel.envs"
preset="pair"
key-placeholder="变量名"
value-placeholder="变量值"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleCreate">
提交
</n-button>
</n-modal>
<n-modal
v-model:show="updateModal"
preset="card"
title="编辑编排"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="updateModel">
<n-form-item path="compose" label="编排">
<n-input
v-model:value="updateModel.compose"
type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"
/>
</n-form-item>
<n-form-item path="envs" label="环境变量">
<n-dynamic-input
v-model:value="updateModel.envs"
preset="pair"
key-placeholder="变量名"
value-placeholder="变量值"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleUpdate">
提交
</n-button>
</n-modal>
</template>
+3 -3
View File
@@ -335,11 +335,11 @@ onMounted(() => {
<n-dynamic-input
v-model:value="createModel.env"
preset="pair"
key-placeholder="环境变量名"
value-placeholder="环境变量值"
key-placeholder="变量名"
value-placeholder="变量值"
/>
</n-form-item>
<n-form-item path="env" label="标签">
<n-form-item path="labels" label="标签">
<n-dynamic-input
v-model:value="createModel.labels"
preset="pair"
+21 -56
View File
@@ -291,17 +291,12 @@ const bulkStart = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerStart(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 启动成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerStart(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('启动成功')
}
const bulkStop = async () => {
@@ -310,17 +305,12 @@ const bulkStop = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerStop(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 停止成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerStop(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('停止成功')
}
const bulkRestart = async () => {
@@ -329,17 +319,12 @@ const bulkRestart = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerRestart(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 重启成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerRestart(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('重启成功')
}
const bulkForceStop = async () => {
@@ -348,17 +333,12 @@ const bulkForceStop = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerKill(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 强制停止成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerKill(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('强制停止成功')
}
const bulkDelete = async () => {
@@ -367,17 +347,12 @@ const bulkDelete = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerRemove(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 删除成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerRemove(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('删除成功')
}
const bulkPause = async () => {
@@ -386,17 +361,12 @@ const bulkPause = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerPause(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 暂停成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerPause(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('暂停成功')
}
const bulkUnpause = async () => {
@@ -405,17 +375,12 @@ const bulkUnpause = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) =>
useRequest(container.containerUnpause(id)).onSuccess(() => {
const container = data.value.find((item: any) => item.id === id)
window.$message.success(`${container?.name} 恢复成功`)
})
)
const promises = selectedRowKeys.value.map((id: any) => container.containerUnpause(id))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('恢复成功')
}
const closeContainerCreateModal = () => {
+5
View File
@@ -1,4 +1,6 @@
<script setup lang="ts">
import ComposeView from '@/views/container/ComposeView.vue'
defineOptions({
name: 'container-index'
})
@@ -17,6 +19,9 @@ const current = ref('container')
<n-tab-pane name="container" tab="容器">
<container-view />
</n-tab-pane>
<n-tab-pane name="compose" tab="编排">
<compose-view />
</n-tab-pane>
<n-tab-pane name="image" tab="镜像">
<image-view />
</n-tab-pane>
+1
View File
@@ -152,6 +152,7 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
const handleDelete = (row: any) => {
useRequest(container.networkRemove(row.id)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
})
}
+9 -4
View File
@@ -438,7 +438,7 @@ if (import.meta.hot) {
<n-card :segmented="true" size="small" title="资源总览">
<n-flex v-if="realtime" size="large">
<n-popover trigger="hover">
<n-popover placement="bottom" trigger="hover">
<template #trigger>
<n-flex vertical flex items-center p-20 pl-40 pr-40>
<p>负载状态</p>
@@ -475,7 +475,7 @@ if (import.meta.hot) {
</tr>
</n-table>
</n-popover>
<n-popover trigger="hover">
<n-popover placement="bottom" trigger="hover">
<template #trigger>
<n-flex vertical flex items-center p-20 pl-40 pr-40>
<p>CPU</p>
@@ -508,7 +508,7 @@ if (import.meta.hot) {
</tr>
</n-table>
</n-popover>
<n-popover trigger="hover">
<n-popover placement="bottom" trigger="hover">
<template #trigger>
<n-flex vertical flex items-center p-20 pl-40 pr-40>
<p>内存</p>
@@ -602,7 +602,12 @@ if (import.meta.hot) {
</tr>
</n-table>
</n-popover>
<n-popover v-for="item in realtime.disk_usage" :key="item.path" trigger="hover">
<n-popover
v-for="item in realtime.disk_usage"
:key="item.path"
placement="bottom"
trigger="hover"
>
<template #trigger>
<n-flex vertical flex items-center p-20 pl-40 pr-40>
<p>{{ item.path }}</p>
+4 -2
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NButton, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import database from '@/api/panel/database'
@@ -69,8 +70,9 @@ const columns: any = [
type: 'primary',
ghost: true,
onClick: () => {
navigator.clipboard.writeText(row.password)
window.$message.success('复制成功')
copy2clipboard(row.password).then(() => {
window.$message.success('复制成功')
})
}
},
{ default: () => '复制' }
+4 -2
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NButton, NFlex, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import database from '@/api/panel/database'
@@ -63,8 +64,9 @@ const columns: any = [
type: 'primary',
ghost: true,
onClick: () => {
navigator.clipboard.writeText(row.password)
window.$message.success('复制成功')
copy2clipboard(row.password).then(() => {
window.$message.success('复制成功')
})
}
},
{ default: () => '复制' }
+2 -4
View File
@@ -17,16 +17,14 @@ const checkbox = ref({
const handlePermission = async () => {
const promises = selected.value.map((path) =>
useRequest(file.permission(path, `0${mode.value}`, owner.value, group.value)).onSuccess(() => {
window.$message.success(`修改 ${path} 成功`)
})
file.permission(path, `0${mode.value}`, owner.value, group.value)
)
await Promise.all(promises)
show.value = false
selected.value = []
window.$bus.emit('file:refresh')
window.$message.success('修改成功')
}
const calculateOctal = (permissions: string[]) => {
+4 -2
View File
@@ -2,6 +2,7 @@
import file from '@/api/panel/file'
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
import copy2clipboard from '@vavt/copy2clipboard'
import type { DataTableColumns } from 'naive-ui'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
@@ -58,8 +59,9 @@ const columns: DataTableColumns<RowData> = [
type: 'success',
tertiary: true,
onClick: () => {
navigator.clipboard.writeText(row.full)
window.$message.success('复制成功')
copy2clipboard(row.full).then(() => {
window.$message.success('复制成功')
})
}
},
{
+2 -6
View File
@@ -170,16 +170,12 @@ const bulkDelete = async () => {
return
}
const promises = selected.value.map((path) =>
useRequest(file.delete(path)).onSuccess(() => {
window.$message.success(`删除 ${path} 成功`)
})
)
const promises = selected.value.map((path) => file.delete(path))
await Promise.all(promises)
selected.value = []
window.$bus.emit('file:refresh')
window.$message.success('删除成功')
}
// 自动填充下载文件名
+1 -7
View File
@@ -40,13 +40,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
:segmented="false"
>
<n-flex vertical>
<n-upload
ref="upload"
multiple
directory-dnd
action="/api/panel/file/upload"
:custom-request="uploadRequest"
>
<n-upload ref="upload" multiple directory-dnd :custom-request="uploadRequest">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<the-icon :size="48" icon="bi:arrow-up-square" />
+3 -5
View File
@@ -139,15 +139,13 @@ const batchDelete = async () => {
const promises = selectedRowKeys.value.map((key: any) => {
const rule = JSON.parse(key)
return useRequest(firewall.deleteForward(rule)).onSuccess(() => {
window.$message.success(`${rule.protocol} ${rule.target_ip}:${rule.target_port} 删除成功`)
})
return firewall.deleteForward(rule)
})
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('删除成功')
}
watch(createModalShow, () => {
+3 -5
View File
@@ -181,15 +181,13 @@ const batchDelete = async () => {
const promises = selectedRowKeys.value.map((key: any) => {
const rule = JSON.parse(key)
return useRequest(firewall.deleteIpRule(rule)).onSuccess(() => {
window.$message.success(`${rule.address} 删除成功`)
})
return firewall.deleteIpRule(rule)
})
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('删除成功')
}
watch(createModalShow, () => {
+3 -7
View File
@@ -218,17 +218,13 @@ const batchDelete = async () => {
const promises = selectedRowKeys.value.map((key: any) => {
const rule = JSON.parse(key)
return useRequest(firewall.deleteRule(rule)).onSuccess(() => {
const port =
rule.port_start == rule.port_end ? rule.port_start : `${rule.port_start}-${rule.port_end}`
window.$message.success(`${rule.family} 规则 ${port}/${rule.protocol} 删除成功`)
})
return firewall.deleteRule(rule)
})
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('删除成功')
}
watch(createModalShow, () => {
+1 -1
View File
@@ -151,7 +151,7 @@ const openSession = async (id: number) => {
})
term.value.open(terminal.value!)
fitAddon.fit()
onResize()
term.value.focus()
window.addEventListener('resize', onResize, false)
current.value = id
+10 -19
View File
@@ -25,12 +25,6 @@ const websites = ref<any>([])
const { data: installedDbAndPhp } = useRequest(dashboard.installedDbAndPhp, {
initialData: {
php: [
{
label: '不使用',
value: 0
}
],
db: [
{
label: '',
@@ -48,17 +42,6 @@ const postgreSQLInstalled = computed(() => {
return installedDbAndPhp.value.db.find((item: any) => item.value === 'postgresql')
})
const getWebsiteList = async (page: number, limit: number) => {
const data = await website.list(page, limit)
for (const item of data.items) {
websites.value.push({
label: item.name,
value: item.name
})
}
createModel.value.target = websites.value[0]?.value
}
const handleSubmit = async () => {
loading.value = true
useRequest(cron.create(createModel.value))
@@ -83,7 +66,15 @@ watch(createModel, (value) => {
onMounted(() => {
useRequest(app.isInstalled('nginx')).onSuccess(({ data }) => {
if (data.installed) {
getWebsiteList(1, 10000)
useRequest(website.list(1, 10000)).onSuccess(({ data }: { data: any }) => {
for (const item of data.items) {
websites.value.push({
label: item.name,
value: item.name
})
}
createModel.value.target = websites.value[0]?.value
})
}
})
})
@@ -134,7 +125,7 @@ onMounted(() => {
</div>
<n-form-item v-if="createModel.type === 'backup'" label="备份类型">
<n-radio-group v-model:value="createModel.backup_type">
<n-radio value="website">网站目录</n-radio>
<n-radio value="website">网站</n-radio>
<n-radio value="mysql" :disabled="!mySQLInstalled"> MySQL 数据库</n-radio>
<n-radio value="postgres" :disabled="!postgreSQLInstalled"> PostgreSQL 数据库 </n-radio>
</n-radio-group>
+3 -8
View File
@@ -303,17 +303,12 @@ const bulkDelete = async () => {
return
}
const promises = selectedRowKeys.value.map((id: any) => {
const site = data.value.find((item: any) => item.id === id)
return useRequest(website.delete(id, true, false)).onSuccess(() => {
window.$message.success('网站 ' + site?.name + ' 删除成功')
})
})
const promises = selectedRowKeys.value.map((id: any) => website.delete(id, true, false))
await Promise.all(promises)
selectedRowKeys.value = []
await refresh()
refresh()
window.$message.success('删除成功')
}
const formatDbValue = (value: string) => {