vitess.io/vitess@v0.16.2/go/vt/logz/logz_utils.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package logz provides an infrastructure to expose a list of entries as 18 // a sortable table on a webpage. 19 // 20 // It is used by many internal vttablet pages e.g. /queryz, /querylogz, /schemaz 21 // /livequeryz or /txlogz. 22 // 23 // See tabletserver/querylogz.go for an example how to use it. 24 package logz 25 26 import ( 27 "bytes" 28 "net/http" 29 ) 30 31 // StartHTMLTable writes the start of a logz-style table to an HTTP response. 32 func StartHTMLTable(w http.ResponseWriter) { 33 w.Write([]byte(`<!DOCTYPE html> 34 <style type="text/css"> 35 table.gridtable { 36 font-family: verdana,arial,sans-serif; 37 font-size:11px; 38 border-width: 1px; 39 border-collapse: collapse; 40 table-layout:fixed; 41 overflow: hidden; 42 } 43 table.gridtable th { 44 border-width: 1px; 45 padding: 8px; 46 border-style: solid; 47 background-color: #dedede; 48 white-space: nowrap; 49 } 50 table.gridtable tr.low { 51 background-color: #f0f0f0; 52 } 53 table.gridtable tr.medium { 54 background-color: #ffcc00; 55 } 56 table.gridtable tr.high { 57 background-color: #ff3300; 58 } 59 table.gridtable tr.error { 60 background-color: #00ddff; 61 } 62 table.gridtable td { 63 border-width: 1px; 64 padding: 4px; 65 border-style: solid; 66 } 67 table.gridtable th { 68 padding-left: 2em; 69 padding-right: 2em; 70 } 71 table.gridtable thead th { 72 cursor: pointer; 73 } 74 75 table.gridtable th.descending:before { 76 content: "▲"; 77 float: left; 78 } 79 table.gridtable th.ascending:before { 80 content: "▼"; 81 float: left; 82 } 83 </style> 84 85 <table class="gridtable"> 86 `)) 87 } 88 89 // EndHTMLTable writes the end of a logz-style table to an HTTP response. 90 func EndHTMLTable(w http.ResponseWriter) { 91 w.Write([]byte(` 92 </table> 93 <script type="text/javascript"> 94 function wrapInner(parent, element, style) { 95 96 const wrapper = document.createElement(element); 97 wrapper.style = style 98 99 const div = parent.appendChild(wrapper); 100 101 while(parent.firstChild !== wrapper) { 102 wrapper.appendChild(parent.firstChild); 103 } 104 } 105 106 function sortableByColumn(element) { 107 const body = element.querySelector('tbody') 108 const header = element.querySelector('thead') 109 110 const contents = (element, i) => { 111 const data = element.querySelectorAll("td")[i].innerText.toLowerCase(); 112 if (data == "n/a") { 113 return -1; 114 } 115 116 var asNumber = parseFloat(data); 117 if (data.slice(-1) == "s" && !isNaN(asNumber) && data.slice(0, -1) == asNumber) { 118 // Return durations (e.g. "11.2s") always as number. 119 return asNumber; 120 } 121 return data == asNumber ? asNumber : data; 122 } 123 124 element.querySelectorAll('thead tr th').forEach((th, index) => { 125 wrapInner(th, "div", "overflow: auto; display: inline-block;"); 126 let direction = -1; 127 128 th.onclick=() => { 129 direction *= -1; 130 const headerCells = header.querySelectorAll('th'); 131 headerCells.forEach((hc) => { 132 hc.classList.remove('ascending', 'descending'); 133 }) 134 th.classList.add(direction > 0? 'ascending' : 'descending'); 135 const rows = body.querySelectorAll('tr'); 136 137 const sortedRows = Array.from(rows).sort(function(left, right) { 138 var cl = contents(left, index); 139 var cr = contents(right, index); 140 if (cl === cr) { 141 return 0 142 } else { 143 return contents(left, index) > contents(right, index)? direction : -direction; 144 } 145 }); 146 147 // Remove original rows 148 rows.forEach(row => row.remove()); 149 150 // Add new sorted rows 151 sortedRows.forEach(row => { 152 body.appendChild(row); 153 }); 154 } 155 }) 156 } 157 158 // Execute the function when the DOM loads 159 (function() { 160 const table = document.querySelector('table'); 161 sortableByColumn(table) 162 })(); 163 </script> 164 `)) 165 } 166 167 // Wrappable inserts zero-width whitespaces to make 168 // the string wrappable. 169 func Wrappable(in string) string { 170 buf := bytes.NewBuffer(nil) 171 for _, ch := range in { 172 buf.WriteRune(ch) 173 if ch == ',' || ch == ')' { 174 // zero-width whitespace 175 buf.WriteRune('\u200B') 176 } 177 } 178 return buf.String() 179 }