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

     1  // Copyright 2019 GoAdmin Core Team. All rights reserved.
     2  // Use of this source code is governed by a Apache-2.0 style
     3  // license that can be found in the LICENSE file.
     4  
     5  package engine
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	errors2 "errors"
    11  	"fmt"
    12  	template2 "html/template"
    13  	"net/http"
    14  	"runtime/debug"
    15  	"strings"
    16  	"sync"
    17  
    18  	"github.com/kotovmak/go-admin/modules/language"
    19  	"github.com/kotovmak/go-admin/template/icon"
    20  	"github.com/kotovmak/go-admin/template/types/action"
    21  
    22  	"github.com/kotovmak/go-admin/adapter"
    23  	"github.com/kotovmak/go-admin/context"
    24  	"github.com/kotovmak/go-admin/modules/auth"
    25  	"github.com/kotovmak/go-admin/modules/config"
    26  	"github.com/kotovmak/go-admin/modules/db"
    27  	"github.com/kotovmak/go-admin/modules/errors"
    28  	"github.com/kotovmak/go-admin/modules/logger"
    29  	"github.com/kotovmak/go-admin/modules/menu"
    30  	"github.com/kotovmak/go-admin/modules/service"
    31  	"github.com/kotovmak/go-admin/modules/system"
    32  	"github.com/kotovmak/go-admin/modules/ui"
    33  	"github.com/kotovmak/go-admin/plugins"
    34  	"github.com/kotovmak/go-admin/plugins/admin"
    35  	"github.com/kotovmak/go-admin/plugins/admin/models"
    36  	"github.com/kotovmak/go-admin/plugins/admin/modules/response"
    37  	"github.com/kotovmak/go-admin/plugins/admin/modules/table"
    38  	"github.com/kotovmak/go-admin/template"
    39  	"github.com/kotovmak/go-admin/template/types"
    40  )
    41  
    42  // Engine is the core component of goAdmin. It has two attributes.
    43  // PluginList is an array of plugin. Adapter is the adapter of
    44  // web framework context and goAdmin context. The relationship of adapter and
    45  // plugin is that the adapter use the plugin which contains routers and
    46  // controller methods to inject into the framework entity and make it work.
    47  type Engine struct {
    48  	PluginList   plugins.Plugins
    49  	Adapter      adapter.WebFrameWork
    50  	Services     service.List
    51  	NavButtons   *types.Buttons
    52  	config       *config.Config
    53  	announceLock sync.Once
    54  }
    55  
    56  // Default return the default engine instance.
    57  func Default() *Engine {
    58  	engine = &Engine{
    59  		Adapter:    defaultAdapter,
    60  		Services:   service.GetServices(),
    61  		NavButtons: new(types.Buttons),
    62  	}
    63  	return engine
    64  }
    65  
    66  // Use enable the adapter.
    67  func (eng *Engine) Use(router interface{}) error {
    68  	if eng.Adapter == nil {
    69  		emptyAdapterPanic()
    70  	}
    71  
    72  	eng.Services.Add(auth.InitCSRFTokenSrv(eng.DefaultConnection()))
    73  	eng.initSiteSetting()
    74  	eng.initJumpNavButtons()
    75  	eng.initPlugins()
    76  
    77  	printInitMsg(language.Get("initialize success"))
    78  
    79  	return eng.Adapter.Use(router, eng.PluginList)
    80  }
    81  
    82  // AddPlugins add the plugins
    83  func (eng *Engine) AddPlugins(plugs ...plugins.Plugin) *Engine {
    84  
    85  	if len(plugs) == 0 {
    86  		return eng
    87  	}
    88  
    89  	for _, plug := range plugs {
    90  		eng.PluginList = eng.PluginList.Add(plug)
    91  	}
    92  
    93  	return eng
    94  }
    95  
    96  // AddPluginList add the plugins
    97  func (eng *Engine) AddPluginList(plugs plugins.Plugins) *Engine {
    98  
    99  	if len(plugs) == 0 {
   100  		return eng
   101  	}
   102  
   103  	for _, plug := range plugs {
   104  		eng.PluginList = eng.PluginList.Add(plug)
   105  	}
   106  
   107  	return eng
   108  }
   109  
   110  // FindPluginByName find the register plugin by given name.
   111  func (eng *Engine) FindPluginByName(name string) (plugins.Plugin, bool) {
   112  	for _, plug := range eng.PluginList {
   113  		if plug.Name() == name {
   114  			return plug, true
   115  		}
   116  	}
   117  	return nil, false
   118  }
   119  
   120  // AddAuthService customize the auth logic with given callback function.
   121  func (eng *Engine) AddAuthService(processor auth.Processor) *Engine {
   122  	eng.Services.Add("auth", auth.NewService(processor))
   123  	return eng
   124  }
   125  
   126  // ============================
   127  // Config APIs
   128  // ============================
   129  
   130  func (eng *Engine) announce() *Engine {
   131  	if eng.config.Debug {
   132  		eng.announceLock.Do(func() {
   133  			fmt.Printf(language.Get("goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n"))
   134  		})
   135  	}
   136  	return eng
   137  }
   138  
   139  // AddConfig set the global config.
   140  func (eng *Engine) AddConfig(cfg *config.Config) *Engine {
   141  	return eng.setConfig(cfg).announce().initDatabase()
   142  }
   143  
   144  // setConfig set the config of engine.
   145  func (eng *Engine) setConfig(cfg *config.Config) *Engine {
   146  	eng.config = config.Initialize(cfg)
   147  	sysCheck, themeCheck := template.CheckRequirements()
   148  	if !sysCheck {
   149  		logger.Panicf(language.Get("wrong goadmin version, theme %s required goadmin version are %s"),
   150  			eng.config.Theme, strings.Join(template.Default().GetRequirements(), ","))
   151  	}
   152  	if !themeCheck {
   153  		logger.Panicf(language.Get("wrong theme version, goadmin %s required version of theme %s is %s"),
   154  			system.Version(), eng.config.Theme, strings.Join(system.RequireThemeVersion()[eng.config.Theme], ","))
   155  	}
   156  	return eng
   157  }
   158  
   159  // AddConfigFromJSON set the global config from json file.
   160  func (eng *Engine) AddConfigFromJSON(path string) *Engine {
   161  	cfg := config.ReadFromJson(path)
   162  	return eng.setConfig(&cfg).announce().initDatabase()
   163  }
   164  
   165  // AddConfigFromYAML set the global config from yaml file.
   166  func (eng *Engine) AddConfigFromYAML(path string) *Engine {
   167  	cfg := config.ReadFromYaml(path)
   168  	return eng.setConfig(&cfg).announce().initDatabase()
   169  }
   170  
   171  // AddConfigFromINI set the global config from ini file.
   172  func (eng *Engine) AddConfigFromINI(path string) *Engine {
   173  	cfg := config.ReadFromINI(path)
   174  	return eng.setConfig(&cfg).announce().initDatabase()
   175  }
   176  
   177  // InitDatabase initialize all database connection.
   178  func (eng *Engine) initDatabase() *Engine {
   179  	printInitMsg(language.Get("initialize database connections"))
   180  	for driver, databaseCfg := range eng.config.Databases.GroupByDriver() {
   181  		eng.Services.Add(driver, db.GetConnectionByDriver(driver).InitDB(databaseCfg))
   182  	}
   183  	if defaultAdapter == nil {
   184  		emptyAdapterPanic()
   185  	}
   186  	defaultConnection := db.GetConnection(eng.Services)
   187  	defaultAdapter.SetConnection(defaultConnection)
   188  	eng.Adapter.SetConnection(defaultConnection)
   189  	return eng
   190  }
   191  
   192  // AddAdapter add the adapter of engine.
   193  func (eng *Engine) AddAdapter(ada adapter.WebFrameWork) *Engine {
   194  	eng.Adapter = ada
   195  	defaultAdapter = ada
   196  	return eng
   197  }
   198  
   199  // defaultAdapter is the default adapter of engine.
   200  var defaultAdapter adapter.WebFrameWork
   201  
   202  var engine *Engine
   203  
   204  // navButtons is the default buttons in the navigation bar.
   205  var navButtons = new(types.Buttons)
   206  
   207  func emptyAdapterPanic() {
   208  	logger.Panic(language.Get("adapter is nil, import the default adapter or use addadapter method add the adapter"))
   209  }
   210  
   211  // Register set default adapter of engine.
   212  func Register(ada adapter.WebFrameWork) {
   213  	if ada == nil {
   214  		emptyAdapterPanic()
   215  	}
   216  	defaultAdapter = ada
   217  }
   218  
   219  // User call the User method of defaultAdapter.
   220  func User(ctx interface{}) (models.UserModel, bool) {
   221  	return defaultAdapter.User(ctx)
   222  }
   223  
   224  // User call the User method of engine adapter.
   225  func (eng *Engine) User(ctx interface{}) (models.UserModel, bool) {
   226  	return eng.Adapter.User(ctx)
   227  }
   228  
   229  // ============================
   230  // DB Connection APIs
   231  // ============================
   232  
   233  // DB return the db connection of given driver.
   234  func (eng *Engine) DB(driver string) db.Connection {
   235  	return db.GetConnectionFromService(eng.Services.Get(driver))
   236  }
   237  
   238  // DefaultConnection return the default db connection.
   239  func (eng *Engine) DefaultConnection() db.Connection {
   240  	return eng.DB(eng.config.Databases.GetDefault().Driver)
   241  }
   242  
   243  // MysqlConnection return the mysql db connection of given driver.
   244  func (eng *Engine) MysqlConnection() db.Connection {
   245  	return db.GetConnectionFromService(eng.Services.Get(db.DriverMysql))
   246  }
   247  
   248  // MssqlConnection return the mssql db connection of given driver.
   249  func (eng *Engine) MssqlConnection() db.Connection {
   250  	return db.GetConnectionFromService(eng.Services.Get(db.DriverMssql))
   251  }
   252  
   253  // PostgresqlConnection return the postgresql db connection of given driver.
   254  func (eng *Engine) PostgresqlConnection() db.Connection {
   255  	return db.GetConnectionFromService(eng.Services.Get(db.DriverPostgresql))
   256  }
   257  
   258  // SqliteConnection return the sqlite db connection of given driver.
   259  func (eng *Engine) SqliteConnection() db.Connection {
   260  	return db.GetConnectionFromService(eng.Services.Get(db.DriverSqlite))
   261  }
   262  
   263  // OceanBaseConnection return the OceanBase db connection of given driver.
   264  func (eng *Engine) OceanBaseConnection() db.Connection {
   265  	return db.GetConnectionFromService(eng.Services.Get(db.DriverOceanBase))
   266  }
   267  
   268  type ConnectionSetter func(db.Connection)
   269  
   270  // ResolveConnection resolve the specified driver connection.
   271  func (eng *Engine) ResolveConnection(setter ConnectionSetter, driver string) *Engine {
   272  	setter(eng.DB(driver))
   273  	return eng
   274  }
   275  
   276  // ResolveMysqlConnection resolve the mysql connection.
   277  func (eng *Engine) ResolveMysqlConnection(setter ConnectionSetter) *Engine {
   278  	eng.ResolveConnection(setter, db.DriverMysql)
   279  	return eng
   280  }
   281  
   282  // ResolveMssqlConnection resolve the mssql connection.
   283  func (eng *Engine) ResolveMssqlConnection(setter ConnectionSetter) *Engine {
   284  	eng.ResolveConnection(setter, db.DriverMssql)
   285  	return eng
   286  }
   287  
   288  // ResolveSqliteConnection resolve the sqlite connection.
   289  func (eng *Engine) ResolveSqliteConnection(setter ConnectionSetter) *Engine {
   290  	eng.ResolveConnection(setter, db.DriverSqlite)
   291  	return eng
   292  }
   293  
   294  // ResolvePostgresqlConnection resolve the postgres connection.
   295  func (eng *Engine) ResolvePostgresqlConnection(setter ConnectionSetter) *Engine {
   296  	eng.ResolveConnection(setter, db.DriverPostgresql)
   297  	return eng
   298  }
   299  
   300  type Setter func(*Engine)
   301  
   302  // Clone copy a new Engine.
   303  func (eng *Engine) Clone(e *Engine) *Engine {
   304  	e = eng
   305  	return eng
   306  }
   307  
   308  // ClonedBySetter copy a new Engine by a setter callback function.
   309  func (eng *Engine) ClonedBySetter(setter Setter) *Engine {
   310  	setter(eng)
   311  	return eng
   312  }
   313  
   314  func (eng *Engine) deferHandler(conn db.Connection) context.Handler {
   315  	return func(ctx *context.Context) {
   316  		defer func(ctx *context.Context) {
   317  			if user, ok := ctx.UserValue["user"].(models.UserModel); ok {
   318  				var input []byte
   319  				form := ctx.Request.MultipartForm
   320  				if form != nil {
   321  					input, _ = json.Marshal((*form).Value)
   322  				}
   323  
   324  				models.OperationLog().SetConn(conn).New(user.Id, ctx.Path(), ctx.Method(), ctx.LocalIP(), string(input))
   325  			}
   326  
   327  			if err := recover(); err != nil {
   328  				logger.Error(err)
   329  				logger.Error(string(debug.Stack()))
   330  
   331  				var (
   332  					errMsg string
   333  					ok     bool
   334  					e      error
   335  				)
   336  
   337  				if errMsg, ok = err.(string); !ok {
   338  					if e, ok = err.(error); ok {
   339  						errMsg = e.Error()
   340  					}
   341  				}
   342  
   343  				if errMsg == "" {
   344  					errMsg = "system error"
   345  				}
   346  
   347  				if ctx.WantJSON() {
   348  					response.Error(ctx, errMsg)
   349  					return
   350  				}
   351  
   352  				eng.errorPanelHTML(ctx, new(bytes.Buffer), errors2.New(errMsg))
   353  			}
   354  		}(ctx)
   355  		ctx.Next()
   356  	}
   357  }
   358  
   359  // wrapWithAuthMiddleware wrap a auth middleware to the given handler.
   360  func (eng *Engine) wrapWithAuthMiddleware(handler context.Handler) context.Handlers {
   361  	conn := db.GetConnection(eng.Services)
   362  	return []context.Handler{eng.deferHandler(conn), response.OffLineHandler, auth.Middleware(conn), handler}
   363  }
   364  
   365  // wrapWithAuthMiddleware wrap a auth middleware to the given handler.
   366  func (eng *Engine) wrap(handler context.Handler) context.Handlers {
   367  	conn := db.GetConnection(eng.Services)
   368  	return []context.Handler{eng.deferHandler(conn), response.OffLineHandler, handler}
   369  }
   370  
   371  // ============================
   372  // Initialize methods
   373  // ============================
   374  
   375  // AddNavButtons add the nav buttons.
   376  func (eng *Engine) AddNavButtons(title template2.HTML, icon string, action types.Action) *Engine {
   377  	btn := types.GetNavButton(title, icon, action)
   378  	*eng.NavButtons = append(*eng.NavButtons, btn)
   379  	return eng
   380  }
   381  
   382  // AddNavButtonsRaw add the nav buttons.
   383  func (eng *Engine) AddNavButtonsRaw(btns ...types.Button) *Engine {
   384  	*eng.NavButtons = append(*eng.NavButtons, btns...)
   385  	return eng
   386  }
   387  
   388  type navJumpButtonParam struct {
   389  	Exist      bool
   390  	Icon       string
   391  	BtnName    string
   392  	URL        string
   393  	Title      string
   394  	TitleScore string
   395  }
   396  
   397  func (eng *Engine) addJumpNavButton(param navJumpButtonParam) *Engine {
   398  	if param.Exist {
   399  		*eng.NavButtons = (*eng.NavButtons).AddNavButton(param.Icon, param.BtnName,
   400  			action.JumpInNewTab(config.Url(param.URL),
   401  				language.GetWithScope(param.Title, param.TitleScore)))
   402  	}
   403  	return eng
   404  }
   405  
   406  func printInitMsg(msg string) {
   407  	logger.Info("=====> " + msg)
   408  }
   409  
   410  func (eng *Engine) initJumpNavButtons() {
   411  	printInitMsg(language.Get("initialize navigation buttons"))
   412  	for _, param := range eng.initNavJumpButtonParams() {
   413  		eng.addJumpNavButton(param)
   414  	}
   415  	navButtons = eng.NavButtons
   416  	eng.Services.Add(ui.ServiceKey, ui.NewService(eng.NavButtons))
   417  }
   418  
   419  func (eng *Engine) initPlugins() {
   420  
   421  	printInitMsg(language.Get("initialize plugins"))
   422  
   423  	eng.AddPlugins(admin.NewAdmin()).AddPluginList(plugins.Get())
   424  
   425  	var plugGenerators = make(table.GeneratorList)
   426  
   427  	for i := range eng.PluginList {
   428  		if eng.PluginList[i].Name() != "admin" {
   429  			printInitMsg("--> " + eng.PluginList[i].Name())
   430  			eng.PluginList[i].InitPlugin(eng.Services)
   431  			if !eng.PluginList[i].GetInfo().SkipInstallation {
   432  				eng.AddGenerator("plugin_"+eng.PluginList[i].Name(), eng.PluginList[i].GetSettingPage())
   433  			}
   434  			plugGenerators = plugGenerators.Combine(eng.PluginList[i].GetGenerators())
   435  		}
   436  	}
   437  	adm := eng.AdminPlugin().AddGenerators(plugGenerators)
   438  	adm.InitPlugin(eng.Services)
   439  	plugins.Add(adm)
   440  }
   441  
   442  func (eng *Engine) initNavJumpButtonParams() []navJumpButtonParam {
   443  	return []navJumpButtonParam{
   444  		{
   445  			Exist:      !eng.config.HideConfigCenterEntrance,
   446  			Icon:       icon.Gear,
   447  			BtnName:    types.NavBtnSiteName,
   448  			URL:        "/info/site/edit",
   449  			Title:      "site setting",
   450  			TitleScore: "config",
   451  		}, {
   452  			Exist:      !eng.config.HideToolEntrance && eng.config.IsNotProductionEnvironment(),
   453  			Icon:       icon.Wrench,
   454  			BtnName:    types.NavBtnToolName,
   455  			URL:        "/info/generate/new",
   456  			Title:      "tool",
   457  			TitleScore: "tool",
   458  		}, {
   459  			Exist:      !eng.config.HideAppInfoEntrance,
   460  			Icon:       icon.Info,
   461  			BtnName:    types.NavBtnInfoName,
   462  			URL:        "/application/info",
   463  			Title:      "system info",
   464  			TitleScore: "system",
   465  		}, {
   466  			Exist:      !eng.config.HidePluginEntrance,
   467  			Icon:       icon.Th,
   468  			BtnName:    types.NavBtnPlugName,
   469  			URL:        "/plugins",
   470  			Title:      "plugin",
   471  			TitleScore: "plugin",
   472  		},
   473  	}
   474  }
   475  
   476  func (eng *Engine) initSiteSetting() {
   477  
   478  	printInitMsg(language.Get("initialize configuration"))
   479  
   480  	err := eng.config.Update(models.Site().
   481  		SetConn(eng.DefaultConnection()).
   482  		Init(eng.config.ToMap()).
   483  		AllToMap())
   484  	if err != nil {
   485  		logger.Panic(err)
   486  	}
   487  	eng.Services.Add("config", config.SrvWithConfig(eng.config))
   488  
   489  	errors.Init()
   490  }
   491  
   492  // ============================
   493  // HTML Content Render APIs
   494  // ============================
   495  
   496  // Content call the Content method of engine adapter.
   497  // If adapter is nil, it will panic.
   498  func (eng *Engine) Content(ctx interface{}, panel types.GetPanelFn) {
   499  	if eng.Adapter == nil {
   500  		emptyAdapterPanic()
   501  	}
   502  	eng.Adapter.Content(ctx, panel, eng.AdminPlugin().GetAddOperationFn(), *eng.NavButtons...)
   503  }
   504  
   505  // Content call the Content method of defaultAdapter.
   506  // If defaultAdapter is nil, it will panic.
   507  func Content(ctx interface{}, panel types.GetPanelFn) {
   508  	if defaultAdapter == nil {
   509  		emptyAdapterPanic()
   510  	}
   511  	defaultAdapter.Content(ctx, panel, engine.AdminPlugin().GetAddOperationFn(), *navButtons...)
   512  }
   513  
   514  // Data inject the route and corresponding handler to the web framework.
   515  func (eng *Engine) Data(method, url string, handler context.Handler, noAuth ...bool) {
   516  	if len(noAuth) > 0 && noAuth[0] {
   517  		eng.Adapter.AddHandler(method, url, eng.wrap(handler))
   518  	} else {
   519  		eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(handler))
   520  	}
   521  }
   522  
   523  // HTML inject the route and corresponding handler wrapped by the given function to the web framework.
   524  func (eng *Engine) HTML(method, url string, fn types.GetPanelInfoFn, noAuth ...bool) {
   525  
   526  	var handler = func(ctx *context.Context) {
   527  		panel, err := fn(ctx)
   528  		if err != nil {
   529  			panel = template.WarningPanel(err.Error())
   530  		}
   531  
   532  		eng.AdminPlugin().GetAddOperationFn()(panel.Callbacks...)
   533  
   534  		var (
   535  			tmpl, tmplName = template.Default().GetTemplate(ctx.IsPjax())
   536  
   537  			user = auth.Auth(ctx)
   538  			buf  = new(bytes.Buffer)
   539  		)
   540  
   541  		hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(&types.NewPageParam{
   542  			User:         user,
   543  			Menu:         menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())),
   544  			Panel:        panel.GetContent(eng.config.IsProductionEnvironment()),
   545  			Assets:       template.GetComponentAssetImportHTML(),
   546  			Buttons:      eng.NavButtons.CheckPermission(user),
   547  			TmplHeadHTML: template.Default().GetHeadHTML(),
   548  			TmplFootJS:   template.Default().GetFootJS(),
   549  			Iframe:       ctx.IsIframe(),
   550  		}))
   551  
   552  		if hasError != nil {
   553  			logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError)
   554  		}
   555  
   556  		ctx.HTMLByte(http.StatusOK, buf.Bytes())
   557  	}
   558  
   559  	if len(noAuth) > 0 && noAuth[0] {
   560  		eng.Adapter.AddHandler(method, url, eng.wrap(handler))
   561  	} else {
   562  		eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(handler))
   563  	}
   564  }
   565  
   566  // HTMLFile inject the route and corresponding handler which returns the panel content of given html file path
   567  // to the web framework.
   568  func (eng *Engine) HTMLFile(method, url, path string, data map[string]interface{}, noAuth ...bool) {
   569  
   570  	var handler = func(ctx *context.Context) {
   571  
   572  		cbuf := new(bytes.Buffer)
   573  
   574  		t, err := template2.ParseFiles(path)
   575  		if err != nil {
   576  			eng.errorPanelHTML(ctx, cbuf, err)
   577  			return
   578  		} else if err := t.Execute(cbuf, data); err != nil {
   579  			eng.errorPanelHTML(ctx, cbuf, err)
   580  			return
   581  		}
   582  
   583  		var (
   584  			tmpl, tmplName = template.Default().GetTemplate(ctx.IsPjax())
   585  
   586  			user = auth.Auth(ctx)
   587  			buf  = new(bytes.Buffer)
   588  		)
   589  
   590  		hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(&types.NewPageParam{
   591  			User: user,
   592  			Menu: menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(eng.config.URLRemovePrefix(ctx.Path())),
   593  			Panel: types.Panel{
   594  				Content: template.HTML(cbuf.String()),
   595  			},
   596  			Assets:       template.GetComponentAssetImportHTML(),
   597  			Buttons:      eng.NavButtons.CheckPermission(user),
   598  			TmplHeadHTML: template.Default().GetHeadHTML(),
   599  			TmplFootJS:   template.Default().GetFootJS(),
   600  			Iframe:       ctx.IsIframe(),
   601  		}))
   602  
   603  		if hasError != nil {
   604  			logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError)
   605  		}
   606  
   607  		ctx.HTMLByte(http.StatusOK, buf.Bytes())
   608  	}
   609  
   610  	if len(noAuth) > 0 && noAuth[0] {
   611  		eng.Adapter.AddHandler(method, url, eng.wrap(handler))
   612  	} else {
   613  		eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(handler))
   614  	}
   615  }
   616  
   617  // HTMLFiles inject the route and corresponding handler which returns the panel content of given html files path
   618  // to the web framework.
   619  func (eng *Engine) HTMLFiles(method, url string, data map[string]interface{}, files ...string) {
   620  	eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(eng.htmlFilesHandler(data, files...)))
   621  }
   622  
   623  // HTMLFilesNoAuth inject the route and corresponding handler which returns the panel content of given html files path
   624  // to the web framework without auth check.
   625  func (eng *Engine) HTMLFilesNoAuth(method, url string, data map[string]interface{}, files ...string) {
   626  	eng.Adapter.AddHandler(method, url, eng.wrap(eng.htmlFilesHandler(data, files...)))
   627  }
   628  
   629  // HTMLFiles inject the route and corresponding handler which returns the panel content of given html files path
   630  // to the web framework.
   631  func (eng *Engine) htmlFilesHandler(data map[string]interface{}, files ...string) context.Handler {
   632  	return func(ctx *context.Context) {
   633  
   634  		cbuf := new(bytes.Buffer)
   635  
   636  		t, err := template2.ParseFiles(files...)
   637  		if err != nil {
   638  			eng.errorPanelHTML(ctx, cbuf, err)
   639  			return
   640  		} else if err := t.Execute(cbuf, data); err != nil {
   641  			eng.errorPanelHTML(ctx, cbuf, err)
   642  			return
   643  		}
   644  
   645  		var (
   646  			tmpl, tmplName = template.Default().GetTemplate(ctx.IsPjax())
   647  
   648  			user = auth.Auth(ctx)
   649  			buf  = new(bytes.Buffer)
   650  		)
   651  
   652  		hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(&types.NewPageParam{
   653  			User: user,
   654  			Menu: menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(eng.config.URLRemovePrefix(ctx.Path())),
   655  			Panel: types.Panel{
   656  				Content: template.HTML(cbuf.String()),
   657  			},
   658  			Assets:       template.GetComponentAssetImportHTML(),
   659  			Buttons:      eng.NavButtons.CheckPermission(user),
   660  			TmplHeadHTML: template.Default().GetHeadHTML(),
   661  			TmplFootJS:   template.Default().GetFootJS(),
   662  			Iframe:       ctx.IsIframe(),
   663  		}))
   664  
   665  		if hasError != nil {
   666  			logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError)
   667  		}
   668  
   669  		ctx.HTMLByte(http.StatusOK, buf.Bytes())
   670  	}
   671  }
   672  
   673  // errorPanelHTML add an error panel html to context response.
   674  func (eng *Engine) errorPanelHTML(ctx *context.Context, buf *bytes.Buffer, err error) {
   675  
   676  	user := auth.Auth(ctx)
   677  	tmpl, tmplName := template.Default().GetTemplate(ctx.IsPjax())
   678  
   679  	hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(&types.NewPageParam{
   680  		User:         user,
   681  		Menu:         menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(eng.config.URLRemovePrefix(ctx.Path())),
   682  		Panel:        template.WarningPanel(err.Error()).GetContent(eng.config.IsProductionEnvironment()),
   683  		Assets:       template.GetComponentAssetImportHTML(),
   684  		Buttons:      (*eng.NavButtons).CheckPermission(user),
   685  		TmplHeadHTML: template.Default().GetHeadHTML(),
   686  		TmplFootJS:   template.Default().GetFootJS(),
   687  		Iframe:       ctx.IsIframe(),
   688  	}))
   689  
   690  	if hasError != nil {
   691  		logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError)
   692  	}
   693  
   694  	ctx.HTMLByte(http.StatusOK, buf.Bytes())
   695  }
   696  
   697  // ============================
   698  // Admin Plugin APIs
   699  // ============================
   700  
   701  // AddGenerators add the admin generators.
   702  func (eng *Engine) AddGenerators(list ...table.GeneratorList) *Engine {
   703  	plug, exist := eng.FindPluginByName("admin")
   704  	if exist {
   705  		plug.(*admin.Admin).AddGenerators(list...)
   706  		return eng
   707  	}
   708  	eng.PluginList = append(eng.PluginList, admin.NewAdmin(list...))
   709  	return eng
   710  }
   711  
   712  // AdminPlugin get the admin plugin. if not exist, create one.
   713  func (eng *Engine) AdminPlugin() *admin.Admin {
   714  	plug, exist := eng.FindPluginByName("admin")
   715  	if exist {
   716  		return plug.(*admin.Admin)
   717  	}
   718  	adm := admin.NewAdmin()
   719  	eng.PluginList = append(eng.PluginList, adm)
   720  	return adm
   721  }
   722  
   723  // SetCaptcha set the captcha config.
   724  func (eng *Engine) SetCaptcha(captcha map[string]string) *Engine {
   725  	eng.AdminPlugin().SetCaptcha(captcha)
   726  	return eng
   727  }
   728  
   729  // SetCaptchaDriver set the captcha config with driver.
   730  func (eng *Engine) SetCaptchaDriver(driver string) *Engine {
   731  	eng.AdminPlugin().SetCaptcha(map[string]string{"driver": driver})
   732  	return eng
   733  }
   734  
   735  // AddGenerator add table model generator.
   736  func (eng *Engine) AddGenerator(key string, g table.Generator) *Engine {
   737  	eng.AdminPlugin().AddGenerator(key, g)
   738  	return eng
   739  }
   740  
   741  // AddGlobalDisplayProcessFn call types.AddGlobalDisplayProcessFn.
   742  func (eng *Engine) AddGlobalDisplayProcessFn(f types.FieldFilterFn) *Engine {
   743  	types.AddGlobalDisplayProcessFn(f)
   744  	return eng
   745  }
   746  
   747  // AddDisplayFilterLimit call types.AddDisplayFilterLimit.
   748  func (eng *Engine) AddDisplayFilterLimit(limit int) *Engine {
   749  	types.AddLimit(limit)
   750  	return eng
   751  }
   752  
   753  // AddDisplayFilterTrimSpace call types.AddDisplayFilterTrimSpace.
   754  func (eng *Engine) AddDisplayFilterTrimSpace() *Engine {
   755  	types.AddTrimSpace()
   756  	return eng
   757  }
   758  
   759  // AddDisplayFilterSubstr call types.AddDisplayFilterSubstr.
   760  func (eng *Engine) AddDisplayFilterSubstr(start int, end int) *Engine {
   761  	types.AddSubstr(start, end)
   762  	return eng
   763  }
   764  
   765  // AddDisplayFilterToTitle call types.AddDisplayFilterToTitle.
   766  func (eng *Engine) AddDisplayFilterToTitle() *Engine {
   767  	types.AddToTitle()
   768  	return eng
   769  }
   770  
   771  // AddDisplayFilterToUpper call types.AddDisplayFilterToUpper.
   772  func (eng *Engine) AddDisplayFilterToUpper() *Engine {
   773  	types.AddToUpper()
   774  	return eng
   775  }
   776  
   777  // AddDisplayFilterToLower call types.AddDisplayFilterToLower.
   778  func (eng *Engine) AddDisplayFilterToLower() *Engine {
   779  	types.AddToUpper()
   780  	return eng
   781  }
   782  
   783  // AddDisplayFilterXssFilter call types.AddDisplayFilterXssFilter.
   784  func (eng *Engine) AddDisplayFilterXssFilter() *Engine {
   785  	types.AddXssFilter()
   786  	return eng
   787  }
   788  
   789  // AddDisplayFilterXssJsFilter call types.AddDisplayFilterXssJsFilter.
   790  func (eng *Engine) AddDisplayFilterXssJsFilter() *Engine {
   791  	types.AddXssJsFilter()
   792  	return eng
   793  }