admin / plugins /plugins.go
AZLABS's picture
Upload folder using huggingface_hub
530729e verified
// Copyright 2019 GoAdmin Core Team. All rights reserved.
// Use of this source code is governed by a Apache-2.0 style
// license that can be found in the LICENSE file.
package plugins
import (
"bytes"
"encoding/json"
"errors"
template2 "html/template"
"net/http"
"plugin"
"time"
"github.com/GoAdminGroup/go-admin/template/icon"
"github.com/GoAdminGroup/go-admin/template/types/action"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/auth"
"github.com/GoAdminGroup/go-admin/modules/config"
"github.com/GoAdminGroup/go-admin/modules/db"
"github.com/GoAdminGroup/go-admin/modules/language"
"github.com/GoAdminGroup/go-admin/modules/logger"
"github.com/GoAdminGroup/go-admin/modules/menu"
"github.com/GoAdminGroup/go-admin/modules/remote_server"
"github.com/GoAdminGroup/go-admin/modules/service"
"github.com/GoAdminGroup/go-admin/modules/ui"
"github.com/GoAdminGroup/go-admin/modules/utils"
"github.com/GoAdminGroup/go-admin/plugins/admin/models"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
)
// Plugin as one of the key components of goAdmin has three
// methods. GetRequest return all the path registered in the
// plugin. GetHandler according the url and method return the
// corresponding handler. InitPlugin init the plugin which do
// something like init the database and set the config and register
// the routes. The Plugin must implement the three methods.
type Plugin interface {
GetHandler() context.HandlerMap
InitPlugin(services service.List)
GetGenerators() table.GeneratorList
Name() string
Prefix() string
GetInfo() Info
GetIndexURL() string
GetSettingPage() table.Generator
IsInstalled() bool
Uninstall() error
Upgrade() error
}
type Info struct {
Title string `json:"title" yaml:"title" ini:"title"`
Description string `json:"description" yaml:"description" ini:"description"`
OldVersion string `json:"old_version" yaml:"old_version" ini:"old_version"`
Version string `json:"version" yaml:"version" ini:"version"`
Author string `json:"author" yaml:"author" ini:"author"`
Banners []string `json:"banners" yaml:"banners" ini:"banners"`
Url string `json:"url" yaml:"url" ini:"url"`
Cover string `json:"cover" yaml:"cover" ini:"cover"`
MiniCover string `json:"mini_cover" yaml:"mini_cover" ini:"mini_cover"`
Website string `json:"website" yaml:"website" ini:"website"`
Agreement string `json:"agreement" yaml:"agreement" ini:"agreement"`
CreateDate time.Time `json:"create_date" yaml:"create_date" ini:"create_date"`
UpdateDate time.Time `json:"update_date" yaml:"update_date" ini:"update_date"`
ModulePath string `json:"module_path" yaml:"module_path" ini:"module_path"`
Name string `json:"name" yaml:"name" ini:"name"`
Uuid string `json:"uuid" yaml:"uuid" ini:"uuid"`
Downloaded bool `json:"downloaded" yaml:"downloaded" ini:"downloaded"`
ExtraDownloadUrl string `json:"extra_download_url" yaml:"extra_download_url" ini:"extra_download_url"`
Price []string `json:"price" yaml:"price" ini:"price"`
GoodUUIDs []string `json:"good_uuids" yaml:"good_uuids" ini:"good_uuids"`
GoodNum int64 `json:"good_num" yaml:"good_num" ini:"good_num"`
CommentNum int64 `json:"comment_num" yaml:"comment_num" ini:"comment_num"`
Order int64 `json:"order" yaml:"order" ini:"order"`
Features string `json:"features" yaml:"features" ini:"features"`
Questions []string `json:"questions" yaml:"questions" ini:"questions"`
HasBought bool `json:"has_bought" yaml:"has_bought" ini:"has_bought"`
CanUpdate bool `json:"can_update" yaml:"can_update" ini:"can_update"`
Legal bool `json:"legal" yaml:"legal" ini:"legal"`
SkipInstallation bool `json:"skip_installation" yaml:"skip_installation" ini:"skip_installation"`
}
func (i Info) IsFree() bool {
return len(i.Price) == 0
}
type Base struct {
App *context.App
Services service.List
Conn db.Connection
UI *ui.Service
PlugName string
URLPrefix string
Info Info
}
func (b *Base) InitPlugin(services service.List) {}
func (b *Base) GetGenerators() table.GeneratorList { return make(table.GeneratorList) }
func (b *Base) GetHandler() context.HandlerMap { return b.App.Handlers }
func (b *Base) Name() string { return b.PlugName }
func (b *Base) GetInfo() Info { return b.Info }
func (b *Base) Prefix() string { return b.URLPrefix }
func (b *Base) IsInstalled() bool { return false }
func (b *Base) Uninstall() error { return nil }
func (b *Base) Upgrade() error { return nil }
func (b *Base) GetIndexURL() string { return "" }
func (b *Base) GetSettingPage() table.Generator { return nil }
func (b *Base) InitBase(srv service.List, prefix string) {
b.Services = srv
b.Conn = db.GetConnection(b.Services)
b.UI = ui.GetService(b.Services)
b.URLPrefix = prefix
}
func (b *Base) SetInfo(info Info) {
b.Info = info
}
func (b *Base) Title() string {
return language.GetWithScope(b.Info.Title, b.Name())
}
func (b *Base) ExecuteTmpl(ctx *context.Context, panel types.Panel, options template.ExecuteOptions) *bytes.Buffer {
return Execute(ctx, b.Conn, *b.UI.NavButtons, auth.Auth(ctx), panel, options)
}
func (b *Base) ExecuteTmplWithNavButtons(ctx *context.Context, panel types.Panel, btns types.Buttons,
options template.ExecuteOptions) *bytes.Buffer {
return Execute(ctx, b.Conn, btns, auth.Auth(ctx), panel, options)
}
func (b *Base) ExecuteTmplWithMenu(ctx *context.Context, panel types.Panel, options template.ExecuteOptions) *bytes.Buffer {
return ExecuteWithMenu(ctx, b.Conn, *b.UI.NavButtons, auth.Auth(ctx), panel, b.Name(), b.Title(), options)
}
func (b *Base) ExecuteTmplWithCustomMenu(ctx *context.Context, panel types.Panel, menu *menu.Menu, options template.ExecuteOptions) *bytes.Buffer {
return ExecuteWithCustomMenu(ctx, *b.UI.NavButtons, auth.Auth(ctx), panel, menu, b.Title(), options)
}
func (b *Base) ExecuteTmplWithMenuAndNavButtons(ctx *context.Context, panel types.Panel, menu *menu.Menu,
btns types.Buttons, options template.ExecuteOptions) *bytes.Buffer {
return ExecuteWithMenu(ctx, b.Conn, btns, auth.Auth(ctx), panel, b.Name(), b.Title(), options)
}
func (b *Base) NewMenu(data menu.NewMenuData) (int64, error) {
return menu.NewMenu(b.Conn, data)
}
func (b *Base) HTML(ctx *context.Context, panel types.Panel, options ...template.ExecuteOptions) {
buf := b.ExecuteTmpl(ctx, panel, template.GetExecuteOptions(options))
ctx.HTMLByte(http.StatusOK, buf.Bytes())
}
func (b *Base) HTMLCustomMenu(ctx *context.Context, panel types.Panel, menu *menu.Menu, options ...template.ExecuteOptions) {
buf := b.ExecuteTmplWithCustomMenu(ctx, panel, menu, template.GetExecuteOptions(options))
ctx.HTMLByte(http.StatusOK, buf.Bytes())
}
func (b *Base) HTMLMenu(ctx *context.Context, panel types.Panel, options ...template.ExecuteOptions) {
buf := b.ExecuteTmplWithMenu(ctx, panel, template.GetExecuteOptions(options))
ctx.HTMLByte(http.StatusOK, buf.Bytes())
}
func (b *Base) HTMLBtns(ctx *context.Context, panel types.Panel, btns types.Buttons, options ...template.ExecuteOptions) {
buf := b.ExecuteTmplWithNavButtons(ctx, panel, btns, template.GetExecuteOptions(options))
ctx.HTMLByte(http.StatusOK, buf.Bytes())
}
func (b *Base) HTMLMenuWithBtns(ctx *context.Context, panel types.Panel, menu *menu.Menu, btns types.Buttons, options ...template.ExecuteOptions) {
buf := b.ExecuteTmplWithMenuAndNavButtons(ctx, panel, menu, btns, template.GetExecuteOptions(options))
ctx.HTMLByte(http.StatusOK, buf.Bytes())
}
func (b *Base) HTMLFile(ctx *context.Context, path string, data map[string]interface{}, options ...template.ExecuteOptions) {
buf := new(bytes.Buffer)
var panel types.Panel
t, err := template2.ParseFiles(path)
if err != nil {
panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment())
} else {
if err := t.Execute(buf, data); err != nil {
panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment())
} else {
panel = types.Panel{
Content: template.HTML(buf.String()),
}
}
}
b.HTML(ctx, panel, options...)
}
func (b *Base) HTMLFiles(ctx *context.Context, data map[string]interface{}, files []string, options ...template.ExecuteOptions) {
buf := new(bytes.Buffer)
var panel types.Panel
t, err := template2.ParseFiles(files...)
if err != nil {
panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment())
} else {
if err := t.Execute(buf, data); err != nil {
panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment())
} else {
panel = types.Panel{
Content: template.HTML(buf.String()),
}
}
}
b.HTML(ctx, panel, options...)
}
type BasePlugin struct {
Base
Info Info
IndexURL string
Installed bool
}
func (b *BasePlugin) GetInfo() Info { return b.Info }
func (b *BasePlugin) Name() string { return b.Info.Name }
func (b *BasePlugin) GetIndexURL() string { return b.IndexURL }
func (b *BasePlugin) IsInstalled() bool { return b.Installed }
func NewBasePluginWithInfo(info Info) Plugin {
return &BasePlugin{Info: info}
}
func NewBasePluginWithInfoAndIndexURL(info Info, u string, installed bool) Plugin {
return &BasePlugin{Info: info, IndexURL: u, Installed: installed}
}
func GetPluginsWithInfos(info []Info) Plugins {
p := make(Plugins, len(info))
for k, i := range info {
p[k] = NewBasePluginWithInfo(i)
}
return p
}
func LoadFromPlugin(mod string) Plugin {
plug, err := plugin.Open(mod)
if err != nil {
logger.Error("LoadFromPlugin err", err)
panic(err)
}
symPlugin, err := plug.Lookup("Plugin")
if err != nil {
logger.Error("LoadFromPlugin err", err)
panic(err)
}
var p Plugin
p, ok := symPlugin.(Plugin)
if !ok {
logger.Error("LoadFromPlugin err: unexpected type from module symbol")
panic(errors.New("LoadFromPlugin err: unexpected type from module symbol"))
}
return p
}
// GetHandler is a help method for Plugin GetHandler.
func GetHandler(app *context.App) context.HandlerMap { return app.Handlers }
func Execute(ctx *context.Context, conn db.Connection, navButtons types.Buttons, user models.UserModel,
panel types.Panel, options template.ExecuteOptions) *bytes.Buffer {
tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax())
return template.Execute(ctx, &template.ExecuteParam{
User: user,
TmplName: tmplName,
Tmpl: tmpl,
Panel: panel,
Config: config.Get(),
Menu: menu.GetGlobalMenu(user, conn, ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())),
Animation: options.Animation,
Buttons: navButtons.CheckPermission(user),
NoCompress: options.NoCompress,
IsPjax: ctx.IsPjax(),
Iframe: ctx.IsIframe(),
})
}
func ExecuteWithCustomMenu(ctx *context.Context,
navButtons types.Buttons,
user models.UserModel,
panel types.Panel,
menu *menu.Menu, logo string, options template.ExecuteOptions) *bytes.Buffer {
tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax())
return template.Execute(ctx, &template.ExecuteParam{
User: user,
TmplName: tmplName,
Tmpl: tmpl,
Panel: panel,
Config: config.Get(),
Menu: menu,
Animation: options.Animation,
Buttons: navButtons.CheckPermission(user),
NoCompress: options.NoCompress,
Logo: template2.HTML(logo),
IsPjax: ctx.IsPjax(),
Iframe: ctx.IsIframe(),
})
}
func ExecuteWithMenu(ctx *context.Context,
conn db.Connection,
navButtons types.Buttons,
user models.UserModel,
panel types.Panel,
name, logo string, options template.ExecuteOptions) *bytes.Buffer {
tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax())
btns := options.NavDropDownButton
if btns == nil {
btns = []*types.NavDropDownItemButton{
types.GetDropDownItemButton(language.GetFromHtml("plugin setting"),
action.Jump(config.Url("/info/plugin_"+name+"/edit"))),
types.GetDropDownItemButton(language.GetFromHtml("menus manage"),
action.Jump(config.Url("/menu?__plugin_name="+name))),
}
} else {
btns = append(btns, []*types.NavDropDownItemButton{
types.GetDropDownItemButton(language.GetFromHtml("plugin setting"),
action.Jump(config.Url("/info/plugin_"+name+"/edit"))),
types.GetDropDownItemButton(language.GetFromHtml("menus manage"),
action.Jump(config.Url("/menu?__plugin_name="+name))),
}...)
}
return template.Execute(ctx, &template.ExecuteParam{
User: user,
TmplName: tmplName,
Tmpl: tmpl,
Panel: panel,
Config: config.Get(),
Menu: menu.GetGlobalMenu(user, conn, ctx.Lang(), name).SetActiveClass(config.URLRemovePrefix(ctx.Path())),
Animation: options.Animation,
Buttons: navButtons.Copy().
RemoveInfoNavButton().
RemoveSiteNavButton().
RemoveToolNavButton().
Add(types.GetDropDownButton("", icon.Gear, btns)).CheckPermission(user),
NoCompress: options.NoCompress,
Logo: template2.HTML(logo),
IsPjax: ctx.IsPjax(),
Iframe: ctx.IsIframe(),
})
}
type Plugins []Plugin
func (pp Plugins) Add(p Plugin) Plugins {
if !pp.Exist(p) {
pp = append(pp, p)
}
return pp
}
func (pp Plugins) Exist(p Plugin) bool {
for _, v := range pp {
if v.Name() == p.Name() {
return true
}
}
return false
}
func FindByName(name string) (Plugin, bool) {
for _, v := range pluginList {
if v.Name() == name {
return v, true
}
}
return nil, false
}
func FindByNameAll(name string) (Plugin, bool) {
for _, v := range allPluginList {
if v.Name() == name {
return v, true
}
}
return nil, false
}
var (
pluginList = make(Plugins, 0)
allPluginList = make(Plugins, 0)
)
func Exist(p Plugin) bool {
return pluginList.Exist(p)
}
func Add(p Plugin) {
// TODO: 验证插件合法性
pluginList = pluginList.Add(p)
}
func GetAll(req remote_server.GetOnlineReq, token string) (Plugins, Page) {
plugs := make(Plugins, 0)
page := Page{}
res, err := remote_server.GetOnline(req, token)
if err != nil {
return plugs, page
}
var data GetOnlineRes
err = json.Unmarshal(res, &data)
if err != nil {
return plugs, page
}
if data.Code != 0 {
return plugs, page
}
plugs = GetPluginsWithInfos(data.Data.List)
page = data.Data.Page
for index, p := range plugs {
for key, value := range pluginList {
if value.Name() == p.Name() {
info := pluginList[key].GetInfo()
info.CanUpdate = utils.CompareVersion(info.Version, plugs[index].GetInfo().Version)
info.OldVersion = info.Version
info.Downloaded = true
info.Description = language.GetWithScope(info.Description, info.Name)
info.Title = language.GetWithScope(info.Title, info.Name)
info.Version = plugs[index].GetInfo().Version
plugs[index] = NewBasePluginWithInfoAndIndexURL(info, value.GetIndexURL(), value.IsInstalled())
break
}
}
}
for _, p := range plugs {
exist := false
for _, pp := range allPluginList {
if pp.Name() == p.Name() {
exist = true
break
}
}
if !exist {
allPluginList = append(allPluginList, p)
}
}
return plugs, page
}
func Get() Plugins {
var plugs = make(Plugins, len(pluginList))
copy(plugs, pluginList)
return plugs
}
type GetOnlineRes struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data GetOnlineResData `json:"data"`
}
type GetOnlineResData struct {
List []Info `json:"list"`
Count int `json:"count"`
HasMore bool `json:"has_more"`
Page Page `json:"page"`
}
type Page struct {
CSS string `json:"css"`
HTML string `json:"html"`
JS string `json:"js"`
}