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  }