Compare commits

...

130 Commits

Author SHA1 Message Date
耗子 f65cf9e38e feat: 优化设置项 2024-07-13 03:40:07 +08:00
耗子 4a8ebfbdfa feat: 发布v2.2.24 2024-07-13 03:37:18 +08:00
耗子 fee0fcb998 fix: 等待中的任务显示为运行中 2024-07-13 03:25:55 +08:00
耗子 1e081fcdf2 feat(网站): 支持设置QUIC和OCSP 2024-07-13 03:17:21 +08:00
耗子 6c6fb09270 feat: 重定向至登录页面 2024-07-13 01:54:42 +08:00
耗子 51b47818c0 feat: 发布v2.2.23 2024-07-13 01:48:18 +08:00
耗子 5b393819ba fix: optimize ci 2024-07-13 01:24:57 +08:00
耗子 f807d0cd1a fix: embed目录错误 2024-07-13 01:20:41 +08:00
耗子 0d6f0f317d fix: embed目录错误 2024-07-13 01:18:03 +08:00
耗子 6bad7f4b5b refactor: 重构入口和前端加载逻辑 2024-07-13 01:17:24 +08:00
耗子 7a7134b26a chore: 更新框架 2024-07-12 22:55:11 +08:00
耗子 28cf7098ff feat: 发布v2.2.22 2024-07-12 18:20:56 +08:00
耗子 b56979147e feat: 发布v2.2.21 2024-07-12 16:51:39 +08:00
耗子 2ec0d9e844 Merge remote-tracking branch 'origin/main' 2024-07-12 16:32:54 +08:00
耗子 66641a626e feat: 更新框架 2024-07-12 16:32:47 +08:00
renovate[bot] 95bfd6843b chore(deps): Update module gorm.io/gorm to v1.25.11 (#139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 15:46:32 +00:00
耗子 7e28a34b7f refactor: 重构filter标签 2024-07-11 16:54:34 +08:00
耗子 b2152edeeb feat: session支持刷新 2024-07-11 14:52:16 +08:00
耗子 00815979d2 feat: 发布v2.2.20 2024-07-11 02:59:33 +08:00
耗子 c4e01b37b7 refactor: 使用session鉴权 2024-07-11 02:51:49 +08:00
耗子 7907345521 feat: 更新依赖 2024-07-11 01:08:37 +08:00
耗子 8f32d6cdb0 feat: 优化版本号处理 2024-07-08 04:56:00 +08:00
耗子 c3582903bf fix: CVE-2024-0406 2024-07-08 04:35:52 +08:00
renovate[bot] 7d25d2f5aa chore(deps): Update module golang.org/x/net to v0.27.0 2024-07-05 20:03:32 +00:00
renovate[bot] 46fc7b1c6a chore(deps): Update module golang.org/x/crypto to v0.25.0 2024-07-05 15:11:03 +00:00
耗子 34ac62d1ae fix: lint 2024-07-03 02:03:58 +08:00
耗子 30e78ccf46 feat: 发布v2.2.19 2024-07-03 02:02:38 +08:00
耗子 22f4cc7fcb feat: 支持腾讯云 2024-07-03 01:45:25 +08:00
耗子 1771ae682d Merge remote-tracking branch 'origin/main' 2024-07-02 21:07:19 +08:00
耗子 d1beb60ffd fix: service文件错误 2024-07-02 21:06:57 +08:00
renovate[bot] cba5f86667 chore(deps): Update module github.com/docker/docker to v27.0.3+incompatible 2024-07-02 10:03:44 +00:00
耗子 e1d9832ce0 fix: lint 2024-07-01 15:49:24 +08:00
耗子 6278535902 fix: lint 2024-06-28 23:40:59 +08:00
耗子 32fb87243b feat: 部分软件增加安装校验 2024-06-28 16:41:57 +08:00
耗子 5f85e7fbe0 feat: 添加PostgreSQL的操作方法 2024-06-28 04:01:46 +08:00
耗子 c36104a075 feat: 去掉mysql命令的路径 2024-06-28 03:25:59 +08:00
耗子 583f5d13c7 feat: 优化安装脚本 2024-06-28 03:14:26 +08:00
耗子 b453738daf fix: docker的弃用警告 2024-06-28 03:05:46 +08:00
耗子 98431fb4b2 feat: 发布v2.2.18 2024-06-28 02:53:45 +08:00
耗子 4d76e9286d feat: 删除网站支持删除数据库 2024-06-28 02:52:31 +08:00
耗子 c70758339f fix: build 2024-06-28 01:58:43 +08:00
耗子 ff524cdb0b feat: update dependencies 2024-06-28 01:32:32 +08:00
耗子 73c95d880a Merge remote-tracking branch 'refs/remotes/origin/renovate/github.com-docker-docker-27.x' 2024-06-28 01:28:31 +08:00
耗子 8a1d8c0b05 fix: 优化数据库正则 2024-06-28 01:20:37 +08:00
耗子 2251175906 docs: update readme 2024-06-27 19:13:45 +08:00
机器人 69edde98c8 chore(deps): Update module github.com/docker/docker to v27 2024-06-27 04:07:08 +00:00
耗子 29a3db640d fix: 去掉错误的参数 2024-06-25 18:43:55 +08:00
耗子 ee87bf65b4 feat: 调整部分参数 2024-06-25 18:42:00 +08:00
耗子 aa080dab15 feat: 发布v2.2.17 2024-06-25 03:37:37 +08:00
耗子 b4e5498b48 feat: OpenResty支持返回具体错误 2024-06-25 03:35:35 +08:00
耗子 63838bffa1 feat: phpMyAdmin支持编辑配置 2024-06-25 03:07:38 +08:00
耗子 fa41b18ced fix: 优化查询 2024-06-25 02:51:34 +08:00
耗子 31bd15217b feat: 添加日志记录中间件 2024-06-25 02:38:13 +08:00
耗子 e6b6f06706 feat: 尝试优化内存 2024-06-25 02:08:13 +08:00
耗子 1b836297bf feat: 尝试优化内存 2024-06-25 01:52:41 +08:00
耗子 96bea2cf90 fix: 去掉无用的检查 2024-06-25 01:23:01 +08:00
耗子 8aff513284 fix: 禁止80端口使用HTTPS 2024-06-25 01:13:24 +08:00
耗子 368e41a512 feat: 发布v2.2.16 2024-06-25 01:01:07 +08:00
耗子 924fdee7c5 feat: 支持设置HTTPS端口 2024-06-25 01:00:08 +08:00
耗子 438ba1db1e fix: lint 2024-06-24 23:50:53 +08:00
耗子 d4e52a2d87 feat: ZeroSSL支持自动获取EAB 2024-06-24 23:46:13 +08:00
耗子 4432a12c7a feat: 临时性手段阻止任务重复添加 2024-06-24 23:28:35 +08:00
耗子 a75e36e447 feat: 优化任务 2024-06-24 23:10:39 +08:00
耗子 05674ee04e feat: 优化迁移 2024-06-24 22:53:43 +08:00
耗子 09703761eb fix(文件): 权限设置错误 2024-06-24 22:20:41 +08:00
耗子 dfa14c3fd4 refactor: MySQL改密逻辑 2024-06-24 21:52:46 +08:00
耗子 7b80f034ea feat: 安装前先清理文件 2024-06-24 19:46:43 +08:00
耗子 71923093fd feat(文件): 解压不再判断路径存在 2024-06-24 19:11:01 +08:00
耗子 aae3cf0518 fix: 统一格式 2024-06-24 03:46:21 +08:00
耗子 ed22e79552 feat: 防止密码爆破攻击 2024-06-24 03:45:38 +08:00
耗子 15dd5bb718 feat: 发布v2.2.15 2024-06-23 05:09:30 +08:00
耗子 0e7af2914e feat: 发布v2.2.14 2024-06-23 04:52:18 +08:00
耗子 7ec378829b feat: 优化https证书签发 2024-06-23 04:44:14 +08:00
耗子 f2e8ff86c6 feat: 发布v2.2.13 2024-06-23 03:19:39 +08:00
耗子 eed03d644f fix: test 2024-06-23 03:19:13 +08:00
耗子 d9be877710 feat: 发布v2.2.12 2024-06-23 03:03:38 +08:00
耗子 10439d7d97 feat: 优化证书信息获取 2024-06-23 02:42:01 +08:00
耗子 55eaa26970 feat: 添加面板HTTPS接口 2024-06-23 02:35:25 +08:00
耗子 0d769d3400 fix: lint 2024-06-23 01:46:14 +08:00
耗子 7ff084e5a8 refactor: 重构types包 2024-06-23 01:32:45 +08:00
耗子 89e133c626 refactor: 重构str包 2024-06-23 01:12:51 +08:00
耗子 c876f4c484 refactor: 重构os包 2024-06-23 01:08:07 +08:00
耗子 85c38e5bc5 refactor: 重构io包 2024-06-23 00:59:07 +08:00
耗子 6bd07251fc refactor: 重构systemctl包 2024-06-23 00:48:37 +08:00
耗子 11d6b40dce refactor: 重构shell命令执行 2024-06-23 00:44:14 +08:00
耗子 aa9b55d1eb feat: bump v2.2.11 2024-06-19 02:06:58 +08:00
耗子 2e97812776 fix: 修复版本号比较错误 2024-06-19 02:06:39 +08:00
耗子 e0a7ada6d5 feat(监控): 去掉cpu型号信息 2024-06-19 01:58:40 +08:00
耗子 d48963ab50 fix: 由于v2.2.8迁移问题导致更新脚本可能未运行 2024-06-19 01:20:48 +08:00
耗子 59bfed66dc chore: 发布v2.2.10 2024-06-19 01:18:38 +08:00
耗子 5c0bc8a1b7 feat: 安装后删除安装脚本和checksum 2024-06-19 01:15:00 +08:00
耗子 ae9e39d0fe feat: 优化分页函数 2024-06-19 00:47:57 +08:00
耗子 49b72aa656 feat: 一些细节优化 2024-06-18 13:19:28 +08:00
耗子 e0f642cbff feat: 一些细节优化 2024-06-18 12:58:06 +08:00
耗子 eb2c6cab81 feat: 优化防火墙启停流程 2024-06-18 10:09:53 +08:00
耗子 0a3d3828c8 feat: 优化验证器使用 2024-06-18 03:41:34 +08:00
耗子 46b431de30 fix: 一些小问题 2024-06-18 03:30:59 +08:00
耗子 8a5b46ad7e fix(容器): 分页为空时不返回null 2024-06-18 03:13:39 +08:00
耗子 5bd14946de refactor: 重构postgresql插件 2024-06-18 03:04:37 +08:00
耗子 f6c091c725 feat: 更新文档 2024-06-18 01:35:05 +08:00
耗子 f4dc94ad9a feat: 更新网站文档 2024-06-18 01:32:04 +08:00
耗子 8bcc0c0c05 feat: 更新openresty插件文档 2024-06-18 01:29:44 +08:00
耗子 0be04e1859 feat: 更新php插件文档 2024-06-18 01:26:47 +08:00
耗子 bec9336f2f feat: 重构php插件 2024-06-18 01:20:24 +08:00
耗子 0be4ce9891 revert: 加回must_install中间件 2024-06-18 00:48:48 +08:00
耗子 634a9a3219 feat: 优化编译线程数计算算法 2024-06-18 00:32:47 +08:00
耗子 1f2a32f883 feat: 检查接口返回插件名 2024-06-18 00:28:35 +08:00
耗子 32a56aa2dc feat: 去掉must install中间件和提供新接口检查插件安装 2024-06-17 22:03:38 +08:00
耗子 8185a69354 feat: optimize mysql api 2024-06-17 21:48:28 +08:00
耗子 d41862f63b fix: 自启动podman socket 2024-06-16 04:38:28 +08:00
耗子 20e5122425 fix: lint 2024-06-16 04:20:58 +08:00
耗子 17bb8ad62b fix: debian下缺失podman存储配置 2024-06-16 04:20:39 +08:00
耗子 43624de2e4 feat: 优化自动迁移 2024-06-16 03:55:59 +08:00
耗子 cc6a45b127 feat: 修改模型 2024-06-14 22:52:05 +08:00
耗子 7a4a71f0a0 fix: tests 2024-06-14 21:03:15 +08:00
耗子 27cbb92a06 fix: build 2024-06-14 21:00:12 +08:00
耗子 5ddbdfc5df fix: lint 2024-06-14 19:58:49 +08:00
耗子 b3b1b0971f feat: use new migrate 2024-06-14 19:56:44 +08:00
耗子 9caad365e9 Merge remote-tracking branch 'origin/main' 2024-06-14 19:49:33 +08:00
耗子 5b54232e16 refactor: 移动pureftpd类型定义 2024-06-14 19:49:23 +08:00
机器人 a510bdb05d Merge branch 'renovate/github.com-gorilla-websocket-1.x' into 'main'
chore(deps): Update module github.com/gorilla/websocket to v1.5.3

See merge request opensource/panel!77
2024-06-14 04:12:54 +00:00
机器人 50a8b16832 chore(deps): Update module github.com/gorilla/websocket to v1.5.3 2024-06-14 04:07:55 +00:00
耗子 2f56241da1 refactor: 移动pureftpd类型定义 2024-06-14 11:30:30 +08:00
耗子 8557259b53 feat: 移除部分软件服务接口 2024-06-14 11:28:02 +08:00
耗子 1d7ea0280a feat: 移除部分软件服务接口 2024-06-14 11:10:52 +08:00
耗子 10145da2f9 feat: 移除部分软件服务接口 2024-06-14 10:59:22 +08:00
耗子 6ba7d01875 feat: 添加服务操作接口 2024-06-14 10:33:51 +08:00
耗子 763fa7379d Merge remote-tracking branch 'origin/renovate/goreleaser-goreleaser-action-6.x' 2024-06-14 02:44:56 +08:00
耗子 a36eaf7e9a feat: 自启动podman-restart 2024-06-13 02:38:29 +08:00
机器人 18de16047d chore(deps): Update goreleaser/goreleaser-action action to v6 2024-06-08 10:05:02 +00:00
249 changed files with 8161 additions and 8076 deletions
-32
View File
@@ -1,32 +0,0 @@
root = "."
tmp_dir = "storage/temp"
[build]
bin = "./storage/temp/main.exe --env=panel.conf"
cmd = "go build -o ./storage/temp/main.exe ."
delay = 1000
exclude_dir = ["storage", "database"]
exclude_file = []
exclude_regex = []
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false
+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms
open_collective: tnb
custom: ['https://afdian.net/a/TheTNB']
custom: [ 'https://afdian.net/a/TheTNB' ]
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
CGO_ENABLED: 0
GOOS: linux
GOARCH: ${{ matrix.goarch }}
run: go build -ldflags '-s -w --extldflags "-static"' -o panel-${{ matrix.goarch }}
run: go build -ldflags '-s -w --extldflags "-static"' -tags='nomsgpack' -o panel-${{ matrix.goarch }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
+2 -4
View File
@@ -19,13 +19,11 @@ jobs:
go-version: '1.22'
- name: Fetch Frontend
run: |
sudo apt-get install -y curl jq unzip zip
curl -sSL https://api.github.com/repos/TheTNB/panel-frontend/releases/latest | jq -r ".assets[] | select(.name | contains(\"dist\")) | .browser_download_url" | xargs curl -L -o frontend.zip
rm -rf public
unzip frontend.zip
mv dist public
mv dist/* embed/frontend/
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean
@@ -1,25 +0,0 @@
name: Issue Close Question
on:
schedule:
- cron: "0 0 * * *"
permissions:
contents: read
jobs:
issue-close-require:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: needs more info
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: 'question'
inactive-day: 3
body: |
由于该 Issue 3天未收到回应,现已被自动关闭,若有任何问题,可评论回复。
This issue has been closed automatically because it has not had recent activity for 3 days. If you have any questions, please comment here.
-1
View File
@@ -19,6 +19,5 @@ jobs:
cp panel-example.conf .env
echo "DB_FILE=$(pwd)/storage/panel.db" >> .env
go run . artisan key:generate
go run . artisan migrate
- name: Run tests
run: go test ./...
-5
View File
@@ -67,8 +67,3 @@ out/
*.sublime*
__debug_bin
.project
# 前端工具链 #
.sass-cache/*
node_modules/
/public
+3 -5
View File
@@ -40,7 +40,6 @@ unit_test:
- cp panel-example.conf .env
- echo "DB_FILE=$(pwd)/storage/panel.db" >> .env
- go run . artisan key:generate
- go run . artisan migrate
- go test -v -coverprofile=coverage.txt -covermode=atomic ./...
build:
@@ -48,7 +47,7 @@ build:
extends: .go_cache
script:
- go mod download
- CGO_ENABLED=0 go build -ldflags '-s -w --extldflags "-static"' -o panel
- CGO_ENABLED=0 go build -ldflags '-s -w --extldflags "-static"' -tags='nomsgpack' -o panel
artifacts:
name: "panel"
paths:
@@ -63,13 +62,12 @@ fetch:
- 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
- rm -rf public
- unzip frontend.zip
- mv dist public
- mv dist/* embed/frontend/
artifacts:
name: "frontend"
paths:
- public
- embed/frontend
expire_in: 3 days
release:
+2 -2
View File
@@ -15,6 +15,8 @@ builds:
- v2
ldflags:
- -s -w --extldflags "-static"
tags:
- nomsgpack
upx:
- enabled: true
@@ -38,9 +40,7 @@ archives:
files:
- LICENSE
- docs/*
- public/*
- storage/*
- database/*
- lang/*
- scripts/*
- panel-example.conf
-1
View File
@@ -1 +0,0 @@
replace carbon.DateTime string
+5 -1
View File
@@ -2,7 +2,11 @@
[简体中文] | [<a href="README_EN.md">English</a>]
</p>
<h1 align="center">耗子面板</h1>
<h1 align="center" style="font-size: 40px">耗子面板</h1>
<p align="center">
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
+5 -1
View File
@@ -2,7 +2,11 @@
[<a href="README.md">简体中文</a>] | [English]
</p>
<h1 align="center">Rat Panel</h1>
<h1 align="center" style="font-size: 40px">Rat Panel</h1>
<p align="center">
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
+10 -14
View File
@@ -2,8 +2,6 @@ package commands
import (
"context"
"crypto/x509"
"encoding/pem"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
@@ -12,7 +10,8 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/types"
panelcert "github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/types"
)
// CertRenew 证书续签
@@ -54,18 +53,15 @@ func (receiver *CertRenew) Handle(console.Context) error {
continue
}
block, _ := pem.Decode([]byte(cert.Cert))
if block != nil {
data, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
decode, err := panelcert.ParseCert(cert.Cert)
if err != nil {
continue
}
// 结束时间大于 7 天的证书不续签
endTime := carbon.FromStdTime(data.NotAfter)
if endTime.Gt(carbon.Now().AddDays(7)) {
continue
}
// 结束时间大于 7 天的证书不续签
endTime := carbon.FromStdTime(decode.NotAfter)
if endTime.Gt(carbon.Now().AddDays(7)) {
continue
}
certService := services.NewCertImpl()
+4 -12
View File
@@ -13,7 +13,7 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
// Monitoring 系统监控
@@ -45,9 +45,8 @@ func (receiver *Monitoring) Handle(console.Context) error {
}
// 将等待中的任务分发
// TODO 有bug,需要设计一个锁机制防止重复分发
//task := services.NewTaskImpl()
//_ = task.DispatchWaiting()
task := services.NewTaskImpl()
_ = task.DispatchWaiting()
setting := services.NewSettingImpl()
monitor := setting.Get(models.SettingKeyMonitor)
@@ -60,14 +59,7 @@ func (receiver *Monitoring) Handle(console.Context) error {
// 去除部分数据以减少数据库存储
info.Disk = nil
for _, cpu := range info.Cpus {
cpu.VendorID = ""
cpu.Family = ""
cpu.Model = ""
cpu.PhysicalID = ""
cpu.ModelName = ""
cpu.Flags = nil
}
info.Cpus = nil
if types.Status != types.StatusNormal {
return nil
+41 -38
View File
@@ -15,10 +15,15 @@ import (
"github.com/goravel/framework/support/color"
"github.com/spf13/cast"
requests "github.com/TheTNB/panel/app/http/requests/website"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
// Panel 面板命令行
@@ -70,7 +75,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
hash, err := facades.Hash().Make(tools.RandomString(32))
hash, err := facades.Hash().Make(str.RandomString(32))
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.init.fail"))
return nil
@@ -104,7 +109,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
// 停止面板服务,因为在shell中运行的和systemd的不同
_ = tools.ServiceStop("panel")
_ = systemctl.Stop("panel")
types.Status = types.StatusUpgrade
if err = tools.UpdatePanel(panel); err != nil {
@@ -125,16 +130,16 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
password := tools.RandomString(16)
password := str.RandomString(16)
hash, err := facades.Hash().Make(password)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.getInfo.passwordGenerationFail"))
return nil
}
user.Username = tools.RandomString(8)
user.Username = str.RandomString(8)
user.Password = hash
if user.Email == "" {
user.Email = tools.RandomString(8) + "@example.com"
user.Email = str.RandomString(8) + "@example.com"
}
err = facades.Orm().Query().Save(&user)
@@ -143,7 +148,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.portFail"))
return nil
@@ -160,11 +165,11 @@ func (receiver *Panel) Handle(ctx console.Context) error {
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"))
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("panel.entrance"))
color.Green().Printfln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("panel.entrance"))
case "getPort":
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.portFail"))
return nil
@@ -173,15 +178,15 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Green().Printfln(translate.Get("commands.panel.port") + ": " + port)
case "getEntrance":
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("panel.entrance"))
case "deleteEntrance":
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
oldEntrance, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
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 {
if _, err = shell.Execf("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf"); err != nil {
color.Red().Printfln(translate.Get("commands.panel.deleteEntrance.fail"))
return nil
}
@@ -270,8 +275,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
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 {
if !io.Exists(path) {
if err := io.Mkdir(path, 0644); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.backupDirFail") + ": " + err.Error())
return nil
}
@@ -288,7 +293,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
backupFile := path + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
if _, err := tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
if _, err := shell.Execf(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.backupFail") + ": " + err.Error())
return nil
}
@@ -307,23 +312,23 @@ func (receiver *Panel) Handle(ctx console.Context) error {
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 {
if _, err = shell.Execf(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
return nil
}
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 {
if _, err = shell.Execf("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
if err := io.Remove("/tmp/" + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
return nil
}
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 {
if err := io.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
return nil
}
@@ -333,7 +338,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
case "postgresql":
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
check, err := tools.Exec(`su - postgres -c "psql -l" 2>&1`)
check, err := shell.Execf(`su - postgres -c "psql -l" 2>&1`)
if err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.databaseGetFail") + ": " + err.Error())
color.Green().Printfln(hr)
@@ -347,23 +352,23 @@ func (receiver *Panel) Handle(ctx console.Context) error {
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 {
if _, err = shell.Execf(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
return nil
}
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 {
if _, err = shell.Execf("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
if err := io.Remove("/tmp/" + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
return nil
}
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 {
if err := io.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
return nil
}
@@ -393,7 +398,7 @@ 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.Yellow().Printfln("|-" + translate.Get("commands.panel.backup.cleanBackup") + ": " + fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
if err := io.Remove(fileToDelete); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.cleanupFail") + ": " + err.Error())
return nil
}
@@ -425,18 +430,18 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
logPath := "/www/wwwlogs/" + website.Name + ".log"
if !tools.Exists(logPath) {
if !io.Exists(logPath) {
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 {
if _, err := shell.Execf(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log"); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.backupFail") + ": " + err.Error())
return nil
}
if _, err := tools.Exec(`echo "" > ` + logPath); err != nil {
if _, err := shell.Execf(`echo "" > ` + logPath); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.clearFail") + ": " + err.Error())
return nil
}
@@ -464,7 +469,7 @@ 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.Yellow().Printfln("|-" + translate.Get("commands.panel.cutoff.clearLog") + ": " + fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
if err := io.Remove(fileToDelete); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.cleanupFail") + ": " + err.Error())
return nil
}
@@ -501,8 +506,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
Name: name,
Status: status,
Path: path,
Php: php,
Ssl: ssl,
PHP: php,
SSL: ssl,
})
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.writeSite.fail"))
@@ -610,15 +615,13 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
_, err = website.Add(types.Website{
_, err = website.Add(requests.Add{
Name: name,
Status: true,
Domains: domains,
Ports: uintPorts,
Path: path,
Php: php,
Ssl: false,
Db: false,
PHP: php,
DB: false,
})
if err != nil {
color.Red().Printfln(err.Error())
@@ -645,7 +648,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
if err = website.Delete(id); err != nil {
if err = website.Delete(requests.Delete{ID: id}); err != nil {
color.Red().Printfln(err.Error())
return nil
}
+11 -4
View File
@@ -2,14 +2,17 @@ package commands
import (
"context"
"runtime"
"runtime/debug"
"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/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
)
// PanelTask 面板每日任务
@@ -48,7 +51,7 @@ func (receiver *PanelTask) Handle(console.Context) error {
}
// 备份面板
if err := tools.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
if err := io.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{
@@ -58,7 +61,7 @@ func (receiver *PanelTask) Handle(console.Context) error {
}
// 清理 7 天前的备份
if _, err := tools.Exec(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
if _, err := shell.Execf(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
types.Status = types.StatusFailed
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
@@ -67,6 +70,10 @@ func (receiver *PanelTask) Handle(console.Context) error {
return err
}
// 回收内存
runtime.GC()
debug.FreeOSMemory()
types.Status = types.StatusNormal
return nil
}
-77
View File
@@ -1,77 +0,0 @@
package controllers
import (
"os"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
)
type AssetController struct {
setting internal.Setting
}
func NewAssetController() *AssetController {
return &AssetController{
setting: services.NewSettingImpl(),
}
}
func (r *AssetController) Index(ctx http.Context) http.Response {
entrance := facades.Config().GetString("http.entrance")
if entrance == "/" {
entrance = ""
}
// 自动纠正 URL 格式
if ctx.Request().Path() == entrance && ctx.Request().Path() != "/" {
return ctx.Response().Redirect(http.StatusMovedPermanently, ctx.Request().Path()+"/")
}
// 拒绝访问非入口文件
if !strings.HasPrefix(ctx.Request().Path(), entrance) {
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
path := strings.TrimPrefix(ctx.Request().Path(), entrance)
// 设置默认首页
if path == "/" || path == "" {
path = "/index.html"
}
if !tools.Exists("public" + path) {
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
file, err := os.Open("public" + path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
stat, err := file.Stat()
if err != nil {
return Error(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if stat.IsDir() {
return Error(ctx, http.StatusForbidden, http.StatusText(http.StatusForbidden))
}
return ctx.Response().Header("Cache-Control", "no-cache").File("public" + path)
}
func (r *AssetController) Favicon(ctx http.Context) http.Response {
return ctx.Response().File("public/favicon.png")
}
func (r *AssetController) Robots(ctx http.Context) http.Response {
return ctx.Response().File("public/robots.txt")
}
func (r *AssetController) NotFound(ctx http.Context) http.Response {
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
+45 -41
View File
@@ -28,7 +28,7 @@ func NewCertController() *CertController {
//
// @Summary 获取 CA 提供商
// @Description 获取面板证书管理支持的 CA 提供商
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
@@ -62,7 +62,7 @@ func (r *CertController) CAProviders(ctx http.Context) http.Response {
//
// @Summary 获取 DNS 提供商
// @Description 获取面板证书管理支持的 DNS 提供商
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
@@ -73,6 +73,10 @@ func (r *CertController) DNSProviders(ctx http.Context) http.Response {
"name": "DNSPod",
"dns": acme.DnsPod,
},
{
"name": "腾讯云",
"dns": acme.Tencent,
},
{
"name": "阿里云",
"dns": acme.AliYun,
@@ -88,7 +92,7 @@ func (r *CertController) DNSProviders(ctx http.Context) http.Response {
//
// @Summary 获取算法列表
// @Description 获取面板证书管理支持的算法列表
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
@@ -118,7 +122,7 @@ func (r *CertController) Algorithms(ctx http.Context) http.Response {
//
// @Summary 获取用户列表
// @Description 获取面板证书管理的 ACME 用户列表
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
@@ -126,7 +130,7 @@ func (r *CertController) Algorithms(ctx http.Context) http.Response {
// @Router /panel/cert/users [get]
func (r *CertController) UserList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
@@ -151,7 +155,7 @@ func (r *CertController) UserList(ctx http.Context) http.Response {
//
// @Summary 添加 ACME 用户
// @Description 添加 ACME 用户到面板证书管理
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -160,7 +164,7 @@ func (r *CertController) UserList(ctx http.Context) http.Response {
// @Router /panel/cert/users [post]
func (r *CertController) UserStore(ctx http.Context) http.Response {
var storeRequest requests.UserStore
sanitize := Sanitize(ctx, &storeRequest)
sanitize := SanitizeRequest(ctx, &storeRequest)
if sanitize != nil {
return sanitize
}
@@ -180,7 +184,7 @@ func (r *CertController) UserStore(ctx http.Context) http.Response {
//
// @Summary 更新 ACME 用户
// @Description 更新面板证书管理的 ACME 用户
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -190,7 +194,7 @@ func (r *CertController) UserStore(ctx http.Context) http.Response {
// @Router /panel/cert/users/{id} [put]
func (r *CertController) UserUpdate(ctx http.Context) http.Response {
var updateRequest requests.UserUpdate
sanitize := Sanitize(ctx, &updateRequest)
sanitize := SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
@@ -211,7 +215,7 @@ func (r *CertController) UserUpdate(ctx http.Context) http.Response {
//
// @Summary 获取 ACME 用户
// @Description 获取面板证书管理的 ACME 用户
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
@@ -219,7 +223,7 @@ func (r *CertController) UserUpdate(ctx http.Context) http.Response {
// @Router /panel/cert/users/{id} [get]
func (r *CertController) UserShow(ctx http.Context) http.Response {
var showAndDestroyRequest requests.UserShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
sanitize := SanitizeRequest(ctx, &showAndDestroyRequest)
if sanitize != nil {
return sanitize
}
@@ -240,7 +244,7 @@ func (r *CertController) UserShow(ctx http.Context) http.Response {
//
// @Summary 删除 ACME 用户
// @Description 删除面板证书管理的 ACME 用户
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -249,7 +253,7 @@ func (r *CertController) UserShow(ctx http.Context) http.Response {
// @Router /panel/cert/users/{id} [delete]
func (r *CertController) UserDestroy(ctx http.Context) http.Response {
var showAndDestroyRequest requests.UserShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
sanitize := SanitizeRequest(ctx, &showAndDestroyRequest)
if sanitize != nil {
return sanitize
}
@@ -270,7 +274,7 @@ func (r *CertController) UserDestroy(ctx http.Context) http.Response {
//
// @Summary 获取 DNS 接口列表
// @Description 获取面板证书管理的 DNS 接口列表
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
@@ -278,7 +282,7 @@ func (r *CertController) UserDestroy(ctx http.Context) http.Response {
// @Router /panel/cert/dns [get]
func (r *CertController) DNSList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
@@ -303,7 +307,7 @@ func (r *CertController) DNSList(ctx http.Context) http.Response {
//
// @Summary 添加 DNS 接口
// @Description 添加 DNS 接口到面板证书管理
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -312,7 +316,7 @@ func (r *CertController) DNSList(ctx http.Context) http.Response {
// @Router /panel/cert/dns [post]
func (r *CertController) DNSStore(ctx http.Context) http.Response {
var storeRequest requests.DNSStore
sanitize := Sanitize(ctx, &storeRequest)
sanitize := SanitizeRequest(ctx, &storeRequest)
if sanitize != nil {
return sanitize
}
@@ -332,7 +336,7 @@ func (r *CertController) DNSStore(ctx http.Context) http.Response {
//
// @Summary 获取 DNS 接口
// @Description 获取面板证书管理的 DNS 接口
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
@@ -340,7 +344,7 @@ func (r *CertController) DNSStore(ctx http.Context) http.Response {
// @Router /panel/cert/dns/{id} [get]
func (r *CertController) DNSShow(ctx http.Context) http.Response {
var showAndDestroyRequest requests.DNSShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
sanitize := SanitizeRequest(ctx, &showAndDestroyRequest)
if sanitize != nil {
return sanitize
}
@@ -361,7 +365,7 @@ func (r *CertController) DNSShow(ctx http.Context) http.Response {
//
// @Summary 更新 DNS 接口
// @Description 更新面板证书管理的 DNS 接口
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -371,7 +375,7 @@ func (r *CertController) DNSShow(ctx http.Context) http.Response {
// @Router /panel/cert/dns/{id} [put]
func (r *CertController) DNSUpdate(ctx http.Context) http.Response {
var updateRequest requests.DNSUpdate
sanitize := Sanitize(ctx, &updateRequest)
sanitize := SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
@@ -392,7 +396,7 @@ func (r *CertController) DNSUpdate(ctx http.Context) http.Response {
//
// @Summary 删除 DNS 接口
// @Description 删除面板证书管理的 DNS 接口
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -401,7 +405,7 @@ func (r *CertController) DNSUpdate(ctx http.Context) http.Response {
// @Router /panel/cert/dns/{id} [delete]
func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
var showAndDestroyRequest requests.DNSShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
sanitize := SanitizeRequest(ctx, &showAndDestroyRequest)
if sanitize != nil {
return sanitize
}
@@ -422,7 +426,7 @@ func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
//
// @Summary 获取证书列表
// @Description 获取面板证书管理的证书列表
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
@@ -430,7 +434,7 @@ func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
// @Router /panel/cert/certs [get]
func (r *CertController) CertList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
@@ -455,7 +459,7 @@ func (r *CertController) CertList(ctx http.Context) http.Response {
//
// @Summary 添加证书
// @Description 添加证书到面板证书管理
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -464,7 +468,7 @@ func (r *CertController) CertList(ctx http.Context) http.Response {
// @Router /panel/cert/certs [post]
func (r *CertController) CertStore(ctx http.Context) http.Response {
var storeRequest requests.CertStore
sanitize := Sanitize(ctx, &storeRequest)
sanitize := SanitizeRequest(ctx, &storeRequest)
if sanitize != nil {
return sanitize
}
@@ -484,7 +488,7 @@ func (r *CertController) CertStore(ctx http.Context) http.Response {
//
// @Summary 更新证书
// @Description 更新面板证书管理的证书
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -494,7 +498,7 @@ func (r *CertController) CertStore(ctx http.Context) http.Response {
// @Router /panel/cert/certs/{id} [put]
func (r *CertController) CertUpdate(ctx http.Context) http.Response {
var updateRequest requests.CertUpdate
sanitize := Sanitize(ctx, &updateRequest)
sanitize := SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
@@ -515,7 +519,7 @@ func (r *CertController) CertUpdate(ctx http.Context) http.Response {
//
// @Summary 获取证书
// @Description 获取面板证书管理的证书
// @Tags 证书管理
// @Tags TLS证书
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
@@ -523,7 +527,7 @@ func (r *CertController) CertUpdate(ctx http.Context) http.Response {
// @Router /panel/cert/certs/{id} [get]
func (r *CertController) CertShow(ctx http.Context) http.Response {
var showAndDestroyRequest requests.CertShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
sanitize := SanitizeRequest(ctx, &showAndDestroyRequest)
if sanitize != nil {
return sanitize
}
@@ -544,7 +548,7 @@ func (r *CertController) CertShow(ctx http.Context) http.Response {
//
// @Summary 删除证书
// @Description 删除面板证书管理的证书
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -553,7 +557,7 @@ func (r *CertController) CertShow(ctx http.Context) http.Response {
// @Router /panel/cert/certs/{id} [delete]
func (r *CertController) CertDestroy(ctx http.Context) http.Response {
var showAndDestroyRequest requests.CertShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
sanitize := SanitizeRequest(ctx, &showAndDestroyRequest)
if sanitize != nil {
return sanitize
}
@@ -574,7 +578,7 @@ func (r *CertController) CertDestroy(ctx http.Context) http.Response {
//
// @Summary 签发证书
// @Description 签发面板证书管理的证书
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -583,7 +587,7 @@ func (r *CertController) CertDestroy(ctx http.Context) http.Response {
// @Router /panel/cert/obtain [post]
func (r *CertController) Obtain(ctx http.Context) http.Response {
var obtainRequest requests.Obtain
sanitize := Sanitize(ctx, &obtainRequest)
sanitize := SanitizeRequest(ctx, &obtainRequest)
if sanitize != nil {
return sanitize
}
@@ -616,7 +620,7 @@ func (r *CertController) Obtain(ctx http.Context) http.Response {
//
// @Summary 续签证书
// @Description 续签面板证书管理的证书
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -625,7 +629,7 @@ func (r *CertController) Obtain(ctx http.Context) http.Response {
// @Router /panel/cert/renew [post]
func (r *CertController) Renew(ctx http.Context) http.Response {
var renewRequest requests.Renew
sanitize := Sanitize(ctx, &renewRequest)
sanitize := SanitizeRequest(ctx, &renewRequest)
if sanitize != nil {
return sanitize
}
@@ -645,7 +649,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
//
// @Summary 获取手动 DNS 记录
// @Description 获取签发证书所需的 DNS 记录
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -654,7 +658,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
// @Router /panel/cert/manualDNS [post]
func (r *CertController) ManualDNS(ctx http.Context) http.Response {
var obtainRequest requests.Obtain
sanitize := Sanitize(ctx, &obtainRequest)
sanitize := SanitizeRequest(ctx, &obtainRequest)
if sanitize != nil {
return sanitize
}
@@ -674,7 +678,7 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
//
// @Summary 部署证书
// @Description 部署面板证书管理的证书
// @Tags 证书管理
// @Tags TLS证书
// @Accept json
// @Produce json
// @Security BearerToken
@@ -683,7 +687,7 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
// @Router /panel/cert/deploy [post]
func (r *CertController) Deploy(ctx http.Context) http.Response {
var deployRequest requests.CertDeploy
sanitize := Sanitize(ctx, &deployRequest)
sanitize := SanitizeRequest(ctx, &deployRequest)
if sanitize != nil {
return sanitize
}
+41 -118
View File
@@ -5,19 +5,15 @@ 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"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/go-connections/nat"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
requests "github.com/TheTNB/panel/app/http/requests/container"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/str"
)
type ContainerController struct {
@@ -41,28 +37,12 @@ func NewContainerController() *ContainerController {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/list [get]
func (r *ContainerController) ContainerList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
return sanitize
}
containers, err := r.container.ContainerListAll()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(containers) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(containers) {
endIndex = len(containers)
}
paged := containers[startIndex:endIndex]
paged, total := Paginate(ctx, containers)
items := make([]any, 0)
for _, item := range paged {
@@ -85,7 +65,7 @@ func (r *ContainerController) ContainerList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(containers),
"total": total,
"items": items,
})
}
@@ -123,7 +103,7 @@ func (r *ContainerController) ContainerSearch(ctx http.Context) http.Response {
// @Router /panel/container/create [post]
func (r *ContainerController) ContainerCreate(ctx http.Context) http.Response {
var request requests.ContainerCreate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -220,7 +200,7 @@ func (r *ContainerController) ContainerCreate(ctx http.Context) http.Response {
// @Router /panel/container/remove [post]
func (r *ContainerController) ContainerRemove(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -243,7 +223,7 @@ func (r *ContainerController) ContainerRemove(ctx http.Context) http.Response {
// @Router /panel/container/start [post]
func (r *ContainerController) ContainerStart(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -266,7 +246,7 @@ func (r *ContainerController) ContainerStart(ctx http.Context) http.Response {
// @Router /panel/container/stop [post]
func (r *ContainerController) ContainerStop(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -289,7 +269,7 @@ func (r *ContainerController) ContainerStop(ctx http.Context) http.Response {
// @Router /panel/container/restart [post]
func (r *ContainerController) ContainerRestart(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -311,7 +291,7 @@ func (r *ContainerController) ContainerRestart(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
func (r *ContainerController) ContainerPause(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -335,7 +315,7 @@ func (r *ContainerController) ContainerPause(ctx http.Context) http.Response {
// @Router /panel/container/unpause [post]
func (r *ContainerController) ContainerUnpause(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -358,7 +338,7 @@ func (r *ContainerController) ContainerUnpause(ctx http.Context) http.Response {
// @Router /panel/container/inspect [get]
func (r *ContainerController) ContainerInspect(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -382,7 +362,7 @@ func (r *ContainerController) ContainerInspect(ctx http.Context) http.Response {
// @Router /panel/container/kill [post]
func (r *ContainerController) ContainerKill(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -405,7 +385,7 @@ func (r *ContainerController) ContainerKill(ctx http.Context) http.Response {
// @Router /panel/container/rename [post]
func (r *ContainerController) ContainerRename(ctx http.Context) http.Response {
var request requests.ContainerRename
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -428,7 +408,7 @@ func (r *ContainerController) ContainerRename(ctx http.Context) http.Response {
// @Router /panel/container/stats [get]
func (r *ContainerController) ContainerStats(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -452,7 +432,7 @@ func (r *ContainerController) ContainerStats(ctx http.Context) http.Response {
// @Router /panel/container/exist [get]
func (r *ContainerController) ContainerExist(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -476,7 +456,7 @@ func (r *ContainerController) ContainerExist(ctx http.Context) http.Response {
// @Router /panel/container/logs [get]
func (r *ContainerController) ContainerLogs(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -516,33 +496,14 @@ func (r *ContainerController) ContainerPrune(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/network/list [get]
func (r *ContainerController) NetworkList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
return sanitize
}
networks, err := r.container.NetworkList()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(networks) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(networks) {
endIndex = len(networks)
}
paged := networks[startIndex:endIndex]
if paged == nil {
paged = []types.NetworkResource{}
}
paged, total := Paginate(ctx, networks)
var items []any
items := make([]any, 0)
for _, item := range paged {
var ipamConfig []any
for _, v := range item.IPAM.Config {
@@ -574,7 +535,7 @@ func (r *ContainerController) NetworkList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(networks),
"total": total,
"items": items,
})
}
@@ -592,7 +553,7 @@ func (r *ContainerController) NetworkList(ctx http.Context) http.Response {
// @Router /panel/container/network/create [post]
func (r *ContainerController) NetworkCreate(ctx http.Context) http.Response {
var request requests.NetworkCreate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -616,7 +577,7 @@ func (r *ContainerController) NetworkCreate(ctx http.Context) http.Response {
// @Router /panel/container/network/remove [post]
func (r *ContainerController) NetworkRemove(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -639,7 +600,7 @@ func (r *ContainerController) NetworkRemove(ctx http.Context) http.Response {
// @Router /panel/container/network/exist [get]
func (r *ContainerController) NetworkExist(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -663,7 +624,7 @@ func (r *ContainerController) NetworkExist(ctx http.Context) http.Response {
// @Router /panel/container/network/inspect [get]
func (r *ContainerController) NetworkInspect(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -688,7 +649,7 @@ func (r *ContainerController) NetworkInspect(ctx http.Context) http.Response {
// @Router /panel/container/network/connect [post]
func (r *ContainerController) NetworkConnect(ctx http.Context) http.Response {
var request requests.NetworkConnectDisConnect
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -712,7 +673,7 @@ func (r *ContainerController) NetworkConnect(ctx http.Context) http.Response {
// @Router /panel/container/network/disconnect [post]
func (r *ContainerController) NetworkDisconnect(ctx http.Context) http.Response {
var request requests.NetworkConnectDisConnect
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -751,39 +712,20 @@ func (r *ContainerController) NetworkPrune(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/image/list [get]
func (r *ContainerController) ImageList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
return sanitize
}
images, err := r.container.ImageList()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(images) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(images) {
endIndex = len(images)
}
paged := images[startIndex:endIndex]
if paged == nil {
paged = []image.Summary{}
}
paged, total := Paginate(ctx, images)
var items []any
items := make([]any, 0)
for _, item := range paged {
items = append(items, map[string]any{
"id": item.ID,
"created": carbon.FromTimestamp(item.Created).ToDateTimeString(),
"containers": item.Containers,
"size": tools.FormatBytes(float64(item.Size)),
"size": str.FormatBytes(float64(item.Size)),
"labels": item.Labels,
"repo_tags": item.RepoTags,
"repo_digests": item.RepoDigests,
@@ -791,7 +733,7 @@ func (r *ContainerController) ImageList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(images),
"total": total,
"items": items,
})
}
@@ -808,7 +750,7 @@ func (r *ContainerController) ImageList(ctx http.Context) http.Response {
// @Router /panel/container/image/exist [get]
func (r *ContainerController) ImageExist(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -833,7 +775,7 @@ func (r *ContainerController) ImageExist(ctx http.Context) http.Response {
// @Router /panel/container/image/pull [post]
func (r *ContainerController) ImagePull(ctx http.Context) http.Response {
var request requests.ImagePull
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -856,7 +798,7 @@ func (r *ContainerController) ImagePull(ctx http.Context) http.Response {
// @Router /panel/container/image/remove [post]
func (r *ContainerController) ImageRemove(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -896,7 +838,7 @@ func (r *ContainerController) ImagePrune(ctx http.Context) http.Response {
// @Router /panel/container/image/inspect [get]
func (r *ContainerController) ImageInspect(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -919,39 +861,20 @@ func (r *ContainerController) ImageInspect(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/volume/list [get]
func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
return sanitize
}
volumes, err := r.container.VolumeList()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(volumes) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(volumes) {
endIndex = len(volumes)
}
paged := volumes[startIndex:endIndex]
if paged == nil {
paged = []*volume.Volume{}
}
paged, total := Paginate(ctx, volumes)
var items []any
items := make([]any, 0)
for _, item := range paged {
var usage any
if item.UsageData != nil {
usage = map[string]any{
"ref_count": item.UsageData.RefCount,
"size": tools.FormatBytes(float64(item.UsageData.Size)),
"size": str.FormatBytes(float64(item.UsageData.Size)),
}
}
items = append(items, map[string]any{
@@ -968,7 +891,7 @@ func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(volumes),
"total": total,
"items": items,
})
}
@@ -986,7 +909,7 @@ func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
// @Router /panel/container/volume/create [post]
func (r *ContainerController) VolumeCreate(ctx http.Context) http.Response {
var request requests.VolumeCreate
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -1010,7 +933,7 @@ func (r *ContainerController) VolumeCreate(ctx http.Context) http.Response {
// @Router /panel/container/volume/exist [get]
func (r *ContainerController) VolumeExist(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -1034,7 +957,7 @@ func (r *ContainerController) VolumeExist(ctx http.Context) http.Response {
// @Router /panel/container/volume/inspect [get]
func (r *ContainerController) VolumeInspect(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
@@ -1058,7 +981,7 @@ func (r *ContainerController) VolumeInspect(ctx http.Context) http.Response {
// @Router /panel/container/volume/remove [post]
func (r *ContainerController) VolumeRemove(ctx http.Context) http.Response {
var request requests.ID
if sanitize := Sanitize(ctx, &request); sanitize != nil {
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
+44 -3
View File
@@ -3,6 +3,8 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
)
// SuccessResponse 通用成功响应
@@ -27,7 +29,7 @@ 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: facades.Lang(ctx).Get("messages.mistake") + ": " + message,
Message: message,
})
}
@@ -38,8 +40,34 @@ func ErrorSystem(ctx http.Context) http.Response {
})
}
// Sanitize 消毒请求参数
func Sanitize(ctx http.Context, request http.FormRequest) http.Response {
func Paginate[T any](ctx http.Context, allItems []T) (pagedItems []T, total int) {
var paginateRequest commonrequests.Paginate
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return []T{}, 0
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
total = len(allItems)
startIndex := (page - 1) * limit
endIndex := page * limit
if total == 0 {
return []T{}, 0
}
if startIndex > total {
return []T{}, total
}
if endIndex > total {
endIndex = total
}
return allItems[startIndex:endIndex], total
}
// SanitizeRequest 消毒请求参数
func SanitizeRequest(ctx http.Context, request http.FormRequest) http.Response {
errors, err := ctx.Request().ValidateRequest(request)
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
@@ -50,3 +78,16 @@ func Sanitize(ctx http.Context, request http.FormRequest) http.Response {
return nil
}
// Sanitize 消毒参数
func Sanitize(ctx http.Context, rules map[string]string) http.Response {
validator, err := ctx.Request().Validate(rules)
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
return nil
}
+32 -47
View File
@@ -12,7 +12,9 @@ import (
"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/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type CronController struct {
@@ -50,18 +52,14 @@ func (r *CronController) List(ctx http.Context) http.Response {
// Add 添加计划任务
func (r *CronController) Add(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := Sanitize(ctx, map[string]string{
"name": "required|min_len:1|max_len:255",
"time": "required",
"script": "required",
"type": "required|in:shell,backup,cutoff",
"backup_type": "required_if:type,backup|in:website,mysql,postgresql",
})
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
// 单独验证时间格式
@@ -69,7 +67,7 @@ func (r *CronController) Add(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "时间格式错误")
}
shell := ctx.Request().Input("script")
script := ctx.Request().Input("script")
cronType := ctx.Request().Input("type")
if cronType == "backup" {
backupType := ctx.Request().Input("backup_type")
@@ -82,7 +80,7 @@ func (r *CronController) Add(ctx http.Context) http.Response {
backupPath = r.setting.Get(models.SettingKeyBackupPath) + "/" + backupType
}
backupSave := ctx.Request().InputInt("save", 10)
shell = `#!/bin/bash
script = `#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 数据备份脚本
@@ -99,7 +97,7 @@ panel backup ${type} ${name} ${path} ${save} 2>&1
if cronType == "cutoff" {
website := ctx.Request().Input("website")
save := ctx.Request().InputInt("save", 180)
shell = `#!/bin/bash
script = `#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 日志切割脚本
@@ -114,17 +112,17 @@ panel cutoff ${name} ${save} 2>&1
shellDir := "/www/server/cron/"
shellLogDir := "/www/server/cron/logs/"
if !tools.Exists(shellDir) {
if !io.Exists(shellDir) {
return Error(ctx, http.StatusInternalServerError, "计划任务目录不存在")
}
if !tools.Exists(shellLogDir) {
if !io.Exists(shellLogDir) {
return Error(ctx, http.StatusInternalServerError, "计划任务日志目录不存在")
}
shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + tools.RandomString(16)
if err = tools.Write(shellDir+shellFile+".sh", shell, 0700); err != nil {
shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + str.RandomString(16)
if err := io.Write(shellDir+shellFile+".sh", script, 0700); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("dos2unix " + shellDir + shellFile + ".sh"); err != nil {
if out, err := shell.Execf("dos2unix " + shellDir + shellFile + ".sh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -136,8 +134,7 @@ panel cutoff ${name} ${save} 2>&1
cron.Shell = shellDir + shellFile + ".sh"
cron.Log = shellLogDir + shellFile + ".log"
err = facades.Orm().Query().Create(&cron)
if err != nil {
if err := facades.Orm().Query().Create(&cron); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("保存计划任务失败")
@@ -161,26 +158,22 @@ func (r *CronController) Script(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
shell, err := tools.Read(cron.Shell)
script, err := io.Read(cron.Shell)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, shell)
return Success(ctx, script)
}
// Update 更新计划任务
func (r *CronController) Update(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := Sanitize(ctx, map[string]string{
"name": "required|min_len:1|max_len:255",
"time": "required",
"script": "required",
})
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
// 单独验证时间格式
@@ -189,8 +182,7 @@ func (r *CronController) Update(ctx http.Context) http.Response {
}
var cron models.Cron
err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
if err != nil {
if err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron); err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
@@ -200,18 +192,17 @@ func (r *CronController) Update(ctx http.Context) http.Response {
cron.Time = ctx.Request().Input("time")
cron.Name = ctx.Request().Input("name")
err = facades.Orm().Query().Save(&cron)
if err != nil {
if err := facades.Orm().Query().Save(&cron); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("更新计划任务失败")
return ErrorSystem(ctx)
}
if err = tools.Write(cron.Shell, ctx.Request().Input("script"), 0644); err != nil {
if err := io.Write(cron.Shell, ctx.Request().Input("script"), 0644); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("dos2unix " + cron.Shell); err != nil {
if out, err := shell.Execf("dos2unix " + cron.Shell); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -237,7 +228,7 @@ func (r *CronController) Delete(ctx http.Context) http.Response {
if err := r.cron.DeleteFromSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Remove(cron.Shell); err != nil {
if err := io.Remove(cron.Shell); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -253,25 +244,19 @@ func (r *CronController) Delete(ctx http.Context) http.Response {
// Status 更新计划任务状态
func (r *CronController) Status(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := Sanitize(ctx, map[string]string{
"status": "bool",
})
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
var cron models.Cron
err = facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
if err != nil {
if err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron); err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
cron.Status = ctx.Request().InputBool("status")
err = facades.Orm().Query().Save(&cron)
if err != nil {
if err := facades.Orm().Query().Save(&cron); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("更新计划任务状态失败")
@@ -297,11 +282,11 @@ func (r *CronController) Log(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
if !tools.Exists(cron.Log) {
if !io.Exists(cron.Log) {
return Error(ctx, http.StatusUnprocessableEntity, "日志文件不存在")
}
log, err := tools.Exec("tail -n 1000 " + cron.Log)
log, err := shell.Execf("tail -n 1000 " + cron.Log)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
+78 -88
View File
@@ -2,18 +2,21 @@ package controllers
import (
"fmt"
"io"
"os"
stdio "io"
stdos "os"
"path/filepath"
"strconv"
"strings"
"syscall"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
requests "github.com/TheTNB/panel/app/http/requests/file"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type FileController struct {
@@ -27,7 +30,7 @@ func NewFileController() *FileController {
//
// @Summary 创建文件/目录
// @Description 创建文件/目录到给定路径
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -36,18 +39,18 @@ func NewFileController() *FileController {
// @Router /panel/file/create [post]
func (r *FileController) Create(ctx http.Context) http.Response {
var request requests.NotExist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
isDir := ctx.Request().InputBool("dir")
if !isDir {
if out, err := tools.Exec("touch " + request.Path); err != nil {
if out, err := shell.Execf("touch " + request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if err := tools.Mkdir(request.Path, 0755); err != nil {
if err := io.Mkdir(request.Path, 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
@@ -60,7 +63,7 @@ func (r *FileController) Create(ctx http.Context) http.Response {
//
// @Summary 获取文件内容
// @Description 获取给定路径的文件内容
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -69,12 +72,12 @@ func (r *FileController) Create(ctx http.Context) http.Response {
// @Router /panel/file/content [get]
func (r *FileController) Content(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
fileInfo, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -85,7 +88,7 @@ func (r *FileController) Content(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
}
content, err := tools.Read(request.Path)
content, err := io.Read(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -97,26 +100,25 @@ func (r *FileController) Content(ctx http.Context) http.Response {
//
// @Summary 保存文件内容
// @Description 保存给定路径的文件内容
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Save true "request"
// @Param content body string true "content"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/save [post]
func (r *FileController) Save(ctx http.Context) http.Response {
var request requests.Save
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
fileInfo, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, request.Content, fileInfo.Mode()); err != nil {
if err = io.Write(request.Path, request.Content, fileInfo.Mode()); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -128,7 +130,7 @@ func (r *FileController) Save(ctx http.Context) http.Response {
//
// @Summary 删除文件/目录
// @Description 删除给定路径的文件/目录
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -137,12 +139,12 @@ func (r *FileController) Save(ctx http.Context) http.Response {
// @Router /panel/file/delete [post]
func (r *FileController) Delete(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.Remove(request.Path); err != nil {
if err := io.Remove(request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -153,7 +155,7 @@ func (r *FileController) Delete(ctx http.Context) http.Response {
//
// @Summary 上传文件
// @Description 上传文件到给定路径
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -163,7 +165,7 @@ func (r *FileController) Delete(ctx http.Context) http.Response {
// @Router /panel/file/upload [post]
func (r *FileController) Upload(ctx http.Context) http.Response {
var request requests.Upload
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
@@ -174,16 +176,16 @@ func (r *FileController) Upload(ctx http.Context) http.Response {
}
defer src.Close()
if tools.Exists(request.Path) && !ctx.Request().InputBool("force") {
if io.Exists(request.Path) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
}
data, err := io.ReadAll(src)
data, err := stdio.ReadAll(src)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, string(data), 0755); err != nil {
if err = io.Write(request.Path, string(data), 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -195,7 +197,7 @@ func (r *FileController) Upload(ctx http.Context) http.Response {
//
// @Summary 移动文件/目录
// @Description 移动文件/目录到给定路径,等效于重命名
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -204,16 +206,16 @@ func (r *FileController) Upload(ctx http.Context) http.Response {
// @Router /panel/file/move [post]
func (r *FileController) Move(ctx http.Context) http.Response {
var request requests.Move
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
if io.Exists(request.Target) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
}
if err := tools.Mv(request.Source, request.Target); err != nil {
if err := io.Mv(request.Source, request.Target); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -225,7 +227,7 @@ func (r *FileController) Move(ctx http.Context) http.Response {
//
// @Summary 复制文件/目录
// @Description 复制文件/目录到给定路径
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -234,16 +236,16 @@ func (r *FileController) Move(ctx http.Context) http.Response {
// @Router /panel/file/copy [post]
func (r *FileController) Copy(ctx http.Context) http.Response {
var request requests.Copy
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
if io.Exists(request.Target) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
}
if err := tools.Cp(request.Source, request.Target); err != nil {
if err := io.Cp(request.Source, request.Target); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -255,7 +257,7 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
//
// @Summary 下载文件
// @Description 下载给定路径的文件
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -264,12 +266,12 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
// @Router /panel/file/download [get]
func (r *FileController) Download(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
info, err := tools.FileInfo(request.Path)
info, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -284,7 +286,7 @@ func (r *FileController) Download(ctx http.Context) http.Response {
//
// @Summary 下载远程文件
// @Description 下载远程文件到给定路径
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -293,7 +295,7 @@ func (r *FileController) Download(ctx http.Context) http.Response {
// @Router /panel/file/remoteDownload [post]
func (r *FileController) RemoteDownload(ctx http.Context) http.Response {
var request requests.NotExist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
@@ -306,7 +308,7 @@ func (r *FileController) RemoteDownload(ctx http.Context) http.Response {
//
// @Summary 获取文件/目录信息
// @Description 获取给定路径的文件/目录信息
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -315,19 +317,19 @@ func (r *FileController) RemoteDownload(ctx http.Context) http.Response {
// @Router /panel/file/info [get]
func (r *FileController) Info(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
fileInfo, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, http.Json{
"name": fileInfo.Name(),
"size": tools.FormatBytes(float64(fileInfo.Size())),
"size": str.FormatBytes(float64(fileInfo.Size())),
"mode_str": fileInfo.Mode().String(),
"mode": fmt.Sprintf("%04o", fileInfo.Mode().Perm()),
"dir": fileInfo.IsDir(),
@@ -339,7 +341,7 @@ func (r *FileController) Info(ctx http.Context) http.Response {
//
// @Summary 修改文件/目录权限
// @Description 修改给定路径的文件/目录权限
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -348,15 +350,21 @@ func (r *FileController) Info(ctx http.Context) http.Response {
// @Router /panel/file/permission [post]
func (r *FileController) Permission(ctx http.Context) http.Response {
var request requests.Permission
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.Chmod(request.Path, os.FileMode(request.Mode)); err != nil {
// 解析成8进制
mode, err := strconv.ParseUint(request.Mode, 8, 64)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Chown(request.Path, request.Owner, request.Group); err != nil {
if err = io.Chmod(request.Path, stdos.FileMode(mode)); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = io.Chown(request.Path, request.Owner, request.Group); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -367,7 +375,7 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
//
// @Summary 压缩文件/目录
// @Description 压缩文件/目录到给定路径
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -376,12 +384,12 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
// @Router /panel/file/archive [post]
func (r *FileController) Archive(ctx http.Context) http.Response {
var request requests.Archive
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.Archive(request.Paths, request.File); err != nil {
if err := io.Archive(request.Paths, request.File); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -393,7 +401,7 @@ func (r *FileController) Archive(ctx http.Context) http.Response {
//
// @Summary 解压文件/目录
// @Description 解压文件/目录到给定路径
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Security BearerToken
@@ -402,12 +410,12 @@ func (r *FileController) Archive(ctx http.Context) http.Response {
// @Router /panel/file/unArchive [post]
func (r *FileController) UnArchive(ctx http.Context) http.Response {
var request requests.UnArchive
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.UnArchive(request.File, request.Path); err != nil {
if err := io.UnArchive(request.File, request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -419,7 +427,7 @@ func (r *FileController) UnArchive(ctx http.Context) http.Response {
//
// @Summary 搜索文件/目录
// @Description 通过关键词搜索给定路径的文件/目录
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Param data body requests.Search true "request"
@@ -427,13 +435,13 @@ func (r *FileController) UnArchive(ctx http.Context) http.Response {
// @Router /panel/file/search [post]
func (r *FileController) Search(ctx http.Context) http.Response {
var request requests.Search
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
paths := make(map[string]os.FileInfo)
err := filepath.Walk(request.Path, func(path string, info os.FileInfo, err error) error {
paths := make(map[string]stdos.FileInfo)
err := filepath.Walk(request.Path, func(path string, info stdos.FileInfo, err error) error {
if err != nil {
return err
}
@@ -453,7 +461,7 @@ func (r *FileController) Search(ctx http.Context) http.Response {
//
// @Summary 获取文件/目录列表
// @Description 获取给定路径的文件/目录列表
// @Tags 文件管理
// @Tags 文件
// @Accept json
// @Produce json
// @Param data query requests.Exist true "request"
@@ -462,18 +470,12 @@ func (r *FileController) Search(ctx http.Context) http.Response {
// @Router /panel/file/list [get]
func (r *FileController) List(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
sanitize := SanitizeRequest(ctx, &request)
if sanitize != nil {
return sanitize
}
var paginate commonrequests.Paginate
paginateSanitize := Sanitize(ctx, &paginate)
if paginateSanitize != nil {
return paginateSanitize
}
fileInfoList, err := os.ReadDir(request.Path)
fileInfoList, err := io.ReadDir(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -486,43 +488,31 @@ func (r *FileController) List(ctx http.Context) http.Response {
paths = append(paths, map[string]any{
"name": info.Name(),
"full": filepath.Join(request.Path, info.Name()),
"size": tools.FormatBytes(float64(info.Size())),
"size": str.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),
"owner": os.GetUser(stat.Uid),
"group": os.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())),
"hidden": io.IsHidden(info.Name()),
"symlink": io.IsSymlink(info.Mode()),
"link": io.GetSymlink(filepath.Join(request.Path, info.Name())),
"dir": info.IsDir(),
"modify": carbon.FromStdTime(info.ModTime()).ToDateTimeString(),
})
}
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{}
}
paged, total := Paginate(ctx, paths)
return Success(ctx, http.Json{
"total": len(paths),
"total": total,
"items": paged,
})
}
// setPermission
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
_ = tools.Chmod(path, os.FileMode(mode))
_ = tools.Chown(path, owner, group)
func (r *FileController) setPermission(path string, mode stdos.FileMode, owner, group string) {
_ = io.Chmod(path, mode)
_ = io.Chown(path, owner, group)
}
+31 -12
View File
@@ -8,12 +8,15 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/hashicorp/go-version"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type MenuItem struct {
@@ -108,7 +111,7 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
}
var databaseCount int64
if mysqlInstalled {
status, err := tools.ServiceStatus("mysqld")
status, err := systemctl.Status("mysqld")
if status && err == nil {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
@@ -150,9 +153,9 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
}
}
if postgresqlInstalled {
status, err := tools.ServiceStatus("postgresql")
status, err := systemctl.Status("postgresql")
if status && err == nil {
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
raw, err := shell.Execf(`echo "\l" | su - postgres -c "psql"`)
if err == nil {
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
@@ -173,7 +176,7 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
var ftpCount int64
var ftpPlugin = r.plugin.GetInstalledBySlug("pureftpd")
if ftpPlugin.ID != 0 {
listRaw, err := tools.Exec("pure-pw list")
listRaw, err := shell.Execf("pure-pw list")
if len(listRaw) != 0 && err == nil {
listArr := strings.Split(listRaw, "\n")
ftpCount = int64(len(listArr))
@@ -248,13 +251,21 @@ func (r *InfoController) InstalledDbAndPhp(ctx http.Context) http.Response {
// CheckUpdate 检查面板更新
func (r *InfoController) CheckUpdate(ctx http.Context) http.Response {
version := facades.Config().GetString("panel.version")
remote, err := tools.GetLatestPanelVersion()
current := facades.Config().GetString("panel.version")
latest, err := tools.GetLatestPanelVersion()
if err != nil {
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
}
if tools.VersionCompare(version, remote.Version, ">=") {
v1, err := version.NewVersion(current)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
}
v2, err := version.NewVersion(latest.Version)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
}
if v1.GreaterThanOrEqual(v2) {
return Success(ctx, http.Json{
"update": false,
})
@@ -267,17 +278,25 @@ func (r *InfoController) CheckUpdate(ctx http.Context) http.Response {
// UpdateInfo 获取更新信息
func (r *InfoController) UpdateInfo(ctx http.Context) http.Response {
version := facades.Config().GetString("panel.version")
current, err := tools.GetLatestPanelVersion()
current := facades.Config().GetString("panel.version")
latest, err := tools.GetLatestPanelVersion()
if err != nil {
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
}
if tools.VersionCompare(version, current.Version, ">=") {
v1, err := version.NewVersion(current)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
}
v2, err := version.NewVersion(latest.Version)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
}
if v1.GreaterThanOrEqual(v2) {
return Error(ctx, http.StatusInternalServerError, "当前版本已是最新版本")
}
versions, err := tools.GenerateVersions(version, current.Version)
versions, err := tools.GenerateVersions(current, latest.Version)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "获取更新信息失败")
}
+75 -21
View File
@@ -21,7 +21,14 @@ func NewPluginController() *PluginController {
}
}
// List 列出所有插件
// List
//
// @Summary 插件列表
// @Tags 插件
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/plugin/list [get]
func (r *PluginController) List(ctx http.Context) http.Response {
plugins := r.plugin.All()
installedPlugins, err := r.plugin.AllInstalled()
@@ -68,28 +75,23 @@ func (r *PluginController) List(ctx http.Context) http.Response {
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(pluginArr) {
return Success(ctx, http.Json{
"total": 0,
"items": []plugin{},
})
}
if endIndex > len(pluginArr) {
endIndex = len(pluginArr)
}
pagedPlugins := pluginArr[startIndex:endIndex]
paged, total := Paginate(ctx, pluginArr)
return Success(ctx, http.Json{
"total": len(pluginArr),
"items": pagedPlugins,
"total": total,
"items": paged,
})
}
// Install 安装插件
// Install
//
// @Summary 安装插件
// @Tags 插件
// @Produce json
// @Security BearerToken
// @Param slug query string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/plugin/install [post]
func (r *PluginController) Install(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
@@ -100,7 +102,15 @@ func (r *PluginController) Install(ctx http.Context) http.Response {
return Success(ctx, "任务已提交")
}
// Uninstall 卸载插件
// Uninstall
//
// @Summary 卸载插件
// @Tags 插件
// @Produce json
// @Security BearerToken
// @Param slug query string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/plugin/uninstall [post]
func (r *PluginController) Uninstall(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
@@ -111,7 +121,15 @@ func (r *PluginController) Uninstall(ctx http.Context) http.Response {
return Success(ctx, "任务已提交")
}
// Update 更新插件
// Update
//
// @Summary 更新插件
// @Tags 插件
// @Produce json
// @Security BearerToken
// @Param slug query string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/plugin/update [post]
func (r *PluginController) Update(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
@@ -122,7 +140,16 @@ func (r *PluginController) Update(ctx http.Context) http.Response {
return Success(ctx, "任务已提交")
}
// UpdateShow 更新插件首页显示状态
// UpdateShow
//
// @Summary 更新插件首页显示状态
// @Tags 插件
// @Produce json
// @Security BearerToken
// @Param slug query string true "request"
// @Param show query bool true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/plugin/updateShow [post]
func (r *PluginController) UpdateShow(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
show := ctx.Request().InputBool("show")
@@ -150,3 +177,30 @@ func (r *PluginController) UpdateShow(ctx http.Context) http.Response {
return Success(ctx, "操作成功")
}
// IsInstalled
//
// @Summary 检查插件是否已安装
// @Tags 插件
// @Produce json
// @Security BearerToken
// @Param slug query string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/plugin/isInstalled [get]
func (r *PluginController) IsInstalled(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetInstalledBySlug(slug)
info := r.plugin.GetBySlug(slug)
if plugin.Slug != slug {
return Success(ctx, http.Json{
"name": info.Name,
"installed": false,
})
}
return Success(ctx, http.Json{
"name": info.Name,
"installed": true,
})
}
@@ -12,8 +12,11 @@ import (
"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"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/types"
)
type Fail2banController struct {
@@ -26,61 +29,9 @@ func NewFail2banController() *Fail2banController {
}
}
// Status 获取运行状态
func (r *Fail2banController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("fail2ban")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Fail2banController) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("fail2ban"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Fail2banController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("fail2ban"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启服务失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Fail2banController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("fail2ban"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动服务失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Fail2banController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("fail2ban"); err != nil {
return nil
}
status, err := tools.ServiceStatus("fail2ban")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
return controllers.Success(ctx, !status)
}
// List 所有 Fail2ban 规则
func (r *Fail2banController) List(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -97,7 +48,7 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
}
jailName := jail[1]
jailRaw := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
jailRaw := str.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
if len(jailRaw) == 0 {
continue
}
@@ -117,31 +68,17 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
})
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(jails) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.Fail2banJail{},
})
}
if endIndex > len(jails) {
endIndex = len(jails)
}
pagedJails := jails[startIndex:endIndex]
if pagedJails == nil {
pagedJails = []types.Fail2banJail{}
}
paged, total := controllers.Paginate(ctx, jails)
return controllers.Success(ctx, http.Json{
"total": len(jails),
"items": pagedJails,
"total": total,
"items": paged,
})
}
// Add 添加 Fail2ban 规则
func (r *Fail2banController) Add(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"name": "required",
"type": "required|in:website,service",
"maxretry": "required",
@@ -150,12 +87,8 @@ func (r *Fail2banController) Add(ctx http.Context) http.Response {
"website_name": "required_if:type,website",
"website_mode": "required_if:type,website",
"website_path": "required_if:website_mode,path",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
jailName := ctx.Request().Input("name")
@@ -167,7 +100,7 @@ func (r *Fail2banController) Add(ctx http.Context) http.Response {
jailWebsiteMode := ctx.Request().Input("website_mode")
jailWebsitePath := ctx.Request().Input("website_path")
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -206,7 +139,7 @@ logpath = /www/wwwlogs/` + website.Name + `.log
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
`
raw += rule
if err = tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
@@ -224,7 +157,7 @@ failregex = ^<HOST>\s-.*\s` + jailWebsitePath + `.*HTTP/.*$
ignoreregex =
`
}
if err = tools.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
@@ -235,21 +168,21 @@ ignoreregex =
var err error
switch jailName {
case "ssh":
if tools.IsDebian() {
if os.IsDebian() {
logPath = "/var/log/auth.log"
} else {
logPath = "/var/log/secure"
}
filter = "sshd"
port, err = tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
port, err = shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
case "mysql":
logPath = "/www/server/mysql/mysql-error.log"
filter = "mysqld-auth"
port, err = tools.Exec("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
port, err = shell.Execf("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
case "pure-ftpd":
logPath = "/var/log/messages"
filter = "pure-ftpd"
port, err = tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
port, err = shell.Execf(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
default:
return controllers.Error(ctx, http.StatusUnprocessableEntity, "未知服务")
}
@@ -271,12 +204,12 @@ logpath = ` + logPath + `
# ` + jailName + `-END
`
raw += rule
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
@@ -286,7 +219,7 @@ logpath = ` + logPath + `
// Delete 删除规则
func (r *Fail2banController) Delete(ctx http.Context) http.Response {
jailName := ctx.Request().Input("name")
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -294,14 +227,14 @@ func (r *Fail2banController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "规则不存在")
}
rule := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
rule := str.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
raw = strings.Replace(raw, "\n# "+jailName+"-START"+rule+"# "+jailName+"-END", "", -1)
raw = strings.TrimSpace(raw)
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
@@ -315,15 +248,15 @@ func (r *Fail2banController) BanList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
currentlyBan, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Currently banned" | awk '{print $4}'`)
currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, name)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
totalBan, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Total banned" | awk '{print $4}'`)
totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, name)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
bannedIp, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Banned IP list" | awk -F ":" '{print $2}'`)
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, name)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
@@ -357,7 +290,7 @@ func (r *Fail2banController) Unban(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
if _, err := tools.Exec("fail2ban-client set " + name + " unbanip " + ip); err != nil {
if _, err := shell.Execf("fail2ban-client set %s unbanip %s", name, ip); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "解封失败")
}
@@ -371,7 +304,7 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -383,11 +316,11 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
}
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
return controllers.Success(ctx, nil)
@@ -395,7 +328,7 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
// GetWhiteList 获取白名单
func (r *Fail2banController) GetWhiteList(ctx http.Context) http.Response {
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
+7 -186
View File
@@ -7,7 +7,8 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/frp"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type FrpController struct {
@@ -17,186 +18,6 @@ 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 获取配置
@@ -209,12 +30,12 @@ func (r *FrpController) Stop(ctx http.Context) http.Response {
// @Router /plugins/frp/config [get]
func (r *FrpController) GetConfig(ctx http.Context) http.Response {
var serviceRequest requests.Service
sanitize := controllers.Sanitize(ctx, &serviceRequest)
sanitize := controllers.SanitizeRequest(ctx, &serviceRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read(fmt.Sprintf("/www/server/frp/%s.toml", serviceRequest.Service))
config, err := io.Read(fmt.Sprintf("/www/server/frp/%s.toml", serviceRequest.Service))
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -234,16 +55,16 @@ func (r *FrpController) GetConfig(ctx http.Context) http.Response {
// @Router /plugins/frp/config [post]
func (r *FrpController) UpdateConfig(ctx http.Context) http.Response {
var updateRequest requests.UpdateConfig
sanitize := controllers.Sanitize(ctx, &updateRequest)
sanitize := controllers.SanitizeRequest(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 {
if err := io.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 {
if err := systemctl.Restart(updateRequest.Service); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -5,7 +5,8 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/gitea"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type GiteaController struct {
@@ -15,137 +16,6 @@ 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 获取配置
@@ -156,7 +26,7 @@ func (r *GiteaController) Stop(ctx http.Context) http.Response {
// @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")
config, err := io.Read("/www/server/gitea/app.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -176,16 +46,16 @@ func (r *GiteaController) GetConfig(ctx http.Context) http.Response {
// @Router /plugins/gitea/config [post]
func (r *GiteaController) UpdateConfig(ctx http.Context) http.Response {
var updateRequest requests.UpdateConfig
sanitize := controllers.Sanitize(ctx, &updateRequest)
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
if err := tools.Write("/www/server/gitea/app.ini", updateRequest.Config, 0644); err != nil {
if err := io.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 {
if err := systemctl.Restart("gitea"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
+165 -327
View File
@@ -1,7 +1,6 @@
package plugins
import (
"database/sql"
"fmt"
"regexp"
@@ -12,8 +11,12 @@ import (
"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"
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type MySQLController struct {
@@ -28,55 +31,9 @@ func NewMySQLController() *MySQLController {
}
}
// Status 获取运行状态
func (r *MySQLController) 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 *MySQLController) 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 *MySQLController) 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 *MySQLController) 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 *MySQLController) 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 *MySQLController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
config, err := io.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
}
@@ -91,11 +48,15 @@ func (r *MySQLController) SaveConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
if err := io.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
}
return r.Restart(ctx)
if err := systemctl.Reload("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL失败")
}
return controllers.Success(ctx, nil)
}
// Load 获取负载
@@ -105,15 +66,12 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
status, _ := systemctl.Status("mysqld")
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
return controllers.Success(ctx, []types.NV{})
}
raw, err := tools.Exec("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
raw, err := shell.Execf("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
}
@@ -149,7 +107,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
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]))
d["value"] = str.FormatBytes(cast.ToFloat64(matches[1]))
}
data = append(data, d)
@@ -170,7 +128,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
// ErrorLog 获取错误日志
func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
log, err := shell.Execf("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -180,7 +138,7 @@ func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
// ClearErrorLog 清空错误日志
func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -189,7 +147,7 @@ func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
// SlowLog 获取慢查询日志
func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
log, err := shell.Execf("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -199,7 +157,7 @@ func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
// ClearSlowLog 清空慢查询日志
func (r *MySQLController) ClearSlowLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
@@ -217,109 +175,61 @@ func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
// SetRootPassword 设置root密码
func (r *MySQLController) 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密码失败")
mysql, err := db.NewMySQL("root", oldRootPassword, r.getSock(), "unix")
if err != nil {
// 尝试安全模式直接改密
if err = db.MySQLResetRootPassword(rootPassword); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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密码失败")
} else {
if err = mysql.UserPassword("root", rootPassword); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
}
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
}
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *MySQLController) 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)/")
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
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{},
"items": []types.MySQLDatabase{},
})
}
if endIndex > len(databases) {
endIndex = len(databases)
databases, err := mysql.Databases()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
pagedDatabases := databases[startIndex:endIndex]
paged, total := controllers.Paginate(ctx, databases)
return controllers.Success(ctx, http.Json{
"total": len(databases),
"items": pagedDatabases,
"total": total,
"items": paged,
})
}
// AddDatabase 添加数据库
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_]+$",
"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())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:32",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
@@ -327,17 +237,18 @@ func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
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)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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 err = mysql.DatabaseCreate(database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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 err = mysql.UserCreate(user, password); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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)
if err = mysql.PrivilegesGrant(user, database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -345,20 +256,20 @@ func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
// DeleteDatabase 删除数据库
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",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
}); sanitize != nil {
return sanitize
}
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)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.DatabaseDrop(database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -366,32 +277,16 @@ func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
// BackupList 获取备份列表
func (r *MySQLController) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.MysqlList()
backups, 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": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
paged, total := controllers.Paginate(ctx, backups)
return controllers.Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
"total": total,
"items": paged,
})
}
@@ -403,8 +298,8 @@ func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
if err = tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err = io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
@@ -420,19 +315,14 @@ func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
// CreateBackup 创建备份
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",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
}); sanitize != nil {
return sanitize
}
database := ctx.Request().Input("database")
err = r.backup.MysqlBackup(database)
if err != nil {
if err := r.backup.MysqlBackup(database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -441,19 +331,15 @@ func (r *MySQLController) CreateBackup(ctx http.Context) http.Response {
// DeleteBackup 删除备份
func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, 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())
}); sanitize != nil {
return sanitize
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
fileName := ctx.Request().Input("name")
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
if err := io.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -462,19 +348,14 @@ func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
// RestoreBackup 还原备份
func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, 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())
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
}); sanitize != nil {
return sanitize
}
err = r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
if err != nil {
if err := r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup")); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -483,109 +364,50 @@ func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
// UserList 用户列表
func (r *MySQLController) 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)/")
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
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{},
"items": []types.MySQLUser{},
})
}
if endIndex > len(userGrants) {
endIndex = len(userGrants)
users, err := mysql.Users()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
}
pagedUserGrants := userGrants[startIndex:endIndex]
paged, total := controllers.Paginate(ctx, users)
return controllers.Success(ctx, http.Json{
"total": len(userGrants),
"items": pagedUserGrants,
"total": total,
"items": paged,
})
}
// AddUser 添加用户
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_]+$",
"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())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:32",
}); sanitize != nil {
return sanitize
}
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)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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 err = mysql.UserCreate(user, password); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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)
if err = mysql.PrivilegesGrant(user, database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -593,20 +415,20 @@ func (r *MySQLController) AddUser(ctx http.Context) http.Response {
// DeleteUser 删除用户
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_]+$",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
}); sanitize != nil {
return sanitize
}
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)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.UserDrop(user); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -614,25 +436,22 @@ func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
// SetUserPassword 设置用户密码
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",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:32",
}); sanitize != nil {
return sanitize
}
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)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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)
if err = mysql.UserPassword(user, password); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -640,29 +459,48 @@ func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
// SetUserPrivileges 设置用户权限
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",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
if sanitize := controllers.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
}); sanitize != nil {
return sanitize
}
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)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
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)
if err = mysql.PrivilegesGrant(user, database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// getSock 获取sock文件位置
func (r *MySQLController) getSock() string {
if io.Exists("/tmp/mysql.sock") {
return "/tmp/mysql.sock"
}
if io.Exists("/www/server/mysql/config/my.cnf") {
config, _ := io.Read("/www/server/mysql/config/my.cnf")
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
matches := re.FindStringSubmatch(config)
if len(matches) > 2 {
return matches[2]
}
}
if io.Exists("/etc/my.cnf") {
config, _ := io.Read("/etc/my.cnf")
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
matches := re.FindStringSubmatch(config)
if len(matches) > 2 {
return matches[2]
}
}
return "/tmp/mysql.sock"
}
@@ -1,6 +1,7 @@
package plugins
import (
"fmt"
"regexp"
"time"
@@ -9,7 +10,11 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type OpenRestyController struct {
@@ -17,88 +22,67 @@ type OpenRestyController struct {
}
func NewOpenrestyController() *OpenRestyController {
return &OpenRestyController{
// Inject services
}
return &OpenRestyController{}
}
// Status 获取运行状态
func (r *OpenRestyController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("openresty")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *OpenRestyController) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动OpenResty
func (r *OpenRestyController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动OpenResty失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止OpenResty
func (r *OpenRestyController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止OpenResty失败")
}
return controllers.Success(ctx, nil)
}
// Restart 重启OpenResty
func (r *OpenRestyController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启OpenResty失败")
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
// GetConfig
//
// @Summary 获取配置
// @Tags 插件-OpenResty
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/config [get]
func (r *OpenRestyController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/openresty/conf/nginx.conf")
config, err := io.Read("/www/server/openresty/conf/nginx.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty配置失败")
return controllers.Error(ctx, http.StatusInternalServerError, "获取配置失败")
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
// SaveConfig
//
// @Summary 保存配置
// @Tags 插件-OpenResty
// @Produce json
// @Security BearerToken
// @Param config body string true "配置"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/config [post]
func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "配置不能为空")
}
if err := tools.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "保存OpenResty配置失败")
if err := io.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "保存配置失败")
}
return r.Reload(ctx)
if err := systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err))
}
return controllers.Success(ctx, nil)
}
// ErrorLog 获取错误日志
// ErrorLog
//
// @Summary 获取错误日志
// @Tags 插件-OpenResty
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/errorLog [get]
func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
if !tools.Exists("/www/wwwlogs/nginx_error.log") {
if !io.Exists("/www/wwwlogs/nginx_error.log") {
return controllers.Success(ctx, "")
}
out, err := tools.Exec("tail -n 100 /www/wwwlogs/openresty_error.log")
out, err := shell.Execf("tail -n 100 /www/wwwlogs/openresty_error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -106,52 +90,62 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
return controllers.Success(ctx, out)
}
// ClearErrorLog 清空错误日志
// ClearErrorLog
//
// @Summary 清空错误日志
// @Tags 插件-OpenResty
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/clearErrorLog [post]
func (r *OpenRestyController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// Load 获取负载
// Load
//
// @Summary 获取负载状态
// @Tags 插件-OpenResty
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/load [get]
func (r *OpenRestyController) Load(ctx http.Context) http.Response {
client := resty.New().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
if err != nil || !resp.IsSuccess() {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
return controllers.Success(ctx, []types.NV{})
}
raw := resp.String()
type nginxStatus struct {
Name string `json:"name"`
Value string `json:"value"`
}
var data []nginxStatus
var data []types.NV
workers, err := tools.Exec("ps aux | grep nginx | grep 'worker process' | wc -l")
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
return controllers.Error(ctx, http.StatusInternalServerError, "获取负载失败")
}
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "工作进程",
Value: workers,
})
out, err := tools.Exec("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
return controllers.Error(ctx, http.StatusInternalServerError, "获取负载失败")
}
mem := tools.FormatBytes(cast.ToFloat64(out))
data = append(data, nginxStatus{
mem := str.FormatBytes(cast.ToFloat64(out))
data = append(data, types.NV{
Name: "内存占用",
Value: mem,
})
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
if len(match) == 2 {
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "活跃连接数",
Value: match[1],
})
@@ -159,15 +153,15 @@ func (r *OpenRestyController) Load(ctx http.Context) http.Response {
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
if len(match) == 4 {
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "总连接次数",
Value: match[1],
})
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "总握手次数",
Value: match[2],
})
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "总请求次数",
Value: match[3],
})
@@ -175,15 +169,15 @@ func (r *OpenRestyController) Load(ctx http.Context) http.Response {
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
if len(match) == 4 {
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "请求数",
Value: match[1],
})
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "响应数",
Value: match[2],
})
data = append(data, nginxStatus{
data = append(data, types.NV{
Name: "驻留进程",
Value: match[3],
})
+144 -65
View File
@@ -1,65 +1,30 @@
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"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/internal/services"
)
type PHPController struct {
service internal.PHP
}
func NewPHPController(version uint) *PHPController {
return &PHPController{
service: services.NewPHPImpl(version),
}
}
func (r *PHPController) Status(ctx http.Context) http.Response {
status, err := r.service.Status()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, status)
}
func (r *PHPController) Reload(ctx http.Context) http.Response {
if err := r.service.Reload(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Start(ctx http.Context) http.Response {
if err := r.service.Start(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Stop(ctx http.Context) http.Response {
if err := r.service.Stop(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Restart(ctx http.Context) http.Response {
if err := r.service.Restart(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
type PHPController struct{}
func NewPHPController() *PHPController {
return &PHPController{}
}
// GetConfig
//
// @Summary 获取配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/config [get]
func (r *PHPController) GetConfig(ctx http.Context) http.Response {
config, err := r.service.GetConfig()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
config, err := service.GetConfig()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -67,17 +32,38 @@ func (r *PHPController) GetConfig(ctx http.Context) http.Response {
return controllers.Success(ctx, config)
}
// SaveConfig
//
// @Summary 保存配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param config body string true "配置"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/config [post]
func (r *PHPController) SaveConfig(ctx http.Context) http.Response {
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
config := ctx.Request().Input("config")
if err := r.service.SaveConfig(config); err != nil {
if err := service.SaveConfig(config); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// GetFPMConfig
//
// @Summary 获取 FPM 配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/fpmConfig [get]
func (r *PHPController) GetFPMConfig(ctx http.Context) http.Response {
config, err := r.service.GetFPMConfig()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
config, err := service.GetFPMConfig()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -85,17 +71,38 @@ func (r *PHPController) GetFPMConfig(ctx http.Context) http.Response {
return controllers.Success(ctx, config)
}
// SaveFPMConfig
//
// @Summary 保存 FPM 配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param config body string true "配置"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/fpmConfig [post]
func (r *PHPController) SaveFPMConfig(ctx http.Context) http.Response {
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
config := ctx.Request().Input("config")
if err := r.service.SaveFPMConfig(config); err != nil {
if err := service.SaveFPMConfig(config); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// Load
//
// @Summary 获取负载状态
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/load [get]
func (r *PHPController) Load(ctx http.Context) http.Response {
load, err := r.service.Load()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
load, err := service.Load()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -103,18 +110,48 @@ func (r *PHPController) Load(ctx http.Context) http.Response {
return controllers.Success(ctx, load)
}
// ErrorLog
//
// @Summary 获取错误日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/errorLog [get]
func (r *PHPController) ErrorLog(ctx http.Context) http.Response {
log, _ := r.service.GetErrorLog()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
log, _ := service.GetErrorLog()
return controllers.Success(ctx, log)
}
// SlowLog
//
// @Summary 获取慢日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/slowLog [get]
func (r *PHPController) SlowLog(ctx http.Context) http.Response {
log, _ := r.service.GetSlowLog()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
log, _ := service.GetSlowLog()
return controllers.Success(ctx, log)
}
// ClearErrorLog
//
// @Summary 清空错误日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/clearErrorLog [post]
func (r *PHPController) ClearErrorLog(ctx http.Context) http.Response {
err := r.service.ClearErrorLog()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
err := service.ClearErrorLog()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -122,8 +159,18 @@ func (r *PHPController) ClearErrorLog(ctx http.Context) http.Response {
return controllers.Success(ctx, nil)
}
// ClearSlowLog
//
// @Summary 清空慢日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/clearSlowLog [post]
func (r *PHPController) ClearSlowLog(ctx http.Context) http.Response {
err := r.service.ClearSlowLog()
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
err := service.ClearSlowLog()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -131,8 +178,18 @@ func (r *PHPController) ClearSlowLog(ctx http.Context) http.Response {
return controllers.Success(ctx, nil)
}
func (r *PHPController) GetExtensionList(ctx http.Context) http.Response {
extensions, err := r.service.GetExtensions()
// ExtensionList
//
// @Summary 获取扩展列表
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/extensions [get]
func (r *PHPController) ExtensionList(ctx http.Context) http.Response {
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
extensions, err := service.GetExtensions()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -140,26 +197,48 @@ func (r *PHPController) GetExtensionList(ctx http.Context) http.Response {
return controllers.Success(ctx, extensions)
}
// InstallExtension
//
// @Summary 安装扩展
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param slug query string true "slug"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/extensions [post]
func (r *PHPController) InstallExtension(ctx http.Context) http.Response {
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
if err := r.service.InstallExtension(slug); err != nil {
if err := service.InstallExtension(slug); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// UninstallExtension
//
// @Summary 卸载扩展
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param slug query string true "slug"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/extensions [delete]
func (r *PHPController) UninstallExtension(ctx http.Context) http.Response {
service := services.NewPHPImpl(uint(ctx.Request().RouteInt("version")))
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
if err := r.service.UninstallExtension(slug); err != nil {
if err := service.UninstallExtension(slug); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -1,7 +1,7 @@
package plugins
import (
"os"
"fmt"
"regexp"
"strings"
@@ -10,7 +10,10 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type PhpMyAdminController struct {
@@ -21,7 +24,7 @@ func NewPhpMyAdminController() *PhpMyAdminController {
}
func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
files, err := os.ReadDir("/www/server/phpmyadmin")
files, err := io.ReadDir("/www/server/phpmyadmin")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
}
@@ -36,7 +39,7 @@ func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
}
conf, err := tools.Read("/www/server/vhost/phpmyadmin.conf")
conf, err := io.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -52,41 +55,72 @@ func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
}
func (r *PhpMyAdminController) SetPort(ctx http.Context) http.Response {
port := ctx.Request().Input("port")
if len(port) == 0 {
port := ctx.Request().InputInt("port")
if port == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "端口不能为空")
}
conf, err := tools.Read("/www/server/vhost/phpmyadmin.conf")
conf, err := io.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+port+";")
if err := tools.Write("/www/server/vhost/phpmyadmin.conf", conf, 0644); err != nil {
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(port)+";")
if err := io.Write("/www/server/vhost/phpmyadmin.conf", conf, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("插件", "phpMyAdmin").With(map[string]any{
"error": err.Error(),
}).Info("修改 phpMyAdmin 端口失败")
return controllers.ErrorSystem(ctx)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --zone=public --add-port=%d/tcp --permanent", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw allow " + port + "/tcp"); err != nil {
if out, err := shell.Execf("ufw allow %d/tcp", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err := tools.ServiceReload("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
if err = systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
}
return controllers.Success(ctx, nil)
}
func (r *PhpMyAdminController) GetConfig(ctx http.Context) http.Response {
config, err := io.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, config)
}
func (r *PhpMyAdminController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "配置不能为空")
}
if err := io.Write("/www/server/vhost/phpmyadmin.conf", config, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("插件", "phpMyAdmin").With(map[string]any{
"error": err.Error(),
}).Info("修改 phpMyAdmin 配置失败")
return controllers.ErrorSystem(ctx)
}
if err := systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
}
return controllers.Success(ctx, nil)
+10 -140
View File
@@ -5,7 +5,8 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/podman"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type PodmanController struct {
@@ -15,137 +16,6 @@ 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 获取注册表配置
@@ -156,7 +26,7 @@ func (r *PodmanController) Stop(ctx http.Context) http.Response {
// @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")
config, err := io.Read("/etc/containers/registries.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -176,16 +46,16 @@ func (r *PodmanController) GetRegistryConfig(ctx http.Context) http.Response {
// @Router /plugins/podman/registryConfig [post]
func (r *PodmanController) UpdateRegistryConfig(ctx http.Context) http.Response {
var updateRequest requests.UpdateRegistryConfig
sanitize := controllers.Sanitize(ctx, &updateRequest)
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
if err := tools.Write("/etc/containers/registries.conf", updateRequest.Config, 0644); err != nil {
if err := io.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 {
if err := systemctl.Restart("podman"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -202,7 +72,7 @@ func (r *PodmanController) UpdateRegistryConfig(ctx http.Context) http.Response
// @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")
config, err := io.Read("/etc/containers/storage.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -222,16 +92,16 @@ func (r *PodmanController) GetStorageConfig(ctx http.Context) http.Response {
// @Router /plugins/podman/storageConfig [post]
func (r *PodmanController) UpdateStorageConfig(ctx http.Context) http.Response {
var updateRequest requests.UpdateStorageConfig
sanitize := controllers.Sanitize(ctx, &updateRequest)
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
if err := tools.Write("/etc/containers/storage.conf", updateRequest.Config, 0644); err != nil {
if err := io.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 {
if err := systemctl.Restart("podman"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -1,553 +0,0 @@
package plugins
import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
"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 {
setting internal.Setting
backup internal.Backup
}
func NewPostgresql15Controller() *Postgresql15Controller {
return &Postgresql15Controller{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// Status 获取运行状态
func (r *Postgresql15Controller) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Postgresql15Controller) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Postgresql15Controller) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Postgresql15Controller) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Postgresql15Controller) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Postgresql15Controller) GetConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/postgresql/data/postgresql.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// GetUserConfig 获取用户配置
func (r *Postgresql15Controller) GetUserConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (r *Postgresql15Controller) 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/postgresql/data/postgresql.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
return r.Restart(ctx)
}
// SaveUserConfig 保存用户配置
func (r *Postgresql15Controller) SaveUserConfig(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/postgresql/data/pg_hba.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
return r.Restart(ctx)
}
// Load 获取负载
func (r *Postgresql15Controller) Load(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
time, err := tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
}
pid, err := tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
}
process, err := tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
}
connections, err := tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
}
storage, err := tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []types.NV{
{Name: "启动时间", Value: carbon.Parse(time).ToDateTimeString()},
{Name: "进程 PID", Value: pid},
{Name: "进程数", Value: process},
{Name: "总连接数", Value: connections},
{Name: "空间占用", Value: storage},
}
return controllers.Success(ctx, data)
}
// Log 获取日志
func (r *Postgresql15Controller) Log(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (r *Postgresql15Controller) ClearLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Postgresql15Controller) DatabaseList(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
type database struct {
Name string `json:"name"`
Owner string `json:"owner"`
Encoding string `json:"encoding"`
}
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
databases = databases[3 : len(databases)-1]
} else {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
var databaseList []database
for _, db := range databases {
parts := strings.Split(db, "|")
if len(parts) != 8 || len(strings.TrimSpace(parts[0])) == 0 {
continue
}
databaseList = append(databaseList, database{
Name: strings.TrimSpace(parts[0]),
Owner: strings.TrimSpace(parts[1]),
Encoding: strings.TrimSpace(parts[2]),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databaseList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if endIndex > len(databaseList) {
endIndex = len(databaseList)
}
pagedDatabases := databaseList[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(databaseList),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
func (r *Postgresql15Controller) 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())
}
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteDatabase 删除数据库
func (r *Postgresql15Controller) 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:postgres,template0,template1",
})
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")
if out, err := tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.PostgresqlList()
if 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(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
return controllers.Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
})
}
// UploadBackup 上传备份
func (r *Postgresql15Controller) 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) + "/postgresql"
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 *Postgresql15Controller) 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.PostgresqlBackup(database)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *Postgresql15Controller) 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) + "/postgresql"
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 *Postgresql15Controller) 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.PostgresqlRestore(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 *Postgresql15Controller) UserList(ctx http.Context) http.Response {
type user struct {
User string `json:"user"`
Role string `json:"role"`
}
raw, err := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
users := strings.Split(raw, "\n")
if len(users) < 4 {
return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空")
}
users = users[3:]
var userList []user
for _, u := range users {
userInfo := strings.Split(u, "|")
if len(userInfo) != 3 {
continue
}
userList = append(userList, user{
User: strings.TrimSpace(userInfo[0]),
Role: strings.TrimSpace(userInfo[1]),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
}
if endIndex > len(userList) {
endIndex = len(userList)
}
pagedUsers := userList[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(userList),
"items": pagedUsers,
})
}
// AddUser 添加用户
func (r *Postgresql15Controller) 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())
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteUser 删除用户
func (r *Postgresql15Controller) 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())
}
user := ctx.Request().Input("user")
if out, err := tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// SetUserPassword 设置用户密码
func (r *Postgresql15Controller) 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())
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
@@ -1,553 +0,0 @@
package plugins
import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
"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 {
setting internal.Setting
backup internal.Backup
}
func NewPostgresql16Controller() *Postgresql16Controller {
return &Postgresql16Controller{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// Status 获取运行状态
func (r *Postgresql16Controller) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Postgresql16Controller) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Postgresql16Controller) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Postgresql16Controller) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Postgresql16Controller) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PostgreSQL失败")
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Postgresql16Controller) GetConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/postgresql/data/postgresql.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// GetUserConfig 获取用户配置
func (r *Postgresql16Controller) GetUserConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (r *Postgresql16Controller) 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/postgresql/data/postgresql.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
return r.Restart(ctx)
}
// SaveUserConfig 保存用户配置
func (r *Postgresql16Controller) SaveUserConfig(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/postgresql/data/pg_hba.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
return r.Restart(ctx)
}
// Load 获取负载
func (r *Postgresql16Controller) Load(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
time, err := tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
}
pid, err := tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
}
process, err := tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
}
connections, err := tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
}
storage, err := tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []types.NV{
{Name: "启动时间", Value: carbon.Parse(time).ToDateTimeString()},
{Name: "进程 PID", Value: pid},
{Name: "进程数", Value: process},
{Name: "总连接数", Value: connections},
{Name: "空间占用", Value: storage},
}
return controllers.Success(ctx, data)
}
// Log 获取日志
func (r *Postgresql16Controller) Log(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (r *Postgresql16Controller) ClearLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
type database struct {
Name string `json:"name"`
Owner string `json:"owner"`
Encoding string `json:"encoding"`
}
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
databases = databases[3 : len(databases)-1]
} else {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
var databaseList []database
for _, db := range databases {
parts := strings.Split(db, "|")
if len(parts) != 9 || len(strings.TrimSpace(parts[0])) == 0 {
continue
}
databaseList = append(databaseList, database{
Name: strings.TrimSpace(parts[0]),
Owner: strings.TrimSpace(parts[1]),
Encoding: strings.TrimSpace(parts[2]),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databaseList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if endIndex > len(databaseList) {
endIndex = len(databaseList)
}
pagedDatabases := databaseList[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(databaseList),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
func (r *Postgresql16Controller) 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())
}
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteDatabase 删除数据库
func (r *Postgresql16Controller) 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:postgres,template0,template1",
})
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")
if out, err := tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.PostgresqlList()
if 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(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
return controllers.Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
})
}
// UploadBackup 上传备份
func (r *Postgresql16Controller) 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) + "/postgresql"
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 *Postgresql16Controller) 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.PostgresqlBackup(database)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *Postgresql16Controller) 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) + "/postgresql"
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 *Postgresql16Controller) 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.PostgresqlRestore(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 *Postgresql16Controller) UserList(ctx http.Context) http.Response {
type user struct {
User string `json:"user"`
Role string `json:"role"`
}
raw, err := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
users := strings.Split(raw, "\n")
if len(users) < 4 {
return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空")
}
users = users[3:]
var userList []user
for _, u := range users {
userInfo := strings.Split(u, "|")
if len(userInfo) != 2 {
continue
}
userList = append(userList, user{
User: strings.TrimSpace(userInfo[0]),
Role: strings.TrimSpace(userInfo[1]),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
}
if endIndex > len(userList) {
endIndex = len(userList)
}
pagedUsers := userList[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(userList),
"items": pagedUsers,
})
}
// AddUser 添加用户
func (r *Postgresql16Controller) 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())
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteUser 删除用户
func (r *Postgresql16Controller) 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())
}
user := ctx.Request().Input("user")
if out, err := tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// SetUserPassword 设置用户密码
func (r *Postgresql16Controller) 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())
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
@@ -0,0 +1,491 @@
package plugins
import (
"database/sql"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
_ "github.com/lib/pq"
"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/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type PostgreSQLController struct {
setting internal.Setting
backup internal.Backup
}
func NewPostgreSQLController() *PostgreSQLController {
return &PostgreSQLController{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// GetConfig 获取配置
func (r *PostgreSQLController) GetConfig(ctx http.Context) http.Response {
// 获取配置
config, err := io.Read("/www/server/postgresql/data/postgresql.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// GetUserConfig 获取用户配置
func (r *PostgreSQLController) GetUserConfig(ctx http.Context) http.Response {
// 获取配置
config, err := io.Read("/www/server/postgresql/data/pg_hba.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (r *PostgreSQLController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := io.Write("/www/server/postgresql/data/postgresql.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
return controllers.Success(ctx, nil)
}
// SaveUserConfig 保存用户配置
func (r *PostgreSQLController) SaveUserConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := io.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
return controllers.Success(ctx, nil)
}
// Load 获取负载
func (r *PostgreSQLController) Load(ctx http.Context) http.Response {
status, _ := systemctl.Status("postgresql")
if !status {
return controllers.Success(ctx, []types.NV{})
}
time, err := shell.Execf(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
}
pid, err := shell.Execf(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
}
process, err := shell.Execf(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
}
connections, err := shell.Execf(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
}
storage, err := shell.Execf(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []types.NV{
{Name: "启动时间", Value: carbon.Parse(time).ToDateTimeString()},
{Name: "进程 PID", Value: pid},
{Name: "进程数", Value: process},
{Name: "总连接数", Value: connections},
{Name: "空间占用", Value: storage},
}
return controllers.Success(ctx, data)
}
// Log 获取日志
func (r *PostgreSQLController) Log(ctx http.Context) http.Response {
log, err := shell.Execf("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (r *PostgreSQLController) ClearLog(ctx http.Context) http.Response {
if out, err := shell.Execf("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *PostgreSQLController) DatabaseList(ctx http.Context) http.Response {
type database struct {
Name string `json:"name"`
Owner string `json:"owner"`
Encoding string `json:"encoding"`
}
db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres dbname=postgres sslmode=disable")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if err = db.Ping(); err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
query := `
SELECT d.datname, pg_catalog.pg_get_userbyid(d.datdba), pg_catalog.pg_encoding_to_char(d.encoding)
FROM pg_catalog.pg_database d
WHERE datistemplate = false;
`
rows, err := db.Query(query)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
var databases []database
for rows.Next() {
var db database
if err := rows.Scan(&db.Name, &db.Owner, &db.Encoding); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
databases = append(databases, db)
}
if err = rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
paged, total := controllers.Paginate(ctx, databases)
return controllers.Success(ctx, http.Json{
"total": total,
"items": paged,
})
}
// AddDatabase 添加数据库
func (r *PostgreSQLController) AddDatabase(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:40",
}); sanitize != nil {
return sanitize
}
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := shell.Execf(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := shell.Execf(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := shell.Execf(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := shell.Execf(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
return controllers.Success(ctx, nil)
}
// DeleteDatabase 删除数据库
func (r *PostgreSQLController) DeleteDatabase(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
}); sanitize != nil {
return sanitize
}
database := ctx.Request().Input("database")
if out, err := shell.Execf(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *PostgreSQLController) BackupList(ctx http.Context) http.Response {
backups, err := r.backup.PostgresqlList()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
}
paged, total := controllers.Paginate(ctx, backups)
return controllers.Success(ctx, http.Json{
"total": total,
"items": paged,
})
}
// UploadBackup 上传备份
func (r *PostgreSQLController) 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) + "/postgresql"
if !io.Exists(backupPath) {
if err = io.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 *PostgreSQLController) CreateBackup(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
}); sanitize != nil {
return sanitize
}
database := ctx.Request().Input("database")
if err := r.backup.PostgresqlBackup(database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *PostgreSQLController) DeleteBackup(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"name": "required|min_len:1|max_len:255",
}); sanitize != nil {
return sanitize
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
fileName := ctx.Request().Input("name")
if err := io.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *PostgreSQLController) RestoreBackup(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"backup": "required|min_len:1|max_len:255",
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
}); sanitize != nil {
return sanitize
}
if err := r.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup")); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
}
return controllers.Success(ctx, nil)
}
// RoleList 角色列表
func (r *PostgreSQLController) RoleList(ctx http.Context) http.Response {
type role struct {
Role string `json:"role"`
Attributes []string `json:"attributes"`
}
db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres dbname=postgres sslmode=disable")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []role{},
})
}
if err = db.Ping(); err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []role{},
})
}
query := `
SELECT rolname,
rolsuper,
rolcreaterole,
rolcreatedb,
rolreplication,
rolbypassrls
FROM pg_roles
WHERE rolcanlogin = true;
`
rows, err := db.Query(query)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
var roles []role
for rows.Next() {
var r role
var super, canCreateRole, canCreateDb, replication, bypassRls bool
if err = rows.Scan(&r.Role, &super, &canCreateRole, &canCreateDb, &replication, &bypassRls); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
permissions := map[string]bool{
"超级用户": super,
"创建角色": canCreateRole,
"创建数据库": canCreateDb,
"可以复制": replication,
"绕过行级安全": bypassRls,
}
for perm, enabled := range permissions {
if enabled {
r.Attributes = append(r.Attributes, perm)
}
}
if len(r.Attributes) == 0 {
r.Attributes = append(r.Attributes, "无")
}
roles = append(roles, r)
}
if err = rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
paged, total := controllers.Paginate(ctx, roles)
return controllers.Success(ctx, http.Json{
"total": total,
"items": paged,
})
}
// AddRole 添加角色
func (r *PostgreSQLController) AddRole(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:40",
}); sanitize != nil {
return sanitize
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
if out, err := shell.Execf(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := shell.Execf(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
return controllers.Success(ctx, nil)
}
// DeleteRole 删除角色
func (r *PostgreSQLController) DeleteRole(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
}); sanitize != nil {
return sanitize
}
user := ctx.Request().Input("user")
if out, err := shell.Execf(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := shell.Execf(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
return controllers.Success(ctx, nil)
}
// SetRolePassword 设置用户密码
func (r *PostgreSQLController) SetRolePassword(ctx http.Context) http.Response {
if sanitize := controllers.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:40",
}); sanitize != nil {
return sanitize
}
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := shell.Execf(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
@@ -8,118 +8,60 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type PureFtpdController struct {
}
type User struct {
Username string `json:"username"`
Path string `json:"path"`
}
func NewPureFtpdController() *PureFtpdController {
return &PureFtpdController{}
}
// Status 获取运行状态
func (r *PureFtpdController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
}
return controllers.Success(ctx, status)
}
// Restart 重启服务
func (r *PureFtpdController) Restart(ctx http.Context) http.Response {
err := tools.ServiceRestart("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PureFtpd失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *PureFtpdController) Start(ctx http.Context) http.Response {
err := tools.ServiceStart("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PureFtpd失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *PureFtpdController) Stop(ctx http.Context) http.Response {
err := tools.ServiceStop("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PureFtpd失败")
}
return controllers.Success(ctx, nil)
}
// List 获取用户列表
func (r *PureFtpdController) List(ctx http.Context) http.Response {
listRaw, err := tools.Exec("pure-pw list")
listRaw, err := shell.Execf("pure-pw list")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []User{},
"items": []types.PureFtpdUser{},
})
}
listArr := strings.Split(listRaw, "\n")
var users []User
var users []types.PureFtpdUser
for _, v := range listArr {
if len(v) == 0 {
continue
}
match := regexp.MustCompile(`(\S+)\s+(\S+)`).FindStringSubmatch(v)
users = append(users, User{
users = append(users, types.PureFtpdUser{
Username: match[1],
Path: strings.Replace(match[2], "/./", "/", 1),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(users) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []User{},
})
}
if endIndex > len(users) {
endIndex = len(users)
}
pagedUsers := users[startIndex:endIndex]
paged, total := controllers.Paginate(ctx, users)
return controllers.Success(ctx, http.Json{
"total": len(users),
"items": pagedUsers,
"total": total,
"items": paged,
})
}
// Add 添加用户
func (r *PureFtpdController) Add(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"username": "required",
"password": "required|min_len:6",
"path": "required",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
username := ctx.Request().Input("username")
@@ -129,20 +71,20 @@ func (r *PureFtpdController) Add(ctx http.Context) http.Response {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if !tools.Exists(path) {
if !io.Exists(path) {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "目录不存在")
}
if err = tools.Chmod(path, 0755); err != nil {
if err := io.Chmod(path, 0755); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "修改目录权限失败")
}
if err = tools.Chown(path, "www", "www"); err != nil {
if err := io.Chown(path, "www", "www"); err != nil {
return nil
}
if out, err := tools.Exec(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path); err != nil {
if out, err := shell.Execf(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
if out, err := shell.Execf("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -151,22 +93,18 @@ func (r *PureFtpdController) Add(ctx http.Context) http.Response {
// Delete 删除用户
func (r *PureFtpdController) Delete(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"username": "required",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
username := ctx.Request().Input("username")
if out, err := tools.Exec("pure-pw userdel " + username + " -m"); err != nil {
if out, err := shell.Execf("pure-pw userdel " + username + " -m"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
if out, err := shell.Execf("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -175,24 +113,20 @@ func (r *PureFtpdController) Delete(ctx http.Context) http.Response {
// ChangePassword 修改密码
func (r *PureFtpdController) ChangePassword(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"username": "required",
"password": "required|min_len:6",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
username := ctx.Request().Input("username")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`); err != nil {
if out, err := shell.Execf(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
if out, err := shell.Execf("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -201,7 +135,7 @@ func (r *PureFtpdController) ChangePassword(ctx http.Context) http.Response {
// GetPort 获取端口
func (r *PureFtpdController) GetPort(ctx http.Context) http.Response {
port, err := tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
port, err := shell.Execf(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd端口失败")
}
@@ -211,35 +145,35 @@ func (r *PureFtpdController) GetPort(ctx http.Context) http.Response {
// SetPort 设置端口
func (r *PureFtpdController) SetPort(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"port": "required",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
port := ctx.Request().Input("port")
if out, err := tools.Exec(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`); err != nil {
if out, err := shell.Execf(`sed -i "s/Bind.*/Bind 0.0.0.0,%s/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`, port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --zone=public --add-port=%s/tcp --permanent", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw allow " + port + "/tcp"); err != nil {
if out, err := shell.Execf("ufw allow %s/tcp", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
return r.Restart(ctx)
if err := systemctl.Restart("pure-ftpd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
@@ -6,8 +6,10 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type RedisController struct {
@@ -17,47 +19,10 @@ func NewRedisController() *RedisController {
return &RedisController{}
}
// Status 获取运行状态
func (r *RedisController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("redis")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
return controllers.Success(ctx, status)
}
// Restart 重启服务
func (r *RedisController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启Redis失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *RedisController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动Redis失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *RedisController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止Redis失败")
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *RedisController) GetConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/redis/redis.conf")
config, err := io.Read("/www/server/redis/redis.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis配置失败")
}
@@ -72,16 +37,20 @@ func (r *RedisController) SaveConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/redis/redis.conf", config, 0644); err != nil {
if err := io.Write("/www/server/redis/redis.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Redis配置失败")
}
return r.Restart(ctx)
if err := systemctl.Restart("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启Redis失败")
}
return controllers.Success(ctx, nil)
}
// Load 获取负载
func (r *RedisController) Load(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("redis")
status, err := systemctl.Status("redis")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
@@ -89,7 +58,7 @@ func (r *RedisController) Load(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "Redis已停止运行")
}
raw, err := tools.Exec("redis-cli info")
raw, err := shell.Execf("redis-cli info")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis负载失败")
}
+31 -122
View File
@@ -7,10 +7,12 @@ import (
"github.com/goravel/framework/contracts/http"
"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"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type RsyncController struct {
@@ -20,79 +22,6 @@ func NewRsyncController() *RsyncController {
return &RsyncController{}
}
// Status
//
// @Summary 服务状态
// @Description 获取 Rsync 服务状态
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/status [get]
func (r *RsyncController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("rsyncd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
return controllers.Success(ctx, status)
}
// Restart
//
// @Summary 重启服务
// @Description 重启 Rsync 服务
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/restart [post]
func (r *RsyncController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启服务失败")
}
return controllers.Success(ctx, nil)
}
// Start
//
// @Summary 启动服务
// @Description 启动 Rsync 服务
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/start [post]
func (r *RsyncController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动服务失败")
}
return controllers.Success(ctx, nil)
}
// Stop
//
// @Summary 停止服务
// @Description 停止 Rsync 服务
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/stop [post]
func (r *RsyncController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("rsyncd"); err != nil {
return nil
}
status, err := tools.ServiceStatus("rsyncd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
return controllers.Success(ctx, !status)
}
// List
//
// @Summary 列出模块
@@ -104,13 +33,7 @@ func (r *RsyncController) Stop(ctx http.Context) http.Response {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules [get]
func (r *RsyncController) List(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := controllers.Sanitize(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -149,7 +72,7 @@ func (r *RsyncController) List(ctx http.Context) http.Response {
currentModule.ReadOnly = value == "yes" || value == "true"
case "auth users":
currentModule.AuthUser = value
currentModule.Secret, err = tools.Exec("grep -E '^" + currentModule.AuthUser + ":.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'")
currentModule.Secret, err = shell.Execf("grep -E '^" + currentModule.AuthUser + ":.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取模块"+currentModule.AuthUser+"的密钥失败")
}
@@ -164,25 +87,11 @@ func (r *RsyncController) List(ctx http.Context) http.Response {
modules = append(modules, *currentModule)
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(modules) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.RsyncModule{},
})
}
if endIndex > len(modules) {
endIndex = len(modules)
}
pagedModules := modules[startIndex:endIndex]
if pagedModules == nil {
pagedModules = []types.RsyncModule{}
}
paged, total := controllers.Paginate(ctx, modules)
return controllers.Success(ctx, http.Json{
"total": len(modules),
"items": pagedModules,
"total": total,
"items": paged,
})
}
@@ -198,12 +107,12 @@ func (r *RsyncController) List(ctx http.Context) http.Response {
// @Router /plugins/rsync/modules [post]
func (r *RsyncController) Create(ctx http.Context) http.Response {
var createRequest requests.Create
sanitize := controllers.Sanitize(ctx, &createRequest)
sanitize := controllers.SanitizeRequest(ctx, &createRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -222,14 +131,14 @@ secrets file = /etc/rsyncd.secrets
# ` + createRequest.Name + `-END
`
if err := tools.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
if err := io.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("echo '" + createRequest.AuthUser + ":" + createRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("echo '" + createRequest.AuthUser + ":" + createRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := tools.ServiceRestart("rsyncd"); err != nil {
if err := systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -252,7 +161,7 @@ func (r *RsyncController) Destroy(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "name 不能为空")
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -260,22 +169,22 @@ func (r *RsyncController) Destroy(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+name+" 不存在")
}
module := tools.Cut(config, "# "+name+"-START", "# "+name+"-END")
module := str.Cut(config, "# "+name+"-START", "# "+name+"-END")
config = strings.Replace(config, "\n# "+name+"-START"+module+"# "+name+"-END", "", -1)
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
if len(match) == 2 {
authUser := match[1]
if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil {
if err = io.Write("/etc/rsyncd.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.ServiceRestart("rsyncd"); err != nil {
if err = systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -295,12 +204,12 @@ func (r *RsyncController) Destroy(ctx http.Context) http.Response {
// @Router /plugins/rsync/modules/{name} [post]
func (r *RsyncController) Update(ctx http.Context) http.Response {
var updateRequest requests.Update
sanitize := controllers.Sanitize(ctx, &updateRequest)
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -318,25 +227,25 @@ hosts allow = ` + updateRequest.HostsAllow + `
secrets file = /etc/rsyncd.secrets
# ` + updateRequest.Name + `-END`
module := tools.Cut(config, "# "+updateRequest.Name+"-START", "# "+updateRequest.Name+"-END")
module := str.Cut(config, "# "+updateRequest.Name+"-START", "# "+updateRequest.Name+"-END")
config = strings.Replace(config, "# "+updateRequest.Name+"-START"+module+"# "+updateRequest.Name+"-END", newConf, -1)
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
if len(match) == 2 {
authUser := match[1]
if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil {
if err = io.Write("/etc/rsyncd.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("echo '" + updateRequest.AuthUser + ":" + updateRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("echo '" + updateRequest.AuthUser + ":" + updateRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err = tools.ServiceRestart("rsyncd"); err != nil {
if err = systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -353,7 +262,7 @@ secrets file = /etc/rsyncd.secrets
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/config [get]
func (r *RsyncController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -373,16 +282,16 @@ func (r *RsyncController) GetConfig(ctx http.Context) http.Response {
// @Router /plugins/rsync/config [post]
func (r *RsyncController) UpdateConfig(ctx http.Context) http.Response {
var updateRequest requests.UpdateConfig
sanitize := controllers.Sanitize(ctx, &updateRequest)
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
if err := tools.Write("/etc/rsyncd.conf", updateRequest.Config, 0644); err != nil {
if err := io.Write("/etc/rsyncd.conf", updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart("rsyncd"); err != nil {
if err := systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
+24 -45
View File
@@ -11,8 +11,9 @@ import (
"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"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
)
type S3fsController struct {
@@ -27,51 +28,30 @@ func NewS3fsController() *S3fsController {
// List 所有 S3fs 挂载
func (r *S3fsController) List(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
var s3fsList []types.S3fsMount
err := json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(s3fsList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.S3fsMount{},
})
}
if endIndex > len(s3fsList) {
endIndex = len(s3fsList)
}
pagedS3fsList := s3fsList[startIndex:endIndex]
if pagedS3fsList == nil {
pagedS3fsList = []types.S3fsMount{}
}
paged, total := controllers.Paginate(ctx, s3fsList)
return controllers.Success(ctx, http.Json{
"total": len(s3fsList),
"items": pagedS3fsList,
"total": total,
"items": paged,
})
}
// Add 添加 S3fs 挂载
func (r *S3fsController) Add(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"ak": "required|regex:^[a-zA-Z0-9]*$",
"sk": "required|regex:^[a-zA-Z0-9]*$",
"bucket": "required|regex:^[a-zA-Z0-9_-]*$",
"url": "required|full_url",
"path": "required|regex:^/[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())
}); sanitize != nil {
return sanitize
}
ak := ctx.Request().Input("ak")
@@ -86,18 +66,17 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
}
// 检查挂载目录是否存在且为空
if !tools.Exists(path) {
if err = tools.Mkdir(path, 0755); err != nil {
if !io.Exists(path) {
if err := io.Mkdir(path, 0755); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录创建失败")
}
}
if !tools.Empty(path) {
if !io.Empty(path) {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录必须为空")
}
var s3fsList []types.S3fsMount
err = json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
if err := json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
}
@@ -109,19 +88,19 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
id := carbon.Now().TimestampMilli()
password := ak + ":" + sk
if err = tools.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
if err := io.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
return nil
}
out, err := tools.Exec(`echo 's3fs#` + bucket + ` ` + path + ` fuse _netdev,allow_other,nonempty,url=` + url + `,passwd_file=/etc/passwd-s3fs-` + cast.ToString(id) + ` 0 0' >> /etc/fstab`)
out, err := shell.Execf(`echo 's3fs#` + bucket + ` ` + path + ` fuse _netdev,allow_other,nonempty,url=` + url + `,passwd_file=/etc/passwd-s3fs-` + cast.ToString(id) + ` 0 0' >> /etc/fstab`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if mountCheck, err := tools.Exec("mount -a 2>&1"); err != nil {
_, _ = tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
if mountCheck, err := shell.Execf("mount -a 2>&1"); err != nil {
_, _ = shell.Execf(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
return controllers.Error(ctx, http.StatusInternalServerError, "检测到/etc/fstab有误: "+mountCheck)
}
if _, err := tools.Exec("df -h | grep " + path + " 2>&1"); err != nil {
_, _ = tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
if _, err := shell.Execf("df -h | grep " + path + " 2>&1"); err != nil {
_, _ = shell.Execf(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
return controllers.Error(ctx, http.StatusInternalServerError, "挂载失败,请检查配置是否正确")
}
@@ -167,19 +146,19 @@ func (r *S3fsController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载ID不存在")
}
if out, err := tools.Exec(`fusermount -u '` + mount.Path + `' 2>&1`); err != nil {
if out, err := shell.Execf(`fusermount -u '` + mount.Path + `' 2>&1`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`umount '` + mount.Path + `' 2>&1`); err != nil {
if out, err := shell.Execf(`umount '` + mount.Path + `' 2>&1`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i 's@^s3fs#` + mount.Bucket + `\s` + mount.Path + `.*$@@g' /etc/fstab`); err != nil {
if out, err := shell.Execf(`sed -i 's@^s3fs#` + mount.Bucket + `\s` + mount.Path + `.*$@@g' /etc/fstab`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if mountCheck, err := tools.Exec("mount -a 2>&1"); err != nil {
if mountCheck, err := shell.Execf("mount -a 2>&1"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "检测到/etc/fstab有误: "+mountCheck)
}
if err := tools.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
if err := io.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -1,81 +1,44 @@
package plugins
import (
"fmt"
"strconv"
"strings"
"github.com/TheTNB/panel/pkg/tools"
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type SupervisorController struct {
ServiceName string
service string
}
func NewSupervisorController() *SupervisorController {
var serviceName string
if tools.IsRHEL() {
serviceName = "supervisord"
var service string
if os.IsRHEL() {
service = "supervisord"
} else {
serviceName = "supervisor"
service = "supervisor"
}
return &SupervisorController{
ServiceName: serviceName,
service: service,
}
}
// Status 状态
func (r *SupervisorController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus(r.ServiceName)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Supervisor状态失败")
}
return controllers.Success(ctx, status)
}
// Start 启动
func (r *SupervisorController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动Supervisor失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止
func (r *SupervisorController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止Supervisor失败")
}
return controllers.Success(ctx, nil)
}
// Restart 重启
func (r *SupervisorController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启Supervisor失败")
}
return controllers.Success(ctx, nil)
}
// Reload 重载
func (r *SupervisorController) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载Supervisor失败")
}
return controllers.Success(ctx, nil)
// Service 获取服务名称
func (r *SupervisorController) Service(ctx http.Context) http.Response {
return controllers.Success(ctx, r.service)
}
// Log 日志
func (r *SupervisorController) Log(ctx http.Context) http.Response {
log, err := tools.Exec(`tail -n 200 /var/log/supervisor/supervisord.log`)
log, err := shell.Execf(`tail -n 200 /var/log/supervisor/supervisord.log`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -85,7 +48,7 @@ func (r *SupervisorController) Log(ctx http.Context) http.Response {
// ClearLog 清空日志
func (r *SupervisorController) ClearLog(ctx http.Context) http.Response {
if out, err := tools.Exec(`echo "" > /var/log/supervisor/supervisord.log`); err != nil {
if out, err := shell.Execf(`echo "" > /var/log/supervisor/supervisord.log`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -96,10 +59,10 @@ func (r *SupervisorController) ClearLog(ctx http.Context) http.Response {
func (r *SupervisorController) Config(ctx http.Context) http.Response {
var config string
var err error
if tools.IsRHEL() {
config, err = tools.Read(`/etc/supervisord.conf`)
if os.IsRHEL() {
config, err = io.Read(`/etc/supervisord.conf`)
} else {
config, err = tools.Read(`/etc/supervisor/supervisord.conf`)
config, err = io.Read(`/etc/supervisor/supervisord.conf`)
}
if err != nil {
@@ -113,24 +76,25 @@ func (r *SupervisorController) Config(ctx http.Context) http.Response {
func (r *SupervisorController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
var err error
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.conf`, config, 0644)
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/supervisord.conf`, config, 0644)
err = io.Write(`/etc/supervisor/supervisord.conf`, config, 0644)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return r.Restart(ctx)
if err = systemctl.Restart(r.service); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重启 %s 服务失败", r.service))
}
return controllers.Success(ctx, nil)
}
// Processes 进程列表
func (r *SupervisorController) Processes(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
type process struct {
Name string `json:"name"`
Status string `json:"status"`
@@ -138,12 +102,12 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
Uptime string `json:"uptime"`
}
out, err := tools.Exec(`supervisorctl status | awk '{print $1}'`)
out, err := shell.Execf(`supervisorctl status | awk '{print $1}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
var processList []process
var processes []process
for _, line := range strings.Split(out, "\n") {
if len(line) == 0 {
continue
@@ -151,47 +115,33 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
var p process
p.Name = line
if status, err := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $2}'`); err == nil {
if status, err := shell.Execf(`supervisorctl status ` + line + ` | awk '{print $2}'`); err == nil {
p.Status = status
}
if p.Status == "RUNNING" {
pid, _ := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $4}'`)
pid, _ := shell.Execf(`supervisorctl status ` + line + ` | awk '{print $4}'`)
p.Pid = strings.ReplaceAll(pid, ",", "")
uptime, _ := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $6}'`)
uptime, _ := shell.Execf(`supervisorctl status ` + line + ` | awk '{print $6}'`)
p.Uptime = uptime
} else {
p.Pid = "-"
p.Uptime = "-"
}
processList = append(processList, p)
processes = append(processes, p)
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(processList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []process{},
})
}
if endIndex > len(processList) {
endIndex = len(processList)
}
pagedProcessList := processList[startIndex:endIndex]
if pagedProcessList == nil {
pagedProcessList = []process{}
}
paged, total := controllers.Paginate(ctx, processes)
return controllers.Success(ctx, http.Json{
"total": len(processList),
"items": pagedProcessList,
"total": total,
"items": paged,
})
}
// StartProcess 启动进程
func (r *SupervisorController) StartProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl start ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl start %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -201,7 +151,7 @@ func (r *SupervisorController) StartProcess(ctx http.Context) http.Response {
// StopProcess 停止进程
func (r *SupervisorController) StopProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl stop ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl stop %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -211,7 +161,7 @@ func (r *SupervisorController) StopProcess(ctx http.Context) http.Response {
// RestartProcess 重启进程
func (r *SupervisorController) RestartProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl restart ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl restart %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -223,17 +173,17 @@ func (r *SupervisorController) ProcessLog(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
var logPath string
var err error
if tools.IsRHEL() {
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
} else {
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
log, err := tools.Exec(`tail -n 200 ` + logPath)
log, err := shell.Execf(`tail -n 200 ` + logPath)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -246,17 +196,17 @@ func (r *SupervisorController) ClearProcessLog(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
var logPath string
var err error
if tools.IsRHEL() {
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
} else {
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("无法从进程%s的配置文件中获取日志路径", process))
}
if out, err := tools.Exec(`echo "" > ` + logPath); err != nil {
if out, err := shell.Execf(`echo "" > ` + logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -268,10 +218,10 @@ func (r *SupervisorController) ProcessConfig(ctx http.Context) http.Response {
process := ctx.Request().Query("process")
var config string
var err error
if tools.IsRHEL() {
config, err = tools.Read(`/etc/supervisord.d/` + process + `.conf`)
if os.IsRHEL() {
config, err = io.Read(`/etc/supervisord.d/` + process + `.conf`)
} else {
config, err = tools.Read(`/etc/supervisor/conf.d/` + process + `.conf`)
config, err = io.Read(`/etc/supervisor/conf.d/` + process + `.conf`)
}
if err != nil {
@@ -286,37 +236,33 @@ func (r *SupervisorController) SaveProcessConfig(ctx http.Context) http.Response
process := ctx.Request().Input("process")
config := ctx.Request().Input("config")
var err error
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
err = io.Write(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
}
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl restart ` + process)
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl restart %s`, process)
return controllers.Success(ctx, nil)
}
// AddProcess 添加进程
func (r *SupervisorController) AddProcess(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := controllers.Sanitize(ctx, map[string]string{
"name": "required|alpha_dash",
"user": "required|alpha_dash",
"path": "required",
"command": "required",
"num": "required",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
name := ctx.Request().Input("name")
@@ -336,19 +282,21 @@ redirect_stderr=true
stdout_logfile=/var/log/supervisor/` + name + `.log
stdout_logfile_maxbytes=2MB
`
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
var err error
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
err = io.Write(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
}
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl start ` + name)
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl start %s`, name)
return controllers.Success(ctx, nil)
}
@@ -356,20 +304,20 @@ stdout_logfile_maxbytes=2MB
// DeleteProcess 删除进程
func (r *SupervisorController) DeleteProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl stop ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl stop %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
var logPath string
var err error
if tools.IsRHEL() {
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if err := tools.Remove(`/etc/supervisord.d/` + process + `.conf`); err != nil {
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
if err := io.Remove(`/etc/supervisord.d/` + process + `.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
} else {
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if err := tools.Remove(`/etc/supervisor/conf.d/` + process + `.conf`); err != nil {
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
if err := io.Remove(`/etc/supervisor/conf.d/` + process + `.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
}
@@ -378,11 +326,11 @@ func (r *SupervisorController) DeleteProcess(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
if err := tools.Remove(logPath); err != nil {
if err := io.Remove(logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
return controllers.Success(ctx, nil)
}
@@ -8,7 +8,9 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type ToolBoxController struct {
@@ -20,7 +22,7 @@ func NewToolBoxController() *ToolBoxController {
// GetDNS 获取 DNS 信息
func (r *ToolBoxController) GetDNS(ctx http.Context) http.Response {
raw, err := tools.Read("/etc/resolv.conf")
raw, err := io.Read("/etc/resolv.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -50,7 +52,7 @@ func (r *ToolBoxController) SetDNS(ctx http.Context) http.Response {
dns += "nameserver " + dns1 + "\n"
dns += "nameserver " + dns2 + "\n"
if err := tools.Write("/etc/resolv.conf", dns, 0644); err != nil {
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入 DNS 信息失败")
}
@@ -61,28 +63,28 @@ func (r *ToolBoxController) SetDNS(ctx http.Context) http.Response {
func (r *ToolBoxController) GetSWAP(ctx http.Context) http.Response {
var total, used, free string
var size int64
if tools.Exists("/www/swap") {
file, err := tools.FileInfo("/www/swap")
if io.Exists("/www/swap") {
file, err := io.FileInfo("/www/swap")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 SWAP 信息失败")
}
size = file.Size() / 1024 / 1024
total = tools.FormatBytes(float64(file.Size()))
total = str.FormatBytes(float64(file.Size()))
} else {
size = 0
total = "0.00 B"
}
raw, err := tools.Exec("free | grep Swap")
raw, err := shell.Execf("free | grep Swap")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 SWAP 信息失败")
}
match := regexp.MustCompile(`Swap:\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
if len(match) > 0 {
used = tools.FormatBytes(cast.ToFloat64(match[2]) * 1024)
free = tools.FormatBytes(cast.ToFloat64(match[3]) * 1024)
used = str.FormatBytes(cast.ToFloat64(match[2]) * 1024)
free = str.FormatBytes(cast.ToFloat64(match[3]) * 1024)
}
return controllers.Success(ctx, http.Json{
@@ -97,47 +99,47 @@ func (r *ToolBoxController) GetSWAP(ctx http.Context) http.Response {
func (r *ToolBoxController) SetSWAP(ctx http.Context) http.Response {
size := ctx.Request().InputInt("size")
if tools.Exists("/www/swap") {
if out, err := tools.Exec("swapoff /www/swap"); err != nil {
if io.Exists("/www/swap") {
if out, err := shell.Execf("swapoff /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("rm -f /www/swap"); err != nil {
if out, err := shell.Execf("rm -f /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("sed -i '/www\\/swap/d' /etc/fstab"); err != nil {
if out, err := shell.Execf("sed -i '/www\\/swap/d' /etc/fstab"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
}
if size > 1 {
free, err := tools.Exec("df -k /www | awk '{print $4}' | tail -n 1")
free, err := shell.Execf("df -k /www | awk '{print $4}' | tail -n 1")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取磁盘空间失败")
}
if cast.ToInt64(free)*1024 < int64(size)*1024*1024 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "磁盘空间不足,当前剩余 "+tools.FormatBytes(cast.ToFloat64(free)))
return controllers.Error(ctx, http.StatusUnprocessableEntity, "磁盘空间不足,当前剩余 "+str.FormatBytes(cast.ToFloat64(free)))
}
btrfsCheck, _ := tools.Exec("df -T /www | awk '{print $2}' | tail -n 1")
btrfsCheck, _ := shell.Execf("df -T /www | awk '{print $2}' | tail -n 1")
if strings.Contains(btrfsCheck, "btrfs") {
if out, err := tools.Exec("btrfs filesystem mkswapfile --size " + cast.ToString(size) + "M --uuid clear /www/swap"); err != nil {
if out, err := shell.Execf("btrfs filesystem mkswapfile --size " + cast.ToString(size) + "M --uuid clear /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
} else {
if out, err := tools.Exec("dd if=/dev/zero of=/www/swap bs=1M count=" + cast.ToString(size)); err != nil {
if out, err := shell.Execf("dd if=/dev/zero of=/www/swap bs=1M count=" + cast.ToString(size)); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("mkswap -f /www/swap"); err != nil {
if out, err := shell.Execf("mkswap -f /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if err := tools.Chmod("/www/swap", 0600); err != nil {
if err := io.Chmod("/www/swap", 0600); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "设置 SWAP 权限失败")
}
}
if out, err := tools.Exec("swapon /www/swap"); err != nil {
if out, err := shell.Execf("swapon /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("echo '/www/swap swap swap defaults 0 0' >> /etc/fstab"); err != nil {
if out, err := shell.Execf("echo '/www/swap swap swap defaults 0 0' >> /etc/fstab"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
}
@@ -147,7 +149,7 @@ func (r *ToolBoxController) SetSWAP(ctx http.Context) http.Response {
// GetTimezone 获取时区
func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
raw, err := tools.Exec("timedatectl | grep zone")
raw, err := shell.Execf("timedatectl | grep zone")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取时区信息失败")
}
@@ -162,7 +164,7 @@ func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
Value string `json:"value"`
}
zonesRaw, err := tools.Exec("timedatectl list-timezones")
zonesRaw, err := shell.Execf("timedatectl list-timezones")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取时区列表失败")
}
@@ -189,7 +191,7 @@ func (r *ToolBoxController) SetTimezone(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "时区不能为空")
}
if out, err := tools.Exec("timedatectl set-timezone " + timezone); err != nil {
if out, err := shell.Execf("timedatectl set-timezone %s", timezone); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
@@ -198,7 +200,7 @@ func (r *ToolBoxController) SetTimezone(ctx http.Context) http.Response {
// GetHosts 获取 hosts 信息
func (r *ToolBoxController) GetHosts(ctx http.Context) http.Response {
hosts, err := tools.Read("/etc/hosts")
hosts, err := io.Read("/etc/hosts")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -213,7 +215,7 @@ func (r *ToolBoxController) SetHosts(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "hosts 信息不能为空")
}
if err := tools.Write("/etc/hosts", hosts, 0644); err != nil {
if err := io.Write("/etc/hosts", hosts, 0644); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "写入 hosts 信息失败")
}
@@ -231,7 +233,7 @@ func (r *ToolBoxController) SetRootPassword(ctx http.Context) http.Response {
}
password = strings.ReplaceAll(password, `'`, `\'`)
if out, err := tools.Exec(`yes '` + password + `' | passwd root`); err != nil {
if out, err := shell.Execf(`yes '` + password + `' | passwd root`); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
+75 -72
View File
@@ -4,11 +4,13 @@ import (
"regexp"
"strings"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
"github.com/goravel/framework/contracts/http"
"github.com/spf13/cast"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type SafeController struct {
@@ -17,7 +19,7 @@ type SafeController struct {
func NewSafeController() *SafeController {
var ssh string
if tools.IsRHEL() {
if os.IsRHEL() {
ssh = "sshd"
} else {
ssh = "ssh"
@@ -37,16 +39,34 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) http.Response {
func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
var err error
if ctx.Request().InputBool("status") {
if tools.IsRHEL() {
err = tools.ServiceStart("firewalld")
if os.IsRHEL() {
err = systemctl.Start("firewalld")
if err == nil {
err = systemctl.Enable("firewalld")
}
} else {
_, err = tools.Exec("echo y | ufw enable")
_, err = shell.Execf("echo y | ufw enable")
if err == nil {
err = systemctl.Start("ufw")
}
if err == nil {
err = systemctl.Enable("ufw")
}
}
} else {
if tools.IsRHEL() {
err = tools.ServiceStop("firewalld")
if os.IsRHEL() {
err = systemctl.Stop("firewalld")
if err == nil {
err = systemctl.Disable("firewalld")
}
} else {
_, err = tools.Exec("ufw disable")
_, err = shell.Execf("ufw disable")
if err == nil {
err = systemctl.Stop("ufw")
}
if err == nil {
err = systemctl.Disable("ufw")
}
}
}
@@ -59,19 +79,13 @@ func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
// GetFirewallRules 获取防火墙规则
func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
if !r.firewallStatus() {
return Success(ctx, nil)
}
var rules []map[string]string
if tools.IsRHEL() {
out, err := tools.Exec("firewall-cmd --list-all 2>&1")
if os.IsRHEL() {
out, err := shell.Execf("firewall-cmd --list-all")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -92,7 +106,7 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
})
}
} else {
out, err := tools.Exec("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
out, err := shell.Execf("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -112,22 +126,11 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
}
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(rules) {
return Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
paged, total := Paginate(ctx, rules)
return Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
"total": total,
"items": paged,
})
}
@@ -157,14 +160,14 @@ func (r *SafeController) AddFirewallRule(ctx http.Context) http.Response {
}
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --remove-port=%s/%s --permanent", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
if out, err := shell.Execf("firewall-cmd --add-port=%s/%s --permanent", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
@@ -172,13 +175,13 @@ func (r *SafeController) AddFirewallRule(ctx http.Context) http.Response {
if strings.Contains(port, "-") {
port = strings.ReplaceAll(port, "-", ":")
}
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol); err != nil {
if out, err := shell.Execf("ufw delete allow %s/%s", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw allow " + cast.ToString(port) + "/" + protocol); err != nil {
if out, err := shell.Execf("ufw allow %s/%s", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
@@ -192,24 +195,24 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "防火墙未启动")
}
port := ctx.Request().InputInt("port", 0)
protocol := ctx.Request().Input("protocol", "")
if port == 0 || protocol == "" {
port := ctx.Request().Input("port")
protocol := ctx.Request().Input("protocol")
if port == "" || protocol == "" {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --remove-port=%s/%s --permanent", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol); err != nil {
if out, err := shell.Execf("ufw delete allow %s/%s", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
@@ -220,10 +223,10 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
// firewallStatus 获取防火墙状态
func (r *SafeController) firewallStatus() bool {
var running bool
if tools.IsRHEL() {
running, _ = tools.ServiceStatus("firewalld")
if os.IsRHEL() {
running, _ = systemctl.Status("firewalld")
} else {
running, _ = tools.ServiceStatus("ufw")
running, _ = systemctl.Status("ufw")
}
return running
@@ -231,7 +234,7 @@ func (r *SafeController) firewallStatus() bool {
// GetSshStatus 获取 SSH 状态
func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
running, err := tools.ServiceStatus(r.ssh)
running, err := systemctl.Status(r.ssh)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -242,17 +245,17 @@ func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
// SetSshStatus 设置 SSH 状态
func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
if ctx.Request().InputBool("status") {
if err := tools.ServiceEnable(r.ssh); err != nil {
if err := systemctl.Enable(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceStart(r.ssh); err != nil {
if err := systemctl.Start(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
} else {
if err := tools.ServiceStop(r.ssh); err != nil {
if err := systemctl.Stop(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceDisable(r.ssh); err != nil {
if err := systemctl.Disable(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
@@ -262,7 +265,7 @@ func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
// GetSshPort 获取 SSH 端口
func (r *SafeController) GetSshPort(ctx http.Context) http.Response {
out, err := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
out, err := shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -277,16 +280,16 @@ func (r *SafeController) SetSshPort(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
oldPort, err := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
oldPort, err := shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, oldPort)
}
_, _ = 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")
_, _ = shell.Execf("sed -i 's/#Port %s/Port %d/g' /etc/ssh/sshd_config", oldPort, port)
_, _ = shell.Execf("sed -i 's/Port %s/Port %d/g' /etc/ssh/sshd_config", oldPort, port)
status, _ := tools.ServiceStatus(r.ssh)
status, _ := systemctl.Status(r.ssh)
if status {
_ = tools.ServiceRestart(r.ssh)
_ = systemctl.Restart(r.ssh)
}
return Success(ctx, nil)
@@ -294,8 +297,8 @@ func (r *SafeController) SetSshPort(ctx http.Context) http.Response {
// GetPingStatus 获取 Ping 状态
func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
if tools.IsRHEL() {
out, err := tools.Exec(`firewall-cmd --list-all 2>&1`)
if os.IsRHEL() {
out, err := shell.Execf(`firewall-cmd --list-all`)
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -306,7 +309,7 @@ func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
return Success(ctx, false)
}
} else {
config, err := tools.Read("/etc/ufw/before.rules")
config, err := io.Read("/etc/ufw/before.rules")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -322,17 +325,17 @@ func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
func (r *SafeController) SetPingStatus(ctx http.Context) http.Response {
var out string
var err error
if tools.IsRHEL() {
if os.IsRHEL() {
if ctx.Request().InputBool("status") {
out, err = tools.Exec(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
out, err = shell.Execf(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
} else {
out, err = tools.Exec(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
out, err = shell.Execf(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
}
} else {
if ctx.Request().InputBool("status") {
out, err = tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
out, err = shell.Execf(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
} else {
out, err = tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
out, err = shell.Execf(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
}
}
@@ -340,10 +343,10 @@ func (r *SafeController) SetPingStatus(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
out, err = tools.Exec(`firewall-cmd --reload`)
if os.IsRHEL() {
out, err = shell.Execf(`firewall-cmd --reload`)
} else {
out, err = tools.Exec(`ufw reload`)
out, err = shell.Execf(`ufw reload`)
}
if err != nil {
+118 -57
View File
@@ -3,12 +3,17 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/path"
"github.com/spf13/cast"
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/cert"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/tools"
)
@@ -24,13 +29,12 @@ func NewSettingController() *SettingController {
// List
//
// @Summary 设置列表
// @Description 获取面板设置列表
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/list [get]
// @Summary 设置列表
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/list [get]
func (r *SettingController) List(ctx http.Context) http.Response {
var settings []models.Setting
err := facades.Orm().Query().Get(&settings)
@@ -41,16 +45,13 @@ func (r *SettingController) List(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
userID := cast.ToUint(ctx.Value("user_id"))
var user models.User
err = facades.Auth(ctx).User(&user)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Info("获取用户信息失败")
return ErrorSystem(ctx)
if err = facades.Orm().Query().Where("id", userID).Get(&user); err != nil {
return Error(ctx, http.StatusInternalServerError, "获取用户信息失败")
}
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -61,7 +62,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"),
"entrance": facades.Config().GetString("panel.entrance"),
"ssl": facades.Config().GetBool("panel.ssl"),
"website_path": r.setting.Get(models.SettingKeyWebsitePath),
"backup_path": r.setting.Get(models.SettingKeyBackupPath),
@@ -74,18 +75,17 @@ func (r *SettingController) List(ctx http.Context) http.Response {
// Update
//
// @Summary 更新设置
// @Description 更新面板设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Update true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/update [post]
// @Summary 更新设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Update true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/update [post]
func (r *SettingController) Update(ctx http.Context) http.Response {
var updateRequest requests.Update
sanitize := Sanitize(ctx, &updateRequest)
sanitize := SanitizeRequest(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
@@ -98,8 +98,8 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
if !tools.Exists(updateRequest.BackupPath) {
if err = tools.Mkdir(updateRequest.BackupPath, 0644); err != nil {
if !io.Exists(updateRequest.BackupPath) {
if err = io.Mkdir(updateRequest.BackupPath, 0644); err != nil {
return ErrorSystem(ctx)
}
}
@@ -110,11 +110,11 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
}).Info("保存备份目录失败")
return ErrorSystem(ctx)
}
if !tools.Exists(updateRequest.WebsitePath) {
if err = tools.Mkdir(updateRequest.WebsitePath, 0755); err != nil {
if !io.Exists(updateRequest.WebsitePath) {
if err = io.Mkdir(updateRequest.WebsitePath, 0755); err != nil {
return ErrorSystem(ctx)
}
if err = tools.Chown(updateRequest.WebsitePath, "www", "www"); err != nil {
if err = io.Chown(updateRequest.WebsitePath, "www", "www"); err != nil {
return ErrorSystem(ctx)
}
}
@@ -126,11 +126,12 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
userID := cast.ToUint(ctx.Value("user_id"))
var user models.User
err = facades.Auth(ctx).User(&user)
if err != nil {
return ErrorSystem(ctx)
if err = facades.Orm().Query().Where("id", userID).Get(&user); err != nil {
return Error(ctx, http.StatusInternalServerError, "获取用户信息失败")
}
user.Username = updateRequest.UserName
user.Email = updateRequest.Email
if len(updateRequest.Password) > 0 {
@@ -147,7 +148,7 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
oldPort, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
oldPort, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -157,33 +158,33 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
port := cast.ToString(updateRequest.Port)
if oldPort != port {
if out, err := tools.Exec("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf"); err != nil {
if out, err := shell.Execf("sed -i 's/APP_PORT=%s/APP_PORT=%s/g' /www/panel/panel.conf", oldPort, port); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/tcp --permanent 2>&1"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --remove-port=%s/tcp --permanent", oldPort); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/tcp --permanent 2>&1"); err != nil {
if out, err := shell.Execf("firewall-cmd --add-port=%s/tcp --permanent", port); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/tcp"); err != nil {
if out, err := shell.Execf("ufw delete allow %s/tcp", oldPort); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw allow " + cast.ToString(port) + "/tcp"); err != nil {
if out, err := shell.Execf("ufw allow %s/tcp", port); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
}
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
oldEntrance, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -192,12 +193,12 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
}
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 {
if out, err := shell.Execf("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=" + entrance + "!g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
oldLanguage, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_LOCALE | awk -F '=' '{print $2}' | tr -d '\n'`)
oldLanguage, err := shell.Execf(`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(),
@@ -205,24 +206,84 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
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 {
if out, err := shell.Execf("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)
}
} else {
if out, err := tools.Exec("sed -i 's/APP_SSL=true/APP_SSL=false/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
if oldPort != port || oldEntrance != entrance || oldLanguage != updateRequest.Language || updateRequest.SSL != facades.Config().GetBool("panel.ssl") {
if oldPort != port || oldEntrance != entrance || oldLanguage != updateRequest.Language {
tools.RestartPanel()
}
return Success(ctx, nil)
}
// GetHttps
//
// @Summary 获取面板 HTTPS 设置
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/https [get]
func (r *SettingController) GetHttps(ctx http.Context) http.Response {
certPath := facades.Config().GetString("http.tls.ssl.cert")
keyPath := facades.Config().GetString("http.tls.ssl.key")
crt, err := io.Read(certPath)
if err != nil {
return ErrorSystem(ctx)
}
key, err := io.Read(keyPath)
if err != nil {
return ErrorSystem(ctx)
}
return Success(ctx, http.Json{
"https": facades.Config().GetBool("panel.ssl"),
"cert": crt,
"key": key,
})
}
// UpdateHttps
//
// @Summary 更新面板 HTTPS 设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Https true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/https [post]
func (r *SettingController) UpdateHttps(ctx http.Context) http.Response {
var httpsRequest requests.Https
sanitize := SanitizeRequest(ctx, &httpsRequest)
if sanitize != nil {
return sanitize
}
if httpsRequest.Https {
if _, err := cert.ParseCert(httpsRequest.Cert); err != nil {
return Error(ctx, http.StatusBadRequest, "证书格式错误")
}
if _, err := cert.ParseKey(httpsRequest.Key); err != nil {
return Error(ctx, http.StatusBadRequest, "密钥格式错误")
}
if err := io.Write(path.Executable("storage/ssl.crt"), httpsRequest.Cert, 0700); err != nil {
return ErrorSystem(ctx)
}
if err := io.Write(path.Executable("storage/ssl.key"), httpsRequest.Key, 0700); err != nil {
return ErrorSystem(ctx)
}
if out, err := shell.Execf("sed -i 's/APP_SSL=false/APP_SSL=true/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := shell.Execf("sed -i 's/APP_SSL=true/APP_SSL=false/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
tools.RestartPanel()
return Success(ctx, nil)
}
+7 -11
View File
@@ -50,33 +50,29 @@ func (r *SshController) GetInfo(ctx http.Context) http.Response {
// UpdateInfo 更新 SSH 配置
func (r *SshController) UpdateInfo(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
if sanitize := Sanitize(ctx, map[string]string{
"host": "required",
"port": "required",
"user": "required",
"password": "required",
})
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}); sanitize != nil {
return sanitize
}
host := ctx.Request().Input("host")
port := ctx.Request().Input("port")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if err = r.setting.Set(models.SettingKeySshHost, host); err != nil {
if err := r.setting.Set(models.SettingKeySshHost, host); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = r.setting.Set(models.SettingKeySshPort, port); err != nil {
if err := r.setting.Set(models.SettingKeySshPort, port); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = r.setting.Set(models.SettingKeySshUser, user); err != nil {
if err := r.setting.Set(models.SettingKeySshUser, user); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = r.setting.Set(models.SettingKeySshPassword, password); err != nil {
if err := r.setting.Set(models.SettingKeySshPassword, password); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -2,7 +2,6 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/swaggo/http-swagger/v2"
_ "github.com/TheTNB/panel/docs"
@@ -25,10 +24,6 @@ func NewSwaggerController() *SwaggerController {
// @Failure 500
// @Router /swagger [get]
func (r *SwaggerController) Index(ctx http.Context) http.Response {
if !facades.Config().GetBool("app.debug") {
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
handler := httpSwagger.Handler()
handler(ctx.Response().Writer(), ctx.Request().Origin())
+210
View File
@@ -0,0 +1,210 @@
package controllers
import (
"fmt"
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/pkg/systemctl"
)
type SystemController struct {
}
func NewSystemController() *SystemController {
return &SystemController{}
}
// ServiceStatus
//
// @Summary 服务状态
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data query string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/status [get]
func (r *SystemController) ServiceStatus(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Query("service")
status, err := systemctl.Status(service)
if err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("获取 %s 服务运行状态失败", service))
}
return Success(ctx, status)
}
// ServiceIsEnabled
//
// @Summary 是否启用服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data query string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/isEnabled [get]
func (r *SystemController) ServiceIsEnabled(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Query("service")
enabled, err := systemctl.IsEnabled(service)
if err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("获取 %s 服务启用状态失败", service))
}
return Success(ctx, enabled)
}
// ServiceEnable
//
// @Summary 启用服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data body string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/enable [post]
func (r *SystemController) ServiceEnable(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Input("service")
if err := systemctl.Enable(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("启用 %s 服务失败", service))
}
return Success(ctx, nil)
}
// ServiceDisable
//
// @Summary 禁用服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data body string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/disable [post]
func (r *SystemController) ServiceDisable(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Input("service")
if err := systemctl.Disable(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("禁用 %s 服务失败", service))
}
return Success(ctx, nil)
}
// ServiceRestart
//
// @Summary 重启服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data body string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/restart [post]
func (r *SystemController) ServiceRestart(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Input("service")
if err := systemctl.Restart(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重启 %s 服务失败", service))
}
return Success(ctx, nil)
}
// ServiceReload
//
// @Summary 重载服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data body string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/reload [post]
func (r *SystemController) ServiceReload(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Input("service")
if err := systemctl.Reload(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载 %s 服务失败", service))
}
return Success(ctx, nil)
}
// ServiceStart
//
// @Summary 启动服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data body string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/start [post]
func (r *SystemController) ServiceStart(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Input("service")
if err := systemctl.Start(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("启动 %s 服务失败", service))
}
return Success(ctx, nil)
}
// ServiceStop
//
// @Summary 停止服务
// @Tags 系统
// @Produce json
// @Security BearerToken
// @Param data body string true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /panel/system/service/stop [post]
func (r *SystemController) ServiceStop(ctx http.Context) http.Response {
if sanitize := Sanitize(ctx, map[string]string{
"service": "required|string",
}); sanitize != nil {
return sanitize
}
service := ctx.Request().Input("service")
if err := systemctl.Stop(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("停止 %s 服务失败", service))
}
return Success(ctx, nil)
}
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/shell"
)
type TaskController struct {
@@ -63,7 +63,7 @@ func (r *TaskController) Log(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
log, err := tools.Exec(`tail -n 500 '` + task.Log + `'`)
log, err := shell.Execf(`tail -n 500 '` + task.Log + `'`)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "日志已被清理")
}
+33 -30
View File
@@ -3,6 +3,7 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/requests/user"
"github.com/TheTNB/panel/app/models"
@@ -20,19 +21,18 @@ func NewUserController() *UserController {
// Login
//
// @Summary 登录
// @Description 通过用户名和密码获取访问令牌
// @Tags 用户鉴权
// @Accept json
// @Produce json
// @Param data body requests.Login true "request"
// @Success 200 {object} SuccessResponse
// @Failure 403 {object} ErrorResponse "用户名或密码错误"
// @Failure 500 {object} ErrorResponse "系统内部错误
// @Router /panel/user/login [post]
// @Summary 登录
// @Tags 用户鉴权
// @Accept json
// @Produce json
// @Param data body requests.Login true "request"
// @Success 200 {object} SuccessResponse
// @Failure 403 {object} ErrorResponse "用户名或密码错误"
// @Failure 500 {object} ErrorResponse "系统内部错误
// @Router /panel/user/login [post]
func (r *UserController) Login(ctx http.Context) http.Response {
var loginRequest requests.Login
sanitize := Sanitize(ctx, &loginRequest)
sanitize := SanitizeRequest(ctx, &loginRequest)
if sanitize != nil {
return sanitize
}
@@ -60,32 +60,35 @@ func (r *UserController) Login(ctx http.Context) http.Response {
}
}
token, loginErr := facades.Auth(ctx).LoginUsingID(user.ID)
if loginErr != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
}).Info("登录失败")
return ErrorSystem(ctx)
}
ctx.Request().Session().Put("user_id", user.ID)
return Success(ctx, nil)
}
return Success(ctx, http.Json{
"access_token": token,
})
// Logout
//
// @Summary 登出
// @Tags 用户鉴权
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/user/logout [post]
func (r *UserController) Logout(ctx http.Context) http.Response {
ctx.Request().Session().Forget("user_id")
return Success(ctx, nil)
}
// Info
//
// @Summary 用户信息
// @Description 获取当前登录用户信息
// @Tags 用户鉴权
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/user/info [get]
// @Summary 用户信息
// @Tags 用户鉴权
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/user/info [get]
func (r *UserController) Info(ctx http.Context) http.Response {
userID := cast.ToUint(ctx.Value("user_id"))
var user models.User
err := facades.Auth(ctx).User(&user)
if err != nil {
if err := facades.Orm().Query().Where("id", userID).Get(&user); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
}).Info("获取用户信息失败")
+190 -226
View File
@@ -13,8 +13,10 @@ import (
"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"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
)
type WebsiteController struct {
@@ -33,17 +35,16 @@ func NewWebsiteController() *WebsiteController {
// List
//
// @Summary 获取网站列表
// @Description 获取网站管理的网站列表
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites [get]
// @Summary 获取网站列表
// @Tags 网站
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites [get]
func (r *WebsiteController) List(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
@@ -53,7 +54,7 @@ func (r *WebsiteController) List(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("获取网站列表失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, http.Json{
@@ -64,18 +65,17 @@ func (r *WebsiteController) List(ctx http.Context) http.Response {
// Add
//
// @Summary 添加网站
// @Description 添加网站到网站管理
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Add true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites [post]
// @Summary 添加网站
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Add true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites [post]
func (r *WebsiteController) Add(ctx http.Context) http.Response {
var addRequest requests.Add
sanitize := Sanitize(ctx, &addRequest)
sanitize := SanitizeRequest(ctx, &addRequest)
if sanitize != nil {
return sanitize
}
@@ -84,27 +84,12 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
}
website := types.Website{
Name: addRequest.Name,
Status: true,
Domains: addRequest.Domains,
Ports: addRequest.Ports,
Path: addRequest.Path,
Php: addRequest.Php,
Ssl: false,
Db: addRequest.Db,
DbType: addRequest.DbType,
DbName: addRequest.DbName,
DbUser: addRequest.DbUser,
DbPassword: addRequest.DbPassword,
}
_, err := r.website.Add(website)
_, err := r.website.Add(addRequest)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("添加网站失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
@@ -112,29 +97,27 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
// Delete
//
// @Summary 删除网站
// @Description 删除网站管理的网站
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id} [delete]
// @Summary 删除网站
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Delete true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/delete [post]
func (r *WebsiteController) Delete(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
var deleteRequest requests.Delete
sanitize := SanitizeRequest(ctx, &deleteRequest)
if sanitize != nil {
return sanitize
}
err := r.website.Delete(idRequest.ID)
if err != nil {
if err := r.website.Delete(deleteRequest); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"id": deleteRequest.ID,
"error": err.Error(),
}).Info("删除网站失败")
return Error(ctx, http.StatusInternalServerError, "删除网站失败: "+err.Error())
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
@@ -142,19 +125,18 @@ func (r *WebsiteController) Delete(ctx http.Context) http.Response {
// GetDefaultConfig
//
// @Summary 获取默认配置
// @Description 获取默认首页和停止页配置
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=map[string]string}
// @Router /panel/website/defaultConfig [get]
// @Summary 获取默认配置
// @Tags 网站
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=map[string]string}
// @Router /panel/website/defaultConfig [get]
func (r *WebsiteController) GetDefaultConfig(ctx http.Context) http.Response {
index, err := tools.Read("/www/server/openresty/html/index.html")
index, err := io.Read("/www/server/openresty/html/index.html")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
stop, err := tools.Read("/www/server/openresty/html/stop.html")
stop, err := io.Read("/www/server/openresty/html/stop.html")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -167,31 +149,30 @@ func (r *WebsiteController) GetDefaultConfig(ctx http.Context) http.Response {
// SaveDefaultConfig
//
// @Summary 保存默认配置
// @Description 保存默认首页和停止页配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body map[string]string true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/defaultConfig [post]
// @Summary 保存默认配置
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body map[string]string true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/defaultConfig [post]
func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
index := ctx.Request().Input("index")
stop := ctx.Request().Input("stop")
if err := tools.Write("/www/server/openresty/html/index.html", index, 0644); err != nil {
if err := io.Write("/www/server/openresty/html/index.html", index, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("保存默认首页配置失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Write("/www/server/openresty/html/stop.html", stop, 0644); err != nil {
if err := io.Write("/www/server/openresty/html/stop.html", stop, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("保存默认停止页配置失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
@@ -199,18 +180,17 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
// GetConfig
//
// @Summary 获取配置
// @Description 获取网站的配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse{data=types.Website}
// @Router /panel/websites/{id}/config [get]
// @Summary 获取网站配置
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse{data=types.WebsiteAdd}
// @Router /panel/websites/{id}/config [get]
func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
sanitize := SanitizeRequest(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
@@ -221,7 +201,7 @@ func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
"id": idRequest.ID,
"error": err.Error(),
}).Info("获取网站配置失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, config)
@@ -229,19 +209,18 @@ func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
// SaveConfig
//
// @Summary 保存配置
// @Description 保存网站的配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Param data body requests.SaveConfig true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/config [post]
// @Summary 保存网站配置
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Param data body requests.SaveConfig true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/config [post]
func (r *WebsiteController) SaveConfig(ctx http.Context) http.Response {
var saveConfigRequest requests.SaveConfig
sanitize := Sanitize(ctx, &saveConfigRequest)
sanitize := SanitizeRequest(ctx, &saveConfigRequest)
if sanitize != nil {
return sanitize
}
@@ -256,18 +235,17 @@ func (r *WebsiteController) SaveConfig(ctx http.Context) http.Response {
// ClearLog
//
// @Summary 清空日志
// @Description 清空网站的日志
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/log [delete]
// @Summary 清空网站日志
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/log [delete]
func (r *WebsiteController) ClearLog(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
sanitize := SanitizeRequest(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
@@ -278,7 +256,7 @@ func (r *WebsiteController) ClearLog(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
if err := tools.Remove("/www/wwwlogs/" + website.Name + ".log"); err != nil {
if err := io.Remove("/www/wwwlogs/" + website.Name + ".log"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -287,18 +265,17 @@ func (r *WebsiteController) ClearLog(ctx http.Context) http.Response {
// UpdateRemark
//
// @Summary 更新备注
// @Description 更新网站的备注
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/updateRemark [post]
// @Summary 更新网站备注
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/updateRemark [post]
func (r *WebsiteController) UpdateRemark(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
sanitize := SanitizeRequest(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
@@ -323,65 +300,49 @@ func (r *WebsiteController) UpdateRemark(ctx http.Context) http.Response {
// BackupList
//
// @Summary 获取备份列表
// @Description 获取网站的备份列表
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=[]types.BackupFile}
// @Router /panel/website/backupList [get]
// @Summary 获取网站备份列表
// @Tags 网站
// @Produce json
// @Security BearerToken
// @Param data query commonrequests.Paginate true "request"
// @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
sanitize := Sanitize(ctx, &paginateRequest)
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
backupList, err := r.backup.WebsiteList()
backups, err := r.backup.WebsiteList()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("获取备份列表失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(backupList) {
return Success(ctx, http.Json{
"total": 0,
"items": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
paged, total := Paginate(ctx, backups)
return Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
"total": total,
"items": paged,
})
}
// CreateBackup
//
// @Summary 创建备份
// @Description 创建网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/createBackup [post]
// @Summary 创建网站备份
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/createBackup [post]
func (r *WebsiteController) CreateBackup(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
sanitize := SanitizeRequest(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
@@ -408,15 +369,14 @@ func (r *WebsiteController) CreateBackup(ctx http.Context) http.Response {
// UploadBackup
//
// @Summary 上传备份
// @Description 上传网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param file formData file true "备份文件"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/uploadBackup [put]
// @Summary 上传网站备份
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param file formData file true "备份文件"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/uploadBackup [put]
func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
file, err := ctx.Request().File("file")
if err != nil {
@@ -424,8 +384,8 @@ func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
if err = tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err = io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
@@ -436,7 +396,7 @@ func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("上传备份失败")
return ErrorSystem(ctx)
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
@@ -444,18 +404,17 @@ func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
// RestoreBackup
//
// @Summary 还原备份
// @Description 还原网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/restoreBackup [post]
// @Summary 还原网站备份
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/restoreBackup [post]
func (r *WebsiteController) RestoreBackup(ctx http.Context) http.Response {
var restoreBackupRequest requests.RestoreBackup
sanitize := Sanitize(ctx, &restoreBackupRequest)
sanitize := SanitizeRequest(ctx, &restoreBackupRequest)
if sanitize != nil {
return sanitize
}
@@ -479,30 +438,29 @@ func (r *WebsiteController) RestoreBackup(ctx http.Context) http.Response {
// DeleteBackup
//
// @Summary 删除备份
// @Description 删除网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.DeleteBackup true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/deleteBackup [delete]
// @Summary 删除网站备份
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.DeleteBackup true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/deleteBackup [delete]
func (r *WebsiteController) DeleteBackup(ctx http.Context) http.Response {
var deleteBackupRequest requests.DeleteBackup
sanitize := Sanitize(ctx, &deleteBackupRequest)
sanitize := SanitizeRequest(ctx, &deleteBackupRequest)
if sanitize != nil {
return sanitize
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
if err := tools.Remove(backupPath + "/" + deleteBackupRequest.Name); err != nil {
if err := io.Remove(backupPath + "/" + deleteBackupRequest.Name); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -511,18 +469,17 @@ func (r *WebsiteController) DeleteBackup(ctx http.Context) http.Response {
// ResetConfig
//
// @Summary 重置配置
// @Description 重置网站的配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/resetConfig [post]
// @Summary 重置网站配置
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/resetConfig [post]
func (r *WebsiteController) ResetConfig(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
sanitize := SanitizeRequest(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
@@ -533,7 +490,7 @@ func (r *WebsiteController) ResetConfig(ctx http.Context) http.Response {
}
website.Status = true
website.Ssl = false
website.SSL = false
if err := facades.Orm().Query().Save(&website); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
@@ -576,9 +533,12 @@ server
# waf标记位结束
# 错误页配置,可自行设置
#error_page 404 /404.html;
error_page 404 /404.html;
#error_page 502 /502.html;
# acme证书签发配置,不可修改
include /www/server/vhost/acme/%s.conf;
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
include /www/server/vhost/rewrite/%s.conf;
@@ -599,15 +559,19 @@ server
error_log /www/wwwlogs/%s.log;
}
`, website.Path, website.Php, website.Name, website.Name, website.Name)
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
`, website.Path, website.PHP, website.Name, website.Name, website.Name, website.Name)
if err := io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return nil
}
if err := tools.Write("/www/server/vhost/rewrite"+website.Name+".conf", "", 0644); err != nil {
if err := io.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644); err != nil {
return nil
}
if err := tools.ServiceReload("openresty"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
if err := io.Write("/www/server/vhost/acme/"+website.Name+".conf", "", 0644); err != nil {
return nil
}
if err := systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
}
return Success(ctx, nil)
@@ -615,18 +579,17 @@ server
// Status
//
// @Summary 状态
// @Description 启用或停用网站
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/status [post]
// @Summary 获取网站状态
// @Tags 网站
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/status [post]
func (r *WebsiteController) Status(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
sanitize := SanitizeRequest(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
@@ -641,13 +604,13 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
raw, err := tools.Read("/www/server/vhost/" + website.Name + ".conf")
raw, err := io.Read("/www/server/vhost/" + website.Name + ".conf")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
// 运行目录
rootConfig := tools.Cut(raw, "# root标记位开始\n", "# root标记位结束")
rootConfig := str.Cut(raw, "# root标记位开始\n", "# root标记位结束")
match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(rootConfig)
if len(match) == 2 {
if website.Status {
@@ -659,7 +622,7 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
}
// 默认文件
indexConfig := tools.Cut(raw, "# index标记位开始\n", "# index标记位结束")
indexConfig := str.Cut(raw, "# index标记位开始\n", "# index标记位结束")
match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(indexConfig)
if len(match) == 2 {
if website.Status {
@@ -670,12 +633,13 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
}
}
if err = tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return ErrorSystem(ctx)
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
}
return Success(ctx, nil)
}
+5
View File
@@ -2,6 +2,7 @@ package http
import (
"github.com/goravel/framework/contracts/http"
sessionmiddleware "github.com/goravel/framework/session/middleware"
"github.com/TheTNB/panel/app/http/middleware"
)
@@ -13,6 +14,10 @@ type Kernel struct {
// These middleware are run during every request to your application.
func (kernel Kernel) Middleware() []http.Middleware {
return []http.Middleware{
sessionmiddleware.StartSession(),
middleware.Log(),
middleware.Status(),
middleware.Entrance(),
middleware.Static(),
}
}
+39
View File
@@ -0,0 +1,39 @@
package middleware
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
)
func Entrance() http.Middleware {
return func(ctx http.Context) {
translate := facades.Lang(ctx)
if !ctx.Request().HasSession() {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": translate.Get("auth.session.missing"),
})
return
}
entrance := facades.Config().GetString("panel.entrance")
if ctx.Request().Path() == entrance {
ctx.Request().Session().Put("verify_entrance", true)
_ = ctx.Response().Redirect(http.StatusFound, "/login").Render()
ctx.Request().AbortWithStatus(http.StatusFound)
return
}
if !facades.Config().GetBool("app.debug") &&
(ctx.Request().Session().Missing("verify_entrance") || !cast.ToBool(ctx.Request().Session().Get("verify_entrance"))) &&
ctx.Request().Path() != "/robots.txt" {
ctx.Request().AbortWithStatusJson(http.StatusTeapot, http.Json{
"message": "请通过正确的入口访问",
})
return
}
ctx.Request().Next()
}
}
-47
View File
@@ -1,47 +0,0 @@
package middleware
import (
"errors"
"github.com/goravel/framework/auth"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
)
// 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": translate.Get("auth.token.missing"),
})
return
}
// JWT 鉴权
if _, err := facades.Auth(ctx).Parse(token); err != nil {
if errors.Is(err, auth.ErrorTokenExpired) {
token, err = facades.Auth(ctx).Refresh()
if err != nil {
// 到达刷新时间上限
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": translate.Get("auth.token.expired"),
})
return
}
token = "Bearer " + token
} else {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": translate.Get("auth.token.expired"),
})
return
}
}
ctx.Response().Header("Authorization", token)
ctx.Request().Next()
}
}
+20
View File
@@ -0,0 +1,20 @@
package middleware
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
)
// Log 记录请求日志
func Log() http.Middleware {
return func(ctx http.Context) {
facades.Log().Channel("http").With(map[string]any{
"Method": ctx.Request().Method(),
"URL": ctx.Request().FullUrl(),
"IP": ctx.Request().Ip(),
"UA": ctx.Request().Header("User-Agent"),
"Body": ctx.Request().All(),
}).Info("HTTP Request")
ctx.Request().Next()
}
}
+53
View File
@@ -0,0 +1,53 @@
package middleware
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
)
// Session 确保通过 JWT 鉴权
func Session() http.Middleware {
return func(ctx http.Context) {
translate := facades.Lang(ctx)
if !ctx.Request().HasSession() {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": translate.Get("auth.session.missing"),
})
return
}
if ctx.Request().Session().Missing("user_id") {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": translate.Get("auth.session.expired"),
})
return
}
userID := cast.ToUint(ctx.Request().Session().Get("user_id"))
if userID == 0 {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": translate.Get("auth.session.invalid"),
})
return
}
// 刷新会话
/*if err := ctx.Request().Session().Regenerate(); err == nil {
ctx.Response().Cookie(http.Cookie{
Name: ctx.Request().Session().GetName(),
Value: ctx.Request().Session().GetID(),
MaxAge: facades.Config().GetInt("session.lifetime") * 60,
Path: facades.Config().GetString("session.path"),
Domain: facades.Config().GetString("session.domain"),
Secure: facades.Config().GetBool("session.secure"),
HttpOnly: facades.Config().GetBool("session.http_only"),
SameSite: facades.Config().GetString("session.same_site"),
})
}*/
ctx.WithValue("user_id", userID)
ctx.Request().Next()
}
}
+16
View File
@@ -0,0 +1,16 @@
package middleware
import (
"github.com/gin-contrib/static"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/gin"
"github.com/TheTNB/panel/embed"
)
func Static() http.Middleware {
return func(ctx http.Context) {
static.Serve("/", static.EmbedFolder(embed.PublicFS, "frontend"))(ctx.(*gin.Context).Instance())
ctx.Request().Next()
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
// Status 检查程序状态
+9 -2
View File
@@ -6,8 +6,8 @@ import (
)
type CertDeploy struct {
ID uint `form:"id" json:"id" filter:"uint"`
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
ID uint `form:"id" json:"id"`
WebsiteID uint `form:"website_id" json:"website_id"`
}
func (r *CertDeploy) Authorize(ctx http.Context) error {
@@ -21,6 +21,13 @@ func (r *CertDeploy) Rules(ctx http.Context) map[string]string {
}
}
func (r *CertDeploy) Filters(ctx http.Context) map[string]string {
return map[string]string{
"id": "uint",
"website_id": "uint",
}
}
func (r *CertDeploy) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -19,6 +19,10 @@ func (r *CertShowAndDestroy) Rules(ctx http.Context) map[string]string {
}
}
func (r *CertShowAndDestroy) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *CertShowAndDestroy) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+10 -27
View File
@@ -3,7 +3,6 @@ package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/spf13/cast"
)
type CertStore struct {
@@ -11,8 +10,8 @@ type CertStore struct {
Domains []string `form:"domains" json:"domains"`
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
UserID uint `form:"user_id" json:"user_id"`
DNSID *uint `form:"dns_id" json:"dns_id"`
WebsiteID *uint `form:"website_id" json:"website_id"`
DNSID uint `form:"dns_id" json:"dns_id"`
WebsiteID uint `form:"website_id" json:"website_id"`
}
func (r *CertStore) Authorize(ctx http.Context) error {
@@ -30,6 +29,14 @@ func (r *CertStore) Rules(ctx http.Context) map[string]string {
}
}
func (r *CertStore) Filters(ctx http.Context) map[string]string {
return map[string]string{
"user_id": "uint",
"dns_id": "uint",
"website_id": "uint",
}
}
func (r *CertStore) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -39,29 +46,5 @@ func (r *CertStore) Attributes(ctx http.Context) map[string]string {
}
func (r *CertStore) PrepareForValidation(ctx http.Context, data validation.Data) error {
// TODO 由于验证器 filter 标签的问题,暂时这里这样处理
userID, exist := data.Get("user_id")
if exist {
err := data.Set("user_id", cast.ToUint(userID))
if err != nil {
return err
}
}
dnsID, exist := data.Get("dns_id")
if exist {
err := data.Set("dns_id", cast.ToUint(dnsID))
if err != nil {
return err
}
}
websiteID, exist := data.Get("website_id")
if exist {
err := data.Set("website_id", cast.ToUint(websiteID))
if err != nil {
return err
}
}
return nil
}
+10 -27
View File
@@ -3,7 +3,6 @@ package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/spf13/cast"
)
type CertUpdate struct {
@@ -12,8 +11,8 @@ type CertUpdate struct {
Domains []string `form:"domains" json:"domains"`
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
UserID uint `form:"user_id" json:"user_id"`
DNSID *uint `form:"dns_id" json:"dns_id"`
WebsiteID *uint `form:"website_id" json:"website_id"`
DNSID uint `form:"dns_id" json:"dns_id"`
WebsiteID uint `form:"website_id" json:"website_id"`
}
func (r *CertUpdate) Authorize(ctx http.Context) error {
@@ -32,6 +31,14 @@ func (r *CertUpdate) Rules(ctx http.Context) map[string]string {
}
}
func (r *CertUpdate) Filters(ctx http.Context) map[string]string {
return map[string]string{
"user_id": "uint",
"dns_id": "uint",
"website_id": "uint",
}
}
func (r *CertUpdate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -41,29 +48,5 @@ func (r *CertUpdate) Attributes(ctx http.Context) map[string]string {
}
func (r *CertUpdate) PrepareForValidation(ctx http.Context, data validation.Data) error {
// TODO 由于验证器 filter 标签的问题,暂时这里这样处理
userID, exist := data.Get("user_id")
if exist {
err := data.Set("user_id", cast.ToUint(userID))
if err != nil {
return err
}
}
dnsID, exist := data.Get("dns_id")
if exist {
err := data.Set("dns_id", cast.ToUint(dnsID))
if err != nil {
return err
}
}
websiteID, exist := data.Get("website_id")
if exist {
err := data.Set("website_id", cast.ToUint(websiteID))
if err != nil {
return err
}
}
return nil
}
@@ -19,6 +19,10 @@ func (r *DNSShowAndDestroy) Rules(ctx http.Context) map[string]string {
}
}
func (r *DNSShowAndDestroy) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *DNSShowAndDestroy) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+7 -3
View File
@@ -19,17 +19,21 @@ func (r *DNSStore) Authorize(ctx http.Context) error {
func (r *DNSStore) Rules(ctx http.Context) map[string]string {
return map[string]string{
"type": "required|in:dnspod,aliyun,cloudflare",
"type": "required|in:dnspod,tencent,aliyun,cloudflare",
"name": "required",
"data": "required",
"data.id": "required_if:type,dnspod",
"data.token": "required_if:type,dnspod",
"data.access_key": "required_if:type,aliyun",
"data.secret_key": "required_if:type,aliyun",
"data.access_key": "required_if:type,aliyun,tencent",
"data.secret_key": "required_if:type,aliyun,tencent",
"data.api_key": "required_if:type,cloudflare",
}
}
func (r *DNSStore) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *DNSStore) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+4
View File
@@ -33,6 +33,10 @@ func (r *DNSUpdate) Rules(ctx http.Context) map[string]string {
}
}
func (r *DNSUpdate) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *DNSUpdate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+4
View File
@@ -19,6 +19,10 @@ func (r *Obtain) Rules(ctx http.Context) map[string]string {
}
}
func (r *Obtain) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Obtain) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+4
View File
@@ -19,6 +19,10 @@ func (r *Renew) Rules(ctx http.Context) map[string]string {
}
}
func (r *Renew) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Renew) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -19,6 +19,10 @@ func (r *UserShowAndDestroy) Rules(ctx http.Context) map[string]string {
}
}
func (r *UserShowAndDestroy) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UserShowAndDestroy) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+6 -2
View File
@@ -21,12 +21,16 @@ func (r *UserStore) Rules(ctx http.Context) map[string]string {
return map[string]string{
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
"email": "required|email",
"kid": "required_unless:ca,letsencrypt,buypass",
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
"kid": "required_if:ca,sslcom,google",
"hmac_encoded": "required_if:ca,sslcom,google",
"key_type": "required|in:P256,P384,2048,4096",
}
}
func (r *UserStore) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UserStore) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+6 -2
View File
@@ -23,12 +23,16 @@ func (r *UserUpdate) Rules(ctx http.Context) map[string]string {
"id": "required|uint|min:1|exists:cert_users,id",
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
"email": "required|email",
"kid": "required_unless:ca,letsencrypt,buypass",
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
"kid": "required_if:ca,sslcom,google",
"hmac_encoded": "required_if:ca,sslcom,google",
"key_type": "required|in:P256,P384,2048,4096",
}
}
func (r *UserUpdate) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UserUpdate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+9 -2
View File
@@ -6,8 +6,8 @@ import (
)
type Paginate struct {
Page int `form:"page" json:"page" filter:"int"`
Limit int `form:"limit" json:"limit" filter:"int"`
Page int `form:"page" json:"page"`
Limit int `form:"limit" json:"limit"`
}
func (r *Paginate) Authorize(ctx http.Context) error {
@@ -21,6 +21,13 @@ func (r *Paginate) Rules(ctx http.Context) map[string]string {
}
}
func (r *Paginate) Filters(ctx http.Context) map[string]string {
return map[string]string{
"page": "int",
"limit": "int",
}
}
func (r *Paginate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerCreate struct {
@@ -23,9 +23,9 @@ type ContainerCreate struct {
OpenStdin bool `form:"openStdin" json:"open_stdin"`
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
Tty bool `form:"tty" json:"tty"`
CPUShares int64 `form:"cpu_shares" json:"cpu_shares" filter:"int"`
CPUs int64 `form:"cpus" json:"cpus" filter:"int"`
Memory int64 `form:"memory" json:"memory" filter:"int"`
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
CPUs int64 `form:"cpus" json:"cpus"`
Memory int64 `form:"memory" json:"memory"`
}
func (r *ContainerCreate) Authorize(ctx http.Context) error {
@@ -64,6 +64,14 @@ func (r *ContainerCreate) Rules(ctx http.Context) map[string]string {
}
}
func (r *ContainerCreate) Filters(ctx http.Context) map[string]string {
return map[string]string{
"cpu_shares": "int",
"cpus": "int",
"memory": "int",
}
}
func (r *ContainerCreate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -21,6 +21,10 @@ func (r *ContainerRename) Rules(ctx http.Context) map[string]string {
}
}
func (r *ContainerRename) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerRename) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerUpdate struct {
@@ -57,6 +57,10 @@ func (r *ContainerUpdate) Rules(ctx http.Context) map[string]string {
}
}
func (r *ContainerUpdate) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerUpdate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+4
View File
@@ -19,6 +19,10 @@ func (r *ID) Rules(ctx http.Context) map[string]string {
}
}
func (r *ID) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ID) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -25,6 +25,10 @@ func (r *ImagePull) Rules(ctx http.Context) map[string]string {
}
}
func (r *ImagePull) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ImagePull) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -21,6 +21,10 @@ func (r *NetworkConnectDisConnect) Rules(ctx http.Context) map[string]string {
}
}
func (r *NetworkConnectDisConnect) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NetworkConnectDisConnect) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type NetworkCreate struct {
@@ -31,6 +31,10 @@ func (r *NetworkCreate) Rules(ctx http.Context) map[string]string {
}
}
func (r *NetworkCreate) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NetworkCreate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type VolumeCreate struct {
@@ -27,6 +27,10 @@ func (r *VolumeCreate) Rules(ctx http.Context) map[string]string {
}
}
func (r *VolumeCreate) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *VolumeCreate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+6 -2
View File
@@ -17,11 +17,15 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"file": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
"paths.*": `regex:^/.*$|path_exists`,
"file": `regex:^/.*$|path_not_exists`,
}
}
func (r *Archive) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Archive) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+6 -2
View File
@@ -16,11 +16,15 @@ func (r *Copy) Authorize(ctx http.Context) error {
func (r *Copy) Rules(ctx http.Context) map[string]string {
return map[string]string{
"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\[\]()]+)*$`,
"source": `regex:^/.*$|path_exists`,
"target": `regex:^/.*$`,
}
}
func (r *Copy) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Copy) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -15,10 +15,14 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*)?$|path_exists`,
"path": `regex:^/.*$|path_exists`,
}
}
func (r *Exist) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Exist) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+6 -2
View File
@@ -16,11 +16,15 @@ func (r *Move) Authorize(ctx http.Context) error {
func (r *Move) Rules(ctx http.Context) map[string]string {
return map[string]string{
"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\[\]()]+)*$`,
"source": `regex:^/.*$|path_exists`,
"target": `regex:^/.*$`,
}
}
func (r *Move) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Move) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -15,10 +15,14 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
"path": `regex:^/.*$|path_not_exists`,
}
}
func (r *NotExist) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NotExist) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+7 -3
View File
@@ -7,7 +7,7 @@ import (
type Permission struct {
Path string `form:"path" json:"path"`
Mode uint `form:"mode" json:"mode" filter:"uint"`
Mode string `form:"mode" json:"mode"`
Owner string `form:"owner" json:"owner"`
Group string `form:"group" json:"group"`
}
@@ -18,13 +18,17 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"mode": "regex:^[0-7]{3}$|uint",
"path": `regex:^/.*$|path_exists`,
"mode": "regex:^0[0-7]{3}$",
"owner": "regex:^[a-zA-Z0-9_-]+$",
"group": "regex:^[a-zA-Z0-9_-]+$",
}
}
func (r *Permission) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Permission) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -16,11 +16,15 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"path": `regex:^/.*$|path_exists`,
"content": "required|string",
}
}
func (r *Save) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Save) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -16,11 +16,15 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"path": `regex:^/.*$|path_exists`,
"keyword": "required|string",
}
}
func (r *Search) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Search) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+6 -2
View File
@@ -16,11 +16,15 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
"file": `regex:^/.*$|path_exists`,
"path": `regex:^/.*$`,
}
}
func (r *UnArchive) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UnArchive) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -18,11 +18,15 @@ 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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"path": `regex:^/.*$`,
"file": "required",
}
}
func (r *Upload) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Upload) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+4
View File
@@ -19,6 +19,10 @@ func (r *Service) Rules(ctx http.Context) map[string]string {
}
}
func (r *Service) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Service) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -21,6 +21,10 @@ func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
}
}
func (r *UpdateConfig) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -19,6 +19,10 @@ func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
}
}
func (r *UpdateConfig) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -19,6 +19,10 @@ func (r *UpdateRegistryConfig) Rules(ctx http.Context) map[string]string {
}
}
func (r *UpdateRegistryConfig) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateRegistryConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -19,6 +19,10 @@ func (r *UpdateStorageConfig) Rules(ctx http.Context) map[string]string {
}
}
func (r *UpdateStorageConfig) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateStorageConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"path": `regex:^/.*$`,
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",
@@ -29,6 +29,10 @@ func (r *Create) Rules(ctx http.Context) map[string]string {
}
}
func (r *Create) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Create) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+5 -1
View File
@@ -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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"path": `regex:^/.*$`,
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",
@@ -29,6 +29,10 @@ func (r *Update) Rules(ctx http.Context) map[string]string {
}
}
func (r *Update) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Update) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
@@ -19,6 +19,10 @@ func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
}
}
func (r *UpdateConfig) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+40
View File
@@ -0,0 +1,40 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Https struct {
Https bool `form:"https" json:"https"`
Cert string `form:"cert" json:"cert"`
Key string `form:"key" json:"key"`
}
func (r *Https) Authorize(ctx http.Context) error {
return nil
}
func (r *Https) Rules(ctx http.Context) map[string]string {
return map[string]string{
"https": "bool",
"cert": "string",
"key": "string",
}
}
func (r *Https) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Https) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Https) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Https) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+7 -3
View File
@@ -8,11 +8,10 @@ import (
type Update struct {
Name string `form:"name" json:"name"`
Language string `form:"language" json:"language"`
Port uint `form:"port" json:"port" filter:"uint"`
Port uint `form:"port" json:"port"`
BackupPath string `form:"backup_path" json:"backup_path"`
WebsitePath string `form:"website_path" json:"website_path"`
Entrance string `form:"entrance" json:"entrance"`
SSL bool `form:"ssl" json:"ssl"`
UserName string `form:"username" json:"username"`
Email string `form:"email" json:"email"`
Password string `form:"password" json:"password"`
@@ -30,13 +29,18 @@ func (r *Update) Rules(ctx http.Context) map[string]string {
"backup_path": "required|string:2,255",
"website_path": "required|string:2,255",
"entrance": `required|regex:^/(\w+)?$|not_in:/api`,
"ssl": "bool",
"username": "required|string:2,20",
"email": "required|email",
"password": "string:8,255",
}
}
func (r *Update) Filters(ctx http.Context) map[string]string {
return map[string]string{
"port": "uint",
}
}
func (r *Update) Messages(ctx http.Context) map[string]string {
return map[string]string{
"port.int": "port 值必须是一个整数且在 1000 - 65535 之间",
+5 -1
View File
@@ -23,12 +23,16 @@ func (r *Login) Rules(ctx http.Context) map[string]string {
func (r *Login) Messages(ctx http.Context) map[string]string {
return map[string]string{
"username.required": "登录名不能为空",
"username.required": "用户名不能为空",
"password.required": "密码不能为空",
"password.min_len": "密码长度不能小于 8 位",
}
}
func (r *Login) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Login) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
+11 -7
View File
@@ -10,12 +10,12 @@ type Add struct {
Domains []string `form:"domains" json:"domains"`
Ports []uint `form:"ports" json:"ports"`
Path string `form:"path" json:"path"`
Php string `form:"php" json:"php"`
Db bool `form:"db" json:"db"`
DbType string `form:"db_type" json:"db_type"`
DbName string `form:"db_name" json:"db_name"`
DbUser string `form:"db_user" json:"db_user"`
DbPassword string `form:"db_password" json:"db_password"`
PHP string `form:"php" json:"php"`
DB bool `form:"db" json:"db"`
DBType string `form:"db_type" json:"db_type"`
DBName string `form:"db_name" json:"db_name"`
DBUser string `form:"db_user" json:"db_user"`
DBPassword string `form:"db_password" json:"db_password"`
}
func (r *Add) Authorize(ctx http.Context) error {
@@ -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_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"path": `regex:^/.*$`,
"php": "required",
"db": "bool",
"db_type": "required_if:db,true|in:0,mysql,postgresql",
@@ -37,6 +37,10 @@ func (r *Add) Rules(ctx http.Context) map[string]string {
}
}
func (r *Add) Filters(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Add) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
+42
View File
@@ -0,0 +1,42 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Delete struct {
ID uint `form:"id" json:"id"`
Path bool `form:"path" json:"path"`
DB bool `form:"db" json:"db"`
}
func (r *Delete) Authorize(ctx http.Context) error {
return nil
}
func (r *Delete) Rules(ctx http.Context) map[string]string {
return map[string]string{
"id": "required|exists:websites,id",
"path": "bool",
"db": "bool",
}
}
func (r *Delete) Filters(ctx http.Context) map[string]string {
return map[string]string{
"id": "uint",
}
}
func (r *Delete) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Delete) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Delete) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}

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