mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 09:19:41 +08:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c547b00a5f | |||
| e5f837799f | |||
| 25194d8c50 | |||
| e1f6b291bb | |||
| df9425b202 | |||
| c50a33f323 | |||
| b264381b84 | |||
| d5ee789a61 | |||
| 5fa4219a31 | |||
| 430640e93c | |||
| 135c5c844f | |||
| 05721dbb47 | |||
| 0235679821 | |||
| 11e206b41a | |||
| 4985eb2e1f | |||
| 2181653ff7 | |||
| 26f7edaa24 | |||
| 75078c88d9 | |||
| 329aecb444 | |||
| 1559c9f8d3 | |||
| 26add61117 | |||
| 763c625b83 | |||
| 1375de2caa | |||
| c4b84632b4 | |||
| c20bfdc3ea | |||
| cc55aef306 | |||
| ed5c74c753 | |||
| 5a9ccddabd | |||
| e6b82ab9ae | |||
| 8868a125e2 | |||
| 04474acf5f | |||
| 6a17173b3b | |||
| 711fb33b48 | |||
| 3ed8fb4f95 | |||
| 65944a72ec | |||
| 24d18df86b | |||
| b330763746 | |||
| 2e914ea41a | |||
| 85a5d22861 | |||
| 9f31a2061d | |||
| d1b54c2690 | |||
| 029f86d9d5 | |||
| 9de30f30b8 | |||
| 8582bfc36b | |||
| 4a2af3434c | |||
| 7f697ca734 | |||
| 7f08ee92ff | |||
| 2f09043915 | |||
| 50aab262a8 | |||
| 99b033f070 | |||
| 6277a17d53 | |||
| 8bb6bd1882 | |||
| 3161c61001 | |||
| e68a882e75 | |||
| 24066f05fc | |||
| 28325f4af6 | |||
| 0c57e9d628 | |||
| 8fb7ddcf7a | |||
| 76185a97f3 | |||
| 567ef561c7 | |||
| b70b9df0b7 | |||
| 0e4fbea166 | |||
| 6cf4e0c0cc | |||
| 68da337cc0 | |||
| c492639897 | |||
| 5e10b2267e | |||
| 68bbbc76e2 | |||
| 7cd1c764d2 | |||
| a053a4212a | |||
| 2d0974a5d5 | |||
| d90396c8ea | |||
| 877d9c57c8 | |||
| 275760c18a | |||
| 9dd6063611 | |||
| 0acbb05d3f | |||
| d860e9361a | |||
| 97d76f62ae | |||
| 910a2d92cc | |||
| 76ef54fe0e | |||
| bf41b98339 | |||
| f3cec5e476 | |||
| bb5b66958c | |||
| 04fb0a1de8 | |||
| f7752387da | |||
| a7420eef34 | |||
| 28847bcc1f | |||
| cb24946aea | |||
| 371424114a | |||
| 60086b9839 | |||
| 6d55088a0d | |||
| c4234a11f2 | |||
| 7644e1a5f6 | |||
| f8a54b2860 | |||
| 8c1813b9e5 | |||
| 7a9a803451 | |||
| 681d0b5324 | |||
| a33490b316 | |||
| fb95ff2861 | |||
| 326c2585f3 | |||
| 61cc26cdb0 | |||
| 2ecd3a020b | |||
| c5b23feeac | |||
| 4f39f62208 | |||
| 146401e9f9 | |||
| d544ca6a90 | |||
| 43248c1d88 | |||
| 2b5bb04bbd | |||
| 30a5427778 | |||
| c547200c05 | |||
| 7f223812dc | |||
| 0e86c77d6d | |||
| fb24407cd1 | |||
| ec7791b73d | |||
| 7987cec9b9 | |||
| 7be3e799be | |||
| 5667bb236f | |||
| 5a391a459e | |||
| 60b0761de2 | |||
| 952546f845 | |||
| b7c055c43f | |||
| ba4df0c76f | |||
| dfae3f1d8d | |||
| 310d83a2aa | |||
| 77eb802076 | |||
| 913a6776dc | |||
| f245ff06c5 | |||
| 79901878e6 | |||
| c597e6b99c | |||
| 3c98e6ff37 | |||
| 447b0332e3 | |||
| 1459b287fa | |||
| 252b67ce36 | |||
| 8119eca0c6 | |||
| f0b96417bf | |||
| b8018284c6 | |||
| 737986f218 | |||
| 200815bf10 | |||
| 8cff4e3575 | |||
| 64c39bd462 | |||
| 4a5e629ab1 | |||
| 368519f3fa | |||
| 8fa7f4a22b | |||
| a8e4360c29 | |||
| 719a9a580d | |||
| fb32151b76 | |||
| c50d892e52 | |||
| b31ccace85 | |||
| e57da7e41a | |||
| 38141c3f4e | |||
| 69d39e6c67 | |||
| 4d8a330644 | |||
| 26c417f18d | |||
| f6b7fcb4af | |||
| 212e989782 | |||
| d0ece55dd1 | |||
| 03c0b191fe | |||
| adacee1851 | |||
| 2725c4902d | |||
| 77d7539880 | |||
| 560b80038e | |||
| c9d4c79bf8 | |||
| 905f13f0d3 | |||
| e7128c65d9 | |||
| c15e3cc3ae | |||
| d9f1145d93 | |||
| 0aafe8baa3 | |||
| 7b0e9db8dc | |||
| 07881311b3 |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
@@ -13,6 +13,7 @@ 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
|
||||
|
||||
@@ -21,6 +21,7 @@ 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
|
||||
|
||||
+16
-29
@@ -4,11 +4,23 @@ on:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
xgotext:
|
||||
l10n:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
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
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
@@ -20,33 +32,8 @@ jobs:
|
||||
- name: Generate pot files
|
||||
run: |
|
||||
~/go/bin/xgotext -default backend -pkg-tree ./cmd/web -out ./pkg/embed/locales
|
||||
git pull
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
cd web && pnpm run gettext:extract
|
||||
- uses: stefanzweifel/git-auto-commit-action@v6
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore(l10n): update backend pot files"
|
||||
vue3-gettext:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Generate pot files and merge po files
|
||||
working-directory: web
|
||||
run: |
|
||||
pnpm run gettext:extract
|
||||
git pull
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore(l10n): update frontend pot files"
|
||||
commit_message: "chore(l10n): update pot files"
|
||||
|
||||
@@ -46,6 +46,7 @@ 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
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
run: |
|
||||
~/go/bin/mockery
|
||||
git pull
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
- uses: stefanzweifel/git-auto-commit-action@v6
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore: update mocks"
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ upx:
|
||||
|
||||
archives:
|
||||
- id: panel
|
||||
builds:
|
||||
ids:
|
||||
- web
|
||||
- cli
|
||||
formats: ["zip"]
|
||||
|
||||
@@ -26,45 +26,43 @@
|
||||
3. **追随时代:** 整体设计走在时代前沿,对新系统兼容性好,在同类产品中处于领先地位
|
||||
4. **高效运维:** 功能完善,自定义能力强,既可快速部署小型网站,也可基于定制化需求部署复杂应用
|
||||
5. **离线运行:** 支持离线模式,甚至可以在部署完成后停止面板进程,不会对已有服务造成任何影响
|
||||
6. **安全稳定:** 面板采用业界多种技术保障本体安全性,已在我们的多个生存环境中长期稳定运行
|
||||
6. **安全稳定:** 面板采用业界多种技术保障本体安全性,已在我们的多个生产环境中长期稳定运行
|
||||
7. **全面开源:** 少有的全开源面板,您可以在遵守开源协议的前提下对面板自由修改、二次开发
|
||||
8. **全面开源:** 承诺面板本体未来不会引入任何收费/授权功能,永久免费使用
|
||||
8. **永久免费:** 承诺面板本体未来不会引入任何收费/授权功能,永久免费使用
|
||||
|
||||
## 快速安装
|
||||
|
||||
支持 `amd64` | `arm64` 架构下的干净的主流系统,具体支持的系统请参考[安装文档](https://ratpanel.github.io/zh_CN/quickstart/install)。
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
|
||||
```
|
||||
|
||||
## UI 截图
|
||||
|
||||

|
||||
|
||||
## 快速安装
|
||||
|
||||
[https://ratpanel.github.io/quickstart/install.html](https://ratpanel.github.io/quickstart/install.html)
|
||||
|
||||
## 合作伙伴
|
||||
|
||||
如果耗子面板对您有帮助,欢迎赞助我们,同时感谢以下支持者/赞助商的支持:
|
||||
如果耗子面板对您有帮助,欢迎[赞助我们](https://github.com/tnb-labs/panel/issues/90),同时感谢以下支持者/赞助商的支持:
|
||||
|
||||
 
|
||||
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="80" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="80" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/sctes.png" alt="无畏云加速">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://hongtoutong.com">
|
||||
<img height="80" src=".github/assets/htt.png" alt="红透通">
|
||||
</a>
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="60" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="60" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img height="60" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="60" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://1ms.run/">
|
||||
<img height="60" src=".github/assets/1ms.svg" alt="毫秒镜像">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnblabs">
|
||||
|
||||
+26
-28
@@ -30,41 +30,39 @@ Website: [panel.haozi.net](https://panel.haozi.net) | QQ group: [12370907](https
|
||||
7. **Fully Open Source:** Few fully open source panels, you can freely modify and develop the panel on the premise of complying with the open source agreement
|
||||
8. **Permanently free:** Commit to the panel body will not introduce any charging/authorization functions in the future, and will be permanently free to use
|
||||
|
||||
## Quick Install
|
||||
|
||||
Supported clean mainstream systems under `amd64` | `arm64` architecture, please refer to the [installation documentation](https://ratpanel.github.io/quickstart/install) for specific supported systems.
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
|
||||
```
|
||||
|
||||
## UI Screenshots
|
||||
|
||||

|
||||
|
||||
## Quick Install
|
||||
|
||||
[https://ratpanel.github.io/quickstart/install.html](https://ratpanel.github.io/quickstart/install.html)
|
||||
|
||||
## Partners
|
||||
|
||||
If the Rat Panel is helpful to you, welcome to sponsor us, also thanks to the following supporters/sponsors:
|
||||
If the Rat Panel is helpful to you, welcome to [sponsor us](https://github.com/tnb-labs/panel/issues/90), also thanks to the following supporters/sponsors:
|
||||
|
||||
 
|
||||
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="80" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="80" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/sctes.png" alt="无畏云加速">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://hongtoutong.com">
|
||||
<img height="80" src=".github/assets/htt.png" alt="红透通">
|
||||
</a>
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="60" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="60" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img height="60" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="60" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://1ms.run/">
|
||||
<img height="60" src=".github/assets/1ms.svg" alt="毫秒镜像">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnblabs">
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@
|
||||
|
||||
提前感谢您的支持与帮助!
|
||||
|
||||
致某些安全初学者:通过面板 `session` / `access_token` 执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均不被认为是安全问题,请不要刷此类报告浪费彼此的时间,这类低水平的报告对你的简历也没有任何帮助。
|
||||
致某些安全初学者:通过已登录面板的 `session` / `access_token` 执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均不被认为是安全问题,请不要刷此类报告浪费彼此的时间。
|
||||
|
||||
## Security Policy
|
||||
|
||||
@@ -22,4 +22,4 @@ If you find any security issues while using the panel, please do not submit an I
|
||||
|
||||
Thank you in advance for your support and help!
|
||||
|
||||
To some security beginners: Any operation performed through the panel `session` / `access_token` (including but not limited to: obtaining root permissions, reading/writing sensitive system files, executing arbitrary shell commands, etc.) is not considered a security issue. Please do not waste each other's time by submitting such reports. Such low-level reports are of no help to your resume.
|
||||
To some security beginners: Any operation performed through an already logged-in panel's `session` / `access_token` (including but not limited to: obtaining root permissions, reading/writing sensitive system files, executing arbitrary shell commands, etc.) is not considered a security issue. Please do not waste each other's time by submitting such reports.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# cmd
|
||||
|
||||
cmd 目录存放应用的入口文件。
|
||||
+1
-1
@@ -61,7 +61,7 @@ func initCli() (*app.Cli, error) {
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, db, cacheRepo, taskRepo)
|
||||
appRepo := data.NewAppRepo(locale, koanf, db, cacheRepo, taskRepo)
|
||||
userRepo := data.NewUserRepo(locale, db)
|
||||
settingRepo := data.NewSettingRepo(locale, db, koanf, taskRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ func initWeb() (*app.Web, error) {
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, db, cacheRepo, taskRepo)
|
||||
appRepo := data.NewAppRepo(locale, koanf, db, cacheRepo, taskRepo)
|
||||
userTokenRepo := data.NewUserTokenRepo(locale, db)
|
||||
middlewares := middleware.NewMiddlewares(koanf, logger, manager, appRepo, userTokenRepo)
|
||||
userRepo := data.NewUserRepo(locale, db)
|
||||
|
||||
@@ -7,26 +7,26 @@ require (
|
||||
github.com/beevik/ntp v1.4.3
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/devhaozi/westcn v0.0.0-20250407083704-b49375823872
|
||||
github.com/expr-lang/expr v1.17.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/expr-lang/expr v1.17.5
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4
|
||||
github.com/go-rat/chix v1.2.0
|
||||
github.com/go-rat/gormstore v1.0.6
|
||||
github.com/go-rat/sessions v1.1.0
|
||||
github.com/go-rat/utils v1.1.5
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-sql-driver/mysql v1.9.2
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/golang-cz/httplog v0.0.2
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/gookit/validate v1.5.4
|
||||
github.com/gookit/validate v1.5.5
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/knadh/koanf/parsers/yaml v1.0.0
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0
|
||||
github.com/knadh/koanf/providers/file v1.2.0
|
||||
github.com/knadh/koanf/v2 v2.2.0
|
||||
github.com/leonelquinteros/gotext v1.7.1
|
||||
github.com/knadh/koanf/v2 v2.2.1
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/libdns/alidns v1.0.4
|
||||
github.com/libdns/cloudflare v0.1.3
|
||||
@@ -38,30 +38,30 @@ require (
|
||||
github.com/libdns/huaweicloud v0.3.5
|
||||
github.com/libdns/libdns v0.2.3
|
||||
github.com/libdns/linode v0.4.1
|
||||
github.com/libdns/namecheap v0.0.0-20250228022813-d8b4b66c5072
|
||||
github.com/libdns/namecheap v0.0.0-20250523201144-144eb6168d52
|
||||
github.com/libdns/namedotcom v0.3.3
|
||||
github.com/libdns/namesilo v0.1.2
|
||||
github.com/libdns/porkbun v0.2.0
|
||||
github.com/libdns/tencentcloud v1.2.0
|
||||
github.com/libdns/vercel v0.0.2
|
||||
github.com/mholt/acmez/v3 v3.1.2
|
||||
github.com/ncruces/go-sqlite3 v0.25.2
|
||||
github.com/ncruces/go-sqlite3 v0.26.3
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/pquerna/otp v1.4.1-0.20241231231527-5971b1ef1d66
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.50.0
|
||||
github.com/samber/lo v1.51.0
|
||||
github.com/sethvargo/go-limiter v1.0.1-0.20250412144437-fa26982c7e1a
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cast v1.8.0
|
||||
github.com/spf13/cast v1.9.2
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250429180229-7e931b1d4276
|
||||
github.com/urfave/cli/v3 v3.3.3
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/net v0.40.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701
|
||||
github.com/urfave/cli/v3 v3.3.8
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/net v0.41.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.26.1
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -100,10 +100,11 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250416074048-0e9f6b5f8ec5
|
||||
replace github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250707093727-dc5aedd96413
|
||||
|
||||
@@ -51,8 +51,8 @@ github.com/devhaozi/westcn v0.0.0-20250407083704-b49375823872 h1:cns0Vj69pUuQ+bf
|
||||
github.com/devhaozi/westcn v0.0.0-20250407083704-b49375823872/go.mod h1:3DfSvYwyUHt1JtN+JCWAGtOLFWlRXz19wJa/Xk159ao=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
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/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k=
|
||||
github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
@@ -62,8 +62,8 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8
|
||||
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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=
|
||||
@@ -85,8 +85,8 @@ github.com/go-rat/utils v1.1.5 h1:nB5qNU4TxqG9BJe7PdeDzznmh6lk+GIWb7hZcvAY4FQ=
|
||||
github.com/go-rat/utils v1.1.5/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.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
||||
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
@@ -94,8 +94,8 @@ 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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
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/golang-cz/httplog v0.0.2 h1:3d8iScWLeMWQG5/bfMZ5Dizh+zvRfNmLBZMe5N2HrGU=
|
||||
github.com/golang-cz/httplog v0.0.2/go.mod h1:bgk4Ij/0OQ89UeoFFAQrSNhbbr4rKJ0fwWfo7wc+TCc=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -132,8 +132,8 @@ github.com/gookit/filter v1.2.2 h1:LSBQLk4M4fpfhaOG0hJ/GJ+qu+2+lLddgxlGfzcd8VQ=
|
||||
github.com/gookit/filter v1.2.2/go.mod h1:y4xpiqM/fV9g9yT/Vg5OWUq4q9siPj0UlH9O57xYuGs=
|
||||
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
|
||||
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
||||
github.com/gookit/validate v1.5.4 h1:nwBo6vULnVUeNFCOde6RKFRbOCKJXVMnWR0ghedacLg=
|
||||
github.com/gookit/validate v1.5.4/go.mod h1:p9sRPfpvYB4vXICBpEPzv8FoAky+XhUOhWQghgmmat4=
|
||||
github.com/gookit/validate v1.5.5 h1:jzMrwAYy9tbQa0mH8YFnv3C3JQR/0N4pRTsYiySwRJM=
|
||||
github.com/gookit/validate v1.5.5/go.mod h1:p9sRPfpvYB4vXICBpEPzv8FoAky+XhUOhWQghgmmat4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
@@ -183,12 +183,12 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v1.0.0 h1:PXyeHCRhAMKyfLJaoTWsqUTxIFeDMmdAKz3XVEslZV4=
|
||||
github.com/knadh/koanf/parsers/yaml v1.0.0/go.mod h1:Q63VAOh/s6XaQs6a0TB2w9GFUuuPGvfYrCSWb9eWAQU=
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
|
||||
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
|
||||
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||
github.com/knadh/koanf/v2 v2.2.0 h1:FZFwd9bUjpb8DyCWARUBy5ovuhDs1lI87dOEn2K8UVU=
|
||||
github.com/knadh/koanf/v2 v2.2.0/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
||||
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
|
||||
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@@ -199,8 +199,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leonelquinteros/gotext v1.7.1 h1:/JNPeE3lY5JeVYv2+KBpz39994W3W9fmZCGq3eO9Ri8=
|
||||
github.com/leonelquinteros/gotext v1.7.1/go.mod h1:I0WoFDn9u2D3VbPnnDPT8mzZu0iSXG8iih+AH2fHHqg=
|
||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
|
||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
||||
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.4 h1:Rc3Yy2SzMoho+3q3+fNy9vOVr2h9dcL8OLTgNKgxYbU=
|
||||
@@ -225,8 +225,8 @@ 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/linode v0.4.1 h1:Fr22y3aWBzaj3AlFbCQ743eC+5Hss+sJ+OEjtlK61AA=
|
||||
github.com/libdns/linode v0.4.1/go.mod h1:BqAJnQxzpnQgy4GDVYf3hQvhLGDc3n8BRwMZ0/9m2y0=
|
||||
github.com/libdns/namecheap v0.0.0-20250228022813-d8b4b66c5072 h1:hCPOoLd9Mr5kAWLofhSCuGYZxAVVy0CV9zgbahzIeSg=
|
||||
github.com/libdns/namecheap v0.0.0-20250228022813-d8b4b66c5072/go.mod h1:jo2LWZSD/g/scevxNSp2kWtdMIXfsgWxrtSc3Z0Yy/I=
|
||||
github.com/libdns/namecheap v0.0.0-20250523201144-144eb6168d52 h1:kV0L03h9sbiZInF9L+V3Cb5SDD9jnA0mitzKkCg6TDk=
|
||||
github.com/libdns/namecheap v0.0.0-20250523201144-144eb6168d52/go.mod h1:jo2LWZSD/g/scevxNSp2kWtdMIXfsgWxrtSc3Z0Yy/I=
|
||||
github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE=
|
||||
github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s=
|
||||
github.com/libdns/namesilo v0.1.2 h1:ySW4QiWx2vMWw1xBD7kTnTWVY/CLIaCgko6ZbmOufbk=
|
||||
@@ -261,14 +261,12 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncruces/go-sqlite3 v0.25.2 h1:suu3C7y92hPqozqO8+w3K333Q1VhWyN6K3JJKXdtC2U=
|
||||
github.com/ncruces/go-sqlite3 v0.25.2/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3 h1:WFkQj4KNMhbqiBPGDrVpK74w1DzcxQu3wYpmdWAvfYM=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||
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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU=
|
||||
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
|
||||
@@ -282,8 +280,8 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/otp v1.4.1-0.20241231231527-5971b1ef1d66 h1:7pccJSb6/BRgd5BKejX/dgzinfq0K5HPwj35+saHuCU=
|
||||
github.com/pquerna/otp v1.4.1-0.20241231231527-5971b1ef1d66/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@@ -301,8 +299,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sethvargo/go-limiter v1.0.1-0.20250412144437-fa26982c7e1a h1:CdCoDHVynJVAQWN7ZQrAUOp0SV5TmRwNOSkF5KedDko=
|
||||
github.com/sethvargo/go-limiter v1.0.1-0.20250412144437-fa26982c7e1a/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
@@ -317,8 +315,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
|
||||
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@@ -348,12 +346,12 @@ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYN
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250416074048-0e9f6b5f8ec5 h1:eeJt90c4buPPC4JjfP5EaTFwlARgOaaXqewHFqZJJag=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250416074048-0e9f6b5f8ec5/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250429180229-7e931b1d4276 h1:928VpvLiMWSulfjhKUYj57f70HhOE1ImsQHaeDC2jVc=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250429180229-7e931b1d4276/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
|
||||
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
|
||||
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250707093727-dc5aedd96413 h1:M1ubPXmO7ez2Ay/4J0MVS3HjkKEnWhHXfHvOOi7IMj8=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250707093727-dc5aedd96413/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701 h1:JgeHIJzRSEdcuLXufZrni5+a4yDnBhQG+DdKhqCFhq0=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
|
||||
github.com/urfave/cli/v3 v3.3.8 h1:BzolUExliMdet9NlJ/u4m5vHSotJ3PzEqSAZ1oPMa/E=
|
||||
github.com/urfave/cli/v3 v3.3.8/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
@@ -366,6 +364,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -376,8 +376,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -424,8 +424,8 @@ 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -438,8 +438,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -483,8 +483,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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
@@ -539,8 +539,8 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
@@ -557,8 +557,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
|
||||
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -104,7 +104,10 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MySQL status: %v", err))
|
||||
return
|
||||
}
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
if err = os.Unsetenv("MYSQL_PWD"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to unset MYSQL_PWD env: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
var load []map[string]string
|
||||
expressions := []struct {
|
||||
|
||||
@@ -24,6 +24,7 @@ type UserRepo interface {
|
||||
List(page, limit uint) ([]*User, int64, error)
|
||||
Get(id uint) (*User, error)
|
||||
Create(username, password, email string) (*User, error)
|
||||
UpdateUsername(id uint, username string) error
|
||||
UpdatePassword(id uint, password string) error
|
||||
UpdateEmail(id uint, email string) error
|
||||
Delete(id uint) error
|
||||
|
||||
@@ -19,6 +19,8 @@ type Website struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
CertExpire string `gorm:"-:all" json:"cert_expire"` // 仅显示
|
||||
|
||||
Cert *Cert `gorm:"foreignKey:WebsiteID" json:"cert"`
|
||||
}
|
||||
|
||||
|
||||
@@ -17,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(gormlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
|
||||
return gorm.Open(gormlite.Open("file:"+filepath.Join(app.Root, "panel/storage/app.db?_txlock=immediate")), &gorm.Config{
|
||||
Logger: sloggorm.New(sloggorm.WithHandler(log.Handler())),
|
||||
SkipDefaultTransaction: true,
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/go-rat/utils/collect"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
"gorm.io/gorm"
|
||||
@@ -21,14 +22,16 @@ import (
|
||||
|
||||
type appRepo struct {
|
||||
t *gotext.Locale
|
||||
conf *koanf.Koanf
|
||||
db *gorm.DB
|
||||
cache biz.CacheRepo
|
||||
task biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewAppRepo(t *gotext.Locale, db *gorm.DB, cache biz.CacheRepo, task biz.TaskRepo) biz.AppRepo {
|
||||
func NewAppRepo(t *gotext.Locale, conf *koanf.Koanf, db *gorm.DB, cache biz.CacheRepo, task biz.TaskRepo) biz.AppRepo {
|
||||
return &appRepo{
|
||||
t: t,
|
||||
conf: conf,
|
||||
db: db,
|
||||
cache: cache,
|
||||
task: task,
|
||||
@@ -166,7 +169,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
continue
|
||||
}
|
||||
if ch.Slug == channel {
|
||||
if vs.GreaterThan(panel) {
|
||||
if vs.GreaterThan(panel) && !r.conf.Bool("app.debug") {
|
||||
return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version))
|
||||
}
|
||||
shellUrl = ch.Install
|
||||
@@ -221,7 +224,7 @@ func (r *appRepo) UnInstall(slug string) error {
|
||||
continue
|
||||
}
|
||||
if ch.Slug == installed.Channel {
|
||||
if vs.GreaterThan(panel) {
|
||||
if vs.GreaterThan(panel) && !r.conf.Bool("app.debug") {
|
||||
return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version))
|
||||
}
|
||||
shellUrl = ch.Uninstall
|
||||
@@ -276,7 +279,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
continue
|
||||
}
|
||||
if ch.Slug == installed.Channel {
|
||||
if vs.GreaterThan(panel) {
|
||||
if vs.GreaterThan(panel) && !r.conf.Bool("app.debug") {
|
||||
return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version))
|
||||
}
|
||||
shellUrl = ch.Update
|
||||
|
||||
@@ -326,7 +326,7 @@ func (r *certRepo) ManualDNS(id uint) ([]acme.DNSRecord, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.UseManualDns(len(cert.Domains))
|
||||
client.UseManualDns()
|
||||
records, err := client.GetDNSRecords(context.Background(), cert.Domains, acme.KeyType(cert.Type))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -19,9 +19,11 @@ func NewContainerComposeRepo() biz.ContainerComposeRepo {
|
||||
return &containerComposeRepo{}
|
||||
}
|
||||
|
||||
// List 列出所有编排文件名
|
||||
// List 列出所有编排
|
||||
func (r *containerComposeRepo) List() ([]types.ContainerCompose, error) {
|
||||
_ = os.Setenv("PODMAN_COMPOSE_WARNING_LOGS", "false") // 禁用 Podman Compose 的警告日志
|
||||
raw, err := shell.Execf("docker compose ls -a --format json")
|
||||
_ = os.Unsetenv("PODMAN_COMPOSE_WARNING_LOGS")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -142,19 +144,26 @@ func (r *containerComposeRepo) Up(name string, force bool) error {
|
||||
if force {
|
||||
cmd += " --pull always" // 强制拉取镜像
|
||||
}
|
||||
_ = os.Setenv("PODMAN_COMPOSE_WARNING_LOGS", "false") // 禁用 Podman Compose 的警告日志
|
||||
_, err := shell.Execf(cmd, file)
|
||||
_ = os.Unsetenv("PODMAN_COMPOSE_WARNING_LOGS")
|
||||
return err
|
||||
}
|
||||
|
||||
// Down 停止编排
|
||||
func (r *containerComposeRepo) Down(name string) error {
|
||||
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
|
||||
_ = os.Setenv("PODMAN_COMPOSE_WARNING_LOGS", "false") // 禁用 Podman Compose 的警告日志
|
||||
_, err := shell.Execf("docker compose -f %s down", file)
|
||||
_ = os.Unsetenv("PODMAN_COMPOSE_WARNING_LOGS")
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove 删除编排
|
||||
func (r *containerComposeRepo) Remove(name string) error {
|
||||
if err := r.Down(name); err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
return os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
@@ -78,6 +78,9 @@ func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
|
||||
return err
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = mysql.DatabaseCreate(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.PrivilegesGrant(req.Username, name, req.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,6 +102,9 @@ func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
|
||||
return err
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = postgres.DatabaseCreate(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.PrivilegesGrant(req.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -145,6 +151,9 @@ func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
|
||||
}
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = mysql.DatabaseCreate(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.PrivilegesGrant(user.Username, name, user.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -163,6 +172,9 @@ func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
|
||||
}
|
||||
}
|
||||
for name := range slices.Values(req.Privileges) {
|
||||
if err = postgres.DatabaseCreate(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.PrivilegesGrant(user.Username, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+16
-14
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/firewall"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/os"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
@@ -310,6 +311,20 @@ func (r *settingRepo) UpdatePanel(req *request.SettingPanel) (bool, error) {
|
||||
if os.TCPPortInUse(req.Port) {
|
||||
return false, errors.New(r.t.Get("port is already in use"))
|
||||
}
|
||||
// 放行端口
|
||||
if ok, _ := systemctl.IsEnabled("firewalld"); ok {
|
||||
fw := firewall.NewFirewall()
|
||||
err = fw.Port(firewall.FireInfo{
|
||||
Type: firewall.TypeNormal,
|
||||
PortStart: config.HTTP.Port,
|
||||
PortEnd: config.HTTP.Port,
|
||||
Direction: firewall.DirectionIn,
|
||||
Strategy: firewall.StrategyAccept,
|
||||
}, firewall.OperationAdd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.App.Locale = req.Locale
|
||||
@@ -321,19 +336,6 @@ func (r *settingRepo) UpdatePanel(req *request.SettingPanel) (bool, error) {
|
||||
config.HTTP.BindUA = req.BindUA
|
||||
config.Session.Lifetime = req.Lifetime
|
||||
|
||||
// 放行端口
|
||||
fw := firewall.NewFirewall()
|
||||
err = fw.Port(firewall.FireInfo{
|
||||
Type: firewall.TypeNormal,
|
||||
PortStart: config.HTTP.Port,
|
||||
PortEnd: config.HTTP.Port,
|
||||
Direction: firewall.DirectionIn,
|
||||
Strategy: firewall.StrategyAccept,
|
||||
}, firewall.OperationAdd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
encoded, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -62,6 +62,16 @@ func (r *userRepo) Create(username, password, email string) (*biz.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepo) UpdateUsername(id uint, username string) error {
|
||||
user, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Username = username
|
||||
return r.db.Save(user).Error
|
||||
}
|
||||
|
||||
func (r *userRepo) UpdatePassword(id uint, password string) error {
|
||||
value, err := r.hasher.Make(password)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/str"
|
||||
@@ -144,7 +144,26 @@ func (r userTokenRepo) ValidateReq(req *http.Request) (uint, error) {
|
||||
if err != nil {
|
||||
ip = req.RemoteAddr
|
||||
}
|
||||
if !slices.Contains(userToken.IPs, ip) {
|
||||
allowed := false
|
||||
requestIP := net.ParseIP(ip)
|
||||
if requestIP != nil {
|
||||
for _, allowedIP := range userToken.IPs {
|
||||
if strings.Contains(allowedIP, "/") {
|
||||
// CIDR
|
||||
if _, ipNet, err := net.ParseCIDR(allowedIP); err == nil && ipNet.Contains(requestIP) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// IP
|
||||
if allowedIP == ip {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return 0, errors.New(r.t.Get("invalid request ip: %s", ip))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,15 @@ func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 取证书剩余有效时间
|
||||
for _, website := range websites {
|
||||
crt, _ := io.Read(filepath.Join(app.Root, "server/vhost/cert", website.Name+".pem"))
|
||||
if decode, err := cert.ParseCert(crt); err == nil {
|
||||
hours := time.Until(decode.NotAfter).Hours()
|
||||
website.CertExpire = fmt.Sprintf("%.2f", hours/24)
|
||||
}
|
||||
}
|
||||
|
||||
return websites, total, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"github.com/go-rat/sessions"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/punycode"
|
||||
)
|
||||
|
||||
@@ -22,19 +20,11 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sess, err := session.GetSession(r)
|
||||
if err != nil {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusInternalServerError)
|
||||
render.JSON(chix.M{
|
||||
"msg": err.Error(),
|
||||
})
|
||||
Abort(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
entrance := strings.TrimSuffix(conf.String("http.entrance"), "/")
|
||||
if entrance == "" {
|
||||
entrance = "/"
|
||||
}
|
||||
if !strings.HasPrefix(entrance, "/") {
|
||||
entrance = "/" + entrance
|
||||
}
|
||||
@@ -50,44 +40,54 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
|
||||
}
|
||||
}
|
||||
if len(conf.Strings("http.bind_domain")) > 0 && !slices.Contains(conf.Strings("http.bind_domain"), host) {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusTeapot)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("invalid request domain: %s", r.Host),
|
||||
})
|
||||
Abort(w, http.StatusTeapot, t.Get("invalid request domain: %s", r.Host))
|
||||
return
|
||||
}
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
if len(conf.Strings("http.bind_ip")) > 0 && !slices.Contains(conf.Strings("http.bind_ip"), ip) {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusTeapot)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("invalid request ip: %s", ip),
|
||||
})
|
||||
return
|
||||
if len(conf.Strings("http.bind_ip")) > 0 {
|
||||
allowed := false
|
||||
requestIP := net.ParseIP(ip)
|
||||
if requestIP != nil {
|
||||
for _, allowedIP := range conf.Strings("http.bind_ip") {
|
||||
if strings.Contains(allowedIP, "/") {
|
||||
// CIDR
|
||||
if _, ipNet, err := net.ParseCIDR(allowedIP); err == nil && ipNet.Contains(requestIP) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// IP
|
||||
if allowedIP == ip {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
Abort(w, http.StatusTeapot, t.Get("invalid request ip: %s", ip))
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(conf.Strings("http.bind_ua")) > 0 && !slices.Contains(conf.Strings("http.bind_ua"), r.UserAgent()) {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusTeapot)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("invalid request user agent: %s", r.UserAgent()),
|
||||
})
|
||||
Abort(w, http.StatusTeapot, t.Get("invalid request user agent: %s", r.UserAgent()))
|
||||
return
|
||||
}
|
||||
|
||||
// 情况二:请求路径与入口路径相同,标记通过验证并重定向到登录页面
|
||||
if strings.TrimSuffix(r.URL.Path, "/") == entrance {
|
||||
// 情况二:请求路径与入口路径相同或未设置访问入口,标记通过验证并重定向
|
||||
if (strings.TrimSuffix(r.URL.Path, "/") == entrance || entrance == "/") &&
|
||||
r.Header.Get("Authorization") == "" {
|
||||
sess.Put("verify_entrance", true)
|
||||
render := chix.NewRender(w, r)
|
||||
defer render.Release()
|
||||
render.Redirect("/login")
|
||||
return
|
||||
// 设置入口的情况下进行重定向
|
||||
if entrance != "/" {
|
||||
render := chix.NewRender(w, r)
|
||||
defer render.Release()
|
||||
render.Redirect("/login")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 情况三:通过APIKey+入口路径访问,重写请求路径并跳过验证
|
||||
@@ -96,23 +96,18 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
|
||||
if entrance != "/" {
|
||||
if rctx := chi.RouteContext(r.Context()); rctx != nil {
|
||||
rctx.RoutePath = strings.TrimPrefix(rctx.RoutePath, entrance)
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, entrance)
|
||||
}
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, entrance)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 情况三:非调试模式且未通过验证的请求,返回错误
|
||||
// 情况四:非调试模式且未通过验证的请求,返回错误
|
||||
if !conf.Bool("app.debug") &&
|
||||
!cast.ToBool(sess.Get("verify_entrance", false)) &&
|
||||
sess.Missing("verify_entrance") &&
|
||||
r.URL.Path != "/robots.txt" {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusTeapot)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("invalid access entrance"),
|
||||
})
|
||||
Abort(w, http.StatusTeapot, t.Get("invalid access entrance"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
)
|
||||
|
||||
func Abort(w http.ResponseWriter, code int, format string, args ...any) {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Header(chix.HeaderContentType, chix.MIMEApplicationJSONCharsetUTF8) // must before Status()
|
||||
render.Status(code)
|
||||
if len(args) > 0 {
|
||||
format = fmt.Sprintf(format, args...)
|
||||
}
|
||||
render.JSON(chix.M{
|
||||
"msg": format,
|
||||
})
|
||||
}
|
||||
@@ -39,16 +39,14 @@ func NewMiddlewares(conf *koanf.Koanf, log *slog.Logger, session *sessions.Manag
|
||||
// Globals is a collection of global middleware that will be applied to every request.
|
||||
func (r *Middlewares) Globals(t *gotext.Locale, mux *chi.Mux) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
sessionmiddleware.StartSession(r.session),
|
||||
middleware.Recoverer,
|
||||
//middleware.SupressNotFound(mux),// bug https://github.com/go-chi/chi/pull/940
|
||||
middleware.CleanPath,
|
||||
middleware.StripSlashes,
|
||||
middleware.Compress(5),
|
||||
httplog.RequestLogger(r.log, &httplog.Options{
|
||||
Level: slog.LevelInfo,
|
||||
LogRequestHeaders: []string{"User-Agent"},
|
||||
}),
|
||||
middleware.Recoverer,
|
||||
middleware.Compress(5),
|
||||
sessionmiddleware.StartSession(r.session),
|
||||
Status(t),
|
||||
Entrance(t, r.conf, r.session),
|
||||
MustLogin(t, r.session, r.userToken),
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -22,12 +21,7 @@ func MustInstall(t *gotext.Locale, app biz.AppRepo) func(next http.Handler) http
|
||||
} else if strings.HasPrefix(r.URL.Path, "/api/apps/") {
|
||||
pathArr := strings.Split(r.URL.Path, "/")
|
||||
if len(pathArr) < 4 {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("app not found"),
|
||||
})
|
||||
Abort(w, http.StatusForbidden, t.Get("app not found"))
|
||||
return
|
||||
}
|
||||
slugs = append(slugs, pathArr[3])
|
||||
@@ -41,12 +35,7 @@ func MustInstall(t *gotext.Locale, app biz.AppRepo) func(next http.Handler) http
|
||||
}
|
||||
}
|
||||
if !flag && len(slugs) > 0 {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("app %s not installed", slugs),
|
||||
})
|
||||
Abort(w, http.StatusForbidden, t.Get("app %s not installed", slugs))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/sessions"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
@@ -32,12 +31,7 @@ func MustLogin(t *gotext.Locale, session *sessions.Manager, userToken biz.UserTo
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sess, err := session.GetSession(r)
|
||||
if err != nil {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusInternalServerError)
|
||||
render.JSON(chix.M{
|
||||
"msg": err.Error(),
|
||||
})
|
||||
Abort(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,32 +45,17 @@ func MustLogin(t *gotext.Locale, session *sessions.Manager, userToken biz.UserTo
|
||||
if r.Header.Get("Authorization") != "" {
|
||||
// 禁止访问 ws 相关的接口
|
||||
if strings.HasPrefix(r.URL.Path, "/api/ws") {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("ws not allowed"),
|
||||
})
|
||||
Abort(w, http.StatusForbidden, t.Get("ws not allowed"))
|
||||
return
|
||||
}
|
||||
// API 请求验证
|
||||
if userID, err = userToken.ValidateReq(r); err != nil {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusUnauthorized)
|
||||
render.JSON(chix.M{
|
||||
"msg": err.Error(),
|
||||
})
|
||||
Abort(w, http.StatusUnauthorized, "%v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if sess.Missing("user_id") {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusUnauthorized)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("session expired, please login again"),
|
||||
})
|
||||
Abort(w, http.StatusUnauthorized, t.Get("session expired, please login again"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,12 +65,8 @@ func MustLogin(t *gotext.Locale, session *sessions.Manager, userToken biz.UserTo
|
||||
ip, _, _ := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr))
|
||||
clientHash := fmt.Sprintf("%x", sha256.Sum256([]byte(ip)))
|
||||
if safeClientHash != clientHash || safeClientHash == "" {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusUnauthorized)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("client ip/ua changed, please login again"),
|
||||
})
|
||||
sess.Forget("user_id") // 清除 user_id,否则会来回跳转
|
||||
Abort(w, http.StatusUnauthorized, t.Get("client ip/ua changed, please login again"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -100,12 +75,7 @@ func MustLogin(t *gotext.Locale, session *sessions.Manager, userToken biz.UserTo
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusUnauthorized)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("invalid user id, please login again"),
|
||||
})
|
||||
Abort(w, http.StatusUnauthorized, "%v", t.Get("invalid user id, please login again"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package middleware
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -15,36 +14,16 @@ func Status(t *gotext.Locale) func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch app.Status {
|
||||
case app.StatusUpgrade:
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusServiceUnavailable)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("panel is upgrading, please refresh later"),
|
||||
})
|
||||
Abort(w, http.StatusServiceUnavailable, t.Get("panel is upgrading, please refresh later"))
|
||||
return
|
||||
case app.StatusMaintain:
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusServiceUnavailable)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("panel is maintaining, please refresh later"),
|
||||
})
|
||||
Abort(w, http.StatusServiceUnavailable, t.Get("panel is maintaining, please refresh later"))
|
||||
return
|
||||
case app.StatusClosed:
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("panel is closed"),
|
||||
})
|
||||
Abort(w, http.StatusServiceUnavailable, t.Get("panel is closed"))
|
||||
return
|
||||
case app.StatusFailed:
|
||||
render := chix.NewRender(w)
|
||||
defer render.Release()
|
||||
render.Status(http.StatusInternalServerError)
|
||||
render.JSON(chix.M{
|
||||
"msg": t.Get("panel run error, please check or contact support"),
|
||||
})
|
||||
Abort(w, http.StatusInternalServerError, t.Get("panel run error, please check or contact support"))
|
||||
return
|
||||
default:
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
@@ -25,7 +25,7 @@ type SettingPanel struct {
|
||||
func (r *SettingPanel) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"BindDomain.*": "required",
|
||||
"BindIP.*": "required|ip",
|
||||
"BindIP.*": "required|ipcidr",
|
||||
"BindUA.*": "required",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ type UserCreate struct {
|
||||
Email string `json:"email" validate:"required|email"`
|
||||
}
|
||||
|
||||
type UserUpdateUsername struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
Username string `json:"username" validate:"required|notExists:users,username"`
|
||||
}
|
||||
|
||||
type UserUpdatePassword struct {
|
||||
ID uint `json:"id" validate:"required|exists:users,id"`
|
||||
Password string `json:"password" validate:"required|password"`
|
||||
|
||||
@@ -15,7 +15,7 @@ type UserTokenCreate struct {
|
||||
|
||||
func (r *UserTokenCreate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"IPs.*": "required|ip",
|
||||
"IPs.*": "required|ipcidr",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,6 @@ type UserTokenUpdate struct {
|
||||
|
||||
func (r *UserTokenUpdate) Rules(_ *http.Request) map[string]string {
|
||||
return map[string]string{
|
||||
"IPs.*": "required|ip",
|
||||
"IPs.*": "required|ipcidr",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package rule
|
||||
|
||||
import "net"
|
||||
|
||||
// IPCIDR 验证一个值是否是一个有效的 IP 或 CIDR 格式
|
||||
type IPCIDR struct{}
|
||||
|
||||
func NewIPCIDR() *IPCIDR {
|
||||
return &IPCIDR{}
|
||||
}
|
||||
|
||||
func (r *IPCIDR) Passes(val any, options ...any) bool {
|
||||
if str, ok := val.(string); ok {
|
||||
if ip := net.ParseIP(str); ip != nil {
|
||||
return true // 是有效的 IP
|
||||
}
|
||||
if _, _, err := net.ParseCIDR(str); err == nil {
|
||||
return true // 是有效的 CIDR
|
||||
}
|
||||
}
|
||||
return false // 既不是 IP 也不是 CIDR
|
||||
}
|
||||
@@ -11,11 +11,13 @@ func GlobalRules(db *gorm.DB) {
|
||||
"notExists": NewNotExists(db).Passes,
|
||||
"password": NewPassword().Passes,
|
||||
"cron": NewCron().Passes,
|
||||
"ipcidr": NewIPCIDR().Passes,
|
||||
})
|
||||
validate.AddGlobalMessages(map[string]string{
|
||||
"exists": "{field} 不存在",
|
||||
"notExists": "{field} 已存在",
|
||||
"password": "密码不满足要求(8-20位,至少包含字母、数字、特殊字符中的两种)",
|
||||
"cron": "Cron 表达式不合法",
|
||||
"ipcidr": "IP 或 CIDR 格式不合法",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", route.user.List)
|
||||
r.Post("/", route.user.Create)
|
||||
r.Post("/{id}/username", route.user.UpdateUsername)
|
||||
r.Post("/{id}/password", route.user.UpdatePassword)
|
||||
r.Post("/{id}/email", route.user.UpdateEmail)
|
||||
r.Get("/{id}/2fa", route.user.GenerateTwoFA)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/urfave/cli/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
@@ -179,15 +178,11 @@ func (s *FirewallService) CreateIPRule(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// rich rule 下,address 不支持多个地址,需要单独添加
|
||||
addresses := strings.Split(req.Address, ",")
|
||||
for address := range slices.Values(addresses) {
|
||||
if err = s.firewall.RichRules(firewall.FireInfo{
|
||||
Family: req.Family, Address: address, Protocol: firewall.Protocol(req.Protocol), Strategy: firewall.Strategy(req.Strategy), Direction: firewall.Direction(req.Direction),
|
||||
}, firewall.OperationAdd); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = s.firewall.RichRules(firewall.FireInfo{
|
||||
Family: req.Family, Address: req.Address, Protocol: firewall.Protocol(req.Protocol), Strategy: firewall.Strategy(req.Strategy), Direction: firewall.Direction(req.Direction),
|
||||
}, firewall.OperationAdd); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
|
||||
@@ -120,13 +120,15 @@ func (s *UserService) Login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *UserService) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
sess, err := s.session.GetSession(r)
|
||||
if err == nil {
|
||||
if err = sess.Invalidate(); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
}
|
||||
|
||||
sess.Forget("user_id")
|
||||
sess.Forget("key")
|
||||
sess.Forget("safe_login")
|
||||
sess.Forget("safe_client")
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -206,6 +208,21 @@ func (s *UserService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, user)
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateUsername(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserUpdateUsername](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.userRepo.UpdateUsername(req.ID, req.Username); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *UserService) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.UserUpdatePassword](r)
|
||||
if err != nil {
|
||||
|
||||
@@ -582,6 +582,53 @@ func (_c *UserRepo_UpdateTwoFA_Call) RunAndReturn(run func(uint, string, string)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateUsername provides a mock function with given fields: id, username
|
||||
func (_m *UserRepo) UpdateUsername(id uint, username string) error {
|
||||
ret := _m.Called(id, username)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateUsername")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(uint, string) error); ok {
|
||||
r0 = rf(id, username)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UserRepo_UpdateUsername_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateUsername'
|
||||
type UserRepo_UpdateUsername_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateUsername is a helper method to define mock.On call
|
||||
// - id uint
|
||||
// - username string
|
||||
func (_e *UserRepo_Expecter) UpdateUsername(id interface{}, username interface{}) *UserRepo_UpdateUsername_Call {
|
||||
return &UserRepo_UpdateUsername_Call{Call: _e.mock.On("UpdateUsername", id, username)}
|
||||
}
|
||||
|
||||
func (_c *UserRepo_UpdateUsername_Call) Run(run func(id uint, username string)) *UserRepo_UpdateUsername_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(uint), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserRepo_UpdateUsername_Call) Return(_a0 error) *UserRepo_UpdateUsername_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UserRepo_UpdateUsername_Call) RunAndReturn(run func(uint, string) error) *UserRepo_UpdateUsername_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewUserRepo creates a new instance of UserRepo. 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 NewUserRepo(t interface {
|
||||
|
||||
+14
-12
@@ -35,14 +35,16 @@ func (c *Client) UseDns(dnsType DnsType, param DNSParam) {
|
||||
}
|
||||
|
||||
// UseManualDns 使用手动 DNS 验证
|
||||
func (c *Client) UseManualDns(total int, check ...bool) {
|
||||
func (c *Client) UseManualDns(check ...bool) {
|
||||
c.controlChan = make(chan struct{})
|
||||
c.dataChan = make(chan any)
|
||||
c.dnsChan = make(chan any)
|
||||
c.certChan = make(chan any)
|
||||
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
|
||||
acme.ChallengeTypeDNS01: &manualDNSSolver{
|
||||
check: len(check) > 0 && check[0],
|
||||
controlChan: c.controlChan,
|
||||
dataChan: c.dataChan,
|
||||
dnsChan: c.dnsChan,
|
||||
certChan: c.certChan,
|
||||
records: []DNSRecord{},
|
||||
},
|
||||
}
|
||||
@@ -84,13 +86,13 @@ func (c *Client) ObtainCertificateManual() (Certificate, error) {
|
||||
// 发送信号,开始验证
|
||||
c.controlChan <- struct{}{}
|
||||
// 等待验证完成
|
||||
data := <-c.dataChan
|
||||
certs := <-c.certChan
|
||||
|
||||
if err, ok := data.(error); ok {
|
||||
if err, ok := certs.(error); ok {
|
||||
return Certificate{}, err
|
||||
}
|
||||
|
||||
return data.(Certificate), nil
|
||||
return certs.(Certificate), nil
|
||||
}
|
||||
|
||||
// RenewCertificate 续签 SSL 证书
|
||||
@@ -107,22 +109,22 @@ func (c *Client) RenewCertificate(ctx context.Context, certUrl string, domains [
|
||||
func (c *Client) GetDNSRecords(ctx context.Context, domains []string, keyType KeyType) ([]DNSRecord, error) {
|
||||
go func(ctx context.Context, domains []string, keyType KeyType) {
|
||||
certs, err := c.ObtainCertificate(ctx, domains, keyType)
|
||||
// 将证书和错误信息发送到 dataChan
|
||||
// 将证书和错误信息发送到 certChan
|
||||
if err != nil {
|
||||
c.dataChan <- err
|
||||
c.certChan <- err
|
||||
return
|
||||
}
|
||||
c.dataChan <- certs
|
||||
c.certChan <- certs
|
||||
}(ctx, domains, keyType)
|
||||
|
||||
// 这里要少一次循环,因为需要卡住最后一次的 dataChan,等待手动 DNS 验证完成
|
||||
// 这里要少一次循环,因为需要卡住最后一次的 dnsChan,等待手动 DNS 验证完成
|
||||
for i := 1; i < len(domains); i++ {
|
||||
<-c.dataChan
|
||||
<-c.dnsChan
|
||||
c.controlChan <- struct{}{}
|
||||
}
|
||||
|
||||
// 因为上面少了一次循环,所以这里接收到的即为完整的 DNS 记录切片
|
||||
data := <-c.dataChan
|
||||
data := <-c.dnsChan
|
||||
if err, ok := data.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+6
-4
@@ -267,9 +267,10 @@ type DNSProvider interface {
|
||||
}
|
||||
|
||||
type manualDNSSolver struct {
|
||||
check bool
|
||||
check bool // 是否检查 DNS 解析,目前没写
|
||||
controlChan chan struct{}
|
||||
dataChan chan any
|
||||
dnsChan chan any
|
||||
certChan chan any
|
||||
records []DNSRecord
|
||||
}
|
||||
|
||||
@@ -286,7 +287,7 @@ func (s *manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge)
|
||||
Domain: domain,
|
||||
Value: keyAuth,
|
||||
})
|
||||
s.dataChan <- s.records
|
||||
s.dnsChan <- s.records
|
||||
|
||||
select {
|
||||
case <-s.controlChan:
|
||||
@@ -301,7 +302,8 @@ func (s *manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
|
||||
_ = recover()
|
||||
}()
|
||||
close(s.controlChan)
|
||||
close(s.dataChan)
|
||||
close(s.dnsChan)
|
||||
close(s.certChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -71,7 +71,15 @@ func (r *Postgres) Prepare(query string) (*sql.Stmt, error) {
|
||||
}
|
||||
|
||||
func (r *Postgres) DatabaseCreate(name string) error {
|
||||
_, err := r.Exec(fmt.Sprintf("CREATE DATABASE %s", name))
|
||||
// postgres 不支持 CREATE DATABASE IF NOT EXISTS,但是为了保持与 MySQL 一致,先检查数据库是否存在
|
||||
exist, err := r.DatabaseExist(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return nil
|
||||
}
|
||||
_, err = r.Exec(fmt.Sprintf("CREATE DATABASE %s", name))
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ msgstr ""
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
#: internal/data/website.go:270
|
||||
#: internal/data/website.go:643
|
||||
#: internal/data/website.go:279
|
||||
#: internal/data/website.go:652
|
||||
msgid "# Rewrite rule"
|
||||
msgstr ""
|
||||
|
||||
@@ -28,7 +28,7 @@ msgstr ""
|
||||
msgid "Accepted Connections"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:121
|
||||
#: internal/apps/mysql/app.go:124
|
||||
msgid "Active Connections"
|
||||
msgstr ""
|
||||
|
||||
@@ -56,7 +56,7 @@ msgstr ""
|
||||
msgid "Already initialized"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:347
|
||||
#: internal/data/app.go:350
|
||||
msgid "App %s %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -134,13 +134,13 @@ msgstr ""
|
||||
msgid "Bind domain disabled"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:120
|
||||
#: internal/apps/mysql/app.go:139
|
||||
#: internal/apps/mysql/app.go:123
|
||||
#: internal/apps/mysql/app.go:142
|
||||
msgid "Bytes Received"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:119
|
||||
#: internal/apps/mysql/app.go:139
|
||||
#: internal/apps/mysql/app.go:122
|
||||
#: internal/apps/mysql/app.go:142
|
||||
msgid "Bytes Sent"
|
||||
msgstr ""
|
||||
|
||||
@@ -460,11 +460,11 @@ msgstr ""
|
||||
msgid "Forum:https://bbs.haozi.net"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:127
|
||||
#: internal/apps/mysql/app.go:130
|
||||
msgid "Full Joins without Index"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:128
|
||||
#: internal/apps/mysql/app.go:131
|
||||
msgid "Full Range Joins without Index"
|
||||
msgstr ""
|
||||
|
||||
@@ -544,7 +544,7 @@ msgstr ""
|
||||
msgid "ImageMagick is free software for creating, editing, and composing images"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:123
|
||||
#: internal/apps/mysql/app.go:126
|
||||
msgid "Index Hit Rate"
|
||||
msgstr ""
|
||||
|
||||
@@ -559,7 +559,7 @@ msgstr ""
|
||||
msgid "Initialize panel (use only under guidance)"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:124
|
||||
#: internal/apps/mysql/app.go:127
|
||||
msgid "Innodb Index Hit Rate"
|
||||
msgstr ""
|
||||
|
||||
@@ -567,7 +567,7 @@ msgstr ""
|
||||
msgid "Install PHP-%d %s extension"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:191
|
||||
#: internal/data/app.go:194
|
||||
msgid "Install app %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -732,7 +732,7 @@ msgstr ""
|
||||
msgid "Old username cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:126
|
||||
#: internal/apps/mysql/app.go:129
|
||||
msgid "Open Tables"
|
||||
msgstr ""
|
||||
|
||||
@@ -807,7 +807,7 @@ msgstr ""
|
||||
msgid "Path where the website is hosted (default path if not filled)"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:122
|
||||
#: internal/apps/mysql/app.go:125
|
||||
msgid "Peak Connections"
|
||||
msgstr ""
|
||||
|
||||
@@ -911,7 +911,7 @@ msgstr ""
|
||||
msgid "Restart panel service"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:118
|
||||
#: internal/apps/mysql/app.go:121
|
||||
msgid "Rollbacks per Second"
|
||||
msgstr ""
|
||||
|
||||
@@ -983,7 +983,7 @@ msgstr ""
|
||||
msgid "Slow Requests"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:130
|
||||
#: internal/apps/mysql/app.go:133
|
||||
msgid "Sort Merge Passes"
|
||||
msgstr ""
|
||||
|
||||
@@ -1004,7 +1004,7 @@ msgstr ""
|
||||
msgid "Storage Usage"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:129
|
||||
#: internal/apps/mysql/app.go:132
|
||||
msgid "Subqueries without Index"
|
||||
msgstr ""
|
||||
|
||||
@@ -1040,7 +1040,7 @@ msgstr ""
|
||||
msgid "TCP Port"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:131
|
||||
#: internal/apps/mysql/app.go:134
|
||||
msgid "Table Locks Waited"
|
||||
msgstr ""
|
||||
|
||||
@@ -1048,7 +1048,7 @@ msgstr ""
|
||||
msgid "Tasks cleared successfully"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:125
|
||||
#: internal/apps/mysql/app.go:128
|
||||
msgid "Temporary Tables Created on Disk"
|
||||
msgstr ""
|
||||
|
||||
@@ -1076,7 +1076,7 @@ msgstr ""
|
||||
msgid "Total Commands Processed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:116
|
||||
#: internal/apps/mysql/app.go:119
|
||||
#: internal/apps/postgresql/app.go:143
|
||||
msgid "Total Connections"
|
||||
msgstr ""
|
||||
@@ -1093,7 +1093,7 @@ msgstr ""
|
||||
msgid "Total Processes"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:115
|
||||
#: internal/apps/mysql/app.go:118
|
||||
msgid "Total Queries"
|
||||
msgstr ""
|
||||
|
||||
@@ -1109,7 +1109,7 @@ msgstr ""
|
||||
msgid "Total requests"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:117
|
||||
#: internal/apps/mysql/app.go:120
|
||||
msgid "Transactions per Second"
|
||||
msgstr ""
|
||||
|
||||
@@ -1128,7 +1128,7 @@ msgstr ""
|
||||
msgid "Uninstall PHP-%d %s extension"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:246
|
||||
#: internal/data/app.go:249
|
||||
msgid "Uninstall app %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1144,7 +1144,7 @@ msgstr ""
|
||||
msgid "Unzip panel data failed: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:301
|
||||
#: internal/data/app.go:304
|
||||
msgid "Update app %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1156,7 +1156,7 @@ msgstr ""
|
||||
msgid "Update panel"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:114
|
||||
#: internal/apps/mysql/app.go:117
|
||||
msgid "Uptime"
|
||||
msgstr ""
|
||||
|
||||
@@ -1270,38 +1270,38 @@ msgstr ""
|
||||
msgid "Zip is a library for handling ZIP files"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:159
|
||||
#: internal/data/app.go:162
|
||||
msgid "app %s already installed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:56
|
||||
#: internal/data/app.go:59
|
||||
msgid "app %s not found"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:210
|
||||
#: internal/data/app.go:265
|
||||
#: internal/http/middleware/must_install.go:48
|
||||
#: internal/data/app.go:213
|
||||
#: internal/data/app.go:268
|
||||
#: internal/http/middleware/must_install.go:38
|
||||
msgid "app %s not installed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:179
|
||||
#: internal/data/app.go:289
|
||||
#: internal/data/app.go:182
|
||||
#: internal/data/app.go:292
|
||||
msgid "app %s not support current panel version"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:170
|
||||
#: internal/data/app.go:225
|
||||
#: internal/data/app.go:280
|
||||
#: internal/data/app.go:173
|
||||
#: internal/data/app.go:228
|
||||
#: internal/data/app.go:283
|
||||
msgid "app %s requires panel version %s, current version %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_install.go:29
|
||||
#: internal/http/middleware/must_install.go:24
|
||||
msgid "app not found"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/setting.go:282
|
||||
#: internal/data/setting.go:343
|
||||
#: internal/data/setting.go:356
|
||||
#: internal/data/setting.go:283
|
||||
#: internal/data/setting.go:345
|
||||
#: internal/data/setting.go:358
|
||||
msgid "background task is running, modifying some settings is prohibited, please try again later"
|
||||
msgstr ""
|
||||
|
||||
@@ -1323,7 +1323,7 @@ msgstr ""
|
||||
msgid "can't download a directory"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:371
|
||||
#: internal/data/website.go:380
|
||||
msgid "can't find %s database server, please add it first"
|
||||
msgstr ""
|
||||
|
||||
@@ -1332,7 +1332,7 @@ msgstr ""
|
||||
msgid "check server connection failed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:93
|
||||
#: internal/http/middleware/must_login.go:69
|
||||
msgid "client ip/ua changed, please login again"
|
||||
msgstr ""
|
||||
|
||||
@@ -1359,11 +1359,11 @@ msgstr ""
|
||||
msgid "database does not exist: %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:725
|
||||
#: internal/data/website.go:734
|
||||
msgid "default document comment count is incorrect, expected 1, actual %d"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:722
|
||||
#: internal/data/website.go:731
|
||||
msgid "default document comment not found"
|
||||
msgstr ""
|
||||
|
||||
@@ -1431,7 +1431,7 @@ msgstr ""
|
||||
msgid "failed to get %s service running status: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:306
|
||||
#: internal/data/website.go:315
|
||||
msgid "failed to get 404 template file: %v"
|
||||
msgstr ""
|
||||
|
||||
@@ -1504,7 +1504,7 @@ msgstr ""
|
||||
msgid "failed to get home apps: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:294
|
||||
#: internal/data/website.go:303
|
||||
msgid "failed to get index template file: %v"
|
||||
msgstr ""
|
||||
|
||||
@@ -1580,28 +1580,28 @@ msgstr ""
|
||||
msgid "failed to get total banned list"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/app.go:234
|
||||
#: internal/data/app.go:237
|
||||
msgid "failed to get uninstall script for app %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:188
|
||||
#: internal/apps/mysql/app.go:191
|
||||
#: internal/apps/mysql/app.go:83
|
||||
msgid "failed to load MySQL root password: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/cert.go:92
|
||||
#: internal/data/setting.go:287
|
||||
#: internal/data/setting.go:359
|
||||
#: internal/data/website.go:475
|
||||
#: internal/data/website.go:764
|
||||
#: internal/data/setting.go:288
|
||||
#: internal/data/setting.go:361
|
||||
#: internal/data/website.go:484
|
||||
#: internal/data/website.go:773
|
||||
msgid "failed to parse certificate: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/cert.go:95
|
||||
#: internal/data/setting.go:290
|
||||
#: internal/data/setting.go:362
|
||||
#: internal/data/website.go:478
|
||||
#: internal/data/website.go:767
|
||||
#: internal/data/setting.go:291
|
||||
#: internal/data/setting.go:364
|
||||
#: internal/data/website.go:487
|
||||
#: internal/data/website.go:776
|
||||
msgid "failed to parse private key: %v"
|
||||
msgstr ""
|
||||
|
||||
@@ -1697,6 +1697,10 @@ msgstr ""
|
||||
msgid "failed to unmount: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/apps/mysql/app.go:108
|
||||
msgid "failed to unset MYSQL_PWD env: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/service/toolbox_system.go:63
|
||||
msgid "failed to update DNS: %v"
|
||||
msgstr ""
|
||||
@@ -1717,13 +1721,13 @@ msgstr ""
|
||||
msgid "get service port failed, please check if it is installed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/user.go:170
|
||||
#: internal/data/user.go:189
|
||||
#: internal/data/user.go:180
|
||||
#: internal/data/user.go:199
|
||||
#: internal/service/user.go:96
|
||||
msgid "invalid 2FA code"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:114
|
||||
#: internal/http/middleware/entrance.go:110
|
||||
msgid "invalid access entrance"
|
||||
msgstr ""
|
||||
|
||||
@@ -1735,16 +1739,16 @@ msgstr ""
|
||||
msgid "invalid key, please refresh the page"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:57
|
||||
#: internal/http/middleware/entrance.go:43
|
||||
msgid "invalid request domain: %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/user_token.go:148
|
||||
#: internal/http/middleware/entrance.go:70
|
||||
#: internal/data/user_token.go:167
|
||||
#: internal/http/middleware/entrance.go:71
|
||||
msgid "invalid request ip: %s"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/entrance.go:79
|
||||
#: internal/http/middleware/entrance.go:76
|
||||
msgid "invalid request user agent: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1754,7 +1758,7 @@ msgstr ""
|
||||
msgid "invalid signature"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:107
|
||||
#: internal/http/middleware/must_login.go:78
|
||||
msgid "invalid user id, please login again"
|
||||
msgstr ""
|
||||
|
||||
@@ -1795,7 +1799,7 @@ msgstr ""
|
||||
msgid "mysql not support database comment"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:795
|
||||
#: internal/data/website.go:804
|
||||
msgid "not support one-key obtain wildcard certificate, please use Cert menu to obtain it with DNS method"
|
||||
msgstr ""
|
||||
|
||||
@@ -1803,19 +1807,19 @@ msgstr ""
|
||||
msgid "open file error: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:38
|
||||
#: internal/http/middleware/status.go:23
|
||||
msgid "panel is closed"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:30
|
||||
#: internal/http/middleware/status.go:20
|
||||
msgid "panel is maintaining, please refresh later"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:22
|
||||
#: internal/http/middleware/status.go:17
|
||||
msgid "panel is upgrading, please refresh later"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/status.go:46
|
||||
#: internal/http/middleware/status.go:26
|
||||
msgid "panel run error, please check or contact support"
|
||||
msgstr ""
|
||||
|
||||
@@ -1840,7 +1844,7 @@ msgstr ""
|
||||
msgid "phpMyAdmin port not found"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/user.go:96
|
||||
#: internal/data/user.go:106
|
||||
#: internal/service/file.go:135
|
||||
#: internal/service/file.go:222
|
||||
#: internal/service/file.go:251
|
||||
@@ -1851,7 +1855,7 @@ msgstr ""
|
||||
msgid "please retry the manual obtain operation"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/setting.go:311
|
||||
#: internal/data/setting.go:312
|
||||
msgid "port is already in use"
|
||||
msgstr ""
|
||||
|
||||
@@ -1871,20 +1875,20 @@ msgstr ""
|
||||
msgid "rule not found"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:712
|
||||
#: internal/data/website.go:721
|
||||
msgid "runtime directory comment count is incorrect, expected 1, actual %d"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:709
|
||||
#: internal/data/website.go:718
|
||||
msgid "runtime directory comment not found"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:449
|
||||
#: internal/data/website.go:716
|
||||
#: internal/data/website.go:458
|
||||
#: internal/data/website.go:725
|
||||
msgid "runtime directory does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:78
|
||||
#: internal/http/middleware/must_login.go:58
|
||||
msgid "session expired, please login again"
|
||||
msgstr ""
|
||||
|
||||
@@ -1982,17 +1986,17 @@ msgstr ""
|
||||
msgid "upload file error: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/user.go:116
|
||||
#: internal/data/user.go:123
|
||||
#: internal/data/user.go:126
|
||||
#: internal/data/user.go:133
|
||||
#: internal/data/user.go:143
|
||||
msgid "username or password error"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:560
|
||||
#: internal/data/website.go:569
|
||||
msgid "website %s has bound certificates, please delete the certificate first"
|
||||
msgstr ""
|
||||
|
||||
#: internal/data/website.go:456
|
||||
#: internal/data/website.go:465
|
||||
msgid "website directory does not exist"
|
||||
msgstr ""
|
||||
|
||||
@@ -2005,7 +2009,7 @@ msgstr ""
|
||||
msgid "write file error: %v"
|
||||
msgstr ""
|
||||
|
||||
#: internal/http/middleware/must_login.go:58
|
||||
#: internal/http/middleware/must_login.go:48
|
||||
msgid "ws not allowed"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ msgstr ""
|
||||
"X-Crowdin-Project: ratpanel\n"
|
||||
"X-Crowdin-Project-ID: 778640\n"
|
||||
"X-Crowdin-Language: zh-CN\n"
|
||||
"X-Crowdin-File: /main/pkg/embed/locales/backend.pot\n"
|
||||
"X-Crowdin-File: /[tnb-labs.panel] main/pkg/embed/locales/backend.pot\n"
|
||||
"X-Crowdin-File-ID: 922\n"
|
||||
"Project-Id-Version: ratpanel\n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"PO-Revision-Date: 2025-05-18 18:00\n"
|
||||
"PO-Revision-Date: 2025-07-07 10:34\n"
|
||||
|
||||
#: internal/data/website.go:270
|
||||
#: internal/data/website.go:643
|
||||
#: internal/data/website.go:279
|
||||
#: internal/data/website.go:652
|
||||
msgid "# Rewrite rule"
|
||||
msgstr "# 伪静态规则"
|
||||
|
||||
@@ -36,7 +36,7 @@ msgstr "作者"
|
||||
msgid "Accepted Connections"
|
||||
msgstr "已接受连接"
|
||||
|
||||
#: internal/apps/mysql/app.go:121
|
||||
#: internal/apps/mysql/app.go:124
|
||||
msgid "Active Connections"
|
||||
msgstr "活动连接"
|
||||
|
||||
@@ -64,7 +64,7 @@ msgstr "阿里云"
|
||||
msgid "Already initialized"
|
||||
msgstr "已经初始化过了"
|
||||
|
||||
#: internal/data/app.go:347
|
||||
#: internal/data/app.go:350
|
||||
msgid "App %s %s"
|
||||
msgstr "应用 %s %s"
|
||||
|
||||
@@ -142,13 +142,13 @@ msgstr "绑定 UA 已禁用"
|
||||
msgid "Bind domain disabled"
|
||||
msgstr "绑定域名已禁用"
|
||||
|
||||
#: internal/apps/mysql/app.go:120
|
||||
#: internal/apps/mysql/app.go:139
|
||||
#: internal/apps/mysql/app.go:123
|
||||
#: internal/apps/mysql/app.go:142
|
||||
msgid "Bytes Received"
|
||||
msgstr "已接收字节"
|
||||
|
||||
#: internal/apps/mysql/app.go:119
|
||||
#: internal/apps/mysql/app.go:139
|
||||
#: internal/apps/mysql/app.go:122
|
||||
#: internal/apps/mysql/app.go:142
|
||||
msgid "Bytes Sent"
|
||||
msgstr "已发送字节"
|
||||
|
||||
@@ -468,11 +468,11 @@ msgstr "修复面板"
|
||||
msgid "Forum:https://bbs.haozi.net"
|
||||
msgstr "论坛:https://bbs.haozi.net"
|
||||
|
||||
#: internal/apps/mysql/app.go:127
|
||||
#: internal/apps/mysql/app.go:130
|
||||
msgid "Full Joins without Index"
|
||||
msgstr "没有使用索引的 Join"
|
||||
|
||||
#: internal/apps/mysql/app.go:128
|
||||
#: internal/apps/mysql/app.go:131
|
||||
msgid "Full Range Joins without Index"
|
||||
msgstr "没有使用索引的范围 Join"
|
||||
|
||||
@@ -552,7 +552,7 @@ msgstr "Igbinary 是一个用于序列化和反序列化数据的库"
|
||||
msgid "ImageMagick is free software for creating, editing, and composing images"
|
||||
msgstr "ImageMagick 是一个创建、编辑、合成图片的免费软件"
|
||||
|
||||
#: internal/apps/mysql/app.go:123
|
||||
#: internal/apps/mysql/app.go:126
|
||||
msgid "Index Hit Rate"
|
||||
msgstr "索引命中率"
|
||||
|
||||
@@ -567,7 +567,7 @@ msgstr "初始化失败:%v"
|
||||
msgid "Initialize panel (use only under guidance)"
|
||||
msgstr "初始化面板(仅在指导下使用)"
|
||||
|
||||
#: internal/apps/mysql/app.go:124
|
||||
#: internal/apps/mysql/app.go:127
|
||||
msgid "Innodb Index Hit Rate"
|
||||
msgstr "Innodb 索引命中率"
|
||||
|
||||
@@ -575,7 +575,7 @@ msgstr "Innodb 索引命中率"
|
||||
msgid "Install PHP-%d %s extension"
|
||||
msgstr "安装 PHP-%d %s 扩展"
|
||||
|
||||
#: internal/data/app.go:191
|
||||
#: internal/data/app.go:194
|
||||
msgid "Install app %s"
|
||||
msgstr "安装应用 %s"
|
||||
|
||||
@@ -740,7 +740,7 @@ msgstr "OPcache 将 PHP 脚本预编译的字节码存储到共享内存中来
|
||||
msgid "Old username cannot be empty"
|
||||
msgstr "旧用户名不能为空"
|
||||
|
||||
#: internal/apps/mysql/app.go:126
|
||||
#: internal/apps/mysql/app.go:129
|
||||
msgid "Open Tables"
|
||||
msgstr "已打开的表"
|
||||
|
||||
@@ -815,7 +815,7 @@ msgstr "密码:%s"
|
||||
msgid "Path where the website is hosted (default path if not filled)"
|
||||
msgstr "网站托管的路径(不填则使用默认路径)"
|
||||
|
||||
#: internal/apps/mysql/app.go:122
|
||||
#: internal/apps/mysql/app.go:125
|
||||
msgid "Peak Connections"
|
||||
msgstr "峰值连接数"
|
||||
|
||||
@@ -919,7 +919,7 @@ msgstr "移除网站"
|
||||
msgid "Restart panel service"
|
||||
msgstr "重启面板服务"
|
||||
|
||||
#: internal/apps/mysql/app.go:118
|
||||
#: internal/apps/mysql/app.go:121
|
||||
msgid "Rollbacks per Second"
|
||||
msgstr "每秒回滚"
|
||||
|
||||
@@ -991,7 +991,7 @@ msgstr "设置不存在"
|
||||
msgid "Slow Requests"
|
||||
msgstr "慢请求"
|
||||
|
||||
#: internal/apps/mysql/app.go:130
|
||||
#: internal/apps/mysql/app.go:133
|
||||
msgid "Sort Merge Passes"
|
||||
msgstr "排序后的合并次数"
|
||||
|
||||
@@ -1012,7 +1012,7 @@ msgstr "停止面板服务"
|
||||
msgid "Storage Usage"
|
||||
msgstr "存储使用"
|
||||
|
||||
#: internal/apps/mysql/app.go:129
|
||||
#: internal/apps/mysql/app.go:132
|
||||
msgid "Subqueries without Index"
|
||||
msgstr "没有索引的子查询"
|
||||
|
||||
@@ -1048,7 +1048,7 @@ msgstr "Sysvshm 是一个处理 V 共享内存的库"
|
||||
msgid "TCP Port"
|
||||
msgstr "TCP 端口"
|
||||
|
||||
#: internal/apps/mysql/app.go:131
|
||||
#: internal/apps/mysql/app.go:134
|
||||
msgid "Table Locks Waited"
|
||||
msgstr "锁表次数"
|
||||
|
||||
@@ -1056,7 +1056,7 @@ msgstr "锁表次数"
|
||||
msgid "Tasks cleared successfully"
|
||||
msgstr "任务已成功清除"
|
||||
|
||||
#: internal/apps/mysql/app.go:125
|
||||
#: internal/apps/mysql/app.go:128
|
||||
msgid "Temporary Tables Created on Disk"
|
||||
msgstr "创建临时表到磁盘"
|
||||
|
||||
@@ -1084,7 +1084,7 @@ msgstr "已分配内存总量"
|
||||
msgid "Total Commands Processed"
|
||||
msgstr "执行的命令总数"
|
||||
|
||||
#: internal/apps/mysql/app.go:116
|
||||
#: internal/apps/mysql/app.go:119
|
||||
#: internal/apps/postgresql/app.go:143
|
||||
msgid "Total Connections"
|
||||
msgstr "总连接数"
|
||||
@@ -1101,7 +1101,7 @@ msgstr "内存使用总数"
|
||||
msgid "Total Processes"
|
||||
msgstr "总进程数"
|
||||
|
||||
#: internal/apps/mysql/app.go:115
|
||||
#: internal/apps/mysql/app.go:118
|
||||
msgid "Total Queries"
|
||||
msgstr "查询总数"
|
||||
|
||||
@@ -1117,7 +1117,7 @@ msgstr "总握手数"
|
||||
msgid "Total requests"
|
||||
msgstr "总请求数"
|
||||
|
||||
#: internal/apps/mysql/app.go:117
|
||||
#: internal/apps/mysql/app.go:120
|
||||
msgid "Transactions per Second"
|
||||
msgstr "每秒事务"
|
||||
|
||||
@@ -1136,7 +1136,7 @@ msgstr "无法在离线模式下更新应用列表缓存"
|
||||
msgid "Uninstall PHP-%d %s extension"
|
||||
msgstr "卸载 PHP-%d %s 扩展"
|
||||
|
||||
#: internal/data/app.go:246
|
||||
#: internal/data/app.go:249
|
||||
msgid "Uninstall app %s"
|
||||
msgstr "卸载应用 %s"
|
||||
|
||||
@@ -1152,7 +1152,7 @@ msgstr "解压备份文件失败:%v"
|
||||
msgid "Unzip panel data failed: %v"
|
||||
msgstr "解压面板数据失败:%v"
|
||||
|
||||
#: internal/data/app.go:301
|
||||
#: internal/data/app.go:304
|
||||
msgid "Update app %s"
|
||||
msgstr "更新应用 %s"
|
||||
|
||||
@@ -1164,7 +1164,7 @@ msgstr "更新应用"
|
||||
msgid "Update panel"
|
||||
msgstr "更新面板"
|
||||
|
||||
#: internal/apps/mysql/app.go:114
|
||||
#: internal/apps/mysql/app.go:117
|
||||
msgid "Uptime"
|
||||
msgstr "运行时间"
|
||||
|
||||
@@ -1278,38 +1278,38 @@ msgstr "XSL 是用于处理 XML 文档的库"
|
||||
msgid "Zip is a library for handling ZIP files"
|
||||
msgstr "Zip 是一个用于处理 ZIP 文件的库"
|
||||
|
||||
#: internal/data/app.go:159
|
||||
#: internal/data/app.go:162
|
||||
msgid "app %s already installed"
|
||||
msgstr "应用 %s 已安装"
|
||||
|
||||
#: internal/data/app.go:56
|
||||
#: internal/data/app.go:59
|
||||
msgid "app %s not found"
|
||||
msgstr "找不到应用 %s"
|
||||
|
||||
#: internal/data/app.go:210
|
||||
#: internal/data/app.go:265
|
||||
#: internal/http/middleware/must_install.go:48
|
||||
#: internal/data/app.go:213
|
||||
#: internal/data/app.go:268
|
||||
#: internal/http/middleware/must_install.go:38
|
||||
msgid "app %s not installed"
|
||||
msgstr "应用 %s 未安装"
|
||||
|
||||
#: internal/data/app.go:179
|
||||
#: internal/data/app.go:289
|
||||
#: internal/data/app.go:182
|
||||
#: internal/data/app.go:292
|
||||
msgid "app %s not support current panel version"
|
||||
msgstr "应用 %s 不支持当前面板版本"
|
||||
|
||||
#: internal/data/app.go:170
|
||||
#: internal/data/app.go:225
|
||||
#: internal/data/app.go:280
|
||||
#: internal/data/app.go:173
|
||||
#: internal/data/app.go:228
|
||||
#: internal/data/app.go:283
|
||||
msgid "app %s requires panel version %s, current version %s"
|
||||
msgstr "应用 %s 需要面板版本 %s,当前版本 %s"
|
||||
|
||||
#: internal/http/middleware/must_install.go:29
|
||||
#: internal/http/middleware/must_install.go:24
|
||||
msgid "app not found"
|
||||
msgstr "应用未找到"
|
||||
|
||||
#: internal/data/setting.go:282
|
||||
#: internal/data/setting.go:343
|
||||
#: internal/data/setting.go:356
|
||||
#: internal/data/setting.go:283
|
||||
#: internal/data/setting.go:345
|
||||
#: internal/data/setting.go:358
|
||||
msgid "background task is running, modifying some settings is prohibited, please try again later"
|
||||
msgstr "后台任务正在运行,禁止修改某些设置,请稍后再试"
|
||||
|
||||
@@ -1331,7 +1331,7 @@ msgstr "备份文件 %s 不存在"
|
||||
msgid "can't download a directory"
|
||||
msgstr "无法下载目录"
|
||||
|
||||
#: internal/data/website.go:371
|
||||
#: internal/data/website.go:380
|
||||
msgid "can't find %s database server, please add it first"
|
||||
msgstr "找不到 %s 数据库服务器,请先添加"
|
||||
|
||||
@@ -1340,7 +1340,7 @@ msgstr "找不到 %s 数据库服务器,请先添加"
|
||||
msgid "check server connection failed"
|
||||
msgstr "检查服务器连接失败"
|
||||
|
||||
#: internal/http/middleware/must_login.go:93
|
||||
#: internal/http/middleware/must_login.go:69
|
||||
msgid "client ip/ua changed, please login again"
|
||||
msgstr "客户端 IP/UA 已更改,请重新登录"
|
||||
|
||||
@@ -1367,11 +1367,11 @@ msgstr "cron 日志目录 %s 不存在"
|
||||
msgid "database does not exist: %s"
|
||||
msgstr "数据库不存在:%s"
|
||||
|
||||
#: internal/data/website.go:725
|
||||
#: internal/data/website.go:734
|
||||
msgid "default document comment count is incorrect, expected 1, actual %d"
|
||||
msgstr "默认文档注释数量不正确,预期为1,实际为 %d"
|
||||
|
||||
#: internal/data/website.go:722
|
||||
#: internal/data/website.go:731
|
||||
msgid "default document comment not found"
|
||||
msgstr "未找到默认文档注释"
|
||||
|
||||
@@ -1439,7 +1439,7 @@ msgstr "获取 %s 服务启用状态失败:%v"
|
||||
msgid "failed to get %s service running status: %v"
|
||||
msgstr "获取 %s 服务运行状态失败:%v"
|
||||
|
||||
#: internal/data/website.go:306
|
||||
#: internal/data/website.go:315
|
||||
msgid "failed to get 404 template file: %v"
|
||||
msgstr "获取404模板文件失败:%v"
|
||||
|
||||
@@ -1512,7 +1512,7 @@ msgstr "获取磁盘空间失败: %v"
|
||||
msgid "failed to get home apps: %v"
|
||||
msgstr "获取主页应用失败: %v"
|
||||
|
||||
#: internal/data/website.go:294
|
||||
#: internal/data/website.go:303
|
||||
msgid "failed to get index template file: %v"
|
||||
msgstr "获取首页模板文件失败:%v"
|
||||
|
||||
@@ -1588,28 +1588,28 @@ msgstr "获取时区失败: %v"
|
||||
msgid "failed to get total banned list"
|
||||
msgstr "获取全部被禁止的列表失败"
|
||||
|
||||
#: internal/data/app.go:234
|
||||
#: internal/data/app.go:237
|
||||
msgid "failed to get uninstall script for app %s"
|
||||
msgstr "获取应用 %s 的卸载脚本失败"
|
||||
|
||||
#: internal/apps/mysql/app.go:188
|
||||
#: internal/apps/mysql/app.go:191
|
||||
#: internal/apps/mysql/app.go:83
|
||||
msgid "failed to load MySQL root password: %v"
|
||||
msgstr "加载 MySQL root 密码失败: %v"
|
||||
|
||||
#: internal/data/cert.go:92
|
||||
#: internal/data/setting.go:287
|
||||
#: internal/data/setting.go:359
|
||||
#: internal/data/website.go:475
|
||||
#: internal/data/website.go:764
|
||||
#: internal/data/setting.go:288
|
||||
#: internal/data/setting.go:361
|
||||
#: internal/data/website.go:484
|
||||
#: internal/data/website.go:773
|
||||
msgid "failed to parse certificate: %v"
|
||||
msgstr "解析证书失败: %v"
|
||||
|
||||
#: internal/data/cert.go:95
|
||||
#: internal/data/setting.go:290
|
||||
#: internal/data/setting.go:362
|
||||
#: internal/data/website.go:478
|
||||
#: internal/data/website.go:767
|
||||
#: internal/data/setting.go:291
|
||||
#: internal/data/setting.go:364
|
||||
#: internal/data/website.go:487
|
||||
#: internal/data/website.go:776
|
||||
msgid "failed to parse private key: %v"
|
||||
msgstr "解析私钥失败: %v"
|
||||
|
||||
@@ -1675,7 +1675,7 @@ msgstr "运行命令失败: %v"
|
||||
|
||||
#: internal/apps/mysql/app.go:99
|
||||
msgid "failed to set MYSQL_PWD env: %v"
|
||||
msgstr "设置 MYSQL_PWD 环境变量失败: %v"
|
||||
msgstr "设置 MYSQL_PWD 环境变量失败:%v"
|
||||
|
||||
#: internal/service/toolbox_system.go:159
|
||||
msgid "failed to set SWAP permission: %v"
|
||||
@@ -1705,6 +1705,10 @@ msgstr "停止 %s 服务失败: %v"
|
||||
msgid "failed to unmount: %v"
|
||||
msgstr "卸载失败: %v"
|
||||
|
||||
#: internal/apps/mysql/app.go:108
|
||||
msgid "failed to unset MYSQL_PWD env: %v"
|
||||
msgstr "取消设置 MYSQL_PWD 环境变量失败:%v"
|
||||
|
||||
#: internal/service/toolbox_system.go:63
|
||||
msgid "failed to update DNS: %v"
|
||||
msgstr "更新 DNS失败: %v"
|
||||
@@ -1725,13 +1729,13 @@ msgstr "gRPC 是一个高性能、开源和通用的 RPC 框架"
|
||||
msgid "get service port failed, please check if it is installed"
|
||||
msgstr "获取服务端口失败,请检查是否安装"
|
||||
|
||||
#: internal/data/user.go:175
|
||||
#: internal/data/user.go:198
|
||||
#: internal/service/user.go:102
|
||||
#: internal/data/user.go:180
|
||||
#: internal/data/user.go:199
|
||||
#: internal/service/user.go:96
|
||||
msgid "invalid 2FA code"
|
||||
msgstr "无效的两步验证代码"
|
||||
|
||||
#: internal/http/middleware/entrance.go:114
|
||||
#: internal/http/middleware/entrance.go:110
|
||||
msgid "invalid access entrance"
|
||||
msgstr "无效的访问入口"
|
||||
|
||||
@@ -1739,20 +1743,20 @@ msgstr "无效的访问入口"
|
||||
msgid "invalid header: %v"
|
||||
msgstr "无效的请求头: %v"
|
||||
|
||||
#: internal/service/user.go:84
|
||||
#: internal/service/user.go:82
|
||||
msgid "invalid key, please refresh the page"
|
||||
msgstr "无效的密钥,请刷新页面"
|
||||
|
||||
#: internal/http/middleware/entrance.go:57
|
||||
#: internal/http/middleware/entrance.go:43
|
||||
msgid "invalid request domain: %s"
|
||||
msgstr "无效的请求域名:%s"
|
||||
|
||||
#: internal/data/user_token.go:148
|
||||
#: internal/http/middleware/entrance.go:70
|
||||
#: internal/data/user_token.go:167
|
||||
#: internal/http/middleware/entrance.go:71
|
||||
msgid "invalid request ip: %s"
|
||||
msgstr "无效的请求 IP:%s"
|
||||
|
||||
#: internal/http/middleware/entrance.go:79
|
||||
#: internal/http/middleware/entrance.go:76
|
||||
msgid "invalid request user agent: %s"
|
||||
msgstr "无效的请求UA:%s"
|
||||
|
||||
@@ -1762,7 +1766,7 @@ msgstr "无效的请求UA:%s"
|
||||
msgid "invalid signature"
|
||||
msgstr "无效的签名"
|
||||
|
||||
#: internal/http/middleware/must_login.go:107
|
||||
#: internal/http/middleware/must_login.go:78
|
||||
msgid "invalid user id, please login again"
|
||||
msgstr "无效的用户ID,请重新登录"
|
||||
|
||||
@@ -1803,7 +1807,7 @@ msgstr "挂载路径不为空"
|
||||
msgid "mysql not support database comment"
|
||||
msgstr "mysql 不支持数据库注释"
|
||||
|
||||
#: internal/data/website.go:795
|
||||
#: internal/data/website.go:804
|
||||
msgid "not support one-key obtain wildcard certificate, please use Cert menu to obtain it with DNS method"
|
||||
msgstr "不支持一键获取通配符证书,请在证书菜单通过 DNS 方法获取"
|
||||
|
||||
@@ -1811,19 +1815,19 @@ msgstr "不支持一键获取通配符证书,请在证书菜单通过 DNS 方
|
||||
msgid "open file error: %v"
|
||||
msgstr "打开文件错误: %v"
|
||||
|
||||
#: internal/http/middleware/status.go:38
|
||||
#: internal/http/middleware/status.go:23
|
||||
msgid "panel is closed"
|
||||
msgstr "面板已关闭"
|
||||
|
||||
#: internal/http/middleware/status.go:30
|
||||
#: internal/http/middleware/status.go:20
|
||||
msgid "panel is maintaining, please refresh later"
|
||||
msgstr "面板正在维护,请稍后刷新"
|
||||
|
||||
#: internal/http/middleware/status.go:22
|
||||
#: internal/http/middleware/status.go:17
|
||||
msgid "panel is upgrading, please refresh later"
|
||||
msgstr "面板正在升级,请稍后刷新"
|
||||
|
||||
#: internal/http/middleware/status.go:46
|
||||
#: internal/http/middleware/status.go:26
|
||||
msgid "panel run error, please check or contact support"
|
||||
msgstr "面板运行错误,请检查或联系技术支持"
|
||||
|
||||
@@ -1848,7 +1852,7 @@ msgstr "没有找到 phpMyAdmin 目录"
|
||||
msgid "phpMyAdmin port not found"
|
||||
msgstr "没有找到 phpMyAdmin 端口"
|
||||
|
||||
#: internal/data/user.go:97
|
||||
#: internal/data/user.go:106
|
||||
#: internal/service/file.go:135
|
||||
#: internal/service/file.go:222
|
||||
#: internal/service/file.go:251
|
||||
@@ -1859,7 +1863,7 @@ msgstr "请不要花样作死"
|
||||
msgid "please retry the manual obtain operation"
|
||||
msgstr "请重新操作手动签发"
|
||||
|
||||
#: internal/data/setting.go:311
|
||||
#: internal/data/setting.go:312
|
||||
msgid "port is already in use"
|
||||
msgstr "端口已被占用"
|
||||
|
||||
@@ -1879,20 +1883,20 @@ msgstr "规则已存在"
|
||||
msgid "rule not found"
|
||||
msgstr "找不到规则"
|
||||
|
||||
#: internal/data/website.go:712
|
||||
#: internal/data/website.go:721
|
||||
msgid "runtime directory comment count is incorrect, expected 1, actual %d"
|
||||
msgstr "运行时目录注释数量不正确,预期为1,实际为%d"
|
||||
|
||||
#: internal/data/website.go:709
|
||||
#: internal/data/website.go:718
|
||||
msgid "runtime directory comment not found"
|
||||
msgstr "未找到运行目录注释"
|
||||
|
||||
#: internal/data/website.go:449
|
||||
#: internal/data/website.go:716
|
||||
#: internal/data/website.go:458
|
||||
#: internal/data/website.go:725
|
||||
msgid "runtime directory does not exist"
|
||||
msgstr "运行目录不存在"
|
||||
|
||||
#: internal/http/middleware/must_login.go:78
|
||||
#: internal/http/middleware/must_login.go:58
|
||||
msgid "session expired, please login again"
|
||||
msgstr "会话已过期,请重新登录"
|
||||
|
||||
@@ -1990,17 +1994,17 @@ msgstr "上传的证书无法设置为自动续签"
|
||||
msgid "upload file error: %v"
|
||||
msgstr "上传文件错误:%v"
|
||||
|
||||
#: internal/data/user.go:117
|
||||
#: internal/data/user.go:124
|
||||
#: internal/data/user.go:134
|
||||
#: internal/data/user.go:126
|
||||
#: internal/data/user.go:133
|
||||
#: internal/data/user.go:143
|
||||
msgid "username or password error"
|
||||
msgstr "用户名或密码错误"
|
||||
|
||||
#: internal/data/website.go:560
|
||||
#: internal/data/website.go:569
|
||||
msgid "website %s has bound certificates, please delete the certificate first"
|
||||
msgstr "网站 %s 已绑定证书,请先删除证书"
|
||||
|
||||
#: internal/data/website.go:456
|
||||
#: internal/data/website.go:465
|
||||
msgid "website directory does not exist"
|
||||
msgstr "网站目录不存在"
|
||||
|
||||
@@ -2013,7 +2017,7 @@ msgstr "通配符域名不能使用 HTTP 验证"
|
||||
msgid "write file error: %v"
|
||||
msgstr "写入文件错误:%v"
|
||||
|
||||
#: internal/http/middleware/must_login.go:58
|
||||
#: internal/http/middleware/must_login.go:48
|
||||
msgid "ws not allowed"
|
||||
msgstr "不允许 ws"
|
||||
|
||||
@@ -2194,7 +2198,7 @@ msgstr "|-运行更新后脚本……"
|
||||
#: internal/data/backup.go:684
|
||||
#: internal/data/backup.go:815
|
||||
msgid "|-Set key file permissions..."
|
||||
msgstr "|-设置密钥文件权限……"
|
||||
msgstr "|-设置关键文件权限……"
|
||||
|
||||
#: internal/data/backup.go:577
|
||||
msgid "|-Start fixing the panel..."
|
||||
|
||||
@@ -9,14 +9,14 @@ msgstr ""
|
||||
"X-Crowdin-Project: ratpanel\n"
|
||||
"X-Crowdin-Project-ID: 778640\n"
|
||||
"X-Crowdin-Language: zh-TW\n"
|
||||
"X-Crowdin-File: /main/pkg/embed/locales/backend.pot\n"
|
||||
"X-Crowdin-File: /[tnb-labs.panel] main/pkg/embed/locales/backend.pot\n"
|
||||
"X-Crowdin-File-ID: 922\n"
|
||||
"Project-Id-Version: ratpanel\n"
|
||||
"Language-Team: Chinese Traditional\n"
|
||||
"PO-Revision-Date: 2025-05-18 18:01\n"
|
||||
"PO-Revision-Date: 2025-07-07 10:34\n"
|
||||
|
||||
#: internal/data/website.go:270
|
||||
#: internal/data/website.go:643
|
||||
#: internal/data/website.go:279
|
||||
#: internal/data/website.go:652
|
||||
msgid "# Rewrite rule"
|
||||
msgstr "# 重寫規則"
|
||||
|
||||
@@ -36,7 +36,7 @@ msgstr "作者"
|
||||
msgid "Accepted Connections"
|
||||
msgstr "已接受連接"
|
||||
|
||||
#: internal/apps/mysql/app.go:121
|
||||
#: internal/apps/mysql/app.go:124
|
||||
msgid "Active Connections"
|
||||
msgstr "活動連接"
|
||||
|
||||
@@ -64,7 +64,7 @@ msgstr "阿里雲"
|
||||
msgid "Already initialized"
|
||||
msgstr "已經初始化過了"
|
||||
|
||||
#: internal/data/app.go:347
|
||||
#: internal/data/app.go:350
|
||||
msgid "App %s %s"
|
||||
msgstr "應用 %s %s"
|
||||
|
||||
@@ -142,13 +142,13 @@ msgstr "繫結 UA 已禁用"
|
||||
msgid "Bind domain disabled"
|
||||
msgstr "繫結網域已禁用"
|
||||
|
||||
#: internal/apps/mysql/app.go:120
|
||||
#: internal/apps/mysql/app.go:139
|
||||
#: internal/apps/mysql/app.go:123
|
||||
#: internal/apps/mysql/app.go:142
|
||||
msgid "Bytes Received"
|
||||
msgstr "已接收字節"
|
||||
|
||||
#: internal/apps/mysql/app.go:119
|
||||
#: internal/apps/mysql/app.go:139
|
||||
#: internal/apps/mysql/app.go:122
|
||||
#: internal/apps/mysql/app.go:142
|
||||
msgid "Bytes Sent"
|
||||
msgstr "已發送字節"
|
||||
|
||||
@@ -468,11 +468,11 @@ msgstr "修復面板"
|
||||
msgid "Forum:https://bbs.haozi.net"
|
||||
msgstr "論壇:https://bbs.haozi.net"
|
||||
|
||||
#: internal/apps/mysql/app.go:127
|
||||
#: internal/apps/mysql/app.go:130
|
||||
msgid "Full Joins without Index"
|
||||
msgstr "沒有使用索引的 Join"
|
||||
|
||||
#: internal/apps/mysql/app.go:128
|
||||
#: internal/apps/mysql/app.go:131
|
||||
msgid "Full Range Joins without Index"
|
||||
msgstr "沒有使用索引的範圍 Join"
|
||||
|
||||
@@ -552,7 +552,7 @@ msgstr "Igbinary 是一個用於序列化和反序列化數據的庫"
|
||||
msgid "ImageMagick is free software for creating, editing, and composing images"
|
||||
msgstr "ImageMagick 是一個創建、編輯、合成圖片的免費軟件"
|
||||
|
||||
#: internal/apps/mysql/app.go:123
|
||||
#: internal/apps/mysql/app.go:126
|
||||
msgid "Index Hit Rate"
|
||||
msgstr "索引命中率"
|
||||
|
||||
@@ -567,7 +567,7 @@ msgstr "初始化失敗:%v"
|
||||
msgid "Initialize panel (use only under guidance)"
|
||||
msgstr "初始化面板(僅在指導下使用)"
|
||||
|
||||
#: internal/apps/mysql/app.go:124
|
||||
#: internal/apps/mysql/app.go:127
|
||||
msgid "Innodb Index Hit Rate"
|
||||
msgstr "Innodb 索引命中率"
|
||||
|
||||
@@ -575,7 +575,7 @@ msgstr "Innodb 索引命中率"
|
||||
msgid "Install PHP-%d %s extension"
|
||||
msgstr "安裝 PHP-%d %s 擴展"
|
||||
|
||||
#: internal/data/app.go:191
|
||||
#: internal/data/app.go:194
|
||||
msgid "Install app %s"
|
||||
msgstr "安裝應用 %s"
|
||||
|
||||
@@ -740,7 +740,7 @@ msgstr "OPcache 將 PHP 腳本預編譯的位元組碼儲存到共享記憶體
|
||||
msgid "Old username cannot be empty"
|
||||
msgstr "舊用戶名不能為空"
|
||||
|
||||
#: internal/apps/mysql/app.go:126
|
||||
#: internal/apps/mysql/app.go:129
|
||||
msgid "Open Tables"
|
||||
msgstr "已開啟的表"
|
||||
|
||||
@@ -815,7 +815,7 @@ msgstr "密碼:%s"
|
||||
msgid "Path where the website is hosted (default path if not filled)"
|
||||
msgstr "網站託管的路徑(不填則使用默認路徑)"
|
||||
|
||||
#: internal/apps/mysql/app.go:122
|
||||
#: internal/apps/mysql/app.go:125
|
||||
msgid "Peak Connections"
|
||||
msgstr "峰值連接數"
|
||||
|
||||
@@ -919,7 +919,7 @@ msgstr "移除網站"
|
||||
msgid "Restart panel service"
|
||||
msgstr "重新啟動面板服務"
|
||||
|
||||
#: internal/apps/mysql/app.go:118
|
||||
#: internal/apps/mysql/app.go:121
|
||||
msgid "Rollbacks per Second"
|
||||
msgstr "每秒回滾"
|
||||
|
||||
@@ -991,7 +991,7 @@ msgstr "設置不存在"
|
||||
msgid "Slow Requests"
|
||||
msgstr "慢請求"
|
||||
|
||||
#: internal/apps/mysql/app.go:130
|
||||
#: internal/apps/mysql/app.go:133
|
||||
msgid "Sort Merge Passes"
|
||||
msgstr "排序後的合併次數"
|
||||
|
||||
@@ -1012,7 +1012,7 @@ msgstr "停止面板服務"
|
||||
msgid "Storage Usage"
|
||||
msgstr "存儲使用"
|
||||
|
||||
#: internal/apps/mysql/app.go:129
|
||||
#: internal/apps/mysql/app.go:132
|
||||
msgid "Subqueries without Index"
|
||||
msgstr "沒有索引的子查詢"
|
||||
|
||||
@@ -1048,7 +1048,7 @@ msgstr "Sysvshm 是一個處理系統 V 共享記憶體的庫"
|
||||
msgid "TCP Port"
|
||||
msgstr "TCP 連接埠"
|
||||
|
||||
#: internal/apps/mysql/app.go:131
|
||||
#: internal/apps/mysql/app.go:134
|
||||
msgid "Table Locks Waited"
|
||||
msgstr "等待表鎖定"
|
||||
|
||||
@@ -1056,7 +1056,7 @@ msgstr "等待表鎖定"
|
||||
msgid "Tasks cleared successfully"
|
||||
msgstr "任務已成功清除"
|
||||
|
||||
#: internal/apps/mysql/app.go:125
|
||||
#: internal/apps/mysql/app.go:128
|
||||
msgid "Temporary Tables Created on Disk"
|
||||
msgstr "在磁碟上創建的臨時表"
|
||||
|
||||
@@ -1084,7 +1084,7 @@ msgstr "已分配記憶體總量"
|
||||
msgid "Total Commands Processed"
|
||||
msgstr "已處理的命令總數"
|
||||
|
||||
#: internal/apps/mysql/app.go:116
|
||||
#: internal/apps/mysql/app.go:119
|
||||
#: internal/apps/postgresql/app.go:143
|
||||
msgid "Total Connections"
|
||||
msgstr "總連接數"
|
||||
@@ -1101,7 +1101,7 @@ msgstr "記憶體使用總量"
|
||||
msgid "Total Processes"
|
||||
msgstr "總進程數"
|
||||
|
||||
#: internal/apps/mysql/app.go:115
|
||||
#: internal/apps/mysql/app.go:118
|
||||
msgid "Total Queries"
|
||||
msgstr "查詢總數"
|
||||
|
||||
@@ -1117,7 +1117,7 @@ msgstr "總握手數"
|
||||
msgid "Total requests"
|
||||
msgstr "總請求數"
|
||||
|
||||
#: internal/apps/mysql/app.go:117
|
||||
#: internal/apps/mysql/app.go:120
|
||||
msgid "Transactions per Second"
|
||||
msgstr "每秒事務"
|
||||
|
||||
@@ -1136,7 +1136,7 @@ msgstr "無法在離線模式下更新應用程式列表快取"
|
||||
msgid "Uninstall PHP-%d %s extension"
|
||||
msgstr "卸載 PHP-%d %s 擴展"
|
||||
|
||||
#: internal/data/app.go:246
|
||||
#: internal/data/app.go:249
|
||||
msgid "Uninstall app %s"
|
||||
msgstr "卸載應用 %s"
|
||||
|
||||
@@ -1152,7 +1152,7 @@ msgstr "解壓備份檔案失敗:%v"
|
||||
msgid "Unzip panel data failed: %v"
|
||||
msgstr "解壓面板數據失敗:%v"
|
||||
|
||||
#: internal/data/app.go:301
|
||||
#: internal/data/app.go:304
|
||||
msgid "Update app %s"
|
||||
msgstr "更新應用 %s"
|
||||
|
||||
@@ -1164,7 +1164,7 @@ msgstr "更新應用"
|
||||
msgid "Update panel"
|
||||
msgstr "更新面板"
|
||||
|
||||
#: internal/apps/mysql/app.go:114
|
||||
#: internal/apps/mysql/app.go:117
|
||||
msgid "Uptime"
|
||||
msgstr "運行時間"
|
||||
|
||||
@@ -1278,38 +1278,38 @@ msgstr "XSL 是用於處理 XML 文檔的庫"
|
||||
msgid "Zip is a library for handling ZIP files"
|
||||
msgstr "Zip 是一個用於處理 ZIP 文件的庫"
|
||||
|
||||
#: internal/data/app.go:159
|
||||
#: internal/data/app.go:162
|
||||
msgid "app %s already installed"
|
||||
msgstr "應用 %s 已安裝"
|
||||
|
||||
#: internal/data/app.go:56
|
||||
#: internal/data/app.go:59
|
||||
msgid "app %s not found"
|
||||
msgstr "找不到應用 %s"
|
||||
|
||||
#: internal/data/app.go:210
|
||||
#: internal/data/app.go:265
|
||||
#: internal/http/middleware/must_install.go:48
|
||||
#: internal/data/app.go:213
|
||||
#: internal/data/app.go:268
|
||||
#: internal/http/middleware/must_install.go:38
|
||||
msgid "app %s not installed"
|
||||
msgstr "應用程式 %s 未安裝"
|
||||
|
||||
#: internal/data/app.go:179
|
||||
#: internal/data/app.go:289
|
||||
#: internal/data/app.go:182
|
||||
#: internal/data/app.go:292
|
||||
msgid "app %s not support current panel version"
|
||||
msgstr "應用程式 %s 不支援目前面板版本"
|
||||
|
||||
#: internal/data/app.go:170
|
||||
#: internal/data/app.go:225
|
||||
#: internal/data/app.go:280
|
||||
#: internal/data/app.go:173
|
||||
#: internal/data/app.go:228
|
||||
#: internal/data/app.go:283
|
||||
msgid "app %s requires panel version %s, current version %s"
|
||||
msgstr "應用程式 %s 需要面板版本 %s,目前版本 %s"
|
||||
|
||||
#: internal/http/middleware/must_install.go:29
|
||||
#: internal/http/middleware/must_install.go:24
|
||||
msgid "app not found"
|
||||
msgstr "找不到應用"
|
||||
|
||||
#: internal/data/setting.go:282
|
||||
#: internal/data/setting.go:343
|
||||
#: internal/data/setting.go:356
|
||||
#: internal/data/setting.go:283
|
||||
#: internal/data/setting.go:345
|
||||
#: internal/data/setting.go:358
|
||||
msgid "background task is running, modifying some settings is prohibited, please try again later"
|
||||
msgstr "後台任務正在運行,禁止修改某些設置,請稍後再試"
|
||||
|
||||
@@ -1331,7 +1331,7 @@ msgstr "備份檔案 %s 不存在"
|
||||
msgid "can't download a directory"
|
||||
msgstr "無法下載目錄"
|
||||
|
||||
#: internal/data/website.go:371
|
||||
#: internal/data/website.go:380
|
||||
msgid "can't find %s database server, please add it first"
|
||||
msgstr "找不到 %s 數據庫服務器,請先添加"
|
||||
|
||||
@@ -1340,7 +1340,7 @@ msgstr "找不到 %s 數據庫服務器,請先添加"
|
||||
msgid "check server connection failed"
|
||||
msgstr "檢查伺服器連接失敗"
|
||||
|
||||
#: internal/http/middleware/must_login.go:93
|
||||
#: internal/http/middleware/must_login.go:69
|
||||
msgid "client ip/ua changed, please login again"
|
||||
msgstr "客戶端 IP/UA 已更改,請重新登入"
|
||||
|
||||
@@ -1367,11 +1367,11 @@ msgstr "cron 日誌目錄 %s 不存在"
|
||||
msgid "database does not exist: %s"
|
||||
msgstr "資料庫不存在:%s"
|
||||
|
||||
#: internal/data/website.go:725
|
||||
#: internal/data/website.go:734
|
||||
msgid "default document comment count is incorrect, expected 1, actual %d"
|
||||
msgstr "默認文檔註釋數量不正確,預期為1,實際為 %d"
|
||||
|
||||
#: internal/data/website.go:722
|
||||
#: internal/data/website.go:731
|
||||
msgid "default document comment not found"
|
||||
msgstr "未找到預設文件註釋"
|
||||
|
||||
@@ -1439,7 +1439,7 @@ msgstr "無法獲取 %s 服務啟用狀態:%v"
|
||||
msgid "failed to get %s service running status: %v"
|
||||
msgstr "無法獲取 %s 服務運行狀態:%v"
|
||||
|
||||
#: internal/data/website.go:306
|
||||
#: internal/data/website.go:315
|
||||
msgid "failed to get 404 template file: %v"
|
||||
msgstr "獲取404模板文件失敗:%v"
|
||||
|
||||
@@ -1512,7 +1512,7 @@ msgstr "無法獲取磁盤空間:%v"
|
||||
msgid "failed to get home apps: %v"
|
||||
msgstr "無法獲取主頁應用:%v"
|
||||
|
||||
#: internal/data/website.go:294
|
||||
#: internal/data/website.go:303
|
||||
msgid "failed to get index template file: %v"
|
||||
msgstr "獲取首頁模板文件失敗:%v"
|
||||
|
||||
@@ -1588,28 +1588,28 @@ msgstr "無法獲取時區:%v"
|
||||
msgid "failed to get total banned list"
|
||||
msgstr "無法獲取全部被禁止的列表"
|
||||
|
||||
#: internal/data/app.go:234
|
||||
#: internal/data/app.go:237
|
||||
msgid "failed to get uninstall script for app %s"
|
||||
msgstr "無法取得應用程式 %s 的解除安裝腳本"
|
||||
|
||||
#: internal/apps/mysql/app.go:188
|
||||
#: internal/apps/mysql/app.go:191
|
||||
#: internal/apps/mysql/app.go:83
|
||||
msgid "failed to load MySQL root password: %v"
|
||||
msgstr "無法載入 MySQL root 密碼:%v"
|
||||
|
||||
#: internal/data/cert.go:92
|
||||
#: internal/data/setting.go:287
|
||||
#: internal/data/setting.go:359
|
||||
#: internal/data/website.go:475
|
||||
#: internal/data/website.go:764
|
||||
#: internal/data/setting.go:288
|
||||
#: internal/data/setting.go:361
|
||||
#: internal/data/website.go:484
|
||||
#: internal/data/website.go:773
|
||||
msgid "failed to parse certificate: %v"
|
||||
msgstr "無法解析證書:%v"
|
||||
|
||||
#: internal/data/cert.go:95
|
||||
#: internal/data/setting.go:290
|
||||
#: internal/data/setting.go:362
|
||||
#: internal/data/website.go:478
|
||||
#: internal/data/website.go:767
|
||||
#: internal/data/setting.go:291
|
||||
#: internal/data/setting.go:364
|
||||
#: internal/data/website.go:487
|
||||
#: internal/data/website.go:776
|
||||
msgid "failed to parse private key: %v"
|
||||
msgstr "解析私鑰失敗:%v"
|
||||
|
||||
@@ -1705,6 +1705,10 @@ msgstr "停止 %s 服務失敗:%v"
|
||||
msgid "failed to unmount: %v"
|
||||
msgstr "卸載失敗:%v"
|
||||
|
||||
#: internal/apps/mysql/app.go:108
|
||||
msgid "failed to unset MYSQL_PWD env: %v"
|
||||
msgstr "取消設置 MYSQL_PWD 環境變量失敗:%v"
|
||||
|
||||
#: internal/service/toolbox_system.go:63
|
||||
msgid "failed to update DNS: %v"
|
||||
msgstr "更新 DNS 失敗: %v"
|
||||
@@ -1725,13 +1729,13 @@ msgstr "gRPC 是一個高性能、開源和通用的 RPC 框架"
|
||||
msgid "get service port failed, please check if it is installed"
|
||||
msgstr "獲取服務端口失敗,請檢查是否安裝"
|
||||
|
||||
#: internal/data/user.go:175
|
||||
#: internal/data/user.go:198
|
||||
#: internal/service/user.go:102
|
||||
#: internal/data/user.go:180
|
||||
#: internal/data/user.go:199
|
||||
#: internal/service/user.go:96
|
||||
msgid "invalid 2FA code"
|
||||
msgstr "無效的兩步驗證代碼"
|
||||
|
||||
#: internal/http/middleware/entrance.go:114
|
||||
#: internal/http/middleware/entrance.go:110
|
||||
msgid "invalid access entrance"
|
||||
msgstr "無效的訪問入口"
|
||||
|
||||
@@ -1739,20 +1743,20 @@ msgstr "無效的訪問入口"
|
||||
msgid "invalid header: %v"
|
||||
msgstr "無效的請求標頭:%v"
|
||||
|
||||
#: internal/service/user.go:84
|
||||
#: internal/service/user.go:82
|
||||
msgid "invalid key, please refresh the page"
|
||||
msgstr "無效的密鑰,請重新整理頁面"
|
||||
|
||||
#: internal/http/middleware/entrance.go:57
|
||||
#: internal/http/middleware/entrance.go:43
|
||||
msgid "invalid request domain: %s"
|
||||
msgstr "無效的請求域名:%s"
|
||||
|
||||
#: internal/data/user_token.go:148
|
||||
#: internal/http/middleware/entrance.go:70
|
||||
#: internal/data/user_token.go:167
|
||||
#: internal/http/middleware/entrance.go:71
|
||||
msgid "invalid request ip: %s"
|
||||
msgstr "無效的請求 IP:%s"
|
||||
|
||||
#: internal/http/middleware/entrance.go:79
|
||||
#: internal/http/middleware/entrance.go:76
|
||||
msgid "invalid request user agent: %s"
|
||||
msgstr "無效的請求UA:%s"
|
||||
|
||||
@@ -1762,7 +1766,7 @@ msgstr "無效的請求UA:%s"
|
||||
msgid "invalid signature"
|
||||
msgstr "無效的簽名"
|
||||
|
||||
#: internal/http/middleware/must_login.go:107
|
||||
#: internal/http/middleware/must_login.go:78
|
||||
msgid "invalid user id, please login again"
|
||||
msgstr "無效的用戶ID,請重新登錄"
|
||||
|
||||
@@ -1803,7 +1807,7 @@ msgstr "掛載路徑不為空"
|
||||
msgid "mysql not support database comment"
|
||||
msgstr "mysql 不支援資料庫註釋"
|
||||
|
||||
#: internal/data/website.go:795
|
||||
#: internal/data/website.go:804
|
||||
msgid "not support one-key obtain wildcard certificate, please use Cert menu to obtain it with DNS method"
|
||||
msgstr "不支持一鍵獲取通配符證書,請在證書菜單通過 DNS 方法獲取"
|
||||
|
||||
@@ -1811,19 +1815,19 @@ msgstr "不支持一鍵獲取通配符證書,請在證書菜單通過 DNS 方
|
||||
msgid "open file error: %v"
|
||||
msgstr "開啟檔案錯誤:%v"
|
||||
|
||||
#: internal/http/middleware/status.go:38
|
||||
#: internal/http/middleware/status.go:23
|
||||
msgid "panel is closed"
|
||||
msgstr "面板已關閉"
|
||||
|
||||
#: internal/http/middleware/status.go:30
|
||||
#: internal/http/middleware/status.go:20
|
||||
msgid "panel is maintaining, please refresh later"
|
||||
msgstr "面板正在維護,請稍後刷新"
|
||||
|
||||
#: internal/http/middleware/status.go:22
|
||||
#: internal/http/middleware/status.go:17
|
||||
msgid "panel is upgrading, please refresh later"
|
||||
msgstr "面板正在升級,請稍後刷新"
|
||||
|
||||
#: internal/http/middleware/status.go:46
|
||||
#: internal/http/middleware/status.go:26
|
||||
msgid "panel run error, please check or contact support"
|
||||
msgstr "面板運行錯誤,請檢查或聯繫技術支持"
|
||||
|
||||
@@ -1848,7 +1852,7 @@ msgstr "未找到 phpMyAdmin 目錄"
|
||||
msgid "phpMyAdmin port not found"
|
||||
msgstr "未找到 phpMyAdmin 端口"
|
||||
|
||||
#: internal/data/user.go:97
|
||||
#: internal/data/user.go:106
|
||||
#: internal/service/file.go:135
|
||||
#: internal/service/file.go:222
|
||||
#: internal/service/file.go:251
|
||||
@@ -1859,7 +1863,7 @@ msgstr "請不要這樣做"
|
||||
msgid "please retry the manual obtain operation"
|
||||
msgstr "請重新操作手動簽發"
|
||||
|
||||
#: internal/data/setting.go:311
|
||||
#: internal/data/setting.go:312
|
||||
msgid "port is already in use"
|
||||
msgstr "端口已被佔用"
|
||||
|
||||
@@ -1879,20 +1883,20 @@ msgstr "規則已存在"
|
||||
msgid "rule not found"
|
||||
msgstr "找不到規則"
|
||||
|
||||
#: internal/data/website.go:712
|
||||
#: internal/data/website.go:721
|
||||
msgid "runtime directory comment count is incorrect, expected 1, actual %d"
|
||||
msgstr "運行時目錄註釋數量不正確,預期為1,實際為%d"
|
||||
|
||||
#: internal/data/website.go:709
|
||||
#: internal/data/website.go:718
|
||||
msgid "runtime directory comment not found"
|
||||
msgstr "未找到運行目錄註釋"
|
||||
|
||||
#: internal/data/website.go:449
|
||||
#: internal/data/website.go:716
|
||||
#: internal/data/website.go:458
|
||||
#: internal/data/website.go:725
|
||||
msgid "runtime directory does not exist"
|
||||
msgstr "運行目錄不存在"
|
||||
|
||||
#: internal/http/middleware/must_login.go:78
|
||||
#: internal/http/middleware/must_login.go:58
|
||||
msgid "session expired, please login again"
|
||||
msgstr "會話已過期,請重新登錄"
|
||||
|
||||
@@ -1990,17 +1994,17 @@ msgstr "上傳的憑證無法設定為自動續簽"
|
||||
msgid "upload file error: %v"
|
||||
msgstr "上傳檔案錯誤:%v"
|
||||
|
||||
#: internal/data/user.go:117
|
||||
#: internal/data/user.go:124
|
||||
#: internal/data/user.go:134
|
||||
#: internal/data/user.go:126
|
||||
#: internal/data/user.go:133
|
||||
#: internal/data/user.go:143
|
||||
msgid "username or password error"
|
||||
msgstr "使用者名稱或密碼錯誤"
|
||||
|
||||
#: internal/data/website.go:560
|
||||
#: internal/data/website.go:569
|
||||
msgid "website %s has bound certificates, please delete the certificate first"
|
||||
msgstr "網站 %s 已綁定證書,請先刪除證書"
|
||||
|
||||
#: internal/data/website.go:456
|
||||
#: internal/data/website.go:465
|
||||
msgid "website directory does not exist"
|
||||
msgstr "網站目錄不存在"
|
||||
|
||||
@@ -2013,7 +2017,7 @@ msgstr "萬用字元網域無法使用 HTTP 驗證"
|
||||
msgid "write file error: %v"
|
||||
msgstr "寫入檔案錯誤:%v"
|
||||
|
||||
#: internal/http/middleware/must_login.go:58
|
||||
#: internal/http/middleware/must_login.go:48
|
||||
msgid "ws not allowed"
|
||||
msgstr "不允許 ws"
|
||||
|
||||
@@ -2194,7 +2198,7 @@ msgstr "|-執行更新後腳本……"
|
||||
#: internal/data/backup.go:684
|
||||
#: internal/data/backup.go:815
|
||||
msgid "|-Set key file permissions..."
|
||||
msgstr "|-設定金鑰檔案權限……"
|
||||
msgstr "|-設定關鍵檔案權限……"
|
||||
|
||||
#: internal/data/backup.go:577
|
||||
msgid "|-Start fixing the panel..."
|
||||
|
||||
@@ -207,6 +207,11 @@ func (r *Firewall) Port(rule FireInfo, operation Operation) error {
|
||||
}
|
||||
|
||||
func (r *Firewall) RichRules(rule FireInfo, operation Operation) error {
|
||||
// 出站规则下,必须指定具体的地址,否则会添加成入站规则
|
||||
if rule.Direction == "out" && rule.Address == "" {
|
||||
return fmt.Errorf("outbound rules must specify an address")
|
||||
}
|
||||
|
||||
protocols := strings.Split(string(rule.Protocol), "/")
|
||||
for protocol := range slices.Values(protocols) {
|
||||
var ruleBuilder strings.Builder
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@
|
||||
"github.com/libdns/cloudflare",
|
||||
"github.com/libdns/tencentcloud",
|
||||
"github.com/libdns/duckdns",
|
||||
"github.com/libdns/gcore"
|
||||
"github.com/libdns/gcore",
|
||||
"github.com/libdns/cloudns"
|
||||
]
|
||||
}
|
||||
|
||||
+39
-40
@@ -26,12 +26,12 @@
|
||||
},
|
||||
"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": "^13.0.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.2.5",
|
||||
"@guolao/vue-monaco-editor": "^1.5.5",
|
||||
"@vavt/copy2clipboard": "^1.0.3",
|
||||
"@vue-js-cron/naive-ui": "^2.0.11",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"@xterm/addon-attach": "^0.11.0",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
@@ -39,60 +39,59 @@
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/addon-webgl": "^0.18.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"alova": "^3.2.7",
|
||||
"cronstrue": "^2.52.0",
|
||||
"alova": "^3.2.11",
|
||||
"cronstrue": "^3.0.0",
|
||||
"echarts": "^5.6.0",
|
||||
"install": "^0.13.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.5.0",
|
||||
"marked": "^15.0.4",
|
||||
"luxon": "^3.6.1",
|
||||
"marked": "^16.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"pinia": "^3.0.0",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"remove": "^0.1.5",
|
||||
"vue": "^3.5.13",
|
||||
"vue": "^3.5.14",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-gettext": "4.0.0-alpha.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.290",
|
||||
"@iconify/json": "^2.2.341",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@rushstack/eslint-patch": "^1.10.4",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@rushstack/eslint-patch": "^1.11.0",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/luxon": "^3.6.2",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@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",
|
||||
"@unocss/eslint-config": "^66.1.2",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.5.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"colord": "^2.9.3",
|
||||
"cpx2": "^8.0.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"md-editor-v3": "^5.1.1",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-vue": "^10.1.0",
|
||||
"md-editor-v3": "^5.6.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"naive-ui": "^2.40.4",
|
||||
"npm-run-all2": "^8.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"naive-ui": "^2.41.0",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"sass": "^1.83.0",
|
||||
"tsx": "^4.19.2",
|
||||
"sass": "^1.89.0",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "5.8.3",
|
||||
"unocss": "^66.0.0",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
"unplugin-icons": "^22.0.0",
|
||||
"unplugin-vue-components": "^28.0.0",
|
||||
"vite": "^6.0.6",
|
||||
"unocss": "^66.1.2",
|
||||
"unplugin-auto-import": "^19.3.0",
|
||||
"unplugin-icons": "^22.1.0",
|
||||
"unplugin-vue-components": "^28.7.0",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-mock": "^3.0.2",
|
||||
"vite-plugin-static-copy": "^3.0.0",
|
||||
"vite-plugin-vue-devtools": "^7.6.8",
|
||||
"vue-tsc": "^2.2.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
|
||||
"vite-plugin-vue-devtools": "^7.7.6",
|
||||
"vue-tsc": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1372
-1916
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,9 @@ export default {
|
||||
http.Post('/users', { username, password, email }),
|
||||
// 删除用户
|
||||
delete: (id: number): any => http.Delete(`/users/${id}`),
|
||||
// 更新用户用户名
|
||||
updateUsername: (id: number, username: string): any =>
|
||||
http.Post(`/users/${id}/username`, { username }),
|
||||
// 更新用户邮箱
|
||||
updateEmail: (id: number, email: string): any => http.Post(`/users/${id}/email`, { email }),
|
||||
// 更新用户密码
|
||||
|
||||
+259
-239
File diff suppressed because it is too large
Load Diff
+264
-244
File diff suppressed because it is too large
Load Diff
+264
-244
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import { useThemeStore } from '@/store'
|
||||
export function createPageTitleGuard(router: Router) {
|
||||
const themeStore = useThemeStore()
|
||||
router.afterEach((to) => {
|
||||
const pageTitle = translateTitle(String(to.meta.title))
|
||||
const pageTitle = typeof to.meta.title === 'string' ? translateTitle(to.meta.title) : '404'
|
||||
if (pageTitle) document.title = `${pageTitle} | ${themeStore.name}`
|
||||
else document.title = themeStore.name
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ export const basicRoutes: RoutesType = [
|
||||
component: () => import('@/views/login/IndexView.vue'),
|
||||
isHidden: true,
|
||||
meta: {
|
||||
title: $gettext('Login Page')
|
||||
title: $gettext('Login')
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -68,6 +68,7 @@ const handleClose = () => {
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="handleClose"
|
||||
@mask-click="handleClose"
|
||||
>
|
||||
<n-form :model="model">
|
||||
<n-form-item path="channel" :label="$gettext('Channel')">
|
||||
|
||||
@@ -568,6 +568,7 @@ onUnmounted(() => {
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="handleShowModalClose"
|
||||
@mask-click="handleShowModalClose"
|
||||
>
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane name="cert" :tab="$gettext('Certificate')">
|
||||
|
||||
@@ -57,7 +57,7 @@ const columns: any = [
|
||||
{
|
||||
title: $gettext('Status'),
|
||||
key: 'status',
|
||||
width: 150,
|
||||
width: 250,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
@@ -165,10 +165,18 @@ const columns: any = [
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => {
|
||||
useRequest(container.composeDown(row.name)).onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success($gettext('Stop successful'))
|
||||
const messageReactive = window.$message.loading($gettext('Stopping...'), {
|
||||
duration: 0
|
||||
})
|
||||
useRequest(container.composeDown(row.name))
|
||||
.onSuccess(() => {
|
||||
refresh()
|
||||
forcePush.value = false
|
||||
window.$message.success($gettext('Stop successful'))
|
||||
})
|
||||
.onComplete(() => {
|
||||
messageReactive?.destroy()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -290,10 +298,10 @@ onMounted(() => {
|
||||
striped
|
||||
remote
|
||||
:loading="loading"
|
||||
:scroll-x="1000"
|
||||
:scroll-x="1100"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:row-key="(row: any) => row.id"
|
||||
:row-key="(row: any) => row.name"
|
||||
v-model:page="page"
|
||||
v-model:pageSize="pageSize"
|
||||
:pagination="{
|
||||
|
||||
@@ -132,6 +132,7 @@ onMounted(() => {
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="handleClose"
|
||||
@mask-click="handleClose"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="name" :label="$gettext('Container Name')">
|
||||
|
||||
@@ -69,8 +69,12 @@ const columns: any = [
|
||||
default: () =>
|
||||
row.ports.map((port: any) =>
|
||||
h(NTag, null, {
|
||||
default: () =>
|
||||
`${port.host ? port.host + ':' : ''}${port.container_start}->${port.host_start}/${port.protocol}`
|
||||
default: () => {
|
||||
if (port.container_start == port.container_end) {
|
||||
return `${port.host ? port.host + ':' : ''}${port.host_start}->${port.container_start}/${port.protocol}`
|
||||
}
|
||||
return `${port.host ? port.host + ':' : ''}${port.host_start}-${port.host_end}->${port.container_start}-${port.container_end}/${port.protocol}`
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -107,7 +107,7 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
|
||||
)
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
useRequest(container.volumeRemove(row.id)).onSuccess(() => {
|
||||
useRequest(container.volumeRemove(row.name)).onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success($gettext('Delete successful'))
|
||||
})
|
||||
@@ -155,7 +155,7 @@ onMounted(() => {
|
||||
:scroll-x="1000"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:row-key="(row: any) => row.id"
|
||||
:row-key="(row: any) => row.name"
|
||||
v-model:checked-row-keys="selectedRowKeys"
|
||||
v-model:page="page"
|
||||
v-model:pageSize="pageSize"
|
||||
|
||||
@@ -34,7 +34,8 @@ watch(
|
||||
() => show.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
useRequest(database.serverList(1, 10000)).onSuccess(({ data }) => {
|
||||
useRequest(database.serverList(1, 10000)).onSuccess(({ data }: { data: any }) => {
|
||||
servers.value = []
|
||||
for (const server of data.items) {
|
||||
servers.value.push({
|
||||
label: server.name,
|
||||
|
||||
@@ -34,7 +34,8 @@ watch(
|
||||
() => show.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
useRequest(database.serverList(1, 10000)).onSuccess(({ data }) => {
|
||||
useRequest(database.serverList(1, 10000)).onSuccess(({ data }: { data: any }) => {
|
||||
servers.value = []
|
||||
for (const server of data.items) {
|
||||
servers.value.push({
|
||||
label: server.name,
|
||||
@@ -58,64 +59,71 @@ watch(
|
||||
:segmented="false"
|
||||
@close="show = false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="server_id" :label="$gettext('Server')">
|
||||
<n-select
|
||||
v-model:value="createModel.server_id"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Select server')"
|
||||
:options="servers"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="username" :label="$gettext('Username')">
|
||||
<n-input
|
||||
v-model:value="createModel.username"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter username')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="password" :label="$gettext('Password')">
|
||||
<n-input
|
||||
v-model:value="createModel.password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter password')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="host-select" :label="$gettext('Host (MySQL only)')">
|
||||
<n-select
|
||||
v-model:value="createModel.host"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Select host')"
|
||||
:options="hostType"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="createModel.host === ''" path="host" :label="$gettext('Specific Host')">
|
||||
<n-input
|
||||
v-model:value="createModel.host"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter supported host address')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="privileges" :label="$gettext('Privileges')">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.privileges"
|
||||
:placeholder="$gettext('Enter database name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$gettext('Comment')">
|
||||
<n-input
|
||||
v-model:value="createModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter database user comment')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleCreate">{{ $gettext('Submit') }}</n-button>
|
||||
<n-flex vertical>
|
||||
<n-alert type="info">
|
||||
{{
|
||||
$gettext('If the privilege databases does not exist, it will be created automatically.')
|
||||
}}
|
||||
</n-alert>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="server_id" :label="$gettext('Server')">
|
||||
<n-select
|
||||
v-model:value="createModel.server_id"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Select server')"
|
||||
:options="servers"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="username" :label="$gettext('Username')">
|
||||
<n-input
|
||||
v-model:value="createModel.username"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter username')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="password" :label="$gettext('Password')">
|
||||
<n-input
|
||||
v-model:value="createModel.password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter password')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="host-select" :label="$gettext('Host (MySQL only)')">
|
||||
<n-select
|
||||
v-model:value="createModel.host"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Select host')"
|
||||
:options="hostType"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="createModel.host === ''" path="host" :label="$gettext('Specific Host')">
|
||||
<n-input
|
||||
v-model:value="createModel.host"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter supported host address')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="privileges" :label="$gettext('Privileges')">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.privileges"
|
||||
:placeholder="$gettext('Enter database name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$gettext('Comment')">
|
||||
<n-input
|
||||
v-model:value="createModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter database user comment')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleCreate">{{ $gettext('Submit') }}</n-button>
|
||||
</n-flex>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ watch(
|
||||
() => show.value,
|
||||
(value) => {
|
||||
if (value && id.value) {
|
||||
useRequest(database.serverGet(id.value)).onSuccess(({ data }) => {
|
||||
useRequest(database.serverGet(id.value)).onSuccess(({ data }: { data: any }) => {
|
||||
updateModel.value.name = data.name
|
||||
updateModel.value.host = data.host
|
||||
updateModel.value.port = data.port
|
||||
|
||||
@@ -24,7 +24,7 @@ watch(
|
||||
() => show.value,
|
||||
(value) => {
|
||||
if (value && id.value) {
|
||||
useRequest(database.userGet(id.value)).onSuccess(({ data }) => {
|
||||
useRequest(database.userGet(id.value)).onSuccess(({ data }: { data: any }) => {
|
||||
updateModel.value.password = data.password
|
||||
updateModel.value.privileges = data.privileges
|
||||
updateModel.value.remark = data.remark
|
||||
@@ -45,32 +45,39 @@ watch(
|
||||
:segmented="false"
|
||||
@close="show = false"
|
||||
>
|
||||
<n-form :model="updateModel">
|
||||
<n-form-item path="password" :label="$gettext('Password')">
|
||||
<n-input
|
||||
v-model:value="updateModel.password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter password')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="privileges" :label="$gettext('Privileges')">
|
||||
<n-dynamic-input
|
||||
v-model:value="updateModel.privileges"
|
||||
:placeholder="$gettext('Enter database name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$gettext('Comment')">
|
||||
<n-input
|
||||
v-model:value="updateModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter database user comment')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleUpdate">{{ $gettext('Submit') }}</n-button>
|
||||
<n-flex vertical>
|
||||
<n-alert type="info">
|
||||
{{
|
||||
$gettext('If the privilege databases does not exist, it will be created automatically.')
|
||||
}}
|
||||
</n-alert>
|
||||
<n-form :model="updateModel">
|
||||
<n-form-item path="password" :label="$gettext('Password')">
|
||||
<n-input
|
||||
v-model:value="updateModel.password"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter password')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="privileges" :label="$gettext('Privileges')">
|
||||
<n-dynamic-input
|
||||
v-model:value="updateModel.privileges"
|
||||
:placeholder="$gettext('Enter database name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$gettext('Comment')">
|
||||
<n-input
|
||||
v-model:value="updateModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Enter database user comment')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleUpdate">{{ $gettext('Submit') }}</n-button>
|
||||
</n-flex>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -104,11 +104,7 @@ const handleCreate = async () => {
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.address"
|
||||
show-sort-button
|
||||
:placeholder="
|
||||
$gettext(
|
||||
'Optional IP or IP range: 127.0.0.1 or 172.16.0.0/24 (multiple separated by commas)'
|
||||
)
|
||||
"
|
||||
:placeholder="$gettext('IP or IP range: 172.16.0.1 or 172.16.0.0/16')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="strategy" :label="$gettext('Strategy')">
|
||||
|
||||
@@ -64,25 +64,23 @@ const createModel = ref({
|
||||
protocol: 'tcp',
|
||||
port_start: 80,
|
||||
port_end: 80,
|
||||
address: '',
|
||||
address: [],
|
||||
strategy: 'accept',
|
||||
direction: 'in'
|
||||
})
|
||||
|
||||
const handleCreate = async () => {
|
||||
useRequest(firewall.createRule(createModel.value)).onSuccess(() => {
|
||||
show.value = false
|
||||
createModel.value = {
|
||||
family: 'ipv4',
|
||||
protocol: 'tcp',
|
||||
port_start: 80,
|
||||
port_end: 80,
|
||||
address: '',
|
||||
strategy: 'accept',
|
||||
direction: 'in'
|
||||
}
|
||||
window.$message.success($gettext('Created successfully'))
|
||||
})
|
||||
for (const address of createModel.value.address) {
|
||||
useRequest(
|
||||
firewall.createRule({
|
||||
...createModel.value,
|
||||
address
|
||||
})
|
||||
).onSuccess(() => {
|
||||
window.$message.success($gettext('%{ address } created successfully', { address: address }))
|
||||
show.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -127,13 +125,10 @@ const handleCreate = async () => {
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-form-item path="address" :label="$gettext('Target')">
|
||||
<n-input
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.address"
|
||||
:placeholder="
|
||||
$gettext(
|
||||
'Optional IP or IP range: 127.0.0.1 or 172.16.0.0/24 (multiple separated by commas)'
|
||||
)
|
||||
"
|
||||
show-sort-button
|
||||
:placeholder="$gettext('IP or IP range: 172.16.0.1 or 172.16.0.0/16')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="strategy" :label="$gettext('Strategy')">
|
||||
|
||||
@@ -23,7 +23,7 @@ const model = defineModel<any>('model', { type: Object, required: true })
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Access Entrance')">
|
||||
<n-input v-model:value="model.entrance" :placeholder="$gettext('admin')" />
|
||||
<n-input v-model:value="model.entrance" :placeholder="$gettext('/admin')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Bind Domain')">
|
||||
<n-dynamic-input
|
||||
|
||||
@@ -20,7 +20,17 @@ const columns: any = [
|
||||
key: 'username',
|
||||
minWidth: 100,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any) {
|
||||
return h(NInput, {
|
||||
size: 'small',
|
||||
value: row.username,
|
||||
onBlur: () => handleUsername(row),
|
||||
onUpdateValue(v) {
|
||||
row.username = v
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('Email'),
|
||||
@@ -151,6 +161,12 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
|
||||
}
|
||||
)
|
||||
|
||||
const handleUsername = (row: any) => {
|
||||
useRequest(user.updateUsername(row.id, row.username)).onSuccess(() => {
|
||||
window.$message.success($gettext('Modified successfully'))
|
||||
})
|
||||
}
|
||||
|
||||
const handleEmail = (row: any) => {
|
||||
useRequest(user.updateEmail(row.id, row.email)).onSuccess(() => {
|
||||
window.$message.success($gettext('Modified successfully'))
|
||||
|
||||
@@ -73,6 +73,38 @@ const columns: any = [
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('Certificate expiration'),
|
||||
key: 'cert_expire',
|
||||
width: 200,
|
||||
render(row: any) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: row.cert_expire == 0 ? 'default' : row.cert_expire > 0 ? 'success' : 'error',
|
||||
class: 'cursor-pointer hover:opacity-60',
|
||||
onClick: () => handleEdit(row)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
if (row.cert_expire == 0) {
|
||||
return $gettext('Not configured')
|
||||
}
|
||||
if (row.cert_expire < 0) {
|
||||
return $gettext('Expired %{ days } days ago', {
|
||||
days: Math.abs(row.cert_expire)
|
||||
})
|
||||
}
|
||||
if (row.cert_expire > 0) {
|
||||
return $gettext('Expires in %{ days } days', {
|
||||
days: row.cert_expire
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('Remark'),
|
||||
key: 'remark',
|
||||
@@ -378,7 +410,7 @@ onMounted(() => {
|
||||
striped
|
||||
remote
|
||||
:loading="loading"
|
||||
:scroll-x="1200"
|
||||
:scroll-x="1400"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-key="(row: any) => row.id"
|
||||
|
||||
Reference in New Issue
Block a user