Compare commits

..

192 Commits

Author SHA1 Message Date
耗子 ecc44b2e92 fix: fuse文件系统没有inode 2025-03-29 17:03:05 +08:00
耗子 2b1e21ba4b fix: test 2025-03-29 15:04:00 +08:00
耗子 7a22469fb1 chore: update deps 2025-03-29 15:02:15 +08:00
耗子 31c063ba68 feat: acme日志记录 2025-03-29 14:57:35 +08:00
耗子 dad868b64c fix: 重写迁移 2025-03-29 14:44:56 +08:00
耗子 16de31c838 Merge remote-tracking branch 'origin/main' 2025-03-29 14:38:28 +08:00
耗子 3c1062a006 fix: 严格所有字段的默认值 2025-03-29 14:38:22 +08:00
renovate[bot] 7017aff841 chore(deps): Update module github.com/go-gormigrate/gormigrate/v2 to v2.1.4 (#640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 23:01:02 +00:00
耗子 334c4bc4c1 chore: update renovate.json 2025-03-29 03:31:51 +08:00
耗子 e07a69ab67 fix: lint 2025-03-28 22:39:35 +08:00
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
耗子 a72eb2ba43 feat: 优化文案 2025-02-09 23:37:21 +08:00
耗子 6cff72de98 feat: 优化 2025-02-09 23:11:05 +08:00
耗子 89be5e077a feat: 证书支持关闭自动续签 2025-02-09 22:41:50 +08:00
耗子 3e1caa4f2e fix: lint 2025-02-09 22:19:14 +08:00
耗子 459b71e8cb feat: 全局去除圆角 2025-02-09 22:13:23 +08:00
耗子 8c8a957df1 feat: 前端样式优化 2025-02-09 22:06:07 +08:00
耗子 9648b24d58 Merge remote-tracking branch 'origin/main' 2025-02-09 21:27:10 +08:00
耗子 b5752ac9e5 feat: 路径选择器 close #332 2025-02-09 21:27:00 +08:00
renovate[bot] 6e8cb1d513 chore(deps): Update non-major dependencies (#529)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-09 12:23:49 +00:00
耗子 191c3d8b3f feat: 修正部分链接 2025-02-09 20:19:20 +08:00
耗子 657382f929 feat: 优化一键签发证书 2025-02-09 16:52:55 +08:00
耗子 a64ec36744 feat: 优化代码 2025-02-09 16:38:47 +08:00
耗子 c527af97ca feat: 优化已登录自动跳转仪表盘 2025-02-09 15:51:42 +08:00
耗子 2293646c4c feat: close #527 2025-02-09 04:48:58 +08:00
耗子 23e215b009 feat: alova.js替换axios 2025-02-09 04:42:44 +08:00
耗子 a07c50b3f5 feat: alova.js替换axios 2025-02-09 04:19:44 +08:00
耗子 9956824d54 feat: alova.js替换axios 2025-02-09 03:09:09 +08:00
耗子 6ac5e63684 feat: alova.js替换axios 2025-02-09 02:10:17 +08:00
耗子 23b13ff79d Merge remote-tracking branch 'origin/main' 2025-02-09 02:03:06 +08:00
耗子 4a7742b991 feat: alova.js替换axios 2025-02-09 02:02:55 +08:00
renovate[bot] 377b904459 chore(deps): Update dependency @iconify/json to v2.2.304 (#526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 08:48:33 +00:00
renovate[bot] b30d476a59 chore(deps): Update module golang.org/x/crypto to v0.33.0 (#525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 02:12:52 +00:00
renovate[bot] 3e7629b5b1 chore(deps): Update dependency eslint to v9.20.0 (#524)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-07 21:53:47 +00:00
renovate[bot] 32bda21196 chore(deps): Update dependency md-editor-v3 to v5.2.2 (#523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-07 08:49:03 +00:00
耗子 d27a29151e feat: alova.js替换axios 2025-02-06 18:18:07 +08:00
renovate[bot] 2b74b35701 chore(deps): Update dependency sass to v1.84.0 (#522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 01:42:03 +00:00
耗子 30b1cf28cb chore: update issue template 2025-02-06 01:58:53 +08:00
耗子 2d17de5266 feat: update goreleaser config 2025-02-06 01:35:18 +08:00
195 changed files with 5413 additions and 4604 deletions
+3 -4
View File
@@ -1,7 +1,6 @@
name: 🐛 报告问题 (Bug Report)
name: ☢️ 报告问题 (Bug Report)
description: 创建一个报告以帮助我们改进 (Create a report to help us improve)
title: "🐛 [Bug] "
labels: [ "☢️ Bug", "bug" ]
type: ☢️ Bug
body:
- type: markdown
@@ -42,7 +41,7 @@ body:
description: |
请提供面板的版本号。
Please provide the version number of the panel.
placeholder: "2.3.0"
placeholder: "2.4.0"
validations:
required: true
- type: textarea
+1 -2
View File
@@ -1,7 +1,6 @@
name: ✨ 功能请求 (Feature Request)
description: 为这个项目提出一个想法 (Suggest an idea for this project)
title: "[Feature] "
labels: [ "✏️ Feature", "enhancement" ]
type: ✨ Feature
body:
- type: markdown
+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
@@ -60,7 +60,7 @@ archives:
builds:
- web
- cli
format: zip
formats: ["zip", "tar.gz", "tar.xz"]
wrap_in_directory: false
strip_binary_directory: true
files:
+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)
+5 -3
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"
@@ -63,8 +64,8 @@ func initCli() (*app.Cli, error) {
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
certRepo := data.NewCertRepo(db)
certAccountRepo := data.NewCertAccountRepo(db, userRepo)
certRepo := data.NewCertRepo(db, logger)
certAccountRepo := data.NewCertAccountRepo(db, userRepo, logger)
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
cliService := service.NewCliService(koanf, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo)
@@ -77,6 +78,7 @@ func initCli() (*app.Cli, error) {
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
memcachedApp := memcached.NewApp()
minioApp := minio.NewApp()
mysqlApp := mysql.NewApp(settingRepo)
nginxApp := nginx.NewApp()
php74App := php74.NewApp(taskRepo)
@@ -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, memcachedApp, minioApp, 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
}
+8 -4
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"
@@ -70,8 +71,8 @@ func initWeb() (*app.Web, error) {
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
certRepo := data.NewCertRepo(db)
certAccountRepo := data.NewCertAccountRepo(db, userRepo)
certRepo := data.NewCertRepo(db, logger)
certAccountRepo := data.NewCertAccountRepo(db, userRepo, logger)
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
cronRepo := data.NewCronRepo(db)
@@ -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()
@@ -114,6 +117,7 @@ func initWeb() (*app.Web, error) {
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
memcachedApp := memcached.NewApp()
minioApp := minio.NewApp()
mysqlApp := mysql.NewApp(settingRepo)
nginxApp := nginx.NewApp()
php74App := php74.NewApp(taskRepo)
@@ -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, memcachedApp, minioApp, 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)
+28 -38
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-gormigrate/gormigrate/v2 v2.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.32.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.29.0 // indirect
golang.org/x/text v0.21.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-20250329064837-dd8e7d30835a
+59 -94
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-gormigrate/gormigrate/v2 v2.1.4 h1:KOPEt27qy1cNzHfMZbp9YTmEuzkY4F4wrdsJW9WFk1U=
github.com/go-gormigrate/gormigrate/v2 v2.1.4/go.mod h1:y/6gPAH6QGAgP1UfHMiXcqGeJ88/GRQbfCReE1JJD5Y=
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-20250329064837-dd8e7d30835a h1:Qs7oWoFZO1CR9ui4NnwRVFtK3uNcLMYIlOHsikIBmUo=
github.com/tnb-labs/acmez/v3 v3.0.0-20250329064837-dd8e7d30835a/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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.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.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
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"
@@ -35,6 +36,7 @@ var ProviderSet = wire.NewSet(
frp.NewApp,
gitea.NewApp,
memcached.NewApp,
minio.NewApp,
mysql.NewApp,
nginx.NewApp,
php74.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
@@ -59,7 +59,7 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
service.Error(w, http.StatusInternalServerError, "重载服务失败%v", err)
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
return
}
+6 -6
View File
@@ -51,13 +51,13 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
return
}
if err := io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
return
}
if err := systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载服务失败")
if err = systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
return
}
@@ -84,13 +84,13 @@ func (s *App) UpdateUserConfig(w http.ResponseWriter, r *http.Request) {
return
}
if err := io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil {
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
return
}
if err := systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载服务失败")
if err = systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
return
}
+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 -1
View File
@@ -102,7 +102,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Restart(s.name); err != nil {
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败", s.name)
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", s.name, err)
return
}
+5 -5
View File
@@ -8,11 +8,11 @@ import (
type App struct {
ID uint `gorm:"primaryKey" json:"id"`
Slug string `gorm:"not null;unique" json:"slug"`
Channel string `gorm:"not null" json:"channel"`
Version string `gorm:"not null" json:"version"`
Show bool `gorm:"not null" json:"show"`
ShowOrder int `gorm:"not null" json:"show_order"`
Slug string `gorm:"not null;default:'';unique" json:"slug"`
Channel string `gorm:"not null;default:''" json:"channel"`
Version string `gorm:"not null;default:''" json:"version"`
Show bool `gorm:"not null;default:false" json:"show"`
ShowOrder int `gorm:"not null;default:0" json:"show_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+1 -1
View File
@@ -11,7 +11,7 @@ const (
type Cache struct {
Key CacheKey `gorm:"primaryKey" json:"key"`
Value string `gorm:"not null" json:"value"`
Value string `gorm:"not null;default:''" json:"value"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+10 -9
View File
@@ -10,15 +10,16 @@ import (
type Cert struct {
ID uint `gorm:"primaryKey" json:"id"`
AccountID uint `gorm:"not null" json:"account_id"` // 关联的 ACME 账户 ID
WebsiteID uint `gorm:"not null" json:"website_id"` // 关联的网站 ID
DNSID uint `gorm:"not null" json:"dns_id"` // 关联的 DNS ID
Type string `gorm:"not null" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
Domains []string `gorm:"not null;serializer:json" json:"domains"`
AutoRenew bool `gorm:"not null" json:"auto_renew"` // 自动续签
CertURL string `gorm:"not null" json:"cert_url"` // 证书 URL (续签时使用)
Cert string `gorm:"not null" json:"cert"` // 证书内容
Key string `gorm:"not null" json:"key"` // 私钥内容
AccountID uint `gorm:"not null;default:0" json:"account_id"` // 关联的 ACME 账户 ID
WebsiteID uint `gorm:"not null;default:0" json:"website_id"` // 关联的网站 ID
DNSID uint `gorm:"not null;default:0" json:"dns_id"` // 关联的 DNS ID
Type string `gorm:"not null;default:''" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
Domains []string `gorm:"not null;default:'[]';serializer:json" json:"domains"`
AutoRenew bool `gorm:"not null;default:false" json:"auto_renew"` // 自动续签
CertURL string `gorm:"not null;default:''" json:"cert_url"` // 证书 URL (续签时使用)
Cert string `gorm:"not null;default:''" json:"cert"` // 证书内容
Key string `gorm:"not null;default:''" json:"key"` // 私钥内容
Script string `gorm:"not null;default:''" json:"script"` // 部署脚本
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
+6 -6
View File
@@ -8,12 +8,12 @@ import (
type CertAccount struct {
ID uint `gorm:"primaryKey" json:"id"`
Email string `gorm:"not null" json:"email"`
CA string `gorm:"not null" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
Kid string `gorm:"not null" json:"kid"`
HmacEncoded string `gorm:"not null" json:"hmac_encoded"`
PrivateKey string `gorm:"not null" json:"private_key"`
KeyType string `gorm:"not null" json:"key_type"` // 密钥类型 (P256, P384, 2048, 3072, 4096)
Email string `gorm:"not null;default:''" json:"email"`
CA string `gorm:"not null;default:'letsencrypt'" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
Kid string `gorm:"not null;default:''" json:"kid"`
HmacEncoded string `gorm:"not null;default:''" json:"hmac_encoded"`
PrivateKey string `gorm:"not null;default:''" json:"private_key"`
KeyType string `gorm:"not null;default:'P256'" json:"key_type"` // 密钥类型 (P256, P384, 2048, 3072, 4096)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
+2 -2
View File
@@ -9,8 +9,8 @@ import (
type CertDNS struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null" json:"name"` // 备注名称
Type string `gorm:"not null" json:"type"` // DNS 提供商 (tencent, aliyun, cloudflare)
Name string `gorm:"not null;default:''" json:"name"` // 备注名称
Type string `gorm:"not null;default:'aliyun'" json:"type"` // DNS 提供商 (tencent, aliyun, cloudflare)
Data acme.DNSParam `gorm:"not null;serializer:json" json:"dns_param"`
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
}
+6 -6
View File
@@ -8,12 +8,12 @@ import (
type Cron struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;unique" json:"name"`
Status bool `gorm:"not null" json:"status"`
Type string `gorm:"not null" json:"type"`
Time string `gorm:"not null" json:"time"`
Shell string `gorm:"not null" json:"shell"`
Log string `gorm:"not null" json:"log"`
Name string `gorm:"not null;default:'';unique" json:"name"`
Status bool `gorm:"not null;default:false" json:"status"`
Type string `gorm:"not null;default:''" json:"type"`
Time string `gorm:"not null;default:''" json:"time"`
Shell string `gorm:"not null;default:''" json:"shell"`
Log string `gorm:"not null;default:''" json:"log"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+7 -7
View File
@@ -19,14 +19,14 @@ const (
type DatabaseServer struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;unique" json:"name"`
Type DatabaseType `gorm:"not null" json:"type"`
Host string `gorm:"not null" json:"host"`
Port uint `gorm:"not null" json:"port"`
Username string `gorm:"not null" json:"username"`
Password string `gorm:"not null" json:"password"`
Name string `gorm:"not null;default:'';unique" json:"name"`
Type DatabaseType `gorm:"not null;default:''" json:"type"`
Host string `gorm:"not null;default:''" json:"host"`
Port uint `gorm:"not null;default:0" json:"port"`
Username string `gorm:"not null;default:''" json:"username"`
Password string `gorm:"not null;default:''" json:"password"`
Status DatabaseServerStatus `gorm:"-:all" json:"status"`
Remark string `gorm:"not null" json:"remark"`
Remark string `gorm:"not null;default:''" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+7 -7
View File
@@ -19,13 +19,13 @@ const (
type DatabaseUser struct {
ID uint `gorm:"primaryKey" json:"id"`
ServerID uint `gorm:"not null" json:"server_id"`
Username string `gorm:"not null" json:"username"`
Password string `gorm:"not null" json:"password"`
Host string `gorm:"not null" json:"host"` // 仅 mysql
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
Privileges []string `gorm:"-:all" json:"privileges"` // 仅显示
Remark string `gorm:"not null" json:"remark"`
ServerID uint `gorm:"not null;default:0" json:"server_id"`
Username string `gorm:"not null;default:''" json:"username"`
Password string `gorm:"not null;default:''" json:"password"`
Host string `gorm:"not null;default:''" json:"host"` // 仅 mysql
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
Privileges []string `gorm:"-:all" json:"privileges"` // 仅显示
Remark string `gorm:"not null;default:''" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
+1 -1
View File
@@ -9,7 +9,7 @@ import (
type Monitor struct {
ID uint `gorm:"primaryKey" json:"id"`
Info types.CurrentInfo `gorm:"not null;serializer:json" json:"info"`
Info types.CurrentInfo `gorm:"not null;default:'{}';serializer:json" json:"info"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+2 -2
View File
@@ -23,8 +23,8 @@ const (
type Setting struct {
ID uint `gorm:"primaryKey" json:"id"`
Key SettingKey `gorm:"not null;unique" json:"key"`
Value string `gorm:"not null" json:"value"`
Key SettingKey `gorm:"not null;default:'';unique" json:"key"`
Value string `gorm:"not null;default:''" json:"value"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+5 -5
View File
@@ -13,11 +13,11 @@ import (
type SSH struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null" json:"name"`
Host string `gorm:"not null" json:"host"`
Port uint `gorm:"not null" json:"port"`
Config ssh.ClientConfig `gorm:"not null;serializer:json" json:"config"`
Remark string `gorm:"not null" json:"remark"`
Name string `gorm:"not null;default:''" json:"name"`
Host string `gorm:"not null;default:''" json:"host"`
Port uint `gorm:"not null;default:0" json:"port"`
Config ssh.ClientConfig `gorm:"not null;default:'{}';serializer:json" json:"config"`
Remark string `gorm:"not null;default:''" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+3 -3
View File
@@ -13,10 +13,10 @@ const (
type Task struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;index" json:"name"`
Name string `gorm:"not null;default:'';index" json:"name"`
Status TaskStatus `gorm:"not null;default:'waiting'" json:"status"`
Shell string `gorm:"not null" json:"-"`
Log string `gorm:"not null" json:"log"`
Shell string `gorm:"not null;default:''" json:"-"`
Log string `gorm:"not null;default:''" json:"log"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+3 -3
View File
@@ -8,9 +8,9 @@ import (
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Username string `gorm:"not null;unique" json:"username"`
Password string `gorm:"not null" json:"password"`
Email string `gorm:"not null" json:"email"`
Username string `gorm:"not null;default:'';unique" json:"username"`
Password string `gorm:"not null;default:''" json:"password"`
Email string `gorm:"not null;default:''" json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
+4 -4
View File
@@ -10,11 +10,11 @@ import (
type Website struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;unique" json:"name"`
Name string `gorm:"not null;default:'';unique" json:"name"`
Status bool `gorm:"not null;default:true" json:"status"`
Path string `gorm:"not null" json:"path"`
Https bool `gorm:"not null" json:"https"`
Remark string `gorm:"not null" json:"remark"`
Path string `gorm:"not null;default:''" json:"path"`
Https bool `gorm:"not null;default:false" json:"https"`
Remark string `gorm:"not null;default:''" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
+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"
@@ -34,6 +35,7 @@ func NewLoader(
frp *frp.App,
gitea *gitea.App,
memcached *memcached.App,
minio *minio.App,
mysql *mysql.App,
nginx *nginx.App,
php74 *php74.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, memcached, minio, 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 {
+6 -2
View File
@@ -502,9 +502,10 @@ func (r *backupRepo) preCheckPath(to, path string) error {
if uint64(size) > usage.Free {
return errors.New("备份目录空间不足")
}
if uint64(files) > usage.InodesFree {
// 对于 fuse 等文件系统,可能没有 inode 的概念
/*if uint64(files) > usage.InodesFree {
return errors.New("备份目录Inode不足")
}
}*/
return nil
}
@@ -833,6 +834,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
+53 -3
View File
@@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"slices"
"strings"
"time"
@@ -23,12 +25,14 @@ import (
type certRepo struct {
db *gorm.DB
log *slog.Logger
client *acme.Client
}
func NewCertRepo(db *gorm.DB) biz.CertRepo {
func NewCertRepo(db *gorm.DB, log *slog.Logger) biz.CertRepo {
return &certRepo{
db: db,
db: db,
log: log,
}
}
@@ -49,6 +53,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,
}
@@ -119,6 +124,9 @@ func (r *certRepo) Update(req *request.CertUpdate) error {
if err == nil && req.Type == "upload" {
req.Domains = info.DNSNames
}
if req.Type == "upload" && req.AutoRenew {
return errors.New("upload certificate cannot be set to auto renew")
}
return r.db.Model(&biz.Cert{}).Where("id = ?", req.ID).Select("*").Updates(&biz.Cert{
ID: req.ID,
@@ -128,6 +136,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
@@ -180,6 +189,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
}
@@ -209,6 +222,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
}
@@ -233,6 +250,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
}
@@ -341,6 +362,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")
@@ -367,5 +417,5 @@ func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
eab = &acme.EAB{KeyID: cert.Account.Kid, MACKey: cert.Account.HmacEncoded}
}
return acme.NewPrivateKeyAccount(cert.Account.Email, cert.Account.PrivateKey, ca, eab)
return acme.NewPrivateKeyAccount(cert.Account.Email, cert.Account.PrivateKey, ca, eab, r.log)
}
+16 -13
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/go-resty/resty/v2"
@@ -17,12 +18,14 @@ import (
type certAccountRepo struct {
db *gorm.DB
log *slog.Logger
user biz.UserRepo
}
func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo) biz.CertAccountRepo {
func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo, log *slog.Logger) biz.CertAccountRepo {
return &certAccountRepo{
db: db,
log: log,
user: user,
}
}
@@ -78,13 +81,13 @@ func (r certAccountRepo) Create(req *request.CertAccountCreate) (*biz.CertAccoun
}
account.Kid = eab.KeyID
account.HmacEncoded = eab.MACKey
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType), r.log)
case "google":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
case "letsencrypt":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType), r.log)
case "buypass":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType), r.log)
case "zerossl":
eab, eabErr := r.getZeroSSLEAB(account.Email)
if eabErr != nil {
@@ -92,9 +95,9 @@ func (r certAccountRepo) Create(req *request.CertAccountCreate) (*biz.CertAccoun
}
account.Kid = eab.KeyID
account.HmacEncoded = eab.MACKey
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType), r.log)
case "sslcom":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
default:
return nil, errors.New("unsupported CA")
}
@@ -137,13 +140,13 @@ func (r certAccountRepo) Update(req *request.CertAccountUpdate) error {
}
account.Kid = eab.KeyID
account.HmacEncoded = eab.MACKey
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType), r.log)
case "google":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
case "letsencrypt":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType), r.log)
case "buypass":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType), r.log)
case "zerossl":
eab, eabErr := r.getZeroSSLEAB(account.Email)
if eabErr != nil {
@@ -151,9 +154,9 @@ func (r certAccountRepo) Update(req *request.CertAccountUpdate) error {
}
account.Kid = eab.KeyID
account.HmacEncoded = eab.MACKey
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType), r.log)
case "sslcom":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
default:
return errors.New("unsupported CA")
}
+11 -11
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,
@@ -152,59 +152,59 @@ func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
// Remove 移除容器
func (r *containerRepo) Remove(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rm -f %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker rm -f %s", id)
return err
}
// Start 启动容器
func (r *containerRepo) Start(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker start %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker start %s", id)
return err
}
// Stop 停止容器
func (r *containerRepo) Stop(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker stop %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker stop %s", id)
return err
}
// Restart 重启容器
func (r *containerRepo) Restart(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker restart %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker restart %s", id)
return err
}
// Pause 暂停容器
func (r *containerRepo) Pause(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker pause %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker pause %s", id)
return err
}
// Unpause 恢复容器
func (r *containerRepo) Unpause(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker unpause %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker unpause %s", id)
return err
}
// Kill 杀死容器
func (r *containerRepo) Kill(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker kill %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker kill %s", id)
return err
}
// Rename 重命名容器
func (r *containerRepo) Rename(id string, newName string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rename %s %s", id, newName)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker rename %s %s", id, newName)
return err
}
// Logs 查看容器日志
func (r *containerRepo) Logs(id string) (string, error) {
return shell.ExecfWithTimeout(120*time.Second, "docker logs %s", id)
return shell.ExecfWithTimeout(2*time.Minute, "docker logs %s", id)
}
// Prune 清理未使用的容器
func (r *containerRepo) Prune() error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker container prune -f")
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker container prune -f")
return err
}
+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)
}
+2 -2
View File
@@ -77,12 +77,12 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
// Remove 删除镜像
func (r *containerImageRepo) Remove(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rmi %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker rmi %s", id)
return err
}
// Prune 清理未使用的镜像
func (r *containerImageRepo) Prune() error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker image prune -f")
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker image prune -f")
return err
}
+2 -2
View File
@@ -104,12 +104,12 @@ func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (stri
// Remove 删除网络
func (r *containerNetworkRepo) Remove(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network rm -f %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker network rm -f %s", id)
return err
}
// Prune 清理未使用的网络
func (r *containerNetworkRepo) Prune() error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network prune -f")
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker network prune -f")
return err
}
+2 -2
View File
@@ -78,12 +78,12 @@ func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string
// Remove 删除存储卷
func (r *containerVolumeRepo) Remove(id string) error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume rm -f %s", id)
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker volume rm -f %s", id)
return err
}
// Prune 清理未使用的存储卷
func (r *containerVolumeRepo) Prune() error {
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume prune -f")
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker volume prune -f")
return err
}
+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,
+7 -1
View File
@@ -186,7 +186,13 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
rewrite, _ := io.Read(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"))
setting.Rewrite = rewrite
// 访问日志
setting.Log = fmt.Sprintf("%s/wwwlogs/%s.log", app.Root, website.Name)
if setting.Log, err = p.GetAccessLog(); err != nil {
setting.Log = fmt.Sprintf("%s/wwwlogs/%s.log", app.Root, website.Name)
}
// 错误日志
if setting.ErrorLog, err = p.GetErrorLog(); err != nil {
setting.ErrorLog = fmt.Sprintf("%s/wwwlogs/%s.error.log", app.Root, website.Name)
}
return setting, err
}
+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))
+24
View File
@@ -69,4 +69,28 @@ func init() {
)
},
})
Migrations = append(Migrations, &gormigrate.Migration{
ID: "20250318-cert-script",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&biz.Cache{},
&biz.Cert{},
&biz.CertDNS{},
&biz.CertAccount{},
&biz.Cron{},
&biz.Monitor{},
&biz.App{},
&biz.Setting{},
&biz.Task{},
&biz.User{},
&biz.Website{},
&biz.SSH{},
&biz.DatabaseServer{},
&biz.DatabaseUser{},
)
},
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,
+8 -8
View File
@@ -23,7 +23,7 @@ func (s *SystemctlService) Status(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status(req.Service)
if err != nil {
Error(w, http.StatusInternalServerError, "获取 %s 服务运行状态失败", req.Service)
Error(w, http.StatusInternalServerError, "获取 %s 服务运行状态失败: %v", req.Service, err)
return
}
@@ -39,7 +39,7 @@ func (s *SystemctlService) IsEnabled(w http.ResponseWriter, r *http.Request) {
enabled, err := systemctl.IsEnabled(req.Service)
if err != nil {
Error(w, http.StatusInternalServerError, "获取 %s 服务启用状态失败", req.Service)
Error(w, http.StatusInternalServerError, "获取 %s 服务启用状态失败: %v", req.Service, err)
return
}
@@ -54,7 +54,7 @@ func (s *SystemctlService) Enable(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Enable(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "启用 %s 服务失败", req.Service)
Error(w, http.StatusInternalServerError, "启用 %s 服务失败: %v", req.Service, err)
return
}
@@ -69,7 +69,7 @@ func (s *SystemctlService) Disable(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Disable(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "禁用 %s 服务失败", req.Service)
Error(w, http.StatusInternalServerError, "禁用 %s 服务失败: %v", req.Service, err)
return
}
@@ -84,7 +84,7 @@ func (s *SystemctlService) Restart(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Restart(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "重启 %s 服务失败", req.Service)
Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", req.Service, err)
return
}
@@ -99,7 +99,7 @@ func (s *SystemctlService) Reload(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Reload(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "重载 %s 服务失败", req.Service)
Error(w, http.StatusInternalServerError, "重载 %s 服务失败: %v", req.Service, err)
return
}
@@ -114,7 +114,7 @@ func (s *SystemctlService) Start(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Start(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "启动 %s 服务失败", req.Service)
Error(w, http.StatusInternalServerError, "启动 %s 服务失败: %v", req.Service, err)
return
}
@@ -129,7 +129,7 @@ func (s *SystemctlService) Stop(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Stop(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "停止 %s 服务失败", req.Service)
Error(w, http.StatusInternalServerError, "停止 %s 服务失败: %v", req.Service, err)
return
}
+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
}
+7 -5
View File
@@ -8,6 +8,7 @@ import (
"crypto/rand"
"crypto/rsa"
"errors"
"log/slog"
"net/http"
"github.com/mholt/acmez/v3"
@@ -38,8 +39,8 @@ const (
type EAB = acme.EAB
func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType KeyType) (*Client, error) {
client, err := getClient(CA)
func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType KeyType, log *slog.Logger) (*Client, error) {
client, err := getClient(CA, log)
if err != nil {
return nil, err
}
@@ -68,8 +69,8 @@ func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType
return &Client{Account: account, zClient: client}, nil
}
func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB) (*Client, error) {
client, err := getClient(CA)
func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB, log *slog.Logger) (*Client, error) {
client, err := getClient(CA, log)
if err != nil {
return nil, err
}
@@ -116,11 +117,12 @@ func generatePrivateKey(keyType KeyType) (crypto.Signer, error) {
return nil, errors.New("未知的密钥类型")
}
func getClient(CA string) (acmez.Client, error) {
func getClient(CA string, log *slog.Logger) (acmez.Client, error) {
client := acmez.Client{
Client: &acme.Client{
Directory: CA,
HTTPClient: http.DefaultClient,
Logger: log,
},
}
+2 -1
View File
@@ -2,6 +2,7 @@ package acme
import (
"context"
"log/slog"
"testing"
"github.com/stretchr/testify/suite"
@@ -17,7 +18,7 @@ func TestClientTestSuite(t *testing.T) {
func (s *ClientTestSuite) TestObtainSSL() {
ctx := context.Background()
client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256)
client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256, slog.Default())
s.Nil(err)
client.UseDns(AliYun, DNSParam{
-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
+9 -8
View File
@@ -3,6 +3,7 @@ package systemctl
import (
"errors"
"fmt"
"time"
"github.com/tnb-labs/panel/pkg/shell"
)
@@ -38,48 +39,48 @@ func IsEnabled(name string) (bool, error) {
// Start 启动服务
func Start(name string) error {
_, err := shell.Execf("systemctl start %s", name)
_, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl start %s", name)
return err
}
// Stop 停止服务
func Stop(name string) error {
_, err := shell.Execf("systemctl stop %s", name)
_, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl stop %s", name)
return err
}
// Restart 重启服务
func Restart(name string) error {
_, err := shell.Execf("systemctl restart %s", name)
_, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl restart %s", name)
return err
}
// Reload 重载服务
func Reload(name string) error {
_, err := shell.Execf("systemctl reload %s", name)
_, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl reload %s", name)
return err
}
// Enable 启用服务
func Enable(name string) error {
_, err := shell.Execf("systemctl enable %s", name)
_, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl enable %s", name)
return err
}
// Disable 禁用服务
func Disable(name string) error {
_, err := shell.Execf("systemctl disable %s", name)
_, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl disable %s", name)
return err
}
// Logs 获取服务日志
func Logs(name string) (string, error) {
return shell.Execf("journalctl -u %s", name)
return shell.ExecfWithTimeout(2*time.Minute, "journalctl -u %s", name)
}
// LogsTail 获取服务日志
func LogsTail(name string, lines int) (string, error) {
return shell.Execf("journalctl -u %s --lines %d", name, lines)
return shell.ExecfWithTimeout(2*time.Minute, "journalctl -u %s --lines %d", name, lines)
}
// LogsClear 清空服务日志
+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"`
}
+1
View File
@@ -32,4 +32,5 @@ type WebsiteSetting struct {
Rewrite string `json:"rewrite"`
Raw string `json:"raw"`
Log string `json:"log"`
ErrorLog string `json:"error_log"`
}
+1
View File
@@ -14,6 +14,7 @@
"platformAutomerge": true,
"postUpdateOptions": [
"gomodTidy",
"gomodUpdateImportPaths",
"pnpmDedupe"
],
"packageRules": [
+1 -1
View File
@@ -6,7 +6,7 @@ VITE_PUBLIC_PATH = '/'
# 是否hash路由模式
VITE_USE_HASH = false
# axios base api
# base api
VITE_BASE_API = '/api'
# 是否启用代理(只对本地vite server生效)
+11 -9
View File
@@ -8,7 +8,7 @@
},
"license": "MIT",
"repository": {
"url": "https://github.com/tnb-labs/panel-frontend"
"url": "https://github.com/tnb-labs/panel"
},
"scripts": {
"dev": "vite",
@@ -23,11 +23,13 @@
"gen-auto-import": "tsx gen-auto-import.ts"
},
"dependencies": {
"@alova/adapter-xhr": "^2.1.1",
"@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",
@@ -36,7 +38,6 @@
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"alova": "^3.2.7",
"axios": "^1.7.9",
"cronstrue": "^2.52.0",
"echarts": "^5.6.0",
"install": "^0.13.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"
}
+1455 -1914
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 -1
View File
@@ -30,5 +30,5 @@ export default {
http.Post(`/apps/php${version}/extensions`, { slug }),
// 卸载拓展
uninstallExtension: (version: number, slug: string): any =>
http.Delete(`/apps/php${version}/extensions`, { data: { slug } })
http.Delete(`/apps/php${version}/extensions`, { slug })
}
+1 -1
View File
@@ -7,5 +7,5 @@ export default {
// 添加
add: (data: any): any => http.Post('/apps/s3fs/mounts', data),
// 删除
delete: (id: number): any => http.Delete('/apps/s3fs/mounts', { data: { id } })
delete: (id: number): any => http.Delete('/apps/s3fs/mounts', { id })
}
+6 -11
View File
@@ -1,22 +1,17 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// 获取备份列表
list: (type: string, page: number, limit: number): any =>
request.get(`/backup/${type}`, { params: { page, limit } }),
http.Get(`/backup/${type}`, { params: { page, limit } }),
// 创建备份
create: (type: string, target: string, path: string): any =>
request.post(`/backup/${type}`, { target, path }),
http.Post(`/backup/${type}`, { target, path }),
// 上传备份
upload: (type: string, formData: FormData): any => {
return request.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 =>
request.delete(`/backup/${type}/delete`, { data: { file } }),
delete: (type: string, file: string): any => http.Delete(`/backup/${type}/delete`, { file }),
// 恢复备份
restore: (type: string, file: string, target: string): any =>
request.post(`/backup/${type}/restore`, { file, target })
http.Post(`/backup/${type}/restore`, { file, target })
}
+26 -27
View File
@@ -1,57 +1,56 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// CA 供应商列表
caProviders: (): any => request.get('/cert/caProviders'),
caProviders: (): any => http.Get('/cert/caProviders'),
// DNS 供应商列表
dnsProviders: (): any => request.get('/cert/dnsProviders'),
dnsProviders: (): any => http.Get('/cert/dnsProviders'),
// 证书算法列表
algorithms: (): any => request.get('/cert/algorithms'),
algorithms: (): any => http.Get('/cert/algorithms'),
// ACME 账号列表
accounts: (page: number, limit: number): any =>
request.get('/cert/account', { params: { page, limit } }),
http.Get('/cert/account', { params: { page, limit } }),
// ACME 账号详情
accountInfo: (id: number): any => request.get(`/cert/account/${id}`),
accountInfo: (id: number): any => http.Get(`/cert/account/${id}`),
// ACME 账号添加
accountCreate: (data: any): any => request.post('/cert/account', data),
accountCreate: (data: any): any => http.Post('/cert/account', data),
// ACME 账号更新
accountUpdate: (id: number, data: any): any => request.put(`/cert/account/${id}`, data),
accountUpdate: (id: number, data: any): any => http.Put(`/cert/account/${id}`, data),
// ACME 账号删除
accountDelete: (id: number): any => request.delete(`/cert/account/${id}`),
accountDelete: (id: number): any => http.Delete(`/cert/account/${id}`),
// DNS 记录列表
dns: (page: number, limit: number): any => request.get('/cert/dns', { params: { page, limit } }),
dns: (page: number, limit: number): any => http.Get('/cert/dns', { params: { page, limit } }),
// DNS 记录详情
dnsInfo: (id: number): any => request.get(`/cert/dns/${id}`),
dnsInfo: (id: number): any => http.Get(`/cert/dns/${id}`),
// DNS 记录添加
dnsCreate: (data: any): any => request.post('/cert/dns', data),
dnsCreate: (data: any): any => http.Post('/cert/dns', data),
// DNS 记录更新
dnsUpdate: (id: number, data: any): any => request.put(`/cert/dns/${id}`, data),
dnsUpdate: (id: number, data: any): any => http.Put(`/cert/dns/${id}`, data),
// DNS 记录删除
dnsDelete: (id: number): any => request.delete(`/cert/dns/${id}`),
dnsDelete: (id: number): any => http.Delete(`/cert/dns/${id}`),
// 证书列表
certs: (page: number, limit: number): any =>
request.get('/cert/cert', { params: { page, limit } }),
certs: (page: number, limit: number): any => http.Get('/cert/cert', { params: { page, limit } }),
// 证书详情
certInfo: (id: number): any => request.get(`/cert/cert/${id}`),
certInfo: (id: number): any => http.Get(`/cert/cert/${id}`),
// 证书上传
certUpload: (data: any): any => request.post('/cert/cert/upload', data),
certUpload: (data: any): any => http.Post('/cert/cert/upload', data),
// 证书添加
certCreate: (data: any): any => request.post('/cert/cert', data),
certCreate: (data: any): any => http.Post('/cert/cert', data),
// 证书更新
certUpdate: (id: number, data: any): any => request.put(`/cert/cert/${id}`, data),
certUpdate: (id: number, data: any): any => http.Put(`/cert/cert/${id}`, data),
// 证书删除
certDelete: (id: number): any => request.delete(`/cert/cert/${id}`),
certDelete: (id: number): any => http.Delete(`/cert/cert/${id}`),
// 证书自动签发
obtainAuto: (id: number): any => request.post(`/cert/cert/${id}/obtainAuto`, { id }),
obtainAuto: (id: number): any => http.Post(`/cert/cert/${id}/obtainAuto`, { id }),
// 证书手动签发
obtainManual: (id: number): any => request.post(`/cert/cert/${id}/obtainManual`, { id }),
obtainManual: (id: number): any => http.Post(`/cert/cert/${id}/obtainManual`, { id }),
// 证书自签名签发
obtainSelfSigned: (id: number): any => request.post(`/cert/cert/${id}/obtainSelfSigned`, { id }),
obtainSelfSigned: (id: number): any => http.Post(`/cert/cert/${id}/obtainSelfSigned`, { id }),
// 续签
renew: (id: number): any => request.post(`/cert/cert/${id}/renew`, { id }),
renew: (id: number): any => http.Post(`/cert/cert/${id}/renew`, { id }),
// 获取 DNS 记录
manualDNS: (id: number): any => request.post(`/cert/cert/${id}/manualDNS`, { id }),
manualDNS: (id: number): any => http.Post(`/cert/cert/${id}/manualDNS`, { id }),
// 部署
deploy: (id: number, website_id: number): any =>
request.post(`/cert/cert/${id}/deploy`, { id, website_id })
http.Post(`/cert/cert/${id}/deploy`, { id, website_id })
}
+41 -25
View File
@@ -1,57 +1,73 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// 获取容器列表
containerList: (page: number, limit: number): any =>
request.get('/container/container', { params: { page, limit } }),
http.Get('/container/container', { params: { page, limit } }),
// 添加容器
containerCreate: (config: any): any => request.post('/container/container', config),
containerCreate: (config: any): any => http.Post('/container/container', config),
// 删除容器
containerRemove: (id: string): any => request.delete(`/container/container/${id}`),
containerRemove: (id: string): any => http.Delete(`/container/container/${id}`),
// 启动容器
containerStart: (id: string): any => request.post(`/container/container/${id}/start`),
containerStart: (id: string): any => http.Post(`/container/container/${id}/start`),
// 停止容器
containerStop: (id: string): any => request.post(`/container/container/${id}/stop`),
containerStop: (id: string): any => http.Post(`/container/container/${id}/stop`),
// 重启容器
containerRestart: (id: string): any => request.post(`/container/container/${id}/restart`),
containerRestart: (id: string): any => http.Post(`/container/container/${id}/restart`),
// 暂停容器
containerPause: (id: string): any => request.post(`/container/container/${id}/pause`),
containerPause: (id: string): any => http.Post(`/container/container/${id}/pause`),
// 恢复容器
containerUnpause: (id: string): any => request.post(`/container/container/${id}/unpause`),
containerUnpause: (id: string): any => http.Post(`/container/container/${id}/unpause`),
// 杀死容器
containerKill: (id: string): any => request.post(`/container/container/${id}/kill`),
containerKill: (id: string): any => http.Post(`/container/container/${id}/kill`),
// 重命名容器
containerRename: (id: string, name: string): any =>
request.post(`/container/container/${id}/rename`, { name }),
http.Post(`/container/container/${id}/rename`, { name }),
// 获取容器日志
containerLogs: (id: string): any => request.get(`/container/container/${id}/logs`),
containerLogs: (id: string): any => http.Get(`/container/container/${id}/logs`),
// 清理容器
containerPrune: (): any => request.post(`/container/container/prune`),
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 =>
request.get(`/container/network`, { params: { page, limit } }),
http.Get(`/container/network`, { params: { page, limit } }),
// 创建网络
networkCreate: (config: any): any => request.post(`/container/network`, config),
networkCreate: (config: any): any => http.Post(`/container/network`, config),
// 删除网络
networkRemove: (id: string): any => request.delete(`/container/network/${id}`),
networkRemove: (id: string): any => http.Delete(`/container/network/${id}`),
// 清理网络
networkPrune: (): any => request.post(`/container/network/prune`),
networkPrune: (): any => http.Post(`/container/network/prune`),
// 获取镜像列表
imageList: (page: number, limit: number): any =>
request.get(`/container/image`, { params: { page, limit } }),
http.Get(`/container/image`, { params: { page, limit } }),
// 拉取镜像
imagePull: (config: any): any => request.post(`/container/image`, config),
imagePull: (config: any): any => http.Post(`/container/image`, config),
// 删除镜像
imageRemove: (id: string): any => request.delete(`/container/image/${id}`),
imageRemove: (id: string): any => http.Delete(`/container/image/${id}`),
// 清理镜像
imagePrune: (): any => request.post(`/container/image/prune`),
imagePrune: (): any => http.Post(`/container/image/prune`),
// 获取卷列表
volumeList: (page: number, limit: number): any =>
request.get(`/container/volume`, { params: { page, limit } }),
http.Get(`/container/volume`, { params: { page, limit } }),
// 创建卷
volumeCreate: (config: any): any => request.post(`/container/volume`, config),
volumeCreate: (config: any): any => http.Post(`/container/volume`, config),
// 删除卷
volumeRemove: (id: string): any => request.delete(`/container/volume/${id}`),
volumeRemove: (id: string): any => http.Delete(`/container/volume/${id}`),
// 清理卷
volumePrune: (): any => request.post(`/container/volume/prune`)
volumePrune: (): any => http.Post(`/container/volume/prune`)
}
+7 -7
View File
@@ -1,17 +1,17 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// 获取任务列表
list: (page: number, limit: number): any => request.get('/cron', { params: { page, limit } }),
list: (page: number, limit: number): any => http.Get('/cron', { params: { page, limit } }),
// 获取任务脚本
get: (id: number): any => request.get('/cron/' + id),
get: (id: number): any => http.Get('/cron/' + id),
// 创建任务
create: (task: any): any => request.post('/cron', task),
create: (task: any): any => http.Post('/cron', task),
// 修改任务
update: (id: number, name: string, time: string, script: string): any =>
request.put('/cron/' + id, { name, time, script }),
http.Put('/cron/' + id, { name, time, script }),
// 删除任务
delete: (id: number): any => request.delete('/cron/' + id),
delete: (id: number): any => http.Delete(`/cron/${id}`),
// 修改任务状态
status: (id: number, status: boolean): any => request.post('/cron/' + id + '/status', { status })
status: (id: number, status: boolean): any => http.Post('/cron/' + id + '/status', { status })
}
+11 -19
View File
@@ -1,33 +1,25 @@
import { http, request } from '@/utils'
import type { RequestConfig } from '~/types/axios'
import { http } from '@/utils'
export default {
// 面板信息
panel: (): Promise<Response> => fetch('/api/dashboard/panel'),
// 面板菜单
menu: (): any => request.get('/dashboard/menu'),
panel: (): any => http.Get('/dashboard/panel'),
// 首页应用
homeApps: (): any => request.get('/dashboard/homeApps'),
homeApps: (): any => http.Get('/dashboard/homeApps'),
// 实时信息
current: (nets: string[], disks: string[]): any =>
request.post('/dashboard/current', { nets, disks }, { noNeedTip: true } as RequestConfig),
http.Post('/dashboard/current', { nets, disks }, { meta: { noAlert: true } }),
// 系统信息
systemInfo: (): any => request.get('/dashboard/systemInfo'),
systemInfo: (): any => http.Get('/dashboard/systemInfo'),
// 统计信息
countInfo: (): any => request.get('/dashboard/countInfo'),
countInfo: (): any => http.Get('/dashboard/countInfo'),
// 已安装的数据库和PHP
installedDbAndPhp: (): any => request.get('/dashboard/installedDbAndPhp'),
installedDbAndPhp: (): any => http.Get('/dashboard/installedDbAndPhp'),
// 检查更新
checkUpdate: (): any => request.get('/dashboard/checkUpdate'),
checkUpdate: (): any => http.Get('/dashboard/checkUpdate'),
// 更新日志
updateInfo: (): any => request.get('/dashboard/updateInfo'),
updateInfo: (): any => http.Get('/dashboard/updateInfo'),
// 更新面板
update: (): any => request.post('/dashboard/update', null),
update: (): any => http.Post('/dashboard/update'),
// 重启面板
restart: (): any => request.post('/dashboard/restart')
restart: (): any => http.Post('/dashboard/restart')
}
export const panel = () => http.Get('/dashboard/panel')
export const current = (nets: string[], disks: string[]) =>
http.Post('/dashboard/current', { nets, disks }, { meta: { noAlert: true } })
+16 -24
View File
@@ -1,47 +1,39 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// 创建文件/文件夹
create: (path: string, dir: boolean): any => request.post('/file/create', { path, dir }),
create: (path: string, dir: boolean): any => http.Post('/file/create', { path, dir }),
// 获取文件内容
content: (path: string): any => request.get('/file/content', { params: { path } }),
content: (path: string): any => http.Get('/file/content', { params: { path } }),
// 保存文件
save: (path: string, content: string): any => request.post('/file/save', { path, content }),
save: (path: string, content: string): any => http.Post('/file/save', { path, content }),
// 删除文件
delete: (path: string): any => request.post('/file/delete', { path }),
delete: (path: string): any => http.Post('/file/delete', { path }),
// 上传文件
upload: (path: string, formData: FormData, onProgress: any): any => {
formData.append('path', path)
return request.post('/file/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent: any) => {
onProgress({ percent: Math.ceil((progressEvent.loaded / progressEvent.total) * 100) })
}
})
},
upload: (formData: FormData): any => http.Post('/file/upload', formData),
// 检查文件是否存在
exist: (paths: string[]): any => request.post('/file/exist', paths),
exist: (paths: string[]): any => http.Post('/file/exist', paths),
// 移动文件
move: (paths: any[]): any => request.post('/file/move', paths),
move: (paths: any[]): any => http.Post('/file/move', paths),
// 复制文件
copy: (paths: any[]): any => request.post('/file/copy', paths),
copy: (paths: any[]): any => http.Post('/file/copy', paths),
// 远程下载
remoteDownload: (path: string, url: string): any =>
request.post('/file/remoteDownload', { path, url }),
http.Post('/file/remoteDownload', { path, url }),
// 获取文件信息
info: (path: string): any => request.get('/file/info', { params: { path } }),
info: (path: string): any => http.Get('/file/info', { params: { path } }),
// 修改文件权限
permission: (path: string, mode: string, owner: string, group: string): any =>
request.post('/file/permission', { path, mode, owner, group }),
http.Post('/file/permission', { path, mode, owner, group }),
// 压缩文件
compress: (dir: string, paths: string[], file: string): any =>
request.post('/file/compress', { dir, paths, file }),
http.Post('/file/compress', { dir, paths, file }),
// 解压文件
unCompress: (file: string, path: string): any => request.post('/file/unCompress', { file, path }),
unCompress: (file: string, path: string): any => http.Post('/file/unCompress', { file, path }),
// 搜索文件
search: (path: string, keyword: string, sub: boolean, page: number, limit: number): any =>
request.get('/file/search', { params: { path, keyword, sub, page, limit } }),
http.Get('/file/search', { params: { path, keyword, sub, page, limit } }),
// 获取文件列表
list: (path: string, page: number, limit: number, sort: string): any =>
request.get('/file/list', { params: { path, page, limit, sort } })
http.Get('/file/list', { params: { path, page, limit, sort } })
}
+12 -12
View File
@@ -1,29 +1,29 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// 获取防火墙状态
status: (): any => request.get('/firewall/status'),
status: (): any => http.Get('/firewall/status'),
// 设置防火墙状态
updateStatus: (status: boolean): any => request.post('/firewall/status', { status }),
updateStatus: (status: boolean): any => http.Post('/firewall/status', { status }),
// 获取防火墙规则
rules: (page: number, limit: number): any =>
request.get('/firewall/rule', { params: { page, limit } }),
http.Get('/firewall/rule', { params: { page, limit } }),
// 创建防火墙规则
createRule: (rule: any): any => request.post('/firewall/rule', rule),
createRule: (rule: any): any => http.Post('/firewall/rule', rule),
// 删除防火墙规则
deleteRule: (rule: any): any => request.delete('/firewall/rule', { data: rule }),
deleteRule: (rule: any): any => http.Delete('/firewall/rule', rule),
// 获取防火墙IP规则
ipRules: (page: number, limit: number): any =>
request.get('/firewall/ipRule', { params: { page, limit } }),
http.Get('/firewall/ipRule', { params: { page, limit } }),
// 创建防火墙IP规则
createIpRule: (rule: any): any => request.post('/firewall/ipRule', rule),
createIpRule: (rule: any): any => http.Post('/firewall/ipRule', rule),
// 删除防火墙IP规则
deleteIpRule: (rule: any): any => request.delete('/firewall/ipRule', { data: rule }),
deleteIpRule: (rule: any): any => http.Delete('/firewall/ipRule', rule),
// 获取防火墙转发规则
forwards: (page: number, limit: number): any =>
request.get('/firewall/forward', { params: { page, limit } }),
http.Get('/firewall/forward', { params: { page, limit } }),
// 创建防火墙转发规则
createForward: (rule: any): any => request.post('/firewall/forward', rule),
createForward: (rule: any): any => http.Post('/firewall/forward', rule),
// 删除防火墙转发规则
deleteForward: (rule: any): any => request.delete('/firewall/forward', { data: rule })
deleteForward: (rule: any): any => http.Delete('/firewall/forward', rule)
}
+5 -6
View File
@@ -1,14 +1,13 @@
import { request } from '@/utils'
import { http } from '@/utils'
export default {
// 开关
setting: (): any => request.get('/monitor/setting'),
setting: (): any => http.Get('/monitor/setting'),
// 保存天数
updateSetting: (enabled: boolean, days: number): any =>
request.post('/monitor/setting', { enabled, days }),
http.Post('/monitor/setting', { enabled, days }),
// 清空监控记录
clear: (): any => request.post('/monitor/clear'),
clear: (): any => http.Post('/monitor/clear'),
// 监控记录
list: (start: number, end: number): any =>
request.get('/monitor/list', { params: { start, end } })
list: (start: number, end: number): any => http.Get('/monitor/list', { params: { start, end } })
}
+1 -1
View File
@@ -7,7 +7,7 @@ export default {
create: (data: any): any => http.Post('/website', data),
// 删除
delete: (id: number, path: boolean, db: boolean): any =>
http.Delete(`/website/${id}`, { data: { path, db } }),
http.Delete(`/website/${id}`, { path, db }),
// 伪静态
rewrites: (): any => http.Get(`/website/rewrites`),
// 获取默认配置
+7 -7
View File
@@ -2,11 +2,7 @@
<footer color="#6a6a6a" f-c-c flex-col text-14>
<p>
© 2022 - {{ year }}
<a
hover="decoration-primary color-primary"
target="__blank"
href="https://github.com/tnb-labs/panel"
>
<a hover="decoration-primary color-primary" target="__blank" href="https://panel.haozi.net/">
耗子面板
</a>
All Rights Reserved.
@@ -20,14 +16,18 @@
Q群 12370907
</a>
<n-divider vertical />
<a hover="decoration-primary color-primary" target="_blank" href="https://bbs.haozi.net/docs">
<a
hover="decoration-primary color-primary"
target="_blank"
href="https://panel.haozi.net/docs"
>
使用文档
</a>
<n-divider vertical />
<a
hover="decoration-primary color-primary"
target="_blank"
href="https://bbs.haozi.net/c/technical/panel"
href="https://tom.moe/c/technical/panel"
>
交流社区
</a>
+11 -11
View File
@@ -16,27 +16,27 @@ const props = defineProps({
})
const disabled = ref(false) // 在出现错误的情况下禁用保存
const data = ref('')
const content = ref('')
const get = async () => {
await file
.content(props.path)
.then((res) => {
data.value = decodeBase64(res.data.content)
const get = () => {
useRequest(file.content(props.path))
.onSuccess(({ data }) => {
content.value = decodeBase64(data.content)
window.$message.success('获取成功')
})
.catch(() => {
.onError(() => {
disabled.value = true
})
}
const save = async () => {
const save = () => {
if (disabled.value) {
window.$message.error('当前状态下不可保存')
return
}
await file.save(props.path, data.value)
window.$message.success('保存成功')
useRequest(file.save(props.path, content.value)).onSuccess(() => {
window.$message.success('保存成功')
})
}
onMounted(() => {
@@ -51,7 +51,7 @@ defineExpose({
<template>
<Editor
v-model:value="data"
v-model:value="content"
:language="languageByPath(props.path)"
theme="vs-dark"
height="60vh"
+336
View File
@@ -0,0 +1,336 @@
<script setup lang="ts">
import file from '@/api/panel/file'
import TheIcon from '@/components/custom/TheIcon.vue'
import { checkName, checkPath, getExt, getIconByExt } from '@/utils'
import type { DataTableColumns, InputInst } from 'naive-ui'
import { NButton, NDataTable, NEllipsis, NFlex, NTag } from 'naive-ui'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const path = defineModel<string>('path', { type: String, required: true })
const props = defineProps({
dir: {
type: Boolean,
required: true
}
})
const title = computed(() => (props.dir ? '选择目录' : '选择文件'))
const isInput = ref(false)
const pathInput = ref<InputInst | null>(null)
const input = ref('www')
const sort = ref<string>('')
const selected = defineModel<any[]>('selected', { type: Array, default: () => [] })
const create = ref(false)
const createModel = ref({
dir: false,
path: ''
})
const columns: DataTableColumns<RowData> = [
{
type: 'selection',
multiple: false,
fixed: 'left',
disabled(row) {
return props.dir ? !row.dir : row.dir
}
},
{
title: '名称',
key: 'name',
minWidth: 180,
defaultSortOrder: false,
sorter: 'default',
render(row) {
let icon = 'bi:file-earmark'
if (row.dir) {
icon = 'bi:folder'
} else {
icon = getIconByExt(getExt(row.name))
}
return h(
NFlex,
{
class: 'cursor-pointer hover:opacity-60',
onClick: () => {
if (row.dir) {
path.value = row.full
} else {
selected.value = [row.full]
}
}
},
() => [
h(TheIcon, { icon, size: 24, color: `var(--primary-color)` }),
h(NEllipsis, null, {
default: () => {
if (row.symlink) {
return row.name + ' -> ' + row.link
} else {
return row.name
}
}
})
]
)
}
},
{
title: '权限',
key: 'mode',
minWidth: 80,
render(row: any): any {
return h(
NTag,
{ type: 'success', size: 'small', bordered: false },
{ default: () => row.mode }
)
}
},
{
title: '所有者 / 组',
key: 'owner/group',
minWidth: 120,
render(row: any): any {
return h('div', null, [
h(NTag, { type: 'primary', size: 'small', bordered: false }, { default: () => row.owner }),
' / ',
h(NTag, { type: 'primary', size: 'small', bordered: false }, { default: () => row.group })
])
}
},
{
title: '大小',
key: 'size',
minWidth: 80,
render(row: any): any {
return h(NTag, { type: 'info', size: 'small', bordered: false }, { default: () => row.size })
}
},
{
title: '修改时间',
key: 'modify',
minWidth: 200,
render(row: any): any {
return h(
NTag,
{ type: 'warning', size: 'small', bordered: false },
{ default: () => row.modify }
)
}
}
]
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => file.list(path.value, page, pageSize, sort.value),
{
initialData: { total: 0, list: [] },
initialPageSize: 100,
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
const handleInput = () => {
isInput.value = true
nextTick(() => {
pathInput.value?.focus()
})
}
const handleBlur = () => {
input.value = input.value.replace(/(^\/)|(\/$)/g, '')
if (!checkPath(input.value)) {
window.$message.error('路径不合法')
return
}
isInput.value = false
path.value = '/' + input.value
}
const handleUp = () => {
const count = splitPath(path.value, '/').length
setPath(count - 2)
}
const splitPath = (str: string, delimiter: string) => {
if (str === delimiter || str === '') {
return []
}
return str.split(delimiter).slice(1)
}
const setPath = (index: number) => {
const newPath = splitPath(path.value, '/')
.slice(0, index + 1)
.join('/')
path.value = '/' + newPath
input.value = newPath
}
const handleSorterChange = (sorter: {
columnKey: string | number | null
order: 'ascend' | 'descend' | false
}) => {
if (!sorter || sorter.columnKey === 'name') {
if (!loading.value) {
switch (sorter.order) {
case 'ascend':
sort.value = 'asc'
refresh()
break
case 'descend':
sort.value = 'desc'
refresh()
break
default:
sort.value = ''
refresh()
break
}
}
}
}
const showCreate = (value: string) => {
createModel.value.dir = value !== 'file'
createModel.value.path = ''
create.value = true
}
const handleCreate = () => {
if (!checkName(createModel.value.path)) {
window.$message.error('名称不合法')
return
}
const fullPath = path.value + '/' + createModel.value.path
useRequest(file.create(fullPath, createModel.value.dir)).onSuccess(() => {
create.value = false
refresh()
window.$message.success('新建成功')
})
}
const closeWatch = watch(
path,
(value) => {
input.value = value.slice(1)
selected.value = []
refresh()
},
{ immediate: true }
)
const handleClose = () => {
closeWatch()
if (selected.value.length) {
path.value = selected.value[0]
selected.value = []
}
show.value = false
}
</script>
<template>
<n-modal
v-model:show="show"
preset="card"
:title="title"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="handleClose"
@mask-click="handleClose"
>
<n-flex>
<n-popselect
:options="[
{ label: '文件', value: 'file' },
{ label: '文件夹', value: 'folder' }
]"
@update:value="showCreate"
>
<n-button type="primary"> 新建 </n-button>
</n-popselect>
<n-button @click="handleUp">
<icon-bi-arrow-up />
</n-button>
<n-input-group flex-1>
<n-tag size="large" v-if="!isInput" flex-1 @click="handleInput">
<n-breadcrumb separator=">">
<n-breadcrumb-item @click.stop="setPath(-1)"> 根目录 </n-breadcrumb-item>
<n-breadcrumb-item
v-for="(item, index) in splitPath(path, '/')"
:key="index"
@click.stop="setPath(index)"
>
{{ item }}
</n-breadcrumb-item>
</n-breadcrumb>
</n-tag>
<n-input-group-label v-if="isInput">/</n-input-group-label>
<n-input
ref="pathInput"
v-model:value="input"
v-if="isInput"
@keyup.enter="handleBlur"
@blur="handleBlur"
/>
</n-input-group>
<n-button @click="refresh">
<icon-bi-arrow-clockwise />
</n-button>
</n-flex>
<n-data-table
remote
striped
virtual-scroll
pt-20
size="small"
:scroll-x="600"
:columns="columns"
:data="data"
:loading="loading"
:row-key="(row: any) => row.full"
max-height="60vh"
@update:sorter="handleSorterChange"
v-model:checked-row-keys="selected"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [100, 200, 500, 1000, 1500, 2000, 5000]
}"
/>
</n-modal>
<n-modal
v-model:show="create"
preset="card"
title="新建"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-space vertical>
<n-form :model="createModel">
<n-form-item label="名称">
<n-input v-model:value="createModel.path" />
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
</n-space>
</n-modal>
</template>
<style scoped lang="scss"></style>
+1 -1
View File
@@ -12,7 +12,7 @@ withDefaults(defineProps<Props>(), {
<transition appear mode="out-in" name="fade-slide">
<section class="cus-scroll-y wh-full flex-col bg-[#f5f6fb] p-15" dark:bg-hex-121212>
<slot />
<AppFooter v-if="showFooter" mt-15 />
<app-footer v-if="showFooter" mt-15 />
</section>
</transition>
</template>
+3 -3
View File
@@ -19,7 +19,7 @@ const route = useRoute()
</script>
<template>
<AppPage :show-footer="showFooter">
<app-page :show-footer="showFooter">
<header v-if="showHeader" mb-15 min-h-45 flex items-center justify-between px-15>
<slot v-if="$slots.header" name="header" />
<template v-else>
@@ -33,8 +33,8 @@ const route = useRoute()
</template>
</header>
<n-card flex-1 rounded-10>
<n-card flex-1>
<slot />
</n-card>
</AppPage>
</app-page>
</template>
+3 -3
View File
@@ -41,9 +41,9 @@
"cache": "缓存更新成功",
"warning": "更新应用前强烈建议先备份/快照,以免出现问题时无法第一时间回滚!",
"setup": "设置成功",
"install": "任务已提交,请前往后台任务查看任务进度",
"update": "任务已提交,请前往后台任务查看任务进度",
"uninstall": "任务已提交,请前往后台任务查看任务进度"
"install": "任务已提交,请前往后台任务查看进度",
"update": "任务已提交,请前往后台任务查看进度",
"uninstall": "任务已提交,请前往后台任务查看进度"
},
"buttons": {
"updateCache": "更新缓存",
+7 -7
View File
@@ -14,17 +14,17 @@ const themeStore = useThemeStore()
<template>
<div w-full flex items-center justify-between>
<MenuCollapse v-if="themeStore.isMobile" />
<menu-collapse v-if="themeStore.isMobile" />
<section v-if="!themeStore.isMobile && themeStore.tab.visible" w-0 flex-1 pr-12>
<AppTab />
<app-tab />
</section>
<span v-if="!themeStore.isMobile && themeStore.tab.visible" mx-6 opacity-20>|</span>
<div ml-auto flex flex-shrink-0 items-center px-12>
<ReloadPage />
<FullScreen />
<ThemeMode />
<ThemeSetting />
<UserAvatar />
<reload-page />
<full-screen />
<theme-mode />
<theme-setting />
<user-avatar />
</div>
</div>
</template>

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