| OLD | NEW |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. | 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package frontend | 5 package frontend |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "fmt" | 9 "fmt" |
| 10 "html/template" | 10 "html/template" |
| (...skipping 24 matching lines...) Expand all Loading... |
| 35 var funcMap = template.FuncMap{ | 35 var funcMap = template.FuncMap{ |
| 36 "humanDuration": humanDuration, | 36 "humanDuration": humanDuration, |
| 37 "parseRFC3339": parseRFC3339, | 37 "parseRFC3339": parseRFC3339, |
| 38 "linkify": linkify, | 38 "linkify": linkify, |
| 39 "linkifySet": linkifySet, | 39 "linkifySet": linkifySet, |
| 40 "obfuscateEmail": obfuscateEmail, | 40 "obfuscateEmail": obfuscateEmail, |
| 41 "localTime": localTime, | 41 "localTime": localTime, |
| 42 "shortHash": shortHash, | 42 "shortHash": shortHash, |
| 43 "startswith": strings.HasPrefix, | 43 "startswith": strings.HasPrefix, |
| 44 "sub": sub, | 44 "sub": sub, |
| 45 "consoleHeader": consoleHeader, | |
| 46 "pagedURL": pagedURL, | 45 "pagedURL": pagedURL, |
| 47 "formatTime": formatTime, | 46 "formatTime": formatTime, |
| 48 "percent": percent, | 47 "percent": percent, |
| 49 } | 48 } |
| 50 | 49 |
| 51 // localTime returns a <span> element with t in human format | 50 // localTime returns a <span> element with t in human format |
| 52 // that will be converted to local timezone in the browser. | 51 // that will be converted to local timezone in the browser. |
| 53 // Recommended usage: {{ .Date | localTime "N/A" }} | 52 // Recommended usage: {{ .Date | localTime "N/A" }} |
| 54 func localTime(ifZero string, t time.Time) template.HTML { | 53 func localTime(ifZero string, t time.Time) template.HTML { |
| 55 if t.IsZero() { | 54 if t.IsZero() { |
| 56 return template.HTML(template.HTMLEscapeString(ifZero)) | 55 return template.HTML(template.HTMLEscapeString(ifZero)) |
| 57 } | 56 } |
| 58 milliseconds := t.UnixNano() / 1e6 | 57 milliseconds := t.UnixNano() / 1e6 |
| 59 return template.HTML(fmt.Sprintf( | 58 return template.HTML(fmt.Sprintf( |
| 60 `<span class="local-time" data-timestamp="%d">%s</span>`, | 59 `<span class="local-time" data-timestamp="%d">%s</span>`, |
| 61 milliseconds, | 60 milliseconds, |
| 62 t.Format(time.RFC850))) | 61 t.Format(time.RFC850))) |
| 63 } | 62 } |
| 64 | 63 |
| 65 func consoleHeader(brs []resp.BuilderRef) template.HTML { | |
| 66 // First, split things into nice rows and find the max depth. | |
| 67 cat := make([][]string, len(brs)) | |
| 68 depth := 0 | |
| 69 for i, b := range brs { | |
| 70 cat[i] = b.Category | |
| 71 if len(cat[i]) > depth { | |
| 72 depth = len(cat[i]) | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 result := "" | |
| 77 for row := 0; row < depth; row++ { | |
| 78 result += "<tr><th></th>" | |
| 79 // "" is the first node, " " is an empty node. | |
| 80 current := "" | |
| 81 colspan := 0 | |
| 82 for _, br := range cat { | |
| 83 colspan++ | |
| 84 var s string | |
| 85 if row >= len(br) { | |
| 86 s = " " | |
| 87 } else { | |
| 88 s = br[row] | |
| 89 } | |
| 90 if s != current || current == " " { | |
| 91 if current != "" || current == " " { | |
| 92 result += fmt.Sprintf(`<th colspan="%d">
%s</th>`, colspan, current) | |
| 93 colspan = 0 | |
| 94 } | |
| 95 current = s | |
| 96 } | |
| 97 } | |
| 98 if colspan != 0 { | |
| 99 result += fmt.Sprintf(`<th colspan="%d">%s</th>`, colspa
n, current) | |
| 100 } | |
| 101 result += "</tr>" | |
| 102 } | |
| 103 | |
| 104 // Last row: The actual builder shortnames. | |
| 105 result += "<tr><th></th>" | |
| 106 for _, br := range brs { | |
| 107 result += fmt.Sprintf("<th>%s</th>", br.ShortName) | |
| 108 } | |
| 109 result += "</tr>" | |
| 110 return template.HTML(result) | |
| 111 } | |
| 112 | |
| 113 // humanDuration translates d into a human readable string of x units y units, | 64 // humanDuration translates d into a human readable string of x units y units, |
| 114 // where x and y could be in days, hours, minutes, or seconds, whichever is the | 65 // where x and y could be in days, hours, minutes, or seconds, whichever is the |
| 115 // largest. | 66 // largest. |
| 116 func humanDuration(d time.Duration) string { | 67 func humanDuration(d time.Duration) string { |
| 117 t := int64(d.Seconds()) | 68 t := int64(d.Seconds()) |
| 118 day := t / 86400 | 69 day := t / 86400 |
| 119 hr := (t % 86400) / 3600 | 70 hr := (t % 86400) / 3600 |
| 120 | 71 |
| 121 if day > 0 { | 72 if day > 0 { |
| 122 if hr != 0 { | 73 if hr != 0 { |
| (...skipping 240 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 c.Context = withRequest(c.Context, c.Request) | 314 c.Context = withRequest(c.Context, c.Request) |
| 364 next(c) | 315 next(c) |
| 365 } | 316 } |
| 366 | 317 |
| 367 func getRequest(c context.Context) *http.Request { | 318 func getRequest(c context.Context) *http.Request { |
| 368 if req, ok := c.Value(&requestKey).(*http.Request); ok { | 319 if req, ok := c.Value(&requestKey).(*http.Request); ok { |
| 369 return req | 320 return req |
| 370 } | 321 } |
| 371 panic("No http.request found in context") | 322 panic("No http.request found in context") |
| 372 } | 323 } |
| OLD | NEW |