github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/contacts.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 contactGridDiv = null;
    20  let contactRowData  = [];
    21  var contactData = {};
    22  var allContactsArray;
    23  let contactEditFlag = 0;
    24  
    25  /* Contact Form Component - This component is used on both the "contacts.html" and "alert.html" pages. */
    26  const contactFormHTML = `
    27  <form id="contact-form">
    28  <div class="d-flex btn-container">
    29      <button class="btn" id="cancel-contact-btn" type="button">Cancel</button>
    30      <button class="btn" id="save-contact-btn" type="submit">Save</button>
    31  </div>
    32  <div class="add-contact-form">
    33      <div>
    34          <label for="contact-name">Contact point name</label>
    35          <input type="text" class="form-control" placeholder="Enter a contact point name" id="contact-name" required >
    36      </div>
    37      <div id="main-container">
    38          <div class="contact-container">
    39              <div>
    40                  <label for="type">Type</label>
    41                  <div class="dropdown">
    42                      <button class="btn dropdown-toggle" type="button" id="contact-types"
    43                          data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
    44                          data-bs-toggle="dropdown">
    45                          <span>Slack</span>
    46                          <img class="dropdown-arrow orange" src="assets/arrow-btn.svg">
    47                          <img class="dropdown-arrow blue" src="assets/up-arrow-btn-light-theme.svg">
    48                      </button>
    49                      <div class="dropdown-menu box-shadow contact-options">
    50                          <li id="option-0" class="contact-option active">Slack</li>
    51                          <li id="option-1" class="contact-option">Webhook</li>
    52                      </div>
    53                  </div>
    54              </div>
    55              <div class="slack-container">
    56                  <div>
    57                  <div style="position: relative;">
    58                      <label for="slack-channel-id">Channel ID</label>
    59                      <input type="text" class="form-control" id="slack-channel-id" style="position: relative;" required >
    60                      <i class="fa fa-info-circle position-absolute info-icon" rel="tooltip" style="display: block; top: 29px;"
    61              title="Specify channel, private group, or IM channel (can be an encoded ID or a name)."
    62              id="info-slack-channel-id"></i>
    63              </div>
    64                  </div>
    65                  <div style="position: relative;">
    66                  <label for="slack-token">Slack API Token</label>
    67                  <input type="text" class="form-control" id="slack-token" style="position: relative;" required>
    68                      <i class="fa fa-info-circle position-absolute info-icon" rel="tooltip" style="display: block; top: 29px;"
    69                      title="Provide a Slack bot API token (starts with “xoxb”)."
    70                      id="info-slack-token"></i>
    71                  </div>
    72              </div>
    73              <div class="webhook-container">
    74                  <label for="webhook">Webhook URL</label>
    75                  <input type="text" class="form-control" id="webhook-id">
    76              </div>
    77          </div>
    78      </div>
    79      <button class="add-new-contact-type btn" type="button">
    80          <span>
    81              <img src="./assets/add-icon.svg" class="add-icon">Add new contact type
    82          </span>
    83      </button>
    84  </div>
    85  </form>
    86  `;
    87  
    88  $(document).ready(function () {
    89      if (Cookies.get('theme')) {
    90          theme = Cookies.get('theme');
    91          $('body').attr('data-theme', theme);
    92          
    93      }
    94      $('.theme-btn').on('click', themePickerHandler);
    95      $('#new-contact-point').on('click',initializeContactForm)
    96      $('#contact-form-container').css('display', 'none');
    97      getAllContactPoints();
    98      if(window.location.href.includes("alert.html")){
    99          initializeContactForm();
   100      }
   101  
   102      const tooltipIds = ["info-slack-channel-id", "info-slack-token"];
   103  
   104      tooltipIds.forEach(id => {
   105          $(`#${id}`).tooltip({
   106              delay: { show: 0, hide: 300 },
   107              trigger: "click"
   108          }).on("click", function () {
   109              $(`#${id}`).tooltip("show");
   110          });
   111      });
   112  
   113      $(document).mouseup(function (e) {
   114          if ($(e.target).closest(".tooltip-inner").length === 0) {
   115              tooltipIds.forEach(id => $(`#${id}`).tooltip("hide"));
   116          }
   117      });
   118  });
   119  
   120  $(document).on('click', '.contact-option', setContactTypes);
   121  
   122  function initializeContactForm(contactId) {
   123      $("#new-contact-point").css("display", "none");
   124      $("#alert-grid-container").css("display", "none");
   125      $("#contact-form-container").css("display", "block");
   126      const formContainer = $("#contact-form-container");
   127  
   128      if (formContainer) {
   129          formContainer.append(contactFormHTML);
   130          $(".slack-container").css("display", "block");
   131          $(".webhook-container").css("display", "none");
   132          if (contactEditFlag) {
   133              showContactFormForEdit(contactId);
   134          }
   135      }
   136  
   137      const contactForm = $("#contact-form");
   138      contactForm.on("submit", (e) => submitAddContactPointForm(e));
   139  
   140      //cancel form
   141      $("#cancel-contact-btn").on("click", function () {
   142          resetContactForm();
   143          if (window.location.href.includes("alert.html")) {
   144              $(".popupOverlay, .popupContent").removeClass("active");
   145              $("#main-container .contact-container:gt(0)").remove();
   146          } else {
   147              window.location.href = "../contacts.html";
   148          }
   149      });
   150  
   151      //add new contact type container
   152      $(".add-new-contact-type").on("click", function () {
   153          let newContactContainer = $(".contact-container").first().clone();
   154          newContactContainer.find(".form-control").val("");
   155          newContactContainer.prepend(
   156              '<button class="btn-simple del-contact-type" type="button"></button>',
   157          );
   158          newContactContainer.appendTo("#main-container");
   159          const newChannelIdInfoId = "info-slack-channel-id-" + Date.now();
   160          const newTokenInfoId = "info-slack-token-" + Date.now();
   161  
   162          newContactContainer.find('.fa-info-circle').eq(0).attr('id', newChannelIdInfoId);
   163          newContactContainer.find('.fa-info-circle').eq(1).attr('id', newTokenInfoId);
   164  
   165          // Initialize tooltips for the newly added container
   166          [newChannelIdInfoId, newTokenInfoId].forEach(id => {
   167              $(`#${id}`).tooltip({
   168                  delay: { show: 0, hide: 300 },
   169                  trigger: "hover" // Change "click" to "hover"
   170              });
   171          });
   172      });
   173  
   174      //remove contact type container
   175      $("#main-container").on("click", ".del-contact-type", function () {
   176          $(this).closest(".contact-container").remove();
   177      });
   178  }
   179  
   180  function setContactTypes() {
   181      const selectedOption = $(this).html();
   182      const container = $(this).closest('.contact-container');
   183      container.find('.contact-option').removeClass('active');
   184      container.find('#contact-types span').html(selectedOption);
   185      $(this).addClass('active');
   186      container.find('.slack-container input, .webhook-container input').removeAttr('required').val();
   187      container.find('.slack-container, .webhook-container').css('display', 'none');
   188      if (selectedOption === 'Slack') {
   189          container.find('.slack-container input').attr('required', 'true');
   190          container.find('.slack-container').css('display', 'block');
   191      } else if (selectedOption === 'Webhook') {
   192          container.find('.webhook-container').css('display', 'block');
   193          container.find('.webhook-container input').attr('required', 'true');
   194      } 
   195  }
   196  
   197  function resetContactForm(){
   198      $('#contact-form input').val('');
   199      $('#contact-types span').text('Slack');
   200      $('.slack-container').css('display', 'block');
   201      $('.webhook-container').css('display', 'none');
   202      $('.contact-option').removeClass('active');
   203      $('.contact-options #option-0').addClass('active');
   204      contactData = {};
   205  }
   206  
   207  function setContactForm() {
   208      contactData.contact_name = $('#contact-name').val();
   209      contactData.slack = [];
   210      contactData.webhook = [];
   211      
   212      $('.contact-container').each(function() {
   213        let contactType = $(this).find('#contact-types span').text();
   214        
   215          if (contactType === 'Slack') {
   216              let slackValue = $(this).find('#slack-channel-id').val();
   217              let slackToken = $(this).find('#slack-token').val();
   218              if (slackValue && slackToken) {
   219                  let slackContact  = {
   220                      channel_id: slackValue,
   221                      slack_token: slackToken
   222                      };
   223                      contactData.slack.push(slackContact);
   224  
   225            
   226          }
   227        } else if (contactType === 'Webhook') {
   228          let webhookValue = $(this).find('#webhook-id').val();
   229          if (webhookValue) {
   230              contactData.webhook.push(webhookValue);
   231          }
   232      }
   233      });
   234      contactData.pager_duty = "";
   235  }
   236  
   237  function submitAddContactPointForm(e){
   238      e.preventDefault(); 
   239      setContactForm();
   240      contactEditFlag ? updateContactPoint(contactData) : createContactPoint(contactData);
   241  }
   242  
   243  function createContactPoint(){
   244      $.ajax({
   245          method: "post",
   246          url: "api/alerts/createContact",
   247          headers: {
   248              'Content-Type': 'application/json; charset=utf-8',
   249              'Accept': '*/*'
   250          },
   251          data: JSON.stringify(contactData),
   252          dataType: 'json',
   253          crossDomain: true,
   254      }).then(res=>{
   255          if(window.location.href.includes("alert.html")){
   256              $(".popupOverlay, .popupContent").removeClass("active");
   257              getAllContactPoints(contactData.contact_name);
   258          } else {
   259              window.location.href = "../contacts.html";
   260          }
   261          resetContactForm();
   262          showToast(res.message);
   263      }).catch(err=>{
   264          if (window.location.href.includes("alert.html")) {
   265              $(".popupOverlay, .popupContent").removeClass("active");
   266          }
   267          showToast(err.responseJSON.error);
   268      })
   269  }
   270  
   271  function updateContactPoint(){
   272      $.ajax({
   273          method: "post",
   274          url: "api/alerts/updateContact",
   275          headers: {
   276              'Content-Type': 'application/json; charset=utf-8',
   277              'Accept': '*/*'
   278          },
   279          data: JSON.stringify(contactData),
   280          dataType: 'json',
   281          crossDomain: true,
   282      }).then(res=>{
   283          resetContactForm();
   284          window.location.href='../contacts.html';
   285          showToast(res.message);
   286      }).catch(err=>{
   287          showToast(err.responseJSON.error);
   288      })
   289  }
   290  
   291  function getAllContactPoints(contactName){
   292      $.ajax({
   293          method: "get",
   294          url: "api/alerts/allContacts",
   295          headers: {
   296              'Content-Type': 'application/json; charset=utf-8',
   297              'Accept': '*/*'
   298          },
   299          dataType: 'json',
   300          crossDomain: true,
   301      }).then(function (res) {
   302          allContactsArray = res.contacts;
   303          if(window.location.href.includes("alert.html")){
   304              const contact = allContactsArray.find(contact => contact.contact_name === contactName);
   305              $('#contact-points-dropdown span').html(contact.contact_name);
   306              $('#contact-points-dropdown span').attr('id', contact.contact_id);
   307          }else
   308              displayAllContacts(res.contacts);
   309      })
   310  }
   311  
   312  class btnRenderer {
   313  	init(params) {
   314  		this.eGui = document.createElement('span');
   315  		this.eGui.innerHTML = `<div id="alert-grid-btn">
   316  				<button class='btn' id="editbutton"></button>
   317                  <button class="btn-simple" id="delbutton"></button>
   318  				</div>`;
   319  		this.eButton = this.eGui.querySelector('.btn');
   320  		this.dButton = this.eGui.querySelector('.btn-simple');
   321  
   322  		this.eButton.addEventListener('click',function(){
   323              contactEditFlag = 1;
   324              initializeContactForm(params.data.contactId);
   325          });   
   326  		this.dButton.addEventListener('click',()=>getAllAlertsWithSameContactPoint(params.data));
   327  	}
   328  
   329  	getGui() {
   330  		return this.eGui;
   331  	}
   332  	refresh(params) {
   333  		return false;
   334  	}
   335  }
   336  
   337  function deleteContactPrompt(data) {
   338      $('#contact-name-placeholder').html('<strong>' + data.contactName + '</strong>');
   339      $('.popupOverlay, .popupContent').addClass('active');
   340      $('#cancel-btn, .popupOverlay').click(function () {
   341          $('.popupOverlay, .popupContent').removeClass('active');
   342      });
   343      $('#delete-btn').off('click');
   344      $('#delete-btn').on('click',function (){
   345          $.ajax({
   346              method: 'delete',
   347              url: 'api/alerts/deleteContact',
   348              headers: {
   349                  'Content-Type': 'application/json; charset=utf-8',
   350                  Accept: '*/*',
   351              },
   352              data: JSON.stringify({
   353                  contact_id: data.contactId,
   354              }),
   355              crossDomain: true,
   356          }).then(function (res) {
   357              let deletedRowID = data.rowId;
   358              contactGridOptions.api.applyTransaction({
   359                  remove: [{ rowId: deletedRowID }],
   360              });
   361  
   362              showToast(res.message);
   363              $('.popupOverlay, .popupContent').removeClass('active');
   364          });
   365      });
   366  }
   367  
   368  function showDeleteContactDialog(data,matchingAlertNames){
   369      $('#contact-name-placeholder-delete-dialog').html('<strong>' + data.contactName + '</strong>');
   370      $('.popupOverlay, .delete-dialog').addClass('active');
   371      let el = $('#alert-listing');
   372      el.html(``);
   373      const maxHeight = 100;
   374      matchingAlertNames.forEach(function (alertName) {
   375          el.append(`<div class="alert-dropdown-item">${alertName}</div>`);
   376      });
   377  
   378      // Apply styling to make the dropdown scrollable
   379  el.css({
   380      'max-height': `${maxHeight}px`,
   381      'overflow-y': 'auto'
   382  });
   383      $("body").css("cursor","default");
   384      $('#cancel-btn, .popupOverlay').click(function () {
   385          $('.popupOverlay, .delete-dialog').removeClass('active');
   386      });
   387  }
   388  
   389  function getAllAlertsWithSameContactPoint(data){
   390      const contactId= data.contactId;
   391      const matchingAlertNames = [];
   392      $.ajax({
   393          method: "get",
   394          url: "api/allalerts",
   395          headers: {
   396              'Content-Type': 'application/json; charset=utf-8',
   397              'Accept': '*/*'
   398          },
   399          dataType: 'json',
   400          crossDomain: true,
   401      }).then(function (res) {
   402          if(res.alerts){  
   403              for (const alert of res.alerts) {
   404              if (alert.contact_id === contactId) {
   405                  matchingAlertNames.push(alert.alert_name);
   406          }}}
   407            if(matchingAlertNames.length > 0){
   408              showDeleteContactDialog(data,matchingAlertNames);
   409            }else{
   410              deleteContactPrompt(data);
   411            }
   412      })
   413  }
   414  
   415  
   416  let contactColumnDefs = [
   417      {
   418          field: "rowId",
   419  		hide: true
   420      },
   421      {
   422          field: "contactId",
   423  		hide: true
   424      },
   425      {
   426          headerName: "Contact point name",
   427          field: "contactName",
   428          sortable: true,
   429          width:100,
   430      },
   431      {
   432          headerName: "Type",
   433          field: "type",
   434          width:100,
   435      },
   436      {
   437          headerName: "Actions",
   438          cellRenderer: btnRenderer,
   439          width:50,
   440      },
   441  ];
   442  
   443  const contactGridOptions = {
   444      columnDefs: contactColumnDefs,
   445  	rowData: contactRowData,
   446  	animateRows: true,
   447  	rowHeight: 44,
   448      headerHeight:32,
   449  	defaultColDef: {
   450  		icons: {
   451  			sortAscending: '<i class="fa fa-sort-alpha-up"/>',
   452  			sortDescending: '<i class="fa fa-sort-alpha-down"/>',
   453  		},
   454          cellClass: 'align-center-grid',
   455          resizable: true,
   456          sortable: true,
   457  	},
   458  	enableCellTextSelection: true,
   459  	suppressScrollOnNewData: true,
   460  	suppressAnimationFrame: true,
   461  	getRowId: (params) => params.data.rowId,
   462  	onGridReady(params) {
   463  		this.gridApi = params.api;
   464  	},
   465  };
   466  
   467  function capitalize(str) {
   468      return str.charAt(0).toUpperCase() + str.slice(1);
   469  }
   470  
   471  function displayAllContacts(res){
   472      if (contactGridDiv === null) {
   473          contactGridDiv = document.querySelector('.all-contacts-grid');
   474          new agGrid.Grid(contactGridDiv, contactGridOptions);
   475      }
   476      contactGridOptions.api.setColumnDefs(contactColumnDefs);
   477      let newRow = new Map()
   478      $.each(res, function (key, value) {
   479          let contactType = 
   480          Object.entries(value)
   481          .filter(([k,v]) => v!=null && v.length !== 0 && k !== 'contact_name' && k !== 'contact_id' && k !== 'org_id')
   482          .map(([k, v]) => {
   483              return v.length >1 ? `${capitalize(k)} (${v.length})` : capitalize(k);
   484          } );
   485          let type = contactType.join(', ')
   486  
   487          newRow.set("rowId", key);
   488          newRow.set("contactId", value.contact_id);
   489          newRow.set("contactName", value.contact_name);
   490          newRow.set("type", type);
   491          contactRowData = _.concat(contactRowData, Object.fromEntries(newRow));
   492      })
   493      contactGridOptions.api.setRowData(contactRowData);
   494      contactGridOptions.api.sizeColumnsToFit();
   495  }
   496  
   497  function showToast(msg) {
   498      let toast =
   499          `<div class="div-toast" id="save-db-modal"> 
   500          ${msg}
   501          <button type="button" aria-label="Close" class="toast-close">✖</button>
   502      <div>`
   503      $('body').prepend(toast);
   504      $('.toast-close').on('click', removeToast)
   505      setTimeout(removeToast, 2000);
   506  }
   507  
   508  function removeToast() {
   509      $('.div-toast').remove();
   510  }
   511  
   512  //Edit Contact Point 
   513  function showContactFormForEdit(contactId) {
   514      let data = allContactsArray.find(function(obj) {return obj.contact_id === contactId});
   515      $('#contact-name').val(data.contact_name);
   516      
   517      let isFirst = true;
   518      Object.keys(data).forEach(function(key) {
   519        if (key === 'contact_name' || key === 'contact_id') {
   520          return;
   521        }
   522      
   523        let value = data[key];
   524        if (value != null && value.length > 0) {
   525         value.forEach(function(value){ let contactContainer;
   526          if (isFirst) {
   527              contactContainer = $('.contact-container');
   528              isFirst = false;
   529          } else {
   530              contactContainer = $('.contact-container').first().clone();
   531              contactContainer.prepend('<button class="btn-simple del-contact-type" type="button"></button>');
   532          }
   533          contactContainer.find('#contact-types span').text(key.charAt(0).toUpperCase() + key.slice(1));
   534          if (key === 'slack') {
   535            contactContainer.find('.webhook-container').css('display', 'none');
   536            contactContainer.find('.slack-container').css('display', 'block');
   537            contactContainer.find('.slack-container #slack-channel-id').val(value.channel_id);
   538            contactContainer.find('.slack-container #slack-token').val(value.slack_token);
   539          }
   540          if (key === 'webhook') {
   541              contactContainer.find('.webhook-container').css('display', 'block');
   542              contactContainer.find('.slack-container').css('display', 'none');
   543            }
   544          if (key != 'slack'){
   545              contactContainer.find(`.${key}-container .form-control`).val(value);
   546          }
   547          contactContainer.appendTo('#main-container');
   548        })}
   549      });
   550      if(contactEditFlag){contactData.contact_id=data.contact_id;}
   551  }