github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/server/html_templates.go (about) 1 package server 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "html/template" 7 "math/big" 8 "net/http" 9 "runtime/debug" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/cryptohub-digital/blockbook-fork/api" 15 "github.com/cryptohub-digital/blockbook-fork/common" 16 "github.com/golang/glog" 17 ) 18 19 type tpl int 20 21 const ( 22 noTpl = tpl(iota) 23 errorTpl 24 errorInternalTpl 25 ) 26 27 // htmlTemplateHandler is a handle to public http server 28 type htmlTemplates[TD any] struct { 29 metrics *common.Metrics 30 templates []*template.Template 31 debug bool 32 newTemplateData func(r *http.Request) *TD 33 newTemplateDataWithError func(error *api.APIError, r *http.Request) *TD 34 parseTemplates func() []*template.Template 35 postHtmlTemplateHandler func(data *TD, w http.ResponseWriter, r *http.Request) 36 } 37 38 func (s *htmlTemplates[TD]) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TD, error)) func(w http.ResponseWriter, r *http.Request) { 39 handlerName := getFunctionName(handler) 40 return func(w http.ResponseWriter, r *http.Request) { 41 var t tpl 42 var data *TD 43 var err error 44 defer func() { 45 if e := recover(); e != nil { 46 glog.Error(handlerName, " recovered from panic: ", e) 47 debug.PrintStack() 48 t = errorInternalTpl 49 if s.debug { 50 data = s.newTemplateDataWithError(&api.APIError{Text: fmt.Sprint("Internal server error: recovered from panic ", e)}, r) 51 } else { 52 data = s.newTemplateDataWithError(&api.APIError{Text: "Internal server error"}, r) 53 } 54 } 55 // noTpl means the handler completely handled the request 56 if t != noTpl { 57 w.Header().Set("Content-Type", "text/html; charset=utf-8") 58 // return 500 Internal Server Error with errorInternalTpl 59 if t == errorInternalTpl { 60 w.WriteHeader(http.StatusInternalServerError) 61 } 62 if err := s.templates[t].ExecuteTemplate(w, "base.html", data); err != nil { 63 glog.Error(err) 64 } 65 } 66 if s.metrics != nil { 67 s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec() 68 } 69 }() 70 if s.metrics != nil { 71 s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc() 72 } 73 if s.debug { 74 // reload templates on each request 75 // to reflect changes during development 76 s.templates = s.parseTemplates() 77 } 78 t, data, err = handler(w, r) 79 if err != nil || (data == nil && t != noTpl) { 80 t = errorInternalTpl 81 if apiErr, ok := err.(*api.APIError); ok { 82 data = s.newTemplateDataWithError(apiErr, r) 83 if apiErr.Public { 84 t = errorTpl 85 } 86 } else { 87 if err != nil { 88 glog.Error(handlerName, " error: ", err) 89 } 90 if s.debug { 91 data = s.newTemplateDataWithError(&api.APIError{Text: fmt.Sprintf("Internal server error: %v, data %+v", err, data)}, r) 92 } else { 93 data = s.newTemplateDataWithError(&api.APIError{Text: "Internal server error"}, r) 94 } 95 } 96 } 97 if s.postHtmlTemplateHandler != nil { 98 s.postHtmlTemplateHandler(data, w, r) 99 } 100 101 } 102 } 103 104 func relativeTimeUnit(d int64) string { 105 var u string 106 if d < 60 { 107 if d == 1 { 108 u = " sec" 109 } else { 110 u = " secs" 111 } 112 } else if d < 3600 { 113 d /= 60 114 if d == 1 { 115 u = " min" 116 } else { 117 u = " mins" 118 } 119 } else if d < 3600*24 { 120 d /= 3600 121 if d == 1 { 122 u = " hour" 123 } else { 124 u = " hours" 125 } 126 } else { 127 d /= 3600 * 24 128 if d == 1 { 129 u = " day" 130 } else { 131 u = " days" 132 } 133 } 134 return strconv.FormatInt(d, 10) + u 135 } 136 137 func relativeTime(d int64) string { 138 r := relativeTimeUnit(d) 139 if d > 3600*24 { 140 d = d % (3600 * 24) 141 if d >= 3600 { 142 r += " " + relativeTimeUnit(d) 143 } 144 } else if d > 3600 { 145 d = d % 3600 146 if d >= 60 { 147 r += " " + relativeTimeUnit(d) 148 } 149 } 150 return r 151 } 152 153 func unixTimeSpan(ut int64) template.HTML { 154 t := time.Unix(ut, 0) 155 return timeSpan(&t) 156 } 157 158 var timeNow = time.Now 159 160 func timeSpan(t *time.Time) template.HTML { 161 if t == nil { 162 return "" 163 } 164 u := t.Unix() 165 if u <= 0 { 166 return "" 167 } 168 d := timeNow().Unix() - u 169 f := t.UTC().Format("2006-01-02 15:04:05") 170 if d < 0 { 171 return template.HTML(f) 172 } 173 r := relativeTime(d) 174 return template.HTML(`<span tt="` + f + `">` + r + " ago</span>") 175 } 176 177 func toJSON(data interface{}) string { 178 json, err := json.Marshal(data) 179 if err != nil { 180 return "" 181 } 182 return string(json) 183 } 184 185 func formatAmountWithDecimals(a *api.Amount, d int) string { 186 if a == nil { 187 return "0" 188 } 189 return a.DecimalString(d) 190 } 191 192 func appendAmountSpan(rv *strings.Builder, class, amount, shortcut, txDate string) { 193 rv.WriteString(`<span`) 194 if class != "" { 195 rv.WriteString(` class="`) 196 rv.WriteString(class) 197 rv.WriteString(`"`) 198 } 199 if txDate != "" { 200 rv.WriteString(` tm="`) 201 rv.WriteString(txDate) 202 rv.WriteString(`"`) 203 } 204 rv.WriteString(">") 205 i := strings.IndexByte(amount, '.') 206 if i < 0 { 207 appendSeparatedNumberSpans(rv, amount, "nc") 208 } else { 209 appendSeparatedNumberSpans(rv, amount[:i], "nc") 210 rv.WriteString(`.`) 211 rv.WriteString(`<span class="amt-dec">`) 212 appendLeftSeparatedNumberSpans(rv, amount[i+1:], "ns") 213 rv.WriteString("</span>") 214 } 215 if shortcut != "" { 216 rv.WriteString(" ") 217 rv.WriteString(shortcut) 218 } 219 rv.WriteString("</span>") 220 } 221 222 func appendAmountSpanBitcoinType(rv *strings.Builder, class, amount, shortcut, txDate string) { 223 if amount == "0" { 224 appendAmountSpan(rv, class, amount, shortcut, txDate) 225 return 226 } 227 rv.WriteString(`<span`) 228 if class != "" { 229 rv.WriteString(` class="`) 230 rv.WriteString(class) 231 rv.WriteString(`"`) 232 } 233 if txDate != "" { 234 rv.WriteString(` tm="`) 235 rv.WriteString(txDate) 236 rv.WriteString(`"`) 237 } 238 rv.WriteString(">") 239 i := strings.IndexByte(amount, '.') 240 var decimals string 241 if i < 0 { 242 appendSeparatedNumberSpans(rv, amount, "nc") 243 decimals = "00000000" 244 } else { 245 appendSeparatedNumberSpans(rv, amount[:i], "nc") 246 decimals = amount[i+1:] + "00000000" 247 } 248 rv.WriteString(`.`) 249 rv.WriteString(`<span class="amt-dec">`) 250 rv.WriteString(decimals[:2]) 251 rv.WriteString(`<span class="ns">`) 252 rv.WriteString(decimals[2:5]) 253 rv.WriteString("</span>") 254 rv.WriteString(`<span class="ns">`) 255 rv.WriteString(decimals[5:8]) 256 rv.WriteString("</span>") 257 rv.WriteString("</span>") 258 if shortcut != "" { 259 rv.WriteString(" ") 260 rv.WriteString(shortcut) 261 } 262 rv.WriteString("</span>") 263 } 264 265 func appendAmountWrapperSpan(rv *strings.Builder, primary, symbol, classes string) { 266 rv.WriteString(`<span class="amt`) 267 if classes != "" { 268 rv.WriteString(` `) 269 rv.WriteString(classes) 270 } 271 rv.WriteString(`" cc="`) 272 rv.WriteString(primary) 273 rv.WriteString(" ") 274 rv.WriteString(symbol) 275 rv.WriteString(`">`) 276 } 277 278 func formatInt(i int) template.HTML { 279 return formatInt64(int64(i)) 280 } 281 282 func formatUint32(i uint32) template.HTML { 283 return formatInt64(int64(i)) 284 } 285 286 func appendSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) { 287 if len(s) > 0 && s[0] == '-' { 288 s = s[1:] 289 rv.WriteByte('-') 290 } 291 t := (len(s) - 1) / 3 292 if t <= 0 { 293 rv.WriteString(s) 294 } else { 295 t *= 3 296 rv.WriteString(s[:len(s)-t]) 297 for i := len(s) - t; i < len(s); i += 3 { 298 rv.WriteString(`<span class="`) 299 rv.WriteString(separatorClass) 300 rv.WriteString(`">`) 301 rv.WriteString(s[i : i+3]) 302 rv.WriteString("</span>") 303 } 304 } 305 } 306 307 func appendLeftSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) { 308 l := len(s) 309 if l <= 3 { 310 rv.WriteString(s) 311 } else { 312 rv.WriteString(s[:3]) 313 for i := 3; i < len(s); i += 3 { 314 rv.WriteString(`<span class="`) 315 rv.WriteString(separatorClass) 316 rv.WriteString(`">`) 317 e := i + 3 318 if e > l { 319 e = l 320 } 321 rv.WriteString(s[i:e]) 322 rv.WriteString("</span>") 323 } 324 } 325 } 326 327 func formatInt64(i int64) template.HTML { 328 s := strconv.FormatInt(i, 10) 329 var rv strings.Builder 330 appendSeparatedNumberSpans(&rv, s, "ns") 331 return template.HTML(rv.String()) 332 } 333 334 func formatBigInt(i *big.Int) template.HTML { 335 if i == nil { 336 return "" 337 } 338 s := i.String() 339 var rv strings.Builder 340 appendSeparatedNumberSpans(&rv, s, "ns") 341 return template.HTML(rv.String()) 342 }