mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 07:57:21 +08:00
refactor: 插件
This commit is contained in:
3
app/plugins/README.md
Normal file
3
app/plugins/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 面板插件目录
|
||||
|
||||
文档待定
|
||||
17
app/plugins/loader/loader.go
Normal file
17
app/plugins/loader/loader.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/v2/pkg/types"
|
||||
)
|
||||
|
||||
var data []*types.Plugin
|
||||
|
||||
// All 获取所有插件
|
||||
func All() []*types.Plugin {
|
||||
return data
|
||||
}
|
||||
|
||||
// New 新注册插件
|
||||
func New(plugin *types.Plugin) {
|
||||
data = append(data, plugin)
|
||||
}
|
||||
6
app/plugins/main.go
Normal file
6
app/plugins/main.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package plugins
|
||||
|
||||
import _ "github.com/TheTNB/panel/v2/app/plugins/openresty"
|
||||
|
||||
// Boot 启动所有插件
|
||||
func Boot() {}
|
||||
187
app/plugins/openresty/controller.go
Normal file
187
app/plugins/openresty/controller.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/v2/pkg/h"
|
||||
"github.com/TheTNB/panel/v2/pkg/io"
|
||||
"github.com/TheTNB/panel/v2/pkg/shell"
|
||||
"github.com/TheTNB/panel/v2/pkg/str"
|
||||
"github.com/TheTNB/panel/v2/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/v2/pkg/types"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
// Dependent services
|
||||
}
|
||||
|
||||
func NewController() *Controller {
|
||||
return &Controller{}
|
||||
}
|
||||
|
||||
// GetConfig
|
||||
//
|
||||
// @Summary 获取配置
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/config [get]
|
||||
func (r *Controller) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := io.Read("/www/server/openresty/conf/nginx.conf")
|
||||
if err != nil {
|
||||
return h.Error(ctx, http.StatusInternalServerError, "获取配置失败")
|
||||
}
|
||||
|
||||
return h.Success(ctx, config)
|
||||
}
|
||||
|
||||
// SaveConfig
|
||||
//
|
||||
// @Summary 保存配置
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param config body string true "配置"
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/config [post]
|
||||
func (r *Controller) SaveConfig(ctx http.Context) http.Response {
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
return h.Error(ctx, http.StatusInternalServerError, "配置不能为空")
|
||||
}
|
||||
|
||||
if err := io.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
|
||||
return h.Error(ctx, http.StatusInternalServerError, "保存配置失败")
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return h.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err))
|
||||
}
|
||||
|
||||
return h.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// ErrorLog
|
||||
//
|
||||
// @Summary 获取错误日志
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/errorLog [get]
|
||||
func (r *Controller) ErrorLog(ctx http.Context) http.Response {
|
||||
if !io.Exists("/www/wwwlogs/nginx_error.log") {
|
||||
return h.Success(ctx, "")
|
||||
}
|
||||
|
||||
out, err := shell.Execf("tail -n 100 /www/wwwlogs/openresty_error.log")
|
||||
if err != nil {
|
||||
return h.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return h.Success(ctx, out)
|
||||
}
|
||||
|
||||
// ClearErrorLog
|
||||
//
|
||||
// @Summary 清空错误日志
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/clearErrorLog [post]
|
||||
func (r *Controller) ClearErrorLog(ctx http.Context) http.Response {
|
||||
if out, err := shell.Execf("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
|
||||
return h.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
return h.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// Load
|
||||
//
|
||||
// @Summary 获取负载状态
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/load [get]
|
||||
func (r *Controller) Load(ctx http.Context) http.Response {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
return h.Success(ctx, []types.NV{})
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
var data []types.NV
|
||||
|
||||
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
|
||||
if err != nil {
|
||||
return h.Error(ctx, http.StatusInternalServerError, "获取负载失败")
|
||||
}
|
||||
data = append(data, types.NV{
|
||||
Name: "工作进程",
|
||||
Value: workers,
|
||||
})
|
||||
|
||||
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
|
||||
if err != nil {
|
||||
return h.Error(ctx, http.StatusInternalServerError, "获取负载失败")
|
||||
}
|
||||
mem := str.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, types.NV{
|
||||
Name: "内存占用",
|
||||
Value: mem,
|
||||
})
|
||||
|
||||
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: "活跃连接数",
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "总连接次数",
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总握手次数",
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总请求次数",
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "请求数",
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "响应数",
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "驻留进程",
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
return h.Success(ctx, data)
|
||||
}
|
||||
37
app/plugins/openresty/main.go
Normal file
37
app/plugins/openresty/main.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
|
||||
"github.com/TheTNB/panel/v2/app/http/middleware"
|
||||
"github.com/TheTNB/panel/v2/app/plugins/loader"
|
||||
"github.com/TheTNB/panel/v2/pkg/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
loader.New(&types.Plugin{
|
||||
Name: "OpenResty",
|
||||
Description: "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台",
|
||||
Slug: "openresty",
|
||||
Version: "1.25.3.1",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: "bash /www/panel/scripts/openresty/install.sh",
|
||||
Uninstall: "bash /www/panel/scripts/openresty/uninstall.sh",
|
||||
Update: "bash /www/panel/scripts/openresty/install.sh",
|
||||
Boot: func(app foundation.Application) {
|
||||
RouteFacade := app.MakeRoute()
|
||||
RouteFacade.Prefix("api/plugins/openresty").Middleware(middleware.Session(), middleware.MustInstall()).Group(func(r route.Router) {
|
||||
r.Prefix("openresty").Group(func(route route.Router) {
|
||||
controller := NewController()
|
||||
route.Get("load", controller.Load)
|
||||
route.Get("config", controller.GetConfig)
|
||||
route.Post("config", controller.SaveConfig)
|
||||
route.Get("errorLog", controller.ErrorLog)
|
||||
route.Post("clearErrorLog", controller.ClearErrorLog)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type EventServiceProvider struct {
|
||||
}
|
||||
type EventServiceProvider struct{}
|
||||
|
||||
func (receiver *EventServiceProvider) Register(app foundation.Application) {
|
||||
facades.Event().Register(receiver.listen())
|
||||
|
||||
20
app/providers/plugin_service_provider.go
Normal file
20
app/providers/plugin_service_provider.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
|
||||
"github.com/TheTNB/panel/v2/app/plugins"
|
||||
"github.com/TheTNB/panel/v2/app/plugins/loader"
|
||||
)
|
||||
|
||||
type PluginServiceProvider struct{}
|
||||
|
||||
func (receiver *PluginServiceProvider) Register(app foundation.Application) {
|
||||
plugins.Boot()
|
||||
}
|
||||
|
||||
func (receiver *PluginServiceProvider) Boot(app foundation.Application) {
|
||||
for _, plugin := range loader.All() {
|
||||
plugin.Boot(app)
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,7 @@ import (
|
||||
"github.com/TheTNB/panel/v2/routes"
|
||||
)
|
||||
|
||||
type RouteServiceProvider struct {
|
||||
}
|
||||
type RouteServiceProvider struct{}
|
||||
|
||||
func (receiver *RouteServiceProvider) Register(app foundation.Application) {
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ func init() {
|
||||
&providers.AppServiceProvider{},
|
||||
&providers.AuthServiceProvider{},
|
||||
&providers.RouteServiceProvider{},
|
||||
&providers.PluginServiceProvider{},
|
||||
&providers.ConsoleServiceProvider{},
|
||||
&providers.QueueServiceProvider{},
|
||||
&providers.EventServiceProvider{},
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
type Plugin interface {
|
||||
AllInstalled() ([]models.Plugin, error)
|
||||
All() []types.Plugin
|
||||
GetBySlug(slug string) types.Plugin
|
||||
All() []*types.Plugin
|
||||
GetBySlug(slug string) *types.Plugin
|
||||
GetInstalledBySlug(slug string) models.Plugin
|
||||
Install(slug string) error
|
||||
Uninstall(slug string) error
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/v2/app/models"
|
||||
"github.com/TheTNB/panel/v2/app/plugins/loader"
|
||||
"github.com/TheTNB/panel/v2/internal"
|
||||
"github.com/TheTNB/panel/v2/pkg/io"
|
||||
"github.com/TheTNB/panel/v2/pkg/types"
|
||||
@@ -33,8 +34,8 @@ func (r *PluginImpl) AllInstalled() ([]models.Plugin, error) {
|
||||
}
|
||||
|
||||
// All 获取所有插件
|
||||
func (r *PluginImpl) All() []types.Plugin {
|
||||
var plugins = []types.Plugin{
|
||||
func (r *PluginImpl) All() []*types.Plugin {
|
||||
var _ = []types.Plugin{
|
||||
types.PluginOpenResty,
|
||||
types.PluginMySQL57,
|
||||
types.PluginMySQL80,
|
||||
@@ -59,18 +60,18 @@ func (r *PluginImpl) All() []types.Plugin {
|
||||
types.PluginToolBox,
|
||||
}
|
||||
|
||||
return plugins
|
||||
return loader.All()
|
||||
}
|
||||
|
||||
// GetBySlug 根据 slug 获取插件
|
||||
func (r *PluginImpl) GetBySlug(slug string) types.Plugin {
|
||||
func (r *PluginImpl) GetBySlug(slug string) *types.Plugin {
|
||||
for _, item := range r.All() {
|
||||
if item.Slug == slug {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
return types.Plugin{}
|
||||
return &types.Plugin{}
|
||||
}
|
||||
|
||||
// GetInstalledBySlug 根据 slug 获取已安装的插件
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/v2/app/http/middleware"
|
||||
)
|
||||
|
||||
var plugins sync.Map
|
||||
|
||||
type Plugin struct {
|
||||
Name string // 插件名称
|
||||
Description string // 插件描述
|
||||
Slug string // 插件标识
|
||||
Version string // 插件版本
|
||||
Requires []string // 依赖插件
|
||||
Excludes []string // 排除插件
|
||||
Install string // 安装命令
|
||||
Uninstall string // 卸载命令
|
||||
Update string // 更新命令
|
||||
OnEnable func() error // 启用插件后执行的命令
|
||||
OnDisable func() error // 禁用插件后执行的命令
|
||||
}
|
||||
|
||||
func NewPlugin() (*Plugin, error) {
|
||||
pc, _, _, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
panic("unable to get caller")
|
||||
}
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
a := strings.LastIndex(name, "/")
|
||||
if a < 0 {
|
||||
panic("invalid package name: " + name)
|
||||
}
|
||||
name = name[a+1:]
|
||||
b := strings.Index(name, ".")
|
||||
if b < 0 {
|
||||
panic("invalid package name: " + name)
|
||||
}
|
||||
slug := name[:b]
|
||||
|
||||
if _, ok = plugins.Load(slug); ok {
|
||||
return nil, errors.New("plugin already exists")
|
||||
}
|
||||
|
||||
instance := &Plugin{
|
||||
Slug: slug,
|
||||
}
|
||||
plugins.Store(slug, instance)
|
||||
|
||||
return instance, instance.OnEnable()
|
||||
}
|
||||
|
||||
// Route 注册路由
|
||||
func (r *Plugin) Route(group func(router route.Router)) {
|
||||
facades.Route().Prefix("api/plugins/"+r.Slug).Middleware(middleware.Session(), middleware.MustInstall()).Group(group)
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
package types
|
||||
|
||||
import "github.com/goravel/framework/contracts/foundation"
|
||||
|
||||
// Plugin 插件元数据结构
|
||||
type Plugin struct {
|
||||
Name string
|
||||
Description string
|
||||
Slug string
|
||||
Version string
|
||||
Requires []string
|
||||
Excludes []string
|
||||
Install string
|
||||
Uninstall string
|
||||
Update string
|
||||
Name string // 插件名称
|
||||
Description string // 插件描述
|
||||
Slug string // 插件标识
|
||||
Version string // 插件版本
|
||||
Requires []string // 依赖插件
|
||||
Excludes []string // 排除插件
|
||||
Install string // 安装命令
|
||||
Uninstall string // 卸载命令
|
||||
Update string // 更新命令
|
||||
Boot func(app foundation.Application) // 启动时执行的命令
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user