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 }