QOR Admin
Instantly create a beautiful, cross platform, configurable Admin Interface and API for managing your data in minutes.
Features
- Admin Interface for managing data
- JSON API
- Association handling
- Search and filtering
- Actions/Batch Actions
- Authentication and Authorization (based on Permissions)
- Extendability
Quick Start
package main
import (
"fmt"
"net/http"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
"github.com/qor/qor"
"github.com/qor/admin"
)
// Create a GORM-backend model
type User struct {
gorm.Model
Name string
}
// Create another GORM-backend model
type Product struct {
gorm.Model
Name string
Description string
}
func main() {
DB, _ := gorm.Open("sqlite3", "demo.db")
DB.AutoMigrate(&User{}, &Product{})
// Initalize
Admin := admin.New(&qor.Config{DB: DB})
// Create resources from GORM-backend model
Admin.AddResource(&User{})
Admin.AddResource(&Product{})
// Register route
mux := http.NewServeMux()
// amount to /admin, so visit `/admin` to view the admin interface
Admin.MountTo("/admin", mux)
fmt.Println("Listening on: 9000")
http.ListenAndServe(":9000", mux)
}
go run main.go
and visit localhost:9000/admin
to see the result !
General Setting
Site Name
Use SetSiteName
to set QOR Admin's HTML title, the name will also be used to auto-load javascripts and stylesheet files that you can provide for customizing the admin interface.
For example, say you set the Site Name as QOR Demo
, admin will look up qor_demo.js
, qor_demo.css
in QOR view paths, and load them if present.
Admin.SetSiteName("QOR DEMO")
Dashboard
QOR Admin provides a default dashboard page with some dummy text. If you want to customize the dashboard, you can create a file dashboard.tmpl
in QOR view paths, QOR Admin will load it as golang templates when rendering the dashboard.
If you want to disable the dashboard, you can redirect it to some other page, for example:
Admin.GetRouter().Get("/", func(c *admin.Context) {
http.Redirect(c.Writer, c.Request, "/admin/clients", http.StatusSeeOther)
})
Authentication
QOR Admin provides a flexible authorization solution. With it, you could integrate with your current authorization method.
What you need to do is implement an Auth
interface like below, and set it in the Admin value.
type Auth interface {
GetCurrentUser(*Context) qor.CurrentUser // get current user, if don't have permission, then return nil
LoginURL(*Context) string // get login url, if don't have permission, will redirect to this url
LogoutURL(*Context) string // get logout url, if click logout link from admin interface, will visit this page
}
Here is an example:
type Auth struct{}
func (Auth) LoginURL(c *admin.Context) string {
return "/login"
}
func (Auth) LogoutURL(*Context) string
return "/logout"
}
func (Auth) GetCurrentUser(c *admin.Context) qor.CurrentUser {
if userid, err := c.Request.Cookie("userid"); err == nil {
var user User
if !DB.First(&user, "id = ?", userid.Value).RecordNotFound() {
return &user
}
}
return nil
}
func (u User) DisplayName() string {
return u.Name
}
// Register Auth for QOR Admin
Admin.SetAuth(&Auth{})
Menu
Register a Menu
It is possible to define a nested menu structure for the admin interface.
Admin.AddMenu(&admin.Menu{Name: "Dashboard", Link: "/admin"})
// Register nested menu
Admin.AddMenu(&admin.Menu{Name: "menu", Link: "/link", Ancestors: []string{"Dashboard"}})
// Register menu with permission
Admin.AddMenu(&admin.Menu{Name: "Report", Link: "/admin", Permission: roles.Allow(roles.Read, "admin")})
Add Resources to a menu
Admin.AddResource(&User{})
Admin.AddResource(&Product{}, &admin.Config{Menu: []string{"Product Management"}})
Admin.AddResource(&Color{}, &admin.Config{Menu: []string{"Product Management"}})
Admin.AddResource(&Size{}, &admin.Config{Menu: []string{"Product Management"}})
Admin.AddResource(&Order{}, &admin.Config{Menu: []string{"Order Management"}})
If you don't want a resource to be displayed in the menu, pass the Invisible option:
Admin.AddResource(&User{}, &admin.Config{Invisible: true})
Internationalization
To translate admin interface to a new language, you could use i18n
https://github.com/qor/i18n
Working with a Resource
Every QOR Admin Resource needs a GORM-backend model. Once you have defined the model you can create a QOR Admin resource: Admin.AddResource(&Product{})
Once a resource has been added, QOR Admin will generate the admin interface to manage it, including a RESTFul JSON API.
So for above example, you could visit localhost:9000/admin/products
to manage Product
in the HTML admin interface, or use the RESTFul JSON api localhost:9000/admin/products.json
to perform CRUD activities.
Customizing CRUD pages
// Set attributes will be shown in the index page
// show given attributes
order.IndexAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
// show all attributes except `State`
order.IndexAttrs("-State")
// Set attributes will be shown in the new page
order.NewAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
// show all attributes except `State`
order.NewAttrs("-State")
// Structure the new form to make it tidy and clean with `Section`
product.NewAttrs(
&admin.Section{
Title: "Basic Information",
Rows: [][]string{
{"Name"},
{"Code", "Price"},
}
},
&admin.Section{
Title: "Organization",
Rows: [][]string{
{"Category", "Collections", "MadeCountry"},
}
},
"Description",
"ColorVariations",
}
// Set attributes will be shown for the edit page, similiar with new page
order.EditAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
// Set attributes will be shown for the show page, similiar with new page
// If ShowAttrs haven't been configured, there will be no show page generated, by will show the edit from instead
order.ShowAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
Search
It is possible to specify database table columns as search attributes, using SearchAttrs
, the columns will be used to perform any search queries. It is also possible to specify nested relations.
// Search products with its name, code, category's name, brand's name
product.SearchAttrs("Name", "Code", "Category.Name", "Brand.Name")
If you want to fully customize the search function, you could set the SearchHandler
:
order.SearchHandler = func(keyword string, context *qor.Context) *gorm.DB {
// search orders
}
Search Center
You might want to search a broad range of resources from a single web page, in this case Search Center
is for you! Simply add resources that you want to be searchable to the Admin value's search center:
// add resource `product`, `user`, `order` to search resources
Admin.AddSearchResource(product, user, order)
Scopes
You can define scopes to filter data with given conditions, for example:
// Only show actived users
user.Scope(&admin.Scope{Name: "Active", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("active = ?", true)
}})
Group Scopes
order.Scope(&admin.Scope{Name: "Paid", Group: "State", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("state = ?", "paid")
}})
order.Scope(&admin.Scope{Name: "Shipped", Group: "State", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("state = ?", "shipped")
}})
Actions
QOR Admin has the notion of four action modes:
- Bulk actions (will be shown in index page as bulk actions)
- Edit form action (will be shown in edit page)
- Show page action (will be shown in show page)
- Menu item action (will be shown in table's menu)
You can register an Action of any mode using the Action
method, along with Modes
values to contol where to show them:
product.Action(&admin.Action{
Name: "enable",
Handle: func(actionArgument *admin.ActionArgument) error {
// `FindSelectedRecords` => return selected record in bulk action mode, return current record in other mode
for _, record := range actionArgument.FindSelectedRecords() {
actionArgument.Context.DB.Model(record.(*models.Product)).Update("disabled", false)
}
return nil
},
Modes: []string{"index", "edit", "show", "menu_item"},
})
// Register Actions need user's input
order.Action(&admin.Action{
Name: "Ship",
Handle: func(argument *admin.ActionArgument) error {
trackingNumberArgument := argument.Argument.(*trackingNumberArgument)
for _, record := range argument.FindSelectedRecords() {
argument.Context.GetDB().Model(record).UpdateColumn("tracking_number", trackingNumberArgument.TrackingNumber)
}
return nil
},
Resource: Admin.NewResource(&trackingNumberArgument{}),
Modes: []string{"show", "menu_item"},
})
// the ship action's argument
type trackingNumberArgument struct {
TrackingNumber string
}
// Use `Visible` to hide registered Action in some case
order.Action(&admin.Action{
Name: "Cancel",
Handle: func(argument *admin.ActionArgument) error {
// cancel the order
},
Visible: func(record interface{}) bool {
if order, ok := record.(*models.Order); ok {
for _, state := range []string{"draft", "checkout", "paid", "processing"} {
if order.State == state {
return true
}
}
}
return false
},
Modes: []string{"show", "menu_item"},
})
Customizing the Form
By default, management pages in QOR Admin are rendered based on your resource's fields' data types and relations. The default should satisfy most use cases, however should you need to you can customize the rendering by overwritting the Meta
definition.
There are some Meta types that have been predefined, including string
, password
, date
, datetime
, rich_editor
, select_one
, select_many
and so on (see full list here: qor admin form templates). QOR Admin will auto select a type for Meta
based on a field's data type. For example, if a field's type is time.Time
, QOR Admin will determine datetime
as the type.
// Change the Meta type of `Password` field in User resource from `string` (default value) to `password`
user.Meta(&admin.Meta{Name: "Password", Type: "password"})
// Change the Meta type of `Gender` field in User resource from `string` (default value) to `select_one`, with options `M` | `F`
user.Meta(&admin.Meta{Name: "Gender", Type: "select_one", Collection: []string{"M", "F"}})
Authorization and Permissions
Authorization in QOR Admin is based on setting Permissions per Role. QOR Admin uses https://github.com/qor/roles for Permission management, please refer to it's documentation for information on how to define Roles and Permissions.
// CRUD permission for admin users, deny create permission for manager
user := Admin.AddResource(&User{}, &admin.Config{Permission: roles.Allow(roles.CRUD, "admin").Deny(roles.Create, "manager")})
// For user's Email field, allow CRUD for admin users, deny update for manager
user.Meta(&admin.Meta{Name: "Email", Permission: roles.Allow(roles.CRUD, "admin").Deny(roles.Create, "manager")})
An automagic RESTFul API
The RESTFul API shares the same configuration as your admin interface, including actions and permissions - so after you have configured your admin interface, you will get an API for free!
Extendability
Configuring QOR Admin Resources Automatically
If your model has the following two methods defined, they will be called when registering:
func ConfigureQorResourceBeforeInitialize(resource) {
// resource.(*admin.Resource)
}
func ConfigureQorResource(resource) {
// resource.(*admin.Resource)
}
Configuring QOR Admin Meta Automatically
If your field's type has the following two methods defined, they will be called when registering:
func ConfigureQorMetaBeforeInitialize(meta) {
// resource.(*admin.Meta)
}
func ConfigureMetaInterface(meta) {
// resource.(*admin.Meta)
}
Using a Theme
A custom theme can be applied using a custom javascript and css file, for example to make a product page look super fancy. To apply a custom theme, provide the theme name using the UseTheme
method, this will load assets/javascripts/fancy.js
and assets/stylesheets/fancy.css
from QOR view paths
product.UseTheme("fancy")
Customizing Views
QOR Admin will look up templates in QOR Admin view paths and use them to render any admin page. By placing your own templates in {current path}/app/views/qor
you can extend your application by customizing it's views. If you want to customize your views from other places, you could register any new paths with admin.RegisterViewPath
.
Customize Views Rules:
- To overwrite a template, create a file with the same name under
{current path}/app/views/qor
. - To overwrite templates for a specific resource, put templates with the same name in
{qor view paths}/{resource param}
, for example{current path}/app/views/qor/products/index.tmpl
. - To overwrite templates for resources using a theme, put templates with the same name in
{qor view paths}/themes/{theme name}
.
Registering HTTP routes
Qor admin uses Qor's Router.
router := Admin.GetRouter()
router.Get("/path", func(context *admin.Context) {
// do something here
})
router.Post("/path", func(context *admin.Context) {
// do something here
})
router.Put("/path", func(context *admin.Context) {
// do something here
})
router.Delete("/path", func(context *admin.Context) {
// do something here
})
// naming route
router.Get("/path/:name", func(context *admin.Context) {
context.Request.URL.Query().Get(":name")
})
// regexp support
router.Get("/path/:name[world]", func(context *admin.Context) { // "/hello/world"
context.Request.URL.Query().Get(":name")
})
router.Get("/path/:name[\\d+]", func(context *admin.Context) { // "/hello/123"
context.Request.URL.Query().Get(":name")
})
Plugins
There are a few plugins created for QOR already, you can find some of them at https://github.com/qor, visit them to learn more about how to extend QOR.
Live DEMO
- Live Demo http://demo.getqor.com/admin
- Source Code of Live Demo https://github.com/qor/qor-example
Q & A
- How to integrate with beego
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
beego.Handler("/admin/*", mux)
beego.Run()
- How to integrate with Gin
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
r := gin.Default()
r.Any("/admin/*w", gin.WrapH(mux))
r.Run()
License
Released under the MIT License.