| | |
| | |
| | |
| |
|
| | 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" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | 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) { |
| | |
| | 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"` |
| | } |
| |
|