84a4379f by astaxie

Merge pull request #739 from lei-cao/develop

Added the UI for Admin monitor page
2 parents ab71201c c347dd9e
...@@ -10,9 +10,10 @@ ...@@ -10,9 +10,10 @@
10 package beego 10 package beego
11 11
12 import ( 12 import (
13 "bytes"
13 "fmt" 14 "fmt"
14 "net/http" 15 "net/http"
15 "strconv" 16 "text/template"
16 "time" 17 "time"
17 18
18 "github.com/astaxie/beego/toolbox" 19 "github.com/astaxie/beego/toolbox"
...@@ -49,7 +50,6 @@ func init() { ...@@ -49,7 +50,6 @@ func init() {
49 beeAdminApp.Route("/prof", profIndex) 50 beeAdminApp.Route("/prof", profIndex)
50 beeAdminApp.Route("/healthcheck", healthcheck) 51 beeAdminApp.Route("/healthcheck", healthcheck)
51 beeAdminApp.Route("/task", taskStatus) 52 beeAdminApp.Route("/task", taskStatus)
52 beeAdminApp.Route("/runtask", runTask)
53 beeAdminApp.Route("/listconf", listConf) 53 beeAdminApp.Route("/listconf", listConf)
54 FilterMonitorFunc = func(string, string, time.Duration) bool { return true } 54 FilterMonitorFunc = func(string, string, time.Duration) bool { return true }
55 } 55 }
...@@ -57,22 +57,22 @@ func init() { ...@@ -57,22 +57,22 @@ func init() {
57 // AdminIndex is the default http.Handler for admin module. 57 // AdminIndex is the default http.Handler for admin module.
58 // it matches url pattern "/". 58 // it matches url pattern "/".
59 func adminIndex(rw http.ResponseWriter, r *http.Request) { 59 func adminIndex(rw http.ResponseWriter, r *http.Request) {
60 rw.Write([]byte("<html><head><title>beego admin dashboard</title></head><body>")) 60 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
61 rw.Write([]byte("Welcome to Admin Dashboard<br>\n")) 61 tmpl = template.Must(tmpl.Parse(indexTpl))
62 rw.Write([]byte("There are servral functions:<br>\n")) 62 data := make(map[interface{}]interface{})
63 rw.Write([]byte("1. Record all request and request time, <a href='/qps'>http://localhost:" + strconv.Itoa(AdminHttpPort) + "/qps</a><br>\n")) 63 tmpl.Execute(rw, data)
64 rw.Write([]byte("2. Get runtime profiling data by the pprof, <a href='/prof'>http://localhost:" + strconv.Itoa(AdminHttpPort) + "/prof</a><br>\n"))
65 rw.Write([]byte("3. Get healthcheck result from <a href='/healthcheck'>http://localhost:" + strconv.Itoa(AdminHttpPort) + "/healthcheck</a><br>\n"))
66 rw.Write([]byte("4. Get current task infomation from task <a href='/task'>http://localhost:" + strconv.Itoa(AdminHttpPort) + "/task</a><br> \n"))
67 rw.Write([]byte("5. To run a task passed a param <a href='/runtask'>http://localhost:" + strconv.Itoa(AdminHttpPort) + "/runtask</a><br>\n"))
68 rw.Write([]byte("6. Get all confige & router infomation <a href='/listconf'>http://localhost:" + strconv.Itoa(AdminHttpPort) + "/listconf</a><br>\n"))
69 rw.Write([]byte("</body></html>"))
70 } 64 }
71 65
72 // QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. 66 // QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
73 // it's registered with url pattern "/qbs" in admin module. 67 // it's registered with url pattern "/qbs" in admin module.
74 func qpsIndex(rw http.ResponseWriter, r *http.Request) { 68 func qpsIndex(rw http.ResponseWriter, r *http.Request) {
75 toolbox.StatisticsMap.GetMap(rw) 69 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
70 tmpl = template.Must(tmpl.Parse(qpsTpl))
71 data := make(map[interface{}]interface{})
72 data["Content"] = toolbox.StatisticsMap.GetMap()
73
74 tmpl.Execute(rw, data)
75
76 } 76 }
77 77
78 // ListConf is the http.Handler of displaying all beego configuration values as key/value pair. 78 // 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) { ...@@ -81,112 +81,217 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
81 r.ParseForm() 81 r.ParseForm()
82 command := r.Form.Get("command") 82 command := r.Form.Get("command")
83 if command != "" { 83 if command != "" {
84 data := make(map[interface{}]interface{})
84 switch command { 85 switch command {
85 case "conf": 86 case "conf":
86 fmt.Fprintln(rw, "list all beego's conf:") 87 m := make(map[string]interface{})
87 fmt.Fprintln(rw, "AppName:", AppName) 88
88 fmt.Fprintln(rw, "AppPath:", AppPath) 89 m["AppName"] = AppName
89 fmt.Fprintln(rw, "AppConfigPath:", AppConfigPath) 90 m["AppPath"] = AppPath
90 fmt.Fprintln(rw, "StaticDir:", StaticDir) 91 m["AppConfigPath"] = AppConfigPath
91 fmt.Fprintln(rw, "StaticExtensionsToGzip:", StaticExtensionsToGzip) 92 m["StaticDir"] = StaticDir
92 fmt.Fprintln(rw, "HttpAddr:", HttpAddr) 93 m["StaticExtensionsToGzip"] = StaticExtensionsToGzip
93 fmt.Fprintln(rw, "HttpPort:", HttpPort) 94 m["HttpAddr"] = HttpAddr
94 fmt.Fprintln(rw, "HttpTLS:", EnableHttpTLS) 95 m["HttpPort"] = HttpPort
95 fmt.Fprintln(rw, "HttpCertFile:", HttpCertFile) 96 m["HttpTLS"] = EnableHttpTLS
96 fmt.Fprintln(rw, "HttpKeyFile:", HttpKeyFile) 97 m["HttpCertFile"] = HttpCertFile
97 fmt.Fprintln(rw, "RecoverPanic:", RecoverPanic) 98 m["HttpKeyFile"] = HttpKeyFile
98 fmt.Fprintln(rw, "AutoRender:", AutoRender) 99 m["RecoverPanic"] = RecoverPanic
99 fmt.Fprintln(rw, "ViewsPath:", ViewsPath) 100 m["AutoRender"] = AutoRender
100 fmt.Fprintln(rw, "RunMode:", RunMode) 101 m["ViewsPath"] = ViewsPath
101 fmt.Fprintln(rw, "SessionOn:", SessionOn) 102 m["RunMode"] = RunMode
102 fmt.Fprintln(rw, "SessionProvider:", SessionProvider) 103 m["SessionOn"] = SessionOn
103 fmt.Fprintln(rw, "SessionName:", SessionName) 104 m["SessionProvider"] = SessionProvider
104 fmt.Fprintln(rw, "SessionGCMaxLifetime:", SessionGCMaxLifetime) 105 m["SessionName"] = SessionName
105 fmt.Fprintln(rw, "SessionSavePath:", SessionSavePath) 106 m["SessionGCMaxLifetime"] = SessionGCMaxLifetime
106 fmt.Fprintln(rw, "SessionHashFunc:", SessionHashFunc) 107 m["SessionSavePath"] = SessionSavePath
107 fmt.Fprintln(rw, "SessionHashKey:", SessionHashKey) 108 m["SessionHashFunc"] = SessionHashFunc
108 fmt.Fprintln(rw, "SessionCookieLifeTime:", SessionCookieLifeTime) 109 m["SessionHashKey"] = SessionHashKey
109 fmt.Fprintln(rw, "UseFcgi:", UseFcgi) 110 m["SessionCookieLifeTime"] = SessionCookieLifeTime
110 fmt.Fprintln(rw, "MaxMemory:", MaxMemory) 111 m["UseFcgi"] = UseFcgi
111 fmt.Fprintln(rw, "EnableGzip:", EnableGzip) 112 m["MaxMemory"] = MaxMemory
112 fmt.Fprintln(rw, "DirectoryIndex:", DirectoryIndex) 113 m["EnableGzip"] = EnableGzip
113 fmt.Fprintln(rw, "HttpServerTimeOut:", HttpServerTimeOut) 114 m["DirectoryIndex"] = DirectoryIndex
114 fmt.Fprintln(rw, "ErrorsShow:", ErrorsShow) 115 m["HttpServerTimeOut"] = HttpServerTimeOut
115 fmt.Fprintln(rw, "XSRFKEY:", XSRFKEY) 116 m["ErrorsShow"] = ErrorsShow
116 fmt.Fprintln(rw, "EnableXSRF:", EnableXSRF) 117 m["XSRFKEY"] = XSRFKEY
117 fmt.Fprintln(rw, "XSRFExpire:", XSRFExpire) 118 m["EnableXSRF"] = EnableXSRF
118 fmt.Fprintln(rw, "CopyRequestBody:", CopyRequestBody) 119 m["XSRFExpire"] = XSRFExpire
119 fmt.Fprintln(rw, "TemplateLeft:", TemplateLeft) 120 m["CopyRequestBody"] = CopyRequestBody
120 fmt.Fprintln(rw, "TemplateRight:", TemplateRight) 121 m["TemplateLeft"] = TemplateLeft
121 fmt.Fprintln(rw, "BeegoServerName:", BeegoServerName) 122 m["TemplateRight"] = TemplateRight
122 fmt.Fprintln(rw, "EnableAdmin:", EnableAdmin) 123 m["BeegoServerName"] = BeegoServerName
123 fmt.Fprintln(rw, "AdminHttpAddr:", AdminHttpAddr) 124 m["EnableAdmin"] = EnableAdmin
124 fmt.Fprintln(rw, "AdminHttpPort:", AdminHttpPort) 125 m["AdminHttpAddr"] = AdminHttpAddr
126 m["AdminHttpPort"] = AdminHttpPort
127
128 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
129 tmpl = template.Must(tmpl.Parse(configTpl))
130
131 data["Content"] = m
132
133 tmpl.Execute(rw, data)
134
125 case "router": 135 case "router":
126 fmt.Fprintln(rw, "Print all router infomation:") 136 resultList := new([][]string)
137
138 var result = []string{
139 fmt.Sprintf("header"),
140 fmt.Sprintf("Router Pattern"),
141 fmt.Sprintf("Methods"),
142 fmt.Sprintf("Controller"),
143 }
144 *resultList = append(*resultList, result)
145
127 for method, t := range BeeApp.Handlers.routers { 146 for method, t := range BeeApp.Handlers.routers {
128 fmt.Fprintln(rw) 147 var result = []string{
129 fmt.Fprintln(rw) 148 fmt.Sprintf("success"),
130 fmt.Fprintln(rw, " Method:", method) 149 fmt.Sprintf("Method: %s", method),
131 printTree(rw, t) 150 fmt.Sprintf(""),
151 fmt.Sprintf(""),
152 }
153 *resultList = append(*resultList, result)
154
155 printTree(resultList, t)
132 } 156 }
133 // @todo print routers 157 data["Content"] = resultList
158 data["Title"] = "Routers"
159 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
160 tmpl = template.Must(tmpl.Parse(routerAndFilterTpl))
161 tmpl.Execute(rw, data)
134 case "filter": 162 case "filter":
135 fmt.Fprintln(rw, "Print all filter infomation:") 163 resultList := new([][]string)
164
165 var result = []string{
166 fmt.Sprintf("header"),
167 fmt.Sprintf("Router Pattern"),
168 fmt.Sprintf("Filter Function"),
169 }
170 *resultList = append(*resultList, result)
171
136 if BeeApp.Handlers.enableFilter { 172 if BeeApp.Handlers.enableFilter {
137 fmt.Fprintln(rw, "BeforeRouter:") 173 var result = []string{
174 fmt.Sprintf("success"),
175 fmt.Sprintf("Before Router"),
176 fmt.Sprintf(""),
177 }
178 *resultList = append(*resultList, result)
179
138 if bf, ok := BeeApp.Handlers.filters[BeforeRouter]; ok { 180 if bf, ok := BeeApp.Handlers.filters[BeforeRouter]; ok {
139 for _, f := range bf { 181 for _, f := range bf {
140 fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) 182
183 var result = []string{
184 fmt.Sprintf(""),
185 fmt.Sprintf("%s", f.pattern),
186 fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
187 }
188 *resultList = append(*resultList, result)
189
141 } 190 }
142 } 191 }
143 fmt.Fprintln(rw, "BeforeExec:") 192 result = []string{
193 fmt.Sprintf("success"),
194 fmt.Sprintf("Before Exec"),
195 fmt.Sprintf(""),
196 }
197 *resultList = append(*resultList, result)
144 if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok { 198 if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok {
145 for _, f := range bf { 199 for _, f := range bf {
146 fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) 200
201 var result = []string{
202 fmt.Sprintf(""),
203 fmt.Sprintf("%s", f.pattern),
204 fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
205 }
206 *resultList = append(*resultList, result)
207
147 } 208 }
148 } 209 }
149 fmt.Fprintln(rw, "AfterExec:") 210 result = []string{
211 fmt.Sprintf("success"),
212 fmt.Sprintf("AfterExec Exec"),
213 fmt.Sprintf(""),
214 }
215 *resultList = append(*resultList, result)
216
150 if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok { 217 if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok {
151 for _, f := range bf { 218 for _, f := range bf {
152 fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) 219
220 var result = []string{
221 fmt.Sprintf(""),
222 fmt.Sprintf("%s", f.pattern),
223 fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
224 }
225 *resultList = append(*resultList, result)
226
153 } 227 }
154 } 228 }
155 fmt.Fprintln(rw, "FinishRouter:") 229 result = []string{
230 fmt.Sprintf("success"),
231 fmt.Sprintf("Finish Router"),
232 fmt.Sprintf(""),
233 }
234 *resultList = append(*resultList, result)
235
156 if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok { 236 if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok {
157 for _, f := range bf { 237 for _, f := range bf {
158 fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) 238
239 var result = []string{
240 fmt.Sprintf(""),
241 fmt.Sprintf("%s", f.pattern),
242 fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
243 }
244 *resultList = append(*resultList, result)
245
159 } 246 }
160 } 247 }
161 } 248 }
249 data["Content"] = resultList
250 data["Title"] = "Filters"
251 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
252 tmpl = template.Must(tmpl.Parse(routerAndFilterTpl))
253 tmpl.Execute(rw, data)
254
162 default: 255 default:
163 rw.Write([]byte("command not support")) 256 rw.Write([]byte("command not support"))
164 } 257 }
165 } else { 258 } else {
166 rw.Write([]byte("<html><head><title>beego admin dashboard</title></head><body>"))
167 rw.Write([]byte("ListConf support this command:<br>\n"))
168 rw.Write([]byte("1. <a href='?command=conf'>command=conf</a><br>\n"))
169 rw.Write([]byte("2. <a href='?command=router'>command=router</a><br>\n"))
170 rw.Write([]byte("3. <a href='?command=filter'>command=filter</a><br>\n"))
171 rw.Write([]byte("</body></html>"))
172 } 259 }
173 } 260 }
174 261
175 func printTree(rw http.ResponseWriter, t *Tree) { 262 func printTree(resultList *[][]string, t *Tree) {
176 for _, tr := range t.fixrouters { 263 for _, tr := range t.fixrouters {
177 printTree(rw, tr) 264 printTree(resultList, tr)
178 } 265 }
179 if t.wildcard != nil { 266 if t.wildcard != nil {
180 printTree(rw, t.wildcard) 267 printTree(resultList, t.wildcard)
181 } 268 }
182 for _, l := range t.leaves { 269 for _, l := range t.leaves {
183 if v, ok := l.runObject.(*controllerInfo); ok { 270 if v, ok := l.runObject.(*controllerInfo); ok {
184 if v.routerType == routerTypeBeego { 271 if v.routerType == routerTypeBeego {
185 fmt.Fprintln(rw, v.pattern, v.methods, v.controllerType.Name()) 272 var result = []string{
273 fmt.Sprintf(""),
274 fmt.Sprintf("%s", v.pattern),
275 fmt.Sprintf("%s", v.methods),
276 fmt.Sprintf("%s", v.controllerType),
277 }
278 *resultList = append(*resultList, result)
186 } else if v.routerType == routerTypeRESTFul { 279 } else if v.routerType == routerTypeRESTFul {
187 fmt.Fprintln(rw, v.pattern, v.methods) 280 var result = []string{
281 fmt.Sprintf(""),
282 fmt.Sprintf("%s", v.pattern),
283 fmt.Sprintf("%s", v.methods),
284 fmt.Sprintf(""),
285 }
286 *resultList = append(*resultList, result)
188 } else if v.routerType == routerTypeHandler { 287 } else if v.routerType == routerTypeHandler {
189 fmt.Fprintln(rw, v.pattern, "handler") 288 var result = []string{
289 fmt.Sprintf(""),
290 fmt.Sprintf("%s", v.pattern),
291 fmt.Sprintf(""),
292 fmt.Sprintf(""),
293 }
294 *resultList = append(*resultList, result)
190 } 295 }
191 } 296 }
192 } 297 }
...@@ -197,58 +302,106 @@ func printTree(rw http.ResponseWriter, t *Tree) { ...@@ -197,58 +302,106 @@ func printTree(rw http.ResponseWriter, t *Tree) {
197 func profIndex(rw http.ResponseWriter, r *http.Request) { 302 func profIndex(rw http.ResponseWriter, r *http.Request) {
198 r.ParseForm() 303 r.ParseForm()
199 command := r.Form.Get("command") 304 command := r.Form.Get("command")
305 data := make(map[interface{}]interface{})
306
307 var result bytes.Buffer
200 if command != "" { 308 if command != "" {
201 toolbox.ProcessInput(command, rw) 309 toolbox.ProcessInput(command, &result)
310 data["Content"] = result.String()
311 data["Title"] = command
312
313 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
314 tmpl = template.Must(tmpl.Parse(profillingTpl))
315 tmpl.Execute(rw, data)
202 } else { 316 } else {
203 rw.Write([]byte("<html><head><title>beego admin dashboard</title></head><body>"))
204 rw.Write([]byte("request url like '/prof?command=lookup goroutine'<br>\n"))
205 rw.Write([]byte("the command have below types:<br>\n"))
206 rw.Write([]byte("1. <a href='?command=lookup goroutine'>lookup goroutine</a><br>\n"))
207 rw.Write([]byte("2. <a href='?command=lookup heap'>lookup heap</a><br>\n"))
208 rw.Write([]byte("3. <a href='?command=lookup threadcreate'>lookup threadcreate</a><br>\n"))
209 rw.Write([]byte("4. <a href='?command=lookup block'>lookup block</a><br>\n"))
210 rw.Write([]byte("5. <a href='?command=start cpuprof'>start cpuprof</a><br>\n"))
211 rw.Write([]byte("6. <a href='?command=stop cpuprof'>stop cpuprof</a><br>\n"))
212 rw.Write([]byte("7. <a href='?command=get memprof'>get memprof</a><br>\n"))
213 rw.Write([]byte("8. <a href='?command=gc summary'>gc summary</a><br>\n"))
214 rw.Write([]byte("</body></html>"))
215 } 317 }
216 } 318 }
217 319
218 // Healthcheck is a http.Handler calling health checking and showing the result. 320 // Healthcheck is a http.Handler calling health checking and showing the result.
219 // it's in "/healthcheck" pattern in admin module. 321 // it's in "/healthcheck" pattern in admin module.
220 func healthcheck(rw http.ResponseWriter, req *http.Request) { 322 func healthcheck(rw http.ResponseWriter, req *http.Request) {
323 data := make(map[interface{}]interface{})
324
325 resultList := new([][]string)
326 var result = []string{
327 fmt.Sprintf("header"),
328 fmt.Sprintf("Name"),
329 fmt.Sprintf("Status"),
330 }
331 *resultList = append(*resultList, result)
332
221 for name, h := range toolbox.AdminCheckList { 333 for name, h := range toolbox.AdminCheckList {
222 if err := h.Check(); err != nil { 334 if err := h.Check(); err != nil {
223 fmt.Fprintf(rw, "%s : %s\n", name, err.Error()) 335 result = []string{
336 fmt.Sprintf("error"),
337 fmt.Sprintf("%s", name),
338 fmt.Sprintf("%s", err.Error()),
339 }
340
224 } else { 341 } else {
225 fmt.Fprintf(rw, "%s : ok\n", name) 342 result = []string{
343 fmt.Sprintf("success"),
344 fmt.Sprintf("%s", name),
345 fmt.Sprintf("OK"),
346 }
347
226 } 348 }
349 *resultList = append(*resultList, result)
227 } 350 }
351
352 data["Content"] = resultList
353 data["Title"] = "Health Check"
354 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
355 tmpl = template.Must(tmpl.Parse(healthCheckTpl))
356 tmpl.Execute(rw, data)
357
228 } 358 }
229 359
230 // TaskStatus is a http.Handler with running task status (task name, status and the last execution). 360 // TaskStatus is a http.Handler with running task status (task name, status and the last execution).
231 // it's in "/task" pattern in admin module. 361 // it's in "/task" pattern in admin module.
232 func taskStatus(rw http.ResponseWriter, req *http.Request) { 362 func taskStatus(rw http.ResponseWriter, req *http.Request) {
233 for tname, tk := range toolbox.AdminTaskList { 363 data := make(map[interface{}]interface{})
234 fmt.Fprintf(rw, "%s:%s:%s", tname, tk.GetStatus(), tk.GetPrev().String())
235 }
236 }
237 364
238 // RunTask is a http.Handler to run a Task from the "query string. 365 // Run Task
239 // the request url likes /runtask?taskname=sendmail.
240 func runTask(rw http.ResponseWriter, req *http.Request) {
241 req.ParseForm() 366 req.ParseForm()
242 taskname := req.Form.Get("taskname") 367 taskname := req.Form.Get("taskname")
243 if t, ok := toolbox.AdminTaskList[taskname]; ok { 368 if taskname != "" {
244 err := t.Run() 369
245 if err != nil { 370 if t, ok := toolbox.AdminTaskList[taskname]; ok {
246 fmt.Fprintf(rw, "%v", err) 371 err := t.Run()
372 if err != nil {
373 data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
374 }
375 data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is %s", taskname, t.GetStatus())}
376 } else {
377 data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)}
247 } 378 }
248 fmt.Fprintf(rw, "%s run success,Now the Status is %s", taskname, t.GetStatus())
249 } else {
250 fmt.Fprintf(rw, "there's no task which named:%s", taskname)
251 } 379 }
380
381 // List Tasks
382 resultList := new([][]string)
383 var result = []string{
384 fmt.Sprintf("header"),
385 fmt.Sprintf("Task Name"),
386 fmt.Sprintf("Task Spec"),
387 fmt.Sprintf("Task Function"),
388 }
389 *resultList = append(*resultList, result)
390 for tname, tk := range toolbox.AdminTaskList {
391 result = []string{
392 fmt.Sprintf(""),
393 fmt.Sprintf("%s", tname),
394 fmt.Sprintf("%s", tk.GetStatus()),
395 fmt.Sprintf("%s", tk.GetPrev().String()),
396 }
397 *resultList = append(*resultList, result)
398 }
399
400 data["Content"] = resultList
401 data["Title"] = "Tasks"
402 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
403 tmpl = template.Must(tmpl.Parse(tasksTpl))
404 tmpl.Execute(rw, data)
252 } 405 }
253 406
254 // adminApp is an http.HandlerFunc map used as beeAdminApp. 407 // adminApp is an http.HandlerFunc map used as beeAdminApp.
......
1 // Beego (http://beego.me/)
2 //
3 // @description beego is an open-source, high-performance web framework for the Go programming language.
4 //
5 // @link http://github.com/astaxie/beego for the canonical source repository
6 //
7 // @license http://github.com/astaxie/beego/blob/master/LICENSE
8 //
9 // @authors astaxie
10 package beego
11
12 var indexTpl = `
13 {{define "content"}}
14 <h1>Beego Admin Dashboard</h1>
15 <p>
16 For detail usage please check our document:
17 </p>
18 <p>
19 <a target="_blank" href="http://beego.me/docs/module/toolbox.md">Toolbox</a>
20 </p>
21 <p>
22 <a target="_blank" href="http://beego.me/docs/advantage/monitor.md">Live Monitor</a>
23 </p>
24 {{.Content}}
25 {{end}}`
26
27 var profillingTpl = `
28 {{define "content"}}
29 <h1>{{.Title}}</h1>
30 <pre>
31 {{.Content}}
32 </pre>
33 {{end}}`
34
35 var qpsTpl = `
36 {{define "content"}}
37 <h1>Requests statistics</h1>
38 <table class="table table-striped table-hover ">
39 {{range $i, $slice := .Content}}
40 <tr>
41 {{range $j, $elem := $slice}}
42 {{if eq $i 0}}
43 <th>
44 {{else}}
45 <td>
46 {{end}}
47 {{$elem}}
48 {{if eq $i 0}}
49 </th>
50 {{else}}
51 </td>
52 {{end}}
53 {{end}}
54
55 </tr>
56 {{end}}
57 </table>
58 {{end}}
59 `
60
61 var configTpl = `
62 {{define "content"}}
63 <h1>Configurations</h1>
64 <pre>
65 {{range $index, $elem := .Content}}
66 {{$index}}={{$elem}}
67 {{end}}
68 </pre>
69 {{end}}
70 `
71
72 var routerAndFilterTpl = `
73 {{define "content"}}
74
75 <h1>{{.Title}}</h1>
76 <table class="table table-striped table-hover ">
77 {{range $i, $slice := .Content}}
78 <tr>
79
80 {{ $header := index $slice 0}}
81 {{if eq "header" $header }}
82 {{range $j, $elem := $slice}}
83 {{if ne $j 0}}
84 <th>
85 {{$elem}}
86 </th>
87 {{end}}
88 {{end}}
89 {{else if eq "success" $header}}
90 {{range $j, $elem := $slice}}
91 {{if ne $j 0}}
92 <th class="success">
93 {{$elem}}
94 </th>
95 {{end}}
96 {{end}}
97 {{else}}
98 {{range $j, $elem := $slice}}
99 {{if ne $j 0}}
100 <td>
101 {{$elem}}
102 </td>
103 {{end}}
104 {{end}}
105 {{end}}
106
107 </tr>
108 {{end}}
109 </table>
110 {{end}}
111 `
112
113 var tasksTpl = `
114 {{define "content"}}
115
116 <h1>{{.Title}}</h1>
117
118 {{if .Message }}
119 {{ $messageType := index .Message 0}}
120 <p class="message
121 {{if eq "error" $messageType}}
122 bg-danger
123 {{else if eq "success" $messageType}}
124 bg-success
125 {{else}}
126 bg-warning
127 {{end}}
128 ">
129 {{index .Message 1}}
130 </p>
131 {{end}}
132
133
134 <table class="table table-striped table-hover ">
135 {{range $i, $slice := .Content}}
136 <tr>
137
138 {{ $header := index $slice 0}}
139 {{if eq "header" $header }}
140 {{range $j, $elem := $slice}}
141 {{if ne $j 0}}
142 <th>
143 {{$elem}}
144 </th>
145 {{end}}
146 {{end}}
147 <th>
148 Run Task
149 </th>
150 {{else}}
151 {{range $j, $elem := $slice}}
152 {{if ne $j 0}}
153 <td>
154 {{$elem}}
155 </td>
156 {{end}}
157 {{end}}
158 <td>
159 <a class="btn btn-primary btn-sm" href="/task?taskname={{index $slice 1}}">Run</a>
160 </td>
161 {{end}}
162
163 </tr>
164 {{end}}
165 </table>
166 {{end}}
167 `
168
169 var healthCheckTpl = `
170 {{define "content"}}
171
172 <h1>{{.Title}}</h1>
173 <table class="table table-striped table-hover ">
174 {{range $i, $slice := .Content}}
175
176 {{ $header := index $slice 0}}
177 {{if eq "header" $header }}
178 <tr>
179 {{range $j, $elem := $slice}}
180 {{if ne $j 0}}
181 <th>
182 {{$elem}}
183 </th>
184 {{end}}
185 {{end}}
186 </tr>
187 {{else}}
188 {{ if eq "success" $header}}
189 <tr class="success">
190 {{else if eq "error" $header}}
191 <tr class="danger">
192 {{else}}
193 <tr>
194 {{end}}
195 {{range $j, $elem := $slice}}
196 {{if ne $j 0}}
197 <td>
198 {{$elem}}
199 </td>
200 {{end}}
201 {{end}}
202 </tr>
203 {{end}}
204
205 {{end}}
206 </table>
207 {{end}}`
208
209 // The base dashboardTpl
210 var dashboardTpl = `
211 <!DOCTYPE html>
212 <html lang="en">
213 <head>
214 <!-- Meta, title, CSS, favicons, etc. -->
215 <meta charset="utf-8">
216 <meta http-equiv="X-UA-Compatible" content="IE=edge">
217 <meta name="viewport" content="width=device-width, initial-scale=1">
218
219 <title>
220
221 Welcome to Beego Admin Dashboard
222
223 </title>
224
225 <link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
226
227 <style type="text/css">
228 ul.nav li.dropdown:hover > ul.dropdown-menu {
229 display: block;
230 }
231 #logo {
232 width: 102px;
233 height: 32px;
234 margin-top: 5px;
235 }
236 .message {
237 padding: 15px;
238 }
239 </style>
240
241 </head>
242 <body>
243
244 <header class="navbar navbar-default navbar-static-top bs-docs-nav" id="top" role="banner">
245 <div class="container">
246 <div class="navbar-header">
247 <button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".bs-navbar-collapse">
248 <span class="sr-only">Toggle navigation</span>
249 <span class="icon-bar"></span>
250 <span class="icon-bar"></span>
251 <span class="icon-bar"></span>
252 </button>
253
254 <a href="/">
255 <img id="logo" src="data:image/png;base64,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"/>
256 </a>
257
258 </div>
259 <nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation">
260 <ul class="nav navbar-nav">
261 <li>
262 <a href="/qps">
263 Requests statistics
264 </a>
265 </li>
266 <li>
267
268 <li class="dropdown">
269 <a href="#" class="dropdown-toggle disabled" data-toggle="dropdown">Performance profiling<span class="caret"></span></a>
270 <ul class="dropdown-menu" role="menu">
271
272 <li><a href="/prof?command=lookup goroutine">lookup goroutine</a></li>
273 <li><a href="/prof?command=lookup heap">lookup heap</a></li>
274 <li><a href="/prof?command=lookup threadcreate">lookup threadcreate</a></li>
275 <li><a href="/prof?command=lookup block">lookup block</a></li>
276 <li><a href="/prof?command=start cpuprof">start cpuprof</a></li>
277 <li><a href="/prof?command=stop cpuprof">stop cpuprof</a></li>
278 <li><a href="/prof?command=get memprof">get memprof</a></li>
279 <li><a href="/prof?command=gc summary">gc summary</a></li>
280
281 </ul>
282 </li>
283
284 <li>
285 <a href="/healthcheck">
286 Healthcheck
287 </a>
288 </li>
289
290 <li>
291 <a href="/task" class="dropdown-toggle disabled" data-toggle="dropdown">Tasks</a>
292 </li>
293
294 <li class="dropdown">
295 <a href="#" class="dropdown-toggle disabled" data-toggle="dropdown">Config Status<span class="caret"></span></a>
296 <ul class="dropdown-menu" role="menu">
297 <li><a href="/listconf?command=conf">Configs</a></li>
298 <li><a href="/listconf?command=router">Routers</a></li>
299 <li><a href="/listconf?command=filter">Filters</a></li>
300 </ul>
301 </li>
302 </ul>
303 </nav>
304 </div>
305 </header>
306
307 <div class="container">
308 {{template "content" .}}
309 </div>
310
311 <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
312 <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
313
314 </body>
315 </html>
316 `
...@@ -11,7 +11,6 @@ package toolbox ...@@ -11,7 +11,6 @@ package toolbox
11 11
12 import ( 12 import (
13 "fmt" 13 "fmt"
14 "io"
15 "sync" 14 "sync"
16 "time" 15 "time"
17 ) 16 )
...@@ -79,17 +78,28 @@ func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController stri ...@@ -79,17 +78,28 @@ func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController stri
79 } 78 }
80 79
81 // put url statistics result in io.Writer 80 // put url statistics result in io.Writer
82 func (m *UrlMap) GetMap(rw io.Writer) { 81 func (m *UrlMap) GetMap() [][]string {
83 m.lock.RLock() 82 m.lock.RLock()
84 defer m.lock.RUnlock() 83 defer m.lock.RUnlock()
85 fmt.Fprintf(rw, "| % -50s| % -10s | % -16s | % -16s | % -16s | % -16s | % -16s |\n", "requestUrl", "method", "times", "used", "max used", "min used", "avg used") 84 resultLists := make([][]string, 0)
85
86 var result = []string{"requestUrl", "method", "times", "used", "max used", "min used", "avg used"}
87 resultLists = append(resultLists, result)
86 for k, v := range m.urlmap { 88 for k, v := range m.urlmap {
87 for kk, vv := range v { 89 for kk, vv := range v {
88 fmt.Fprintf(rw, "| % -50s| % -10s | % -16d | % -16s | % -16s | % -16s | % -16s |\n", k, 90 result := []string{
89 kk, vv.RequestNum, toS(vv.TotalTime), toS(vv.MaxTime), toS(vv.MinTime), toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum)), 91 fmt.Sprintf("% -50s", k),
90 ) 92 fmt.Sprintf("% -10s", kk),
93 fmt.Sprintf("% -16d", vv.RequestNum),
94 fmt.Sprintf("% -16s", toS(vv.TotalTime)),
95 fmt.Sprintf("% -16s", toS(vv.MaxTime)),
96 fmt.Sprintf("% -16s", toS(vv.MinTime)),
97 fmt.Sprintf("% -16s", toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum))),
98 }
99 resultLists = append(resultLists, result)
91 } 100 }
92 } 101 }
102 return resultLists
93 } 103 }
94 104
95 // global statistics data map 105 // global statistics data map
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!