diff --git a/admin.go b/admin.go index fa9ef3b3..c20ac7dc 100644 --- a/admin.go +++ b/admin.go @@ -10,9 +10,10 @@ package beego import ( + "bytes" "fmt" "net/http" - "strconv" + "text/template" "time" "github.com/astaxie/beego/toolbox" @@ -49,7 +50,6 @@ func init() { beeAdminApp.Route("/prof", profIndex) beeAdminApp.Route("/healthcheck", healthcheck) beeAdminApp.Route("/task", taskStatus) - beeAdminApp.Route("/runtask", runTask) beeAdminApp.Route("/listconf", listConf) FilterMonitorFunc = func(string, string, time.Duration) bool { return true } } @@ -57,22 +57,22 @@ func init() { // AdminIndex is the default http.Handler for admin module. // it matches url pattern "/". func adminIndex(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("beego admin dashboard")) - rw.Write([]byte("Welcome to Admin Dashboard
\n")) - rw.Write([]byte("There are servral functions:
\n")) - rw.Write([]byte("1. Record all request and request time, http://localhost:" + strconv.Itoa(AdminHttpPort) + "/qps
\n")) - rw.Write([]byte("2. Get runtime profiling data by the pprof, http://localhost:" + strconv.Itoa(AdminHttpPort) + "/prof
\n")) - rw.Write([]byte("3. Get healthcheck result from http://localhost:" + strconv.Itoa(AdminHttpPort) + "/healthcheck
\n")) - rw.Write([]byte("4. Get current task infomation from task http://localhost:" + strconv.Itoa(AdminHttpPort) + "/task
\n")) - rw.Write([]byte("5. To run a task passed a param http://localhost:" + strconv.Itoa(AdminHttpPort) + "/runtask
\n")) - rw.Write([]byte("6. Get all confige & router infomation http://localhost:" + strconv.Itoa(AdminHttpPort) + "/listconf
\n")) - rw.Write([]byte("")) + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(indexTpl)) + data := make(map[interface{}]interface{}) + tmpl.Execute(rw, data) } // QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. // it's registered with url pattern "/qbs" in admin module. func qpsIndex(rw http.ResponseWriter, r *http.Request) { - toolbox.StatisticsMap.GetMap(rw) + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(qpsTpl)) + data := make(map[interface{}]interface{}) + data["Content"] = toolbox.StatisticsMap.GetMap() + + tmpl.Execute(rw, data) + } // ListConf is the http.Handler of displaying all beego configuration values as key/value pair. @@ -81,112 +81,217 @@ func listConf(rw http.ResponseWriter, r *http.Request) { r.ParseForm() command := r.Form.Get("command") if command != "" { + data := make(map[interface{}]interface{}) switch command { case "conf": - fmt.Fprintln(rw, "list all beego's conf:") - fmt.Fprintln(rw, "AppName:", AppName) - fmt.Fprintln(rw, "AppPath:", AppPath) - fmt.Fprintln(rw, "AppConfigPath:", AppConfigPath) - fmt.Fprintln(rw, "StaticDir:", StaticDir) - fmt.Fprintln(rw, "StaticExtensionsToGzip:", StaticExtensionsToGzip) - fmt.Fprintln(rw, "HttpAddr:", HttpAddr) - fmt.Fprintln(rw, "HttpPort:", HttpPort) - fmt.Fprintln(rw, "HttpTLS:", EnableHttpTLS) - fmt.Fprintln(rw, "HttpCertFile:", HttpCertFile) - fmt.Fprintln(rw, "HttpKeyFile:", HttpKeyFile) - fmt.Fprintln(rw, "RecoverPanic:", RecoverPanic) - fmt.Fprintln(rw, "AutoRender:", AutoRender) - fmt.Fprintln(rw, "ViewsPath:", ViewsPath) - fmt.Fprintln(rw, "RunMode:", RunMode) - fmt.Fprintln(rw, "SessionOn:", SessionOn) - fmt.Fprintln(rw, "SessionProvider:", SessionProvider) - fmt.Fprintln(rw, "SessionName:", SessionName) - fmt.Fprintln(rw, "SessionGCMaxLifetime:", SessionGCMaxLifetime) - fmt.Fprintln(rw, "SessionSavePath:", SessionSavePath) - fmt.Fprintln(rw, "SessionHashFunc:", SessionHashFunc) - fmt.Fprintln(rw, "SessionHashKey:", SessionHashKey) - fmt.Fprintln(rw, "SessionCookieLifeTime:", SessionCookieLifeTime) - fmt.Fprintln(rw, "UseFcgi:", UseFcgi) - fmt.Fprintln(rw, "MaxMemory:", MaxMemory) - fmt.Fprintln(rw, "EnableGzip:", EnableGzip) - fmt.Fprintln(rw, "DirectoryIndex:", DirectoryIndex) - fmt.Fprintln(rw, "HttpServerTimeOut:", HttpServerTimeOut) - fmt.Fprintln(rw, "ErrorsShow:", ErrorsShow) - fmt.Fprintln(rw, "XSRFKEY:", XSRFKEY) - fmt.Fprintln(rw, "EnableXSRF:", EnableXSRF) - fmt.Fprintln(rw, "XSRFExpire:", XSRFExpire) - fmt.Fprintln(rw, "CopyRequestBody:", CopyRequestBody) - fmt.Fprintln(rw, "TemplateLeft:", TemplateLeft) - fmt.Fprintln(rw, "TemplateRight:", TemplateRight) - fmt.Fprintln(rw, "BeegoServerName:", BeegoServerName) - fmt.Fprintln(rw, "EnableAdmin:", EnableAdmin) - fmt.Fprintln(rw, "AdminHttpAddr:", AdminHttpAddr) - fmt.Fprintln(rw, "AdminHttpPort:", AdminHttpPort) + m := make(map[string]interface{}) + + m["AppName"] = AppName + m["AppPath"] = AppPath + m["AppConfigPath"] = AppConfigPath + m["StaticDir"] = StaticDir + m["StaticExtensionsToGzip"] = StaticExtensionsToGzip + m["HttpAddr"] = HttpAddr + m["HttpPort"] = HttpPort + m["HttpTLS"] = EnableHttpTLS + m["HttpCertFile"] = HttpCertFile + m["HttpKeyFile"] = HttpKeyFile + m["RecoverPanic"] = RecoverPanic + m["AutoRender"] = AutoRender + m["ViewsPath"] = ViewsPath + m["RunMode"] = RunMode + m["SessionOn"] = SessionOn + m["SessionProvider"] = SessionProvider + m["SessionName"] = SessionName + m["SessionGCMaxLifetime"] = SessionGCMaxLifetime + m["SessionSavePath"] = SessionSavePath + m["SessionHashFunc"] = SessionHashFunc + m["SessionHashKey"] = SessionHashKey + m["SessionCookieLifeTime"] = SessionCookieLifeTime + m["UseFcgi"] = UseFcgi + m["MaxMemory"] = MaxMemory + m["EnableGzip"] = EnableGzip + m["DirectoryIndex"] = DirectoryIndex + m["HttpServerTimeOut"] = HttpServerTimeOut + m["ErrorsShow"] = ErrorsShow + m["XSRFKEY"] = XSRFKEY + m["EnableXSRF"] = EnableXSRF + m["XSRFExpire"] = XSRFExpire + m["CopyRequestBody"] = CopyRequestBody + m["TemplateLeft"] = TemplateLeft + m["TemplateRight"] = TemplateRight + m["BeegoServerName"] = BeegoServerName + m["EnableAdmin"] = EnableAdmin + m["AdminHttpAddr"] = AdminHttpAddr + m["AdminHttpPort"] = AdminHttpPort + + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(configTpl)) + + data["Content"] = m + + tmpl.Execute(rw, data) + case "router": - fmt.Fprintln(rw, "Print all router infomation:") - for method, t := range BeeApp.Handlers.routers { - fmt.Fprintln(rw) - fmt.Fprintln(rw) - fmt.Fprintln(rw, " Method:", method) - printTree(rw, t) + resultList := new([][]string) + + var result = []string{ + fmt.Sprintf("header"), + fmt.Sprintf("Router Pattern"), + fmt.Sprintf("Methods"), + fmt.Sprintf("Controller"), } - // @todo print routers + *resultList = append(*resultList, result) + + for method, t := range BeeApp.Handlers.routers { + var result = []string{ + fmt.Sprintf("success"), + fmt.Sprintf("Method: %s", method), + fmt.Sprintf(""), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) + + printTree(resultList, t) + } + data["Content"] = resultList + data["Title"] = "Routers" + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(routerAndFilterTpl)) + tmpl.Execute(rw, data) case "filter": - fmt.Fprintln(rw, "Print all filter infomation:") + resultList := new([][]string) + + var result = []string{ + fmt.Sprintf("header"), + fmt.Sprintf("Router Pattern"), + fmt.Sprintf("Filter Function"), + } + *resultList = append(*resultList, result) + if BeeApp.Handlers.enableFilter { - fmt.Fprintln(rw, "BeforeRouter:") + var result = []string{ + fmt.Sprintf("success"), + fmt.Sprintf("Before Router"), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) + if bf, ok := BeeApp.Handlers.filters[BeforeRouter]; ok { for _, f := range bf { - fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) + + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", f.pattern), + fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), + } + *resultList = append(*resultList, result) + } } - fmt.Fprintln(rw, "BeforeExec:") + result = []string{ + fmt.Sprintf("success"), + fmt.Sprintf("Before Exec"), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok { for _, f := range bf { - fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) + + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", f.pattern), + fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), + } + *resultList = append(*resultList, result) + } } - fmt.Fprintln(rw, "AfterExec:") + result = []string{ + fmt.Sprintf("success"), + fmt.Sprintf("AfterExec Exec"), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) + if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok { for _, f := range bf { - fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) + + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", f.pattern), + fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), + } + *resultList = append(*resultList, result) + } } - fmt.Fprintln(rw, "FinishRouter:") + result = []string{ + fmt.Sprintf("success"), + fmt.Sprintf("Finish Router"), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) + if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok { for _, f := range bf { - fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) + + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", f.pattern), + fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), + } + *resultList = append(*resultList, result) + } } } + data["Content"] = resultList + data["Title"] = "Filters" + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(routerAndFilterTpl)) + tmpl.Execute(rw, data) + default: rw.Write([]byte("command not support")) } } else { - rw.Write([]byte("beego admin dashboard")) - rw.Write([]byte("ListConf support this command:
\n")) - rw.Write([]byte("1. command=conf
\n")) - rw.Write([]byte("2. command=router
\n")) - rw.Write([]byte("3. command=filter
\n")) - rw.Write([]byte("")) } } -func printTree(rw http.ResponseWriter, t *Tree) { +func printTree(resultList *[][]string, t *Tree) { for _, tr := range t.fixrouters { - printTree(rw, tr) + printTree(resultList, tr) } if t.wildcard != nil { - printTree(rw, t.wildcard) + printTree(resultList, t.wildcard) } for _, l := range t.leaves { if v, ok := l.runObject.(*controllerInfo); ok { if v.routerType == routerTypeBeego { - fmt.Fprintln(rw, v.pattern, v.methods, v.controllerType.Name()) + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", v.pattern), + fmt.Sprintf("%s", v.methods), + fmt.Sprintf("%s", v.controllerType), + } + *resultList = append(*resultList, result) } else if v.routerType == routerTypeRESTFul { - fmt.Fprintln(rw, v.pattern, v.methods) + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", v.pattern), + fmt.Sprintf("%s", v.methods), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) } else if v.routerType == routerTypeHandler { - fmt.Fprintln(rw, v.pattern, "handler") + var result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", v.pattern), + fmt.Sprintf(""), + fmt.Sprintf(""), + } + *resultList = append(*resultList, result) } } } @@ -197,58 +302,106 @@ func printTree(rw http.ResponseWriter, t *Tree) { func profIndex(rw http.ResponseWriter, r *http.Request) { r.ParseForm() command := r.Form.Get("command") + data := make(map[interface{}]interface{}) + + var result bytes.Buffer if command != "" { - toolbox.ProcessInput(command, rw) + toolbox.ProcessInput(command, &result) + data["Content"] = result.String() + data["Title"] = command + + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(profillingTpl)) + tmpl.Execute(rw, data) } else { - rw.Write([]byte("beego admin dashboard")) - rw.Write([]byte("request url like '/prof?command=lookup goroutine'
\n")) - rw.Write([]byte("the command have below types:
\n")) - rw.Write([]byte("1. lookup goroutine
\n")) - rw.Write([]byte("2. lookup heap
\n")) - rw.Write([]byte("3. lookup threadcreate
\n")) - rw.Write([]byte("4. lookup block
\n")) - rw.Write([]byte("5. start cpuprof
\n")) - rw.Write([]byte("6. stop cpuprof
\n")) - rw.Write([]byte("7. get memprof
\n")) - rw.Write([]byte("8. gc summary
\n")) - rw.Write([]byte("")) } } // Healthcheck is a http.Handler calling health checking and showing the result. // it's in "/healthcheck" pattern in admin module. func healthcheck(rw http.ResponseWriter, req *http.Request) { + data := make(map[interface{}]interface{}) + + resultList := new([][]string) + var result = []string{ + fmt.Sprintf("header"), + fmt.Sprintf("Name"), + fmt.Sprintf("Status"), + } + *resultList = append(*resultList, result) + for name, h := range toolbox.AdminCheckList { if err := h.Check(); err != nil { - fmt.Fprintf(rw, "%s : %s\n", name, err.Error()) + result = []string{ + fmt.Sprintf("error"), + fmt.Sprintf("%s", name), + fmt.Sprintf("%s", err.Error()), + } + } else { - fmt.Fprintf(rw, "%s : ok\n", name) + result = []string{ + fmt.Sprintf("success"), + fmt.Sprintf("%s", name), + fmt.Sprintf("OK"), + } + } + *resultList = append(*resultList, result) } + + data["Content"] = resultList + data["Title"] = "Health Check" + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(healthCheckTpl)) + tmpl.Execute(rw, data) + } // TaskStatus is a http.Handler with running task status (task name, status and the last execution). // it's in "/task" pattern in admin module. func taskStatus(rw http.ResponseWriter, req *http.Request) { - for tname, tk := range toolbox.AdminTaskList { - fmt.Fprintf(rw, "%s:%s:%s", tname, tk.GetStatus(), tk.GetPrev().String()) - } -} + data := make(map[interface{}]interface{}) -// RunTask is a http.Handler to run a Task from the "query string. -// the request url likes /runtask?taskname=sendmail. -func runTask(rw http.ResponseWriter, req *http.Request) { + // Run Task req.ParseForm() taskname := req.Form.Get("taskname") - if t, ok := toolbox.AdminTaskList[taskname]; ok { - err := t.Run() - if err != nil { - fmt.Fprintf(rw, "%v", err) + if taskname != "" { + + if t, ok := toolbox.AdminTaskList[taskname]; ok { + err := t.Run() + if err != nil { + data["Message"] = []string{"error", fmt.Sprintf("%s", err)} + } + data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is %s", taskname, t.GetStatus())} + } else { + data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)} } - fmt.Fprintf(rw, "%s run success,Now the Status is %s", taskname, t.GetStatus()) - } else { - fmt.Fprintf(rw, "there's no task which named:%s", taskname) } + + // List Tasks + resultList := new([][]string) + var result = []string{ + fmt.Sprintf("header"), + fmt.Sprintf("Task Name"), + fmt.Sprintf("Task Spec"), + fmt.Sprintf("Task Function"), + } + *resultList = append(*resultList, result) + for tname, tk := range toolbox.AdminTaskList { + result = []string{ + fmt.Sprintf(""), + fmt.Sprintf("%s", tname), + fmt.Sprintf("%s", tk.GetStatus()), + fmt.Sprintf("%s", tk.GetPrev().String()), + } + *resultList = append(*resultList, result) + } + + data["Content"] = resultList + data["Title"] = "Tasks" + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(tasksTpl)) + tmpl.Execute(rw, data) } // adminApp is an http.HandlerFunc map used as beeAdminApp. diff --git a/adminui.go b/adminui.go new file mode 100644 index 00000000..0c5a25d5 --- /dev/null +++ b/adminui.go @@ -0,0 +1,316 @@ +// Beego (http://beego.me/) +// +// @description beego is an open-source, high-performance web framework for the Go programming language. +// +// @link http://github.com/astaxie/beego for the canonical source repository +// +// @license http://github.com/astaxie/beego/blob/master/LICENSE +// +// @authors astaxie +package beego + +var indexTpl = ` +{{define "content"}} +

Beego Admin Dashboard

+

+For detail usage please check our document: +

+

+Toolbox +

+

+Live Monitor +

+{{.Content}} +{{end}}` + +var profillingTpl = ` +{{define "content"}} +

{{.Title}}

+
+{{.Content}}
+
+{{end}}` + +var qpsTpl = ` +{{define "content"}} +

Requests statistics

+ +{{range $i, $slice := .Content}} + +{{range $j, $elem := $slice}} +{{if eq $i 0}} + +{{end}} +{{end}} + + +{{end}} +
+{{else}} + +{{end}} +{{$elem}} +{{if eq $i 0}} + +{{else}} +
+{{end}} +` + +var configTpl = ` +{{define "content"}} +

Configurations

+
+{{range $index, $elem := .Content}}
+{{$index}}={{$elem}}
+{{end}}
+
+{{end}} +` + +var routerAndFilterTpl = ` +{{define "content"}} + +

{{.Title}}

+ +{{range $i, $slice := .Content}} + + +{{ $header := index $slice 0}} +{{if eq "header" $header }} + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} +{{else if eq "success" $header}} + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} +{{else}} + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} +{{end}} + + +{{end}} +
+ {{$elem}} + + {{$elem}} + + {{$elem}} +
+{{end}} +` + +var tasksTpl = ` +{{define "content"}} + +

{{.Title}}

+ +{{if .Message }} +{{ $messageType := index .Message 0}} +

+{{index .Message 1}} +

+{{end}} + + + +{{range $i, $slice := .Content}} + + +{{ $header := index $slice 0}} +{{if eq "header" $header }} + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} + +{{else}} + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} + +{{end}} + + +{{end}} +
+ {{$elem}} + + Run Task + + {{$elem}} + + Run +
+{{end}} +` + +var healthCheckTpl = ` +{{define "content"}} + +

