github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/dashboard.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 localPanels = [], dbData, dbName, dbDescr, dbId, panelIndex, flagDBSaved = true, allResultsDisplayed = 0;
    20  let timeRange = "Last 1 Hr";
    21  let dbRefresh ="";
    22  let panelContainer;
    23  let panelContainerWidthGlobal;
    24  let curFocus;
    25  
    26  $(document).ready(function () {
    27      getListIndices();
    28      var isActive = $('#app-side-nav').hasClass('active');
    29      if (isActive) {
    30          $('#new-dashboard').css("transform", "translate(87px)")
    31          $('#new-dashboard').css("width", "calc(100% - 97px)")
    32      }
    33      else {
    34          $('#new-dashboard').css("transform", "translate(170px)")
    35          $('#new-dashboard').css("width", "calc(100% - 180px)")
    36      }
    37  
    38      panelContainer = document.getElementById('panel-container');
    39      panelContainerWidthGlobal = isActive? panelContainer.offsetWidth-97: panelContainer.offsetWidth-215;
    40  
    41      $('.panelEditor-container').hide();
    42      $('.dbSet-container').hide();
    43      if (Cookies.get('theme')) {
    44          theme = Cookies.get('theme');
    45          $('body').attr('data-theme', theme);
    46      }
    47      $('.theme-btn').on('click', themePickerHandler);
    48      setupEventHandlers();
    49      dbId = getDashboardId();
    50  
    51      $("#add-panel-btn").click(() => addPanel());
    52      $(".all-dashboards").click(function () {
    53          window.location.href = "../dashboards-home.html";
    54      })
    55  
    56      displayDashboardName();
    57  
    58      $("#theme-btn").click(() => displayPanels());
    59  
    60      getDashboardData();
    61  
    62      setTimePicker();
    63  
    64      $(`.dbSet-textareaContainer .copy`).tooltip({
    65          delay: { show: 0, hide: 300 },
    66          trigger: 'hover'
    67      });
    68  })
    69  $(document).mouseup(function (e) {
    70    var popWindows = $("#panel-dropdown-modal");
    71    let panelHead = $(".panel-header");
    72    let j1 = !popWindows.is(e.target);
    73    let j2 = !panelHead.is(e.target);
    74    let j3 = !$(curFocus + " .dropdown-style").hasClass("hidden");
    75    if (
    76      !popWindows.is(e.target) &&
    77      popWindows.has(e.target).length === 0 &&
    78      !panelHead.is(e.target) &&
    79      panelHead.has(e.target).length === 0 &&
    80      !$(curFocus + " .dropdown-style").hasClass("hidden")
    81    ) {
    82      $(curFocus + " .dropdown-btn").toggleClass("active");
    83      $(curFocus + " .dropdown-style").toggleClass("hidden");
    84    }
    85  });
    86  window.addEventListener('resize', function (event) {
    87      if ($('.panelEditor-container').css('display') === 'none'){
    88          panelContainerWidthGlobal = panelContainer.offsetWidth-97;
    89          recalculatePanelWidths();
    90          displayPanels();
    91          resetPanelLocationsHorizontally();
    92      }
    93  });
    94  $(`.dbSet-textareaContainer .copy`).click(function() {
    95      $(this).tooltip('dispose');
    96      $(this).attr('title', 'Copied!').tooltip('show');
    97      navigator.clipboard.writeText($(`.dbSet-jsonModelData`).val())
    98          .then(() => {
    99              setTimeout(() => {
   100                  $(this).tooltip('dispose');
   101                  $(this).attr('title', 'Copy').tooltip({
   102                      delay: { show: 0, hide: 300 },
   103                      trigger: 'hover',
   104                    });
   105              }, 1000);
   106          })
   107  });
   108  
   109  function recalculatePanelWidths(){
   110      localPanels.map(localPanel => {
   111          localPanel.gridpos.w = localPanel.gridpos.wPercent * panelContainerWidthGlobal;
   112      })
   113  }
   114  
   115  $('#save-db-btn').on("click", updateDashboard);
   116  $('.refresh-btn').on("click", refreshDashboardHandler);
   117  $('.settings-btn').on('click', handleDbSettings);
   118  $('#dbSet-save').on('click', saveDbSetting);
   119  $('#dbSet-discard').on('click', discardDbSetting);
   120  $('.dbSet-goToDB').on('click', discardDbSetting);
   121  $('.panView-goToDB').on("click", goToDashboardFromView)
   122  $('.refresh-range-item').on('click', refreshRangeItemHandler);
   123  
   124  
   125  function updateDashboard() {
   126      timeRange = $('#date-picker-btn').text().trim().replace(/\s+/g, ' ');
   127      resetPanelTimeRanges();
   128      flagDBSaved = true;
   129      let tempPanels = JSON.parse(JSON.stringify(localPanels));
   130      for (let i = 0; i < tempPanels.length; i++)
   131          delete tempPanels[i].queryRes;
   132      return fetch('/api/dashboards/update',
   133          {
   134              method: 'POST',
   135              body: JSON.stringify({
   136                  "id": dbId,
   137                  "name": dbName,
   138                  "details": {
   139                      "name": dbName,
   140                      "description": dbDescr,
   141                      "timeRange": timeRange,
   142                      "panels": tempPanels,
   143                      "refresh": dbRefresh,
   144                  },
   145              })
   146          }
   147      )
   148          .then(res => {
   149              if (res.status === 409) {
   150                  showToast('Dashboard name already exists');
   151                  throw new Error('Dashboard name already exists');
   152              }    
   153              if (res.status == 200) {
   154                  displayDashboardName();
   155                  showToast('Dashboard Updated Successfully');
   156                  return true;
   157              }
   158              return res.json();
   159          })
   160          .catch(error => {
   161              console.error(error);
   162              return false;
   163          });
   164  }
   165  
   166  function refreshDashboardHandler() {
   167      if ($('#viewPanel-container').css('display') !== 'none') {
   168          displayPanelView(panelIndex);
   169      }
   170      else {
   171          for (let i = 0; i < localPanels.length; i++) {
   172              localPanels[i].queryRes = undefined;
   173          }
   174          displayPanels();
   175      }
   176  }
   177  
   178  function handlePanelView() {
   179      $(".panel-view-li").unbind("click");
   180      $(".panel-view-li").on("click", function () {
   181          panelIndex = $(this).closest(".panel").attr("panel-index");
   182          pauseRefreshInterval();
   183          viewPanelInit();
   184          displayPanelView(panelIndex);
   185      })
   186  }
   187  
   188  function viewPanelInit() {
   189      $('#app-container').show();
   190      $('.panView-goToDB').css('display', 'block');
   191      $('#viewPanel-container').show();
   192      $('#panel-container').hide();
   193      $('.panelEditor-container').hide();
   194      $('#add-panel-btn').hide();
   195      $('#viewPanel-container').css('display', 'flex');
   196      $('#viewPanel-container').css('height', '100%');
   197      setTimePicker();
   198  }
   199  
   200  function goToDashboardFromView() {
   201      $('#viewPanel-container').hide();
   202      $('#panel-container').show();
   203      $('.panView-goToDB').css('display', 'none');
   204      $('#add-panel-btn').show();
   205      $('#viewPanel-container .panel .panel-info-corner').empty();
   206      updateTimeRangeForPanels();
   207      displayPanels();
   208      if(dbRefresh){
   209  		startRefreshInterval(dbRefresh)
   210  	}
   211  }
   212  
   213  function handlePanelEdit() {
   214      $(".panel-edit-li").unbind("click");
   215      $(".panel-edit-li").on("click", function () {
   216          panelIndex = $(this).closest(".panel").attr("panel-index");
   217  
   218          if ($('#viewPanel-container').css('display') !== 'none') {
   219              editPanelInit(-1);
   220          } else {
   221              editPanelInit();
   222          }
   223          $('.panelEditor-container').show();
   224          $('#app-container').hide();
   225          $('.panelDisplay #panelLogResultsGrid').empty();
   226          $('.panelDisplay .big-number-display-container').hide();
   227          $('.panelDisplay #empty-response').hide();
   228      })
   229  }
   230  function handlePanelRemove(panelId) {
   231      $(`#panel${panelId} .panel-remove-li`).unbind("click");
   232      $(`#panel${panelId} .panel-remove-li`).on("click", function () {
   233          showPrompt(panelId);
   234      });
   235  
   236      function showPrompt(panelId) {
   237          $('.popupOverlay, .popupContent').addClass('active');
   238          $('#delete-btn-panel').on("click", function () {
   239              deletePanel(panelId);
   240              $('.popupOverlay, .popupContent').removeClass('active');
   241          });
   242          $('#cancel-btn-panel, .popupOverlay').on("click", function () {
   243              $('.popupOverlay, .popupContent').removeClass('active');
   244          });
   245      }
   246  
   247      function deletePanel(panelId) {
   248          flagDBSaved = false;
   249          const panel = $(`#panel${panelId}`);
   250          let panelIndex = panel.attr("panel-index");
   251  
   252          localPanels = localPanels.filter(function (el) {
   253              return el.panelIndex != panelIndex;
   254          });
   255          panel.remove();
   256  
   257          resetPanelIndices();
   258          resetPanelLocationsHorizontally();
   259          resetPanelLocationsVertically();
   260          resetPanelContainerHeight();
   261          displayPanels();
   262      }
   263  }
   264  
   265  function handleDescriptionTooltip(panelId,description,searchText) {
   266      const panelInfoCorner = $(`#panel${panelId} .panel-info-corner`);
   267      const panelDescIcon = $(`#panel${panelId} .panel-info-corner #panel-desc-info`);
   268      panelInfoCorner.show();
   269      let tooltipText = '';
   270  
   271      // Check if description is provided
   272      if (description) {
   273          tooltipText += `Description: ${description}`;
   274      }
   275  
   276      // Check if both description and searchText are provided, add line break if needed
   277      if (description && searchText) {
   278          tooltipText += '\n';
   279      }
   280  
   281      // Check if searchText is provided
   282      if (searchText) {
   283          tooltipText += `Query: ${searchText}`;
   284      }
   285  
   286      panelDescIcon.attr('title', tooltipText);
   287  
   288      panelDescIcon.tooltip({
   289          delay: { show: 0, hide: 300 },
   290          trigger: 'hover'});
   291      panelInfoCorner.hover(function () {panelDescIcon.tooltip('show');},
   292      function () {panelDescIcon.tooltip('hide');});
   293  }
   294  
   295  function resetPanelLocationsHorizontally() {
   296      let temp = [];
   297      for (let i = 0; i < localPanels.length; i++) {
   298          let x = localPanels[i].gridpos.x;
   299          temp.push([x, i]);
   300      }
   301      temp.sort((a, b) => a[0] - b[0]);
   302      let indices = [];
   303      for (let i = 0; i < temp.length; i++)
   304          indices.push(temp[i][1])
   305  
   306      for (let i = 0; i < indices.length; i++) {
   307          let hRight = localPanels[indices[i]].gridpos.h;
   308          let wRight = localPanels[indices[i]].gridpos.wPercent * panelContainerWidthGlobal;
   309          let xRight = localPanels[indices[i]].gridpos.x;
   310          let yRight = localPanels[indices[i]].gridpos.y;
   311  
   312          let xmax = 0;
   313          for (let j = 0; j < i; j++) {
   314              let hLeft = localPanels[indices[j]].gridpos.h;
   315              let wLeft = localPanels[indices[j]].gridpos.w;
   316              let xLeft = localPanels[indices[j]].gridpos.x;
   317              let yLeft = localPanels[indices[j]].gridpos.y;
   318  
   319              if ((yLeft >= yRight && yLeft <= yRight + hRight) || (yLeft + hLeft >= yRight && yLeft + hLeft <= yRight + hRight) || (yLeft <= yRight && yLeft + hLeft >= yRight + hRight)) {
   320                  xmax = Math.max(xmax, xLeft + wLeft);
   321              }
   322          }
   323  
   324          if ((xmax + wRight) < ($('#panel-container')[0].offsetWidth + $('#panel-container')[0].offsetLeft - 20)) {
   325              localPanels[indices[i]].gridpos.x = xmax + 10;
   326          }
   327      }
   328  }
   329  
   330  function resetPanelLocationsVertically() {
   331      let temp = [];
   332  
   333      for (let i = 0; i < localPanels.length; i++) {
   334          let y = localPanels[i].gridpos.y;
   335          temp.push([y, i]);
   336      }
   337      temp.sort((a, b) => a[0] - b[0]);
   338      let indices = [];
   339      for (let i = 0; i < temp.length; i++)
   340          indices.push(temp[i][1])
   341  
   342      for (let i = 0; i < indices.length; i++) {
   343          let hDown = localPanels[indices[i]].gridpos.h;
   344          let wDown = localPanels[indices[i]].gridpos.w;
   345          let xDown = localPanels[indices[i]].gridpos.x;
   346          let yDown = localPanels[indices[i]].gridpos.y;
   347  
   348          let ymax = 10;
   349          for (let j = 0; j < i; j++) {
   350              let hTop = localPanels[indices[j]].gridpos.h;
   351              let wTop = localPanels[indices[j]].gridpos.w;
   352              let xTop = localPanels[indices[j]].gridpos.x;
   353              let yTop = localPanels[indices[j]].gridpos.y;
   354  
   355              if ((xTop >= xDown && xTop <= xDown + wDown) || (xTop + wTop >= xDown && xTop + wTop <= xDown + wDown) || (xTop <= xDown && xTop + wTop >= xDown + wDown)) {
   356                  ymax = Math.max(ymax, yTop + hTop);
   357              }
   358          }
   359  
   360          localPanels[indices[i]].gridpos.y = ymax + 10;
   361      }
   362  }
   363  
   364  function handlePanelDuplicate() {
   365      $(".panel-dupl-li").unbind("click");
   366      $(".panel-dupl-li").on("click", function () {
   367          flagDBSaved = false;
   368          let duplicatedPanelIndex = $(this).closest(".panel").attr("panel-index");
   369          addPanel(JSON.parse(JSON.stringify(localPanels[duplicatedPanelIndex])));
   370          renderDuplicatePanel(duplicatedPanelIndex);
   371      })
   372  }
   373  
   374  function renderDuplicatePanel(duplicatedPanelIndex) {
   375      let boundaryY = localPanels[duplicatedPanelIndex].gridpos.y + localPanels[duplicatedPanelIndex].gridpos.h;
   376      for (let i = 0; i < localPanels.length; i++) {
   377          if (localPanels[i].panelIndex == localPanels.length - 1) continue; // this is the newly created duplicate panel.
   378          if (localPanels[i].gridpos.y >= boundaryY) // if any panel starts after the ending Y-cordinate of the duplicated panel, then it should be shifted downwards
   379              localPanels[i].gridpos.y += localPanels[duplicatedPanelIndex].gridpos.h + 20;
   380      }
   381      resetPanelLocationsVertically();
   382      resetPanelLocationsHorizontally();
   383      resetPanelContainerHeight();
   384      displayPanelsWithoutRefreshing();
   385      let localPanel = localPanels[localPanels.length - 1];
   386      let panelId = localPanels[localPanels.length - 1].panelId;
   387      // only render the duplicated panel
   388      $(`#panel${localPanels[localPanels.length - 1].panelId} .panel-header p`).html(localPanels[duplicatedPanelIndex].name + "Copy");
   389  
   390      if (localPanel.chartType == 'Data Table' || localPanel.chartType == 'loglines') {
   391          let panEl = $(`#panel${panelId} .panel-body`)
   392          let responseDiv = `<div id="panelLogResultsGrid" class="panelLogResultsGrid ag-theme-mycustomtheme"></div>
   393          <div id="empty-response"></div>`
   394          panEl.append(responseDiv)
   395          $("#panelLogResultsGrid").show();
   396  
   397          if (localPanel.queryRes)
   398              runPanelLogsQuery(localPanel.queryData, panelId,localPanel, localPanel.queryRes);
   399          else
   400              runPanelLogsQuery(localPanel.queryData, panelId,localPanel);
   401      } else if (localPanel.chartType == 'Line Chart') {
   402          let panEl = $(`#panel${panelId} .panel-body`)
   403          let responseDiv = `<div id="empty-response"></div></div><div id="corner-popup"></div>`
   404          panEl.append(responseDiv)
   405          if (localPanel.queryRes)
   406              runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel, localPanel.queryRes)
   407          else
   408              runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel)
   409      } else if (localPanel.chartType == 'number') {
   410          let panEl = $(`#panel${panelId} .panel-body`)
   411          let responseDiv = `<div class="big-number-display-container"></div>
   412          <div id="empty-response"></div><div id="corner-popup"></div>`
   413          panEl.append(responseDiv)
   414          if(localPanel.queryType ==='metrics') {
   415              if (localPanel.queryRes)
   416                  runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel, localPanel.queryRes)
   417              else
   418                  runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel)
   419          }else{
   420              if (localPanel.queryRes)
   421                  runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   422              else
   423                  runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   424          }
   425          } else if (localPanel.chartType == 'Pie Chart' || localPanel.chartType == 'Bar Chart') {
   426          // generic for both bar and pie chartTypes.
   427          let panEl = $(`#panel${panelId} .panel-body`)
   428          let responseDiv = `<div id="empty-response"></div><div id="corner-popup"></div>`
   429          panEl.append(responseDiv)
   430          if (localPanel.queryRes)
   431              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   432          else
   433              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   434      }
   435  }
   436  
   437  function resetPanelIndices() {
   438      for (let i = 0; i < localPanels.length; i++) {
   439          localPanels[i].panelIndex = i;
   440      }
   441  }
   442  
   443  function displayDashboardName() {
   444      $.ajax({
   445          method: "get",
   446          url: "api/dashboards/" + dbId,
   447          headers: {
   448              'Content-Type': 'application/json; charset=utf-8',
   449              'Accept': '*/*'
   450          },
   451          dataType: 'json',
   452          crossDomain: true,
   453      }).then(function (res) {
   454          $(".name-dashboard").text(res.name);
   455      })
   456  }
   457  
   458  async function getDashboardData() {
   459      await fetch(`/api/dashboards/${dbId}`)
   460          .then(res => {
   461              return res.json();
   462          })
   463          .then(data => {
   464              dbData = data;
   465          })
   466      dbName = dbData.name;
   467      dbDescr = dbData.description;
   468      dbRefresh = dbData.refresh;
   469      if (dbData.panels != undefined) {
   470          localPanels = JSON.parse(JSON.stringify(dbData.panels));
   471      } else localPanels = [];
   472      if (localPanels != undefined) {
   473          updateTimeRangeForPanels();
   474          recalculatePanelWidths();
   475          resetPanelLocationsHorizontally();
   476          setRefreshItemHandler();
   477          refreshDashboardHandler();
   478      }
   479  }
   480  
   481  function updateTimeRangeForPanels() {
   482      localPanels.forEach(panel => {
   483          delete panel.queryRes;
   484          if(panel.queryData) {
   485              if(panel.chartType === "Line Chart" || panel.queryType === "metrics") {
   486                  datePickerHandler(panel.queryData.start, panel.queryData.end, panel.queryData.start)
   487                  panel.queryData.start = filterStartDate.toString();
   488                  panel.queryData.end = filterEndDate.toString();
   489              } else {
   490                  datePickerHandler(panel.queryData.startEpoch, panel.queryData.endEpoch, panel.queryData.startEpoch)
   491                  panel.queryData.startEpoch = filterStartDate
   492                  panel.queryData.endEpoch = filterEndDate
   493              }
   494              $('.inner-range .db-range-item').removeClass('active');
   495              $('.inner-range #' + filterStartDate).addClass('active');
   496          }
   497      })
   498  }
   499  
   500  function updateTimeRangeForPanel(panelIndex) {
   501      delete localPanels[panelIndex].queryRes;
   502      if(localPanels[panelIndex].queryData) {
   503          if(localPanels[panelIndex].chartType === "Line Chart" && localPanels[panelIndex].queryType === "metrics") {
   504              localPanels[panelIndex].queryData.start = filterStartDate.toString();
   505              localPanels[panelIndex].queryData.end = filterEndDate.toString();
   506          } else {
   507              localPanels[panelIndex].queryData.startEpoch = filterStartDate
   508              localPanels[panelIndex].queryData.endEpoch = filterEndDate
   509          }
   510      }
   511  }
   512  
   513  
   514  function displayPanels() {
   515      allResultsDisplayed = localPanels.length;
   516      $('#panel-container .panel').remove();
   517      let panelContainerMinHeight = 0;
   518      $('body').css('cursor', 'progress');
   519      localPanels.map((localPanel) => {
   520          let idpanel = localPanel.panelId;
   521          let panel = $("<div>").append(panelLayout).addClass("panel").attr("id", `panel${idpanel}`).attr("panel-index", localPanel.panelIndex);
   522          $("#panel-container").append(panel);
   523          handleDrag(idpanel);
   524          handleResize(idpanel);
   525          $("#panel" + idpanel + " .panel-header").click(function () {
   526              curFocus = "#panel" + idpanel;
   527              $("#panel" + idpanel + " .dropdown-btn").toggleClass("active")
   528              $("#panel" + idpanel + " .dropdown-style").toggleClass("hidden");
   529          })
   530          $("#panel" + idpanel + " .dropdown-btn").click(function (e) {
   531              e.stopPropagation();
   532              curFocus = "#panel" + idpanel;
   533              $("#panel" + idpanel + " .dropdown-btn").toggleClass("active");
   534              $("#panel" + idpanel + " .dropdown-style").toggleClass("hidden");
   535          });
   536          $(`#panel${idpanel} .panel-header p`).html(localPanel.name);
   537  
   538          if (localPanel.description || (localPanel.queryData && localPanel.queryData.searchText)) {
   539              handleDescriptionTooltip(idpanel, localPanel.description, localPanel.queryData ? localPanel.queryData.searchText : '');
   540          } else {
   541              $(`#panel${idpanel} .panel-info-corner`).hide();
   542          }
   543  
   544          let panelElement = document.getElementById(`panel${idpanel}`);
   545          panelElement.style.position = "absolute";
   546          panelElement.style.height = localPanel.gridpos.h + "px";
   547          panelElement.style.width = localPanel.gridpos.w + "px";
   548          panelElement.style.top = localPanel.gridpos.y + "px";
   549          panelElement.style.left = localPanel.gridpos.x + "px";
   550  
   551          let val = localPanel.gridpos.y + localPanel.gridpos.h;
   552          if (val > panelContainerMinHeight) panelContainerMinHeight = val;
   553  
   554          handlePanelRemove(idpanel)
   555  
   556          if (localPanel.chartType == 'Data Table'||localPanel.chartType == 'loglines') {
   557              let panEl = $(`#panel${idpanel} .panel-body`)
   558              let responseDiv = `<div id="panelLogResultsGrid" class="panelLogResultsGrid ag-theme-mycustomtheme"></div>
   559              <div id="empty-response"></div></div><div id="corner-popup"></div>
   560              <div id="panel-loading"></div>`
   561              panEl.append(responseDiv)
   562  
   563              $("#panelLogResultsGrid").show();
   564              if (localPanel.queryRes)
   565                  runPanelLogsQuery(localPanel.queryData, idpanel,localPanel, localPanel.queryRes);
   566              else
   567                  runPanelLogsQuery(localPanel.queryData, idpanel,localPanel);
   568          } else if (localPanel.chartType == 'Line Chart') {
   569              let panEl = $(`#panel${idpanel} .panel-body`)
   570              let responseDiv = `<div id="empty-response"></div></div><div id="corner-popup"></div>
   571              <div id="panel-loading"></div>`
   572              panEl.append(responseDiv)
   573              if (localPanel.queryRes){
   574                  runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel, localPanel.queryRes)
   575              }
   576              else {
   577                  //remove startEpoch from from localPanel.queryData
   578                  delete localPanel.queryData.startEpoch
   579                  delete localPanel.queryData.endEpoch
   580                  runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel)
   581              }
   582          } else if (localPanel.chartType == 'number') {
   583              let panEl = $(`#panel${idpanel} .panel-body`)
   584              let responseDiv = `<div class="big-number-display-container"></div>
   585              <div id="empty-response"></div><div id="corner-popup"></div>
   586              <div id="panel-loading"></div>`
   587              panEl.append(responseDiv)
   588  
   589              $('.big-number-display-container').show();
   590              if (localPanel.queryType === "metrics"){
   591  
   592                  if (localPanel.queryRes){
   593                      delete localPanel.queryData.startEpoch
   594                      delete localPanel.queryData.endEpoch
   595                      runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel, localPanel.queryRes)
   596                  }
   597                  else {
   598                      //remove startEpoch from from localPanel.queryData
   599                      delete localPanel.queryData.startEpoch
   600                      delete localPanel.queryData.endEpoch
   601                      runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel)
   602                  }
   603              }else {
   604                  if (localPanel.queryRes)
   605                      runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   606                  else
   607                      runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   608              }
   609          } else if (localPanel.chartType == 'Bar Chart' || localPanel.chartType == 'Pie Chart') {
   610              // generic for both bar and pie chartTypes.
   611              let panEl = $(`#panel${idpanel} .panel-body`)
   612              let responseDiv = `<div id="empty-response"></div><div id="corner-popup"></div>
   613              <div id="panel-loading"></div>`
   614              panEl.append(responseDiv)
   615              if (localPanel.queryRes)
   616                  runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   617              else
   618                  runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   619          } else
   620              allResultsDisplayed--;
   621      })
   622      if(allResultsDisplayed === 0) {
   623          $('body').css('cursor', 'default');
   624      }
   625      handlePanelView();
   626      handlePanelEdit();
   627      handlePanelDuplicate();
   628      resetPanelContainerHeight();
   629  }
   630  
   631  function displayPanelView(panelIndex) {
   632      let localPanel = localPanels[panelIndex];
   633      let panelId = localPanel.panelId;
   634      $(`#panel-container #panel${panelId}`).remove();
   635      $(`#viewPanel-container`).empty();
   636  
   637      let panel = $("<div>").append(panelLayout).addClass("panel").attr("id", `panel${panelId}`).attr("panel-index", localPanel.panelIndex);
   638      $("#viewPanel-container").append(panel);
   639      $("#panel" + panelId + " .panel-header").click(function () {
   640          $("#panel" + panelId + " .dropdown-btn").toggleClass("active")
   641          $("#panel" + panelId + " .dropdown-style").toggleClass("hidden");
   642      })
   643      $("#" + `panel${panelId}` + " .dropdown-btn").click(function (e) {
   644          e.stopPropagation();
   645          $("#" + `panel${panelId}` + " .dropdown-btn").toggleClass("active")
   646          $("#" + `panel${panelId}` + " .dropdown-style").toggleClass("hidden");
   647      });
   648      $(`#panel${panelId} .panel-header p`).html(localPanel.name);
   649  
   650      let panelElement = document.getElementById(`panel${panelId}`);
   651      panelElement.style.position = "absolute";
   652  
   653      panelElement.style.height = "100%";
   654      panelElement.style.width = "100%";
   655  
   656      handlePanelRemove(localPanel.panelId);
   657      if (localPanel.description||localPanel.queryData?.searchText) {
   658          handleDescriptionTooltip(localPanel.panelId,localPanel.description,localPanel.queryData.searchText);
   659      } else {
   660          $(`#panel${panelId} .panel-info-corner`).hide();
   661      }
   662  
   663      if (localPanel.chartType == 'Data Table'| localPanel.chartType == 'loglines') {
   664          let panEl = $(`#panel${panelId} .panel-body`)
   665          let responseDiv = `<div id="panelLogResultsGrid" class="panelLogResultsGrid ag-theme-mycustomtheme"></div>
   666          <div id="empty-response"></div>`
   667          panEl.append(responseDiv)
   668          $("#panelLogResultsGrid").show();
   669  
   670          if (localPanel.queryRes)
   671              runPanelLogsQuery(localPanel.queryData, panelId,localPanel, localPanel.queryRes);
   672          else
   673              runPanelLogsQuery(localPanel.queryData, panelId,localPanel);
   674      } else if (localPanel.chartType == 'Line Chart') {
   675          let panEl = $(`#panel${panelId} .panel-body`)
   676          let responseDiv = `<div id="empty-response"></div></div><div id="corner-popup"></div>`
   677          panEl.append(responseDiv)
   678          if (localPanel.queryRes)
   679              runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel, localPanel.queryRes)
   680          else
   681              runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel)
   682      } else if (localPanel.chartType == 'number') {
   683          let panEl = $(`#panel${panelId} .panel-body`)
   684          let responseDiv = `<div class="big-number-display-container"></div>
   685          <div id="empty-response"></div><div id="corner-popup"></div>`
   686          panEl.append(responseDiv)
   687  
   688          if (localPanel.queryRes)
   689              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   690          else
   691              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   692      } else if (localPanel.chartType == 'Pie Chart' || localPanel.chartType == 'Bar Chart') {
   693          // generic for both bar and pie chartTypes.
   694          let panEl = $(`#panel${panelId} .panel-body`)
   695          let responseDiv = `<div id="empty-response"></div><div id="corner-popup"></div>`
   696          panEl.append(responseDiv)
   697          if (localPanel.queryRes)
   698              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   699          else
   700              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   701      }
   702  
   703      handlePanelView();
   704      handlePanelEdit();
   705  }
   706  
   707  function displayPanel(panelIndex) {
   708      let localPanel = localPanels[panelIndex];
   709      let panelId = localPanel.panelId;
   710      $(`#panel-container #panel${panelId}`).remove();
   711      $(`#viewPanel-container`).empty();
   712  
   713      let panel = $("<div>").append(panelLayout).addClass("panel").attr("id", `panel${panelId}`).attr("panel-index", localPanel.panelIndex);
   714      $("#panel-container").append(panel);
   715      handleDrag(panelId);
   716      handleResize(panelId);
   717      $("#panel" + panelId + " .panel-header").click(function () {
   718          $("#panel" + panelId + " .dropdown-btn").toggleClass("active")
   719          $("#panel" + panelId + " .dropdown-style").toggleClass("hidden");
   720      })
   721      $("#" + `panel${panelId}` + " .dropdown-btn").click(function (e) {
   722          e.stopPropagation();
   723          $("#" + `panel${panelId}` + " .dropdown-btn").toggleClass("active")
   724          $("#" + `panel${panelId}` + " .dropdown-style").toggleClass("hidden");
   725      });
   726      $(`#panel${panelId} .panel-header p`).html(localPanel.name);
   727      if (localPanel.description||localPanel.queryData.searchText) {
   728          handleDescriptionTooltip(panelId,localPanel.description,localPanel.queryData.searchText)
   729      } else {
   730          $(`#panel${panelId} .panel-info-corner`).hide();
   731      }
   732  
   733  
   734      let panelElement = document.getElementById(`panel${panelId}`);
   735      panelElement.style.position = "absolute";
   736      panelElement.style.height = localPanel.gridpos.h + "px";
   737      panelElement.style.width = (localPanel.gridpos.wPercent * 100) + "%";
   738      panelElement.style.top = localPanel.gridpos.y + "px";
   739      panelElement.style.left = localPanel.gridpos.x + "px";
   740      handlePanelRemove(localPanel.panelId)
   741  
   742      if (localPanel.chartType == 'Data Table'|| localPanel.chartType =='loglines') {
   743          let panEl = $(`#panel${panelId} .panel-body`)
   744          let responseDiv = `<div id="panelLogResultsGrid" class="panelLogResultsGrid ag-theme-mycustomtheme"></div>
   745          <div id="empty-response"></div>`
   746          panEl.append(responseDiv)
   747          $("#panelLogResultsGrid").show();
   748  
   749          if (localPanel.queryRes)
   750              runPanelLogsQuery(localPanel.queryData, panelId,localPanel, localPanel.queryRes);
   751          else
   752              runPanelLogsQuery(localPanel.queryData, panelId,localPanel);
   753      } else if (localPanel.chartType == 'Line Chart') {
   754          let panEl = $(`#panel${panelId} .panel-body`)
   755          let responseDiv = `<div id="empty-response"></div></div><div id="corner-popup"></div>`
   756          panEl.append(responseDiv)
   757          if (localPanel.queryRes)
   758              runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel, localPanel.queryRes)
   759          else
   760              runMetricsQuery(localPanel.queryData, localPanel.panelId, localPanel)
   761      } else if (localPanel.chartType == 'number') {
   762          let panEl = $(`#panel${panelId} .panel-body`)
   763          let responseDiv = `<div class="big-number-display-container"></div>
   764          <div id="empty-response"></div><div id="corner-popup"></div>`
   765          panEl.append(responseDiv)
   766  
   767          if (localPanel.queryRes)
   768              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   769          else
   770              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   771      } else if (localPanel.chartType == 'Pie Chart' || localPanel.chartType == 'Bar Chart') {
   772          // generic for both bar and pie chartTypes.
   773          let panEl = $(`#panel${panelId} .panel-body`)
   774          let responseDiv = `<div id="empty-response"></div><div id="corner-popup"></div>`
   775          panEl.append(responseDiv)
   776          if (localPanel.queryRes)
   777              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex, localPanel.queryRes);
   778          else
   779              runPanelAggsQuery(localPanel.queryData, localPanel.panelId, localPanel.chartType, localPanel.dataType, localPanel.panelIndex);
   780      }
   781  
   782      handlePanelView();
   783      handlePanelEdit();
   784      handlePanelDuplicate();
   785      resetPanelContainerHeight();
   786  }
   787  
   788  function displayPanelsWithoutRefreshing() {
   789      localPanels.map((localPanel) => {
   790          let panelElement = document.getElementById(`panel${localPanel.panelId}`);
   791          panelElement.style.position = "absolute";
   792          panelElement.style.height = localPanel.gridpos.h + "px";
   793          panelElement.style.width = localPanel.gridpos.w + "px";
   794          panelElement.style.top = localPanel.gridpos.y + "px";
   795          panelElement.style.left = localPanel.gridpos.x + "px";
   796      })
   797  }
   798  
   799  function showToast(msg) {
   800      let toast =
   801          `<div class="div-toast" id="save-db-modal">
   802          ${msg}
   803          <button type="button" aria-label="Close" class="toast-close">✖</button>
   804      <div>`
   805      $('body').prepend(toast);
   806      $('.toast-close').on('click', removeToast)
   807      setTimeout(removeToast, 1000);
   808  }
   809  
   810  function removeToast() {
   811      $('.div-toast').remove();
   812  }
   813  
   814  function getDashboardId() {
   815      let queryString = decodeURIComponent(window.location.search); //parsing
   816      queryString = queryString.substring(1).split("=");
   817      let uniq = queryString[1];
   818      return uniq;
   819  }
   820  
   821  function handleResize(panelId) {
   822      $(`#panel${panelId}`).resizable(
   823          { containment: "parent" }
   824      );
   825      $(`#panel${panelId}`).on("resizestop", function (event, ui) {
   826          flagDBSaved = false;
   827          panelIndex = $(this).attr("panel-index");
   828          localPanels[panelIndex].gridpos.w = ui.size.width;
   829          localPanels[panelIndex].gridpos.wPercent = ui.size.width / panelContainerWidthGlobal;
   830          localPanels[panelIndex].gridpos.h = ui.size.height;
   831          displayPanel(panelIndex);
   832          resetPanelLocationsHorizontally();
   833          resetPanelLocationsVertically();
   834          resetPanelContainerHeight();
   835          displayPanelsWithoutRefreshing();
   836      })
   837  };
   838  
   839  function resizePanelFontSize(panelIndex, panelId) {
   840      if (panelIndex !== -1) {
   841          let bigNumText = $(`#panel${panelId} .big-number`);
   842          let unit = $(`#panel${panelId} .unit`);
   843          let panelHeight = parseFloat((localPanels[panelIndex].gridpos.h));
   844  
   845          let numFontSize = panelHeight / 2;
   846          let panelWidth = parseFloat((localPanels[panelIndex].gridpos.w));
   847  
   848          if (numFontSize > 170)
   849              numFontSize = 170;
   850          $(bigNumText).css('font-size', `${numFontSize}px`);
   851  
   852          panelWidth = parseFloat((localPanels[panelIndex].gridpos.w));
   853  
   854          if (bigNumText.width() + $(`#panel${panelId} .unit`).width() >= panelWidth) {
   855              numFontSize -= (bigNumText.width()  + $(`#panel${panelId} .unit`).width() - panelWidth) / 3.5;
   856          }
   857          if (numFontSize < 140 && numFontSize > 50){
   858              $('.unit').css('bottom','18px')
   859          }
   860  
   861          if (numFontSize < 50)
   862              numFontSize = 50;
   863          $(bigNumText).css('font-size', `${numFontSize}px`);
   864          let unitSize = numFontSize > 10 ? numFontSize - 40 : 12;
   865          if (unitSize < 25) {
   866              unitSize = 25;
   867              $(unit).css('bottom', `10px`);
   868              $(unit).css('margin-left', `8px`);
   869  
   870          }
   871          if (unitSize > 85)
   872              unitSize = 85;
   873          $(unit).css('font-size', `${unitSize}px`);
   874      } else {
   875          $('.big-number-display-container .big-number').css('font-size', `180px`);
   876      }
   877  }
   878  
   879  function handleDrag(panelId) {
   880      $(`#panel${panelId}`).draggable({
   881          start: function (event, ui) {
   882              $(this).removeClass('temp');
   883          },
   884          obstacle: ".temp",
   885          preventCollision: true,
   886          containment: "parent"
   887      });
   888  
   889      $(`#panel${panelId}`).on("dragstop", function (event, ui) {
   890          flagDBSaved = false;
   891          $(this).addClass('temp')
   892          panelIndex = $(this).attr("panel-index");
   893  
   894          if ((ui.position.left + $(this).width()) < ($('#panel-container')[0].offsetWidth + $('#panel-container')[0].offsetLeft)) {
   895              localPanels[panelIndex].gridpos.x = ui.position.left;
   896              localPanels[panelIndex].gridpos.y = ui.position.top;
   897              if (checkForVertical(ui.position)) {
   898                  resetPanelLocationsVertically();
   899                  resetPanelLocationsHorizontally();
   900              } else {
   901                  resetPanelLocationsHorizontally();
   902                  resetPanelLocationsVertically();
   903              }
   904          }
   905  
   906          resetPanelContainerHeight();
   907          displayPanelsWithoutRefreshing();
   908      })
   909  };
   910  
   911  function checkForVertical(uiPos) {
   912      for (let i = 0; i < localPanels.length; i++) {
   913          let x = localPanels[i].gridpos.x;
   914          let y = localPanels[i].gridpos.y;
   915          let w = localPanels[i].gridpos.w;
   916          let h = localPanels[i].gridpos.h;
   917  
   918          if (uiPos.left == x && uiPos.top == y) continue;
   919  
   920          if (uiPos.left >= x && uiPos.left <= x + w && uiPos.top >= y && uiPos.top <= y + h) {
   921              let distRightBound = x + w - uiPos.left;
   922              let distBottomBound = y + h - uiPos.top;
   923              return distBottomBound < distRightBound;
   924          }
   925      }
   926  }
   927  
   928  var panelLayout =
   929      '<div class="panel-header">' +
   930      '<p>Panel Title</p>' +
   931      '<span class="dropdown-btn" id="panel-options-btn"></span>' +
   932      '<ul class="dropdown-style hidden" id="panel-dropdown-modal">' +
   933      '<li data-value="view" class="panel-view-li"><span class="view"></span>View</li>' +
   934      '<li data-value="edit" class="panel-edit-li"><span class="edit"></span>Edit</li>' +
   935      '<li data-value="duplicate" class="panel-dupl-li"><span class="duplicate"></span>Duplicate</li>' +
   936      '<li data-value="remove" class="panel-remove-li"><span class="remove"></span>Remove</li>' +
   937      '</ul>' +
   938      '</div>' +
   939      `<div class="panel-body">
   940      <div class="panEdit-panel"></div>
   941      </div>
   942      <div class="panel-info-corner"><i class="fa fa-info" aria-hidden="true" id="panel-desc-info"></i></div>
   943  `;
   944  
   945  function checkForAddigInTopRow() {
   946      let temp = [];
   947  
   948      for (let i = 0; i < localPanels.length; i++) {
   949          let y = localPanels[i].gridpos.y;
   950          temp.push([y, i]);
   951      }
   952      temp.sort((a, b) => a[0] - b[0]);
   953      let indices = [];
   954      for (let i = 0; i < temp.length; i++)
   955          indices.push(temp[i][1])
   956  
   957      let topmostY = 10000;
   958      let rightBoundary = 0;
   959      if (indices.length == 0) topmostY = 0;
   960      for (let i = 0; i < indices.length; i++) {
   961          let hPanel = localPanels[indices[i]].gridpos.h;
   962          let wPanel = localPanels[indices[i]].gridpos.w;
   963          let xPanel = localPanels[indices[i]].gridpos.x;
   964          let yPanel = localPanels[indices[i]].gridpos.y;
   965  
   966          if (yPanel <= topmostY) {
   967              topmostY = yPanel;
   968              rightBoundary = Math.max(rightBoundary, xPanel + wPanel);
   969          }
   970          else break;
   971      }
   972  
   973  
   974      let panelContainerWidth = $('#panel-container').width();
   975      if (rightBoundary <= panelContainerWidth * 0.49 + 10) return [true, rightBoundary, topmostY];
   976      else return [false, null, null];
   977  }
   978  
   979  function addPanel(panelToDuplicate) {
   980      flagDBSaved = false;
   981      panelIndex = localPanels.length;
   982      let idpanel = uuidv4();
   983      let panel = $("<div>").append(panelLayout).addClass("panel temp").attr("id", `panel${idpanel}`).attr("panel-index", panelIndex);
   984      $("#panel-container").append(panel);
   985      $(`#panel${idpanel} .panel-header p`).html(`panel${panelIndex}`);
   986      $("#panel" + idpanel + " .panel-header").click(function () {
   987          $("#panel" + idpanel + " .dropdown-btn").toggleClass("active")
   988          $("#panel" + idpanel + " .dropdown-style").toggleClass("hidden");
   989      })
   990      $("#panel" + idpanel + " .dropdown-btn").click(function (e) {
   991          e.stopPropagation();
   992          $("#panel" + idpanel + " .dropdown-btn").toggleClass("active")
   993          $("#panel" + idpanel + " .dropdown-style").toggleClass("hidden");
   994      });
   995      $(`#panel${idpanel} .panel-info-corner`).hide();
   996      let marginTop = 0;
   997  
   998      localPanels.map((localPanel) => {
   999          let val = localPanel.gridpos.y + localPanel.gridpos.h;
  1000          if (val > marginTop) marginTop = val;
  1001      })
  1002  
  1003      let panelElement = document.getElementById(`panel${idpanel}`);
  1004      let panelHeight = panelToDuplicate ? panelToDuplicate.gridpos.h : panelElement.offsetHeight;
  1005      let panelWidth = panelToDuplicate ? panelToDuplicate.gridpos.w : panelElement.offsetWidth;
  1006      let panelTop = panelToDuplicate ? panelToDuplicate.gridpos.y + panelToDuplicate.gridpos.h + 20 : marginTop + 20;
  1007      let panelLeft = panelToDuplicate ? panelToDuplicate.gridpos.x : panelElement.offsetLeft;
  1008      let panelWidthPercentage = panelWidth / panelContainerWidthGlobal;
  1009  
  1010      if (panelToDuplicate == undefined) { // means a new panel is being added
  1011          let [shouldAddInTopRow, rightBoundary, topmostY] = checkForAddigInTopRow();
  1012          if (shouldAddInTopRow) {
  1013              panelLeft = rightBoundary == 0 ? rightBoundary : rightBoundary + 20;
  1014              panelTop = topmostY == 0 ? topmostY + 10 : topmostY;
  1015          }
  1016      }
  1017  
  1018      panelElement.style.position = "absolute"
  1019      panelElement.style.top = panelTop + "px"
  1020      panelElement.style.left = panelLeft + "px"
  1021  
  1022      if (panelToDuplicate) {
  1023          panelToDuplicate.panelId = idpanel;
  1024          panelToDuplicate.name += "Copy";
  1025          panelToDuplicate.panelIndex = panelIndex;
  1026          panelToDuplicate.gridpos.x = panelLeft;
  1027          panelToDuplicate.gridpos.y = panelTop;
  1028          panelToDuplicate.gridpos.h = panelHeight;
  1029          panelToDuplicate.gridpos.w = panelWidth;
  1030          if (panelToDuplicate.description){
  1031              handleDescriptionTooltip(panelToDuplicate.panelId,panelToDuplicate.description)
  1032          }
  1033      }
  1034  
  1035      panelToDuplicate
  1036          ?
  1037          localPanels.push(JSON.parse(JSON.stringify(panelToDuplicate)))
  1038          :
  1039          localPanels.push({
  1040              "name": `panel${panelIndex}`,
  1041              "panelIndex": panelIndex,
  1042              "panelId": idpanel,
  1043              "description": "",
  1044              "chartType": "",
  1045              "unit": "",
  1046              "dataType": "",
  1047              "gridpos": {
  1048                  "h": panelHeight,
  1049                  "w": panelWidth,
  1050                  "x": panelLeft,
  1051                  "y": panelTop,
  1052                  "wPercent": panelWidthPercentage,
  1053              },
  1054              "queryType": "",
  1055          });
  1056      if (!panelToDuplicate) {        
  1057          editPanelInit(panelIndex);
  1058          $('.panelEditor-container').show();
  1059          $('#app-container').hide();
  1060          $('.panelDisplay #panelLogResultsGrid').empty();
  1061          $('.panelDisplay .big-number-display-container').hide();
  1062          $('.panelDisplay #empty-response').hide();
  1063      }
  1064      resetPanelContainerHeight();
  1065  
  1066      handlePanelView();
  1067      handlePanelEdit();
  1068      handlePanelRemove(idpanel);
  1069      handlePanelDuplicate();
  1070      handleDrag(idpanel);
  1071      handleResize(idpanel);
  1072      $(`#panel${idpanel}`).get(0).scrollIntoView({ behavior: 'smooth' });
  1073  
  1074  }
  1075  
  1076  function resetPanelContainerHeight() {
  1077      let panelContainerMinHeight = 0;
  1078      localPanels.map((localPanel, index) => {
  1079          let val = localPanel.gridpos.y + localPanel.gridpos.h;
  1080          if (val > panelContainerMinHeight) panelContainerMinHeight = val;
  1081      })
  1082      let panelContainer = document.getElementById('panel-container');
  1083      panelContainer.style.minHeight = panelContainerMinHeight + 50 + "px";
  1084  }
  1085  
  1086  window.onbeforeunload = function () {
  1087      if (!flagDBSaved) {
  1088          return "Unsaved panel changes will be lost if you leave the page, are you sure?";
  1089      }
  1090      else return;
  1091  };
  1092  
  1093  
  1094  // DASHBOARD SETTINGS PAGE
  1095  let editPanelFlag = false;
  1096  function handleDbSettings() {
  1097      if ($('.panelEditor-container').css('display') !== 'none') {
  1098          $('.panelEditor-container').hide();
  1099          editPanelFlag =true;
  1100      } else {
  1101          $('#app-container').hide();
  1102      }
  1103      $('.dbSet-container').show();
  1104  
  1105      $('.dbSet-name').html(dbName)
  1106      $('.dbSet-dbName').val(dbName)
  1107      $('.dbSet-dbDescr').val(dbDescr)
  1108      $('.dbSet-jsonModelData').val(JSON.stringify(JSON.unflatten({
  1109          description: dbDescr,
  1110          name: dbName,
  1111          timeRange: timeRange,
  1112          panels: localPanels,
  1113          refresh: dbRefresh,
  1114      }), null, 2))
  1115      $('.dbSet-dbName').on("change keyup paste", function () {
  1116          dbName = $('.dbSet-dbName').val()
  1117          $('.dbSet-name').html(dbName)
  1118      })
  1119      $('.dbSet-dbDescr').on("change keyup paste", function () {
  1120          dbDescr = $('.dbSet-dbDescr').val()
  1121          $('.dbSet-dbDescr').html(dbDescr)
  1122          $('.dbSet-jsonModelData').val(JSON.stringify(JSON.unflatten({
  1123              description: dbDescr,
  1124              name: dbName,
  1125              timeRange: timeRange,
  1126              panels: localPanels,
  1127              refresh: dbRefresh,
  1128          }), null, 2))
  1129      })
  1130  
  1131      //get dashboard data from database
  1132      $.ajax({
  1133          method: "get",
  1134          url: "api/dashboards/" + dbId,
  1135          headers: {
  1136              'Content-Type': 'application/json; charset=utf-8',
  1137              'Accept': '*/*'
  1138          },
  1139          dataType: 'json',
  1140          crossDomain: true,
  1141      }).then(function (res) {
  1142          console.log(JSON.stringify(res))
  1143          $(".dbSet-dbName").val(res.name);
  1144          $(".dbSet-dbDescr").val(res.description);
  1145          $('.dbSet-jsonModelData').val(JSON.stringify(JSON.unflatten(res), null, 2))
  1146      })
  1147  
  1148      showGeneralDbSettings();
  1149      addDbSettingsEventListeners();
  1150  }
  1151  
  1152  function showGeneralDbSettings() {
  1153      $('.dbSet-general').addClass('selected')
  1154      $('.dbSet-generalHTML').removeClass('hide');
  1155  
  1156      $('.dbSet-jsonModel').removeClass('selected');
  1157      $('.dbSet-jsonModelHTML').addClass('hide')
  1158  }
  1159  
  1160  function showJsonModelDbSettings() {
  1161      $('.dbSet-general').removeClass('selected')
  1162      $('.dbSet-generalHTML').addClass('hide');
  1163  
  1164      $('.dbSet-jsonModel').addClass('selected');
  1165      $('.dbSet-jsonModelHTML').removeClass('hide')
  1166  }
  1167  
  1168  function addDbSettingsEventListeners() {
  1169      $('.dbSet-general').on('click', showGeneralDbSettings);
  1170      $('.dbSet-jsonModel').on('click', showJsonModelDbSettings)
  1171  }
  1172  
  1173  function saveDbSetting() {
  1174      let trimmedDbName = $('.dbSet-dbName').val().trim();
  1175      let trimmedDbDescription = $(".dbSet-dbDescr").val().trim();
  1176      if (!trimmedDbName) {
  1177          // Show error message using error-tip and popupOverlay
  1178          $('.error-tip').addClass('active');
  1179          $('.popupOverlay, .popupContent').addClass('active');
  1180          $('#error-message').text('Dashboard name cannot be empty.');
  1181          return;
  1182      }
  1183  
  1184  
  1185      dbName = trimmedDbName;
  1186      dbDescr = trimmedDbDescription;
  1187  
  1188  
  1189      updateDashboard()
  1190      .then(updateSuccessful => {
  1191          if (updateSuccessful) {
  1192              $('#app-container').show();
  1193              $('.dbSet-container').hide();
  1194          }
  1195      })
  1196  }
  1197  
  1198  $('#error-ok-btn').click(function () {
  1199      $('.popupOverlay, .popupContent').removeClass('active');
  1200      $('.error-tip').removeClass('active');
  1201  });
  1202  
  1203  function discardDbSetting() {
  1204      if(editPanelFlag){
  1205          $('.panelEditor-container').show();
  1206          editPanelFlag=false;
  1207      }else{
  1208          $('#app-container').show();
  1209      }
  1210      $('.dbSet-dbName').val("");
  1211      $('.dbSet-dbDescr').val("");
  1212      $('.dbSet-jsonModelData').val("");
  1213      $('.dbSet-container').hide();
  1214      dbName = dbData.name;
  1215      dbDescr = dbData.description;
  1216  }
  1217  
  1218  function setRefreshItemHandler(){
  1219      $(".refresh-range-item").removeClass("active");
  1220      if(dbRefresh){
  1221          $(`.refresh-range-item:contains('${dbRefresh}')`).addClass("active");
  1222          $('.refresh-container #refresh-picker-btn span').text(dbRefresh);
  1223          startRefreshInterval(dbRefresh)
  1224      }else{
  1225          $('.refresh-container #refresh-picker-btn span').text("");
  1226          $(`.refresh-range-item:contains('Off')`).addClass("active");
  1227      }
  1228  }
  1229  
  1230  function refreshRangeItemHandler(evt){
  1231      $.each($(".refresh-range-item.active"), function () {
  1232          $(this).removeClass('active');
  1233      });
  1234      $(evt.currentTarget).addClass('active');
  1235      let refreshInterval = $(evt.currentTarget).attr('id');
  1236      if(refreshInterval==="0"){
  1237          dbRefresh = "";
  1238          $('.refresh-container #refresh-picker-btn span').html("");
  1239      }else{
  1240          dbRefresh = refreshInterval;
  1241          $('.refresh-container #refresh-picker-btn span').html(refreshInterval);
  1242      }
  1243      startRefreshInterval(refreshInterval)
  1244  }
  1245  
  1246  let intervalId;
  1247  
  1248  function startRefreshInterval(refreshInterval) {
  1249      let parsedRefreshInterval = parseInterval(refreshInterval);
  1250      clearInterval(intervalId);
  1251      if (parsedRefreshInterval > 0) {
  1252              intervalId = setInterval(function () {
  1253                  refreshDashboardHandler();
  1254              }, parsedRefreshInterval);
  1255  
  1256      }else{
  1257          pauseRefreshInterval();
  1258      }
  1259  }
  1260  
  1261  
  1262  function pauseRefreshInterval() {
  1263      clearInterval(intervalId);
  1264      return 0;
  1265  }
  1266  
  1267  function parseInterval(interval) {
  1268      if(interval==="0"){
  1269          pauseRefreshInterval();
  1270          return;
  1271      }
  1272      const regex = /(\d+)([smhd])/;
  1273      const match = interval.match(regex);
  1274      const value = parseInt(match[1]);
  1275      const unit = match[2];
  1276  
  1277      switch (unit) {
  1278          case 'm':
  1279              return value * 60 * 1000;
  1280          case 'h':
  1281              return value * 60 * 60 * 1000;
  1282          case 'd':
  1283              return value * 24 * 60 * 60 * 1000;
  1284          default:
  1285              throw new Error("Invalid interval unit");
  1286      }
  1287  }