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 }