2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 13:47:15 +08:00

feat: CLI 添加插件和网站的命令

This commit is contained in:
耗子
2024-03-24 22:38:22 +08:00
parent da2e66b7c6
commit 5703e3354f
6 changed files with 291 additions and 136 deletions

View File

@@ -48,6 +48,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
arg2 := ctx.Argument(2)
arg3 := ctx.Argument(3)
arg4 := ctx.Argument(4)
arg5 := ctx.Argument(5)
switch action {
case "init":
@@ -564,6 +565,127 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("删除设置成功")
case "addSite":
name := arg1
domain := arg2
port := arg3
path := arg4
php := arg5
if len(name) == 0 || len(domain) == 0 || len(port) == 0 || len(path) == 0 {
color.Redln("参数错误")
return nil
}
domains := strings.Split(domain, ",")
ports := strings.Split(port, ",")
if len(domains) == 0 || len(ports) == 0 {
color.Redln("参数错误")
return nil
}
var uintPorts []uint
for _, p := range ports {
uintPorts = append(uintPorts, cast.ToUint(p))
}
website := services.NewWebsiteImpl()
id, err := website.GetIDByName(name)
if err != nil {
color.Redln(err.Error())
return nil
}
if id != 0 {
color.Redln("网站名已存在")
return nil
}
_, err = website.Add(internal.PanelWebsite{
Name: name,
Status: true,
Domains: domains,
Ports: uintPorts,
Path: path,
Php: php,
Ssl: false,
Db: false,
})
if err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("网站添加成功")
case "removeSite":
name := arg1
if len(name) == 0 {
color.Redln("参数错误")
return nil
}
website := services.NewWebsiteImpl()
id, err := website.GetIDByName(name)
if err != nil {
color.Redln(err.Error())
return nil
}
if id == 0 {
color.Redln("网站名不存在")
return nil
}
if err = website.Delete(id); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("网站删除成功")
case "installPlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Install(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("任务已提交")
case "uninstallPlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Uninstall(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("任务已提交")
case "updatePlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Update(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("任务已提交")
default:
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
color.Greenln("请使用以下命令:")
@@ -575,6 +697,11 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务[任务卡住时使用]")
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} 备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量")
color.Greenln("panel cutoff {website_name} {save_copies} 切割网站日志并保留指定数量")
color.Greenln("panel installPlugin {slug} 安装插件")
color.Greenln("panel uninstallPlugin {slug} 卸载插件")
color.Greenln("panel updatePlugin {slug} 更新插件")
color.Greenln("panel addSite {name} {domain} {port} {path} {php} 添加网站[域名和端口用英文逗号分隔]")
color.Greenln("panel removeSite {name} 删除网站")
color.Redln("以下命令请在开发者指导下使用:")
color.Yellowln("panel init 初始化面板")
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")

View File

@@ -92,162 +92,33 @@ func (r *PluginController) List(ctx http.Context) http.Response {
// Install 安装插件
func (r *PluginController) Install(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Install(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID != 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件已安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
var task models.Task
task.Name = "安装插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Install + ` >> '/tmp/` + plugin.Slug + `.log' 2>&1`
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
// Uninstall 卸载插件
func (r *PluginController) Uninstall(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Uninstall(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
var task models.Task
task.Name = "卸载插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
// Update 更新插件
func (r *PluginController) Update(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Update(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
var task models.Task
task.Name = "更新插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}

View File

@@ -20,4 +20,7 @@ type Plugin interface {
All() []PanelPlugin
GetBySlug(slug string) PanelPlugin
GetInstalledBySlug(slug string) models.Plugin
Install(slug string) error
Uninstall(slug string) error
Update(slug string) error
}

View File

@@ -2,6 +2,8 @@
package services
import (
"errors"
"github.com/goravel/framework/facades"
"panel/app/models"
@@ -9,10 +11,13 @@ import (
)
type PluginImpl struct {
task internal.Task
}
func NewPluginImpl() *PluginImpl {
return &PluginImpl{}
return &PluginImpl{
task: NewTaskImpl(),
}
}
// AllInstalled 获取已安装的所有插件
@@ -71,3 +76,141 @@ func (r *PluginImpl) GetInstalledBySlug(slug string) models.Plugin {
return plugin
}
// Install 安装插件
func (r *PluginImpl) Install(slug string) error {
plugin := r.GetBySlug(slug)
installedPlugin := r.GetInstalledBySlug(slug)
installedPlugins, err := r.AllInstalled()
if err != nil {
return err
}
if installedPlugin.ID != 0 {
return errors.New("插件已安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
}
}
var task models.Task
task.Name = "安装插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Install + ` >> '/tmp/` + plugin.Slug + `.log' 2>&1`
task.Log = "/tmp/" + plugin.Slug + ".log"
if err = facades.Orm().Query().Create(&task); err != nil {
return errors.New("创建任务失败")
}
r.task.Process(task.ID)
return nil
}
// Uninstall 卸载插件
func (r *PluginImpl) Uninstall(slug string) error {
plugin := r.GetBySlug(slug)
installedPlugin := r.GetInstalledBySlug(slug)
installedPlugins, err := r.AllInstalled()
if err != nil {
return err
}
if installedPlugin.ID == 0 {
return errors.New("插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
}
}
var task models.Task
task.Name = "卸载插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err = facades.Orm().Query().Create(&task); err != nil {
return errors.New("创建任务失败")
}
r.task.Process(task.ID)
return nil
}
// Update 更新插件
func (r *PluginImpl) Update(slug string) error {
plugin := r.GetBySlug(slug)
installedPlugin := r.GetInstalledBySlug(slug)
installedPlugins, err := r.AllInstalled()
if err != nil {
return err
}
if installedPlugin.ID == 0 {
return errors.New("插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
}
}
var task models.Task
task.Name = "更新插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err = facades.Orm().Query().Create(&task); err != nil {
return errors.New("创建任务失败")
}
r.task.Process(task.ID)
return nil
}

View File

@@ -620,3 +620,13 @@ func (r *WebsiteImpl) GetConfigByName(name string) (internal.WebsiteSetting, err
return r.GetConfig(website.ID)
}
// GetIDByName 根据网站名称获取网站ID
func (r *WebsiteImpl) GetIDByName(name string) (uint, error) {
var website models.Website
if err := facades.Orm().Query().Where("name", name).First(&website); err != nil {
return 0, err
}
return website.ID, nil
}

View File

@@ -12,6 +12,7 @@ type Website interface {
Delete(id uint) error
GetConfig(id uint) (WebsiteSetting, error)
GetConfigByName(name string) (WebsiteSetting, error)
GetIDByName(name string) (uint, error)
}
type PanelWebsite struct {