github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/metrics.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 var lineChart; 20 21 $(document).ready(() => { 22 23 let stDate = "now-1h"; 24 let endDate = "now"; 25 datePickerHandler(stDate, endDate, stDate); 26 27 setupMetricsEventHandlers(); 28 29 if (Cookies.get('theme')) { 30 theme = Cookies.get('theme'); 31 $('body').attr('data-theme', theme); 32 } 33 $('.theme-btn').on('click', themePickerHandler); 34 35 $("#info-icon").tooltip({ 36 delay: { show: 0, hide: 300 }, 37 trigger: 'click' 38 }); 39 40 $('#info-icon').on('click', function (e) { 41 $('#info-icon').tooltip('show'); 42 }); 43 44 $(document).mouseup(function (e) { 45 if ($(e.target).closest(".tooltip-inner").length === 0) { 46 $('#info-icon').tooltip('hide'); 47 } 48 }); 49 }); 50 51 // Show clear button when there's input 52 $("#metrics-input").on("input", function() { 53 if ($(this).val().trim() !== "") { 54 $("#clearMetricsInput").show(); 55 } else { 56 $("#clearMetricsInput").hide(); 57 } 58 }); 59 60 // Clear input when the clear button is clicked 61 $("#clearMetricsInput").click(function() { 62 $("#metrics-input").val("").focus(); 63 $(this).hide(); 64 }); 65 66 function setupMetricsEventHandlers() { 67 $('#filter-metrics-input').on('keyup', filterMetricsInputHandler); 68 $('#run-metrics-query-btn').on('click', runMetricsFilterBtnHandler); 69 $(document).on('keyup', runMetricsFilterBtnHandler); 70 71 $('#date-picker-btn').on('show.bs.dropdown', showDatePickerHandler); 72 $('#date-picker-btn').on('hide.bs.dropdown', hideDatePickerHandler); 73 $('#reset-timepicker').on('click', resetDatePickerHandler); 74 75 $('#date-start').on('change', getStartDateHandler); 76 $('#date-end').on('change', getEndDateHandler); 77 78 $('#time-start').on('change', getStartTimeHandler); 79 $('#time-end').on('change', getEndTimeHandler); 80 $('#customrange-btn').on('click', customRangeHandler); 81 82 $('.range-item').on('click', rangeItemHandler) 83 84 $('#corner-popup').on('click', '.corner-btn-close', function(){ 85 hideError(); 86 $("#metrics-input").val(''); 87 $("#metrics-graph-container").show(); 88 if (lineChart !== undefined) { 89 lineChart.destroy(); 90 $('#metrics-legends').empty(); 91 } 92 $('#metrics-legends').empty(); 93 $('.metrics-response').hide(); 94 lineChart.destroy(); 95 $('#metrics-legends').empty(); 96 $('.metrics-response').hide(); 97 }); 98 99 } 100 101 function runMetricsFilterBtnHandler(evt) { 102 if (evt.keyCode === 13 || evt.type == "click") { 103 $('.popover').hide(); 104 evt.preventDefault(); 105 if ($('#run-metrics-query-btn').text() === "Run Query") { 106 data = getMetricsSearchFilter(false, false); 107 doMetricsSearch(); 108 } else { 109 data = getMetricsSearchFilter(false, false); 110 doCancel(data); 111 } 112 $('#daterangepicker').hide(); 113 } 114 } 115 116 function filterMetricsInputHandler(evt) { 117 evt.preventDefault(); 118 119 if (evt.keyCode === 13 && $('#run-metrics-query-btn').text() === "Run Query") { 120 data = getMetricsSearchFilter(false, false); 121 doMetricsSearch(); 122 } 123 } 124 125 function getMetricsSearchFilter(skipPushState, scrollingTrigger) { 126 let filterValue = $('#metrics-input').val().trim() || '*'; 127 let endDate = filterEndDate || "now"; 128 let stDate = filterStartDate || "now-15m"; 129 130 if (!isNaN(stDate)) { 131 stDate = Number(stDate); 132 endDate = Number(endDate); 133 datePickerHandler(stDate, endDate, "custom"); 134 loadCustomDateTimeFromEpoch(stDate, endDate); 135 } else if (stDate !== "now-15m") { 136 datePickerHandler(stDate, endDate, stDate); 137 } else { 138 datePickerHandler(stDate, endDate, ""); 139 } 140 141 addQSParm("query", filterValue); 142 addQSParm("start", stDate); 143 addQSParm("end", endDate); 144 145 window.history.pushState({ path: myUrl }, '', myUrl); 146 147 if (scrollingTrigger) { 148 sFrom = scrollFrom; 149 } 150 151 return { 152 'query': filterValue, 153 'start': stDate.toString(), 154 'end': endDate.toString(), 155 }; 156 } 157 158 function doMetricsSearch() { 159 let startTime = (new Date()).getTime(); 160 let data = getMetricsSearchFilter(); 161 $('body').css('cursor', 'progress'); 162 $("#run-filter-btn").html(" ").attr("disabled", true); 163 $("#run-filter-btn").removeClass("cancel-search"); 164 $("#query-builder-btn").html(" ").attr("disabled", true); 165 $("#query-builder-btn").removeClass("cancel-search"); 166 $.ajax({ 167 method: 'post', 168 url: 'promql/api/ui/query', 169 headers: { 170 'Content-Type': 'application/json; charset=utf-8', 171 'Accept': '*/*' 172 }, 173 crossDomain: true, 174 dataType: 'json', 175 data: JSON.stringify(data) 176 }) 177 .then((res) => processSearchResult(res, startTime)) 178 .catch((res) => processSearchError(res)); 179 } 180 181 function processSearchResult(res, startTime) { 182 if (res.aggStats && Object.keys(res.aggStats).length === 0) { 183 processNoResults(); 184 } else { 185 hideError(); 186 renderResponseTime(startTime); 187 var seriesArray = []; 188 var label = []; 189 $.each(res, function (key, value) { 190 var series = value; 191 $.each(series, function (key, value) { 192 seriesArray.push({ seriesName: key, values: value }); 193 label = []; 194 $.each(value, function (k, v) { 195 label.push(k); 196 }) 197 }) 198 }) 199 var labels = label; 200 201 $('body').css('cursor', 'default'); 202 $("#run-filter-btn").html(" ").attr("disabled", false); 203 $("#run-filter-btn").removeClass("cancel-search"); 204 $("#query-builder-btn").html(" ").attr("disabled", false); 205 $("#query-builder-btn").removeClass("cancel-search"); 206 if (lineChart !== undefined) { 207 lineChart.destroy(); 208 $('#metrics-legends').empty(); 209 } 210 updateContainers(); 211 displayTable(res); 212 lineChart = displayGraph(seriesArray, labels, -1); 213 return lineChart; 214 } 215 } 216 217 function renderResponseTime(startTime) { 218 $('.metrics-response').show(); 219 let responseTime = (new Date()).getTime() - startTime; 220 $('.metrics-response').html(`Response: <span id="response-time">${responseTime} ms</span>`); 221 } 222 223 function displayGraph(seriesArray, labels, flag) { 224 const colors = createColorsArray(); 225 let gridLineColor; 226 let tickColor; 227 if ($('body').attr('data-theme') == "light") { 228 gridLineColor = "#DCDBDF"; 229 tickColor = "#160F29"; 230 } 231 else { 232 gridLineColor = "#383148"; 233 tickColor = "#FFFFFF" 234 } 235 236 let datasets = []; 237 let data = {}; 238 239 datasets = seriesArray.map((o, i) => ({ 240 label: o.seriesName, 241 data: Object.values(o.values), 242 backgroundColor: colors[i], 243 borderColor: colors[i], 244 color: gridLineColor, 245 borderWidth: 2, 246 fill: false, 247 })); 248 249 data = { 250 labels: labels, 251 datasets: datasets 252 } 253 254 const config = { 255 type: 'line', 256 data, 257 options: { 258 maintainAspectRatio: false, 259 responsive: true, 260 aspectRatio: 2, 261 title: { 262 display: true, 263 }, 264 plugins: { 265 legend: { 266 display: false 267 }, 268 tooltip: { 269 enabled: true, 270 } 271 }, 272 scales: { 273 x: { 274 grid: { 275 color: gridLineColor, 276 }, 277 ticks: { 278 color: tickColor, 279 } 280 }, 281 y: { 282 grid: { 283 color: gridLineColor, 284 }, 285 ticks: { 286 color: tickColor, 287 } 288 }, 289 } 290 } 291 } 292 293 var lineCanvas = $('#metrics-graph').get(0).getContext('2d'); 294 295 // if chart is created for the first time after response 296 if (flag === -1) { 297 lineChart = new Chart(lineCanvas, config); 298 displayLegends(seriesArray, labels, colors); 299 } 300 301 const bgColor = []; 302 const bColor = data.datasets.map(color => { 303 bgColor.push(color.backgroundColor); 304 return color.borderColor; 305 }) 306 307 if (flag == -2) { 308 lineChart.config.data.datasets.map((dataset, index) => { 309 dataset.backgroundColor = bgColor[index]; 310 dataset.borderColor = bColor[index]; 311 }) 312 lineChart.update(); 313 } 314 315 if (flag >= 0) { 316 const chartDataObject = lineChart.config.data.datasets.map(dataset => { 317 dataset.borderColor = "rgba(0,0,0,0)"; 318 dataset.backgroundColor = "rgba(0,0,0,0)"; 319 }) 320 321 lineChart.config.data.datasets[flag].borderColor = bColor[flag]; 322 lineChart.config.data.datasets[flag].backgroundColor = bgColor[flag]; 323 lineChart.update(); 324 } 325 return lineChart; 326 } 327 328 function createColorsArray() { 329 let root = document.querySelector(':root'); 330 let rootStyles = getComputedStyle(root); 331 let colorArray = []; 332 for (let i = 1; i <= 20; i++) { 333 colorArray.push(rootStyles.getPropertyValue(`--graph-line-color-${i}`)); 334 } 335 return colorArray; 336 } 337 338 function getPrevSelectedElement(el = null) { 339 let prevEl = el; 340 return prevEl; 341 } 342 343 function displayLegends(seriesArray, labels, colors) { 344 $.each(seriesArray, function (k, v) { 345 $('#metrics-legends').append(`<div class="legend-element" id="legend-${k}"><span class="legend-colors" style="background-color:` + colors[k] + '"></span>' + v.seriesName + '</div>'); 346 }); 347 348 let prev = null; 349 350 const legends = document.querySelectorAll('.legend-element'); 351 $.each(legends, function (i, legend) { 352 legend.addEventListener('click', (e) => { 353 let currSelectedEl = e.target; 354 let currSelectedElId = parseInt((e.target.id).slice(7)); 355 if (currSelectedEl.classList.value == "legend-colors") { 356 currSelectedEl = currSelectedEl.closest("div.legend-element") 357 currSelectedElId = parseInt(currSelectedEl.id.slice(7)) 358 } 359 if (prev == null) { 360 currSelectedEl.classList.add("selected"); 361 prev = currSelectedElId; 362 displayGraph(seriesArray, labels, currSelectedElId); 363 } else if (prev == currSelectedElId) { 364 currSelectedEl.classList.remove("selected"); 365 prev = null; 366 displayGraph(seriesArray, labels, -2); 367 } else { 368 let prevEl = document.getElementById(`legend-${prev}`); 369 prevEl.classList.remove("selected"); 370 currSelectedEl.classList.add("selected"); 371 prev = currSelectedElId; 372 displayGraph(seriesArray, labels, currSelectedElId); 373 } 374 } 375 ) 376 }) 377 } 378 379 function showDatePickerHandler(evt) { 380 evt.stopPropagation(); 381 $('#daterangepicker').toggle(); 382 $(evt.currentTarget).toggleClass('active'); 383 } 384 385 function hideDatePickerHandler() { 386 $('#daterangepicker').removeClass('active'); 387 } 388 389 function resetDatePickerHandler(evt) { 390 evt.stopPropagation(); 391 resetCustomDateRange(); 392 $.each($(".range-item.active"), function () { 393 $(this).removeClass('active'); 394 }); 395 396 } 397 function getStartDateHandler(evt) { 398 let inputDate = new Date(this.value); 399 filterStartDate = inputDate.getTime(); 400 $(this).addClass("active"); 401 Cookies.set('customStartDate', this.value); 402 403 } 404 405 function getEndDateHandler(evt) { 406 let inputDate = new Date(this.value); 407 filterEndDate = inputDate.getTime(); 408 $(this).addClass("active"); 409 Cookies.set('customEndDate', this.value); 410 } 411 412 function getStartTimeHandler() { 413 let selectedTime = $(this).val(); 414 let temp = ((Number(selectedTime.split(':')[0]) * 60 + Number(selectedTime.split(':')[1])) * 60) * 1000; 415 //check if filterStartDate is a number or now-* 416 if (!isNaN(filterStartDate)) { 417 filterStartDate = filterStartDate + temp; 418 } else { 419 let start = new Date(); 420 start.setUTCHours(0, 0, 0, 0); 421 filterStartDate = start.getTime() + temp; 422 } 423 $(this).addClass("active"); 424 Cookies.set('customStartTime', selectedTime); 425 } 426 427 function getEndTimeHandler() { 428 let selectedTime = $(this).val(); 429 let temp = ((Number(selectedTime.split(':')[0]) * 60 + Number(selectedTime.split(':')[1])) * 60) * 1000; 430 if (!isNaN(filterEndDate)) { 431 filterEndDate = filterEndDate + temp; 432 } else { 433 let start = new Date(); 434 start.setUTCHours(0, 0, 0, 0); 435 filterEndDate = start.getTime() + temp; 436 } 437 $(this).addClass("active"); 438 Cookies.set('customEndTime', selectedTime); 439 } 440 441 function customRangeHandler(evtvt) { 442 $.each($(".range-item.active"), function () { 443 $(this).removeClass('active'); 444 }); 445 $('#date-picker-btn span').html("Custom"); 446 } 447 448 function rangeItemHandler(evt) { 449 resetCustomDateRange(); 450 $.each($(".range-item.active"), function () { 451 $(this).removeClass('active'); 452 }); 453 $(evt.currentTarget).addClass('active'); 454 datePickerHandler($(this).attr('id'), "now", $(this).attr('id')) 455 } 456 457 function resetCustomDateRange() { 458 // clear custom selections 459 $('#date-start').val(""); 460 $('#date-end').val(""); 461 $('#time-start').val("00:00"); 462 $('#time-end').val("00:00"); 463 $('#date-start').removeClass('active'); 464 $('#date-end').removeClass('active'); 465 $('#time-start').removeClass('active'); 466 $('#time-end').removeClass('active'); 467 Cookies.remove('customStartDate'); 468 Cookies.remove('customEndDate'); 469 Cookies.remove('customStartTime'); 470 Cookies.remove('customEndTime'); 471 } 472 473 function themePickerHandler(evt) { 474 let newgridLineColor; 475 let newTickColor; 476 477 if (Cookies.get('theme')) { 478 theme = Cookies.get('theme'); 479 } else { 480 Cookies.set('theme', 'light'); 481 theme = 'light'; 482 } 483 484 if (theme === 'light') { 485 theme = 'dark'; 486 $(evt.currentTarget).removeClass('dark-theme'); 487 $(evt.currentTarget).addClass('light-theme'); 488 newgridLineColor = "#383148"; 489 newTickColor = "#FFFFFF"; 490 } else { 491 theme = 'light'; 492 $(evt.currentTarget).removeClass('light-theme'); 493 $(evt.currentTarget).addClass('dark-theme'); 494 newgridLineColor = "#DCDBDF"; 495 newTickColor = "#160F29"; 496 } 497 $('body').attr('data-theme', theme); 498 499 if (lineChart !== undefined) { 500 lineChart.config.options.scales.x.grid.color = newgridLineColor; 501 lineChart.config.options.scales.y.grid.color = newgridLineColor; 502 lineChart.config.options.scales.x.ticks.color = newTickColor; 503 lineChart.config.options.scales.y.ticks.color = newTickColor; 504 lineChart.update(); 505 } 506 507 508 Cookies.set('theme', theme, { expires: 365 }); 509 } 510 511 function processSearchError(res) { 512 $('#metrics-graph-container').hide(); 513 $('#metrics-table-container').hide(); 514 showError(`${res.responseText}`) 515 } 516 517 function processNoResults() { 518 $('#metrics-graph-container').hide(); 519 $('#metrics-table-container').hide(); 520 showError(`Your query returned no data, adjust your query.`); 521 } 522 523 $(".metrics-graph-btn, .metrics-table-btn").click(function() { 524 $(".metrics-graph-btn, .metrics-table-btn").removeClass("active"); 525 $(this).addClass("active"); 526 updateContainers(); 527 }); 528 529 function updateContainers() { 530 const isGraphActive = $(".metrics-graph-btn").hasClass("active"); 531 if ($("#corner-popup").is(":visible")) { 532 $("#metrics-graph-container").hide(); 533 $("#metrics-table-container").hide(); 534 }else { 535 if (isGraphActive) { 536 $("#metrics-graph-container").show(); 537 $("#metrics-table-container").hide(); 538 }else { 539 $("#metrics-graph-container").hide(); 540 $("#metrics-table-container").show(); 541 }} 542 } 543 544 function displayTable(res) { 545 let tableBody = $('#metrics-table-container table'); 546 tableBody.empty(); 547 tableBody.append('<tr><th>Metric</th><th>Value</th></tr>'); 548 $.each(res.aggStats, function(metric, timestampValues) { 549 let metricName = metric; 550 let timestamps = Object.keys(timestampValues); 551 let lastValue = timestampValues[timestamps[timestamps.length - 1]]; 552 553 let newRow = $('<tr>'); 554 newRow.append($('<td>').text(metricName)); 555 newRow.append($('<td>').text(lastValue)); 556 tableBody.append(newRow); 557 }); 558 } 559 560 window.addEventListener('resize', function () { 561 lineChart.resize(); 562 });