diff --git a/app/console/commands/panel.go b/app/console/commands/panel.go index 8c76749c..0b041406 100644 --- a/app/console/commands/panel.go +++ b/app/console/commands/panel.go @@ -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} 写入插件安装状态") diff --git a/app/http/controllers/plugin_controller.go b/app/http/controllers/plugin_controller.go index 73cc465d..8bdaad22 100644 --- a/app/http/controllers/plugin_controller.go +++ b/app/http/controllers/plugin_controller.go @@ -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, "任务已提交") } diff --git a/internal/plugin.go b/internal/plugin.go index b10ea4d9..9e19e89f 100644 --- a/internal/plugin.go +++ b/internal/plugin.go @@ -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 } diff --git a/internal/services/plugin.go b/internal/services/plugin.go index bba316ac..ce67e522 100644 --- a/internal/services/plugin.go +++ b/internal/services/plugin.go @@ -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 +} diff --git a/internal/services/website.go b/internal/services/website.go index 2c7d36ac..574e0c9d 100644 --- a/internal/services/website.go +++ b/internal/services/website.go @@ -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 +} diff --git a/internal/website.go b/internal/website.go index ef47fb37..6746703a 100644 --- a/internal/website.go +++ b/internal/website.go @@ -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 {