mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f65cf9e38e | |||
| 4a8ebfbdfa | |||
| fee0fcb998 | |||
| 1e081fcdf2 | |||
| 6c6fb09270 | |||
| 51b47818c0 | |||
| 5b393819ba | |||
| f807d0cd1a | |||
| 0d6f0f317d | |||
| 6bad7f4b5b | |||
| 7a7134b26a | |||
| 28cf7098ff | |||
| b56979147e | |||
| 2ec0d9e844 | |||
| 66641a626e | |||
| 95bfd6843b | |||
| 7e28a34b7f | |||
| b2152edeeb | |||
| 00815979d2 | |||
| c4e01b37b7 | |||
| 7907345521 | |||
| 8f32d6cdb0 | |||
| c3582903bf | |||
| 7d25d2f5aa | |||
| 46fc7b1c6a | |||
| 34ac62d1ae | |||
| 30e78ccf46 | |||
| 22f4cc7fcb | |||
| 1771ae682d | |||
| d1beb60ffd | |||
| cba5f86667 | |||
| e1d9832ce0 | |||
| 6278535902 | |||
| 32fb87243b | |||
| 5f85e7fbe0 | |||
| c36104a075 | |||
| 583f5d13c7 | |||
| b453738daf | |||
| 98431fb4b2 | |||
| 4d76e9286d | |||
| c70758339f | |||
| ff524cdb0b | |||
| 73c95d880a | |||
| 8a1d8c0b05 | |||
| 2251175906 | |||
| 69edde98c8 | |||
| 29a3db640d | |||
| ee87bf65b4 | |||
| aa080dab15 | |||
| b4e5498b48 | |||
| 63838bffa1 | |||
| fa41b18ced | |||
| 31bd15217b | |||
| e6b6f06706 | |||
| 1b836297bf | |||
| 96bea2cf90 | |||
| 8aff513284 | |||
| 368e41a512 | |||
| 924fdee7c5 | |||
| 438ba1db1e | |||
| d4e52a2d87 | |||
| 4432a12c7a | |||
| a75e36e447 | |||
| 05674ee04e | |||
| 09703761eb | |||
| dfa14c3fd4 | |||
| 7b80f034ea | |||
| 71923093fd | |||
| aae3cf0518 | |||
| ed22e79552 | |||
| 15dd5bb718 | |||
| 0e7af2914e | |||
| 7ec378829b | |||
| f2e8ff86c6 | |||
| eed03d644f | |||
| d9be877710 | |||
| 10439d7d97 | |||
| 55eaa26970 | |||
| 0d769d3400 | |||
| 7ff084e5a8 | |||
| 89e133c626 | |||
| c876f4c484 | |||
| 85c38e5bc5 | |||
| 6bd07251fc | |||
| 11d6b40dce | |||
| aa9b55d1eb | |||
| 2e97812776 | |||
| e0a7ada6d5 | |||
| d48963ab50 | |||
| 59bfed66dc | |||
| 5c0bc8a1b7 | |||
| ae9e39d0fe | |||
| 49b72aa656 | |||
| e0f642cbff | |||
| eb2c6cab81 | |||
| 0a3d3828c8 | |||
| 46b431de30 | |||
| 8a5b46ad7e | |||
| 5bd14946de | |||
| f6c091c725 | |||
| f4dc94ad9a | |||
| 8bcc0c0c05 | |||
| 0be04e1859 | |||
| bec9336f2f | |||
| 0be4ce9891 | |||
| 634a9a3219 | |||
| 1f2a32f883 | |||
| 32a56aa2dc | |||
| 8185a69354 | |||
| d41862f63b | |||
| 20e5122425 | |||
| 17bb8ad62b | |||
| 43624de2e4 | |||
| cc6a45b127 | |||
| 7a4a71f0a0 | |||
| 27cbb92a06 | |||
| 5ddbdfc5df | |||
| b3b1b0971f | |||
| 9caad365e9 | |||
| 5b54232e16 | |||
| a510bdb05d | |||
| 50a8b16832 | |||
| 2f56241da1 | |||
| 8557259b53 | |||
| 1d7ea0280a | |||
| 10145da2f9 | |||
| 6ba7d01875 | |||
| 763fa7379d | |||
| a36eaf7e9a | |||
| 18de16047d |
@@ -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
@@ -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' ]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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 ./...
|
||||
|
||||
@@ -67,8 +67,3 @@ out/
|
||||
*.sublime*
|
||||
__debug_bin
|
||||
.project
|
||||
|
||||
# 前端工具链 #
|
||||
.sass-cache/*
|
||||
node_modules/
|
||||
/public
|
||||
|
||||
+3
-5
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, "获取更新信息失败")
|
||||
}
|
||||
|
||||
@@ -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,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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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负载失败")
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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, "日志已被清理")
|
||||
}
|
||||
|
||||
@@ -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("获取用户信息失败")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 检查程序状态
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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,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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 之间",
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user