{{.Title}}

+ +{{range $i, $slice := .Content}} + +{{ $header := index $slice 0}} +{{if eq "header" $header }} + + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} + +{{else}} + {{ if eq "success" $header}} + + {{else if eq "error" $header}} + + {{else}} + + {{end}} + {{range $j, $elem := $slice}} + {{if ne $j 0}} + + {{end}} + {{end}} + +{{end}} + +{{end}} +
+ {{$elem}} +
+ {{$elem}} +
+{{end}}` + +// The base dashboardTpl +var dashboardTpl = ` + + + + + + + + + + +Welcome to Beego Admin Dashboard + + + + + + + + + + + + +
+{{template "content" .}} +
+ + + + + + +` diff --git a/toolbox/statistics.go b/toolbox/statistics.go index fed8cc2b..0ca31473 100644 --- a/toolbox/statistics.go +++ b/toolbox/statistics.go @@ -11,7 +11,6 @@ package toolbox import ( "fmt" - "io" "sync" "time" ) @@ -79,17 +78,28 @@ func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController stri } // put url statistics result in io.Writer -func (m *UrlMap) GetMap(rw io.Writer) { +func (m *UrlMap) GetMap() [][]string { m.lock.RLock() defer m.lock.RUnlock() - fmt.Fprintf(rw, "| % -50s| % -10s | % -16s | % -16s | % -16s | % -16s | % -16s |\n", "requestUrl", "method", "times", "used", "max used", "min used", "avg used") + resultLists := make([][]string, 0) + + var result = []string{"requestUrl", "method", "times", "used", "max used", "min used", "avg used"} + resultLists = append(resultLists, result) for k, v := range m.urlmap { for kk, vv := range v { - fmt.Fprintf(rw, "| % -50s| % -10s | % -16d | % -16s | % -16s | % -16s | % -16s |\n", k, - kk, vv.RequestNum, toS(vv.TotalTime), toS(vv.MaxTime), toS(vv.MinTime), toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum)), - ) + result := []string{ + fmt.Sprintf("% -50s", k), + fmt.Sprintf("% -10s", kk), + fmt.Sprintf("% -16d", vv.RequestNum), + fmt.Sprintf("% -16s", toS(vv.TotalTime)), + fmt.Sprintf("% -16s", toS(vv.MaxTime)), + fmt.Sprintf("% -16s", toS(vv.MinTime)), + fmt.Sprintf("% -16s", toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum))), + } + resultLists = append(resultLists, result) } } + return resultLists } // global statistics data map