mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| ec10cd30a5 | |||
| 847fae5caa | |||
| b3b8c22634 | |||
| 5f504d49ef | |||
| 44045da4eb | |||
| baf90c0602 | |||
| f7e67c3797 | |||
| d7a79fa53e | |||
| b9b3b9fedf | |||
| e27940a71b | |||
| aa22933b36 | |||
| a1dceaaa82 | |||
| ebd5d37e78 | |||
| 107d2ec658 | |||
| 3a70d80bb5 | |||
| 7c58d8dcce | |||
| bde93b488c | |||
| 77aa4bea34 | |||
| 18de16047d | |||
| f7ba602a50 | |||
| 3101486a44 | |||
| b51a8e1d03 |
@@ -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:
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
unzip frontend.zip
|
||||
mv dist public
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
|
||||
@@ -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 ./...
|
||||
|
||||
+1
-2
@@ -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:
|
||||
|
||||
+2
-1
@@ -15,6 +15,8 @@ builds:
|
||||
- v2
|
||||
ldflags:
|
||||
- -s -w --extldflags "-static"
|
||||
tags:
|
||||
- nomsgpack
|
||||
|
||||
upx:
|
||||
- enabled: true
|
||||
@@ -40,7 +42,6 @@ archives:
|
||||
- docs/*
|
||||
- public/*
|
||||
- storage/*
|
||||
- database/*
|
||||
- lang/*
|
||||
- scripts/*
|
||||
- panel-example.conf
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
## 行为准则
|
||||
|
||||
耗子 Linux 面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
|
||||
耗子面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
|
||||
|
||||
- 参与者将容忍反对意见。
|
||||
- 参与者必须确保他们的语言和行为没有人身攻击和贬低个人言论。
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
The HaoZi Linux Panel complies with the industry's common code of conduct. Any breach of the Code of Conduct can be reported to us:
|
||||
The Rat Panel complies with the industry's common code of conduct. Any breach of the Code of Conduct can be reported to us:
|
||||
|
||||
- Participants will be tolerant of opposing views.
|
||||
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[简体中文] | [<a href="README_EN.md">English</a>]
|
||||
</p>
|
||||
|
||||
<h1 align="center">耗子 Linux 面板</h1>
|
||||
<h1 align="center">耗子面板</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
|
||||
@@ -12,7 +12,7 @@
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
|
||||
</p>
|
||||
|
||||
耗子 Linux 面板是使用 Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板。
|
||||
耗子面板是使用 Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板。
|
||||
|
||||
交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 论坛:[tom.moe](https://tom.moe) | 赞助:[爱发电](https://afdian.net/a/TheTNB)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
## 运行环境
|
||||
|
||||
耗子 Linux 面板仅支持 `amd64` | `arm64` 架构下的主流系统的最新版本,不支持 `Ubuntu`,因为其发版太过频繁,难以维护。
|
||||
耗子面板仅支持 `amd64` | `arm64` 架构下的主流系统的最新版本,不支持 `Ubuntu`,因为其发版太过频繁,难以维护。
|
||||
|
||||
低配机器建议使用 `Debian`,资源占用较 `RHEL` 系更低。其他机器建议使用 `AlmaLinux` | `RockyLinux`,维护周期更长也更稳定。
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
## 安装面板
|
||||
|
||||
> **Warning**
|
||||
> 安装面板前,你需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,不建议 0 基础的用户安装和使用耗子 Linux 面板。
|
||||
> 安装面板前,你需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,不建议 0 基础的用户安装和使用耗子面板。
|
||||
|
||||
如果你决定继续,请以`root`用户登录服务器,执行以下命令安装面板:
|
||||
|
||||
@@ -88,7 +88,7 @@ panel
|
||||
|
||||
## 赞助商
|
||||
|
||||
如果耗子 Linux 面板对你有帮助,欢迎[赞助我们](https://afdian.net/a/TheTNB),感谢以下支持者/赞助商的支持:
|
||||
如果耗子面板对你有帮助,欢迎[赞助我们](https://afdian.net/a/TheTNB),感谢以下支持者/赞助商的支持:
|
||||
|
||||
**同时接受云资源赞助,可通过QQ群咨询联系**
|
||||
|
||||
@@ -127,4 +127,4 @@ panel
|
||||
|
||||
## 免责声明
|
||||
|
||||
严禁使用耗子 Linux 面板从事任何非法活动,非法站点请勿向我们请求任何形式的技术支持,如果在技术支持过程中发现非法内容,我们将立即停止技术支持并留存相关证据。
|
||||
严禁使用耗子面板从事任何非法活动,非法站点请勿向我们请求任何形式的技术支持,如果在技术支持过程中发现非法内容,我们将立即停止技术支持并留存相关证据。
|
||||
|
||||
+6
-6
@@ -2,7 +2,7 @@
|
||||
[<a href="README.md">简体中文</a>] | [English]
|
||||
</p>
|
||||
|
||||
<h1 align="center">HaoZi Linux Panel</h1>
|
||||
<h1 align="center">Rat Panel</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
|
||||
@@ -12,7 +12,7 @@
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
|
||||
</p>
|
||||
|
||||
The HaoZi Linux Panel is an open source lightweight Linux server operation and maintenance management panel developed using Golang and Vue.
|
||||
The Rat Panel is an open source lightweight Linux server operation and maintenance management panel developed using Golang and Vue.
|
||||
|
||||
Communication QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | Forum: [tom.moe](https://tom.moe) | Sponsor: [Open Collective](https://opencollective.com/tnb)
|
||||
|
||||
@@ -32,7 +32,7 @@ Communication QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | For
|
||||
|
||||
## Operating Environment
|
||||
|
||||
HaoZi Linux Panel only supports the latest version of mainstream systems under the `amd64` | `arm64` architecture. It does not support `Ubuntu` because its releases are too frequent and difficult to maintain.
|
||||
Rat Panel only supports the latest version of mainstream systems under the `amd64` | `arm64` architecture. It does not support `Ubuntu` because its releases are too frequent and difficult to maintain.
|
||||
|
||||
Recommended to use `Debian` for low-configuration machines, as its resource usage is lower than that of the `RHEL` system. For other machines, recommended to use `RockyLinux` | `AlmaLinux`, which has a longer maintenance cycle and is more stable.
|
||||
|
||||
@@ -50,7 +50,7 @@ As system versions are constantly updated, we may also terminate support for som
|
||||
## Install Panel
|
||||
|
||||
> **Warning**
|
||||
> Before installing the panel, you need to understand the basic knowledge of the LNMP environment and how to deal with common LNMP environment problems. It is not recommended for users with zero basic knowledge to install and use HaoZi Linux Panel.
|
||||
> Before installing the panel, you need to understand the basic knowledge of the LNMP environment and how to deal with common LNMP environment problems. It is not recommended for users with zero basic knowledge to install and use Rat Panel.
|
||||
|
||||
If you decide to continue, please log in to the server as `root` user and execute the following command to install the panel:
|
||||
|
||||
@@ -88,7 +88,7 @@ For issues with the panel itself, you can submit feedback on the `Issues` page o
|
||||
|
||||
## Sponsor
|
||||
|
||||
If the HaoZi Linux Panel is helpful to you, welcome to [sponsor us](https://opencollective.com/tnb), thanks to the following supporters/sponsors:
|
||||
If the Rat Panel is helpful to you, welcome to [sponsor us](https://opencollective.com/tnb), thanks to the following supporters/sponsors:
|
||||
|
||||
**Accept cloud resources and financial sponsorship, you can contact us through QQ group**
|
||||
|
||||
@@ -127,4 +127,4 @@ This project owes its existence to all those who have contributed. To contribute
|
||||
|
||||
## Disclaimer
|
||||
|
||||
It is strictly prohibited to use the HaoZi Linux Panel to engage in any illegal activities. Please do not request any form of technical support from us for illegal sites. If illegal content is discovered during the technical support process, we will immediately stop technical support and retain relevant evidence.
|
||||
It is strictly prohibited to use the Rat Panel to engage in any illegal activities. Please do not request any form of technical support from us for illegal sites. If illegal content is discovered during the technical support process, we will immediately stop technical support and retain relevant evidence.
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
## 安全说明
|
||||
|
||||
安全性是我们最关心的问题之一,我们已在多个不同应用的生产环境广泛应用耗子 Linux 面板,至今无一例安全事故。
|
||||
安全性是我们最关心的问题之一,我们已在多个不同应用的生产环境广泛应用耗子面板,至今无一例安全事故。
|
||||
|
||||
耗子 Linux 面板采用业界多种方案尽可能保证面板的安全性,但是我们不能保证面板的绝对安全性,**因此我们不对面板的安全性做任何保证**。
|
||||
耗子面板采用业界多种方案尽可能保证面板的安全性,但是我们不能保证面板的绝对安全性,**因此我们不对面板的安全性做任何保证**。
|
||||
|
||||
如果您在使用面板的过程中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
## Security Policy
|
||||
|
||||
Security is one of our biggest concerns. We have widely used HaoZi Linux Panel in production environments for many different applications, and there has been no safety incident so far.
|
||||
Security is one of our biggest concerns. We have widely used Rat Panel in production environments for many different applications, and there has been no safety incident so far.
|
||||
|
||||
HaoZi Linux Panel adopts various solutions in the industry to ensure the security of the panel as much as possible, but we cannot guarantee the absolute security of the panel, **therefore we do not make any guarantees on the security of the panel**.
|
||||
Rat Panel adopts various solutions in the industry to ensure the security of the panel as much as possible, but we cannot guarantee the absolute security of the panel, **therefore we do not make any guarantees on the security of the panel**.
|
||||
|
||||
If you find any security issues while using the panel, please do not submit an Issue. You can contact us directly through the following methods:
|
||||
|
||||
|
||||
@@ -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,6 +45,7 @@ func (receiver *Monitoring) Handle(console.Context) error {
|
||||
}
|
||||
|
||||
// 将等待中的任务分发
|
||||
// TODO 有bug,需要设计一个锁机制防止重复分发
|
||||
task := services.NewTaskImpl()
|
||||
_ = task.DispatchWaiting()
|
||||
|
||||
@@ -59,14 +60,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
|
||||
|
||||
@@ -17,8 +17,12 @@ import (
|
||||
|
||||
"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 面板命令行
|
||||
@@ -63,14 +67,14 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子 Linux 面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
|
||||
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
|
||||
err = facades.Orm().Query().Create(&settings)
|
||||
if err != nil {
|
||||
color.Red().Printfln(translate.Get("commands.panel.init.fail"))
|
||||
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 +108,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 +129,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 +147,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
|
||||
@@ -164,7 +168,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
|
||||
|
||||
case "getPort":
|
||||
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
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
|
||||
@@ -176,12 +180,12 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
|
||||
|
||||
case "deleteEntrance":
|
||||
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
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 +274,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 +292,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 +311,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 +337,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 +351,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 +397,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 +429,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 +468,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
|
||||
}
|
||||
@@ -610,7 +614,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = website.Add(types.Website{
|
||||
_, err = website.Add(types.WebsiteAdd{
|
||||
Name: name,
|
||||
Status: true,
|
||||
Domains: domains,
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"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 +49,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 +59,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{
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
)
|
||||
|
||||
type AssetController struct {
|
||||
@@ -43,7 +43,7 @@ func (r *AssetController) Index(ctx http.Context) http.Response {
|
||||
path = "/index.html"
|
||||
}
|
||||
|
||||
if !tools.Exists("public" + path) {
|
||||
if !io.Exists("public" + path) {
|
||||
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
|
||||
@@ -88,7 +88,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 +118,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 +126,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 +151,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 +160,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 +180,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 +190,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 +211,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 +219,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 +240,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 +249,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 +270,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 +278,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 +303,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 +312,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 +332,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 +340,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 +361,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 +371,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 +392,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 +401,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 +422,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 +430,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 +455,7 @@ func (r *CertController) CertList(ctx http.Context) http.Response {
|
||||
//
|
||||
// @Summary 添加证书
|
||||
// @Description 添加证书到面板证书管理
|
||||
// @Tags 证书管理
|
||||
// @Tags TLS证书
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
@@ -464,7 +464,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 +484,7 @@ func (r *CertController) CertStore(ctx http.Context) http.Response {
|
||||
//
|
||||
// @Summary 更新证书
|
||||
// @Description 更新面板证书管理的证书
|
||||
// @Tags 证书管理
|
||||
// @Tags TLS证书
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
@@ -494,7 +494,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 +515,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 +523,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 +544,7 @@ func (r *CertController) CertShow(ctx http.Context) http.Response {
|
||||
//
|
||||
// @Summary 删除证书
|
||||
// @Description 删除面板证书管理的证书
|
||||
// @Tags 证书管理
|
||||
// @Tags TLS证书
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
@@ -553,7 +553,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 +574,7 @@ func (r *CertController) CertDestroy(ctx http.Context) http.Response {
|
||||
//
|
||||
// @Summary 签发证书
|
||||
// @Description 签发面板证书管理的证书
|
||||
// @Tags 证书管理
|
||||
// @Tags TLS证书
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
@@ -583,7 +583,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 +616,7 @@ func (r *CertController) Obtain(ctx http.Context) http.Response {
|
||||
//
|
||||
// @Summary 续签证书
|
||||
// @Description 续签面板证书管理的证书
|
||||
// @Tags 证书管理
|
||||
// @Tags TLS证书
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
@@ -625,7 +625,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 +645,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 +654,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 +674,7 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
|
||||
//
|
||||
// @Summary 部署证书
|
||||
// @Description 部署面板证书管理的证书
|
||||
// @Tags 证书管理
|
||||
// @Tags TLS证书
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
@@ -683,7 +683,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
|
||||
}
|
||||
|
||||
@@ -201,6 +181,10 @@ func (r *ContainerController) ContainerCreate(ctx http.Context) http.Response {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err = r.container.ContainerStart(id); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, id)
|
||||
}
|
||||
|
||||
@@ -216,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
|
||||
}
|
||||
|
||||
@@ -239,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
|
||||
}
|
||||
|
||||
@@ -262,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
|
||||
}
|
||||
|
||||
@@ -285,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
|
||||
}
|
||||
|
||||
@@ -307,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
|
||||
}
|
||||
|
||||
@@ -331,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
|
||||
}
|
||||
|
||||
@@ -354,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
|
||||
}
|
||||
|
||||
@@ -378,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
|
||||
}
|
||||
|
||||
@@ -401,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
|
||||
}
|
||||
|
||||
@@ -424,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
|
||||
}
|
||||
|
||||
@@ -448,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
|
||||
}
|
||||
|
||||
@@ -472,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
|
||||
}
|
||||
|
||||
@@ -512,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 {
|
||||
@@ -570,7 +535,7 @@ func (r *ContainerController) NetworkList(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
"total": len(networks),
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
@@ -588,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
|
||||
}
|
||||
|
||||
@@ -612,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
|
||||
}
|
||||
|
||||
@@ -635,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
|
||||
}
|
||||
|
||||
@@ -659,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
|
||||
}
|
||||
|
||||
@@ -684,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
|
||||
}
|
||||
|
||||
@@ -708,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
|
||||
}
|
||||
|
||||
@@ -747,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,
|
||||
@@ -787,7 +733,7 @@ func (r *ContainerController) ImageList(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
"total": len(images),
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
@@ -804,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
|
||||
}
|
||||
|
||||
@@ -829,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
|
||||
}
|
||||
|
||||
@@ -852,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
|
||||
}
|
||||
|
||||
@@ -892,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
|
||||
}
|
||||
|
||||
@@ -915,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{
|
||||
@@ -964,7 +891,7 @@ func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
"total": len(volumes),
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
@@ -982,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
|
||||
}
|
||||
|
||||
@@ -1006,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
|
||||
}
|
||||
|
||||
@@ -1030,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
|
||||
}
|
||||
|
||||
@@ -1054,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 通用成功响应
|
||||
@@ -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,10 +80,10 @@ 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
|
||||
|
||||
# 耗子 Linux 面板 - 数据备份脚本
|
||||
# 耗子面板 - 数据备份脚本
|
||||
|
||||
type=` + backupType + `
|
||||
path=` + backupPath + `
|
||||
@@ -99,10 +97,10 @@ 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
|
||||
|
||||
# 耗子 Linux 面板 - 日志切割脚本
|
||||
# 耗子面板 - 日志切割脚本
|
||||
|
||||
name=` + website + `
|
||||
save=` + cast.ToString(save) + `
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"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,8 +110,8 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
|
||||
}
|
||||
var databaseCount int64
|
||||
if mysqlInstalled {
|
||||
status, err := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if status == "active" && err == nil {
|
||||
status, err := systemctl.Status("mysqld")
|
||||
if status && err == nil {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
type database struct {
|
||||
Name string `json:"name"`
|
||||
@@ -150,9 +152,9 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
if postgresqlInstalled {
|
||||
status, err := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
|
||||
if status == "active" && err == nil {
|
||||
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
|
||||
status, err := systemctl.Status("postgresql")
|
||||
if status && err == nil {
|
||||
raw, err := shell.Execf(`echo "\l" | su - postgres -c "psql"`)
|
||||
if err == nil {
|
||||
databases := strings.Split(raw, "\n")
|
||||
if len(databases) >= 4 {
|
||||
@@ -173,7 +175,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))
|
||||
|
||||
@@ -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,61 +75,81 @@ 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")
|
||||
|
||||
if err := r.plugin.Install(slug); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
if err := r.plugin.Uninstall(slug); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
if err := r.plugin.Update(slug); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
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.Exec("systemctl reload 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())
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/plugins/gitea"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type GiteaController struct {
|
||||
}
|
||||
|
||||
func NewGiteaController() *GiteaController {
|
||||
return &GiteaController{}
|
||||
}
|
||||
|
||||
// GetConfig
|
||||
//
|
||||
// @Summary 获取配置
|
||||
// @Description 获取 Gitea 配置
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/config [get]
|
||||
func (r *GiteaController) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := io.Read("/www/server/gitea/app.ini")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateConfig
|
||||
//
|
||||
// @Summary 更新配置
|
||||
// @Description 更新 Gitea 配置
|
||||
// @Tags 插件-Gitea
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/gitea/config [post]
|
||||
func (r *GiteaController) UpdateConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateConfig
|
||||
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := io.Write("/www/server/gitea/app.ini", updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := systemctl.Restart("gitea"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
@@ -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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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())
|
||||
}); 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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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{
|
||||
if sanitize := controllers.Sanitize(ctx, 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())
|
||||
}); 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"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,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 +21,66 @@ 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 {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
|
||||
}
|
||||
|
||||
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 +88,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 +151,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 +167,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,6 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -10,7 +9,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 +23,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 +38,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,40 +54,40 @@ 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 {
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
requests "github.com/TheTNB/panel/app/http/requests/plugins/podman"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type PodmanController struct {
|
||||
}
|
||||
|
||||
func NewPodmanController() *PodmanController {
|
||||
return &PodmanController{}
|
||||
}
|
||||
|
||||
// GetRegistryConfig
|
||||
//
|
||||
// @Summary 获取注册表配置
|
||||
// @Description 获取 Podman 注册表配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/registryConfig [get]
|
||||
func (r *PodmanController) GetRegistryConfig(ctx http.Context) http.Response {
|
||||
config, err := io.Read("/etc/containers/registries.conf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateRegistryConfig
|
||||
//
|
||||
// @Summary 更新注册表配置
|
||||
// @Description 更新 Podman 注册表配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateRegistryConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/registryConfig [post]
|
||||
func (r *PodmanController) UpdateRegistryConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateRegistryConfig
|
||||
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := io.Write("/etc/containers/registries.conf", updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := systemctl.Restart("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// GetStorageConfig
|
||||
//
|
||||
// @Summary 获取存储配置
|
||||
// @Description 获取 Podman 存储配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/storageConfig [get]
|
||||
func (r *PodmanController) GetStorageConfig(ctx http.Context) http.Response {
|
||||
config, err := io.Read("/etc/containers/storage.conf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateStorageConfig
|
||||
//
|
||||
// @Summary 更新存储配置
|
||||
// @Description 更新 Podman 存储配置
|
||||
// @Tags 插件-Podman
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param data body requests.UpdateStorageConfig true "request"
|
||||
// @Success 200 {object} controllers.SuccessResponse
|
||||
// @Router /plugins/podman/storageConfig [post]
|
||||
func (r *PodmanController) UpdateStorageConfig(ctx http.Context) http.Response {
|
||||
var updateRequest requests.UpdateStorageConfig
|
||||
sanitize := controllers.SanitizeRequest(ctx, &updateRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := io.Write("/etc/containers/storage.conf", updateRequest.Config, 0644); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := systemctl.Restart("podman"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
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 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: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",
|
||||
}); 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:255|regex:^[a-zA-Z][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:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
}); 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:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
}); 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: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",
|
||||
}); 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:255|regex:^[a-zA-Z][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:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:255",
|
||||
}); 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,20 +4,29 @@ 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 {
|
||||
// Dependent services
|
||||
ssh string
|
||||
}
|
||||
|
||||
func NewSafeController() *SafeController {
|
||||
var ssh string
|
||||
if os.IsRHEL() {
|
||||
ssh = "sshd"
|
||||
} else {
|
||||
ssh = "ssh"
|
||||
}
|
||||
|
||||
return &SafeController{
|
||||
// Inject services
|
||||
ssh: ssh,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,24 +37,41 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) http.Response {
|
||||
|
||||
// SetFirewallStatus 设置防火墙状态
|
||||
func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
|
||||
var out string
|
||||
var err error
|
||||
if ctx.Request().InputBool("status") {
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl start firewalld")
|
||||
if os.IsRHEL() {
|
||||
err = systemctl.Start("firewalld")
|
||||
if err == nil {
|
||||
err = systemctl.Enable("firewalld")
|
||||
}
|
||||
} else {
|
||||
out, 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() {
|
||||
out, err = tools.Exec("systemctl stop firewalld")
|
||||
if os.IsRHEL() {
|
||||
err = systemctl.Stop("firewalld")
|
||||
if err == nil {
|
||||
err = systemctl.Disable("firewalld")
|
||||
}
|
||||
} else {
|
||||
out, err = tools.Exec("ufw disable")
|
||||
_, err = shell.Execf("ufw disable")
|
||||
if err == nil {
|
||||
err = systemctl.Stop("ufw")
|
||||
}
|
||||
if err == nil {
|
||||
err = systemctl.Disable("ufw")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -53,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)
|
||||
}
|
||||
@@ -86,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)
|
||||
}
|
||||
@@ -106,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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -151,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 {
|
||||
@@ -166,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)
|
||||
}
|
||||
}
|
||||
@@ -186,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)
|
||||
}
|
||||
}
|
||||
@@ -213,27 +222,11 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
|
||||
|
||||
// firewallStatus 获取防火墙状态
|
||||
func (r *SafeController) firewallStatus() bool {
|
||||
var out string
|
||||
var err error
|
||||
var running bool
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl status firewalld | grep Active | awk '{print $3}'")
|
||||
if out == "(running)" {
|
||||
running = true
|
||||
} else {
|
||||
running = false
|
||||
}
|
||||
if os.IsRHEL() {
|
||||
running, _ = systemctl.Status("firewalld")
|
||||
} else {
|
||||
out, err = tools.Exec("ufw status | grep Status | awk '{print $2}'")
|
||||
if out == "active" {
|
||||
running = true
|
||||
} else {
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
running, _ = systemctl.Status("ufw")
|
||||
}
|
||||
|
||||
return running
|
||||
@@ -241,54 +234,29 @@ func (r *SafeController) firewallStatus() bool {
|
||||
|
||||
// GetSshStatus 获取 SSH 状态
|
||||
func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
|
||||
var out string
|
||||
var err error
|
||||
if tools.IsRHEL() {
|
||||
out, err = tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
|
||||
} else {
|
||||
out, err = tools.Exec("systemctl status ssh | grep Active | awk '{print $3}'")
|
||||
}
|
||||
|
||||
running, err := systemctl.Status(r.ssh)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, out == "(running)")
|
||||
return Success(ctx, running)
|
||||
}
|
||||
|
||||
// SetSshStatus 设置 SSH 状态
|
||||
func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
|
||||
if ctx.Request().InputBool("status") {
|
||||
if tools.IsRHEL() {
|
||||
if out, err := tools.Exec("systemctl enable sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl start sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
} else {
|
||||
if out, err := tools.Exec("systemctl enable ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl start ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if err := systemctl.Enable(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err := systemctl.Start(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
} else {
|
||||
if tools.IsRHEL() {
|
||||
if out, err := tools.Exec("systemctl stop sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl disable sshd"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
} else {
|
||||
if out, err := tools.Exec("systemctl stop ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := tools.Exec("systemctl disable ssh"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if err := systemctl.Stop(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err := systemctl.Disable(r.ssh); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,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)
|
||||
}
|
||||
@@ -312,26 +280,25 @@ 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)
|
||||
|
||||
out, err := tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
|
||||
if err != nil || out != "(running)" {
|
||||
Error(ctx, http.StatusInternalServerError, out)
|
||||
status, _ := systemctl.Status(r.ssh)
|
||||
if status {
|
||||
_ = systemctl.Restart(r.ssh)
|
||||
}
|
||||
|
||||
_, _ = tools.Exec("systemctl restart sshd")
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -342,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())
|
||||
}
|
||||
@@ -358,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`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,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 {
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"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 +28,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)
|
||||
@@ -50,7 +53,7 @@ func (r *SettingController) List(ctx http.Context) http.Response {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
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(),
|
||||
@@ -74,18 +77,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 +100,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 +112,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)
|
||||
}
|
||||
}
|
||||
@@ -147,7 +149,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 +159,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 +194,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 +207,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(facades.App().ExecutablePath("storage/ssl.crt"), httpsRequest.Cert, 0700); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
if err := io.Write(facades.App().ExecutablePath("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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "日志已被清理")
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewUserController() *UserController {
|
||||
// @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
|
||||
}
|
||||
|
||||
@@ -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/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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,7 +84,7 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
|
||||
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
|
||||
}
|
||||
|
||||
website := types.Website{
|
||||
website := types.WebsiteAdd{
|
||||
Name: addRequest.Name,
|
||||
Status: true,
|
||||
Domains: addRequest.Domains,
|
||||
@@ -112,18 +112,17 @@ 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 id path int true "网站 ID"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/websites/{id} [delete]
|
||||
func (r *WebsiteController) Delete(ctx http.Context) http.Response {
|
||||
var idRequest requests.ID
|
||||
sanitize := Sanitize(ctx, &idRequest)
|
||||
sanitize := SanitizeRequest(ctx, &idRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -142,19 +141,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,27 +165,26 @@ 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)
|
||||
}
|
||||
|
||||
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("保存默认停止页配置失败")
|
||||
@@ -199,18 +196,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
|
||||
}
|
||||
@@ -229,19 +225,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 +251,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 +272,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 +281,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,22 +316,21 @@ 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(),
|
||||
@@ -346,42 +338,27 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
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 +385,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 +400,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
|
||||
}
|
||||
}
|
||||
@@ -444,18 +420,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 +454,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 +485,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
|
||||
}
|
||||
@@ -576,9 +549,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 +575,18 @@ 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 exec, err := tools.Exec("systemctl reload openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, exec)
|
||||
if err := io.Write("/www/server/vhost/acme/"+website.Name+".conf", "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -615,18 +594,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 +619,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 +637,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,11 +648,11 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
|
||||
if err = io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
if exec, err := tools.Exec("systemctl reload openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, exec)
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
|
||||
@@ -18,6 +18,8 @@ func MustInstall() http.Middleware {
|
||||
var slug string
|
||||
if strings.HasPrefix(path, "/api/panel/website") {
|
||||
slug = "openresty"
|
||||
} else if strings.HasPrefix(path, "/api/panel/container") {
|
||||
slug = "podman"
|
||||
} else {
|
||||
pathArr := strings.Split(path, "/")
|
||||
if len(pathArr) < 4 {
|
||||
|
||||
@@ -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 检查程序状态
|
||||
|
||||
@@ -3,16 +3,15 @@ package requests
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type CertStore struct {
|
||||
Type string `form:"type" json:"type"`
|
||||
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"`
|
||||
UserID uint `form:"user_id" json:"user_id" filter:"uint"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id" filter:"uint"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
|
||||
}
|
||||
|
||||
func (r *CertStore) Authorize(ctx http.Context) error {
|
||||
@@ -39,29 +38,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 {
|
||||
@@ -11,9 +10,9 @@ type CertUpdate struct {
|
||||
Type string `form:"type" json:"type"`
|
||||
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"`
|
||||
UserID uint `form:"user_id" json:"user_id" filter:"uint"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id" filter:"uint"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
|
||||
}
|
||||
|
||||
func (r *CertUpdate) Authorize(ctx http.Context) error {
|
||||
@@ -41,29 +40,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
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ 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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ 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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -17,8 +17,8 @@ func (r *Archive) Authorize(ctx http.Context) error {
|
||||
func (r *Archive) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"paths": "array",
|
||||
"paths.*": `regex:^/[a-zA-Z0-9_.@#$%\-\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`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ func (r *Copy) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Copy) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"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:^/.*$`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ func (r *Exist) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Exist) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/([a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*)?$|path_exists`,
|
||||
"path": `regex:^/.*$|path_exists`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ func (r *Move) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Move) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"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:^/.*$`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ func (r *NotExist) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *NotExist) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
|
||||
"path": `regex:^/.*$|path_not_exists`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +18,8 @@ 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_-]+$",
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func (r *Save) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Save) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"path": `regex:^/.*$|path_exists`,
|
||||
"content": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func (r *Search) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Search) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
|
||||
"path": `regex:^/.*$|path_exists`,
|
||||
"keyword": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"file": `regex:^/[a-zA-Z0-9_.@#$%\-\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:^/.*$`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func (r *Upload) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *Upload) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"path": `regex:^/.*$`,
|
||||
"file": "required",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateRegistryConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type UpdateStorageConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"config": "required|string",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func (r *Create) Authorize(ctx http.Context) error {
|
||||
func (r *Create) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"name": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"path": `regex:^/.*$`,
|
||||
"comment": "string",
|
||||
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"secret": "required|min_len:8",
|
||||
|
||||
@@ -21,7 +21,7 @@ func (r *Update) Authorize(ctx http.Context) error {
|
||||
func (r *Update) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"name": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
|
||||
"path": `regex:^/.*$`,
|
||||
"comment": "string",
|
||||
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
|
||||
"secret": "required|min_len:8",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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) 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
|
||||
}
|
||||
@@ -12,7 +12,6 @@ type Update struct {
|
||||
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,7 +29,6 @@ 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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -9,6 +9,7 @@ type SaveConfig struct {
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
Domains []string `form:"domains" json:"domains"`
|
||||
Ports []uint `form:"ports" json:"ports"`
|
||||
TLSPorts []uint `form:"tls_ports" json:"tls_ports"`
|
||||
Hsts bool `form:"hsts" json:"hsts"`
|
||||
Ssl bool `form:"ssl" json:"ssl"`
|
||||
HttpRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
@@ -36,6 +37,7 @@ func (r *SaveConfig) Rules(ctx http.Context) map[string]string {
|
||||
"id": "required|exists:websites,id",
|
||||
"domains": "required|slice",
|
||||
"ports": "required|slice",
|
||||
"tls_ports": "required_if:ssl,true|slice|not_in:80",
|
||||
"hsts": "bool",
|
||||
"ssl": "bool",
|
||||
"http_redirect": "bool",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
)
|
||||
|
||||
// ProcessTask 处理面板任务
|
||||
@@ -27,20 +27,11 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
if err := facades.Orm().Query().Where("id = ?", taskID).Get(&task); err != nil {
|
||||
_ = facades.Orm().Query().Where("id = ?", taskID).Get(&task)
|
||||
if task.ID == 0 {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("获取任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
task.Status = models.TaskStatusRunning
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
}).Infof("任务不存在")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -48,15 +39,9 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
"task_id": taskID,
|
||||
}).Infof("开始执行任务")
|
||||
|
||||
if _, err := tools.Exec(task.Shell); err != nil {
|
||||
if _, err := shell.Execf(task.Shell); err != nil {
|
||||
task.Status = models.TaskStatusFailed
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
_ = facades.Orm().Query().Save(&task)
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
@@ -65,13 +50,7 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
}
|
||||
|
||||
task.Status = models.TaskStatusSuccess
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
_ = facades.Orm().Query().Save(&task)
|
||||
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
|
||||
+11
-13
@@ -1,22 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"default:null" json:"user_id"` // 关联的 ACME 用户 ID
|
||||
WebsiteID *uint `gorm:"default:null" json:"website_id"` // 关联的网站 ID
|
||||
DNSID *uint `gorm:"column:dns_id;default:null" json:"dns_id"` // 关联的 DNS ID
|
||||
Type string `gorm:"not null" json:"type"` // 证书类型 (P256, P384, 2048, 4096)
|
||||
Domains []string `gorm:"type:json;serializer:json" json:"domains"`
|
||||
AutoRenew bool `gorm:"default:true" json:"auto_renew"` // 自动续签
|
||||
CertURL *string `gorm:"default:null" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"default:null" json:"cert"` // 证书内容
|
||||
Key string `gorm:"default:null" json:"key"` // 私钥内容
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
UserID uint `gorm:"not null" json:"user_id"` // 关联的 ACME 用户 ID
|
||||
WebsiteID uint `gorm:"not null" json:"website_id"` // 关联的网站 ID
|
||||
DNSID uint `gorm:"not null" json:"dns_id"` // 关联的 DNS ID
|
||||
Type string `gorm:"not null" json:"type"` // 证书类型 (P256, P384, 2048, 4096)
|
||||
Domains []string `gorm:"not null;serializer:json" json:"domains"`
|
||||
AutoRenew bool `gorm:"not null" json:"auto_renew"` // 自动续签
|
||||
CertURL string `gorm:"not null" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"not null" json:"cert"` // 证书内容
|
||||
Key string `gorm:"not null" json:"key"` // 私钥内容
|
||||
|
||||
Website *Website `gorm:"foreignKey:WebsiteID" json:"website"`
|
||||
User *CertUser `gorm:"foreignKey:UserID" json:"user"`
|
||||
|
||||
+5
-11
@@ -1,22 +1,16 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/database/orm"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type CertDNS struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"` // 备注名称
|
||||
Type string `gorm:"not null" json:"type"` // DNS 提供商 (dnspod, aliyun, cloudflare)
|
||||
Data acme.DNSParam `gorm:"type:json;serializer:json" json:"dns_param"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Name string `gorm:"not null" json:"name"` // 备注名称
|
||||
Type string `gorm:"not null" json:"type"` // DNS 提供商 (dnspod, aliyun, cloudflare)
|
||||
Data acme.DNSParam `gorm:"not null;serializer:json" json:"dns_param"`
|
||||
|
||||
Certs []*Cert `gorm:"foreignKey:DNSID" json:"-"`
|
||||
}
|
||||
|
||||
func (CertDNS) TableName() string {
|
||||
return "cert_dns"
|
||||
}
|
||||
|
||||
+8
-12
@@ -1,19 +1,15 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
)
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
type CertUser struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Email string `gorm:"not null" json:"email"`
|
||||
CA string `gorm:"not null" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
|
||||
Kid *string `gorm:"default:null" json:"kid"`
|
||||
HmacEncoded *string `gorm:"default:null" json:"hmac_encoded"`
|
||||
PrivateKey string `gorm:"not null" json:"private_key"`
|
||||
KeyType string `gorm:"not null" json:"key_type"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Email string `gorm:"not null" json:"email"`
|
||||
CA string `gorm:"not null" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
|
||||
Kid string `gorm:"not null" json:"kid"`
|
||||
HmacEncoded string `gorm:"not null" json:"hmac_encoded"`
|
||||
PrivateKey string `gorm:"not null" json:"private_key"`
|
||||
KeyType string `gorm:"not null" json:"key_type"`
|
||||
|
||||
Certs []*Cert `gorm:"foreignKey:UserID" json:"-"`
|
||||
}
|
||||
|
||||
+8
-12
@@ -1,17 +1,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
)
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
type Cron struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Status bool `gorm:"not null;default:false" json:"status"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Time string `gorm:"not null" json:"time"`
|
||||
Shell string `gorm:"default:''" json:"shell"`
|
||||
Log string `gorm:"default:''" json:"log"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Status bool `gorm:"not null" json:"status"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Time string `gorm:"not null" json:"time"`
|
||||
Shell string `gorm:"not null" json:"shell"`
|
||||
Log string `gorm:"not null" json:"log"`
|
||||
}
|
||||
|
||||
+9
-11
@@ -1,16 +1,14 @@
|
||||
package models
|
||||
|
||||
import "github.com/goravel/framework/support/carbon"
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
type Database struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"unique;not null" json:"name"`
|
||||
Type string `gorm:"not null;index" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port int `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"default:''" json:"password"`
|
||||
Remark string `gorm:"default:''" json:"remark"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port int `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/database/orm"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Info tools.MonitoringInfo `gorm:"type:json;serializer:json" json:"info"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Info tools.MonitoringInfo `gorm:"not null;serializer:json" json:"info"`
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package models
|
||||
|
||||
import "github.com/goravel/framework/support/carbon"
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
type Plugin struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Slug string `gorm:"unique;not null" json:"slug"`
|
||||
Version string `gorm:"not null" json:"version"`
|
||||
Show bool `gorm:"default:false;not null" json:"show"`
|
||||
ShowOrder int `gorm:"default:0;not null" json:"show_order"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Slug string `gorm:"not null;unique" json:"slug"`
|
||||
Version string `gorm:"not null" json:"version"`
|
||||
Show bool `gorm:"not null" json:"show"`
|
||||
ShowOrder int `gorm:"not null" json:"show_order"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package models
|
||||
|
||||
import "github.com/goravel/framework/support/carbon"
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
const (
|
||||
SettingKeyName = "name"
|
||||
@@ -17,9 +17,7 @@ const (
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Key string `gorm:"unique;not null" json:"key"`
|
||||
Value string `gorm:"default:''" json:"value"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Key string `gorm:"not null;unique" json:"key"`
|
||||
Value string `gorm:"not null" json:"value"`
|
||||
}
|
||||
|
||||
+6
-10
@@ -1,8 +1,6 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
)
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
const (
|
||||
TaskStatusWaiting = "waiting"
|
||||
@@ -12,11 +10,9 @@ const (
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Status string `gorm:"not null;default:'waiting'" json:"status"`
|
||||
Shell string `gorm:"default:''" json:"shell"`
|
||||
Log string `gorm:"default:''" json:"log"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Name string `gorm:"not null;index" json:"name"`
|
||||
Status string `gorm:"not null;default:'waiting'" json:"status"`
|
||||
Shell string `gorm:"not null" json:"shell"`
|
||||
Log string `gorm:"not null" json:"log"`
|
||||
}
|
||||
|
||||
+5
-7
@@ -1,12 +1,10 @@
|
||||
package models
|
||||
|
||||
import "github.com/goravel/framework/support/carbon"
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"unique;not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Email string `gorm:"default:''" json:"email"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Username string `gorm:"not null;unique" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Email string `gorm:"not null" json:"email"`
|
||||
}
|
||||
|
||||
+8
-12
@@ -1,19 +1,15 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
)
|
||||
import "github.com/goravel/framework/database/orm"
|
||||
|
||||
type Website struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status bool `gorm:"default:true" json:"status"`
|
||||
Path string `json:"path"`
|
||||
Php int `gorm:"default:0;not null;index" json:"php"`
|
||||
Ssl bool `gorm:"default:false;not null;index" json:"ssl"`
|
||||
Remark string `gorm:"default:''" json:"remark"`
|
||||
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
|
||||
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
|
||||
orm.Model
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Status bool `gorm:"not null;default:true" json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
Php int `gorm:"not null" json:"php"`
|
||||
Ssl bool `gorm:"not null" json:"ssl"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
|
||||
Cert *Cert `gorm:"foreignKey:WebsiteID" json:"cert"`
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ package providers
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/database/seeder"
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/database/gorm"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/database/seeders"
|
||||
"github.com/TheTNB/panel/pkg/migrate"
|
||||
)
|
||||
|
||||
type DatabaseServiceProvider struct {
|
||||
@@ -16,7 +17,6 @@ func (receiver *DatabaseServiceProvider) Register(app foundation.Application) {
|
||||
}
|
||||
|
||||
func (receiver *DatabaseServiceProvider) Boot(app foundation.Application) {
|
||||
facades.Seeder().Register([]seeder.Seeder{
|
||||
&seeders.DatabaseSeeder{},
|
||||
})
|
||||
facades.Seeder().Register([]seeder.Seeder{})
|
||||
migrate.Migrate(facades.Orm().Query().(*gorm.QueryImpl).Instance())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
contractshttp "github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/http/limit"
|
||||
|
||||
"github.com/TheTNB/panel/app/http"
|
||||
"github.com/TheTNB/panel/routes"
|
||||
@@ -25,5 +27,23 @@ func (receiver *RouteServiceProvider) Boot(app foundation.Application) {
|
||||
}
|
||||
|
||||
func (receiver *RouteServiceProvider) configureRateLimiting() {
|
||||
|
||||
facades.RateLimiter().ForWithLimits("login", func(ctx contractshttp.Context) []contractshttp.Limit {
|
||||
return []contractshttp.Limit{
|
||||
limit.PerMinute(5).By(ctx.Request().Ip()).Response(func(ctx contractshttp.Context) {
|
||||
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
|
||||
"message": "请求过于频繁,请等待一分钟后再试",
|
||||
})
|
||||
}),
|
||||
limit.PerHour(100).By(ctx.Request().Ip()).Response(func(ctx contractshttp.Context) {
|
||||
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
|
||||
"message": "请求过于频繁,请等待一小时后再试",
|
||||
})
|
||||
}),
|
||||
limit.PerDay(1000).Response(func(ctx contractshttp.Context) {
|
||||
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
|
||||
"message": "面板遭受登录爆破攻击过多,已暂时屏蔽登录,请立刻更换面板端口",
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
)
|
||||
|
||||
type PathExists struct {
|
||||
@@ -27,7 +28,7 @@ func (receiver *PathExists) Passes(_ validation.Data, val any, options ...any) b
|
||||
return false
|
||||
}
|
||||
|
||||
return tools.Exists(requestValue)
|
||||
return io.Exists(requestValue)
|
||||
}
|
||||
|
||||
// Message Get the validation error message.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
)
|
||||
|
||||
type PathNotExists struct {
|
||||
@@ -27,7 +28,7 @@ func (receiver *PathNotExists) Passes(_ validation.Data, val any, options ...any
|
||||
return false
|
||||
}
|
||||
|
||||
return !tools.Exists(requestValue)
|
||||
return !io.Exists(requestValue)
|
||||
}
|
||||
|
||||
// Message Get the validation error message.
|
||||
|
||||
@@ -70,6 +70,12 @@ func init() {
|
||||
// the language folders that are provided through your application.
|
||||
"fallback_locale": "zh_CN",
|
||||
|
||||
// Application Lang Path
|
||||
//
|
||||
// The path to the language files for the application. You may change
|
||||
// the path to a different directory if you would like to customize it.
|
||||
"lang_path": facades.App().ExecutablePath("lang"),
|
||||
|
||||
// Encryption Key
|
||||
//
|
||||
// 32 character string, otherwise these encrypted strings
|
||||
|
||||
+2
-2
@@ -14,10 +14,10 @@ func init() {
|
||||
// in web browsers. You are free to adjust these settings as needed.
|
||||
//
|
||||
// To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
"paths": []string{"*"},
|
||||
"paths": []string{"1145141919810"}, // 避免框架使用CORS中间件
|
||||
"allowed_methods": []string{"*"},
|
||||
"allowed_origins": []string{"*"},
|
||||
"allowed_headers": []string{"*"},
|
||||
"allowed_headers": []string{"“"},
|
||||
"exposed_headers": []string{""},
|
||||
"max_age": 0,
|
||||
"supports_credentials": false,
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ func init() {
|
||||
"connections": map[string]any{
|
||||
"panel": map[string]any{
|
||||
"driver": "sqlite",
|
||||
"database": config.Env("DB_FILE", "/www/panel/storage/panel.db"),
|
||||
"database": config.Env("DB_FILE", facades.App().ExecutablePath("storage/panel.db")),
|
||||
"prefix": "",
|
||||
"singular": false, // Table name is singular
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
"disks": map[string]any{
|
||||
"local": map[string]any{
|
||||
"driver": "local",
|
||||
"root": "/www/panel/storage",
|
||||
"root": facades.App().ExecutablePath("storage"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+2
-2
@@ -39,9 +39,9 @@ func init() {
|
||||
// SSL Certificate
|
||||
"ssl": map[string]any{
|
||||
// ca.pem
|
||||
"cert": config.Env("APP_SSL_CERT", "/www/panel/storage/ssl.crt"),
|
||||
"cert": config.Env("APP_SSL_CERT", facades.App().ExecutablePath("storage/ssl.crt")),
|
||||
// ca.key
|
||||
"key": config.Env("APP_SSL_KEY", "/www/panel/storage/ssl.key"),
|
||||
"key": config.Env("APP_SSL_KEY", facades.App().ExecutablePath("storage/ssl.key")),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ import (
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("panel", map[string]any{
|
||||
"name": "耗子 Linux 面板",
|
||||
"version": "v2.2.4",
|
||||
"name": "耗子面板",
|
||||
"version": "v2.2.16",
|
||||
"ssl": config.Env("APP_SSL", false),
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user