2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 07:57:21 +08:00

refactor: 插件

This commit is contained in:
耗子
2024-07-29 03:25:53 +08:00
parent 847e55bc8b
commit 325a218e20
13 changed files with 293 additions and 83 deletions

3
app/plugins/README.md Normal file
View File

@@ -0,0 +1,3 @@
# 面板插件目录
文档待定

View 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
View File

@@ -0,0 +1,6 @@
package plugins
import _ "github.com/TheTNB/panel/v2/app/plugins/openresty"
// Boot 启动所有插件
func Boot() {}

View 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)
}

View 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)
})
})
},
})
}

View File

@@ -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())

View 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)
}
}

View File

@@ -10,8 +10,7 @@ import (
"github.com/TheTNB/panel/v2/routes"
)
type RouteServiceProvider struct {
}
type RouteServiceProvider struct{}
func (receiver *RouteServiceProvider) Register(app foundation.Application) {
}

View File

@@ -111,6 +111,7 @@ func init() {
&providers.AppServiceProvider{},
&providers.AuthServiceProvider{},
&providers.RouteServiceProvider{},
&providers.PluginServiceProvider{},
&providers.ConsoleServiceProvider{},
&providers.QueueServiceProvider{},
&providers.EventServiceProvider{},

View File

@@ -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

View File

@@ -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 获取已安装的插件

View File

@@ -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)
}

View File

@@ -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) // 启动时执行的命令
}