github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/log-results-grid.js (about) 1 /* 2 Copyright 2023. 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 'use strict'; 18 19 let cellEditingClass = ''; 20 21 class ReadOnlyCellEditor { 22 // gets called once before the renderer is used 23 init(params) { 24 // create the cell 25 this.eInput = document.createElement('textarea'); 26 cellEditingClass = params.rowIndex%2===0 ? 'even-popup-textarea' : 'odd-popup-textarea' 27 this.eInput.classList.add(cellEditingClass); 28 this.eInput.readOnly = true; 29 this.eInput.cols = params.cols; 30 this.eInput.rows = params.rows; 31 this.eInput.maxLength = params.maxLength; 32 this.eInput.value = params.value; 33 } 34 // gets called once when grid ready to insert the element 35 getGui() { 36 return this.eInput; 37 } 38 // returns the new value after editing 39 getValue() { 40 return this.eInput.value; 41 } 42 isPopup() { 43 return true 44 } 45 refresh(params) { 46 return true; 47 } 48 destroy() { 49 this.eInput.classList.remove(cellEditingClass); 50 } 51 } 52 53 const cellEditorParams = (params) => { 54 const jsonLog = JSON.stringify(JSON.unflatten(params.data), null, 2) 55 return { 56 value :jsonLog, 57 cols: 100, 58 rows: 10 59 }; 60 }; 61 // initial columns 62 let logsColumnDefs = [ 63 { 64 field: "timestamp", 65 headerName: "timestamp", 66 editable: true, 67 cellEditor: ReadOnlyCellEditor, 68 cellEditorPopup: true, 69 cellEditorPopupPosition: 'under', 70 cellRenderer: (params) => { 71 return moment(params.value).format(timestampDateFmt); 72 }, 73 cellEditorParams:cellEditorParams, 74 maxWidth: 216, 75 minWidth: 216, 76 }, 77 { 78 field: "logs", 79 headerName: "logs", 80 minWidth: 1128, 81 cellRenderer: (params) => { 82 let logString = ''; 83 let counter = 0; 84 if (updatedSelFieldList){ 85 selectedFieldsList = _.intersection(selectedFieldsList, availColNames); 86 }else{ 87 selectedFieldsList = _.union(selectedFieldsList, availColNames); 88 } 89 90 if (selectedFieldsList.length != 0) { 91 availColNames.forEach((colName, index) => { 92 if(selectedFieldsList.includes(colName)){ 93 $(`.toggle-${string2Hex(colName)}`).addClass('active'); 94 } else { 95 $(`.toggle-${string2Hex(colName)}`).removeClass('active'); 96 } 97 }); 98 } else { 99 selectedFieldsList = []; 100 availColNames.forEach((colName, index) => { 101 $(`.toggle-${string2Hex(colName)}`).addClass('active'); 102 selectedFieldsList.push(colName); 103 }); 104 } 105 _.forEach(params.data, (value, key) => { 106 let colSep = counter > 0 ? '<span class="col-sep"> | </span>' : ''; 107 if (key != 'logs' && selectedFieldsList.includes(key)) { 108 logString += `<span class="cname-hide-${string2Hex(key)}">${colSep}${key}=`+ JSON.stringify(JSON.unflatten(value), null, 2)+`</span>`; 109 110 counter++; 111 } 112 if (key === 'timestamp'){ 113 logString += `<span class="cname-hide-${string2Hex(key)}">${colSep}${key}=${value}</span>`; 114 } 115 }); 116 return logString; 117 }, 118 } 119 ]; 120 121 // initial dataset 122 let logsRowData = []; 123 let allLiveTailColumns = []; 124 let total_liveTail_searched = 0; 125 // let the grid know which columns and what data to use 126 const gridOptions = { 127 columnDefs: logsColumnDefs, 128 rowData: logsRowData, 129 animateRows: true, 130 readOnlyEdit: true, 131 singleClickEdit: true, 132 headerHeight:32, 133 defaultColDef: { 134 initialWidth: 100, 135 sortable: true, 136 resizable: true, 137 minWidth: 200, 138 icons: { 139 sortAscending: '<i class="fa fa-sort-alpha-down"/>', 140 sortDescending: '<i class="fa fa-sort-alpha-up"/>', 141 }, 142 }, 143 icons: { 144 sortAscending: '<i class="fa fa-sort-alpha-down"/>', 145 sortDescending: '<i class="fa fa-sort-alpha-up"/>', 146 }, 147 enableCellTextSelection: true, 148 suppressScrollOnNewData: true, 149 suppressAnimationFrame: true, 150 suppressFieldDotNotation: true, 151 onBodyScroll(evt){ 152 if(evt.direction === 'vertical' && canScrollMore == true){ 153 let diff = logsRowData.length - evt.api.getLastDisplayedRow(); 154 // if we're less than 1 items from the end...fetch more data 155 if(diff <= 5) { 156 // Show loading indicator 157 showLoadingIndicator(); 158 159 let scrollingTrigger = true; 160 data = getSearchFilter(false, scrollingTrigger); 161 if (data && data.searchText == "error") { 162 alert("Error"); 163 hideLoadingIndicator(); // Hide loading indicator on error 164 return; 165 } 166 doSearch(data).then(() => { 167 hideLoadingIndicator(); // Hide loading indicator once data is fetched 168 }); 169 } 170 } 171 }, 172 overlayLoadingTemplate: '<div class="ag-overlay-loading-center"><div class="loading-icon"></div><div class="loading-text">Loading...</div></div>', 173 }; 174 175 function showLoadingIndicator() { 176 gridOptions.api.showLoadingOverlay(); 177 } 178 179 function hideLoadingIndicator() { 180 gridOptions.api.hideOverlay(); 181 } 182 183 const myCellRenderer= (params) => { 184 let logString = ''; 185 if (typeof params.data === 'object' && params.data !== null){ 186 let value = params.data[params.colName] 187 if (value !== ""){ 188 if (Array.isArray(value)){ 189 logString= JSON.stringify(JSON.unflatten(value), null, 2) 190 }else{ 191 logString= value 192 } 193 } 194 } 195 return logString; 196 }; 197 198 let gridDiv = null; 199 200 function renderLogsGrid(columnOrder, hits){ 201 if (sortByTimestampAtDefault) { 202 logsColumnDefs[0].sort = "desc"; 203 }else { 204 logsColumnDefs[0].sort = undefined; 205 } 206 if (gridDiv == null){ 207 gridDiv = document.querySelector('#LogResultsGrid'); 208 new agGrid.Grid(gridDiv, gridOptions); 209 } 210 211 let logview = getLogView(); 212 213 let cols = columnOrder.map((colName, index) => { 214 let hideCol = false; 215 if (index >= defaultColumnCount) { 216 hideCol = true; 217 } 218 219 if (logview != 'single-line' && colName == 'logs'){ 220 hideCol = true; 221 } 222 223 if (index > 1) { 224 if (selectedFieldsList.indexOf(colName) != -1){ 225 hideCol = true; 226 } else{ 227 hideCol = false; 228 } 229 } 230 return { 231 field: colName, 232 hide: hideCol, 233 headerName: colName, 234 cellRenderer: myCellRenderer, 235 cellRendererParams : { 236 colName: colName 237 } 238 }; 239 }); 240 if(hits.length != 0){ 241 logsRowData = _.concat(hits, logsRowData); 242 if (liveTailState && logsRowData.length > 500){ 243 logsRowData = logsRowData.slice(0, 500); 244 } 245 246 } 247 logsColumnDefs = _.chain(logsColumnDefs).concat(cols).uniqBy('field').value(); 248 gridOptions.api.setColumnDefs(logsColumnDefs); 249 250 const allColumnIds = []; 251 gridOptions.columnApi.getColumns().forEach((column) => { 252 allColumnIds.push(column.getId()); 253 }); 254 gridOptions.columnApi.autoSizeColumns(allColumnIds, false); 255 gridOptions.api.setRowData(logsRowData); 256 257 switch (logview){ 258 case 'single-line': 259 logOptionSingleHandler(); 260 break; 261 case 'multi-line': 262 logOptionMultiHandler(); 263 break; 264 case 'table': 265 logOptionTableHandler(); 266 break; 267 } 268 } 269 270 function updateColumns() { 271 // Always show timestamp 272 gridOptions.columnApi.setColumnVisible("timestamp", true); 273 let isAnyColActive = false; 274 availColNames.forEach((colName, index) => { 275 if ($(`.toggle-${string2Hex(colName)}`).hasClass('active')) { 276 isAnyColActive = true; 277 gridOptions.columnApi.setColumnVisible(colName, true); 278 } else { 279 gridOptions.columnApi.setColumnVisible(colName, false); 280 } 281 }); 282 283 if (isAnyColActive) { 284 // Always hide logs column if we have some fields selected 285 gridOptions.columnApi.setColumnVisible("logs", false); 286 } 287 gridOptions.api.sizeColumnsToFit(); 288 } 289 290 function getLogView(){ 291 let logview = Cookies.get('log-view') || 'table'; 292 return logview 293 } 294 295 JSON.unflatten = function (data) { 296 "use strict"; 297 if (Object(data) !== data || Array.isArray(data)) return data; 298 let regex = /\.?([^.\[\]]+)|\[(\d+)\]/g, 299 resultholder = {}; 300 for (let p in data) { 301 let cur = resultholder, 302 prop = "", 303 m; 304 while (m = regex.exec(p)) { 305 cur = cur[prop] || (cur[prop] = (m[2] ? [] : {})); 306 prop = m[2] || m[1]; 307 } 308 cur[prop] = data[p]; 309 } 310 return resultholder[""] || resultholder; 311 };