mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 20:05:34 +08:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f65cf9e38e | |||
| 4a8ebfbdfa | |||
| fee0fcb998 | |||
| 1e081fcdf2 | |||
| 6c6fb09270 | |||
| 51b47818c0 | |||
| 5b393819ba | |||
| f807d0cd1a | |||
| 0d6f0f317d | |||
| 6bad7f4b5b | |||
| 7a7134b26a | |||
| 28cf7098ff | |||
| b56979147e | |||
| 2ec0d9e844 | |||
| 66641a626e | |||
| 95bfd6843b | |||
| 7e28a34b7f | |||
| b2152edeeb | |||
| 00815979d2 | |||
| c4e01b37b7 | |||
| 7907345521 | |||
| 8f32d6cdb0 | |||
| c3582903bf | |||
| 7d25d2f5aa | |||
| 46fc7b1c6a | |||
| 34ac62d1ae | |||
| 30e78ccf46 | |||
| 22f4cc7fcb | |||
| 1771ae682d | |||
| d1beb60ffd | |||
| cba5f86667 | |||
| e1d9832ce0 | |||
| 6278535902 | |||
| 32fb87243b | |||
| 5f85e7fbe0 | |||
| c36104a075 | |||
| 583f5d13c7 | |||
| b453738daf | |||
| 98431fb4b2 | |||
| 4d76e9286d | |||
| c70758339f | |||
| ff524cdb0b | |||
| 73c95d880a | |||
| 8a1d8c0b05 | |||
| 2251175906 | |||
| 69edde98c8 | |||
| 29a3db640d | |||
| ee87bf65b4 | |||
| aa080dab15 | |||
| b4e5498b48 | |||
| 63838bffa1 | |||
| fa41b18ced | |||
| 31bd15217b | |||
| e6b6f06706 | |||
| 1b836297bf | |||
| 96bea2cf90 | |||
| 8aff513284 | |||
| 368e41a512 | |||
| 924fdee7c5 | |||
| 438ba1db1e | |||
| d4e52a2d87 | |||
| 4432a12c7a | |||
| a75e36e447 | |||
| 05674ee04e | |||
| 09703761eb | |||
| dfa14c3fd4 | |||
| 7b80f034ea | |||
| 71923093fd | |||
| aae3cf0518 | |||
| ed22e79552 | |||
| 15dd5bb718 |
@@ -19,11 +19,9 @@ jobs:
|
||||
go-version: '1.22'
|
||||
- name: Fetch Frontend
|
||||
run: |
|
||||
sudo apt-get install -y curl jq unzip zip
|
||||
curl -sSL https://api.github.com/repos/TheTNB/panel-frontend/releases/latest | jq -r ".assets[] | select(.name | contains(\"dist\")) | .browser_download_url" | xargs curl -L -o frontend.zip
|
||||
rm -rf public
|
||||
unzip frontend.zip
|
||||
mv dist public
|
||||
mv dist/* embed/frontend/
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Issue Close Question
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
issue-close-require:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: needs more info
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'close-issues'
|
||||
labels: 'question'
|
||||
inactive-day: 3
|
||||
body: |
|
||||
由于该 Issue 3天未收到回应,现已被自动关闭,若有任何问题,可评论回复。
|
||||
This issue has been closed automatically because it has not had recent activity for 3 days. If you have any questions, please comment here.
|
||||
@@ -67,8 +67,3 @@ out/
|
||||
*.sublime*
|
||||
__debug_bin
|
||||
.project
|
||||
|
||||
# 前端工具链 #
|
||||
.sass-cache/*
|
||||
node_modules/
|
||||
/public
|
||||
|
||||
+2
-3
@@ -62,13 +62,12 @@ fetch:
|
||||
- apk add --no-cache curl jq unzip zip
|
||||
script:
|
||||
- curl -sSL "https://git.haozi.net/api/v4/projects/opensource%2Fpanel-frontend/releases" | jq -r '.[0].assets.links[] | select(.name | contains("dist")) | .direct_asset_url' | xargs curl -L -o frontend.zip
|
||||
- rm -rf public
|
||||
- unzip frontend.zip
|
||||
- mv dist public
|
||||
- mv dist/* embed/frontend/
|
||||
artifacts:
|
||||
name: "frontend"
|
||||
paths:
|
||||
- public
|
||||
- embed/frontend
|
||||
expire_in: 3 days
|
||||
|
||||
release:
|
||||
|
||||
@@ -40,7 +40,6 @@ archives:
|
||||
files:
|
||||
- LICENSE
|
||||
- docs/*
|
||||
- public/*
|
||||
- storage/*
|
||||
- lang/*
|
||||
- scripts/*
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
[简体中文] | [<a href="README_EN.md">English</a>]
|
||||
</p>
|
||||
|
||||
<h1 align="center">耗子面板</h1>
|
||||
<h1 align="center" style="font-size: 40px">耗子面板</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
|
||||
|
||||
+5
-1
@@ -2,7 +2,11 @@
|
||||
[<a href="README.md">简体中文</a>] | [English]
|
||||
</p>
|
||||
|
||||
<h1 align="center">Rat Panel</h1>
|
||||
<h1 align="center" style="font-size: 40px">Rat Panel</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/10950" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10950" alt="TheTNB%2Fpanel | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
|
||||
|
||||
@@ -45,9 +45,8 @@ func (receiver *Monitoring) Handle(console.Context) error {
|
||||
}
|
||||
|
||||
// 将等待中的任务分发
|
||||
// TODO 有bug,需要设计一个锁机制防止重复分发
|
||||
//task := services.NewTaskImpl()
|
||||
//_ = task.DispatchWaiting()
|
||||
task := services.NewTaskImpl()
|
||||
_ = task.DispatchWaiting()
|
||||
|
||||
setting := services.NewSettingImpl()
|
||||
monitor := setting.Get(models.SettingKeyMonitor)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/goravel/framework/support/color"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
requests "github.com/TheTNB/panel/app/http/requests/website"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
@@ -164,8 +165,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.username") + ": " + user.Username)
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.password") + ": " + password)
|
||||
color.Green().Printfln(translate.Get("commands.panel.port") + ": " + port)
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("panel.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("panel.entrance"))
|
||||
|
||||
case "getPort":
|
||||
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
@@ -177,7 +178,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Green().Printfln(translate.Get("commands.panel.port") + ": " + port)
|
||||
|
||||
case "getEntrance":
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
|
||||
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("panel.entrance"))
|
||||
|
||||
case "deleteEntrance":
|
||||
oldEntrance, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
@@ -505,8 +506,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
Name: name,
|
||||
Status: status,
|
||||
Path: path,
|
||||
Php: php,
|
||||
Ssl: ssl,
|
||||
PHP: php,
|
||||
SSL: ssl,
|
||||
})
|
||||
if err != nil {
|
||||
color.Red().Printfln(translate.Get("commands.panel.writeSite.fail"))
|
||||
@@ -614,15 +615,13 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = website.Add(types.Website{
|
||||
_, err = website.Add(requests.Add{
|
||||
Name: name,
|
||||
Status: true,
|
||||
Domains: domains,
|
||||
Ports: uintPorts,
|
||||
Path: path,
|
||||
Php: php,
|
||||
Ssl: false,
|
||||
Db: false,
|
||||
PHP: php,
|
||||
DB: false,
|
||||
})
|
||||
if err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
@@ -649,7 +648,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = website.Delete(id); err != nil {
|
||||
if err = website.Delete(requests.Delete{ID: id}); err != nil {
|
||||
color.Red().Printfln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
@@ -68,6 +70,10 @@ func (receiver *PanelTask) Handle(console.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 回收内存
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
types.Status = types.StatusNormal
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
)
|
||||
|
||||
type AssetController struct {
|
||||
setting internal.Setting
|
||||
}
|
||||
|
||||
func NewAssetController() *AssetController {
|
||||
return &AssetController{
|
||||
setting: services.NewSettingImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AssetController) Index(ctx http.Context) http.Response {
|
||||
entrance := facades.Config().GetString("http.entrance")
|
||||
if entrance == "/" {
|
||||
entrance = ""
|
||||
}
|
||||
|
||||
// 自动纠正 URL 格式
|
||||
if ctx.Request().Path() == entrance && ctx.Request().Path() != "/" {
|
||||
return ctx.Response().Redirect(http.StatusMovedPermanently, ctx.Request().Path()+"/")
|
||||
}
|
||||
// 拒绝访问非入口文件
|
||||
if !strings.HasPrefix(ctx.Request().Path(), entrance) {
|
||||
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
path := strings.TrimPrefix(ctx.Request().Path(), entrance)
|
||||
// 设置默认首页
|
||||
if path == "/" || path == "" {
|
||||
path = "/index.html"
|
||||
}
|
||||
|
||||
if !io.Exists("public" + path) {
|
||||
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
file, err := os.Open("public" + path)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
if stat.IsDir() {
|
||||
return Error(ctx, http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||
}
|
||||
|
||||
return ctx.Response().Header("Cache-Control", "no-cache").File("public" + path)
|
||||
}
|
||||
|
||||
func (r *AssetController) Favicon(ctx http.Context) http.Response {
|
||||
return ctx.Response().File("public/favicon.png")
|
||||
}
|
||||
|
||||
func (r *AssetController) Robots(ctx http.Context) http.Response {
|
||||
return ctx.Response().File("public/robots.txt")
|
||||
}
|
||||
|
||||
func (r *AssetController) NotFound(ctx http.Context) http.Response {
|
||||
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
@@ -73,6 +73,10 @@ func (r *CertController) DNSProviders(ctx http.Context) http.Response {
|
||||
"name": "DNSPod",
|
||||
"dns": acme.DnsPod,
|
||||
},
|
||||
{
|
||||
"name": "腾讯云",
|
||||
"dns": acme.Tencent,
|
||||
},
|
||||
{
|
||||
"name": "阿里云",
|
||||
"dns": acme.AliYun,
|
||||
|
||||
@@ -29,7 +29,7 @@ func Success(ctx http.Context, data any) http.Response {
|
||||
// Error 响应错误
|
||||
func Error(ctx http.Context, code int, message string) http.Response {
|
||||
return ctx.Response().Json(code, &ErrorResponse{
|
||||
Message: facades.Lang(ctx).Get("messages.mistake") + ": " + message,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
stdio "io"
|
||||
stdos "os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@@ -354,10 +355,16 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := io.Chmod(request.Path, stdos.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 := io.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())
|
||||
}
|
||||
|
||||
@@ -505,7 +512,7 @@ func (r *FileController) List(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// setPermission
|
||||
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
|
||||
_ = io.Chmod(path, stdos.FileMode(mode))
|
||||
func (r *FileController) setPermission(path string, mode stdos.FileMode, owner, group string) {
|
||||
_ = io.Chmod(path, mode)
|
||||
_ = io.Chown(path, owner, group)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
@@ -250,13 +251,21 @@ func (r *InfoController) InstalledDbAndPhp(ctx http.Context) http.Response {
|
||||
|
||||
// CheckUpdate 检查面板更新
|
||||
func (r *InfoController) CheckUpdate(ctx http.Context) http.Response {
|
||||
version := facades.Config().GetString("panel.version")
|
||||
remote, err := tools.GetLatestPanelVersion()
|
||||
current := facades.Config().GetString("panel.version")
|
||||
latest, err := tools.GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
|
||||
}
|
||||
|
||||
if tools.VersionCompare(version, remote.Version, ">=") {
|
||||
v1, err := version.NewVersion(current)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
|
||||
}
|
||||
v2, err := version.NewVersion(latest.Version)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
|
||||
}
|
||||
if v1.GreaterThanOrEqual(v2) {
|
||||
return Success(ctx, http.Json{
|
||||
"update": false,
|
||||
})
|
||||
@@ -269,17 +278,25 @@ func (r *InfoController) CheckUpdate(ctx http.Context) http.Response {
|
||||
|
||||
// UpdateInfo 获取更新信息
|
||||
func (r *InfoController) UpdateInfo(ctx http.Context) http.Response {
|
||||
version := facades.Config().GetString("panel.version")
|
||||
current, err := tools.GetLatestPanelVersion()
|
||||
current := facades.Config().GetString("panel.version")
|
||||
latest, err := tools.GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
|
||||
}
|
||||
|
||||
if tools.VersionCompare(version, current.Version, ">=") {
|
||||
v1, err := version.NewVersion(current)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
|
||||
}
|
||||
v2, err := version.NewVersion(latest.Version)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "版本号解析失败")
|
||||
}
|
||||
if v1.GreaterThanOrEqual(v2) {
|
||||
return Error(ctx, http.StatusInternalServerError, "当前版本已是最新版本")
|
||||
}
|
||||
|
||||
versions, err := tools.GenerateVersions(version, current.Version)
|
||||
versions, err := tools.GenerateVersions(current, latest.Version)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "获取更新信息失败")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
@@ -71,7 +71,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
|
||||
return controllers.Success(ctx, []types.NV{})
|
||||
}
|
||||
|
||||
raw, err := shell.Execf("/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负载失败")
|
||||
}
|
||||
@@ -175,80 +175,45 @@ func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
|
||||
|
||||
// SetRootPassword 设置root密码
|
||||
func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
|
||||
status, err := systemctl.Status("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 = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, oldRootPassword, rootPassword)); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置root密码失败: %v", err))
|
||||
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 = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, rootPassword)); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
}
|
||||
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
|
||||
_, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, rootPassword, oldRootPassword))
|
||||
_, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, oldRootPassword))
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
|
||||
} 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.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
})
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err = db.Ping(); err != nil {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
"items": []types.MySQLDatabase{},
|
||||
})
|
||||
}
|
||||
|
||||
rows, err := db.Query("SHOW DATABASES")
|
||||
databases, err := mysql.Databases()
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var databases []database
|
||||
for rows.Next() {
|
||||
var d database
|
||||
if err = rows.Scan(&d.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
databases = append(databases, d)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
|
||||
}
|
||||
|
||||
paged, total := controllers.Paginate(ctx, databases)
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
@@ -260,9 +225,9 @@ func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
|
||||
// AddDatabase 添加数据库
|
||||
func (r *MySQLController) 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",
|
||||
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:32",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -272,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 := shell.Execf("/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 := shell.Execf("/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 := shell.Execf("/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 := shell.Execf("/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)
|
||||
@@ -291,15 +257,19 @@ func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
|
||||
// DeleteDatabase 删除数据库
|
||||
func (r *MySQLController) 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:information_schema,mysql,performance_schema,sys",
|
||||
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := shell.Execf("/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)
|
||||
@@ -346,7 +316,7 @@ func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
|
||||
// CreateBackup 创建备份
|
||||
func (r *MySQLController) 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",
|
||||
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -380,7 +350,7 @@ func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
|
||||
func (r *MySQLController) 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",
|
||||
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -394,71 +364,20 @@ 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.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
})
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err = db.Ping(); err != nil {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
"items": []types.MySQLUser{},
|
||||
})
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT user, host FROM mysql.user")
|
||||
users, err := mysql.Users()
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var userGrants []user
|
||||
|
||||
for rows.Next() {
|
||||
var u user
|
||||
if err = rows.Scan(&u.User, &u.Host); 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
|
||||
if err = grantsRows.Scan(&grant); 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, "获取用户列表失败")
|
||||
}
|
||||
|
||||
paged, total := controllers.Paginate(ctx, userGrants)
|
||||
paged, total := controllers.Paginate(ctx, users)
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": total,
|
||||
@@ -469,9 +388,9 @@ func (r *MySQLController) UserList(ctx http.Context) http.Response {
|
||||
// AddUser 添加用户
|
||||
func (r *MySQLController) AddUser(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",
|
||||
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:32",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -480,14 +399,15 @@ func (r *MySQLController) AddUser(ctx http.Context) http.Response {
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := shell.Execf("/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 := shell.Execf("/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 := shell.Execf("/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)
|
||||
@@ -496,15 +416,19 @@ func (r *MySQLController) AddUser(ctx http.Context) http.Response {
|
||||
// DeleteUser 删除用户
|
||||
func (r *MySQLController) DeleteUser(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_]+$",
|
||||
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
if out, err := shell.Execf("/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)
|
||||
@@ -513,8 +437,8 @@ func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
|
||||
// SetUserPassword 设置用户密码
|
||||
func (r *MySQLController) SetUserPassword(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",
|
||||
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:32",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -522,11 +446,12 @@ func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
if out, err := shell.Execf("/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 := shell.Execf("/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)
|
||||
@@ -535,8 +460,8 @@ func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
|
||||
// SetUserPrivileges 设置用户权限
|
||||
func (r *MySQLController) SetUserPrivileges(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_]+$",
|
||||
"database": "required|min_len:1|max_len:255",
|
||||
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
|
||||
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -544,15 +469,38 @@ func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := shell.Execf("/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 := shell.Execf("/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 := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.PrivilegesGrant(user, database); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// getSock 获取sock文件位置
|
||||
func (r *MySQLController) getSock() string {
|
||||
if io.Exists("/tmp/mysql.sock") {
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
if io.Exists("/www/server/mysql/config/my.cnf") {
|
||||
config, _ := io.Read("/www/server/mysql/config/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
if io.Exists("/etc/my.cnf") {
|
||||
config, _ := io.Read("/etc/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
@@ -61,7 +62,8 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -87,8 +88,39 @@ func (r *PhpMyAdminController) SetPort(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *PhpMyAdminController) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := io.Read("/www/server/vhost/phpmyadmin.conf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
func (r *PhpMyAdminController) SaveConfig(ctx http.Context) http.Response {
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "配置不能为空")
|
||||
}
|
||||
|
||||
if err := io.Write("/www/server/vhost/phpmyadmin.conf", config, 0644); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("插件", "phpMyAdmin").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("修改 phpMyAdmin 配置失败")
|
||||
return controllers.ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
|
||||
@@ -202,9 +202,9 @@ func (r *PostgreSQLController) DatabaseList(ctx http.Context) http.Response {
|
||||
// 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",
|
||||
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:40",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func (r *PostgreSQLController) AddDatabase(ctx http.Context) http.Response {
|
||||
// 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",
|
||||
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -295,7 +295,7 @@ func (r *PostgreSQLController) UploadBackup(ctx http.Context) http.Response {
|
||||
// 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",
|
||||
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func (r *PostgreSQLController) DeleteBackup(ctx http.Context) http.Response {
|
||||
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",
|
||||
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$|not_in:postgres,template0,template1",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -420,9 +420,9 @@ func (r *PostgreSQLController) RoleList(ctx http.Context) http.Response {
|
||||
// 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",
|
||||
"database": "required|min_len:1|max_len:63|regex:^[a-zA-Z0-9_]+$",
|
||||
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:40",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -452,7 +452,7 @@ func (r *PostgreSQLController) AddRole(ctx http.Context) http.Response {
|
||||
// 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_]+$",
|
||||
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
@@ -475,8 +475,8 @@ func (r *PostgreSQLController) DeleteRole(ctx http.Context) http.Response {
|
||||
// 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",
|
||||
"user": "required|min_len:1|max_len:30|regex:^[a-zA-Z0-9_]+$",
|
||||
"password": "required|min_len:8|max_len:40",
|
||||
}); sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/path"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
requests "github.com/TheTNB/panel/app/http/requests/setting"
|
||||
@@ -44,13 +45,10 @@ func (r *SettingController) List(ctx http.Context) http.Response {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
userID := cast.ToUint(ctx.Value("user_id"))
|
||||
var user models.User
|
||||
err = facades.Auth(ctx).User(&user)
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("获取用户信息失败")
|
||||
return ErrorSystem(ctx)
|
||||
if err = facades.Orm().Query().Where("id", userID).Get(&user); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "获取用户信息失败")
|
||||
}
|
||||
|
||||
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
|
||||
@@ -64,7 +62,7 @@ func (r *SettingController) List(ctx http.Context) http.Response {
|
||||
return Success(ctx, http.Json{
|
||||
"name": r.setting.Get(models.SettingKeyName),
|
||||
"language": facades.Config().GetString("app.locale"),
|
||||
"entrance": facades.Config().GetString("http.entrance"),
|
||||
"entrance": facades.Config().GetString("panel.entrance"),
|
||||
"ssl": facades.Config().GetBool("panel.ssl"),
|
||||
"website_path": r.setting.Get(models.SettingKeyWebsitePath),
|
||||
"backup_path": r.setting.Get(models.SettingKeyBackupPath),
|
||||
@@ -128,11 +126,12 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
userID := cast.ToUint(ctx.Value("user_id"))
|
||||
var user models.User
|
||||
err = facades.Auth(ctx).User(&user)
|
||||
if err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
if err = facades.Orm().Query().Where("id", userID).Get(&user); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, "获取用户信息失败")
|
||||
}
|
||||
|
||||
user.Username = updateRequest.UserName
|
||||
user.Email = updateRequest.Email
|
||||
if len(updateRequest.Password) > 0 {
|
||||
@@ -270,10 +269,10 @@ func (r *SettingController) UpdateHttps(ctx http.Context) http.Response {
|
||||
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 {
|
||||
if err := io.Write(path.Executable("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 {
|
||||
if err := io.Write(path.Executable("storage/ssl.key"), httpsRequest.Key, 0700); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
if out, err := shell.Execf("sed -i 's/APP_SSL=false/APP_SSL=true/g' /www/panel/panel.conf"); err != nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/swaggo/http-swagger/v2"
|
||||
|
||||
_ "github.com/TheTNB/panel/docs"
|
||||
@@ -25,10 +24,6 @@ func NewSwaggerController() *SwaggerController {
|
||||
// @Failure 500
|
||||
// @Router /swagger [get]
|
||||
func (r *SwaggerController) Index(ctx http.Context) http.Response {
|
||||
if !facades.Config().GetBool("app.debug") {
|
||||
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
handler := httpSwagger.Handler()
|
||||
handler(ctx.Response().Writer(), ctx.Request().Origin())
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/requests/user"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
@@ -20,16 +21,15 @@ func NewUserController() *UserController {
|
||||
|
||||
// Login
|
||||
//
|
||||
// @Summary 登录
|
||||
// @Description 通过用户名和密码获取访问令牌
|
||||
// @Tags 用户鉴权
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body requests.Login true "request"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Failure 403 {object} ErrorResponse "用户名或密码错误"
|
||||
// @Failure 500 {object} ErrorResponse "系统内部错误
|
||||
// @Router /panel/user/login [post]
|
||||
// @Summary 登录
|
||||
// @Tags 用户鉴权
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body requests.Login true "request"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Failure 403 {object} ErrorResponse "用户名或密码错误"
|
||||
// @Failure 500 {object} ErrorResponse "系统内部错误
|
||||
// @Router /panel/user/login [post]
|
||||
func (r *UserController) Login(ctx http.Context) http.Response {
|
||||
var loginRequest requests.Login
|
||||
sanitize := SanitizeRequest(ctx, &loginRequest)
|
||||
@@ -60,32 +60,35 @@ func (r *UserController) Login(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
token, loginErr := facades.Auth(ctx).LoginUsingID(user.ID)
|
||||
if loginErr != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("登录失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
ctx.Request().Session().Put("user_id", user.ID)
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
"access_token": token,
|
||||
})
|
||||
// Logout
|
||||
//
|
||||
// @Summary 登出
|
||||
// @Tags 用户鉴权
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/user/logout [post]
|
||||
func (r *UserController) Logout(ctx http.Context) http.Response {
|
||||
ctx.Request().Session().Forget("user_id")
|
||||
return Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Info
|
||||
//
|
||||
// @Summary 用户信息
|
||||
// @Description 获取当前登录用户信息
|
||||
// @Tags 用户鉴权
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/user/info [get]
|
||||
// @Summary 用户信息
|
||||
// @Tags 用户鉴权
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/user/info [get]
|
||||
func (r *UserController) Info(ctx http.Context) http.Response {
|
||||
userID := cast.ToUint(ctx.Value("user_id"))
|
||||
var user models.User
|
||||
err := facades.Auth(ctx).User(&user)
|
||||
if err != nil {
|
||||
if err := facades.Orm().Query().Where("id", userID).Get(&user); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("获取用户信息失败")
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"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/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type WebsiteController struct {
|
||||
@@ -54,7 +54,7 @@ func (r *WebsiteController) List(ctx http.Context) http.Response {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("获取网站列表失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, http.Json{
|
||||
@@ -84,27 +84,12 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
|
||||
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
|
||||
}
|
||||
|
||||
website := types.Website{
|
||||
Name: addRequest.Name,
|
||||
Status: true,
|
||||
Domains: addRequest.Domains,
|
||||
Ports: addRequest.Ports,
|
||||
Path: addRequest.Path,
|
||||
Php: addRequest.Php,
|
||||
Ssl: false,
|
||||
Db: addRequest.Db,
|
||||
DbType: addRequest.DbType,
|
||||
DbName: addRequest.DbName,
|
||||
DbUser: addRequest.DbUser,
|
||||
DbPassword: addRequest.DbPassword,
|
||||
}
|
||||
|
||||
_, err := r.website.Add(website)
|
||||
_, err := r.website.Add(addRequest)
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("添加网站失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -117,23 +102,22 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param id path int true "网站 ID"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/websites/{id} [delete]
|
||||
// @Param data body requests.Delete true "request"
|
||||
// @Success 200 {object} SuccessResponse
|
||||
// @Router /panel/websites/delete [post]
|
||||
func (r *WebsiteController) Delete(ctx http.Context) http.Response {
|
||||
var idRequest requests.ID
|
||||
sanitize := SanitizeRequest(ctx, &idRequest)
|
||||
var deleteRequest requests.Delete
|
||||
sanitize := SanitizeRequest(ctx, &deleteRequest)
|
||||
if sanitize != nil {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
err := r.website.Delete(idRequest.ID)
|
||||
if err != nil {
|
||||
if err := r.website.Delete(deleteRequest); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"id": idRequest.ID,
|
||||
"id": deleteRequest.ID,
|
||||
"error": err.Error(),
|
||||
}).Info("删除网站失败")
|
||||
return Error(ctx, http.StatusInternalServerError, "删除网站失败: "+err.Error())
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -181,14 +165,14 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("保存默认首页配置失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if err := io.Write("/www/server/openresty/html/stop.html", stop, 0644); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("保存默认停止页配置失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -202,7 +186,7 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param id path int true "网站 ID"
|
||||
// @Success 200 {object} SuccessResponse{data=types.Website}
|
||||
// @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
|
||||
@@ -217,7 +201,7 @@ func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
|
||||
"id": idRequest.ID,
|
||||
"error": err.Error(),
|
||||
}).Info("获取网站配置失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, config)
|
||||
@@ -335,7 +319,7 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("获取备份列表失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
paged, total := Paginate(ctx, backups)
|
||||
@@ -412,7 +396,7 @@ func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("上传备份失败")
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -506,7 +490,7 @@ func (r *WebsiteController) ResetConfig(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
website.Status = true
|
||||
website.Ssl = false
|
||||
website.SSL = false
|
||||
if err := facades.Orm().Query().Save(&website); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
|
||||
"id": idRequest.ID,
|
||||
@@ -575,7 +559,7 @@ server
|
||||
error_log /www/wwwlogs/%s.log;
|
||||
}
|
||||
|
||||
`, website.Path, website.Php, website.Name, website.Name, website.Name, website.Name)
|
||||
`, 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
|
||||
}
|
||||
@@ -586,7 +570,8 @@ server
|
||||
return nil
|
||||
}
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -649,10 +634,11 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
if err = io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
sessionmiddleware "github.com/goravel/framework/session/middleware"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/middleware"
|
||||
)
|
||||
@@ -13,6 +14,10 @@ type Kernel struct {
|
||||
// These middleware are run during every request to your application.
|
||||
func (kernel Kernel) Middleware() []http.Middleware {
|
||||
return []http.Middleware{
|
||||
sessionmiddleware.StartSession(),
|
||||
middleware.Log(),
|
||||
middleware.Status(),
|
||||
middleware.Entrance(),
|
||||
middleware.Static(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func Entrance() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
translate := facades.Lang(ctx)
|
||||
|
||||
if !ctx.Request().HasSession() {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.session.missing"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
entrance := facades.Config().GetString("panel.entrance")
|
||||
if ctx.Request().Path() == entrance {
|
||||
ctx.Request().Session().Put("verify_entrance", true)
|
||||
_ = ctx.Response().Redirect(http.StatusFound, "/login").Render()
|
||||
ctx.Request().AbortWithStatus(http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !facades.Config().GetBool("app.debug") &&
|
||||
(ctx.Request().Session().Missing("verify_entrance") || !cast.ToBool(ctx.Request().Session().Get("verify_entrance"))) &&
|
||||
ctx.Request().Path() != "/robots.txt" {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusTeapot, http.Json{
|
||||
"message": "请通过正确的入口访问",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/goravel/framework/auth"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
// Jwt 确保通过 JWT 鉴权
|
||||
func Jwt() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
translate := facades.Lang(ctx)
|
||||
token := ctx.Request().Header("Authorization", ctx.Request().Header("Sec-WebSocket-Protocol"))
|
||||
if len(token) == 0 {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.token.missing"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// JWT 鉴权
|
||||
if _, err := facades.Auth(ctx).Parse(token); err != nil {
|
||||
if errors.Is(err, auth.ErrorTokenExpired) {
|
||||
token, err = facades.Auth(ctx).Refresh()
|
||||
if err != nil {
|
||||
// 到达刷新时间上限
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.token.expired"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
token = "Bearer " + token
|
||||
} else {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.token.expired"),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Response().Header("Authorization", token)
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
// Log 记录请求日志
|
||||
func Log() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
facades.Log().Channel("http").With(map[string]any{
|
||||
"Method": ctx.Request().Method(),
|
||||
"URL": ctx.Request().FullUrl(),
|
||||
"IP": ctx.Request().Ip(),
|
||||
"UA": ctx.Request().Header("User-Agent"),
|
||||
"Body": ctx.Request().All(),
|
||||
}).Info("HTTP Request")
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// Session 确保通过 JWT 鉴权
|
||||
func Session() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
translate := facades.Lang(ctx)
|
||||
|
||||
if !ctx.Request().HasSession() {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.session.missing"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Request().Session().Missing("user_id") {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.session.expired"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := cast.ToUint(ctx.Request().Session().Get("user_id"))
|
||||
if userID == 0 {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"message": translate.Get("auth.session.invalid"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新会话
|
||||
/*if err := ctx.Request().Session().Regenerate(); err == nil {
|
||||
ctx.Response().Cookie(http.Cookie{
|
||||
Name: ctx.Request().Session().GetName(),
|
||||
Value: ctx.Request().Session().GetID(),
|
||||
MaxAge: facades.Config().GetInt("session.lifetime") * 60,
|
||||
Path: facades.Config().GetString("session.path"),
|
||||
Domain: facades.Config().GetString("session.domain"),
|
||||
Secure: facades.Config().GetBool("session.secure"),
|
||||
HttpOnly: facades.Config().GetBool("session.http_only"),
|
||||
SameSite: facades.Config().GetString("session.same_site"),
|
||||
})
|
||||
}*/
|
||||
|
||||
ctx.WithValue("user_id", userID)
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/gin"
|
||||
|
||||
"github.com/TheTNB/panel/embed"
|
||||
)
|
||||
|
||||
func Static() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
static.Serve("/", static.EmbedFolder(embed.PublicFS, "frontend"))(ctx.(*gin.Context).Instance())
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type CertDeploy struct {
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
|
||||
ID uint `form:"id" json:"id"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id"`
|
||||
}
|
||||
|
||||
func (r *CertDeploy) Authorize(ctx http.Context) error {
|
||||
@@ -21,6 +21,13 @@ func (r *CertDeploy) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertDeploy) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"id": "uint",
|
||||
"website_id": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertDeploy) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *CertShowAndDestroy) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertShowAndDestroy) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *CertShowAndDestroy) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ 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" filter:"uint"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id" filter:"uint"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
|
||||
UserID uint `form:"user_id" json:"user_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id"`
|
||||
}
|
||||
|
||||
func (r *CertStore) Authorize(ctx http.Context) error {
|
||||
@@ -29,6 +29,14 @@ func (r *CertStore) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertStore) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"user_id": "uint",
|
||||
"dns_id": "uint",
|
||||
"website_id": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertStore) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -10,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" filter:"uint"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id" filter:"uint"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
|
||||
UserID uint `form:"user_id" json:"user_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
WebsiteID uint `form:"website_id" json:"website_id"`
|
||||
}
|
||||
|
||||
func (r *CertUpdate) Authorize(ctx http.Context) error {
|
||||
@@ -31,6 +31,14 @@ func (r *CertUpdate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertUpdate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"user_id": "uint",
|
||||
"dns_id": "uint",
|
||||
"website_id": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CertUpdate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *DNSShowAndDestroy) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DNSShowAndDestroy) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *DNSShowAndDestroy) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,21 @@ func (r *DNSStore) Authorize(ctx http.Context) error {
|
||||
|
||||
func (r *DNSStore) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"type": "required|in:dnspod,aliyun,cloudflare",
|
||||
"type": "required|in:dnspod,tencent,aliyun,cloudflare",
|
||||
"name": "required",
|
||||
"data": "required",
|
||||
"data.id": "required_if:type,dnspod",
|
||||
"data.token": "required_if:type,dnspod",
|
||||
"data.access_key": "required_if:type,aliyun",
|
||||
"data.secret_key": "required_if:type,aliyun",
|
||||
"data.access_key": "required_if:type,aliyun,tencent",
|
||||
"data.secret_key": "required_if:type,aliyun,tencent",
|
||||
"data.api_key": "required_if:type,cloudflare",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DNSStore) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *DNSStore) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ func (r *DNSUpdate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DNSUpdate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *DNSUpdate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *Obtain) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Obtain) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Obtain) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *Renew) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renew) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Renew) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *UserShowAndDestroy) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserShowAndDestroy) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UserShowAndDestroy) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ func (r *UserStore) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
|
||||
"email": "required|email",
|
||||
"kid": "required_unless:ca,letsencrypt,buypass",
|
||||
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
|
||||
"kid": "required_if:ca,sslcom,google",
|
||||
"hmac_encoded": "required_if:ca,sslcom,google",
|
||||
"key_type": "required|in:P256,P384,2048,4096",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserStore) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UserStore) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,16 @@ func (r *UserUpdate) Rules(ctx http.Context) map[string]string {
|
||||
"id": "required|uint|min:1|exists:cert_users,id",
|
||||
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
|
||||
"email": "required|email",
|
||||
"kid": "required_unless:ca,letsencrypt,buypass",
|
||||
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
|
||||
"kid": "required_if:ca,sslcom,google",
|
||||
"hmac_encoded": "required_if:ca,sslcom,google",
|
||||
"key_type": "required|in:P256,P384,2048,4096",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserUpdate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UserUpdate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type Paginate struct {
|
||||
Page int `form:"page" json:"page" filter:"int"`
|
||||
Limit int `form:"limit" json:"limit" filter:"int"`
|
||||
Page int `form:"page" json:"page"`
|
||||
Limit int `form:"limit" json:"limit"`
|
||||
}
|
||||
|
||||
func (r *Paginate) Authorize(ctx http.Context) error {
|
||||
@@ -21,6 +21,13 @@ func (r *Paginate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Paginate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"page": "int",
|
||||
"limit": "int",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Paginate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ type ContainerCreate struct {
|
||||
OpenStdin bool `form:"openStdin" json:"open_stdin"`
|
||||
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
|
||||
Tty bool `form:"tty" json:"tty"`
|
||||
CPUShares int64 `form:"cpu_shares" json:"cpu_shares" filter:"int"`
|
||||
CPUs int64 `form:"cpus" json:"cpus" filter:"int"`
|
||||
Memory int64 `form:"memory" json:"memory" filter:"int"`
|
||||
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
|
||||
CPUs int64 `form:"cpus" json:"cpus"`
|
||||
Memory int64 `form:"memory" json:"memory"`
|
||||
}
|
||||
|
||||
func (r *ContainerCreate) Authorize(ctx http.Context) error {
|
||||
@@ -64,6 +64,14 @@ func (r *ContainerCreate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ContainerCreate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"cpu_shares": "int",
|
||||
"cpus": "int",
|
||||
"memory": "int",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ContainerCreate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *ContainerRename) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ContainerRename) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *ContainerRename) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ func (r *ContainerUpdate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ContainerUpdate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *ContainerUpdate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *ID) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ID) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *ID) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ func (r *ImagePull) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ImagePull) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *ImagePull) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *NetworkConnectDisConnect) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NetworkConnectDisConnect) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *NetworkConnectDisConnect) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ func (r *NetworkCreate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NetworkCreate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *NetworkCreate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ func (r *VolumeCreate) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *VolumeCreate) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *VolumeCreate) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ func (r *Archive) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Archive) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Archive) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *Copy) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Copy) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Copy) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *Exist) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Exist) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Exist) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *Move) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Move) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Move) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *NotExist) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NotExist) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *NotExist) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
type Permission struct {
|
||||
Path string `form:"path" json:"path"`
|
||||
Mode uint `form:"mode" json:"mode" filter:"uint"`
|
||||
Mode string `form:"mode" json:"mode"`
|
||||
Owner string `form:"owner" json:"owner"`
|
||||
Group string `form:"group" json:"group"`
|
||||
}
|
||||
@@ -19,12 +19,16 @@ func (r *Permission) Authorize(ctx http.Context) error {
|
||||
func (r *Permission) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/.*$|path_exists`,
|
||||
"mode": "regex:^[0-7]{3}$|uint",
|
||||
"mode": "regex:^0[0-7]{3}$",
|
||||
"owner": "regex:^[a-zA-Z0-9_-]+$",
|
||||
"group": "regex:^[a-zA-Z0-9_-]+$",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Permission) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Permission) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *Save) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Save) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Save) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *Search) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Search) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Search) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,14 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
|
||||
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"file": `regex:^/.*$|path_exists`,
|
||||
"path": `regex:^/.*$|path_not_exists`,
|
||||
"path": `regex:^/.*$`,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UnArchive) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UnArchive) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ func (r *Upload) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Upload) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Upload) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *Service) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Service) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Service) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *UpdateRegistryConfig) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateRegistryConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *UpdateStorageConfig) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateStorageConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ func (r *Create) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Create) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Create) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ func (r *Update) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Update) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Update) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ func (r *Https) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Https) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Https) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
type Update struct {
|
||||
Name string `form:"name" json:"name"`
|
||||
Language string `form:"language" json:"language"`
|
||||
Port uint `form:"port" json:"port" filter:"uint"`
|
||||
Port uint `form:"port" json:"port"`
|
||||
BackupPath string `form:"backup_path" json:"backup_path"`
|
||||
WebsitePath string `form:"website_path" json:"website_path"`
|
||||
Entrance string `form:"entrance" json:"entrance"`
|
||||
@@ -35,6 +35,12 @@ func (r *Update) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Update) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"port": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Update) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"port.int": "port 值必须是一个整数且在 1000 - 65535 之间",
|
||||
|
||||
@@ -23,12 +23,16 @@ func (r *Login) Rules(ctx http.Context) map[string]string {
|
||||
|
||||
func (r *Login) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"username.required": "登录名不能为空",
|
||||
"username.required": "用户名不能为空",
|
||||
"password.required": "密码不能为空",
|
||||
"password.min_len": "密码长度不能小于 8 位",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Login) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Login) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ type Add struct {
|
||||
Domains []string `form:"domains" json:"domains"`
|
||||
Ports []uint `form:"ports" json:"ports"`
|
||||
Path string `form:"path" json:"path"`
|
||||
Php string `form:"php" json:"php"`
|
||||
Db bool `form:"db" json:"db"`
|
||||
DbType string `form:"db_type" json:"db_type"`
|
||||
DbName string `form:"db_name" json:"db_name"`
|
||||
DbUser string `form:"db_user" json:"db_user"`
|
||||
DbPassword string `form:"db_password" json:"db_password"`
|
||||
PHP string `form:"php" json:"php"`
|
||||
DB bool `form:"db" json:"db"`
|
||||
DBType string `form:"db_type" json:"db_type"`
|
||||
DBName string `form:"db_name" json:"db_name"`
|
||||
DBUser string `form:"db_user" json:"db_user"`
|
||||
DBPassword string `form:"db_password" json:"db_password"`
|
||||
}
|
||||
|
||||
func (r *Add) Authorize(ctx http.Context) error {
|
||||
@@ -37,6 +37,10 @@ func (r *Add) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Add) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Add) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type Delete struct {
|
||||
ID uint `form:"id" json:"id"`
|
||||
Path bool `form:"path" json:"path"`
|
||||
DB bool `form:"db" json:"db"`
|
||||
}
|
||||
|
||||
func (r *Delete) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Delete) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"id": "required|exists:websites,id",
|
||||
"path": "bool",
|
||||
"db": "bool",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Delete) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"id": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Delete) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Delete) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *Delete) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
@@ -19,6 +19,10 @@ func (r *DeleteBackup) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DeleteBackup) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *DeleteBackup) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type ID struct {
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
ID uint `form:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (r *ID) Authorize(ctx http.Context) error {
|
||||
@@ -19,6 +19,12 @@ func (r *ID) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ID) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"id": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ID) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type RestoreBackup struct {
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
ID uint `form:"id" json:"id"`
|
||||
Name string `form:"name" json:"name"`
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ func (r *RestoreBackup) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RestoreBackup) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"id": "uint",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RestoreBackup) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,15 @@ import (
|
||||
)
|
||||
|
||||
type SaveConfig struct {
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
ID uint `form:"id" json:"id"`
|
||||
Domains []string `form:"domains" json:"domains"`
|
||||
Ports []uint `form:"ports" json:"ports"`
|
||||
Hsts bool `form:"hsts" json:"hsts"`
|
||||
Ssl bool `form:"ssl" json:"ssl"`
|
||||
HttpRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
SSLPorts []uint `form:"ssl_ports" json:"ssl_ports"`
|
||||
QUICPorts []uint `form:"quic_ports" json:"quic_ports"`
|
||||
OCSP bool `form:"ocsp" json:"ocsp"`
|
||||
HSTS bool `form:"hsts" json:"hsts"`
|
||||
SSL bool `form:"ssl" json:"ssl"`
|
||||
HTTPRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
OpenBasedir bool `form:"open_basedir" json:"open_basedir"`
|
||||
Waf bool `form:"waf" json:"waf"`
|
||||
WafCache string `form:"waf_cache" json:"waf_cache"`
|
||||
@@ -22,9 +25,9 @@ type SaveConfig struct {
|
||||
Root string `form:"root" json:"root"`
|
||||
Raw string `form:"raw" json:"raw"`
|
||||
Rewrite string `form:"rewrite" json:"rewrite"`
|
||||
Php int `form:"php" json:"php" filter:"int"`
|
||||
SslCertificate string `form:"ssl_certificate" json:"ssl_certificate"`
|
||||
SslCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key"`
|
||||
PHP int `form:"php" json:"php"`
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate"`
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key"`
|
||||
}
|
||||
|
||||
func (r *SaveConfig) Authorize(ctx http.Context) error {
|
||||
@@ -36,6 +39,9 @@ func (r *SaveConfig) Rules(ctx http.Context) map[string]string {
|
||||
"id": "required|exists:websites,id",
|
||||
"domains": "required|slice",
|
||||
"ports": "required|slice",
|
||||
"ssl_ports": "slice|not_in:80",
|
||||
"quic_ports": "slice|not_in:80",
|
||||
"ocsp": "bool",
|
||||
"hsts": "bool",
|
||||
"ssl": "bool",
|
||||
"http_redirect": "bool",
|
||||
@@ -55,6 +61,13 @@ func (r *SaveConfig) Rules(ctx http.Context) map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SaveConfig) Filters(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"id": "uint",
|
||||
"php": "int",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SaveConfig) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
+14
-20
@@ -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,18 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
"task_id": taskID,
|
||||
}).Infof("开始执行任务")
|
||||
|
||||
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("更新任务状态失败")
|
||||
return 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(),
|
||||
@@ -69,7 +63,7 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
}).Infof("更新任务状态失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
type CertDNS struct {
|
||||
orm.Model
|
||||
Name string `gorm:"not null" json:"name"` // 备注名称
|
||||
Type string `gorm:"not null" json:"type"` // DNS 提供商 (dnspod, aliyun, cloudflare)
|
||||
Type string `gorm:"not null" json:"type"` // DNS 提供商 (dnspod, tencent, aliyun, cloudflare)
|
||||
Data acme.DNSParam `gorm:"not null;serializer:json" json:"dns_param"`
|
||||
|
||||
Certs []*Cert `gorm:"foreignKey:DNSID" json:"-"`
|
||||
|
||||
@@ -7,8 +7,8 @@ type Website struct {
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/app/models"
|
||||
"github.com/TheTNB/panel/pkg/migrate"
|
||||
)
|
||||
|
||||
type DatabaseServiceProvider struct {
|
||||
@@ -20,19 +18,5 @@ func (receiver *DatabaseServiceProvider) Register(app foundation.Application) {
|
||||
|
||||
func (receiver *DatabaseServiceProvider) Boot(app foundation.Application) {
|
||||
facades.Seeder().Register([]seeder.Seeder{})
|
||||
if err := facades.Orm().Query().(*gorm.QueryImpl).Instance().AutoMigrate(
|
||||
&models.Cert{},
|
||||
&models.CertDNS{},
|
||||
&models.CertUser{},
|
||||
&models.Cron{},
|
||||
&models.Database{},
|
||||
&models.Monitor{},
|
||||
&models.Plugin{},
|
||||
&models.Setting{},
|
||||
&models.Task{},
|
||||
&models.User{},
|
||||
&models.Website{},
|
||||
); err != nil {
|
||||
panic(fmt.Sprintf("Failed to migrate database: %v", err))
|
||||
}
|
||||
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": "面板遭受登录爆破攻击过多,已暂时屏蔽登录,请立刻更换面板端口",
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ func (receiver *ValidationServiceProvider) rules() []validation.Rule {
|
||||
return []validation.Rule{
|
||||
&rules.Exists{},
|
||||
&rules.NotExists{},
|
||||
&rules.Captcha{},
|
||||
&rules.PathExists{},
|
||||
&rules.PathNotExists{},
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/captcha"
|
||||
)
|
||||
|
||||
type Captcha struct {
|
||||
}
|
||||
|
||||
// Signature The name of the rule.
|
||||
func (receiver *Captcha) Signature() string {
|
||||
return "captcha"
|
||||
}
|
||||
|
||||
// Passes Determine if the validation rule passes.
|
||||
func (receiver *Captcha) Passes(data validation.Data, val any, options ...any) bool {
|
||||
captchaID, exist := data.Get("captcha_id")
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
|
||||
// 第一个参数(如果有),是否清除验证码,如 false
|
||||
clear := true
|
||||
var err error
|
||||
if len(options) > 0 {
|
||||
clear, err = strconv.ParseBool(options[0].(string))
|
||||
if err != nil {
|
||||
clear = true
|
||||
}
|
||||
}
|
||||
|
||||
if !captcha.NewCaptcha().VerifyCaptcha(captchaID.(string), val.(string), clear) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Message Get the validation error message.
|
||||
func (receiver *Captcha) Message() string {
|
||||
return ""
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/gookit/validate/locales/zhcn"
|
||||
"github.com/goravel/framework/foundation"
|
||||
|
||||
@@ -8,6 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func Boot() {
|
||||
debug.SetGCPercent(10)
|
||||
debug.SetMemoryLimit(64 << 20)
|
||||
|
||||
zhcn.RegisterGlobal()
|
||||
|
||||
app := foundation.NewApplication()
|
||||
|
||||
+5
-2
@@ -17,7 +17,9 @@ import (
|
||||
"github.com/goravel/framework/queue"
|
||||
"github.com/goravel/framework/route"
|
||||
"github.com/goravel/framework/schedule"
|
||||
"github.com/goravel/framework/session"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/support/path"
|
||||
"github.com/goravel/framework/testing"
|
||||
"github.com/goravel/framework/translation"
|
||||
"github.com/goravel/framework/validation"
|
||||
@@ -37,7 +39,7 @@ func init() {
|
||||
// This value is the name of your application. This value is used when the
|
||||
// framework needs to place the application's name in a notification or
|
||||
// any other location as required by the application or its pkg.
|
||||
"name": "Panel",
|
||||
"name": "耗子面板",
|
||||
|
||||
// Application Environment
|
||||
//
|
||||
@@ -74,7 +76,7 @@ func init() {
|
||||
//
|
||||
// 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"),
|
||||
"lang_path": path.Executable("lang"),
|
||||
|
||||
// Encryption Key
|
||||
//
|
||||
@@ -103,6 +105,7 @@ func init() {
|
||||
&crypt.ServiceProvider{},
|
||||
&filesystem.ServiceProvider{},
|
||||
&validation.ServiceProvider{},
|
||||
&session.ServiceProvider{},
|
||||
&translation.ServiceProvider{},
|
||||
&testing.ServiceProvider{},
|
||||
&providers.AppServiceProvider{},
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("captcha", map[string]any{
|
||||
// 验证码图片高度
|
||||
"height": 60,
|
||||
|
||||
// 验证码图片宽度
|
||||
"width": 120,
|
||||
|
||||
// 验证码的长度
|
||||
"length": 6,
|
||||
|
||||
// 数字的最大倾斜角度
|
||||
"maxskew": 0.6,
|
||||
|
||||
// 图片背景里的混淆点数量
|
||||
"dotcount": 40,
|
||||
|
||||
// 过期时间,单位是分钟
|
||||
"expire_time": 5,
|
||||
|
||||
// debug 模式下的过期时间,方便本地开发调试
|
||||
"debug_expire_time": 3600,
|
||||
|
||||
// 非 production 环境,使用此 key 可跳过验证,方便测试
|
||||
"testing_key": "captcha_skip_test",
|
||||
})
|
||||
}
|
||||
+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,
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -14,7 +15,7 @@ func init() {
|
||||
"connections": map[string]any{
|
||||
"panel": map[string]any{
|
||||
"driver": "sqlite",
|
||||
"database": config.Env("DB_FILE", facades.App().ExecutablePath("storage/panel.db")),
|
||||
"database": config.Env("DB_FILE", path.Executable("storage/panel.db")),
|
||||
"prefix": "",
|
||||
"singular": false, // Table name is singular
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -24,7 +25,7 @@ func init() {
|
||||
"disks": map[string]any{
|
||||
"local": map[string]any{
|
||||
"driver": "local",
|
||||
"root": facades.App().ExecutablePath("storage"),
|
||||
"root": path.Executable("storage"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+3
-4
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/path"
|
||||
ginfacades "github.com/goravel/gin/facades"
|
||||
)
|
||||
|
||||
@@ -28,8 +29,6 @@ func init() {
|
||||
"host": "",
|
||||
// HTTP Port
|
||||
"port": config.Env("APP_PORT", "8888"),
|
||||
// HTTP Entrance
|
||||
"entrance": config.Env("APP_ENTRANCE", "/"),
|
||||
// HTTPS Configuration
|
||||
"tls": map[string]any{
|
||||
// HTTPS Host
|
||||
@@ -39,9 +38,9 @@ func init() {
|
||||
// SSL Certificate
|
||||
"ssl": map[string]any{
|
||||
// ca.pem
|
||||
"cert": config.Env("APP_SSL_CERT", facades.App().ExecutablePath("storage/ssl.crt")),
|
||||
"cert": config.Env("APP_SSL_CERT", path.Executable("storage/ssl.crt")),
|
||||
// ca.key
|
||||
"key": config.Env("APP_SSL_KEY", facades.App().ExecutablePath("storage/ssl.key")),
|
||||
"key": config.Env("APP_SSL_KEY", path.Executable("storage/ssl.key")),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+10
-2
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -26,17 +27,24 @@ func init() {
|
||||
},
|
||||
"single": map[string]any{
|
||||
"driver": "single",
|
||||
"path": "storage/logs/panel.log",
|
||||
"path": path.Executable("storage/logs/panel.log"),
|
||||
"level": "info",
|
||||
"print": true,
|
||||
},
|
||||
"daily": map[string]any{
|
||||
"driver": "daily",
|
||||
"path": "storage/logs/panel.log",
|
||||
"path": path.Executable("storage/logs/panel.log"),
|
||||
"level": "info",
|
||||
"days": 7,
|
||||
"print": true,
|
||||
},
|
||||
"http": map[string]any{
|
||||
"driver": "daily",
|
||||
"path": path.Executable("storage/logs/http.log"),
|
||||
"level": "info",
|
||||
"days": 7,
|
||||
"print": false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,7 +8,9 @@ func init() {
|
||||
config := facades.Config()
|
||||
config.Add("panel", map[string]any{
|
||||
"name": "耗子面板",
|
||||
"version": "v2.2.14",
|
||||
"version": "v2.2.24",
|
||||
"ssl": config.Env("APP_SSL", false),
|
||||
// 安全入口
|
||||
"entrance": config.Env("APP_ENTRANCE", "/"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("session", map[string]any{
|
||||
// Default Session Driver
|
||||
//
|
||||
// This option controls the default session "driver" that will be used on
|
||||
// requests. By default, we will use the lightweight file session driver, but you
|
||||
// may specify any of the other wonderful drivers provided here.
|
||||
//
|
||||
// Supported: "file"
|
||||
"driver": config.Env("SESSION_DRIVER", "file"),
|
||||
|
||||
// Session Lifetime
|
||||
//
|
||||
// Here you may specify the number of minutes that you wish the session
|
||||
// to be allowed to remain idle before it expires. If you want them
|
||||
// to immediately expire when the browser is closed, then you may
|
||||
// indicate that via the expire_on_close configuration option.
|
||||
"lifetime": config.Env("SESSION_LIFETIME", 120),
|
||||
|
||||
"expire_on_close": config.Env("SESSION_EXPIRE_ON_CLOSE", false),
|
||||
|
||||
// Session File Location
|
||||
//
|
||||
// When using the file session driver, we need a location where the
|
||||
// session files may be stored. A default has been set for you, but a
|
||||
// different location may be specified. This is only needed for file sessions.
|
||||
"files": path.Executable("storage/framework/sessions"),
|
||||
|
||||
// Session Sweeping Lottery
|
||||
//
|
||||
// Some session drivers must manually sweep their storage location to get
|
||||
// rid of old sessions from storage. Here are the chances out of 100 that
|
||||
// the sweeper will sweep the storage location. The default is 2 out of 100.
|
||||
"lottery": []int{2, 100},
|
||||
|
||||
// Session Cookie Name
|
||||
//
|
||||
// Here you may change the name of the cookie used to identify a session
|
||||
// in the application. The name specified here will get used every time
|
||||
// a new session cookie is created by the framework for every driver.
|
||||
"cookie": config.Env("SESSION_COOKIE", "panel_session"),
|
||||
|
||||
// Session Cookie Path
|
||||
//
|
||||
// The session cookie path determines the path for which the cookie will
|
||||
// be regarded as available.Typically, this will be the root path of
|
||||
// your application, but you are free to change this when necessary.
|
||||
"path": config.Env("SESSION_PATH", "/"),
|
||||
|
||||
// Session Cookie Domain
|
||||
//
|
||||
// Here you may change the domain of the cookie used to identify a session
|
||||
// in your application.This will determine which domains the cookie is
|
||||
// available to in your application.A sensible default has been set.
|
||||
"domain": config.Env("SESSION_DOMAIN", ""),
|
||||
|
||||
// HTTPS Only Cookies
|
||||
//
|
||||
// By setting this option to true, session cookies will only be sent back
|
||||
// to the server if the browser has an HTTPS connection. This will keep
|
||||
// the cookie from being sent to you if it cannot be done securely.
|
||||
"secure": config.Env("SESSION_SECURE", false),
|
||||
|
||||
// HTTP Access Only
|
||||
//
|
||||
// Setting this to true will prevent JavaScript from accessing the value of
|
||||
// the cookie, and the cookie will only be accessible through the HTTP protocol.
|
||||
"http_only": config.Env("SESSION_HTTP_ONLY", true),
|
||||
|
||||
// Same-Site Cookies
|
||||
//
|
||||
// This option determines how your cookies behave when cross-site requests
|
||||
// take place, and can be used to mitigate CSRF attacks.By default, we
|
||||
// will set this value to "lax" since this is a secure default value.
|
||||
"same_site": config.Env("SESSION_SAME_SITE", "lax"),
|
||||
})
|
||||
}
|
||||
+57
-13
@@ -3235,7 +3235,6 @@ const docTemplate = `{
|
||||
"BearerToken": []
|
||||
}
|
||||
],
|
||||
"description": "获取当前登录用户信息",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3255,7 +3254,6 @@ const docTemplate = `{
|
||||
},
|
||||
"/panel/user/login": {
|
||||
"post": {
|
||||
"description": "通过用户名和密码获取访问令牌",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3299,6 +3297,30 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/user/logout": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户鉴权"
|
||||
],
|
||||
"summary": "登出",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.SuccessResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/website/backupList": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3574,8 +3596,8 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/websites/{id}": {
|
||||
"delete": {
|
||||
"/panel/websites/delete": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerToken": []
|
||||
@@ -3593,11 +3615,13 @@ const docTemplate = `{
|
||||
"summary": "删除网站",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "网站 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
"description": "request",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/requests.Delete"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3648,7 +3672,7 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/types.Website"
|
||||
"$ref": "#/definitions/types.WebsiteAdd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5156,7 +5180,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "DNS 提供商 (dnspod, aliyun, cloudflare)",
|
||||
"description": "DNS 提供商 (dnspod, tencent, aliyun, cloudflare)",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
@@ -5506,6 +5530,20 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"requests.Delete": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requests.DeleteBackup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5638,7 +5676,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
@@ -5721,6 +5759,12 @@ const docTemplate = `{
|
||||
"ssl_certificate_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"tls_ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"waf": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5915,7 +5959,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.Website": {
|
||||
"types.WebsiteAdd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
|
||||
+57
-13
@@ -3228,7 +3228,6 @@
|
||||
"BearerToken": []
|
||||
}
|
||||
],
|
||||
"description": "获取当前登录用户信息",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3248,7 +3247,6 @@
|
||||
},
|
||||
"/panel/user/login": {
|
||||
"post": {
|
||||
"description": "通过用户名和密码获取访问令牌",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3292,6 +3290,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/user/logout": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户鉴权"
|
||||
],
|
||||
"summary": "登出",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.SuccessResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/website/backupList": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3567,8 +3589,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/websites/{id}": {
|
||||
"delete": {
|
||||
"/panel/websites/delete": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerToken": []
|
||||
@@ -3586,11 +3608,13 @@
|
||||
"summary": "删除网站",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "网站 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
"description": "request",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/requests.Delete"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3641,7 +3665,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/types.Website"
|
||||
"$ref": "#/definitions/types.WebsiteAdd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5149,7 +5173,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "DNS 提供商 (dnspod, aliyun, cloudflare)",
|
||||
"description": "DNS 提供商 (dnspod, tencent, aliyun, cloudflare)",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
@@ -5499,6 +5523,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"requests.Delete": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requests.DeleteBackup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5631,7 +5669,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
@@ -5714,6 +5752,12 @@
|
||||
"ssl_certificate_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"tls_ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"waf": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5908,7 +5952,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.Website": {
|
||||
"types.WebsiteAdd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
|
||||
+54
-28
@@ -148,7 +148,7 @@ definitions:
|
||||
description: 备注名称
|
||||
type: string
|
||||
type:
|
||||
description: DNS 提供商 (dnspod, aliyun, cloudflare)
|
||||
description: DNS 提供商 (dnspod, tencent, aliyun, cloudflare)
|
||||
type: string
|
||||
updated_at:
|
||||
$ref: '#/definitions/github_com_goravel_framework_support_carbon.DateTime'
|
||||
@@ -377,6 +377,15 @@ definitions:
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
requests.Delete:
|
||||
properties:
|
||||
db:
|
||||
type: boolean
|
||||
id:
|
||||
type: integer
|
||||
path:
|
||||
type: boolean
|
||||
type: object
|
||||
requests.DeleteBackup:
|
||||
properties:
|
||||
name:
|
||||
@@ -462,7 +471,7 @@ definitions:
|
||||
group:
|
||||
type: string
|
||||
mode:
|
||||
type: integer
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
path:
|
||||
@@ -516,6 +525,10 @@ definitions:
|
||||
type: string
|
||||
ssl_certificate_key:
|
||||
type: string
|
||||
tls_ports:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
waf:
|
||||
type: boolean
|
||||
waf_cache:
|
||||
@@ -641,7 +654,7 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
types.Website:
|
||||
types.WebsiteAdd:
|
||||
properties:
|
||||
db:
|
||||
type: boolean
|
||||
@@ -2632,7 +2645,6 @@ paths:
|
||||
- 系统
|
||||
/panel/user/info:
|
||||
get:
|
||||
description: 获取当前登录用户信息
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -2649,7 +2661,6 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 通过用户名和密码获取访问令牌
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
@@ -2675,6 +2686,20 @@ paths:
|
||||
summary: 登录
|
||||
tags:
|
||||
- 用户鉴权
|
||||
/panel/user/logout:
|
||||
post:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 登出
|
||||
tags:
|
||||
- 用户鉴权
|
||||
/panel/website/backupList:
|
||||
get:
|
||||
parameters:
|
||||
@@ -2836,28 +2861,6 @@ paths:
|
||||
summary: 添加网站
|
||||
tags:
|
||||
- 网站
|
||||
/panel/websites/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: 网站 ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 删除网站
|
||||
tags:
|
||||
- 网站
|
||||
/panel/websites/{id}/config:
|
||||
get:
|
||||
consumes:
|
||||
@@ -2878,7 +2881,7 @@ paths:
|
||||
- $ref: '#/definitions/controllers.SuccessResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/types.Website'
|
||||
$ref: '#/definitions/types.WebsiteAdd'
|
||||
type: object
|
||||
security:
|
||||
- BearerToken: []
|
||||
@@ -3044,6 +3047,29 @@ paths:
|
||||
summary: 更新网站备注
|
||||
tags:
|
||||
- 网站
|
||||
/panel/websites/delete:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/requests.Delete'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SuccessResponse'
|
||||
security:
|
||||
- BearerToken: []
|
||||
summary: 删除网站
|
||||
tags:
|
||||
- 网站
|
||||
/plugins/frp/config:
|
||||
get:
|
||||
description: 获取 Frp 配置
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package embed
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed frontend/*
|
||||
var PublicFS embed.FS
|
||||
|
||||
//go:embed website/*
|
||||
var WebsiteFS embed.FS
|
||||
@@ -0,0 +1 @@
|
||||
*
|
||||
@@ -0,0 +1,55 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 Not Found</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f9f9f9;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 2em auto;
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
p {
|
||||
color: #555;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #007bff;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
margin: 2em 15px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>404 Not Found</h1>
|
||||
<p>由 <a target="_blank" href="https://panel.haozi.net">耗子面板</a> 强力驱动</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user