github.com/kotovmak/go-admin@v1.1.1/plugins/admin/controller/plugins.go (about)

     1  package controller
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"html/template"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/GoAdminGroup/html"
    15  	"github.com/gin-gonic/gin"
    16  	"github.com/kotovmak/go-admin/modules/system"
    17  	"github.com/kotovmak/go-admin/template/types"
    18  	"github.com/kotovmak/go-admin/template/types/form"
    19  
    20  	"github.com/kotovmak/go-admin/modules/logger"
    21  
    22  	"github.com/kotovmak/go-admin/modules/config"
    23  
    24  	"github.com/kotovmak/go-admin/context"
    25  	"github.com/kotovmak/go-admin/modules/auth"
    26  	"github.com/kotovmak/go-admin/modules/language"
    27  	"github.com/kotovmak/go-admin/modules/remote_server"
    28  	"github.com/kotovmak/go-admin/modules/utils"
    29  	"github.com/kotovmak/go-admin/plugins"
    30  	"github.com/kotovmak/go-admin/plugins/admin/modules/guard"
    31  	template2 "github.com/kotovmak/go-admin/template"
    32  )
    33  
    34  func (h *Handler) Plugins(ctx *context.Context) {
    35  	list := plugins.Get()
    36  	size := types.Size(6, 3, 2)
    37  	rows := template.HTML("")
    38  	if h.config.IsNotProductionEnvironment() {
    39  		getMoreCover := config.Url("/assets/dist/img/plugin_more.png")
    40  		list = list.Add(plugins.NewBasePluginWithInfoAndIndexURL(plugins.Info{
    41  			Title:     "get more plugins",
    42  			Name:      "",
    43  			MiniCover: getMoreCover,
    44  			Cover:     getMoreCover,
    45  		}, config.Url("/plugins/store"), true))
    46  	}
    47  	for i := 0; i < len(list); i += 6 {
    48  		box1 := aBox().
    49  			SetBody(h.pluginBox(GetPluginBoxParamFromPlug(list[i]))).
    50  			GetContent()
    51  		content := aCol().SetSize(size).SetContent(box1).GetContent()
    52  		offset := len(list) - i
    53  		if offset > 6 {
    54  			offset = 6
    55  		}
    56  		for j := i + 1; j < offset; j++ {
    57  			box2 := aBox().
    58  				SetBody(h.pluginBox(GetPluginBoxParamFromPlug(list[j]))).
    59  				GetContent()
    60  			content += aCol().SetSize(size).SetContent(box2).GetContent()
    61  		}
    62  		rows += aRow().SetContent(content).GetContent()
    63  	}
    64  	h.HTML(ctx, auth.Auth(ctx), types.Panel{
    65  		Content:     rows,
    66  		CSS:         pluginsPageCSS,
    67  		Description: language.GetFromHtml("plugins"),
    68  		Title:       language.GetFromHtml("plugins"),
    69  	})
    70  }
    71  
    72  func (h *Handler) PluginStore(ctx *context.Context) {
    73  	var (
    74  		size       = types.Size(12, 6, 4)
    75  		list, page = plugins.GetAll(
    76  			remote_server.GetOnlineReq{
    77  				Page:       ctx.Query("page"),
    78  				Free:       ctx.Query("free"),
    79  				PageSize:   ctx.Query("page_size"),
    80  				Filter:     ctx.Query("filter"),
    81  				Order:      ctx.Query("order"),
    82  				Lang:       h.config.Language,
    83  				Version:    system.Version(),
    84  				CategoryId: ctx.Query("category_id"),
    85  			}, ctx.Cookie(remote_server.TokenKey))
    86  		rows = template.HTML(page.HTML)
    87  	)
    88  
    89  	if ctx.Query("page") == "" && len(list) == 0 {
    90  		h.HTML(ctx, auth.Auth(ctx), types.Panel{
    91  			Content: pluginStore404(),
    92  			CSS: template.CSS(`.plugin-store-404-content {
    93      margin: auto;
    94      width: 80%;
    95      text-align: center;
    96      color: #9e9e9e;
    97      font-size: 17px;
    98      height: 250px;
    99      line-height: 250px;
   100  }`),
   101  			Description: language.GetFromHtml("plugin store"),
   102  			Title:       language.GetFromHtml("plugin store"),
   103  		})
   104  		return
   105  	}
   106  
   107  	for i := 0; i < len(list); i += 3 {
   108  		box1 := aBox().
   109  			SetBody(h.pluginStoreBox(GetPluginBoxParamFromPlug(list[i]))).
   110  			GetContent()
   111  		col1 := aCol().SetSize(size).SetContent(box1).GetContent()
   112  		box2, col2, box3, col3 := template.HTML(""), template.HTML(""), template.HTML(""), template.HTML("")
   113  		if i+1 < len(list) {
   114  			box2 = aBox().
   115  				SetBody(h.pluginStoreBox(GetPluginBoxParamFromPlug(list[i+1]))).
   116  				GetContent()
   117  			col2 = aCol().SetSize(size).SetContent(box2).GetContent()
   118  			if i+2 < len(list) {
   119  				box3 = aBox().
   120  					SetBody(h.pluginStoreBox(GetPluginBoxParamFromPlug(list[i+2]))).
   121  					GetContent()
   122  				col3 = aCol().SetSize(size).SetContent(box3).GetContent()
   123  			}
   124  		}
   125  		rows += aRow().SetContent(col1 + col2 + col3).GetContent()
   126  	}
   127  
   128  	detailPopupModal := template2.Default().Popup().SetID("detail-popup-modal").
   129  		SetTitle(plugWordHTML("plugin detail")).
   130  		SetBody(pluginsPageDetailPopupBody()).
   131  		SetWidth("730px").
   132  		SetHeight("400px").
   133  		SetFooter("1").
   134  		GetContent()
   135  
   136  	buyPopupModal := template2.Default().Popup().SetID("buy-popup-modal").
   137  		SetTitle(plugWordHTML("plugin detail")).
   138  		SetWidth("730px").
   139  		SetHeight("400px").
   140  		SetFooter("1").
   141  		GetContent()
   142  
   143  	loginPopupModal := template2.Default().Popup().SetID("login-popup-modal").
   144  		SetTitle(plugWordHTML("login to goadmin member system")).
   145  		SetBody(aForm().SetContent(types.FormFields{
   146  			{Field: "name", Head: plugWord("account"), FormType: form.Text, Editable: true},
   147  			{Field: "password", Head: plugWord("password"), FormType: form.Password, Editable: true,
   148  				HelpMsg: template.HTML(fmt.Sprintf(plugWord("no account? click %s here %s to register."),
   149  					"<a target='_blank' href='http://www.go-admin.cn/register'>", "</a>"))},
   150  		}).GetContent()).
   151  		SetWidth("540px").
   152  		SetHeight("250px").
   153  		SetFooterHTML(template.HTML(`<button type="button" class="btn btn-primary" onclick="login()">` +
   154  			plugWord("login") + `</button>`)).
   155  		GetContent()
   156  
   157  	h.HTML(ctx, auth.Auth(ctx), types.Panel{
   158  		Content:     rows + detailPopupModal + buyPopupModal + loginPopupModal,
   159  		CSS:         pluginsStorePageCSS + template.CSS(page.CSS),
   160  		JS:          template.JS(page.JS) + GetPluginsPageJS(PluginsPageJSData{Prefix: h.config.Prefix()}),
   161  		Description: language.GetFromHtml("plugin store"),
   162  		Title:       language.GetFromHtml("plugin store"),
   163  	})
   164  }
   165  
   166  func (h *Handler) PluginDetail(ctx *context.Context) {
   167  
   168  	name := ctx.Query("name")
   169  
   170  	plug, exist := plugins.FindByNameAll(name)
   171  	if !exist {
   172  		ctx.JSON(http.StatusOK, gin.H{
   173  			"code": 400,
   174  			"msg":  "bad request",
   175  		})
   176  		return
   177  	}
   178  
   179  	info := plug.GetInfo()
   180  
   181  	if info.MiniCover == "" {
   182  		info.MiniCover = config.Url("/assets/dist/img/plugin_default.png")
   183  	}
   184  
   185  	ctx.JSON(http.StatusOK, gin.H{
   186  		"code": 0,
   187  		"msg":  "ok",
   188  		"data": gin.H{
   189  			"mini_cover":      info.MiniCover,
   190  			"title":           language.GetWithScope(info.Title, name),
   191  			"author":          fmt.Sprintf(plugWord("provided by %s"), language.GetWithScope(info.Author, name)),
   192  			"introduction":    language.GetWithScope(info.Description, name),
   193  			"website":         language.GetWithScope(info.Website, name),
   194  			"version":         language.GetWithScope(info.Version, name),
   195  			"created_at":      language.GetWithScope(info.CreateDate.Format("2006-01-02"), name),
   196  			"updated_at":      language.GetWithScope(info.UpdateDate.Format("2006-01-02"), name),
   197  			"downloaded":      info.Downloaded,
   198  			"download_reboot": plugins.Exist(plug),
   199  			"skip":            info.SkipInstallation,
   200  			"uuid":            info.Uuid,
   201  			"upgrade":         info.CanUpdate,
   202  			"install":         plug.IsInstalled(),
   203  			"free":            info.IsFree(),
   204  		},
   205  	})
   206  }
   207  
   208  type PluginBoxParam struct {
   209  	Info           plugins.Info
   210  	Install        bool
   211  	Upgrade        bool
   212  	Skip           bool
   213  	DownloadReboot bool
   214  	Name           string
   215  	IndexURL       string
   216  }
   217  
   218  func GetPluginBoxParamFromPlug(plug plugins.Plugin) PluginBoxParam {
   219  	return PluginBoxParam{
   220  		Info:           plug.GetInfo(),
   221  		Install:        plug.IsInstalled(),
   222  		Upgrade:        plug.GetInfo().CanUpdate,
   223  		Skip:           plug.GetInfo().SkipInstallation,
   224  		DownloadReboot: plugins.Exist(plug),
   225  		Name:           plug.Name(),
   226  		IndexURL:       plug.GetIndexURL(),
   227  	}
   228  }
   229  
   230  func (h *Handler) pluginStoreBox(param PluginBoxParam) template.HTML {
   231  	cover := template2.HTML(param.Info.MiniCover)
   232  	if cover == template2.HTML("") {
   233  		cover = template2.HTML(config.Url("/assets/dist/img/plugin_default.png"))
   234  	}
   235  	col1 := html.DivEl().SetClass("plugin-store-item-img").
   236  		SetContent(aImage().
   237  			SetSrc(cover).
   238  			SetHeight("110px").
   239  			SetWidth("110px").
   240  			GetContent()).
   241  		Get()
   242  	footer := html.ButtonEl().SetClass(pluginBtnClass("plugin-info")...).
   243  		SetAttr("onclick", `pluginDetail('`+param.Name+`','`+param.Info.Uuid+`')`).
   244  		SetContent(plugWordHTML("info")).
   245  		Get()
   246  	if param.Install {
   247  		if param.Upgrade {
   248  			footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...).
   249  				SetAttr("onclick", `pluginDownload('`+param.Name+`', this)`).
   250  				SetContent(plugWordHTML("upgrade")).
   251  				Get()
   252  		}
   253  	} else {
   254  		if param.Info.Downloaded {
   255  			if param.DownloadReboot {
   256  				if !param.Skip && !param.Install {
   257  					footer += html.AEl().SetAttr("href", h.config.Url(`/info/plugin_`+param.Name+`/new`)).
   258  						SetContent(
   259  							html.ButtonEl().SetClass(pluginBtnClass("installation")...).
   260  								SetAttr("onclick", `pluginInstall('`+param.Name+`')`).
   261  								SetContent(plugWordHTML("install")).
   262  								Get(),
   263  						).Get()
   264  				}
   265  			} else {
   266  				footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...).
   267  					SetAttr("onclick", `pluginRebootInstall()`).
   268  					SetContent(plugWordHTML("install")).
   269  					Get()
   270  			}
   271  		} else {
   272  			if param.Info.IsFree() || param.Info.HasBought {
   273  				footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...).
   274  					SetAttr("onclick", `pluginDownload('`+param.Name+`', this)`).
   275  					SetContent(plugWordHTML("download")).
   276  					Get()
   277  			} else {
   278  				footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...).
   279  					SetAttr("onclick", `pluginBuy('`+param.Name+`', '`+param.Info.Uuid+`')`).
   280  					SetContent(plugWordHTML("buy")).
   281  					Get()
   282  			}
   283  		}
   284  	}
   285  
   286  	col2 := html.DivEl().SetClass("plugin-item-content").SetContent(
   287  		html.DivEl().SetClass("plugin-item-content-title").
   288  			SetContent(language.GetFromHtml(template.HTML(param.Info.Title), param.Name)).
   289  			Get() +
   290  			html.DivEl().SetClass("plugin-item-content-description").
   291  				SetContent(language.GetFromHtml(template.HTML(param.Info.Description), param.Name)).
   292  				Get() +
   293  			footer,
   294  	).Get()
   295  
   296  	return html.Div(col1+col2, html.M{"clear": "both"})
   297  }
   298  
   299  func (h *Handler) pluginBox(param PluginBoxParam) template.HTML {
   300  	cover := template2.HTML(param.Info.MiniCover)
   301  	if cover == template2.HTML("") {
   302  		cover = "/admin/assets/dist/img/plugin_default.png"
   303  	}
   304  
   305  	jump := param.IndexURL
   306  	label := template.HTML("")
   307  	if !param.Install {
   308  		jump = h.config.Url("/info/plugin_" + param.Name + "/new")
   309  		label = html.SpanEl().SetClass("plugin-item-label").SetContent(language.GetFromHtml("uninstalled")).Get()
   310  	}
   311  	col1 := html.AEl().SetContent(html.DivEl().SetClass("plugin-item-img").
   312  		SetContent(aImage().
   313  			SetSrc(cover).
   314  			GetContent()+
   315  			html.PEl().SetContent(language.GetFromHtml(template.HTML(param.Info.Title), param.Name)).
   316  				SetClass("plugin-item-title").Get()).
   317  		Get()+label).SetAttr("href", jump).Get()
   318  	return col1
   319  }
   320  
   321  func (h *Handler) PluginDownload(ctx *context.Context) {
   322  
   323  	if !h.config.Debug {
   324  		ctx.JSON(http.StatusOK, map[string]interface{}{
   325  			"code": 400,
   326  			"msg":  plugWord("change to debug mode first"),
   327  		})
   328  		return
   329  	}
   330  
   331  	name := ctx.FormValue("name")
   332  
   333  	if name == "" {
   334  		ctx.JSON(http.StatusOK, map[string]interface{}{
   335  			"code": 400,
   336  			"msg":  plugWord("download fail, wrong name"),
   337  		})
   338  		return
   339  	}
   340  
   341  	plug, exist := plugins.FindByNameAll(name)
   342  
   343  	if !exist {
   344  		ctx.JSON(http.StatusOK, map[string]interface{}{
   345  			"code": 400,
   346  			"msg":  plugWord("download fail, plugin not exist"),
   347  		})
   348  		return
   349  	}
   350  
   351  	if !plug.GetInfo().IsFree() && !plug.GetInfo().HasBought {
   352  		ctx.JSON(http.StatusOK, map[string]interface{}{
   353  			"code": 400,
   354  			"msg":  plugWord("download fail, plugin has not been bought"),
   355  		})
   356  		return
   357  	}
   358  
   359  	downloadURL := plug.GetInfo().Url
   360  	extraDownloadURL := plug.GetInfo().ExtraDownloadUrl
   361  
   362  	if !plug.GetInfo().IsFree() {
   363  		var err error
   364  		downloadURL, extraDownloadURL, err = remote_server.GetDownloadURL(plug.GetInfo().Uuid, ctx.Cookie(remote_server.TokenKey))
   365  		if err != nil {
   366  			logger.Error("download plugins error", err)
   367  			ctx.JSON(http.StatusOK, map[string]interface{}{
   368  				"code": 500,
   369  				"msg":  plugWord("download fail"),
   370  			})
   371  			return
   372  		}
   373  	}
   374  
   375  	tempFile := "./temp-" + utils.Uuid(10) + ".zip"
   376  
   377  	err := utils.DownloadTo(downloadURL, tempFile)
   378  
   379  	if err != nil {
   380  		logger.Error("download plugins error", map[string]interface{}{
   381  			"error":       err,
   382  			"downloadURL": downloadURL,
   383  		})
   384  		ctx.JSON(http.StatusOK, map[string]interface{}{
   385  			"code": 500,
   386  			"msg":  plugWord("download fail"),
   387  		})
   388  		return
   389  	}
   390  
   391  	gopath := os.Getenv("GOPATH")
   392  
   393  	if gopath == "" {
   394  		ctx.JSON(http.StatusOK, map[string]interface{}{
   395  			"code": 500,
   396  			"msg":  plugWord("golang develop environment does not exist"),
   397  		})
   398  		return
   399  	}
   400  
   401  	gomodule := os.Getenv("GO111MODULE")
   402  	base := filepath.Dir(plug.GetInfo().ModulePath)
   403  	installPath := ""
   404  
   405  	if gomodule == "off" {
   406  		installPath = filepath.ToSlash(gopath + "/src/" + base)
   407  	} else {
   408  		installPath = filepath.ToSlash(gopath + "/pkg/mod/" + base)
   409  	}
   410  
   411  	err = utils.UnzipDir(tempFile, installPath)
   412  
   413  	if err != nil {
   414  		logger.Error("download plugins, unzip error", map[string]interface{}{
   415  			"error":       err,
   416  			"installPath": installPath,
   417  		})
   418  		ctx.JSON(http.StatusOK, map[string]interface{}{
   419  			"code": 500,
   420  			"msg":  plugWord("download fail"),
   421  		})
   422  		return
   423  	}
   424  
   425  	_ = os.Remove(tempFile)
   426  
   427  	if len(downloadURL) > 18 && downloadURL[:18] == "https://github.com" {
   428  		name := filepath.Base(plug.GetInfo().ModulePath)
   429  		version := strings.ReplaceAll(plug.GetInfo().Version, "v", "")
   430  		rawPath := installPath + "/" + name
   431  		nowPath := rawPath + "-" + version
   432  		if gomodule == "off" {
   433  			err = os.Rename(nowPath, rawPath)
   434  		} else {
   435  			err = os.Rename(nowPath, rawPath+"@"+plug.GetInfo().Version)
   436  		}
   437  		if err != nil {
   438  			logger.Error("download plugins, rename error", map[string]interface{}{
   439  				"error":   err,
   440  				"nowPath": nowPath,
   441  				"rawPath": rawPath,
   442  			})
   443  			ctx.JSON(http.StatusOK, map[string]interface{}{
   444  				"code": 500,
   445  				"msg":  plugWord("download fail"),
   446  			})
   447  			return
   448  		}
   449  	} else if gomodule != "off" {
   450  		rawPath := installPath + "/" + name
   451  		err = os.Rename(rawPath, rawPath+"@"+plug.GetInfo().Version)
   452  		if err != nil {
   453  			logger.Error("download plugins, rename error", map[string]interface{}{
   454  				"error":   err,
   455  				"rawPath": rawPath,
   456  			})
   457  			ctx.JSON(http.StatusOK, map[string]interface{}{
   458  				"code": 500,
   459  				"msg":  plugWord("download fail"),
   460  			})
   461  			return
   462  		}
   463  	}
   464  
   465  	if h.config.BootstrapFilePath != "" && utils.FileExist(h.config.BootstrapFilePath) {
   466  		content, err := ioutil.ReadFile(h.config.BootstrapFilePath)
   467  		if err != nil {
   468  			logger.Error("read bootstrap file error: ", err)
   469  		} else {
   470  			err = ioutil.WriteFile(h.config.BootstrapFilePath, []byte(string(content)+`
   471  import _ "`+plug.GetInfo().ModulePath+`"`), 0644)
   472  			if err != nil {
   473  				logger.Error("write bootstrap file error: ", err)
   474  			}
   475  		}
   476  	}
   477  
   478  	if h.config.GoModFilePath != "" && utils.FileExist(h.config.GoModFilePath) &&
   479  		plug.GetInfo().CanUpdate && plug.GetInfo().OldVersion != "" {
   480  		content, _ := ioutil.ReadFile(h.config.BootstrapFilePath)
   481  		src := plug.GetInfo().ModulePath + " " + plug.GetInfo().OldVersion
   482  		dist := plug.GetInfo().ModulePath + " " + plug.GetInfo().Version
   483  		content = bytes.ReplaceAll(content, []byte(src), []byte(dist))
   484  		_ = ioutil.WriteFile(h.config.BootstrapFilePath, content, 0644)
   485  	}
   486  
   487  	// TODO: 实现运行环境与编译环境隔离
   488  
   489  	if plug.GetInfo().ExtraDownloadUrl != "" {
   490  		err = utils.DownloadTo(extraDownloadURL, "./"+plug.Name()+"_extra_"+
   491  			fmt.Sprintf("%d", time.Now().Unix())+".zip")
   492  		if err != nil {
   493  			logger.Error("failed to download "+plug.Name()+" extra data: ", err)
   494  		}
   495  	}
   496  
   497  	plug.(*plugins.BasePlugin).Info.Downloaded = true
   498  	plug.(*plugins.BasePlugin).Info.CanUpdate = false
   499  
   500  	ctx.JSON(http.StatusOK, map[string]interface{}{
   501  		"code": 0,
   502  		"msg":  plugWord("download success, restart to install"),
   503  	})
   504  }
   505  
   506  func (h *Handler) ServerLogin(ctx *context.Context) {
   507  	param := guard.GetServerLoginParam(ctx)
   508  	res := remote_server.Login(param.Account, param.Password)
   509  	if res.Code == 0 && res.Data.Token != "" {
   510  		ctx.SetCookie(&http.Cookie{
   511  			Name:     remote_server.TokenKey,
   512  			Value:    res.Data.Token,
   513  			Expires:  time.Now().Add(time.Second * time.Duration(res.Data.Expire/1000)),
   514  			HttpOnly: true,
   515  			Path:     "/",
   516  		})
   517  	}
   518  	ctx.JSON(http.StatusOK, gin.H{
   519  		"code": res.Code,
   520  		"data": res.Data,
   521  		"msg":  res.Msg,
   522  	})
   523  }
   524  
   525  func pluginBtnClass(class ...string) []string {
   526  	return append([]string{"btn", "btn-primary"}, class...)
   527  }
   528  
   529  func plugWord(word string) string {
   530  	return language.GetWithScope(word, "plugin")
   531  }
   532  
   533  func plugWordHTML(word template.HTML) template.HTML {
   534  	return language.GetFromHtml(word, "plugin")
   535  }