mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44045da4eb | |||
| baf90c0602 | |||
| f7e67c3797 | |||
| d7a79fa53e | |||
| b9b3b9fedf | |||
| e27940a71b | |||
| aa22933b36 | |||
| a1dceaaa82 | |||
| ebd5d37e78 | |||
| 107d2ec658 | |||
| 3a70d80bb5 | |||
| 7c58d8dcce | |||
| bde93b488c | |||
| 77aa4bea34 | |||
| f7ba602a50 | |||
| 3101486a44 | |||
| b51a8e1d03 | |||
| 83f58e0a2e | |||
| e941ab6661 | |||
| 688801eebb | |||
| 8ad42f8c0e | |||
| 02c8809785 | |||
| fd0e43fb26 | |||
| 96ec795096 | |||
| 5ea191b550 | |||
| c37cc3bd2c | |||
| 110f305f16 | |||
| 179b6ff83e | |||
| 0da1b0b565 | |||
| 0da3f7b8e0 | |||
| c6f9a585b0 | |||
| 686bdd9ae2 | |||
| e3a22f924e | |||
| 0c3c47361b | |||
| c3f9f56f11 | |||
| 76e992cc9e | |||
| 73cc5a35ad | |||
| 873520e9f4 | |||
| af3254c11a | |||
| 5575d34ab4 | |||
| a995c56f1b | |||
| 1fbea148e3 | |||
| ca504e1acc | |||
| 18fe84117d | |||
| 9eb9a2c7d6 | |||
| de7d50ce51 | |||
| 6e0d93257f | |||
| 96806d4e94 | |||
| 3de78c95bf | |||
| 367348e688 | |||
| 07dfcd778c | |||
| 789d5e7ddd | |||
| 1102aba9a3 | |||
| fddb076136 | |||
| b1ff8fff3d | |||
| 5cd68fcdc3 | |||
| 3a5ff9179a | |||
| fa7c54ada5 | |||
| 94047174d1 | |||
| 305777f65a | |||
| d56d3ec1dd | |||
| 8ac8da7914 | |||
| 5d56663b69 | |||
| 7ff574cb92 | |||
| 00b4153a28 | |||
| 9412b31279 | |||
| bbf04c91cf | |||
| 252456a1cd | |||
| 8de68b317d | |||
| 8bf273e1fd | |||
| 32111ecd7a | |||
| 82b8a32133 | |||
| b82c644d81 | |||
| 06bf1b0604 | |||
| 7b29e4546a | |||
| ae6b354a78 | |||
| d96c616c61 | |||
| 213698dd2a | |||
| 39b3c74684 | |||
| b3a1af1001 | |||
| 91b244276a | |||
| b1eefd062d | |||
| 2edf6748e7 | |||
| eac69b8dab | |||
| bb7b641193 | |||
| 3b0aee1967 | |||
| 2ae7f3e458 | |||
| ee0442d159 | |||
| 363b7a55be | |||
| 3437bb2b1f | |||
| 7e000cddc8 | |||
| a18652f134 | |||
| dcd8bb3dd9 | |||
| d62a280fe9 | |||
| c3a7710e6f | |||
| 7f1491f739 | |||
| c96b88548e | |||
| f62b112e1b | |||
| e55e3402e9 | |||
| 48b539b31a | |||
| d35245aa11 | |||
| 7f46dea052 | |||
| 48ed174dc6 | |||
| a5743f94b2 | |||
| 227c5344e3 | |||
| 4c8a88ea03 | |||
| 3503aac2f6 | |||
| 6beda2ea12 | |||
| 4579bf8fa9 | |||
| 002db34b4d | |||
| e276c4f747 | |||
| f5e6219ca4 | |||
| fd92b1d360 | |||
| 5b8289c0b0 | |||
| d8fe050607 | |||
| c0310d916f | |||
| 77ed2d77e1 | |||
| c331143e9e | |||
| 71e75312ff | |||
| 0e5bee4852 | |||
| 036ad9222a | |||
| 39cd5545c5 | |||
| 4568e60133 | |||
| d2f224eaa6 | |||
| c77713b3d9 | |||
| f1e2ba5c35 | |||
| 243a36f41c | |||
| 74f772b705 | |||
| e8f1ed5dec | |||
| 634d8c643b | |||
| 1829b46466 | |||
| 478efa5016 | |||
| 841e0e351f | |||
| 0e9761f975 | |||
| 5c067e28b7 | |||
| 0a0e0bcac1 | |||
| c3732b904d | |||
| 161370ac5a | |||
| 0cfe585ad5 | |||
| 283d9dbe26 | |||
| 45ed3f2ff9 | |||
| 5703e3354f | |||
| da2e66b7c6 | |||
| 8dca4c1cbf | |||
| a295eb9a65 | |||
| 868ee73d51 | |||
| ef7d38f2af | |||
| 801ff28854 | |||
| e0a401f688 | |||
| f50f2f7f2b | |||
| 3df1388326 | |||
| 5390ef3140 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: haozi-panel
|
||||
open_collective: tnb
|
||||
custom: ['https://afdian.net/a/TheTNB']
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
go-version: '1.22'
|
||||
- name: Install dependencies
|
||||
run: go mod tidy
|
||||
- name: Build ${{ matrix.goarch }}
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.22'
|
||||
- name: Install dependencies
|
||||
run: go mod tidy
|
||||
- name: Run tests with coverage
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.22'
|
||||
- name: Fetch Frontend
|
||||
run: |
|
||||
sudo apt-get install -y curl jq unzip zip
|
||||
|
||||
@@ -14,13 +14,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.22'
|
||||
cache: false
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
skip-cache: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
version: latest
|
||||
args: --timeout=30m ./...
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.22'
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y curl jq
|
||||
- name: Set up environment
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
image: golang:bookworm
|
||||
image: golang:1.22-bookworm
|
||||
|
||||
# 在每个任务执行前运行
|
||||
before_script:
|
||||
@@ -59,7 +59,7 @@ fetch:
|
||||
stage: build
|
||||
image: alpine:latest
|
||||
before_script:
|
||||
- sed -i 's/dl-cdn.alpinelinux.org/mirror.sjtu.edu.cn/g' /etc/apk/repositories
|
||||
- sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
- apk add --no-cache curl jq unzip zip
|
||||
script:
|
||||
- curl -sSL "https://git.haozi.net/api/v4/projects/opensource%2Fpanel-frontend/releases" | jq -r '.[0].assets.links[] | select(.name | contains("dist")) | .direct_asset_url' | xargs curl -L -o frontend.zip
|
||||
@@ -78,7 +78,7 @@ release:
|
||||
- build
|
||||
- fetch
|
||||
image:
|
||||
name: goreleaser/goreleaser:nightly
|
||||
name: goreleaser/goreleaser
|
||||
entrypoint: [ '' ]
|
||||
only:
|
||||
- tags
|
||||
@@ -87,4 +87,6 @@ release:
|
||||
# generate a changelog.
|
||||
GIT_DEPTH: 0
|
||||
script:
|
||||
- sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
- apk add --no-cache upx
|
||||
- goreleaser release --clean
|
||||
|
||||
+18
-8
@@ -1,7 +1,7 @@
|
||||
project_name: panel
|
||||
|
||||
builds:
|
||||
-
|
||||
id: panel
|
||||
- id: panel
|
||||
binary: panel
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
@@ -15,36 +15,46 @@ builds:
|
||||
- v2
|
||||
ldflags:
|
||||
- -s -w --extldflags "-static"
|
||||
|
||||
upx:
|
||||
- enabled: true
|
||||
# Filter by build ID.
|
||||
ids: [ panel ]
|
||||
# Compress argument.
|
||||
# Valid options are from '1' (faster) to '9' (better), and 'best'.
|
||||
compress: best
|
||||
# Whether to try LZMA (slower).
|
||||
lzma: true
|
||||
# Whether to try all methods and filters (slow).
|
||||
brute: false
|
||||
|
||||
archives:
|
||||
-
|
||||
id: panel-archive
|
||||
- id: panel-archive
|
||||
builds:
|
||||
- panel
|
||||
format: zip
|
||||
wrap_in_directory: false
|
||||
strip_parent_binary_folder: true
|
||||
strip_binary_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
- docs/*
|
||||
- public/*
|
||||
- storage/*
|
||||
- database/*
|
||||
- lang/*
|
||||
- scripts/*
|
||||
- panel-example.conf
|
||||
|
||||
gitlab_urls:
|
||||
api: https://git.haozi.net/api/v4/
|
||||
download: https://git.haozi.net
|
||||
|
||||
# set to true if you use a self-signed certificate
|
||||
skip_tls_verify: false
|
||||
|
||||
# set to true if you want to upload to the Package Registry rather than attachments
|
||||
# Only works with GitLab 13.5+
|
||||
#
|
||||
# Since: v1.3
|
||||
use_package_registry: true
|
||||
|
||||
# Set this if you set GITLAB_TOKEN to the value of CI_JOB_TOKEN.
|
||||
#
|
||||
# Since: v1.11
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
|
||||
</p>
|
||||
|
||||
耗子 Linux 面板是使用 Golang + Vue 开发的轻量 Linux 服务器运维管理面板,以 GNU Affero General Public License v3.0 开源。
|
||||
耗子 Linux 面板是使用 Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板。
|
||||
|
||||
交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[pd.qq.com/s/fyol46wfy](https://pd.qq.com/s/fyol46wfy)
|
||||
交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 论坛:[tom.moe](https://tom.moe) | 赞助:[爱发电](https://afdian.net/a/TheTNB)
|
||||
|
||||
## 优势
|
||||
|
||||
@@ -36,9 +36,7 @@
|
||||
|
||||
低配机器建议使用 `Debian`,资源占用较 `RHEL` 系更低。其他机器建议使用 `AlmaLinux` | `RockyLinux`,维护周期更长也更稳定。
|
||||
|
||||
不在下表中的其他 RHEL 9.x 系统,可自行尝试安装,但不保证能够正常运行,且不提供无偿技术支持(理论上不会有大问题)。
|
||||
|
||||
CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移脚本](https://github.com/TheTNB/byecentos)
|
||||
不在下表中的其他 RHEL 9.x 系统,可自行尝试安装,但不保证能够正常运行,且不提供技术支持(理论上不会有大问题)。
|
||||
|
||||
| 系统 | 版本 |
|
||||
|------------|----|
|
||||
@@ -57,7 +55,7 @@ CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移
|
||||
如果你决定继续,请以`root`用户登录服务器,执行以下命令安装面板:
|
||||
|
||||
```shell
|
||||
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
|
||||
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
|
||||
```
|
||||
|
||||
## 卸载面板
|
||||
@@ -67,7 +65,7 @@ HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl
|
||||
如果你无法重装系统,请以`root`用户登录服务器,执行以下命令卸载面板:
|
||||
|
||||
```shell
|
||||
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
|
||||
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
|
||||
```
|
||||
|
||||
卸载面板前请务必备份好所有数据,提前卸载面板全部插件。卸载后数据将**无法恢复**!
|
||||
@@ -84,26 +82,17 @@ panel
|
||||
|
||||
## 问题反馈
|
||||
|
||||
使用类问题,可在 [鼠片社区](https://forum.haozi.net) 提问或QQ群`@坤坤`寻求 AI 帮助,亦可在群里寻求付费支持。
|
||||
使用类问题,可在 [Moe Tom](https://tom.moe) 提问或寻求 AI 帮助,亦可在群里寻求付费支持。
|
||||
|
||||
面板自身问题,可在 GitHub 的`Issues`页面提交问题反馈,注意[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)。
|
||||
|
||||
## 贡献代码
|
||||
|
||||
### 寻找/创建 Issue
|
||||
|
||||
您可以在 [Issue 列表](https://github.com/TheTNB/panel/issues) 中寻找或创建一个 Issue,留言表达想要处理该 Issue 的意愿,得到维护者的确认后,即可开始处理。
|
||||
|
||||
### 创建 PR
|
||||
|
||||
- 在开发过程中,如果遇到问题可以随时在 Issue 中详尽描述该问题,以进一步沟通,但在此之前请确保自己已通过 Google 等方式尽可能的尝试解决问题;
|
||||
- PR 须提交至我们的极狐 GitLab 仓库[https://git.haozi.net/opensource/panel](https://git.haozi.net/opensource/panel),勿在 GitHub 上提交;
|
||||
- 当 PR 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审;
|
||||
- 我们非常欢迎您的贡献,将在下次发版时将您添加到首页贡献者中;❤️
|
||||
|
||||
## 赞助商
|
||||
|
||||
### 资金
|
||||
如果耗子 Linux 面板对你有帮助,欢迎[赞助我们](https://afdian.net/a/TheTNB),感谢以下支持者/赞助商的支持:
|
||||
|
||||
**同时接受云资源赞助,可通过QQ群咨询联系**
|
||||
|
||||
### 服务器
|
||||
|
||||
- [微晓朵](https://www.weixiaoduo.com/)
|
||||
|
||||
@@ -112,17 +101,19 @@ panel
|
||||
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
|
||||
- [盾云CDN](http://cdn.ddunyun.com/)
|
||||
|
||||
### DevOps
|
||||
|
||||
- [JetBrains](https://www.jetbrains.com/)
|
||||
|
||||
**接受云资源和资金赞助,可通过QQ群咨询联系**
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.net/a/TheTNB">
|
||||
<img alt="sponsors" src="https://github.com/TheTNB/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 贡献者
|
||||
|
||||
这个项目的存在要归功于所有做出贡献的人,参与贡献请先查看贡献代码部分。
|
||||
|
||||
<a href="https://github.com/DevHaoZi" target="_blank"><img src="https://avatars.githubusercontent.com/u/115467771?v=4" width="48" height="48"></a>
|
||||
<a href="https://github.com/TheTNB/panel/graphs/contributors">
|
||||
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
|
||||
</a>
|
||||
|
||||
## Star 历史
|
||||
|
||||
|
||||
+19
-28
@@ -12,9 +12,9 @@
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
|
||||
</p>
|
||||
|
||||
The HaoZi Linux Panel is a lightweight Linux server operation and maintenance management panel developed using Golang and Vue. It is open source under the GNU Affero General Public License v3.0 protocol.
|
||||
The HaoZi Linux Panel is an open source lightweight Linux server operation and maintenance management panel developed using Golang and Vue.
|
||||
|
||||
Communication QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ channel: [pd.qq.com/s/fyol46wfy](https://pd.qq.com/s/fyol46wfy)
|
||||
Communication QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | Forum: [tom.moe](https://tom.moe) | Sponsor: [Open Collective](https://opencollective.com/tnb)
|
||||
|
||||
## Advantages
|
||||
|
||||
@@ -36,9 +36,7 @@ HaoZi Linux Panel only supports the latest version of mainstream systems under t
|
||||
|
||||
Recommended to use `Debian` for low-configuration machines, as its resource usage is lower than that of the `RHEL` system. For other machines, recommended to use `RockyLinux` | `AlmaLinux`, which has a longer maintenance cycle and is more stable.
|
||||
|
||||
For other RHEL 9.x systems not in the table, you can try to install it yourself, but normal operation is not guaranteed, and free technical support is not provided (theoretically there will be no major question).
|
||||
|
||||
CentOS Stream can be migrated to a supported system using the migration script: [CentOS 8/9 Migration Script](https://github.com/TheTNB/byecentos)
|
||||
For other RHEL 9.x systems not in the table, you can try to install it yourself, but normal operation is not guaranteed, and technical support is not provided (theoretically there will be no major question).
|
||||
|
||||
| OS | Version |
|
||||
|------------|---------|
|
||||
@@ -57,7 +55,7 @@ As system versions are constantly updated, we may also terminate support for som
|
||||
If you decide to continue, please log in to the server as `root` user and execute the following command to install the panel:
|
||||
|
||||
```shell
|
||||
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
|
||||
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
|
||||
```
|
||||
|
||||
## Uninstall Panel
|
||||
@@ -67,7 +65,7 @@ Recommended to back up data and reinstall the system first, so that the system c
|
||||
If you are unable to reinstall the system, log in to the server as the `root` user and execute the following command to uninstall the panel:
|
||||
|
||||
```shell
|
||||
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
|
||||
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
|
||||
```
|
||||
|
||||
Before uninstalling the panel, please be sure to back up all data and uninstall all panel plugins in advance. The data will **not be recoverable** after uninstallation!
|
||||
@@ -84,26 +82,17 @@ See more usage methods and tips in [Wiki](https://github.com/TheTNB/panel/wiki).
|
||||
|
||||
## Feedback
|
||||
|
||||
For usage issues, you can ask questions in the [HaoZi Community Forum](https://forum.haozi.net) or seek AI help in the QQ group `@坤坤`. You can also seek paid support in the group.
|
||||
For usage issues, you can ask questions in the [Moe Tom](https://tom.moe) or seek AI help. You can also seek paid support in the group.
|
||||
|
||||
For issues with the panel itself, you can submit feedback on the `Issues` page of GitHub. Please note [the wisdom of asking questions](http://www.catb.org/~esr/faqs/smart-questions.html).
|
||||
|
||||
## Contribute code
|
||||
|
||||
### Find/Create Issue
|
||||
|
||||
You can find or create an Issue in [Issue List](https://github.com/TheTNB/panel/issues), leave a message to express your willingness to deal with the Issue, and get confirmation from the maintainer. Start processing.
|
||||
|
||||
### Create PR
|
||||
|
||||
- During the development process, if you encounter a problem, you can describe the problem in detail in the Issue at any time for further communication, but before doing so, please make sure that you have tried your best to solve the problem through Google and other methods;
|
||||
- PRs must be submitted to our JiHu GitLab repository [https://git.haozi.net/opensource/panel](https://git.haozi.net/opensource/panel). Do not submit on GitHub;
|
||||
- When the PR is completed, please add the `🚀 Review Ready` tag to it, and the maintainer will review it in time;
|
||||
- We very much welcome your contributions and will add you to the homepage contributors in the next release; ❤️
|
||||
|
||||
## Sponsor
|
||||
|
||||
### Funding
|
||||
If the HaoZi Linux Panel is helpful to you, welcome to [sponsor us](https://opencollective.com/tnb), thanks to the following supporters/sponsors:
|
||||
|
||||
**Accept cloud resources and financial sponsorship, you can contact us through QQ group**
|
||||
|
||||
### Server
|
||||
|
||||
- [微晓朵](https://www.weixiaoduo.com/)
|
||||
|
||||
@@ -112,17 +101,19 @@ You can find or create an Issue in [Issue List](https://github.com/TheTNB/panel/
|
||||
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
|
||||
- [盾云CDN](http://cdn.ddunyun.com/)
|
||||
|
||||
### DevOps
|
||||
|
||||
- [JetBrains](https://www.jetbrains.com/)
|
||||
|
||||
**Accept cloud resources and financial sponsorship, you can contact us through QQ group**
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.net/a/TheTNB">
|
||||
<img alt="sponsors" src="https://github.com/TheTNB/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Contributor
|
||||
|
||||
This project owes its existence to all those who have contributed. To contribute, please check the contributed code section first.
|
||||
|
||||
<a href="https://github.com/DevHaoZi" target="_blank"><img src="https://avatars.githubusercontent.com/u/115467771?v=4" width="48" height="48"></a>
|
||||
<a href="https://github.com/TheTNB/panel/graphs/contributors">
|
||||
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
|
||||
</a>
|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
@@ -9,9 +10,9 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
// CertRenew 证书续签
|
||||
@@ -25,7 +26,8 @@ func (receiver *CertRenew) Signature() string {
|
||||
|
||||
// Description The console command description.
|
||||
func (receiver *CertRenew) Description() string {
|
||||
return "[面板] 证书续签"
|
||||
ctx := context.Background()
|
||||
return facades.Lang(ctx).Get("commands.panel:cert-renew.description")
|
||||
}
|
||||
|
||||
// Extend The console command extend.
|
||||
@@ -36,8 +38,8 @@ func (receiver *CertRenew) Extend() command.Extend {
|
||||
}
|
||||
|
||||
// Handle Execute the console command.
|
||||
func (receiver *CertRenew) Handle(ctx console.Context) error {
|
||||
if internal.Status != internal.StatusNormal {
|
||||
func (receiver *CertRenew) Handle(console.Context) error {
|
||||
if types.Status != types.StatusNormal {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"context"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/support/color"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
// Monitoring 系统监控
|
||||
@@ -27,7 +27,8 @@ func (receiver *Monitoring) Signature() string {
|
||||
|
||||
// Description The console command description.
|
||||
func (receiver *Monitoring) Description() string {
|
||||
return "[面板] 系统监控"
|
||||
ctx := context.Background()
|
||||
return facades.Lang(ctx).Get("commands.panel:monitoring.description")
|
||||
}
|
||||
|
||||
// Extend The console command extend.
|
||||
@@ -38,11 +39,16 @@ func (receiver *Monitoring) Extend() command.Extend {
|
||||
}
|
||||
|
||||
// Handle Execute the console command.
|
||||
func (receiver *Monitoring) Handle(ctx console.Context) error {
|
||||
if internal.Status != internal.StatusNormal {
|
||||
func (receiver *Monitoring) Handle(console.Context) error {
|
||||
if types.Status != types.StatusNormal {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将等待中的任务分发
|
||||
// TODO 有bug,需要设计一个锁机制防止重复分发
|
||||
//task := services.NewTaskImpl()
|
||||
//_ = task.DispatchWaiting()
|
||||
|
||||
setting := services.NewSettingImpl()
|
||||
monitor := setting.Get(models.SettingKeyMonitor)
|
||||
if !cast.ToBool(monitor) {
|
||||
@@ -50,6 +56,7 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
|
||||
}
|
||||
|
||||
info := tools.GetMonitoringInfo()
|
||||
translate := facades.Lang(context.Background())
|
||||
|
||||
// 去除部分数据以减少数据库存储
|
||||
info.Disk = nil
|
||||
@@ -62,30 +69,29 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
|
||||
cpu.Flags = nil
|
||||
}
|
||||
|
||||
if internal.Status != internal.StatusNormal {
|
||||
if types.Status != types.StatusNormal {
|
||||
return nil
|
||||
}
|
||||
err := facades.Orm().Query().Create(&models.Monitor{
|
||||
Info: info,
|
||||
})
|
||||
if err != nil {
|
||||
facades.Log().Infof("[面板] 系统监控保存失败: %s", err.Error())
|
||||
color.Redf("[面板] 系统监控保存失败: %s", err.Error())
|
||||
facades.Log().Tags("面板", "系统监控").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Infof("保存失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel:monitoring.fail")+": %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除过期数据
|
||||
monitorDays := setting.Get(models.SettingKeyMonitorDays)
|
||||
days, err := strconv.Atoi(monitorDays)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if days <= 0 || internal.Status != internal.StatusNormal {
|
||||
days := cast.ToInt(setting.Get(models.SettingKeyMonitorDays))
|
||||
if days <= 0 || types.Status != types.StatusNormal {
|
||||
return nil
|
||||
}
|
||||
if _, err = facades.Orm().Query().Where("created_at < ?", carbon.Now().SubDays(days).ToDateTimeString()).Delete(&models.Monitor{}); err != nil {
|
||||
facades.Log().Infof("[面板] 系统监控删除过期数据失败: %s", err.Error())
|
||||
facades.Log().Tags("面板", "系统监控").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Infof("删除过期数据失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+287
-149
@@ -1,23 +1,24 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/support/color"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
// Panel 面板命令行
|
||||
@@ -31,7 +32,8 @@ func (receiver *Panel) Signature() string {
|
||||
|
||||
// Description The console command description.
|
||||
func (receiver *Panel) Description() string {
|
||||
return "[面板] 命令行"
|
||||
ctx := context.Background()
|
||||
return facades.Lang(ctx).Get("commands.panel.description")
|
||||
}
|
||||
|
||||
// Extend The console command extend.
|
||||
@@ -48,75 +50,85 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
arg2 := ctx.Argument(2)
|
||||
arg3 := ctx.Argument(3)
|
||||
arg4 := ctx.Argument(4)
|
||||
arg5 := ctx.Argument(5)
|
||||
|
||||
translate := facades.Lang(context.Background())
|
||||
|
||||
switch action {
|
||||
case "init":
|
||||
var check models.User
|
||||
err := facades.Orm().Query().FirstOrFail(&check)
|
||||
if err == nil {
|
||||
color.Redln("面板已初始化")
|
||||
color.Red().Printfln(translate.Get("commands.panel.init.exist"))
|
||||
return nil
|
||||
}
|
||||
|
||||
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子 Linux 面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
|
||||
err = facades.Orm().Query().Create(&settings)
|
||||
if err != nil {
|
||||
color.Redln("初始化失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.init.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
hash, err := facades.Hash().Make(tools.RandomString(32))
|
||||
if err != nil {
|
||||
color.Redln("初始化失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.init.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
user := services.NewUserImpl()
|
||||
_, err = user.Create("admin", hash)
|
||||
if err != nil {
|
||||
color.Redln("创建管理员失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.init.adminFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("初始化成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.init.success"))
|
||||
|
||||
case "update":
|
||||
var task models.Task
|
||||
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
|
||||
if err == nil {
|
||||
color.Redln("当前有任务正在执行,禁止更新")
|
||||
if err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task); err == nil {
|
||||
color.Red().Printfln(translate.Get("commands.panel.update.taskCheck"))
|
||||
return nil
|
||||
}
|
||||
if _, err := facades.Orm().Query().Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
color.Red().Printfln(translate.Get("commands.panel.update.dbFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
panel, err := tools.GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
color.Redln("获取最新版本失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.update.versionFail"))
|
||||
return err
|
||||
}
|
||||
|
||||
internal.Status = internal.StatusUpgrade
|
||||
// 停止面板服务,因为在shell中运行的和systemd的不同
|
||||
_ = tools.ServiceStop("panel")
|
||||
|
||||
types.Status = types.StatusUpgrade
|
||||
if err = tools.UpdatePanel(panel); err != nil {
|
||||
internal.Status = internal.StatusFailed
|
||||
color.Redln("更新失败: " + err.Error())
|
||||
types.Status = types.StatusFailed
|
||||
color.Red().Printfln(translate.Get("commands.panel.update.fail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
internal.Status = internal.StatusNormal
|
||||
color.Greenln("更新成功")
|
||||
types.Status = types.StatusNormal
|
||||
color.Green().Printfln(translate.Get("commands.panel.update.success"))
|
||||
tools.RestartPanel()
|
||||
|
||||
case "getInfo":
|
||||
var user models.User
|
||||
err := facades.Orm().Query().Where("id", 1).FirstOrFail(&user)
|
||||
if err != nil {
|
||||
color.Redln("获取管理员信息失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.getInfo.adminGetFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
password := tools.RandomString(16)
|
||||
hash, err := facades.Hash().Make(password)
|
||||
if err != nil {
|
||||
color.Redln("生成密码失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.getInfo.passwordGenerationFail"))
|
||||
return nil
|
||||
}
|
||||
user.Username = tools.RandomString(8)
|
||||
@@ -127,13 +139,13 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
|
||||
err = facades.Orm().Query().Save(&user)
|
||||
if err != nil {
|
||||
color.Redln("保存管理员信息失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.getInfo.adminSaveFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
if err != nil {
|
||||
color.Redln("获取面板端口失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.portFail"))
|
||||
return nil
|
||||
}
|
||||
ip, err := tools.GetPublicIP()
|
||||
@@ -145,42 +157,42 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
color.Greenln("用户名: " + user.Username)
|
||||
color.Greenln("密码: " + password)
|
||||
color.Greenln("面板端口: " + port)
|
||||
color.Greenln("面板入口: " + facades.Config().GetString("http.entrance"))
|
||||
color.Greenln("面板地址: " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.username") + ": " + user.Username)
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.password") + ": " + password)
|
||||
color.Green().Printfln(translate.Get("commands.panel.port") + ": " + port)
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
|
||||
|
||||
case "getPort":
|
||||
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
if err != nil {
|
||||
color.Redln("获取面板端口失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.portFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("面板端口: " + port)
|
||||
color.Green().Printfln(translate.Get("commands.panel.port") + ": " + port)
|
||||
|
||||
case "getEntrance":
|
||||
color.Greenln("面板入口: " + facades.Config().GetString("http.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
|
||||
|
||||
case "deleteEntrance":
|
||||
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
if err != nil {
|
||||
color.Redln("获取面板入口失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deleteEntrance.fail"))
|
||||
return nil
|
||||
}
|
||||
if _, err = tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf"); err != nil {
|
||||
color.Redln("删除面板入口失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deleteEntrance.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("删除面板入口成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.deleteEntrance.success"))
|
||||
|
||||
case "writePlugin":
|
||||
slug := arg1
|
||||
version := arg2
|
||||
if len(slug) == 0 || len(version) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writePlugin.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,31 +204,31 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
color.Redln("写入插件安装状态失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writePlugin.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入插件安装状态成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.writePlugin.success"))
|
||||
|
||||
case "deletePlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deletePlugin.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := facades.Orm().Query().Where("slug", slug).Delete(&models.Plugin{})
|
||||
if err != nil {
|
||||
color.Redln("移除插件安装状态失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deletePlugin.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("移除插件安装状态成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.deletePlugin.success"))
|
||||
|
||||
case "writeMysqlPassword":
|
||||
password := arg1
|
||||
if len(password) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeMysqlPassword.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -228,20 +240,20 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
color.Redln("写入MySQL root密码失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeMysqlPassword.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入MySQL root密码成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.writeMysqlPassword.success"))
|
||||
|
||||
case "cleanTask":
|
||||
_, err := facades.Orm().Query().Model(&models.Task{}).Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).Update("status", models.TaskStatusFailed)
|
||||
if err != nil {
|
||||
color.Redln("清理任务失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.cleanTask.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("清理任务成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.cleanTask.success"))
|
||||
|
||||
case "backup":
|
||||
backupType := arg1
|
||||
@@ -250,37 +262,37 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
save := arg4
|
||||
hr := `+----------------------------------------------------`
|
||||
if len(backupType) == 0 || len(name) == 0 || len(path) == 0 || len(save) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.backup.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln(hr)
|
||||
color.Greenln("★ 开始备份 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
color.Green().Printfln(hr)
|
||||
color.Green().Printfln("★ " + translate.Get("commands.panel.backup.start") + " [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Green().Printfln(hr)
|
||||
|
||||
if !tools.Exists(path) {
|
||||
if err := tools.Mkdir(path, 0644); err != nil {
|
||||
color.Redln("|-创建备份目录失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.backupDirFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
switch backupType {
|
||||
case "website":
|
||||
color.Yellowln("|-目标网站: " + name)
|
||||
color.Yellow().Printfln("|-" + translate.Get("commands.panel.backup.targetSite") + ": " + name)
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err != nil {
|
||||
color.Redln("|-网站不存在")
|
||||
color.Greenln(hr)
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.siteNotExist"))
|
||||
color.Green().Printfln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
backupFile := path + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
|
||||
if _, err := tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
|
||||
color.Redln("|-备份失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.backupFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-备份成功")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.backupSuccess"))
|
||||
|
||||
case "mysql":
|
||||
rootPassword := services.NewSettingImpl().Get(models.SettingKeyMysqlRootPassword)
|
||||
@@ -288,81 +300,81 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
|
||||
err := os.Setenv("MYSQL_PWD", rootPassword)
|
||||
if err != nil {
|
||||
color.Redln("|-备份MySQL数据库失败: " + err.Error())
|
||||
color.Greenln(hr)
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.mysqlBackupFail") + ": " + err.Error())
|
||||
color.Green().Printfln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("|-目标MySQL数据库: " + name)
|
||||
color.Greenln("|-开始导出")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.targetMysql") + ": " + name)
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startExport"))
|
||||
if _, err = tools.Exec(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`); err != nil {
|
||||
color.Redln("|-导出失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-导出成功")
|
||||
color.Greenln("|-开始压缩")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.exportSuccess"))
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startCompress"))
|
||||
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
|
||||
color.Redln("|-压缩失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
if err := tools.Remove("/tmp/" + backupFile); err != nil {
|
||||
color.Redln("|-删除失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-压缩成功")
|
||||
color.Greenln("|-开始移动")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.compressSuccess"))
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startMove"))
|
||||
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
|
||||
color.Redln("|-移动失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-移动成功")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.moveSuccess"))
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
color.Greenln("|-备份成功")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.success"))
|
||||
|
||||
case "postgresql":
|
||||
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
|
||||
check, err := tools.Exec(`su - postgres -c "psql -l" 2>&1`)
|
||||
if err != nil {
|
||||
color.Redln("|-获取数据库失败: " + err.Error())
|
||||
color.Greenln(hr)
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.databaseGetFail") + ": " + err.Error())
|
||||
color.Green().Printfln(hr)
|
||||
return nil
|
||||
}
|
||||
if !strings.Contains(check, name) {
|
||||
color.Redln("|-数据库不存在")
|
||||
color.Greenln(hr)
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.databaseNotExist"))
|
||||
color.Green().Printfln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("|-目标PostgreSQL数据库: " + name)
|
||||
color.Greenln("|-开始导出")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.targetPostgres") + ": " + name)
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startExport"))
|
||||
if _, err = tools.Exec(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`); err != nil {
|
||||
color.Redln("|-导出失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-导出成功")
|
||||
color.Greenln("|-开始压缩")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.exportSuccess"))
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startCompress"))
|
||||
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
|
||||
color.Redln("|-压缩失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
if err := tools.Remove("/tmp/" + backupFile); err != nil {
|
||||
color.Redln("|-删除失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-压缩成功")
|
||||
color.Greenln("|-开始移动")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.compressSuccess"))
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startMove"))
|
||||
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
|
||||
color.Redln("|-移动失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-移动成功")
|
||||
color.Greenln("|-备份成功")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.moveSuccess"))
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.success"))
|
||||
}
|
||||
|
||||
color.Greenln(hr)
|
||||
color.Green().Printfln(hr)
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
color.Redln("|-清理失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.cleanupFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
var filteredFiles []os.FileInfo
|
||||
@@ -380,60 +392,60 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
})
|
||||
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
|
||||
fileToDelete := filepath.Join(path, filteredFiles[i].Name())
|
||||
color.Yellowln("|-清理备份: " + fileToDelete)
|
||||
color.Yellow().Printfln("|-" + translate.Get("commands.panel.backup.cleanBackup") + ": " + fileToDelete)
|
||||
if err := tools.Remove(fileToDelete); err != nil {
|
||||
color.Redln("|-清理失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.cleanupFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
color.Greenln("|-清理完成")
|
||||
color.Greenln(hr)
|
||||
color.Greenln("☆ 备份完成 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.cleanupSuccess"))
|
||||
color.Green().Printfln(hr)
|
||||
color.Green().Printfln("☆ " + translate.Get("commands.panel.backup.success") + " [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Green().Printfln(hr)
|
||||
|
||||
case "cutoff":
|
||||
name := arg1
|
||||
save := arg2
|
||||
hr := `+----------------------------------------------------`
|
||||
if len(name) == 0 || len(save) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.cutoff.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln(hr)
|
||||
color.Greenln("★ 开始切割 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
color.Green().Printfln(hr)
|
||||
color.Green().Printfln("★ " + translate.Get("commands.panel.cutoff.start") + " [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Green().Printfln(hr)
|
||||
|
||||
color.Yellowln("|-目标网站: " + name)
|
||||
color.Yellow().Printfln("|-" + translate.Get("commands.panel.cutoff.targetSite") + ": " + name)
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err != nil {
|
||||
color.Redln("|-网站不存在")
|
||||
color.Greenln(hr)
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.siteNotExist"))
|
||||
color.Green().Printfln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
logPath := "/www/wwwlogs/" + website.Name + ".log"
|
||||
if !tools.Exists(logPath) {
|
||||
color.Redln("|-日志文件不存在")
|
||||
color.Greenln(hr)
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.logNotExist"))
|
||||
color.Green().Printfln(hr)
|
||||
return nil
|
||||
}
|
||||
|
||||
backupPath := "/www/wwwlogs/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".log.zip"
|
||||
if _, err := tools.Exec(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log"); err != nil {
|
||||
color.Redln("|-备份失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.backupFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
if _, err := tools.Exec(`echo "" > ` + logPath); err != nil {
|
||||
color.Redln("|-清空失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.clearFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
color.Greenln("|-切割成功")
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.cutoff.cutSuccess"))
|
||||
|
||||
color.Greenln(hr)
|
||||
color.Green().Printfln(hr)
|
||||
files, err := os.ReadDir("/www/wwwlogs")
|
||||
if err != nil {
|
||||
color.Redln("|-清理失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.cleanupFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
var filteredFiles []os.FileInfo
|
||||
@@ -451,16 +463,16 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
})
|
||||
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
|
||||
fileToDelete := filepath.Join("/www/wwwlogs", filteredFiles[i].Name())
|
||||
color.Yellowln("|-清理日志: " + fileToDelete)
|
||||
color.Yellow().Printfln("|-" + translate.Get("commands.panel.cutoff.clearLog") + ": " + fileToDelete)
|
||||
if err := tools.Remove(fileToDelete); err != nil {
|
||||
color.Redln("|-清理失败: " + err.Error())
|
||||
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.cleanupFail") + ": " + err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
color.Greenln("|-清理完成")
|
||||
color.Greenln(hr)
|
||||
color.Greenln("☆ 切割完成 [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Greenln(hr)
|
||||
color.Green().Printfln("|-" + translate.Get("commands.panel.cutoff.cleanupSuccess"))
|
||||
color.Green().Printfln(hr)
|
||||
color.Green().Printfln("☆ " + translate.Get("commands.panel.cutoff.end") + " [" + carbon.Now().ToDateTimeString() + "]")
|
||||
color.Green().Printfln(hr)
|
||||
|
||||
case "writeSite":
|
||||
name := arg1
|
||||
@@ -469,19 +481,19 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
php := cast.ToInt(arg4)
|
||||
ssl := cast.ToBool(ctx.Argument(5))
|
||||
if len(name) == 0 || len(path) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSite.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err == nil {
|
||||
color.Redln("网站已存在")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSite.siteExist"))
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
color.Redln("网站目录不存在")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSite.pathNotExist"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -493,32 +505,32 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
Ssl: ssl,
|
||||
})
|
||||
if err != nil {
|
||||
color.Redln("写入网站失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSite.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入网站成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.writeSite.success"))
|
||||
|
||||
case "deleteSite":
|
||||
name := arg1
|
||||
if len(name) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deleteSite.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := facades.Orm().Query().Where("name", name).Delete(&models.Website{})
|
||||
if err != nil {
|
||||
color.Redln("删除网站失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deleteSite.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("删除网站成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.deleteSite.success"))
|
||||
|
||||
case "writeSetting":
|
||||
key := arg1
|
||||
value := arg2
|
||||
if len(key) == 0 || len(value) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSetting.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -529,16 +541,16 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
color.Redln("写入设置失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSetting.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入设置成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.writeSetting.success"))
|
||||
|
||||
case "getSetting":
|
||||
key := arg1
|
||||
if len(key) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.getSetting.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -552,39 +564,165 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
case "deleteSetting":
|
||||
key := arg1
|
||||
if len(key) == 0 {
|
||||
color.Redln("参数错误")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deleteSetting.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := facades.Orm().Query().Where("key", key).Delete(&models.Setting{})
|
||||
if err != nil {
|
||||
color.Redln("删除设置失败")
|
||||
color.Red().Printfln(translate.Get("commands.panel.deleteSetting.fail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("删除设置成功")
|
||||
color.Green().Printfln(translate.Get("commands.panel.deleteSetting.success"))
|
||||
|
||||
case "addSite":
|
||||
name := arg1
|
||||
domain := arg2
|
||||
port := arg3
|
||||
path := arg4
|
||||
php := arg5
|
||||
if len(name) == 0 || len(domain) == 0 || len(port) == 0 || len(path) == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.addSite.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
domains := strings.Split(domain, ",")
|
||||
ports := strings.Split(port, ",")
|
||||
if len(domains) == 0 || len(ports) == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.addSite.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
var uintPorts []uint
|
||||
for _, p := range ports {
|
||||
uintPorts = append(uintPorts, cast.ToUint(p))
|
||||
}
|
||||
|
||||
website := services.NewWebsiteImpl()
|
||||
id, err := website.GetIDByName(name)
|
||||
if err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
if id != 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.addSite.siteExist"))
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = website.Add(types.Website{
|
||||
Name: name,
|
||||
Status: true,
|
||||
Domains: domains,
|
||||
Ports: uintPorts,
|
||||
Path: path,
|
||||
Php: php,
|
||||
Ssl: false,
|
||||
Db: false,
|
||||
})
|
||||
if err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Green().Printfln(translate.Get("commands.panel.addSite.success"))
|
||||
|
||||
case "removeSite":
|
||||
name := arg1
|
||||
if len(name) == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.removeSite.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
website := services.NewWebsiteImpl()
|
||||
id, err := website.GetIDByName(name)
|
||||
if err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
if id == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.removeSite.siteNotExist"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = website.Delete(id); err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Green().Printfln(translate.Get("commands.panel.removeSite.success"))
|
||||
|
||||
case "installPlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.installPlugin.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
plugin := services.NewPluginImpl()
|
||||
if err := plugin.Install(slug); err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Green().Printfln(translate.Get("commands.panel.installPlugin.success"))
|
||||
|
||||
case "uninstallPlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.uninstallPlugin.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
plugin := services.NewPluginImpl()
|
||||
if err := plugin.Uninstall(slug); err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Green().Printfln(translate.Get("commands.panel.uninstallPlugin.success"))
|
||||
|
||||
case "updatePlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Red().Printfln(translate.Get("commands.panel.updatePlugin.paramFail"))
|
||||
return nil
|
||||
}
|
||||
|
||||
plugin := services.NewPluginImpl()
|
||||
if err := plugin.Update(slug); err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Green().Printfln(translate.Get("commands.panel.updatePlugin.success"))
|
||||
|
||||
default:
|
||||
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
|
||||
color.Greenln("请使用以下命令:")
|
||||
color.Greenln("panel update 更新 / 修复面板到最新版本")
|
||||
color.Greenln("panel getInfo 重新初始化面板账号信息")
|
||||
color.Greenln("panel getPort 获取面板访问端口")
|
||||
color.Greenln("panel getEntrance 获取面板访问入口")
|
||||
color.Greenln("panel deleteEntrance 删除面板访问入口")
|
||||
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务[任务卡住时使用]")
|
||||
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} 备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量")
|
||||
color.Greenln("panel cutoff {website_name} {save_copies} 切割网站日志并保留指定数量")
|
||||
color.Redln("以下命令请在开发者指导下使用:")
|
||||
color.Yellowln("panel init 初始化面板")
|
||||
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")
|
||||
color.Yellowln("panel deletePlugin {slug} 移除插件安装状态")
|
||||
color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码")
|
||||
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板")
|
||||
color.Yellowln("panel deleteSite {name} 删除面板网站数据")
|
||||
color.Yellowln("panel getSetting {name} 获取面板设置数据")
|
||||
color.Yellowln("panel writeSetting {name} {value} 写入 / 更新面板设置数据")
|
||||
color.Yellowln("panel deleteSetting {name} 删除面板设置数据")
|
||||
color.Yellow().Printfln(facades.Config().GetString("panel.name") + " - " + translate.Get("commands.panel.tool") + " - " + facades.Config().GetString("panel.version"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.use") + ":")
|
||||
color.Green().Printfln("panel update " + translate.Get("commands.panel.update.description"))
|
||||
color.Green().Printfln("panel getInfo " + translate.Get("commands.panel.getInfo.description"))
|
||||
color.Green().Printfln("panel getPort " + translate.Get("commands.panel.getPort.description"))
|
||||
color.Green().Printfln("panel getEntrance " + translate.Get("commands.panel.getEntrance.description"))
|
||||
color.Green().Printfln("panel deleteEntrance " + translate.Get("commands.panel.deleteEntrance.description"))
|
||||
color.Green().Printfln("panel cleanTask " + translate.Get("commands.panel.cleanTask.description"))
|
||||
color.Green().Printfln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} " + translate.Get("commands.panel.backup.description"))
|
||||
color.Green().Printfln("panel cutoff {website_name} {save_copies} " + translate.Get("commands.panel.cutoff.description"))
|
||||
color.Green().Printfln("panel installPlugin {slug} " + translate.Get("commands.panel.installPlugin.description"))
|
||||
color.Green().Printfln("panel uninstallPlugin {slug} " + translate.Get("commands.panel.uninstallPlugin.description"))
|
||||
color.Green().Printfln("panel updatePlugin {slug} " + translate.Get("commands.panel.updatePlugin.description"))
|
||||
color.Green().Printfln("panel addSite {name} {domain} {port} {path} {php} " + translate.Get("commands.panel.addSite.description"))
|
||||
color.Green().Printfln("panel removeSite {name} " + translate.Get("commands.panel.removeSite.description"))
|
||||
color.Red().Printfln(translate.Get("commands.panel.forDeveloper") + ":")
|
||||
color.Yellow().Printfln("panel init " + translate.Get("commands.panel.init.description"))
|
||||
color.Yellow().Printfln("panel writePlugin {slug} {version} " + translate.Get("commands.panel.writePlugin.description"))
|
||||
color.Yellow().Printfln("panel deletePlugin {slug} " + translate.Get("commands.panel.deletePlugin.description"))
|
||||
color.Yellow().Printfln("panel writeMysqlPassword {password} " + translate.Get("commands.panel.writeMysqlPassword.description"))
|
||||
color.Yellow().Printfln("panel writeSite {name} {status} {path} {php} {ssl} " + translate.Get("commands.panel.writeSite.description"))
|
||||
color.Yellow().Printfln("panel deleteSite {name} " + translate.Get("commands.panel.deleteSite.description"))
|
||||
color.Yellow().Printfln("panel getSetting {name} " + translate.Get("commands.panel.getSetting.description"))
|
||||
color.Yellow().Printfln("panel writeSetting {name} {value} " + translate.Get("commands.panel.writeSetting.description"))
|
||||
color.Yellow().Printfln("panel deleteSetting {name} " + translate.Get("commands.panel.deleteSetting.description"))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/internal"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
// PanelTask 面板每日任务
|
||||
@@ -21,7 +23,7 @@ func (receiver *PanelTask) Signature() string {
|
||||
|
||||
// Description The console command description.
|
||||
func (receiver *PanelTask) Description() string {
|
||||
return "[面板] 每日任务"
|
||||
return facades.Lang(context.Background()).Get("commands.panel:task.description")
|
||||
}
|
||||
|
||||
// Extend The console command extend.
|
||||
@@ -32,11 +34,12 @@ func (receiver *PanelTask) Extend() command.Extend {
|
||||
}
|
||||
|
||||
// Handle Execute the console command.
|
||||
func (receiver *PanelTask) Handle(ctx console.Context) error {
|
||||
internal.Status = internal.StatusMaintain
|
||||
func (receiver *PanelTask) Handle(console.Context) error {
|
||||
types.Status = types.StatusMaintain
|
||||
|
||||
// 优化数据库
|
||||
if _, err := facades.Orm().Query().Exec("VACUUM"); err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
facades.Log().Tags("面板", "每日任务").
|
||||
With(map[string]any{
|
||||
"error": err.Error(),
|
||||
@@ -46,6 +49,7 @@ func (receiver *PanelTask) Handle(ctx console.Context) error {
|
||||
|
||||
// 备份面板
|
||||
if err := tools.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
facades.Log().Tags("面板", "每日任务").
|
||||
With(map[string]any{
|
||||
"error": err.Error(),
|
||||
@@ -55,6 +59,7 @@ func (receiver *PanelTask) Handle(ctx console.Context) error {
|
||||
|
||||
// 清理 7 天前的备份
|
||||
if _, err := tools.Exec(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
facades.Log().Tags("面板", "每日任务").
|
||||
With(map[string]any{
|
||||
"error": err.Error(),
|
||||
@@ -62,6 +67,6 @@ func (receiver *PanelTask) Handle(ctx console.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
internal.Status = internal.StatusNormal
|
||||
types.Status = types.StatusNormal
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/schedule"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/console/commands"
|
||||
"github.com/TheTNB/panel/app/console/commands"
|
||||
)
|
||||
|
||||
type Kernel struct {
|
||||
@@ -15,7 +15,7 @@ func (kernel *Kernel) Schedule() []schedule.Event {
|
||||
return []schedule.Event{
|
||||
facades.Schedule().Command("panel:monitoring").EveryMinute().SkipIfStillRunning(),
|
||||
facades.Schedule().Command("panel:cert-renew").DailyAt("04:00").SkipIfStillRunning(),
|
||||
facades.Schedule().Command("panel:task").Daily().SkipIfStillRunning(),
|
||||
facades.Schedule().Command("panel:task").DailyAt("03:30").SkipIfStillRunning(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type AssetController struct {
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
requests "panel/app/http/requests/cert"
|
||||
commonrequests "panel/app/http/requests/common"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/acme"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/cert"
|
||||
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type CertController struct {
|
||||
@@ -650,7 +650,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.Obtain true "request"
|
||||
// @Success 200 {object} SuccessResponse{data=map[string]acme.Resolve}
|
||||
// @Success 200 {object} SuccessResponse{data=[]acme.DNSRecord}
|
||||
// @Router /panel/cert/manualDNS [post]
|
||||
func (r *CertController) ManualDNS(ctx http.Context) http.Response {
|
||||
var obtainRequest requests.Obtain
|
||||
@@ -669,3 +669,33 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
|
||||
|
||||
return Success(ctx, resolves)
|
||||
}
|
||||
|
||||
// Deploy
|
||||
//
|
||||
// @Summary 部署证书
|
||||
// @Description 部署面板证书管理的证书
|
||||
// @Tags 证书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.CertDeploy true "request"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/cert/deploy [post]
|
||||
func (r *CertController) Deploy(ctx http.Context) http.Response {
|
||||
var deployRequest requests.CertDeploy
|
||||
sanitize := Sanitize(ctx, &deployRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
err := r.cert.Deploy(deployRequest.ID, deployRequest.WebsiteID)
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
|
||||
"certID": deployRequest.ID,
|
||||
"error": err.Error(),
|
||||
}).Info("部署证书失败")
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
@@ -13,11 +14,10 @@ import (
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"panel/pkg/tools"
|
||||
|
||||
commonrequests "panel/app/http/requests/common"
|
||||
requests "panel/app/http/requests/container"
|
||||
"panel/internal/services"
|
||||
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/container"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
)
|
||||
|
||||
type ContainerController struct {
|
||||
@@ -63,11 +63,8 @@ func (r *ContainerController) ContainerList(ctx http.Context) http.Response {
|
||||
endIndex = len(containers)
|
||||
}
|
||||
paged := containers[startIndex:endIndex]
|
||||
if paged == nil {
|
||||
paged = []types.Container{}
|
||||
}
|
||||
|
||||
var items []any
|
||||
items := make([]any, 0)
|
||||
for _, item := range paged {
|
||||
var name string
|
||||
if len(item.Names) > 0 {
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
// SuccessResponse 通用成功响应
|
||||
@@ -26,14 +27,14 @@ func Success(ctx http.Context, data any) http.Response {
|
||||
// Error 响应错误
|
||||
func Error(ctx http.Context, code int, message string) http.Response {
|
||||
return ctx.Response().Json(code, &ErrorResponse{
|
||||
Message: "错误: " + message,
|
||||
Message: facades.Lang(ctx).Get("messages.mistake") + ": " + message,
|
||||
})
|
||||
}
|
||||
|
||||
// ErrorSystem 响应系统错误
|
||||
func ErrorSystem(ctx http.Context) http.Response {
|
||||
return ctx.Response().Json(http.StatusInternalServerError, &ErrorResponse{
|
||||
Message: "系统内部错误",
|
||||
Message: facades.Lang(ctx).Get("errors.internal"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type CronController struct {
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
requests "panel/app/http/requests/file"
|
||||
"panel/pkg/tools"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/file"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type FileController struct {
|
||||
@@ -80,7 +82,7 @@ func (r *FileController) Content(ctx http.Context) http.Response {
|
||||
return Error(ctx, http.StatusInternalServerError, "目标路径不是文件")
|
||||
}
|
||||
if fileInfo.Size() > 10*1024*1024 {
|
||||
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10M")
|
||||
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
|
||||
}
|
||||
|
||||
content, err := tools.Read(request.Path)
|
||||
@@ -207,15 +209,15 @@ func (r *FileController) Move(ctx http.Context) http.Response {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
|
||||
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
|
||||
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
|
||||
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
|
||||
}
|
||||
|
||||
if err := tools.Mv(request.Old, request.New); err != nil {
|
||||
if err := tools.Mv(request.Source, request.Target); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
r.setPermission(request.New, 0755, "www", "www")
|
||||
r.setPermission(request.Target, 0755, "www", "www")
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
@@ -237,15 +239,15 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
|
||||
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
|
||||
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
|
||||
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
|
||||
}
|
||||
|
||||
if err := tools.Cp(request.Old, request.New); err != nil {
|
||||
if err := tools.Cp(request.Source, request.Target); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
r.setPermission(request.New, 0755, "www", "www")
|
||||
r.setPermission(request.Source, 0755, "www", "www")
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
@@ -454,7 +456,8 @@ func (r *FileController) Search(ctx http.Context) http.Response {
|
||||
// @Tags 文件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data query requests.Exist true "request"
|
||||
// @Param data query requests.Exist true "request"
|
||||
// @Param data query commonrequests.Paginate true "request"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/file/list [get]
|
||||
func (r *FileController) List(ctx http.Context) http.Response {
|
||||
@@ -464,6 +467,12 @@ func (r *FileController) List(ctx http.Context) http.Response {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
var paginate commonrequests.Paginate
|
||||
paginateSanitize := Sanitize(ctx, &paginate)
|
||||
if paginateSanitize != nil {
|
||||
return paginateSanitize
|
||||
}
|
||||
|
||||
fileInfoList, err := os.ReadDir(request.Path)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
@@ -472,17 +481,44 @@ func (r *FileController) List(ctx http.Context) http.Response {
|
||||
var paths []any
|
||||
for _, fileInfo := range fileInfoList {
|
||||
info, _ := fileInfo.Info()
|
||||
stat := info.Sys().(*syscall.Stat_t)
|
||||
|
||||
paths = append(paths, map[string]any{
|
||||
"name": info.Name(),
|
||||
"full": filepath.Join(request.Path, info.Name()),
|
||||
"size": tools.FormatBytes(float64(info.Size())),
|
||||
"mode_str": info.Mode().String(),
|
||||
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
|
||||
"owner": tools.GetUser(stat.Uid),
|
||||
"group": tools.GetGroup(stat.Gid),
|
||||
"uid": stat.Uid,
|
||||
"gid": stat.Gid,
|
||||
"hidden": tools.IsHidden(info.Name()),
|
||||
"symlink": tools.IsSymlink(info.Mode()),
|
||||
"link": tools.GetSymlink(filepath.Join(request.Path, info.Name())),
|
||||
"dir": info.IsDir(),
|
||||
"modify": carbon.FromStdTime(info.ModTime()).ToDateTimeString(),
|
||||
})
|
||||
}
|
||||
|
||||
return Success(ctx, paths)
|
||||
start := paginate.Limit * (paginate.Page - 1)
|
||||
end := paginate.Limit * paginate.Page
|
||||
if start > len(paths) {
|
||||
start = len(paths)
|
||||
}
|
||||
if end > len(paths) {
|
||||
end = len(paths)
|
||||
}
|
||||
|
||||
paged := paths[start:end]
|
||||
if paged == nil {
|
||||
paged = []any{}
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
"total": len(paths),
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
// setPermission
|
||||
|
||||
@@ -9,10 +9,11 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type MenuItem struct {
|
||||
@@ -34,10 +35,11 @@ func NewInfoController() *InfoController {
|
||||
}
|
||||
}
|
||||
|
||||
// Name 获取面板名称
|
||||
func (r *InfoController) Name(ctx http.Context) http.Response {
|
||||
// Panel 获取面板信息
|
||||
func (r *InfoController) Panel(ctx http.Context) http.Response {
|
||||
return Success(ctx, http.Json{
|
||||
"name": r.setting.Get(models.SettingKeyName),
|
||||
"name": r.setting.Get(models.SettingKeyName),
|
||||
"language": facades.Config().GetString("app.locale"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,8 +108,8 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
|
||||
}
|
||||
var databaseCount int64
|
||||
if mysqlInstalled {
|
||||
status, err := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if status == "active" && err == nil {
|
||||
status, err := tools.ServiceStatus("mysqld")
|
||||
if status && err == nil {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
type database struct {
|
||||
Name string `json:"name"`
|
||||
@@ -148,8 +150,8 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
if postgresqlInstalled {
|
||||
status, err := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if status == "active" && err == nil {
|
||||
status, err := tools.ServiceStatus("postgresql")
|
||||
if status && err == nil {
|
||||
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
|
||||
if err == nil {
|
||||
databases := strings.Split(raw, "\n")
|
||||
@@ -296,10 +298,13 @@ func (r *InfoController) UpdateInfo(ctx http.Context) http.Response {
|
||||
// Update 更新面板
|
||||
func (r *InfoController) Update(ctx http.Context) http.Response {
|
||||
var task models.Task
|
||||
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
|
||||
if err == nil {
|
||||
if err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task); err == nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "当前有任务正在执行,禁止更新")
|
||||
}
|
||||
if _, err := facades.Orm().Query().Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("面板数据库异常,已终止操作:%s", err.Error()))
|
||||
}
|
||||
|
||||
panel, err := tools.GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
@@ -309,16 +314,16 @@ func (r *InfoController) Update(ctx http.Context) http.Response {
|
||||
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
|
||||
}
|
||||
|
||||
internal.Status = internal.StatusUpgrade
|
||||
types.Status = types.StatusUpgrade
|
||||
if err = tools.UpdatePanel(panel); err != nil {
|
||||
internal.Status = internal.StatusFailed
|
||||
types.Status = types.StatusFailed
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("更新面板失败")
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
internal.Status = internal.StatusNormal
|
||||
types.Status = types.StatusNormal
|
||||
tools.RestartPanel()
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
)
|
||||
|
||||
type MonitorController struct {
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
)
|
||||
|
||||
type PluginController struct {
|
||||
@@ -92,162 +92,33 @@ func (r *PluginController) List(ctx http.Context) http.Response {
|
||||
// Install 安装插件
|
||||
func (r *PluginController) Install(ctx http.Context) http.Response {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
}).Info("检查插件安装状态失败")
|
||||
return ErrorSystem(ctx)
|
||||
|
||||
if err := r.plugin.Install(slug); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if installedPlugin.ID != 0 {
|
||||
return Error(ctx, http.StatusUnprocessableEntity, "插件已安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "安装插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
task.Shell = plugin.Install + ` >> '/tmp/` + plugin.Slug + `.log' 2>&1`
|
||||
task.Log = "/tmp/" + plugin.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
"err": err.Error(),
|
||||
}).Info("创建任务失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return Success(ctx, "任务已提交")
|
||||
}
|
||||
|
||||
// Uninstall 卸载插件
|
||||
func (r *PluginController) Uninstall(ctx http.Context) http.Response {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
}).Info("检查插件安装状态失败")
|
||||
return ErrorSystem(ctx)
|
||||
|
||||
if err := r.plugin.Uninstall(slug); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "卸载插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Log = "/tmp/" + plugin.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
"err": err.Error(),
|
||||
}).Info("创建任务失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return Success(ctx, "任务已提交")
|
||||
}
|
||||
|
||||
// Update 更新插件
|
||||
func (r *PluginController) Update(ctx http.Context) http.Response {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
}).Info("检查插件安装状态失败")
|
||||
return ErrorSystem(ctx)
|
||||
|
||||
if err := r.plugin.Update(slug); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "更新插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
|
||||
task.Log = "/tmp/" + plugin.Slug + ".log"
|
||||
if err := facades.Orm().Query().Create(&task); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
"err": err.Error(),
|
||||
}).Info("创建任务失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return Success(ctx, "任务已提交")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type Fail2banController struct {
|
||||
@@ -38,7 +38,7 @@ func (r *Fail2banController) Status(ctx http.Context) http.Response {
|
||||
|
||||
// Reload 重载配置
|
||||
func (r *Fail2banController) Reload(ctx http.Context) http.Response {
|
||||
if _, err := tools.Exec("systemctl reload fail2ban"); err != nil {
|
||||
if err := tools.ServiceReload("fail2ban"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/plugins/frp"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type FrpController struct {
|
||||
}
|
||||
|
||||
func NewFrpController() *FrpController {
|
||||
return &FrpController{}
|
||||
}
|
||||
|
||||
// Status
|
||||
//
|
||||
// @Summary 服务状态
|
||||
// @Description 获取 Frp 服务状态
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/status [get]
|
||||
func (r *FrpController) Status(ctx http.Context) http.Response {
|
||||
frps, err := tools.ServiceStatus("frps")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 frps 服务运行状态失败")
|
||||
}
|
||||
frpc, err := tools.ServiceStatus("frpc")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 frpc 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"frps": frps,
|
||||
"frpc": frpc,
|
||||
})
|
||||
}
|
||||
|
||||
// IsEnabled
|
||||
//
|
||||
// @Summary 是否启用服务
|
||||
// @Description 获取是否启用 Frp 服务
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/isEnabled [get]
|
||||
func (r *FrpController) IsEnabled(ctx http.Context) http.Response {
|
||||
frps, err := tools.ServiceIsEnabled("frps")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 frps 服务启用状态失败")
|
||||
}
|
||||
frpc, err := tools.ServiceIsEnabled("frpc")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 frpc 服务启用状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"frps": frps,
|
||||
"frpc": frpc,
|
||||
})
|
||||
}
|
||||
|
||||
// Enable
|
||||
//
|
||||
// @Summary 启用服务
|
||||
// @Description 启用 Frp 服务
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.Service true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/enable [post]
|
||||
func (r *FrpController) Enable(ctx http.Context) http.Response {
|
||||
var serviceRequest requests.Service
|
||||
sanitize := controllers.Sanitize(ctx, &serviceRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.ServiceEnable(serviceRequest.Service); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("启用 %s 服务失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Disable
|
||||
//
|
||||
// @Summary 禁用服务
|
||||
// @Description 禁用 Frp 服务
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.Service true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/disable [post]
|
||||
func (r *FrpController) Disable(ctx http.Context) http.Response {
|
||||
var serviceRequest requests.Service
|
||||
sanitize := controllers.Sanitize(ctx, &serviceRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.ServiceDisable(serviceRequest.Service); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("禁用 %s 服务失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Restart
|
||||
//
|
||||
// @Summary 重启服务
|
||||
// @Description 重启 Frp 服务
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @param data body requests.Service true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/restart [post]
|
||||
func (r *FrpController) Restart(ctx http.Context) http.Response {
|
||||
var serviceRequest requests.Service
|
||||
sanitize := controllers.Sanitize(ctx, &serviceRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.ServiceRestart(serviceRequest.Service); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重启 %s 服务失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Start
|
||||
//
|
||||
// @Summary 启动服务
|
||||
// @Description 启动 Frp 服务
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.Service true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/start [post]
|
||||
func (r *FrpController) Start(ctx http.Context) http.Response {
|
||||
var serviceRequest requests.Service
|
||||
sanitize := controllers.Sanitize(ctx, &serviceRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.ServiceStart(serviceRequest.Service); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("启动 %s 服务失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus(serviceRequest.Service)
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("获取 %s 服务运行状态失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, status)
|
||||
}
|
||||
|
||||
// Stop
|
||||
//
|
||||
// @Summary 停止服务
|
||||
// @Description 停止 Frp 服务
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.Service true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/stop [post]
|
||||
func (r *FrpController) Stop(ctx http.Context) http.Response {
|
||||
var serviceRequest requests.Service
|
||||
sanitize := controllers.Sanitize(ctx, &serviceRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.ServiceStop(serviceRequest.Service); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("停止 %s 服务失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus(serviceRequest.Service)
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("获取 %s 服务运行状态失败", serviceRequest.Service))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, !status)
|
||||
}
|
||||
|
||||
// GetConfig
|
||||
//
|
||||
// @Summary 获取配置
|
||||
// @Description 获取 Frp 配置
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param service query string false "服务"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/config [get]
|
||||
func (r *FrpController) GetConfig(ctx http.Context) http.Response {
|
||||
var serviceRequest requests.Service
|
||||
sanitize := controllers.Sanitize(ctx, &serviceRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
config, err := tools.Read(fmt.Sprintf("/www/server/frp/%s.toml", serviceRequest.Service))
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateConfig
|
||||
//
|
||||
// @Summary 更新配置
|
||||
// @Description 更新 Frp 配置
|
||||
// @Tags 插件-Frp
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/frp/config [post]
|
||||
func (r *FrpController) UpdateConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateConfig
|
||||
sanitize := controllers.Sanitize(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.Write(fmt.Sprintf("/www/server/frp/%s.toml", updateRequest.Service), updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := tools.ServiceRestart(updateRequest.Service); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/plugins/gitea"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type GiteaController struct {
|
||||
}
|
||||
|
||||
func NewGiteaController() *GiteaController {
|
||||
return &GiteaController{}
|
||||
}
|
||||
|
||||
// Status
|
||||
//
|
||||
// @Summary 服务状态
|
||||
// @Description 获取 Gitea 服务状态
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/status [get]
|
||||
func (r *GiteaController) Status(ctx http.Context) http.Response {
|
||||
status, err := tools.ServiceStatus("gitea")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Gitea 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, status)
|
||||
}
|
||||
|
||||
// IsEnabled
|
||||
//
|
||||
// @Summary 是否启用服务
|
||||
// @Description 获取是否启用 Gitea 服务
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/isEnabled [get]
|
||||
func (r *GiteaController) IsEnabled(ctx http.Context) http.Response {
|
||||
enabled, err := tools.ServiceIsEnabled("gitea")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Gitea 服务启用状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, enabled)
|
||||
}
|
||||
|
||||
// Enable
|
||||
//
|
||||
// @Summary 启用服务
|
||||
// @Description 启用 Gitea 服务
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/enable [post]
|
||||
func (r *GiteaController) Enable(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceEnable("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "启用 Gitea 服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Disable
|
||||
//
|
||||
// @Summary 禁用服务
|
||||
// @Description 禁用 Gitea 服务
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/disable [post]
|
||||
func (r *GiteaController) Disable(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceDisable("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "禁用 Gitea 服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Restart
|
||||
//
|
||||
// @Summary 重启服务
|
||||
// @Description 重启 Gitea 服务
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/restart [post]
|
||||
func (r *GiteaController) Restart(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceRestart("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重启 Gitea 服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Start
|
||||
//
|
||||
// @Summary 启动服务
|
||||
// @Description 启动 Gitea 服务
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/start [post]
|
||||
func (r *GiteaController) Start(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStart("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "启动 Gitea 服务失败")
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus("gitea")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Gitea 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, status)
|
||||
}
|
||||
|
||||
// Stop
|
||||
//
|
||||
// @Summary 停止服务
|
||||
// @Description 停止 Gitea 服务
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/stop [post]
|
||||
func (r *GiteaController) Stop(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStop("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "停止 Gitea 服务失败")
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus("gitea")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Gitea 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, !status)
|
||||
}
|
||||
|
||||
// GetConfig
|
||||
//
|
||||
// @Summary 获取配置
|
||||
// @Description 获取 Gitea 配置
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/config [get]
|
||||
func (r *GiteaController) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := tools.Read("/www/server/gitea/app.ini")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateConfig
|
||||
//
|
||||
// @Summary 更新配置
|
||||
// @Description 更新 Gitea 配置
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/config [post]
|
||||
func (r *GiteaController) UpdateConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateConfig
|
||||
sanitize := controllers.Sanitize(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.Write("/www/server/gitea/app.ini", updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := tools.ServiceRestart("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type Mysql57Controller struct {
|
||||
setting internal.Setting
|
||||
backup internal.Backup
|
||||
}
|
||||
|
||||
func NewMysql57Controller() *Mysql57Controller {
|
||||
return &Mysql57Controller{
|
||||
setting: services.NewSettingImpl(),
|
||||
backup: services.NewBackupImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (r *Mysql57Controller) Status(ctx http.Context) http.Response {
|
||||
status, err := tools.ServiceStatus("mysqld")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, status)
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (r *Mysql57Controller) Reload(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceReload("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (r *Mysql57Controller) Restart(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceRestart("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重启MySQL服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (r *Mysql57Controller) Start(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStart("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "启动MySQL服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (r *Mysql57Controller) Stop(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStop("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "停止MySQL服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (r *Mysql57Controller) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (r *Mysql57Controller) SaveConfig(ctx http.Context) http.Response {
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
|
||||
}
|
||||
|
||||
if err := tools.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
|
||||
}
|
||||
|
||||
return r.Restart(ctx)
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (r *Mysql57Controller) Load(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus("mysqld")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
|
||||
}
|
||||
if !status {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
|
||||
}
|
||||
|
||||
raw, err := tools.Exec("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
|
||||
}
|
||||
|
||||
var data []map[string]string
|
||||
expressions := []struct {
|
||||
regex string
|
||||
name string
|
||||
}{
|
||||
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
|
||||
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
|
||||
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
|
||||
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
|
||||
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
|
||||
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
|
||||
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
|
||||
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
|
||||
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
|
||||
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
|
||||
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
|
||||
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
|
||||
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
|
||||
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
|
||||
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
|
||||
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
|
||||
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
|
||||
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
|
||||
}
|
||||
|
||||
for _, expression := range expressions {
|
||||
re := regexp.MustCompile(expression.regex)
|
||||
matches := re.FindStringSubmatch(raw)
|
||||
if len(matches) > 1 {
|
||||
d := map[string]string{"name": expression.name, "value": matches[1]}
|
||||
if expression.name == "发送" || expression.name == "接收" {
|
||||
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
|
||||
}
|
||||
|
||||
data = append(data, d)
|
||||
}
|
||||
}
|
||||
|
||||
// 索引命中率
|
||||
readRequests := cast.ToFloat64(data[9]["value"])
|
||||
reads := cast.ToFloat64(data[10]["value"])
|
||||
data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
|
||||
// Innodb 索引命中率
|
||||
bufferPoolReads := cast.ToFloat64(data[11]["value"])
|
||||
bufferPoolReadRequests := cast.ToFloat64(data[12]["value"])
|
||||
data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
|
||||
|
||||
return controllers.Success(ctx, data)
|
||||
}
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (r *Mysql57Controller) ErrorLog(ctx http.Context) http.Response {
|
||||
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, log)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (r *Mysql57Controller) ClearErrorLog(ctx http.Context) http.Response {
|
||||
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// SlowLog 获取慢查询日志
|
||||
func (r *Mysql57Controller) SlowLog(ctx http.Context) http.Response {
|
||||
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, log)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, log)
|
||||
}
|
||||
|
||||
// ClearSlowLog 清空慢查询日志
|
||||
func (r *Mysql57Controller) ClearSlowLog(ctx http.Context) http.Response {
|
||||
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// GetRootPassword 获取root密码
|
||||
func (r *Mysql57Controller) GetRootPassword(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, rootPassword)
|
||||
}
|
||||
|
||||
// SetRootPassword 设置root密码
|
||||
func (r *Mysql57Controller) SetRootPassword(ctx http.Context) http.Response {
|
||||
status, err := tools.ServiceStatus("mysqld")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
|
||||
}
|
||||
if !status {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
|
||||
}
|
||||
|
||||
rootPassword := ctx.Request().Input("password")
|
||||
if len(rootPassword) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码不能为空")
|
||||
}
|
||||
|
||||
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if oldRootPassword != rootPassword {
|
||||
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
}
|
||||
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
}
|
||||
err := r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
|
||||
if err != nil {
|
||||
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\""); err != nil {
|
||||
return nil
|
||||
}
|
||||
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return nil
|
||||
}
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
}
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// DatabaseList 获取数据库列表
|
||||
func (r *Mysql57Controller) DatabaseList(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
type database struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SHOW DATABASES")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var databases []database
|
||||
for rows.Next() {
|
||||
var d database
|
||||
err := rows.Scan(&d.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
databases = append(databases, d)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
|
||||
}
|
||||
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(databases) {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(databases) {
|
||||
endIndex = len(databases)
|
||||
}
|
||||
pagedDatabases := databases[startIndex:endIndex]
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": len(databases),
|
||||
"items": pagedDatabases,
|
||||
})
|
||||
}
|
||||
|
||||
// AddDatabase 添加数据库
|
||||
func (r *Mysql57Controller) AddDatabase(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// DeleteDatabase 删除数据库
|
||||
func (r *Mysql57Controller) DeleteDatabase(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// BackupList 获取备份列表
|
||||
func (r *Mysql57Controller) BackupList(ctx http.Context) http.Response {
|
||||
backupList, err := r.backup.MysqlList()
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(backupList) {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []internal.BackupFile{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(backupList) {
|
||||
endIndex = len(backupList)
|
||||
}
|
||||
pagedBackupList := backupList[startIndex:endIndex]
|
||||
if pagedBackupList == nil {
|
||||
pagedBackupList = []internal.BackupFile{}
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": len(backupList),
|
||||
"items": pagedBackupList,
|
||||
})
|
||||
}
|
||||
|
||||
// UploadBackup 上传备份
|
||||
func (r *Mysql57Controller) UploadBackup(ctx http.Context) http.Response {
|
||||
file, err := ctx.Request().File("file")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
|
||||
}
|
||||
|
||||
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
if !tools.Exists(backupPath) {
|
||||
if err = tools.Mkdir(backupPath, 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
name := file.GetClientOriginalName()
|
||||
_, err = file.StoreAs(backupPath, name)
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// CreateBackup 创建备份
|
||||
func (r *Mysql57Controller) CreateBackup(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
database := ctx.Request().Input("database")
|
||||
err = r.backup.MysqlBackup(database)
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// DeleteBackup 删除备份
|
||||
func (r *Mysql57Controller) DeleteBackup(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
|
||||
fileName := ctx.Request().Input("name")
|
||||
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// RestoreBackup 还原备份
|
||||
func (r *Mysql57Controller) RestoreBackup(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"backup": "required|min_len:1|max_len:255",
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
err = r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// UserList 用户列表
|
||||
func (r *Mysql57Controller) UserList(ctx http.Context) http.Response {
|
||||
type user struct {
|
||||
User string `json:"user"`
|
||||
Host string `json:"host"`
|
||||
Grants []string `json:"grants"`
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT user, host FROM mysql.user")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var userGrants []user
|
||||
|
||||
for rows.Next() {
|
||||
var u user
|
||||
err := rows.Scan(&u.User, &u.Host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 查询用户权限
|
||||
grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer grantsRows.Close()
|
||||
|
||||
for grantsRows.Next() {
|
||||
var grant string
|
||||
err := grantsRows.Scan(&grant)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
u.Grants = append(u.Grants, grant)
|
||||
}
|
||||
|
||||
if err := grantsRows.Err(); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
userGrants = append(userGrants, u)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
|
||||
}
|
||||
|
||||
page := ctx.Request().QueryInt("page", 1)
|
||||
limit := ctx.Request().QueryInt("limit", 10)
|
||||
startIndex := (page - 1) * limit
|
||||
endIndex := page * limit
|
||||
if startIndex > len(userGrants) {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(userGrants) {
|
||||
endIndex = len(userGrants)
|
||||
}
|
||||
pagedUserGrants := userGrants[startIndex:endIndex]
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": len(userGrants),
|
||||
"items": pagedUserGrants,
|
||||
})
|
||||
}
|
||||
|
||||
// AddUser 添加用户
|
||||
func (r *Mysql57Controller) AddUser(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (r *Mysql57Controller) DeleteUser(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// SetUserPassword 设置用户密码
|
||||
func (r *Mysql57Controller) SetUserPassword(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// SetUserPrivileges 设置用户权限
|
||||
func (r *Mysql57Controller) SetUserPrivileges(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"database": "required|min_len:1|max_len:255",
|
||||
})
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if validator.Fails() {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
+38
-37
@@ -8,27 +8,28 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type Mysql80Controller struct {
|
||||
type MySQLController struct {
|
||||
setting internal.Setting
|
||||
backup internal.Backup
|
||||
}
|
||||
|
||||
func NewMysql80Controller() *Mysql80Controller {
|
||||
return &Mysql80Controller{
|
||||
func NewMySQLController() *MySQLController {
|
||||
return &MySQLController{
|
||||
setting: services.NewSettingImpl(),
|
||||
backup: services.NewBackupImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
// Status 获取运行状态
|
||||
func (r *Mysql80Controller) Status(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) Status(ctx http.Context) http.Response {
|
||||
status, err := tools.ServiceStatus("mysqld")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
|
||||
@@ -38,7 +39,7 @@ func (r *Mysql80Controller) Status(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// Reload 重载配置
|
||||
func (r *Mysql80Controller) Reload(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) Reload(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceReload("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL配置失败")
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func (r *Mysql80Controller) Reload(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// Restart 重启服务
|
||||
func (r *Mysql80Controller) Restart(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) Restart(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceRestart("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重启MySQL服务失败")
|
||||
}
|
||||
@@ -56,7 +57,7 @@ func (r *Mysql80Controller) Restart(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (r *Mysql80Controller) Start(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) Start(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStart("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "启动MySQL服务失败")
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func (r *Mysql80Controller) Start(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (r *Mysql80Controller) Stop(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) Stop(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStop("mysqld"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "停止MySQL服务失败")
|
||||
}
|
||||
@@ -74,7 +75,7 @@ func (r *Mysql80Controller) Stop(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (r *Mysql80Controller) GetConfig(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
|
||||
@@ -84,7 +85,7 @@ func (r *Mysql80Controller) GetConfig(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// SaveConfig 保存配置
|
||||
func (r *Mysql80Controller) SaveConfig(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) SaveConfig(ctx http.Context) http.Response {
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
|
||||
@@ -98,7 +99,7 @@ func (r *Mysql80Controller) SaveConfig(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// Load 获取负载
|
||||
func (r *Mysql80Controller) Load(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) Load(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
|
||||
@@ -168,7 +169,7 @@ func (r *Mysql80Controller) Load(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// ErrorLog 获取错误日志
|
||||
func (r *Mysql80Controller) ErrorLog(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
|
||||
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, log)
|
||||
@@ -178,7 +179,7 @@ func (r *Mysql80Controller) ErrorLog(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
|
||||
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
@@ -187,7 +188,7 @@ func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// SlowLog 获取慢查询日志
|
||||
func (r *Mysql80Controller) SlowLog(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
|
||||
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, log)
|
||||
@@ -197,7 +198,7 @@ func (r *Mysql80Controller) SlowLog(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// ClearSlowLog 清空慢查询日志
|
||||
func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) ClearSlowLog(ctx http.Context) http.Response {
|
||||
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
@@ -205,7 +206,7 @@ func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// GetRootPassword 获取root密码
|
||||
func (r *Mysql80Controller) GetRootPassword(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if len(rootPassword) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
|
||||
@@ -215,7 +216,7 @@ func (r *Mysql80Controller) GetRootPassword(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// SetRootPassword 设置root密码
|
||||
func (r *Mysql80Controller) SetRootPassword(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
|
||||
status, err := tools.ServiceStatus("mysqld")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
|
||||
@@ -253,7 +254,7 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// DatabaseList 获取数据库列表
|
||||
func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
type database struct {
|
||||
Name string `json:"name"`
|
||||
@@ -308,7 +309,7 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// AddDatabase 添加数据库
|
||||
func (r *Mysql80Controller) AddDatabase(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
@@ -343,7 +344,7 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// DeleteDatabase 删除数据库
|
||||
func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
@@ -364,7 +365,7 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// BackupList 获取备份列表
|
||||
func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) BackupList(ctx http.Context) http.Response {
|
||||
backupList, err := r.backup.MysqlList()
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
@@ -377,7 +378,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
|
||||
if startIndex > len(backupList) {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []internal.BackupFile{},
|
||||
"items": []types.BackupFile{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(backupList) {
|
||||
@@ -385,7 +386,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
|
||||
}
|
||||
pagedBackupList := backupList[startIndex:endIndex]
|
||||
if pagedBackupList == nil {
|
||||
pagedBackupList = []internal.BackupFile{}
|
||||
pagedBackupList = []types.BackupFile{}
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
@@ -395,7 +396,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// UploadBackup 上传备份
|
||||
func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
|
||||
file, err := ctx.Request().File("file")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
|
||||
@@ -418,7 +419,7 @@ func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// CreateBackup 创建备份
|
||||
func (r *Mysql80Controller) CreateBackup(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) CreateBackup(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
})
|
||||
@@ -439,7 +440,7 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// DeleteBackup 删除备份
|
||||
func (r *Mysql80Controller) DeleteBackup(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"name": "required|min_len:1|max_len:255",
|
||||
})
|
||||
@@ -460,7 +461,7 @@ func (r *Mysql80Controller) DeleteBackup(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// RestoreBackup 还原备份
|
||||
func (r *Mysql80Controller) RestoreBackup(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"backup": "required|min_len:1|max_len:255",
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
@@ -481,7 +482,7 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// UserList 用户列表
|
||||
func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) UserList(ctx http.Context) http.Response {
|
||||
type user struct {
|
||||
User string `json:"user"`
|
||||
Host string `json:"host"`
|
||||
@@ -560,7 +561,7 @@ func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// AddUser 添加用户
|
||||
func (r *Mysql80Controller) AddUser(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) AddUser(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
@@ -591,7 +592,7 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (r *Mysql80Controller) DeleteUser(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
})
|
||||
@@ -612,7 +613,7 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// SetUserPassword 设置用户密码
|
||||
func (r *Mysql80Controller) SetUserPassword(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:255",
|
||||
@@ -638,7 +639,7 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// SetUserPrivileges 设置用户权限
|
||||
func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response {
|
||||
validator, err := ctx.Request().Validate(map[string]string{
|
||||
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"database": "required|min_len:1|max_len:255",
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type OpenRestyController struct {
|
||||
@@ -117,9 +117,9 @@ func (r *OpenRestyController) ClearErrorLog(ctx http.Context) http.Response {
|
||||
|
||||
// Load 获取负载
|
||||
func (r *OpenRestyController) Load(ctx http.Context) http.Response {
|
||||
client := req.C().SetTimeout(10 * time.Second)
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
|
||||
if err != nil || !resp.IsSuccessState() {
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"panel/app/http/controllers"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
)
|
||||
|
||||
type PHPController struct {
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type PhpMyAdminController struct {
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/plugins/podman"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type PodmanController struct {
|
||||
}
|
||||
|
||||
func NewPodmanController() *PodmanController {
|
||||
return &PodmanController{}
|
||||
}
|
||||
|
||||
// Status
|
||||
//
|
||||
// @Summary 服务状态
|
||||
// @Description 获取 Podman 服务状态
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/status [get]
|
||||
func (r *PodmanController) Status(ctx http.Context) http.Response {
|
||||
status, err := tools.ServiceStatus("podman")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Podman 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, status)
|
||||
}
|
||||
|
||||
// IsEnabled
|
||||
//
|
||||
// @Summary 是否启用服务
|
||||
// @Description 获取是否启用 Podman 服务
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/isEnabled [get]
|
||||
func (r *PodmanController) IsEnabled(ctx http.Context) http.Response {
|
||||
enabled, err := tools.ServiceIsEnabled("podman")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Podman 服务启用状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, enabled)
|
||||
}
|
||||
|
||||
// Enable
|
||||
//
|
||||
// @Summary 启用服务
|
||||
// @Description 启用 Podman 服务
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/enable [post]
|
||||
func (r *PodmanController) Enable(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceEnable("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "启用 Podman 服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Disable
|
||||
//
|
||||
// @Summary 禁用服务
|
||||
// @Description 禁用 Podman 服务
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/disable [post]
|
||||
func (r *PodmanController) Disable(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceDisable("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "禁用 Podman 服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Restart
|
||||
//
|
||||
// @Summary 重启服务
|
||||
// @Description 重启 Podman 服务
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/restart [post]
|
||||
func (r *PodmanController) Restart(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceRestart("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重启 Podman 服务失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Start
|
||||
//
|
||||
// @Summary 启动服务
|
||||
// @Description 启动 Podman 服务
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/start [post]
|
||||
func (r *PodmanController) Start(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStart("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "启动 Podman 服务失败")
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus("podman")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Podman 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, status)
|
||||
}
|
||||
|
||||
// Stop
|
||||
//
|
||||
// @Summary 停止服务
|
||||
// @Description 停止 Podman 服务
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/stop [post]
|
||||
func (r *PodmanController) Stop(ctx http.Context) http.Response {
|
||||
if err := tools.ServiceStop("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "停止 Podman 服务失败")
|
||||
}
|
||||
|
||||
status, err := tools.ServiceStatus("podman")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取 Podman 服务运行状态失败")
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, !status)
|
||||
}
|
||||
|
||||
// GetRegistryConfig
|
||||
//
|
||||
// @Summary 获取注册表配置
|
||||
// @Description 获取 Podman 注册表配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/registryConfig [get]
|
||||
func (r *PodmanController) GetRegistryConfig(ctx http.Context) http.Response {
|
||||
config, err := tools.Read("/etc/containers/registries.conf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateRegistryConfig
|
||||
//
|
||||
// @Summary 更新注册表配置
|
||||
// @Description 更新 Podman 注册表配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateRegistryConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/registryConfig [post]
|
||||
func (r *PodmanController) UpdateRegistryConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateRegistryConfig
|
||||
sanitize := controllers.Sanitize(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.Write("/etc/containers/registries.conf", updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := tools.ServiceRestart("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// GetStorageConfig
|
||||
//
|
||||
// @Summary 获取存储配置
|
||||
// @Description 获取 Podman 存储配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/storageConfig [get]
|
||||
func (r *PodmanController) GetStorageConfig(ctx http.Context) http.Response {
|
||||
config, err := tools.Read("/etc/containers/storage.conf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateStorageConfig
|
||||
//
|
||||
// @Summary 更新存储配置
|
||||
// @Description 更新 Podman 存储配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateStorageConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/storageConfig [post]
|
||||
func (r *PodmanController) UpdateStorageConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateStorageConfig
|
||||
sanitize := controllers.Sanitize(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := tools.Write("/etc/containers/storage.conf", updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := tools.ServiceRestart("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type Postgresql15Controller struct {
|
||||
@@ -321,7 +321,7 @@ func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
|
||||
if startIndex > len(backupList) {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []internal.BackupFile{},
|
||||
"items": []types.BackupFile{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(backupList) {
|
||||
@@ -329,7 +329,7 @@ func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
|
||||
}
|
||||
pagedBackupList := backupList[startIndex:endIndex]
|
||||
if pagedBackupList == nil {
|
||||
pagedBackupList = []internal.BackupFile{}
|
||||
pagedBackupList = []types.BackupFile{}
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type Postgresql16Controller struct {
|
||||
@@ -321,7 +321,7 @@ func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
|
||||
if startIndex > len(backupList) {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []internal.BackupFile{},
|
||||
"items": []types.BackupFile{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(backupList) {
|
||||
@@ -329,7 +329,7 @@ func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
|
||||
}
|
||||
pagedBackupList := backupList[startIndex:endIndex]
|
||||
if pagedBackupList == nil {
|
||||
pagedBackupList = []internal.BackupFile{}
|
||||
pagedBackupList = []types.BackupFile{}
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type PureFtpdController struct {
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type RedisController struct {
|
||||
|
||||
@@ -6,23 +6,18 @@ import (
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
commonrequests "panel/app/http/requests/common"
|
||||
requests "panel/app/http/requests/plugins/rsync"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/plugins/rsync"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type RsyncController struct {
|
||||
setting internal.Setting
|
||||
}
|
||||
|
||||
func NewRsyncController() *RsyncController {
|
||||
return &RsyncController{
|
||||
setting: services.NewSettingImpl(),
|
||||
}
|
||||
return &RsyncController{}
|
||||
}
|
||||
|
||||
// Status
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"github.com/goravel/framework/support/json"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type S3fsController struct {
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"panel/pkg/tools"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
)
|
||||
|
||||
type SupervisorController struct {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/http/controllers"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type ToolBoxController struct {
|
||||
|
||||
@@ -4,20 +4,27 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/spf13/cast"
|
||||
commonrequests "panel/app/http/requests/common"
|
||||
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type SafeController struct {
|
||||
// Dependent services
|
||||
ssh string
|
||||
}
|
||||
|
||||
func NewSafeController() *SafeController {
|
||||
var ssh string
|
||||
if tools.IsRHEL() {
|
||||
ssh = "sshd"
|
||||
} else {
|
||||
ssh = "ssh"
|
||||
}
|
||||
|
||||
return &SafeController{
|
||||
// Inject services
|
||||
ssh: ssh,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,24 +35,23 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) http.Response {
|
||||
|
||||
// SetFirewallStatus 设置防火墙状态
|
||||
func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
|
||||
var out string
|
||||
var err error
|
||||
if ctx.Request().InputBool("status") {
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl start firewalld")
|
||||
err = tools.ServiceStart("firewalld")
|
||||
} else {
|
||||
out, err = tools.Exec("echo y | ufw enable")
|
||||
_, err = tools.Exec("echo y | ufw enable")
|
||||
}
|
||||
} else {
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl stop firewalld")
|
||||
err = tools.ServiceStop("firewalld")
|
||||
} else {
|
||||
out, err = tools.Exec("ufw disable")
|
||||
_, err = tools.Exec("ufw disable")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -213,27 +219,11 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
|
||||
|
||||
// firewallStatus 获取防火墙状态
|
||||
func (r *SafeController) firewallStatus() bool {
|
||||
var out string
|
||||
var err error
|
||||
var running bool
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl status firewalld | grep Active | awk '{print $3}'")
|
||||
if out == "(running)" {
|
||||
running = true
|
||||
} else {
|
||||
running = false
|
||||
}
|
||||
running, _ = tools.ServiceStatus("firewalld")
|
||||
} else {
|
||||
out, err = tools.Exec("ufw status | grep Status | awk '{print $2}'")
|
||||
if out == "active" {
|
||||
running = true
|
||||
} else {
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
running, _ = tools.ServiceStatus("ufw")
|
||||
}
|
||||
|
||||
return running
|
||||
@@ -241,54 +231,29 @@ func (r *SafeController) firewallStatus() bool {
|
||||
|
||||
// GetSshStatus 获取 SSH 状态
|
||||
func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
|
||||
var out string
|
||||
var err error
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
|
||||
} else {
|
||||
out, err = tools.Exec("systemctl status ssh | grep Active | awk '{print $3}'")
|
||||
}
|
||||
|
||||
running, err := tools.ServiceStatus(r.ssh)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, out == "(running)")
|
||||
return Success(ctx, running)
|
||||
}
|
||||
|
||||
// SetSshStatus 设置 SSH 状态
|
||||
func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
|
||||
if ctx.Request().InputBool("status") {
|
||||
if tools.IsRHEL() {
|
||||
if out, err := tools.Exec("systemctl enable sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl start sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
} else {
|
||||
if out, err := tools.Exec("systemctl enable ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl start ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if err := tools.ServiceEnable(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err := tools.ServiceStart(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
} else {
|
||||
if tools.IsRHEL() {
|
||||
if out, err := tools.Exec("systemctl stop sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl disable sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
} else {
|
||||
if out, err := tools.Exec("systemctl stop ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl disable ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if err := tools.ServiceStop(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err := tools.ServiceDisable(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +267,7 @@ func (r *SafeController) GetSshPort(ctx http.Context) http.Response {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return Success(ctx, out)
|
||||
return Success(ctx, cast.ToInt(out))
|
||||
}
|
||||
|
||||
// SetSshPort 设置 SSH 端口
|
||||
@@ -319,12 +284,11 @@ func (r *SafeController) SetSshPort(ctx http.Context) http.Response {
|
||||
_, _ = tools.Exec("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
|
||||
_, _ = tools.Exec("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
|
||||
|
||||
out, err := tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
|
||||
if err != nil || out != "(running)" {
|
||||
Error(ctx, http.StatusInternalServerError, out)
|
||||
status, _ := tools.ServiceStatus(r.ssh)
|
||||
if status {
|
||||
_ = tools.ServiceRestart(r.ssh)
|
||||
}
|
||||
|
||||
_, _ = tools.Exec("systemctl restart sshd")
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
requests "panel/app/http/requests/setting"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/setting"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type SettingController struct {
|
||||
@@ -60,6 +60,7 @@ func (r *SettingController) List(ctx http.Context) http.Response {
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
"name": r.setting.Get(models.SettingKeyName),
|
||||
"language": facades.Config().GetString("app.locale"),
|
||||
"entrance": facades.Config().GetString("http.entrance"),
|
||||
"ssl": facades.Config().GetBool("panel.ssl"),
|
||||
"website_path": r.setting.Get(models.SettingKeyWebsitePath),
|
||||
@@ -189,7 +190,6 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
|
||||
}).Info("获取面板入口失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
entrance := cast.ToString(updateRequest.Entrance)
|
||||
if oldEntrance != entrance {
|
||||
if out, err := tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=" + entrance + "!g' /www/panel/panel.conf"); err != nil {
|
||||
@@ -197,6 +197,19 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
oldLanguage, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_LOCALE | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("获取面板语言失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
if oldLanguage != updateRequest.Language {
|
||||
if out, err := tools.Exec("sed -i 's/APP_LOCALE=" + oldLanguage + "/APP_LOCALE=" + updateRequest.Language + "/g' /www/panel/panel.conf"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
}
|
||||
|
||||
if updateRequest.SSL {
|
||||
if out, err := tools.Exec("sed -i 's/APP_SSL=false/APP_SSL=true/g' /www/panel/panel.conf"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
@@ -207,7 +220,7 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
if oldPort != port || oldEntrance != entrance || updateRequest.SSL != facades.Config().GetBool("panel.ssl") {
|
||||
if oldPort != port || oldEntrance != entrance || oldLanguage != updateRequest.Language || updateRequest.SSL != facades.Config().GetBool("panel.ssl") {
|
||||
tools.RestartPanel()
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/ssh"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/ssh"
|
||||
)
|
||||
|
||||
type SshController struct {
|
||||
@@ -103,7 +103,7 @@ func (r *SshController) Session(ctx http.Context) http.Response {
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
config := ssh.SSHClientConfigPassword(
|
||||
config := ssh.ClientConfigPassword(
|
||||
r.setting.Get(models.SettingKeySshHost)+":"+r.setting.Get(models.SettingKeySshPort),
|
||||
r.setting.Get(models.SettingKeySshUser),
|
||||
r.setting.Get(models.SettingKeySshPassword),
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/swaggo/http-swagger/v2"
|
||||
|
||||
_ "panel/docs"
|
||||
_ "github.com/TheTNB/panel/docs"
|
||||
)
|
||||
|
||||
type SwaggerController struct {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type TaskController struct {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/http/requests/user"
|
||||
"panel/app/models"
|
||||
"github.com/TheTNB/panel/app/http/requests/user"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
)
|
||||
|
||||
type UserController struct {
|
||||
|
||||
@@ -8,12 +8,13 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
commonrequests "panel/app/http/requests/common"
|
||||
requests "panel/app/http/requests/website"
|
||||
"panel/app/models"
|
||||
"panel/internal"
|
||||
"panel/internal/services"
|
||||
"panel/pkg/tools"
|
||||
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/website"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type WebsiteController struct {
|
||||
@@ -83,7 +84,7 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
|
||||
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
|
||||
}
|
||||
|
||||
website := internal.PanelWebsite{
|
||||
website := types.Website{
|
||||
Name: addRequest.Name,
|
||||
Status: true,
|
||||
Domains: addRequest.Domains,
|
||||
@@ -205,7 +206,7 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param id path int true "网站 ID"
|
||||
// @Success 200 {object} SuccessResponse{data=internal.PanelWebsite}
|
||||
// @Success 200 {object} SuccessResponse{data=types.Website}
|
||||
// @Router /panel/websites/{id}/config [get]
|
||||
func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
|
||||
var idRequest requests.ID
|
||||
@@ -328,7 +329,7 @@ func (r *WebsiteController) UpdateRemark(ctx http.Context) http.Response {
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data query commonrequests.Paginate true "request"
|
||||
// @Success 200 {object} SuccessResponse{data=[]internal.BackupFile}
|
||||
// @Success 200 {object} SuccessResponse{data=[]types.BackupFile}
|
||||
// @Router /panel/website/backupList [get]
|
||||
func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
|
||||
var paginateRequest commonrequests.Paginate
|
||||
@@ -350,7 +351,7 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
|
||||
if startIndex > len(backupList) {
|
||||
return Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []internal.BackupFile{},
|
||||
"items": []types.BackupFile{},
|
||||
})
|
||||
}
|
||||
if endIndex > len(backupList) {
|
||||
@@ -358,7 +359,7 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
|
||||
}
|
||||
pagedBackupList := backupList[startIndex:endIndex]
|
||||
if pagedBackupList == nil {
|
||||
pagedBackupList = []internal.BackupFile{}
|
||||
pagedBackupList = []types.BackupFile{}
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
@@ -605,8 +606,8 @@ server
|
||||
if err := tools.Write("/www/server/vhost/rewrite"+website.Name+".conf", "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if exec, err := tools.Exec("systemctl reload openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, exec)
|
||||
if err := tools.ServiceReload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -669,11 +670,11 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
|
||||
if err = tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
if exec, err := tools.Exec("systemctl reload openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, exec)
|
||||
if err = tools.ServiceReload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package http
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"panel/app/http/middleware"
|
||||
"github.com/TheTNB/panel/app/http/middleware"
|
||||
)
|
||||
|
||||
type Kernel struct {
|
||||
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
// Jwt 确保通过 JWT 鉴权
|
||||
func Jwt() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
translate := facades.Lang(ctx)
|
||||
token := ctx.Request().Header("Authorization", ctx.Request().Header("Sec-WebSocket-Protocol"))
|
||||
if len(token) == 0 {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": "未登录",
|
||||
"message": translate.Get("auth.token.missing"),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -26,7 +27,7 @@ func Jwt() http.Middleware {
|
||||
if err != nil {
|
||||
// 到达刷新时间上限
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": "登录已过期",
|
||||
"message": translate.Get("auth.token.expired"),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -34,7 +35,7 @@ func Jwt() http.Middleware {
|
||||
token = "Bearer " + token
|
||||
} else {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": "登录已过期",
|
||||
"message": translate.Get("auth.token.expired"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,22 +4,27 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/translation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/internal/services"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
)
|
||||
|
||||
// MustInstall 确保已安装插件
|
||||
func MustInstall() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
path := ctx.Request().Path()
|
||||
translate := facades.Lang(ctx)
|
||||
var slug string
|
||||
if strings.HasPrefix(path, "/api/panel/website") {
|
||||
slug = "openresty"
|
||||
} else if strings.HasPrefix(path, "/api/panel/container") {
|
||||
slug = "podman"
|
||||
} else {
|
||||
pathArr := strings.Split(path, "/")
|
||||
if len(pathArr) < 4 {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
|
||||
"message": "插件不存在",
|
||||
"message": translate.Get("errors.plugin.notExist"),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -31,14 +36,18 @@ func MustInstall() http.Middleware {
|
||||
installedPlugins, err := services.NewPluginImpl().AllInstalled()
|
||||
if err != nil {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, http.Json{
|
||||
"message": "系统内部错误",
|
||||
"message": translate.Get("errors.internal"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if installedPlugin.Slug != plugin.Slug {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
|
||||
"message": "插件 " + slug + " 未安装",
|
||||
"message": translate.Get("errors.plugin.notInstalled", translation.Option{
|
||||
Replace: map[string]string{
|
||||
"slug": slug,
|
||||
},
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -53,7 +62,12 @@ func MustInstall() http.Middleware {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
|
||||
"message": "插件 " + slug + " 需要依赖 " + require + " 插件",
|
||||
"message": translate.Get("errors.plugin.dependent", translation.Option{
|
||||
Replace: map[string]string{
|
||||
"slug": slug,
|
||||
"dependency": require,
|
||||
},
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -63,7 +77,12 @@ func MustInstall() http.Middleware {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
|
||||
"message": "插件 " + slug + " 不兼容 " + exclude + " 插件",
|
||||
"message": translate.Get("errors.plugin.incompatible", translation.Option{
|
||||
Replace: map[string]string{
|
||||
"slug": slug,
|
||||
"exclude": exclude,
|
||||
},
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,32 +2,34 @@ package middleware
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/internal"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
// Status 检查程序状态
|
||||
func Status() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
switch internal.Status {
|
||||
case internal.StatusUpgrade:
|
||||
translate := facades.Lang(ctx)
|
||||
switch types.Status {
|
||||
case types.StatusUpgrade:
|
||||
ctx.Request().AbortWithStatusJson(http.StatusServiceUnavailable, http.Json{
|
||||
"message": "面板升级中,请稍后",
|
||||
"message": translate.Get("status.upgrade"),
|
||||
})
|
||||
return
|
||||
case internal.StatusMaintain:
|
||||
case types.StatusMaintain:
|
||||
ctx.Request().AbortWithStatusJson(http.StatusServiceUnavailable, http.Json{
|
||||
"message": "面板正在运行维护,请稍后",
|
||||
"message": translate.Get("status.maintain"),
|
||||
})
|
||||
return
|
||||
case internal.StatusClosed:
|
||||
case types.StatusClosed:
|
||||
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
|
||||
"message": "面板已关闭",
|
||||
"message": translate.Get("status.closed"),
|
||||
})
|
||||
return
|
||||
case internal.StatusFailed:
|
||||
case types.StatusFailed:
|
||||
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, http.Json{
|
||||
"message": "面板运行出错,请检查排除或联系支持",
|
||||
"message": translate.Get("status.failed"),
|
||||
})
|
||||
return
|
||||
default:
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type CertDeploy struct {
|
||||
ID uint `form:"id" json:"id"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id"`
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
|
||||
}
|
||||
|
||||
func (r *CertDeploy) Authorize(ctx http.Context) error {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/pkg/acme"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type DNSStore struct {
|
||||
@@ -26,7 +26,6 @@ func (r *DNSStore) Rules(ctx http.Context) map[string]string {
|
||||
"data.token": "required_if:type,dnspod",
|
||||
"data.access_key": "required_if:type,aliyun",
|
||||
"data.secret_key": "required_if:type,aliyun",
|
||||
"data.email": "required_if:type,cloudflare",
|
||||
"data.api_key": "required_if:type,cloudflare",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/pkg/acme"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type DNSUpdate struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type ContainerCreate struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type ContainerUpdate struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type NetworkCreate struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/types"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type VolumeCreate struct {
|
||||
|
||||
@@ -17,8 +17,8 @@ func (r *Archive) Authorize(ctx http.Context) error {
|
||||
func (r *Archive) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"paths": "array",
|
||||
"paths.*": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"file": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
|
||||
"paths.*": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"file": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type Copy struct {
|
||||
Old string `form:"old" json:"old"`
|
||||
New string `form:"new" json:"new"`
|
||||
Source string `form:"source" json:"source"`
|
||||
Target string `form:"target" json:"target"`
|
||||
}
|
||||
|
||||
func (r *Copy) Authorize(ctx http.Context) error {
|
||||
@@ -16,8 +16,8 @@ func (r *Copy) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Copy) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"old": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"new": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
|
||||
"source": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"target": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ func (r *Exist) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Exist) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"path": `regex:^/([a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*)?$|path_exists`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type Move struct {
|
||||
Old string `form:"old" json:"old"`
|
||||
New string `form:"new" json:"new"`
|
||||
Source string `form:"source" json:"source"`
|
||||
Target string `form:"target" json:"target"`
|
||||
}
|
||||
|
||||
func (r *Move) Authorize(ctx http.Context) error {
|
||||
@@ -16,8 +16,8 @@ func (r *Move) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Move) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"old": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"new": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
|
||||
"source": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"target": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ func (r *NotExist) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *NotExist) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func (r *Permission) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Permission) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"mode": "regex:^[0-7]{3}$|uint",
|
||||
"owner": "regex:^[a-zA-Z0-9_-]+$",
|
||||
"group": "regex:^[a-zA-Z0-9_-]+$",
|
||||
|
||||
@@ -16,7 +16,7 @@ func (r *Save) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Save) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"content": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func (r *Search) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Search) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"keyword": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"file": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
|
||||
"file": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func (r *Upload) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Upload) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"file": "required",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Service string `form:"service" json:"service"`
|
||||
}
|
||||
|
||||
func (r *Service) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Service) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"service": "required|string|in:frps,frpc",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Service) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Service) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Service) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateConfig struct {
|
||||
Service string `form:"service" json:"service"`
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"service": "required|string|in:frps,frpc",
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateRegistryConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateStorageConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func (r *Create) Authorize(ctx http.Context) error {
|
||||
func (r *Create) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"name": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"comment": "string",
|
||||
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"secret": "required|min_len:8",
|
||||
|
||||
@@ -21,7 +21,7 @@ func (r *Update) Authorize(ctx http.Context) error {
|
||||
func (r *Update) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"name": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"comment": "string",
|
||||
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"secret": "required|min_len:8",
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
type Update struct {
|
||||
Name string `form:"name" json:"name"`
|
||||
Language string `form:"language" json:"language"`
|
||||
Port uint `form:"port" json:"port" filter:"uint"`
|
||||
BackupPath string `form:"backup_path" json:"backup_path"`
|
||||
WebsitePath string `form:"website_path" json:"website_path"`
|
||||
@@ -24,6 +25,7 @@ func (r *Update) Authorize(ctx http.Context) error {
|
||||
func (r *Update) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"name": "required|string:2,20",
|
||||
"language": "required|in:zh_CN,en",
|
||||
"port": "required|int:1000,65535",
|
||||
"backup_path": "required|string:2,255",
|
||||
"website_path": "required|string:2,255",
|
||||
|
||||
@@ -27,7 +27,7 @@ func (r *Add) Rules(ctx http.Context) map[string]string {
|
||||
"name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$|not_exists:websites,name|not_in:phpmyadmin,mysql,panel,ssh",
|
||||
"domains": "required|slice",
|
||||
"ports": "required|slice",
|
||||
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"php": "required",
|
||||
"db": "bool",
|
||||
"db_type": "required_if:db,true|in:0,mysql,postgresql",
|
||||
|
||||
+33
-37
@@ -1,12 +1,10 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
)
|
||||
|
||||
// ProcessTask 处理面板任务
|
||||
@@ -22,63 +20,61 @@ func (receiver *ProcessTask) Signature() string {
|
||||
func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
taskID, ok := args[0].(uint)
|
||||
if !ok {
|
||||
facades.Log().Info("[面板][ProcessTask] 任务ID参数错误")
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"args": args,
|
||||
}).Infof("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
if !haveRunningTask() {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
if err := facades.Orm().Query().Where("id = ?", taskID).Get(&task); err != nil {
|
||||
facades.Log().Infof("[面板][ProcessTask] 获取任务%d失败: %s", taskID, err.Error())
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("获取任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
task.Status = models.TaskStatusRunning
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Infof("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
facades.Log().Infof("[面板][ProcessTask] 开始执行任务%d", taskID)
|
||||
cmd := exec.Command("bash", "-c", task.Shell)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
}).Infof("开始执行任务")
|
||||
|
||||
if _, err := tools.Exec(task.Shell); err != nil {
|
||||
task.Status = models.TaskStatusFailed
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Infof("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
facades.Log().Infof("[面板][ProcessTask] 任务%d执行失败: %s", taskID, err.Error())
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("执行任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
task.Status = models.TaskStatusSuccess
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Infof("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
facades.Log().Infof("[面板][ProcessTask] 任务%d执行成功", taskID)
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
}).Infof("执行任务成功")
|
||||
return nil
|
||||
}
|
||||
|
||||
// haveRunningTask 是否有任务正在执行
|
||||
func haveRunningTask() bool {
|
||||
var task models.Task
|
||||
if err := facades.Orm().Query().Where("status = ?", models.TaskStatusRunning).Get(&task); err != nil {
|
||||
facades.Log().Info("[面板][ProcessTask] 获取任务失败: " + err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
if task.ID != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package models
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/pkg/acme"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type CertDNS struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package models
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/console"
|
||||
"github.com/TheTNB/panel/app/console"
|
||||
)
|
||||
|
||||
type ConsoleServiceProvider struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/database/seeders"
|
||||
"github.com/TheTNB/panel/database/seeders"
|
||||
)
|
||||
|
||||
type DatabaseServiceProvider struct {
|
||||
|
||||
@@ -5,14 +5,16 @@ import (
|
||||
"github.com/goravel/framework/contracts/queue"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/jobs"
|
||||
"github.com/TheTNB/panel/app/jobs"
|
||||
)
|
||||
|
||||
type QueueServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *QueueServiceProvider) Register(app foundation.Application) {
|
||||
facades.Queue().Register(receiver.Jobs())
|
||||
if err := facades.Queue().Register(receiver.Jobs()); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (receiver *QueueServiceProvider) Boot(app foundation.Application) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/http"
|
||||
"panel/routes"
|
||||
"github.com/TheTNB/panel/app/http"
|
||||
"github.com/TheTNB/panel/routes"
|
||||
)
|
||||
|
||||
type RouteServiceProvider struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/rules"
|
||||
"github.com/TheTNB/panel/app/rules"
|
||||
)
|
||||
|
||||
type ValidationServiceProvider struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/pkg/captcha"
|
||||
"github.com/TheTNB/panel/pkg/captcha"
|
||||
)
|
||||
|
||||
type Captcha struct {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/spf13/cast"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type PathExists struct {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/spf13/cast"
|
||||
"panel/pkg/tools"
|
||||
)
|
||||
|
||||
type PathNotExists struct {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/gookit/validate/locales/zhcn"
|
||||
"github.com/goravel/framework/foundation"
|
||||
|
||||
"panel/config"
|
||||
"github.com/TheTNB/panel/config"
|
||||
)
|
||||
|
||||
func Boot() {
|
||||
|
||||
+4
-2
@@ -19,10 +19,11 @@ import (
|
||||
"github.com/goravel/framework/schedule"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/testing"
|
||||
"github.com/goravel/framework/translation"
|
||||
"github.com/goravel/framework/validation"
|
||||
"github.com/goravel/gin"
|
||||
|
||||
"panel/app/providers"
|
||||
"github.com/TheTNB/panel/app/providers"
|
||||
)
|
||||
|
||||
// Boot Start all init methods of the current folder to bootstrap all config.
|
||||
@@ -60,7 +61,7 @@ func init() {
|
||||
// The application locale determines the default locale that will be used
|
||||
// by the translation service provider.You are free to set this value
|
||||
// to any of the locales which will be supported by the application.
|
||||
"locale": "zh_CN",
|
||||
"locale": config.Env("APP_LOCALE", "zh_CN"),
|
||||
|
||||
// Application Fallback Locale
|
||||
//
|
||||
@@ -96,6 +97,7 @@ func init() {
|
||||
&crypt.ServiceProvider{},
|
||||
&filesystem.ServiceProvider{},
|
||||
&validation.ServiceProvider{},
|
||||
&translation.ServiceProvider{},
|
||||
&testing.ServiceProvider{},
|
||||
&providers.AppServiceProvider{},
|
||||
&providers.AuthServiceProvider{},
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ func init() {
|
||||
config := facades.Config()
|
||||
config.Add("panel", map[string]any{
|
||||
"name": "耗子 Linux 面板",
|
||||
"version": "v2.1.43",
|
||||
"version": "v2.2.7",
|
||||
"ssl": config.Env("APP_SSL", false),
|
||||
})
|
||||
}
|
||||
|
||||
+4
-4
@@ -8,15 +8,15 @@ func init() {
|
||||
config := facades.Config()
|
||||
config.Add("queue", map[string]any{
|
||||
// Default Queue Connection Name
|
||||
"default": "sync",
|
||||
"default": "async",
|
||||
|
||||
// Queue Connections
|
||||
//
|
||||
// Here you may configure the connection information for each server that is used by your application.
|
||||
// Drivers: "sync", "redis"
|
||||
// Drivers: "sync", "async", "custom"
|
||||
"connections": map[string]any{
|
||||
"sync": map[string]any{
|
||||
"driver": "sync",
|
||||
"async": map[string]any{
|
||||
"driver": "async",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+1046
-122
File diff suppressed because it is too large
Load Diff
+1046
-122
File diff suppressed because it is too large
Load Diff
+653
-92
@@ -6,8 +6,6 @@ definitions:
|
||||
type: string
|
||||
api_key:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
secret_key:
|
||||
@@ -15,10 +13,8 @@ definitions:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
acme.Resolve:
|
||||
acme.DNSRecord:
|
||||
properties:
|
||||
err:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
value:
|
||||
@@ -35,45 +31,65 @@ definitions:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
internal.BackupFile:
|
||||
github_com_TheTNB_panel_app_http_requests_container.ID:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
size:
|
||||
id:
|
||||
type: string
|
||||
type: object
|
||||
internal.PanelWebsite:
|
||||
github_com_TheTNB_panel_app_http_requests_plugins_frp.UpdateConfig:
|
||||
properties:
|
||||
db:
|
||||
type: boolean
|
||||
db_name:
|
||||
config:
|
||||
type: string
|
||||
db_password:
|
||||
service:
|
||||
type: string
|
||||
db_type:
|
||||
type: object
|
||||
github_com_TheTNB_panel_app_http_requests_plugins_gitea.UpdateConfig:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
db_user:
|
||||
type: object
|
||||
github_com_TheTNB_panel_app_http_requests_plugins_rsync.Update:
|
||||
properties:
|
||||
auth_user:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
hosts_allow:
|
||||
type: string
|
||||
domains:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
php:
|
||||
secret:
|
||||
type: string
|
||||
ports:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
remark:
|
||||
type: object
|
||||
github_com_TheTNB_panel_app_http_requests_plugins_rsync.UpdateConfig:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
type: object
|
||||
github_com_TheTNB_panel_app_http_requests_setting.Update:
|
||||
properties:
|
||||
backup_path:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
entrance:
|
||||
type: string
|
||||
language:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
ssl:
|
||||
type: boolean
|
||||
status:
|
||||
type: boolean
|
||||
username:
|
||||
type: string
|
||||
website_path:
|
||||
type: string
|
||||
type: object
|
||||
models.Cert:
|
||||
properties:
|
||||
@@ -180,47 +196,6 @@ definitions:
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
panel_app_http_requests_container.ID:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
type: object
|
||||
panel_app_http_requests_plugins_rsync.Update:
|
||||
properties:
|
||||
auth_user:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
hosts_allow:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
secret:
|
||||
type: string
|
||||
type: object
|
||||
panel_app_http_requests_setting.Update:
|
||||
properties:
|
||||
backup_path:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
entrance:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
ssl:
|
||||
type: boolean
|
||||
username:
|
||||
type: string
|
||||
website_path:
|
||||
type: string
|
||||
type: object
|
||||
requests.Add:
|
||||
properties:
|
||||
db:
|
||||
@@ -257,6 +232,13 @@ definitions:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
requests.CertDeploy:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
website_id:
|
||||
type: integer
|
||||
type: object
|
||||
requests.CertStore:
|
||||
properties:
|
||||
auto_renew:
|
||||
@@ -353,9 +335,9 @@ definitions:
|
||||
type: object
|
||||
requests.Copy:
|
||||
properties:
|
||||
new:
|
||||
source:
|
||||
type: string
|
||||
old:
|
||||
target:
|
||||
type: string
|
||||
type: object
|
||||
requests.Create:
|
||||
@@ -423,9 +405,9 @@ definitions:
|
||||
type: object
|
||||
requests.Move:
|
||||
properties:
|
||||
new:
|
||||
source:
|
||||
type: string
|
||||
old:
|
||||
target:
|
||||
type: string
|
||||
type: object
|
||||
requests.NetworkConnectDisConnect:
|
||||
@@ -539,6 +521,11 @@ definitions:
|
||||
path:
|
||||
type: string
|
||||
type: object
|
||||
requests.Service:
|
||||
properties:
|
||||
service:
|
||||
type: string
|
||||
type: object
|
||||
requests.UnArchive:
|
||||
properties:
|
||||
file:
|
||||
@@ -546,7 +533,12 @@ definitions:
|
||||
path:
|
||||
type: string
|
||||
type: object
|
||||
requests.UpdateConfig:
|
||||
requests.UpdateRegistryConfig:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
type: object
|
||||
requests.UpdateStorageConfig:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
@@ -594,6 +586,13 @@ definitions:
|
||||
$ref: '#/definitions/types.KV'
|
||||
type: array
|
||||
type: object
|
||||
types.BackupFile:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
size:
|
||||
type: string
|
||||
type: object
|
||||
types.ContainerNetwork:
|
||||
properties:
|
||||
enabled:
|
||||
@@ -636,9 +635,42 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
types.Website:
|
||||
properties:
|
||||
db:
|
||||
type: boolean
|
||||
db_name:
|
||||
type: string
|
||||
db_password:
|
||||
type: string
|
||||
db_type:
|
||||
type: string
|
||||
db_user:
|
||||
type: string
|
||||
domains:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
php:
|
||||
type: string
|
||||
ports:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
remark:
|
||||
type: string
|
||||
ssl:
|
||||
type: boolean
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
info:
|
||||
contact:
|
||||
email: i@haozi.net
|
||||
email: admin@haozi.net
|
||||
name: 耗子科技
|
||||
description: 耗子 Linux 面板的 API 信息
|
||||
license:
|
||||
@@ -797,6 +829,30 @@ paths:
|
||||
summary: 更新证书
|
||||
tags:
|
||||
- 证书管理
|
||||
/panel/cert/deploy:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 部署面板证书管理的证书
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.CertDeploy'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 部署证书
|
||||
tags:
|
||||
- 证书管理
|
||||
/panel/cert/dns:
|
||||
get:
|
||||
description: 获取面板证书管理的 DNS 接口列表
|
||||
@@ -955,9 +1011,9 @@ paths:
|
||||
- $ref: '#/definitions/controllers.SuccessResponse'
|
||||
- properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/acme.Resolve'
|
||||
type: object
|
||||
items:
|
||||
$ref: '#/definitions/acme.DNSRecord'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
- BearerToken: []
|
||||
@@ -1284,7 +1340,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1325,7 +1381,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1535,7 +1591,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1572,7 +1628,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1616,7 +1672,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1659,7 +1715,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1700,7 +1756,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1722,7 +1778,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1843,7 +1899,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_container.ID'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_container.ID'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -2024,6 +2080,12 @@ paths:
|
||||
- in: query
|
||||
name: path
|
||||
type: string
|
||||
- in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -2236,7 +2298,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_setting.Update'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_setting.Update'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -2315,7 +2377,7 @@ paths:
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/internal.BackupFile'
|
||||
$ref: '#/definitions/types.BackupFile'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
@@ -2506,7 +2568,7 @@ paths:
|
||||
- $ref: '#/definitions/controllers.SuccessResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/internal.PanelWebsite'
|
||||
$ref: '#/definitions/types.Website'
|
||||
type: object
|
||||
security:
|
||||
- BearerToken: []
|
||||
@@ -2679,6 +2741,505 @@ paths:
|
||||
summary: 更新备注
|
||||
tags:
|
||||
- 网站管理
|
||||
/plugins/frp/config:
|
||||
get:
|
||||
description: 获取 Frp 配置
|
||||
parameters:
|
||||
- description: 服务
|
||||
in: query
|
||||
name: service
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 获取配置
|
||||
tags:
|
||||
- 插件-Frp
|
||||
post:
|
||||
description: 更新 Frp 配置
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_plugins_frp.UpdateConfig'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 更新配置
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/disable:
|
||||
post:
|
||||
description: 禁用 Frp 服务
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.Service'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 禁用服务
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/enable:
|
||||
post:
|
||||
description: 启用 Frp 服务
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.Service'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 启用服务
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/isEnabled:
|
||||
get:
|
||||
description: 获取是否启用 Frp 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 是否启用服务
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/restart:
|
||||
post:
|
||||
description: 重启 Frp 服务
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.Service'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 重启服务
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/start:
|
||||
post:
|
||||
description: 启动 Frp 服务
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.Service'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 启动服务
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/status:
|
||||
get:
|
||||
description: 获取 Frp 服务状态
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 服务状态
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/frp/stop:
|
||||
post:
|
||||
description: 停止 Frp 服务
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.Service'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 停止服务
|
||||
tags:
|
||||
- 插件-Frp
|
||||
/plugins/gitea/config:
|
||||
get:
|
||||
description: 获取 Gitea 配置
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 获取配置
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
post:
|
||||
description: 更新 Gitea 配置
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_plugins_gitea.UpdateConfig'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 更新配置
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/disable:
|
||||
post:
|
||||
description: 禁用 Gitea 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 禁用服务
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/enable:
|
||||
post:
|
||||
description: 启用 Gitea 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 启用服务
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/isEnabled:
|
||||
get:
|
||||
description: 获取是否启用 Gitea 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 是否启用服务
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/restart:
|
||||
post:
|
||||
description: 重启 Gitea 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 重启服务
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/start:
|
||||
post:
|
||||
description: 启动 Gitea 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 启动服务
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/status:
|
||||
get:
|
||||
description: 获取 Gitea 服务状态
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 服务状态
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/gitea/stop:
|
||||
post:
|
||||
description: 停止 Gitea 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 停止服务
|
||||
tags:
|
||||
- 插件-Gitea
|
||||
/plugins/podman/disable:
|
||||
post:
|
||||
description: 禁用 Podman 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 禁用服务
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/enable:
|
||||
post:
|
||||
description: 启用 Podman 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 启用服务
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/isEnabled:
|
||||
get:
|
||||
description: 获取是否启用 Podman 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 是否启用服务
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/registryConfig:
|
||||
get:
|
||||
description: 获取 Podman 注册表配置
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 获取注册表配置
|
||||
tags:
|
||||
- 插件-Podman
|
||||
post:
|
||||
description: 更新 Podman 注册表配置
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.UpdateRegistryConfig'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 更新注册表配置
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/restart:
|
||||
post:
|
||||
description: 重启 Podman 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 重启服务
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/start:
|
||||
post:
|
||||
description: 启动 Podman 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 启动服务
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/status:
|
||||
get:
|
||||
description: 获取 Podman 服务状态
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 服务状态
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/stop:
|
||||
post:
|
||||
description: 停止 Podman 服务
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 停止服务
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/podman/storageConfig:
|
||||
get:
|
||||
description: 获取 Podman 存储配置
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 获取存储配置
|
||||
tags:
|
||||
- 插件-Podman
|
||||
post:
|
||||
description: 更新 Podman 存储配置
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.UpdateStorageConfig'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 更新存储配置
|
||||
tags:
|
||||
- 插件-Podman
|
||||
/plugins/rsync/config:
|
||||
get:
|
||||
description: 获取 Rsync 配置
|
||||
@@ -2702,7 +3263,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.UpdateConfig'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_plugins_rsync.UpdateConfig'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -2792,7 +3353,7 @@ paths:
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/panel_app_http_requests_plugins_rsync.Update'
|
||||
$ref: '#/definitions/github_com_TheTNB_panel_app_http_requests_plugins_rsync.Update'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
module panel
|
||||
module github.com/TheTNB/panel
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/docker/docker v25.0.5+incompatible
|
||||
github.com/docker/docker v26.1.4+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/gertd/go-pluralize v0.2.1
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/gookit/validate v1.5.2
|
||||
github.com/goravel/framework v1.13.1-0.20240215091018-1a9f352e523c
|
||||
github.com/goravel/gin v1.1.6-0.20240215075927-ab614bfae9f3
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/imroc/req/v3 v3.43.1
|
||||
github.com/goravel/framework v1.14.1-0.20240608091017-72e9e3621f24
|
||||
github.com/goravel/gin v1.2.1
|
||||
github.com/gorilla/websocket v1.5.2
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/libdns/dnspod v0.0.3
|
||||
github.com/libdns/libdns v0.2.2
|
||||
github.com/mholt/acmez/v2 v2.0.1
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mojocn/base64Captcha v1.3.6
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
@@ -21,15 +22,16 @@ require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2
|
||||
github.com/swaggo/swag v1.16.3
|
||||
golang.org/x/crypto v0.21.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/net v0.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.111.0 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/pubsub v1.33.0 // indirect
|
||||
atomicgo.dev/cursor v0.2.0 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/schedule v0.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
@@ -39,35 +41,41 @@ require (
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect
|
||||
github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.49.6 // indirect
|
||||
github.com/bytedance/sonic v1.11.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/catppuccin/go v0.2.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.18.0 // indirect
|
||||
github.com/charmbracelet/bubbletea v0.26.3 // indirect
|
||||
github.com/charmbracelet/huh v0.4.2 // indirect
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240508140610-13957916abf0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.11.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240524151031-ff83003bf67a // indirect
|
||||
github.com/charmbracelet/x/input v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/term v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.1.2 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/glebarez/sqlite v1.10.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
@@ -77,147 +85,126 @@ require (
|
||||
github.com/go-openapi/swag v0.22.7 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.5 // indirect
|
||||
github.com/go-redsync/redsync/v4 v4.8.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0 // indirect
|
||||
github.com/golang-module/carbon/v2 v2.3.8 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1 // indirect
|
||||
github.com/golang-module/carbon/v2 v2.3.12 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/google/wire v0.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gookit/filter v1.2.1 // indirect
|
||||
github.com/gookit/goutil v0.6.15 // indirect
|
||||
github.com/goravel/file-rotatelogs/v2 v2.4.2 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.6.0 // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.16 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.4.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||
github.com/pterm/pterm v0.12.79 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rotisserie/eris v0.5.4 // indirect
|
||||
github.com/rs/cors v1.10.1 // indirect
|
||||
github.com/rs/cors v1.11.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/samber/do/v2 v2.0.0-beta.7 // indirect
|
||||
github.com/samber/go-type-to-string v1.7.0 // indirect
|
||||
github.com/savioxavier/termlink v1.3.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/streadway/amqp v1.0.0 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/unrolled/secure v1.14.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.1 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.2 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.mongodb.org/mongo-driver v1.11.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
|
||||
go.opentelemetry.io/otel v1.22.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/api v0.153.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
|
||||
google.golang.org/grpc v1.61.1 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.5.4 // indirect
|
||||
gorm.io/driver/postgres v1.5.6 // indirect
|
||||
gorm.io/driver/mysql v1.5.6 // indirect
|
||||
gorm.io/driver/postgres v1.5.7 // indirect
|
||||
gorm.io/driver/sqlserver v1.5.3 // indirect
|
||||
gorm.io/gorm v1.25.7 // indirect
|
||||
gorm.io/plugin/dbresolver v1.5.0 // indirect
|
||||
gorm.io/gorm v1.25.10 // indirect
|
||||
gorm.io/plugin/dbresolver v1.5.1 // indirect
|
||||
gotest.tools/v3 v3.5.0 // indirect
|
||||
modernc.org/libc v1.37.6 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
|
||||
+7
-9
@@ -1,20 +1,18 @@
|
||||
package internal
|
||||
|
||||
import "panel/app/models"
|
||||
import (
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/types"
|
||||
)
|
||||
|
||||
type Backup interface {
|
||||
WebsiteList() ([]BackupFile, error)
|
||||
WebsiteList() ([]types.BackupFile, error)
|
||||
WebSiteBackup(website models.Website) error
|
||||
WebsiteRestore(website models.Website, backupFile string) error
|
||||
MysqlList() ([]BackupFile, error)
|
||||
MysqlList() ([]types.BackupFile, error)
|
||||
MysqlBackup(database string) error
|
||||
MysqlRestore(database string, backupFile string) error
|
||||
PostgresqlList() ([]BackupFile, error)
|
||||
PostgresqlList() ([]types.BackupFile, error)
|
||||
PostgresqlBackup(database string) error
|
||||
PostgresqlRestore(database string, backupFile string) error
|
||||
}
|
||||
|
||||
type BackupFile struct {
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
+8
-9
@@ -1,11 +1,9 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
|
||||
requests "panel/app/http/requests/cert"
|
||||
"panel/app/models"
|
||||
"panel/pkg/acme"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/cert"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type Cert interface {
|
||||
@@ -21,8 +19,9 @@ type Cert interface {
|
||||
CertUpdate(request requests.CertUpdate) error
|
||||
CertShow(ID uint) (models.Cert, error)
|
||||
CertDestroy(ID uint) error
|
||||
ObtainAuto(ID uint) (certificate.Resource, error)
|
||||
ObtainManual(ID uint) (certificate.Resource, error)
|
||||
ManualDNS(ID uint) (map[string]acme.Resolve, error)
|
||||
Renew(ID uint) (certificate.Resource, error)
|
||||
ObtainAuto(ID uint) (acme.Certificate, error)
|
||||
ObtainManual(ID uint) (acme.Certificate, error)
|
||||
ManualDNS(ID uint) ([]acme.DNSRecord, error)
|
||||
Renew(ID uint) (acme.Certificate, error)
|
||||
Deploy(ID, WebsiteID uint) error
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user