1// "use strict" 2/* 3* webportal.js 4* Author: Benoit Mercier 5* Copyright 2015-2022, Universal Devices inc. 6*/ 7 8// Set temporarily to true when portal should not be accessed 9var maintenanceMode=false; 10 11var registeredUser=null; // Mirror of udiportal session storage 12var currentISY=null; // Mirror of udidevice session storage 13 14// Handles to objects returned by the form initializations 15var domainDialog, 16 userDialog, 17 loginDialog, 18 myProfileDialog, 19 migrateDialog, 20 eisyuiSettingsDialog, 21 isyClaimDialog, 22 isyNoteDialog, 23 isyContactDialog, 24 // isyFirmwareDialog, 25 eisyProvisioningDialog, 26 isyInfoDialog, 27 isyImportDialog, 28 isyAccessLicenseDialog, 29 isyAccessLicenseRenewDialog, 30 nsPurchaseDialog, 31 isyOpenadrRegUploadDialog, 32 isyOpenadrRegDialog, 33 liveMessageDialog, 34 pinDialog, 35 passwordChangeDialog, 36 selfRegistrationDialog, 37 lastConnectedDialog, 38 echoSpokenNodesDialog, 39 echoSpokenNodesEditDialog, 40 echoSpokenNodesAVEditDialog, 41 echoSpokenRoomEditDialog, 42 ghSpokenNodesDialog, 43 ghSpokenNodesEditDialog, 44 ghSpokenRoomEditDialog, 45 iftttInNodesDialog, 46 iftttInNodesEditDialog, 47 iftttInNodesKeyDialog, 48 nsListDialog, 49 hintEditorDialog, 50 activityLogDialog, 51 ipalRenewalOptionEditDialog, 52 ipalNotificationOptionEditDialog, 53 ratePlanEditDialog, 54 downloadUsersDialog, 55 downloadUsageDialog, 56 downloadStatsDialog, 57 nsConfigDialog, 58 nsOccupancyDialog, 59 nsOccupancyEditDialog, 60 nsOccupancyLocationEditDialog, 61 nsOccupancyUrlDialog, 62 nsOccupancy2Dialog, 63 nsOccupancy2EditDialog, 64 nsOccupancy2GeofenceEditDialog, 65 nsOccupancy2DeviceEditDialog, 66 nsOccupancy2GeofenceUrlDialog, 67 nsOccupancy2DevicePinDialog, 68 udCommsEditDialog, 69 udCommsSurveyDialog, 70 udCommsDeliveryDialog; 71 72var allDialogs = []; 73var tempDialogs = []; // Information and confirmation messages 74 75// PASSWORD REGEX 76// Must contain uppercase and/or lowercase letters 77// Must contain digits and/or special characters 78// Must be 8-20 in length 79 80// Beta functions allowed (this.isPilots will be true in renderTools) 81var pilots = ['$superadmin', 82 '56cca3962e48ea151a015852', // Javier 83 '5b98260f136f942ff6c578ac', // Bob 84 '63486a0a867e3029deda5817' // JR dev 85 // '583703092dc9c25a649e2ebb' // UUID 00:21:b9:02:5f:aa, trying to decommission manually 86]; 87 88var rememberMeInfo 89 90var passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d|.*[!?&@#$-\/:-@\[-_])(\w|\d|[!?&@#$-_]){8,20}$/; 91//var passwordRegex = /^(?=[a-zA-Z]).(?=.*\d|.*[!?&@#$-_])(\w|\d|[!?&@#$-_]){7,20}$/ //This one required to start with chars 92var emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; 93var emailRegexWithSpace = /^\s*[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\s*$/; 94var uuidRegex = /^(?:uuid:)?(?=00:*21:*b9|00:*0d:*b9)([a-f\d]{2}):*([a-f\d]{2}):*([a-f\d]{2}):*([a-f\d]{2}):*([a-f\d]{2}):*([a-f\d]{2})$/i; 95var nodeNameRegex = /^[\w .\-\+,=_|]+$/i; 96var roomNameRegex = /^([A-Za-z\s])+$/i; 97var spokenRegex = /^([a-zA-ZñáÃóúà âéèêëïîôçùûüÿ'\-]+\s)*[a-zA-ZñáÃóúà âéèêëïîôçùûüÿ'\-]*$/i; // /^([a-zA-Z]+\s)*[a-zA-Z]*$/i 98 99 100var accessLicenseExpirationSoonDays = 30; 101var nodeServerMinimumFirmware='5.0.9'; 102 103// These are flags to stop giving warnings when adding/editing/deleting Alexa spokens 104var skillNotLinkedWarningDelete = true 105var skillNotLinkedWarningAddUpdate = true 106var incorrectUnitWarningDelete = true 107var incorrectUnitWarningAddUpdate = true 108 109var tinymceLoaded= false 110 111// Called when logging in with Google. Function has to be global. 112function gsi_login(args) { 113 loginDialog.gsi_login(args) 114} 115 116function initTinymceTextarea() { 117 // Push notifications editor 118 $('textarea#ud-comms-content-push, textarea#ud-comms-content-appLink-text-header, textarea#ud-comms-content-appLink-text-footer, textarea#ipal_notification_option_pushContent').tinymce({ 119 selector: 'textarea#ud-comms-content-push', 120 plugins: 'lists link', 121 toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright al
121ignjustify | outdent indent | link', 122 // convert_newlines_to_brs : true, 123 // forced_root_block : false 124 // toolbar: 'bullist numlist', 125 // inline: true 126 // plugins: 'a11ychecker advcode casechange export formatpainter image editimage linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tableofcontents tinycomments tinymcespellchecker', 127 // toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter image editimage pageembed permanentpen table tableofcontents', 128 // toolbar_mode: 'floating', 129 // tinycomments_mode: 'embedded', 130 // tinycomments_author: 'Author name', 131 }) 132 133 // Editor for emails 134 $('textarea#ipal_notification_option_emailBody').tinymce({ 135 selector: 'textarea#ud-comms-content-push', 136 plugins: 'lists', 137 toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright alignjustify | outdent indent | link', 138 plugins: 'link', 139 // br_in_pre: false 140 convert_newlines_to_brs : true, 141 // forced_root_block : false 142 // toolbar: 'bullist numlist', 143 // inline: true 144 // plugins: 'a11ychecker advcode casechange export formatpainter image editimage linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tableofcontents tinycomments tinymcespellchecker', 145 // toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter image editimage pageembed permanentpen table tableofcontents', 146 // toolbar_mode: 'floating', 147 // tinycomments_mode: 'embedded', 148 // tinycomments_author: 'Author name', 149 }) 150 151 $('#ee-admin-tabs-config textarea.tinymce').tinymce({ 152 selector: 'textarea#comms-content-push', 153 plugins: 'lists', 154 toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright al
154ignjustify | outdent indent', 155 // convert_newlines_to_brs : true, 156 // forced_root_block : false 157 // toolbar: 'bullist numlist', 158 // inline: true 159 // plugins: 'a11ychecker advcode casechange export formatpainter image editimage linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tableofcontents tinycomments tinymcespellchecker', 160 // toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter image editimage pageembed permanentpen table tableofcontents', 161 // toolbar_mode: 'floating', 162 // tinycomments_mode: 'embedded', 163 // tinycomments_author: 'Author name', 164 }); 165 166 $('textarea#comms-content-push, textarea#comms-content-appLink-text-header, textarea#comms-content-appLink-text-footer').tinymce({ 167 selector: 'textarea#comms-content-push', 168 plugins: 'lists', 169 toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright alignjustify | outdent indent', 170 // convert_newlines_to_brs : true, 171 // forced_root_block : false 172 // toolbar: 'bullist numlist', 173 // inline: true 174 // plugins: 'a11ychecker advcode casechange export formatpainter image editimage linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tableofcontents tinycomments tinymcespellchecker', 175 // toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter image editimage pageembed permanentpen table tableofcontents', 176 // toolbar_mode: 'floating', 177 // tinycomments_mode: 'embedded', 178 // tinycomments_author: 'Author name', 179 }); 180} 181 182function loadTinymce() { 183 if (!tinymceLoaded) { 184 $.getScript('https://cdn.tiny.cloud/1/b2rxvxrnov0nv5qt9zbh2u69kc7obg7p64aeaz9e2yxts1sz/tinymce/6/tinymce.min.js') 185 .then(function () { 186 return $.getScript('https://cdn.jsdelivr.net/npm/@tinymce/tinymce-jquery@1/dist/tinymce-jquery.min.js') 187 }) 188 .then(function () { 189 tinymceLoaded = true 190 initTinymceTextarea() 191 }) 192 } 193} 194 195// Used by MyProfile & user dialog 196// var preferredIsyChangeWarning = 197// 'You changed your preferred ISY, but it appears that you have an Alexa skill enabled. ' + 198// 'When changing the preferred ISY, you also need to disable and re-enable the skill.<br/><br/>Continue?'; 199 200 201// localStorage: 202// udidata: { lastLoginAction: 'isy' } 203 204// sessionStorage: 205// udidevice: { uuid: '11:22:33:44:55:66', alias: 'production', isyKey: '??' } 206// udiportal: { registeredUser: registeredUser } 207 208// RATES Project 209// var ratesInit=null; // Function defined in webrates.js 210// var ratesMenuOptions=null; // Function defined in webrates.js 211// var ratesInitialized=false; 212//'560895243a29b8641009c6be' 213// var ratesAccounts = ['58fe94cbd61ce57076697989']; // '$superadmin', 214 215// SCE Pilot project 216// var sceInit=null; // Function defined in websce.js 217// var sceMenuOptions=null; // Function defined in websce.js 218// var sceInitialized=false; 219// var sceAccounts = [/*'$superadmin',*/ '5d94eb333888c5344881c589', '56a6dc41ae6343360e2f2813']; 220// 5d94eb333888c5344881c589 SCE UD 221// 56a6dc41ae6343360e2f2813 P.Chan 222// SCE Pilot project 223// var sceInit=null; // Function defined in websce.js 224// var sceMenuOptions=null; // Function defined in websce.js 225// var sceInitialized=false; 226// var sceAccounts = [/*'$superadmin',*/ '5d94eb333888c5344881c589', '56a6dc41ae6343360e2f2813']; 227// 5d94eb333888c5344881c589 SCE UD 228// 56a6dc41ae6343360e2f2813 P.Chan 229 230// Valid location hash / parameters 231// #passwordreset 232// #registration 233// #udajax 234// #myprofile 235// #license 236// ?uuid 237// ?key 238// ?redirect (Optional) 239// #tab-domains 240// #tab-ee 241// #tab-ee-admin 242// #ns-purchase 243// ?pid 244// ?nsid 245// ?redirect 246// ?beta 247// ?test 248// OTHER 249// ?emailverification 250// ?client_id // If we have a client_id, it's a redirect from the authorization page. 251// ?refresh=domain (will refresh domainInfo after logging in from session storage data) 252// ?accessToken=<token> Bypasses login. Meant for temporary usage. Token must have decent life left 253 254function testUI() { 255/* 256 var roomsPromise, 257 lookupDataPromise, 258 lookupDataPromisePgm, 259 lookupDataPromiseStateVar, 260 lookupDataPromiseNetworkRsc, 261 uuid = '00:21:b9:01:fd:26', 262 domain = '$superadmin'; 263 264 function loadRooms(uuid) {
265 roomsPromise = ajaxJsonGet('/api/voice/amazon/spoken/rooms', $.param({ uuid: uuid, domain: domain })); 266 267 roomsPromise 268 .fail(function(jqXHR, textStatus, errorThrown) { 269 if (jqXHR.status==503) { 270 errorMessageDialog('ISY is not online') 271 } 272 else if (jqXHR.status==504) { 273 errorMessageDialog('Timeout occured. Please try again.') 274 } 275 else if (jqXHR.status==550) { // Custom error message 276 var data = JSON.parse(jqXHR.responseText); 277 278 if (data.message) { 279 errorMessageDialog(data.message) 280 } 281 else { 282 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 283 } 284 } 285 else { 286 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 287 } 288 }) 289 .done(); 290 291 return roomsPromise; 292 } 293 294 lookupDataPromise = ajaxJsonGet('/api/nodes/lookup', $.param({ uuid: uuid, domain: domain })) 295 .done(function(content, status, jqXHR){ 296 content.data.devices.map(function (item) { 297 item.data = item.address; 298 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 299 item.option = item; // Self reference; 300 }) 301 302 content.data.devices.sort(function(a, b) { 303 return a.name.localeCompare(b.name) } 304 ); 305 306 content.data.scenes.map(function (item) { 307 item.data = item.address; 308 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 309 item.option = item; // Self reference; 310 }) 311 312 content.data.scenes.sort(function(a, b) { 313 return a.name.localeCompare(b.name) } 314 ); 315 }) 316 .fail(function (jqXHR, textStatus, errorThrown) { 317 jqXHR.requested = 'Devices'; // Track which request it is in the jqXHR object 318 }) 319 320 lookupDataPromisePgm = ajaxJsonGet('/api/programs/lookup', $.param({uuid: uuid, domain: domain})) 321 .done(function(content, status, jqXHR){ 322 content.data.map(function (item) { 323 item.data = item.address; 324 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 325 item.option = item; // Self reference; 326 }) 327 328 content.data.sort(function(a, b) { 329 return a.name.localeCompare(b.name) } 330 ); 331 }) 332 .fail(function (jqXHR, textStatus, errorThrown) { 333 jqXHR.requested = 'Programs'; // Track which request it is in the jqXHR object 334 }) 335 336 lookupDataPromiseStateVar = ajaxJsonGet('/api/statevars/lookup', $.param({uuid: uuid, domain: domain})) 337 .done(function(content, status, jqXHR){ 338 content.data.map(function (item) { 339 item.data = item.address; 340 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 341 item.option = item; // Self reference; 342 }) 343 344 content.data.sort(function(a, b) { 345 return a.name.localeCompare(b.name) } 346 ); 347 }) 348 .fail(function (jqXHR, textStatus, errorThrown) { 349 jqXHR.requested = 'State variables'; // Track which request it is in the jqXHR object 350 jqXHR.skipErrorMessage = true; 351 }) 352 353 lookupDataPromiseNetworkRsc = ajaxJsonGet('/api/networkrsc/lookup', $.param({uuid: uuid, domain: domain})) 354 .done(function(content, status, jqXHR){ 355 content.data.map(function (item) { 356 item.data = item.address; 357 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 358 item.option = item; // Self reference; 359 }) 360 361 content.data.sort(function(a, b) { 362 return a.name.localeCompare(b.name) } 363 ); 364 }) 365 .fail(function (jqXHR, textStatus, errorThrown) { 366 jqXHR.requested = 'Network resources'; // Track which request it is in the jqXHR object 367 jqXHR.skipErrorMessage = true; 368 }) 369 370 $.when(lookupDataPromise, lookupDataPromiseStateVar, lookupDataPromiseNetworkRsc) 371 .fail(function(jqXHR, textStatus, errorThrown) { 372 if (jqXHR.status==503) { 373 errorMessageDialog('ISY is not online') 374 } 375 else if (jqXHR.status==404) { 376 // No need to warn the user about state variables not loading. It may be normal. 377 if (!jqXHR.skipErrorMessage) { 378 errorMessageDialog(jqXHR.requested + ' are not available') 379 } 380 } 381 else { 382 errorMessageDialog(jqXHR.requested + ' are not available<br>' + 'HTTP error ' + jqXHR.status, 'Error', 400) 383 } 384 }) 385 386 loadRooms(uuid) 387 388// var roomsPromise = echoSpokenNodesDialog.loadRooms(uuid); 389 390 $.when(lookupDataPromise, lookupDataPromiseStateVar, lookupDataPromiseNetworkRsc, lookupDataPromisePgm, roomsPromise) 391 .then(function(nodes, vars, net, pgm, rooms) { 392// var content=contentNodes[0]; 393// var rooms=roomsAjax[0]; 394// console.log('nodes', nodes) 395// console.log('vars', vars) 396// console.log('net', net) 397// console.log('rooms', rooms) 398 399 echoSpokenNodesAVEditDialog.setFields({ category: 'av', uuid: uuid, domain: domain, lookupNodes: nodes[0], lookupPgms: pgm[0], lookupVars: vars[0], lookupNet: net[0], rooms: rooms[0].data, editMode:false }); 400 echoSpokenNodesAVEditDialog.modal.dialog('open'); 401 }) 402 .fail(function(jqXHR, textStatus, errorThrown) { 403 errorMessageDialog("A/V data not available", 'Error', 350) 404 }) 405 .done(); 406*/ 407} 408 409 410// Used in the Select tools menu && tableIsys 411// function isRatesAccount(account) { 412// if (typeof account == 'string') { 413// return ratesAccounts.includes(account); 414// } 415// else { 416// console.log('account', account) 417// return ratesAccounts.includes(account._id) || ratesAccounts.includes(account.parent); 418// } 419// } 420 421// True if the logged in user is in the RATES root account 422// function isRatesRootAccount(domain) { 423// return ratesAccounts.includes(domain) 424// } 425 426// Used in the Select tools menu && tableIsys 427// function isSceAccount(account) { 428// if (typeof account == 'string') { 429// return sceAccounts.includes(account); 430// } else if (account) { 431// return sceAccounts.includes(account._id) || sceAccounts.includes(account.parent); 432// } else { 433// return false 434// } 435// } 436 437// True if the logged in user is in the SCE root account 438// function isSceRootAccount(domain) { 439// return sceAccounts.includes(domain) 440// } 441 442function recaptchaRender() { 443 grecaptcha.render('recaptcha_selfr', { 444 'sitekey' : serverConfig.recaptchaKeySite 445 }); 446} 447 448 449function rememberMeActive() { 450 return getLocalStorage('udi_remember_me'); 451} 452 453function rememberMe() { 454 var cookieInfo=null; 455 456 if (rememberMeActive()) { 457 if (rememberMeInfo) { // rememberMeInfo comes from rememberme.js 458 try { 459 cookieInfo = JSON.parse(rememberMeInfo); 460 } catch (e) {} 461 } 462 } 463 464 return cookieInfo; 465} 466 467function rememberMeSet(username, password) { 468 if (rememberMeActive() && typeof rememberMeInfo !== 'undefined') { 469 rememberMeInfo = JSON.stringify({ 470 username: username, 471 password: password 472 }) 473 } 474} 475 476// Page load & login sequence: 477// init 478// If available udiportal from Session storage, 479// Set registeredUser 480// run afterLogin 481// If not, login 482// run afterLogin 483// 484// afterLogin 485// If we have an email verification token, process it 486// If email is not verified, prompt user 487// run afterLoginEmailVerified 488// 489// afterLoginEmailVerified 490// Update history 491// Reset query string 492// Run afterLoginGoToISY or afterLoginPortalUI 493// 494// afterLoginPortalUI 495// 496 497// Initialize Paypal script, then continueInit() 498function initMain() { 499 var isProduction = serverConfig.ppEnv==='production' 500 501 if (!isProduction) { 502 console.log('Using Paypal Sandbox') 503 } 504 505 var ppClientId = isProduction ? serverConfig.ppClientIdProduction : serverConfig.ppClientIdSandbox; 506 var result = $.ajax({
507 url: 'https://www.paypal.com/sdk/js?client-id=' + ppClientId + '¤cy=USD', 508 dataType: 'script', 509 timeout: 1000, // Need this in case of scipt load failure. 510 cache: true 511 }) 512 .fail(function(data, status, jqXHR) { 513 console.log('Error loading Paypal script', status); 514 }) 515 .always(function(data, status, jqXHR) { 516 continueInit(); 517 }) 518} 519 520function continueInit() { 521 var data; 522 523 // Initialises the tab UI, and set events for when tab are switched 524 $('#tabs').tabs({ create: tabCreate, activate: tabNavigate }) 525 $('#admin-tabs').tabs({ activate: tabNavigateAdmin }) 526 $('#body').show() // Body is hidden by default, until tabs are initialized 527 528 polyfill(); 529 530 initCustomCombobox(); 531 532 if (maintenanceMode) { 533 errorMessageDialog('ISY Portal is currently under maintenance.<br> Please come back later.', 'Warning') 534 } else { 535 loginDialog = loginDialogInit(); 536 selfRegistrationDialog = selfRegistrationDialogInit(); 537 538 if ($.gup('error')) { 539 // Error message to display? Possible case after a failed Green button authorization 540 errorMessageDialog(decodeURIComponent($.gup('error')), 'Error', 500) 541 } 542 543 // NOTE: No matter what the path is below, we need to make sure to call continueInit2() only once. 544 // To do this, we set a deferred promise which starts resolved, so we can chain promises 545 546 // Start a chain of promises 547 $.Deferred().resolve().promise() 548 .then(function () { 549 // If we have an accessToken, we will bypass login 550 if (!registeredUser && $.gup('accessToken')) { 551 var accessToken = $.gup('accessToken') 552 553 return $.ajax({ 554 type: 'GET', 555 url: '/api/profile', 556 headers: { 557 Authorization: 'Bearer ' + accessToken 558 }, 559 }) 560 .then(function (profile) { 561 if (profile && profile.domainId) { 562 return $.ajax({ 563 type: 'GET', 564 url: '/api/domain', 565 data: {id: profile.domainId}, 566 // API requires no auth 567 // headers: { 568 // Authorization: 'Bearer ' + accessToken 569 // } 570 }) 571 .then(function (domain) { 572 // console.log('profile', profile) 573 // console.log('domain', domain) 574 setRegisteredUserFromProfileAndAccessToken(profile, accessToken, domain.data) 575 // console.log('registeredUser', registeredUser) 576 // continueInit2() 577 // afterLogin({user: registeredUser}); 578 }) 579 } 580 }) 581 .fail(function (jqXHR, textStatus, errorThrown) { 582 if (jqXHR.status === 403 || jqXHR.status === 401) { 583 console.log('Provided accessToken is not valid. Will show login dialog.') 584 } else { 585 console.log('Request to /api/profile failed with provided accessToken with HTTP %s', jqXHR.status) 586 } 587 }) 588 } else if (!registeredUser) { 589 // See if we have a session cookie set (is this just a page refresh?) 590 data = getSessionStorage('udiportal'); 591 592 if (data && data.registeredUser) { 593 registeredUser = data.registeredUser; 594 setLastConnected(data.registeredUser.lastConnected); 595 596 if (data.registeredUser.jwt) { 597 var refreshTokenExpiry = getJwtExpiry(registeredUser.jwt.refreshToken) 598 599 // If refreshToken is already expired, we need to go to login instead 600 if (refreshTokenExpiry.getTime() < Date.now()) { 601 setRegisteredUser(null); 602 } 603 } 604 } 605 606 if (registeredUser) { 607 if ($.gup('refresh') === 'domain') { 608 return ajaxJsonGet('/api/domain', $.param({id: registeredUser.domain})) 609 .then(function (data) { 610 setRegisteredUserDomainInfo(data.data); 611 // continueInit2() 612 // afterLogin({user: registeredUser}); 613 }) 614 .fail(function (jqXHR, textStatus, errorThrown) { 615 errorMessageDialog('Error refreshing account information. Please logout and login again', 'Error', 350) 616 }) 617 } 618 } 619 } 620 }) 621 .always(function () { 622 continueInit2() 623 }) 624 } 625} 626 627function continueInit2() { 628 // If we did not have a 'udiportal' in session storage, and no accessToken provided, prompt for a login 629 if (!registeredUser) { 630 var data = getLocalStorage('udidata'); 631 var loginAction='portal'; 632 var cookieInfo=null; 633 634 if (data && data.lastLoginAction) { 635 loginAction = data.lastLoginAction; 636 } 637 638 loginDialog.resetForm(); 639 640 if (location.hash && location.hash==='#passwordreset') { 641 loginDialog.setFields({ rememberMeCheckbox: rememberMeActive(), lastLoginAction: loginAction, mode:2 }); // mode: 0=login, 2=forgot your password 4=password change 642 loginDialog.modal.dialog('open'); 643 } else if (location.hash && location.hash==='#registration') { 644 loginDialog.setFields({ lastLoginAction: loginAction }) // If user clicks cancel on self-registration 645 selfRegistrationDialog.setFields({ email: '', cb: afterLogin }); 646 selfRegistrationDialog.modal.dialog('open'); 647 } else { 648 loginDialog.setFields({ cb: afterLogin, lastLoginAction: loginAction, rememberMeCheckbox: rememberMeActive(), cookieInfo: rememberMe() }) 649 loginDialog.modal.dialog('open'); 650 } 651 } else { 652 afterLogin({user: registeredUser}); 653 } 654 655 domainDialog = domainDialogInit(); 656 userDialog = userDialogInit(); 657 isyClaimDialog = isyClaimDialogInit(); 658 isyNoteDialog = isyNoteDialogInit(); 659 isyContactDialog = isyContactDialogInit(); 660 // isyFirmwareDialog = isyFirmwareDialogInit(); 661 eisyProvisioningDialog = eisyProvisioningDialogInit() 662 isyInfoDialog = isyInfoDialogInit(); 663 isyImportDialog = isyImportDialogInit(); 664 isyAccessLicenseDialog = isyAccessLicenseDialogInit();
665 isyAccessLicenseRenewDialog = isyAccessLicenseRenewDialogInit(); 666 nsPurchaseDialog = nsPurchaseDialogInit(); 667 isyOpenadrRegUploadDialog = isyOpenadrRegUploadDialogInit(); 668 isyOpenadrRegDialog = isyOpenadrRegDialogInit(); 669 liveMessageDialog = liveMessageDialogInit(); 670 lastConnectedDialog = lastConnectedDialogInit(); 671 myProfileDialog = myProfileDialogInit(); 672 migrateDialog = migrateDialogInit(); 673 eisyuiSettingsDialog = eisyuiSettingsDialogInit(); 674 pinDialog = pinDialogInit(); 675 passwordChangeDialog = passwordChangeDialogInit(); 676 echoSpokenNodesDialog = echoSpokenNodesDialogInit(); 677 echoSpokenNodesEditDialog = echoSpokenNodesEditDialogInit(); 678 echoSpokenNodesAVEditDialog = echoSpokenNodesAVEditDialogInit(); 679 echoSpokenRoomEditDialog = echoSpokenRoomEditDialogInit(); 680 ghSpokenNodesDialog = ghSpokenNodesDialogInit(); 681 ghSpokenNodesEditDialog = ghSpokenNodesEditDialogInit(); 682 ghSpokenRoomEditDialog = ghSpokenRoomEditDialogInit(); 683 iftttInNodesDialog = iftttInNodesDialogInit(); 684 iftttInNodesEditDialog = iftttInNodesEditDialogInit(); 685 iftttInNodesKeyDialog = iftttInNodesKeyDialogInit(); 686 nsListDialog = nsListDialogInit(); 687 hintEditorDialog = hintEditorDialogInit(); 688 activityLogDialog = activityLogDialogInit(); 689 ipalRenewalOptionEditDialog = ipalRenewalOptionEditDialogInit(); 690 ipalNotificationOptionEditDialog = ipalNotificationOptionEditDialogInit(); 691 ratePlanEditDialog = ratePlanEditDialogInit(); 692 downloadUsersDialog = downloadUsersDialogInit(); 693 downloadUsageDialog = downloadUsageDialogInit(); 694 downloadStatsDialog = downloadStatsDialogInit(); 695 nsConfigDialog = nsConfigDialogInit(); 696 nsOccupancyDialog = nsOccupancyDialogInit(); 697 nsOccupancyEditDialog = nsOccupancyEditDialogInit(); 698 nsOccupancyLocationEditDialog = nsOccupancyLocationEditDialogInit(); 699 nsOccupancyUrlDialog = nsOccupancyUrlDialogInit(); 700 nsOccupancy2Dialog = nsOccupancy2DialogInit(); 701 nsOccupancy2EditDialog = nsOccupancy2EditDialogInit(); 702 nsOccupancy2GeofenceEditDialog = nsOccupancy2GeofenceEditDialogInit(); 703 nsOccupancy2DeviceEditDialog = nsOccupancy2DeviceEditDialogInit(); 704 nsOccupancy2GeofenceUrlDialog = nsOccupancy2GeofenceUrlDialogInit(); 705 nsOccupancy2DevicePinDialog = nsOccupancy2DevicePinDialogInit(); 706 udCommsEditDialog = udCommsEditDialogInit(); 707 udCommsSurveyDialog = udCommsSurveyDialogInit(); 708 udCommsDeliveryDialog = udCommsDeliveryDialogInit(); 709 710 // Used in logout to close all dialogs 711 allDialogs = [ 712 domainDialog.modal, 713 userDialog.modal, 714 loginDialog.modal, 715 myProfileDialog.modal, 716 migrateDialog.modal, 717 eisyuiSettingsDialog.modal, 718 isyClaimDialog.modal, 719 isyNoteDialog.modal, 720 isyContactDialog.modal, 721 // isyFirmwareDialog.modal, 722 eisyProvisioningDialog.modal, 723 isyInfoDialog.modal, 724 isyImportDialog.modal, 725 isyAccessLicenseDialog.modal, 726 isyAccessLicenseRenewDialog.modal, 727 nsPurchaseDialog.modal, 728 isyOpenadrRegUploadDialog.modal, 729 isyOpenadrRegDialog.modal, 730 liveMessageDialog.modal, 731 pinDialog.modal, 732 passwordChangeDialog.modal, 733 selfRegistrationDialog.modal, 734 lastConnectedDialog.modal, 735 echoSpokenNodesDialog.modal, 736 echoSpokenNodesEditDialog.modal, 737 echoSpokenNodesAVEditDialog.modal, 738 echoSpokenRoomEditDialog.modal, 739 ghSpokenNodesDialog.modal, 740 ghSpokenNodesEditDialog.modal, 741 ghSpokenRoomEditDialog.modal, 742 iftttInNodesDialog.modal, 743 iftttInNodesEditDialog.modal, 744 iftttInNodesKeyDialog.modal, 745 nsListDialog.modal, 746 hintEditorDialog.modal, 747 activityLogDialog.modal, 748 ipalRenewalOptionEditDialog.modal, 749 ipalNotificationOptionEditDialog.modal, 750 ratePlanEditDialog.modal, 751 downloadUsersDialog.modal, 752 downloadUsageDialog.modal, 753 downloadStatsDialog.modal, 754 nsConfigDialog.modal, 755 nsOccupancyDialog.modal, 756 nsOccupancyEditDialog.modal, 757 nsOccupancyLocationEditDialog.modal, 758 nsOccupancyUrlDialog.modal, 759 nsOccupancy2Dialog.modal, 760 nsOccupancy2EditDialog.modal, 761 nsOccupancy2GeofenceEditDialog.modal, 762 nsOccupancy2DeviceEditDialog.modal, 763 nsOccupancy2GeofenceUrlDialog.modal, 764 nsOccupancy2DevicePinDialog.modal, 765 udCommsEditDialog.modal, 766 udCommsSurveyDialog.modal, 767 udCommsDeliveryDialog.modal 768 ]; 769 770 initUI() // Initialize buttons 771 772 // Extends the Date object 773 initDateLog() 774 775 initEeUI() 776 777 // Test code only 778 // testUI(); 779} 780 781// This is used when launched with ?accessToken=<accessToken> 782// profile is the output from /api/profile 783// accessToken will only provide temporary access to APIs 784function setRegisteredUserFromProfileAndAccessToken(profile, accessToken, domainInfo) { 785 registeredUser = { 786 basicAuth: null, 787 password: null, 788 jwt: null, 789 accessToken: accessToken, 790 domain: profile.domainId, 791 domainInfo: domainInfo, 792 domainadmin: profile.domainAdmin || (profile.domain === '$superadmin'), // superadmin is implicitly considered a domainadmin as well 793 emailVerified: profile.emailVerified, 794 isDeveloper: profile.isDeveloper, 795 isEEAdmin: profile.isEEAdmin, 796 isReviewer: profile.isReviewer, 797 isRootAccountUser: (!domainInfo.parent || domainInfo.parent===''), 798 isy: profile.preferredIsy, 799 lasConnected: [], // We don't have it in /api/profile. This will hide the last connected time from UI 800 notificationmaint: profile.notificationMaintenance, 801 notificationnews: profile.notificationNews, 802 passwordchange: false, // We won't request for a new password 803 superadmin: (profile.domain === '$superadmin'), 804 username: profile.username, 805 _id: profile.id 806 } 807 808 // Don't store it! If user refreshes the screen, too bad, he will have to re-login 809 deleteSessionStorage('udiportal'); 810 811 // We don't care about the button label - We will not get there when we have an accessToken provided 812 // $('#domainadd').button('option', 'label', registeredUser.superadmin ? 'Add Account' : 'Add Sub-Account'); 813} 814 815function credsToRegisteredUser(credentials) { 816 if (credentials.password) { 817 registeredUser.password = credentials.password 818 registeredUser.basicAuth = btoa(registeredUser.username + ':' + credentials.password) 819 registeredUser.jwt = null 820 registeredUser.accessToken = null 821 } else if (credentials.jwt) { 822 registeredUser.jwt = credentials.jwt 823 registeredUser.password = null 824 registeredUser.basicAuth = null 825 registeredUser.accessToken = null 826 } else { 827 // This should never happen 828 registeredUser.jwt = null 829 registeredUser.password = null 830 registeredUser.basicAuth = null 831 registeredUser.accessToken = null 832 console.log('No credentials are available') 833 } 834} 835 836// User is the user profile object, password is the actual password we got at login 837function setRegisteredUser(user, credentials, domainInfo) { 838 if (user) { 839 registeredUser = user; 840 841 if (credentials) 842 credsToRegisteredUser(credentials) 843 844 registeredUser.superadmin = (registeredUser.domain === '$superadmin'); 845 registeredUser.domainadmin |= registeredUser.superadmin; // superadmin is implicitly considered a domainadmin as well 846 registeredUser.isRootAccountUser = (!domainInfo.parent || domainInfo.parent===''); 847 registeredUser.domainInfo = domainInfo; // Data obtain from /api/login 848 setSessionStorage('udiportal', { registeredUser: registeredUser }); 849 850 // Change the Add account button label 851 // UDI admins can create both accounts and sub-accounts. Regular users, just sub-
851accounts 852 $('#domainadd').button('option', 'label', registeredUser.superadmin ? 'Add Account' : 'Add Sub-Account'); 853 } 854 else { 855 registeredUser = null; 856 deleteSessionStorage('udiportal'); 857 } 858} 859 860// Used after an update to the user profile 861function setRegisteredUserProfile(user) { 862 if (user) { 863 // Keep a copy as we don't know the creds. 864 var password = registeredUser.password; 865 var basicAuth = registeredUser.basicAuth; 866 var jwt = registeredUser.jwt; 867 var superadmin = registeredUser.superadmin; 868 var domainInfo = registeredUser.domainInfo; 869 870 registeredUser = user; 871 registeredUser.password = password; 872 registeredUser.basicAuth = basicAuth; 873 registeredUser.jwt = jwt; 874 registeredUser.superadmin = superadmin; 875 registeredUser.domainadmin |= superadmin; // superadmin is implicitly considered a domainadmin as well 876 registeredUser.isRootAccountUser = (!domainInfo.parent || domainInfo.parent===''); 877 registeredUser.domainInfo = domainInfo; 878 879 setSessionStorage('udiportal', { registeredUser: registeredUser }); 880 } 881} 882 883// Used after an update to the domainInfo 884function setRegisteredUserDomainInfo(domainInfo) { 885 // Make sure we are attaching the right domain 886 if (domainInfo && domainInfo._id === registeredUser.domain) { 887 registeredUser.domainInfo = domainInfo; 888 setSessionStorage('udiportal', { registeredUser: registeredUser }); 889 } 890} 891 892// Used only when changing password 893function setRegisteredUserCredentials(credentials) { 894 credsToRegisteredUser(credentials) 895 setSessionStorage('udiportal', { registeredUser: registeredUser }); 896} 897 898// After adding a children sub-account, add it to our children 899function setRegisteredUserAddChildrenDomain(newDomainRecord) { 900 if (registeredUser.domain === newDomainRecord.parent) { 901 registeredUser.domainInfo.children.push(newDomainRecord._id) 902 } 903 904 setSessionStorage('udiportal', { registeredUser: registeredUser }); 905} 906 907function getJwtExpiry(jwt) { 908 try { 909 const payloadBase64 = jwt.split('.')[1]; 910 const payloadJson = atob(payloadBase64); 911 const payload = JSON.parse(payloadJson); 912 return payload.exp ? new Date(payload.exp * 1000) : null; 913 } catch (error) { 914 console.error('Invalid JWT format:', error); 915 return null; 916 } 917} 918 919var currentRefreshPromise = null 920 921function getJwtAccessToken() { 922 return $.when() 923 .then(function() { 924 if (registeredUser && registeredUser.jwt) { 925 if (!registeredUser.jwt.accessTokenExpiry) { 926 registeredUser.jwt.accessTokenExpiry = getJwtExpiry(registeredUser.jwt.accessToken) 927 } 928 929 // Expiring in less than a minute? 930 if (registeredUser.jwt.accessTokenExpiry.getTime() < Date.now() + (60*1000)) { 931 // If we already have a refresh underway, return the result of that refresh instead 932 if (currentRefreshPromise) { 933 console.log('JWT refresh already in progress') 934 return currentRefreshPromise 935 } 936 937 var refreshTokenExpiry = getJwtExpiry(registeredUser.jwt.refreshToken) 938 939 // If refresh token is not expired 940 if (refreshTokenExpiry.getTime() > Date.now()) { 941 console.log('Refreshing JWT tokens') 942 943 currentRefreshPromise = $.ajax({ 944 url: '/api/jwt/refresh', 945 type: 'POST', 946 contentType: 'application/x-www-form-urlencoded', 947 data: new URLSearchParams({ 948 refreshToken: registeredUser.jwt.refreshToken 949 }).toString() 950 }) 951 .then(function (data) { 952 if (data.data.accessToken && data.data.refreshToken) { 953 setRegisteredUserCredentials({ 954 jwt: { 955 accessToken: data.data.accessToken, 956 refreshToken: data.data.refreshToken 957 } 958 }) 959 960 return data.data.accessToken 961 } 962 }) 963 .fail(function (jqXHR, textStatus, errorThrown) { 964 console.log('Error renewing JWT tokens') 965 logout({ sessionError: true }) 966 }) 967 .done(() => { 968 currentRefreshPromise = null 969 }) 970 971 return currentRefreshPromise 972 } else { 973 console.log('Refresh token is expired') 974 logout({ sessionExpired: true }) 975 } 976 } else { 977 // console.log('Using existing JWT access token') 978 return registeredUser.jwt.accessToken 979 } 980 } 981 }) 982} 983 984// Functions in parent window called from child windows 985function getRegisteredUser() { 986 return JSON.stringify(registeredUser); 987} 988 989function getCurrentISY() { 990 return JSON.stringify(currentISY); 991} 992 993// Functions called from child windows 994function initRegisteredUser() { 995 var user = getSessionStorage('udiportal'); 996 997 if (user) { // Use sessionStorage data if available 998 registeredUser = user.registeredUser; 999 } 1000 else if (window.opener) { // Otherwise, request to the parent (IE/Edge will use this on first launch, then use sessionStorage) 1001 var registeredUserStr = window.opener.getRegisteredUser(); 1002 1003 if (registeredUserStr) { 1004 registeredUser = JSON.parse(registeredUserStr); 1005 1006 // Keep copy in this session storage, in case parent window gets closed. 1007 setSessionStorage('udiportal', { registeredUser: registeredUser }); 1008 } 1009 } 1010 else { 1011 console.log('Initializing RegisteredUser: Neither session storage nor parent window are available') 1012 } 1013} 1014 1015function initCurrentISY() { 1016 var data = getSessionStorage('udidevice'); 1017 1018 if (data) { // Use sessionStorage data if available 1019 currentISY = data; 1020 } 1021 else if (window.opener) { // Otherwise, request to the parent (IE/Edge will use this on first launch, then use sessionStorage) 1022 var currentISYStr = window.opener.getCurrentISY(); 1023 1024 if (currentISYStr) { 1025 currentISY = JSON.parse(currentISYStr); 1026 1027 // Keep copy in this session storage, in case parent window gets closed. 1028 setSessionStorage('udidevice', currentISY); 1029 } 1030 } 1031 else { 1032 console.log('Initializing currentISY: Neither session storage nor parent window are available') 1033 } 1034} 1035 1036 1037// Process this after user login 1038// data contains data returned by /login 1039// loginAction is 'portal' or 'isy' 1040var afterLogin = function (data, loginAction) { 1041 // if (registeredUser.domainInfo && isRatesAccount(registeredUser.domainInfo) && !ratesInitialized) { 1042 // $.getScript('webrates.js') 1043 // .done(function() { 1044 // ratesInitialized=true; 1045 // if (ratesInit) {
1046 // ratesInit(); 1047 // } 1048 // }) 1049 // .fail(function() { 1050 // console.log('ratesInit() was not loaded') 1051 // }); 1052 // } 1053 1054 // if (registeredUser.domainInfo && isSceAccount(registeredUser.domainInfo) && !sceInitialized) { 1055 // $.getScript('MonthPicker.js') 1056 // .fail(function() { 1057 // console.log('MonthPicker.js was not loaded') 1058 // }); 1059 // 1060 // // Load MonthPicker CSS 1061 // $('<link>').appendTo('head').attr({ 1062 // type: 'text/css', 1063 // rel: 'stylesheet', 1064 // href: 'MonthPicker.css' 1065 // }); 1066 // 1067 // $.getScript('websce.js') 1068 // .done(function() { 1069 // sceInitialized=true; 1070 // if (sceInit) { 1071 // sceInit(); 1072 // } 1073 // }) 1074 // .fail(function() { 1075 // console.log('sceInit() was not loaded') 1076 // }); 1077 // } 1078 1079 // If page parameter has email verification token, process it (see emailverification.js) 1080 var emailVerificationToken = $.gup('emailverification'); 1081 1082 if (emailVerificationToken) { 1083 if (data.user.emailVerified) { 1084 errorMessageDialog('Your email address is already verified.', 'Warning'); 1085 afterLoginEmailVerified(data, loginAction); 1086 } 1087 else { 1088 ajaxJsonPost('/api/emailverification/code/' + emailVerificationToken, $.param( { email: data.user.username } )) 1089 .then(function(data, status, jqXHR) { 1090 1091 if (data.o2) { 1092 informationMessageDialog('Your email address verification is successful.<br/><br/>' + 1093 'Please click Ok to finalize the authorization process.') 1094 .then(function () { 1095 // We make the same call as we do from the authozization page, using the captured query string we had 1096 var originalLocationSearch = atob(data.o2) 1097 window.location.replace('/o2/authorize' + originalLocationSearch + '&' + 1098 $.param({ email: registeredUser.username, password: registeredUser.password, new:true })); 1099 // the new:true above indicates that we went through the new registration process 1100 }) 1101 .done(); 1102 } else { 1103 informationMessageDialog('Your email address verification is successful.') 1104 .then(function () { 1105 setRegisteredUserProfile(data.data); // We need to update the profile with the emailVerified flag now set to true 1106 afterLoginEmailVerified({ user: data.data, account: registeredUser.domainInfo }, loginAction); 1107 }) 1108 .done(); 1109 } 1110 }) 1111 .fail(function(jqXHR, textStatus, errorThrown) { 1112 if (jqXHR.status==404) { 1113 errorMessageDialog('email address verification failed'); 1114 } 1115 else { 1116 errorMessageDialog('API error; HTTP error ' + jqXHR.status) 1117 } 1118 }) 1119 .done(); 1120 } 1121 } 1122 else if (!data.user.emailVerified) { 1123 errorMessageDialog('Your email address is not verified.<br><br>Click Ok to send a verification email to ' + data.user.username) 1124 .then(function() { 1125 ajaxJsonPost('/api/emailverification/request', $.param( { email: data.user.username } )) 1126 .then(function(data, status, jqXHR) { 1127 informationMessageDialog('In order to verify your email address, please check your inbox and follow the instructions.') 1128 }) 1129 .fail(function(jqXHR, textStatus, errorThrown) { 1130 errorMessageDialog('API error; HTTP error ' + jqXHR.status) 1131 }) 1132 .done(); 1133 }) 1134 .done(); 1135 } else { 1136 afterLoginEmailVerified(data, loginAction) 1137 } 1138}; 1139 1140function resetLocation() { 1141 // Reset the query string & hash without reloading page 1142 if (history.pushState) { 1143 var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname; 1144 window.history.pushState({path:newurl},'',newurl); 1145 } 1146 else { 1147 location.search=''; //Reset the query & hash string, the old way, with page reload 1148 } 1149} 1150 1151function afterLoginEmailVerified(data, loginAction) { 1152 // Save the login action as selected in the login dialog 1153 setLocalStorage('udidata', { lastLoginAction: loginAction }); 1154 1155 // #udajax allows to load udajax directly, without changing the preference 1156 if (location.hash === '#udajax') { 1157 loginAction = 'isy'; 1158 } 1159 1160 if (loginAction === 'isy') { 1161 resetLocation(); 1162 afterLoginGoToISY(data); 1163 } 1164 else { 1165 //resetLocation() // We do it later. 1166 afterLoginPortalUI(data); 1167 } 1168} 1169 1170function afterLoginGoToISY(data) { 1171 if (!data.user.isy || !data.user.isy.length) { 1172 errorMessageDialog('Preferred ISY not set on user profile', 'Preferred ISY not set') 1173 .then(function () { 1174 afterLoginPortalUI(data); 1175 }) 1176 .done(); 1177 } 1178 else { 1179 var preferredIsy = data.user.isy; 1180 1181 ajaxJsonGet('/api/isy', $.param({ uuid: data.user.isy })) 1182 .done(function (data) { 1183 if (!data.data.online) { 1184 errorMessageDialog('Your ISY is not online', 'ISY not online') 1185 .then(function () { 1186 afterLoginPortalUI(data); 1187 }) 1188 .done(); 1189 } 1190 else { 1191 currentISY = { uuid: preferredIsy, alias: data.data.domain[0].alias, isyKey: data.data.isyKey }; 1192 setSessionStorage('udidevice', currentISY); 1193 launchWebAccess('_self'); 1194 } 1195 }) 1196 .fail(function(jqXHR, textStatus, errorThrown) { 1197 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 1198 }) 1199 .done(); 1200 } 1201} 1202 1203 1204function afterLoginPortalUI(data) { 1205 var domainType; 1206 1207 $('#tabs').show(); 1208 $('#footer').show(); 1209 1210 if (data.domain && data.domain.type) { 1211 domainType = data.domain.type; // 0=Utility, 1=Corp 1212 } 1213 1214 switchLoginState(true); // Adjusts the top-right tab 1215 1216 // default active tab is ISY's list (in case we just re-logged in with less rights) 1217 $('#tabs').tabs('option', 'active', 0); 1218 $('#tabs').tabs('refresh'); 1219 1220 // Remove logged out message 1221 $('#loggedoutmsg').hide(); 1222 1223 updateDomainSelectors(registeredUser.domainInfo) 1224 1225 // Will display or hide buttons based on domain options 1226 var userType = getUserType(registeredUser) 1227 var domainOptions = getDomainOptions(registeredUser.domainInfo) 1228 1229 if (registeredUser.superadmin) { 1230 switchRole(userType, null); // Superadmin tabs & buttons 1231 switchDomainOptions(userType, domainOptions); 1232 $('#tab-domains').text('Accounts'); // Lets personalize the tab name 1233 } else { 1234 $('#tab-domains').text('My account'); // Lets personalize the tab name 1235 1236 if (registeredUser.domainadmin) { 1237 switchRole(userType, domainType); // Show domain admin tabs & buttons 1238 switchDomainOptions(userType, domainOptions); 1239 } 1240 else { 1241 switchRole(userType, domainType); // Show only user tabs & buttons 1242 switchDomainOptions(userType, domainOptions); 1243 } 1244 } 1245 1246 // The sub-account roller is shown only if we are a root account user (domain admin or domain user) 1247 if (registeredUser.isRootAccountUser) { 1248 $('#subDomainSelectorIsys, #subDomainSelectorUsers').show(); 1249 } else { 1250 $('#subDomainSelectorIsys, #subDomainSelectorUsers').hide(); 1251 // The domains tab is for domain admins, but also root accounts only! 1252 $('#tab-domains').hide(); 1253 } 1254 1255 // When we recreate the rollers in updateDomainSelectors, we will start from the defaults 1256 $('.domainSelect, .subDomainSelect').val(''); 1257 1258 // Indirectly triggers updateDomainSelectors - Domain rollers get feeded 1259// tableDomains.setDomain(registeredUser.domain, true); // True = force reload. 1260 1261 // Show all toolbars 1262 $('.toolbar').show(); 1263 1264 // Energy Expert Specific UI code 1265 afterLoginEeUI(data) 1266 1267 1268 if (location.hash) { 1269 var hashEnd = location.hash.indexOf('?'); 1270 var hash = hashEnd>=0?location.hash.slice(0, hashEnd):location.hash; 1271 // Switch with hash without the parameters, if any 1272 switch(hash) { 1273 case '#myprofile': 1274 openMyProfile(); 1275 break; 1276 1277 case '#license': 1278 openLicense( 1279 $.gup('uuid') && decodeURIComponent($.gup('uuid')), 1280 $.gup('key') && decodeURIComponent($.gup('key')), // isyKey 1281 $.gup('redirect') && decodeURIComponent($.gup('redirect'))) 1282 break; 1283 1284 case '#ns-purchase': 1285 nodeserverPurchase( 1286 $.gup('pid') ? decodeURIComponent($.gup('pid')) : null, 1287 $.gup('nsid') ? decodeURIComponent($.gup('nsid')) : null, 1288 $.gup('redirect') ? decodeURIComponent($.gup('redirect')) : null, 1289 $.gup('beta') ? $.gup('beta') === 'true' : false, 1290 // If not specified, will take the paypal env from the server config 1291 ($.gup('test') ? $.gup('test') === 'true' : undefined)); 1292 break; 1293 1294 case '#tab-domains': 1295 case '#tab-ee': 1296 case '#tab-ee-admin': 1297 // If the ee tab is hidden, then we might be on our first try at linking, display the domains tab instead 1298 if ($(hash).is(':hidden')) { 1299 hash = '#tab-domains' 1300 } 1301 1302 // Search the hash in the ids in the tab's a ids 1303 setMainTab(hash) 1304 break; 1305 } 1306 } 1307 1308 resetLocation(); 1309 1310 // Start logout timers & detect if there is activity 1311 autoLogout.start(function() { 1312 logout({ inactivity: true });
1313 }); 1314} 1315 1316function setMainTab(tabId) { 1317 var index = $('#tabs ul a').index($(tabId)); 1318 if (index !== -1) { 1319 $('#tabs').tabs('option', 'active', index); 1320 $('#tabs').tabs('refresh'); 1321 } 1322} 1323 1324function getMainTab() { 1325 var activeTabIdx = $('#tabs').tabs('option','active'); 1326 return $('#tabs > ul > li').eq(activeTabIdx).attr('aria-controls') 1327} 1328 1329function getDomainSelected() { 1330 return $('.domainSelect:first').val(); 1331} 1332 1333// Low level function 1334function setDomainSelector(domainId) { 1335 return $('.domainSelect').val(); 1336} 1337 1338// Triggered from tableDomains (onDataLoad), and at startup (/api/domains/lookup) 1339function updateDomainSelectors(userDomain) { 1340 1341 // currentSelectedDomain will be either: 1342 // - The previously selected domain 1343 // - The domain of the user, if it is a root domain 1344 // - The parent of the domain of the user, if it is a child domain. 1345 1346 // Keep note of the domain before overwriting the selector 1347 var currentSelectedDomain = $('.domainSelect').first().val(); 1348 1349 // currentSelectedSubDomain will be either: 1350 // - The previously selected sub-domain 1351 // - The domain of the user, if it is a sub-domain 1352 // - $all ([All]) the domain of the user is a root domain 1353 1354 // Keep note of the sub-domain before overwriting the selector 1355 var currentSelectedSubDomain = $('.subDomainSelect').first().val(); 1356 1357 // Initialize selectors based on user 1358 if (userDomain === '$superadmin') { 1359 // Keep existing selectors if they have something 1360 if (!currentSelectedDomain || !currentSelectedSubDomain) { 1361 currentSelectedDomain = userDomain; 1362 currentSelectedSubDomain = '$root'; 1363 } 1364 } else { 1365 if (!userDomain.parent || userDomain.parent==='') { 1366 currentSelectedDomain = userDomain._id; 1367 currentSelectedSubDomain = '$root'; 1368 } else { 1369 currentSelectedDomain = userDomain.parent; 1370 currentSelectedSubDomain = userDomain._id; 1371 } 1372 } 1373 1374 createDomainSelect(currentSelectedDomain); 1375 1376 ajaxJsonGet('/api/domains/lookup/sub', $.param({ domain: currentSelectedDomain })) 1377 .done(function (data) { 1378 createSubDomainSelect(data.data, currentSelectedDomain, currentSelectedSubDomain); 1379 1380 var activeTabID = getMainTab() 1381 1382 // Refresh the table (if the current table is tab-panel-isys or tab-panel-users - useful for startup only) 1383 var subDomainSelected = $('#tab-panel-isys .subDomainSelect').val(); 1384 1385 updateTable(activeTabID, subDomainSelected); 1386 }) 1387 .fail(function(jqXHR, textStatus, errorThrown) { 1388 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 1389 }) 1390 .done(); 1391} 1392 1393function createDomainSelect(currentSelectedDomain) { 1394 var selector = $('.domainSelect') 1395 selector.domainCombobox({ 1396 inputClass: 'role sadmin', // widget is visible to superadmin only 1397 // Process the input field 1398 focusout: function(event, ui) { 1399 // $(event.target).blur(); 1400 var newDomainShortname = $(event.target).val(); 1401 1402 // console.log('domainSelector focusout shortname', newDomainShortname) 1403 if (newDomainShortname) { 1404 ajaxJsonGet('/api/domains/lookup/shortname', $.param({ shortname: newDomainShortname })) 1405 .then((content) => { 1406 1407 // Could we locate the shortname? 1408 if (content.data) { 1409 var idDomain = content.data._id 1410 1411 // Will display or hide buttons based on domain options 1412 switchDomainOptions(getUserType(registeredUser), getDomainOptions(content.data)); 1413 1414 // Rebuild the sub-Domain selector 1415 ajaxJsonGet('/api/domains/lookup/sub', $.param({ domain: idDomain })) 1416 .then((content) => { 1417 var subDomains = content.data 1418 // Rebuild the sub-Domain selector 1419 createSubDomainSelect(subDomains, idDomain, '$all'); 1420 1421 // Makes all selectors set to same domain 1422 $('.domainSelect').domainCombobox('option', 'data', idDomain) 1423 if (idDomain==='$superadmin') { 1424 $('.subDomainSelect').val('$root'); 1425 } 1426 else { 1427 $('.subDomainSelect').val('$all'); 1428 } 1429 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1430 1431 var idTab = getMainTab() 1432 1433 if (idTab==='tab-panel-users') { 1434 tableUsers.setDomain(idDomain, true); // sub-account == $all, so show all from sub-accounts as well 1435 } else if (idTab==='tab-panel-isys') { 1436 tableIsys.setDomain(idDomain, true); // sub-account == $all, so show all from sub-accounts as well 1437 } 1438 1439 $('#isys_table_container, #users_table_container').show(); 1440 }) 1441 } else {
1442 // shortname entered was not valid 1443 // Rebuild the sub-Domain selector 1444 createSubDomainSelect([], '', '$all'); 1445 1446 // Makes all selectors set to same domain 1447 $('.domainSelect').domainCombobox('option', 'data', '') 1448 if (idDomain==='$superadmin') { 1449 $('.subDomainSelect').val('$root'); 1450 } 1451 else { 1452 $('.subDomainSelect').val('$all'); 1453 } 1454 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1455 1456 $('#isys_table_container, #users_table_container').hide(); 1457 } 1458 }) 1459 } else { 1460 // shortname is empty 1461 $('#isys_table_container, #users_table_container').hide(); 1462 } 1463 }, 1464 }) 1465 1466 selector.domainCombobox('option', 'source', function (request, response) { 1467 ajaxJsonGet('/api/domains/lookup/root', $.param(request)) 1468 .done(function(content, status, jqXHR){ 1469 response($.map(content.data, function (el) { 1470 return { 1471 label: el.shortname, 1472 value: el.shortname, 1473 data: el._id 1474 }; 1475 })); 1476 }) 1477 }) 1478 .domainCombobox('option', 'data', currentSelectedDomain) 1479 .toggle(registeredUser.superadmin) 1480} 1481 1482 1483function createSubDomainSelect(domains, selectedDomain, selectedSubDomain) { 1484 // ---- Create the sub-domain selector SELECT 1485 var $subSelect = $('<select class="subDomainSelect"></select>'); 1486 var selectedDomainIsValid=false; 1487 1488 // [UDI] has no sub accounts. 1489 if (selectedDomain!=='$superadmin') { 1490 $subSelect.append('<option value="$all">[All]</option>'); 1491 } 1492 1493 $subSelect.append('<option value="$root">[Main Account]</option>'); 1494 1495 domains.forEach(function(domain) { 1496 $subSelect.append('<option value="' + domain._id + '">' + domain.shortname + '</option>'); 1497 1498 if (selectedSubDomain===domain._id) { 1499 selectedDomainIsValid=true; 1500 } 1501 }); 1502 1503 // ---- Write the sub-domain SELECT 1504 $('#subDomainSelectorIsys').empty().append($subSelect.clone()); 1505 $('#subDomainSelectorUsers').empty().append($subSelect.clone()); 1506 1507 if (selectedDomainIsValid || selectedSubDomain==='$root') { 1508 $('.subDomainSelect').val(selectedSubDomain); 1509 } 1510 1511 // Sub-Domain selector 1512 $('.subDomainSelect').selectmenu({ 1513 width:'14em', 1514 1515 // Adjust menu heigth based on window heigth 1516 open: function(event) { 1517 // Adjusts menu max height to the available height when it opens 1518 var topMenu = event.currentTarget.getBoundingClientRect().bottom; 1519 var windowHeigth = window.innerHeight; 1520 var ul = $('#' + $(event.target).attr('id') + '-menu'); 1521 ul.attr('style', ul.attr('style') + 'max-height:' + (windowHeigth-topMenu-15) + 'px;') 1522 }, 1523 1524 change: function(event, ui) { 1525 var idSubDomain = ui.item.value; 1526 1527 // Makes all selector set to same domain 1528 $('.subDomainSelect').val(idSubDomain); 1529 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1530 1531 // Find out which tab we are in 1532 var idTab = $(ui.item.element).closest('.ui-tabs-panel').attr('id'); 1533 1534 // Update the table in that tab 1535 updateTable(idTab, idSubDomain); 1536 } 1537 }); 1538} 1539 1540function updateTable(idTab, idSubDomain) { 1541 var domainSelected = getDomainSelected(); 1542 // console.log('updateTable for', domainSelected) 1543 1544 switch(idSubDomain) { 1545 case '$all': 1546 if (idTab==='tab-panel-users') { 1547 tableUsers.setDomain(domainSelected, true); 1548 } 1549 else if (idTab==='tab-panel-isys') { 1550 tableIsys.setDomain(domainSelected, true); 1551 } 1552 break; 1553 1554 case '$root': 1555 if (idTab==='tab-panel-users') { 1556 tableUsers.setDomain(domainSelected, false); 1557 } 1558 else if (idTab==='tab-panel-isys') { 1559 tableIsys.setDomain(domainSelected, false); 1560 } 1561 break; 1562 1563 default: 1564 if (idTab==='tab-panel-users') { 1565 tableUsers.setDomain(idSubDomain, false); 1566 } 1567 else if (idTab==='tab-panel-isys') { 1568 tableIsys.setDomain(idSubDomain, false); 1569 } 1570 break; 1571 } 1572} 1573 1574// Process this on logout 1575function logout(cause) { 1576 if (!cause) 1577 cause = {} 1578 1579 if (rememberMeActive() && registeredUser) { 1580 rememberMeSet(registeredUser.username, registeredUser.password) 1581 } 1582 1583 // Close any tooltip by triggering a mouseout event. 1584// $(document).trigger('mouseout'); 1585 1586 setRegisteredUser(null); 1587 switchLoginState(false); // adjusts the top-right tab 1588 setLastConnected(null); // Removed the connection history link 1589
1590 allDialogs.forEach(function (item) { 1591 item.dialog('close'); 1592 }); 1593 1594 tempDialogs.forEach(function (item) { 1595 item.dialog('close'); 1596 }); 1597 1598 // Disable autoLogout 1599 autoLogout.stop(); 1600 1601 $('#tabs>ul>li>a:not(.duser)').hide(); // Only show user tabs. 1602 // Isys tab becomes the active tab 1603 $('#tabs').tabs('option', 'active', 0); 1604 $('#tabs').tabs('refresh'); 1605 1606 $('.toolbar').hide(); 1607 tableIsys.clear(); 1608 tableUsers.clear(); 1609 1610 $('#loggedoutmsg').text( 1611 cause.inactivity ? 'You have been logged out due to inactivity.' : 1612 cause.sessionError ? 'Error renewing your session token. Please login again.' : 1613 cause.sessionExpired ? 'Your session has expired.' : 1614 'You are logged out.'); 1615 $('#loggedoutmsg').show(); 1616} 1617 1618 1619// -------------- UI utilities -------------- 1620 1621function getDomainOptions(domainInfo) { 1622 // Will display or hide buttons with class .domainOption.allowIsyImport 1623 var domainOptions = {} 1624 domainOptions.allowIsyImport = domainInfo.allowIsyImport || false 1625 domainOptions.allowMassMaintenance = domainInfo.allowMassMaintenance || false 1626 1627 return domainOptions 1628} 1629 1630function getUserType(user) { 1631 return user.superadmin ? 'sadmin' : user.domainadmin ? 'dadmin' : 'duser' 1632} 1633 1634 1635// Show/Hide tabs & buttons based on user role 1636// userType: sadmin, dadmin, duser 1637// domainType: '0'=Utility, '1'=corporate 1638function switchRole(userType, domainType) { 1639 // html that needs to be hidden/shown based on role needs to have the following syntax: 1640 // A class 'role' AND a class of userType or userType+domainType 1641 var roleSelector = '.role.' + userType; 1642 1643 if (domainType && domainType.length) { 1644 roleSelector += ', .role.' + userType + domainType; 1645 } 1646 1647 $('#tabs').find('.role').not(roleSelector).hide(); 1648 $('#tabs').find(roleSelector).show(); 1649} 1650 1651 1652// Show/Hide tabs & buttons based on user role 1653// userType: sadmin, dadmin, duser 1654// domainType: '0'=Utility, '1'=corporate 1655function switchDomainOptions(userType, domainOptions) { 1656 // html that needs to be hidden/shown based on the domain selected 1657 var domainSelector 1658 1659 if (typeof domainOptions === 'object') { 1660 domainSelector = Object.entries(domainOptions) 1661 .reduce(function (selector, optionPair) { 1662 var option = optionPair[0] 1663 var optionValue = optionPair[1] 1664 if (optionValue) { 1665 return selector += (selector.length?', ':'') + '.domainOption.' + option + '.' + userType 1666 } else { 1667 return selector 1668 } 1669 }, '') 1670 } else { 1671 domainSelector = '' 1672 } 1673 1674 $('#tabs').find('.domainOption').not(domainSelector).hide(); 1675 $('#tabs').find(domainSelector).show(); 1676} 1677 1678// Enables/Disables top right floating tabs 1679function switchLoginState(loggedIn) { 1680 if (loggedIn) { 1681 $('#loginbutton').hide(); 1682 $('#logoutbutton').show(); 1683 $('#myprofilebutton').show(); 1684 $('#openpolyglot').show(); 1685 } 1686 else { 1687 $('#loginbutton').show(); 1688 $('#logoutbutton').hide(); 1689 $('#myprofilebutton').hide(); 1690 $('#openpolyglot').show(); // Shown all the time 1691 } 1692} 1693 1694// Build a dynamic jQuery selectmenu 1695function buildSelect(options, target, width, changeFunction) { 1696 var $select = $('<select></select>'); 1697 1698 options.forEach(function(item) { 1699 $select.append('<option value="' + item.value + '">' + item.description + '</option>'); 1700 }); 1701 1702 $(target).empty().append($select.clone()); 1703 1704 var opts = {}; 1705 1706 if (width) { 1707 opts.width = width; 1708 } 1709 1710 if (changeFunction) { 1711 opts.change = changeFunction; 1712 } 1713 1714 $(target + ' select').selectmenu(opts); 1715} 1716 1717// Compare 2 selects. 1718function selectHasIdenticalOptions($select1, $select2) { 1719 var newOptions = $.map($select1.find('option'), function(option) { return $(option).attr('value'); }); 1720 var oldOptions = $.map($select2.find('option'), function(option) { return $(option).attr('value'); }); 1721 1722 var identical = true; 1723 1724 if (newOptions.length === oldOptions.length) { 1725 newOptions.forEach(function(option, index) { 1726 if (option !== oldOptions[index]) { 1727 identical=false; 1728 } 1729 }) 1730 } 1731 else { 1732 identical=false; 1733 } 1734 1735 return identical; 1736} 1737 1738function deviceHasColorNode(address, stdLookup) { 1739 var found = false;
1740 var addressSplit = address.split('_'); 1741 var addressSearch; 1742 var isMain = (addressSplit.length===2 && addressSplit[1].slice(0,3)==='1'); 1743 1744 if (isMain) { 1745 addressSearch = addressSplit[0]+'_186'; 1746 1747 // See if the individual device is a lock or thermostat, and use that device category instead ('lock' or 'thermostat') 1748 found = stdLookup.find(function(item) { 1749 return item.address === addressSearch; 1750 }) 1751 } 1752 1753 return found 1754} 1755 1756 1757function zpad(n) { 1758 return n < 10 ? '0' + n.toString(10) : n.toString(10); 1759} 1760 1761function dtStr(dt) { 1762 return zpad(dt.getMonth()+1) + '/' + zpad(dt.getDate()) + '/' + + zpad(dt.getFullYear()) + ' ' + 1763 zpad(dt.getHours()) + ':' + zpad(dt.getMinutes()) 1764} 1765 1766function dtDateStr(dt) { 1767 return zpad(dt.getMonth()+1) + '/' + zpad(dt.getDate()) + '/' + + zpad(dt.getFullYear()) 1768} 1769 1770function createTabs(tabsId, contentId, tabsDef) { 1771 $(tabsId).empty().append( 1772 '<ul>' + 1773 tabsDef.map(function(name, index) { 1774 return '<li><a href="' + contentId + index + '">' + name + '</a></li>' 1775 }) + 1776 '</ul>' + 1777 tabsDef.map(function(name, index) { 1778 return '<div id="' + contentId.slice(1) + index + '"></div>' 1779 })) 1780 1781 $(tabsId).tabs() 1782} 1783 1784function addTabs(tabsId, contentId, tabsDef) { 1785 var existingTabs = $(tabsId + ' ul li') 1786 var n = existingTabs.length; 1787 1788 existingTabs.last().after( 1789 tabsDef.map(function(name, index) { 1790 return '<li><a href="' + contentId + (n+index) + '">' + name + '</a></li>' 1791 })) 1792 1793 existingTabs = $(tabsId).children('div:last').after( 1794 tabsDef.map(function(name, index) { 1795 return '<div id="' + contentId.slice(1) + (n+index) + '"></div>' 1796 })) 1797 1798 $(tabsId).tabs('refresh') 1799} 1800 1801function createTabCanvas(tabsContentId, tabContentIndex, title) { 1802 $(tabsContentId+tabContentIndex).append('<label>' + title + '</label><canvas class="spacer-top-10px" width="600" height="600">') 1803} 1804 1805function createChart(tabsContentId, tabContentIndex, data) { 1806 new Chart($(tabsContentId + tabContentIndex + ' canvas'), data); 1807} 1808 1809function makeTabsVertical(selector) { 1810 $(selector).addClass('ui-tabs-vertical ui-helper-clearfix' ) 1811 .find('li').removeClass( 'ui-corner-top' ).addClass( 'ui-corner-left' ); 1812} 1813 1814 1815// Inits buttons 1816function initUI() { 1817 // -------------- Domains page---------------- 1818 1819 $('#domainrefresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 1820 $(this).blur(); // Remove focus from button 1821 tableDomains.reload(); 1822 }); 1823 1824 $('#domainadd').button().click(function(event) { 1825 $(this).blur(); // Remove focus from button 1826 1827 if (registeredUser.superadmin) { 1828 ajaxJsonGet('/api/domains/root') 1829 .then(function(data) { 1830 data.data.map(function (item) { 1831 item.label = item.value = item.shortname; 1832 item.data = item._id; // Data used in the real input field sent to the server 1833 item.option = item; 1834 }); 1835 1836 data.data.sort(function(a, b) { 1837 return a.shortname.localeCompare(b.shortname) } 1838 ); 1839 1840 domainDialog.setFields({ editMode: false, isUDI: registeredUser.superadmin, sub:false, parentList: data.data }); 1841 domainDialog.modal.dialog('open'); 1842 }) 1843 } 1844 else { 1845 domainDialog.setFields({ editMode: false, isUDI: registeredUser.superadmin, sub:true }); 1846 domainDialog.modal.dialog('open'); 1847 } 1848 }); 1849 1850 // -------------- Users page---------------- 1851 $('#useradd').button().click(function(event) { 1852 $(this).blur(); // Remove focus from button 1853 userDialog.resetForm(); 1854 1855 // Get the current sub account 1856 var subDomainSelected = $(this).closest('.toolbar').find('.subDomainSelect').val(); 1857 1858 if (subDomainSelected==='$all') { 1859 // If all we have is $all and $root, automatically select $root! 1860 if ($(this).closest('.toolbar').find('.subDomainSelect option').length===2) { 1861 subDomainSelected='$root'; 1862 $('.subDomainSelect').val(subDomainSelected); 1863 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1864 } 1865 else { 1866 errorMessageDialog('Please select a specific sub-account first', 'Select sub-account') 1867 } 1868 } 1869 1870 switch (subDomainSelected) { 1871 case '$all': 1872 break; // Add requires a sub-account to be specified 1873 1874 case '$root': 1875 // When creating a user, put it in the root account, if $root is selected 1876 var domainSelected = $(this).closest('.toolbar').find('.domainSelect').val(); 1877 userDialog.setDomain(domainSelected); 1878 userDialog.modal.dialog('open'); 1879 break; 1880 1881 default: 1882 // Put the user in the selected sub account 1883 userDialog.setDomain(subDomainSelected); 1884 userDialog.modal.dialog('open'); 1885 break; 1886 } 1887 }); 1888 1889 $('#userrefresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 1890 $(this).blur(); // Remove focus from button 1891 tableUsers.reload(); 1892 }); 1893 1894 // -------------- ISYs page---------------- 1895 $('#isyadd').button().click(function(event) { 1896 var domainSelected = $(this).closest('.toolbar').find('.domainSelect').val(); 1897 var subDomainSelected = $(this).closest('.toolbar').find('.subDomainSelect').val(); 1898 1899 $(this).blur(); // Remove focus from button 1900 1901 isyClaimDialog.resetForm(); 1902 1903 if (subDomainSelected==='$all') { 1904 // If all we have is $all and $root, automatically select $root! 1905 if ($(this).closest('.toolbar').find('.subDomainSelect option').length===2) { 1906 subDomainSelected='$root'; 1907 $('.subDomainSelect').val(subDomainSelected); 1908 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1909 } 1910 else { 1911 errorMessageDialog('Please select a specific sub-account first', 'Select sub-
1911account') 1912 } 1913 } 1914 1915 switch (subDomainSelected) { 1916 case '$all': 1917 break; // Add requires a sub-account to be specified 1918 1919 case '$root': 1920 isyClaimDialog.setDomain(domainSelected); 1921 isyClaimDialog.modal.dialog('open'); 1922 break; 1923 1924 default: 1925 isyClaimDialog.setDomain(subDomainSelected); 1926 isyClaimDialog.modal.dialog('open'); 1927 break; 1928 } 1929 }); 1930 1931 $('#isyimport').button().click(function(event) { 1932 var domainSelected = $(this).closest('.toolbar').find('.domainSelect').val(); 1933 var subDomainSelected = $(this).closest('.toolbar').find('.subDomainSelect').val(); 1934 1935 $(this).blur(); // Remove focus from button 1936 1937 isyClaimDialog.resetForm(); 1938 1939 if (subDomainSelected==='$all') { 1940 // If all we have is $all and $root, automatically select $root! 1941 if ($(this).closest('.toolbar').find('.subDomainSelect option').length===2) { 1942 subDomainSelected='$root'; 1943 $('.subDomainSelect').val(subDomainSelected); 1944 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1945 } else { 1946 errorMessageDialog('Please select a specific sub-account first', 'Select sub-account') 1947 } 1948 } else { 1949 switch (subDomainSelected) { 1950 case '$root': 1951 isyImportDialog.setFields({ domain: domainSelected }); 1952 isyImportDialog.modal.dialog('open'); 1953 break; 1954 1955 default: 1956 isyImportDialog.setFields({ domain: subDomainSelected }); 1957 isyImportDialog.modal.dialog('open'); 1958 break; 1959 } 1960 } 1961 }); 1962 1963 $('#isyupgradeall').button().click(function(event) { 1964 var domainSelected = $(this).closest('.toolbar').find('.domainSelect').val(); 1965 var subDomainSelected = $(this).closest('.toolbar').find('.subDomainSelect').val(); 1966 1967 $(this).blur(); // Remove focus from button 1968 1969 if (subDomainSelected==='$all') { 1970 // If all we have is $all and $root, automatically select $root! 1971 if ($(this).closest('.toolbar').find('.subDomainSelect option').length===2) { 1972 subDomainSelected='$root'; 1973 $('.subDomainSelect').val(subDomainSelected); 1974 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 1975 } else { 1976 errorMessageDialog('Please select a specific sub-account first', 'Select sub-account') 1977 } 1978 } else { 1979 confirmationDialog('All ISYs in account will be upgraded.<br/>Are you sure?', 'Upgrade all ISYs?') 1980 .then(function () { 1981 var domain = subDomainSelected === '$root' ? domainSelected : subDomainSelected 1982 1983 // Works only for eisys 1984 ajaxJsonGet('/api/eisy/upgradeall', $.param({ domain: domain })) 1985 .done(function () { 1986 tableIsys.reload(); 1987 }) 1988 .fail(function(jqXHR, textStatus, errorThrown) { 1989 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 1990 }) 1991 }) 1992 .done(); 1993 } 1994 }); 1995 1996 $('#isyrebootall').button().click(function(event) { 1997 var domainSelected = $(this).closest('.toolbar').find('.domainSelect').val(); 1998 var subDomainSelected = $(this).closest('.toolbar').find('.subDomainSelect').val(); 1999 2000 $(this).blur(); // Remove focus from button 2001 2002 if (subDomainSelected==='$all') { 2003 // If all we have is $all and $root, automatically select $root! 2004 if ($(this).closest('.toolbar').find('.subDomainSelect option').length===2) { 2005 subDomainSelected='$root'; 2006 $('.subDomainSelect').val(subDomainSelected); 2007 $('.subDomainSelect').selectmenu('refresh'); // Needs a refresh to show 2008 } 2009 else { 2010 errorMessageDialog('Please select a specific sub-account first', 'Select sub-account') 2011 } 2012 } else { 2013 confirmationDialog('All ISYs in account will be rebooted.<br/>Are you sure?', 'Reboot all ISYs?') 2014 .then(function () { 2015 var domain = subDomainSelected === '$root' ? domainSelected : subDomainSelected 2016 2017 ajaxJsonGet('/api/isy/rebootall', $.param({ domain: domain })) 2018 .done(function () { 2019 tableIsys.reload(); 2020 }) 2021 .fail(function(jqXHR, textStatus, errorThrown) { 2022 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 2023 }) 2024 }) 2025 .done(); 2026 } 2027 }); 2028 2029 $('#isyrefresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 2030 $(this).blur(); // Remove focus from button 2031 tableIsys.reload(); 2032 }); 2033 2034 $('#scereports').button(); 2035 2036 // -------------- Admin / All ISYs page---------------- 2037 $('#isyconnectedrefresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 2038 $(this).blur(); // Remove focus from button 2039 tableConnectedIsys.reload(); 2040 }); 2041 2042 // -------------- Admin / IPAL Renewals options ---------------- 2043 $('#ipal_renewal_option_add').button().tooltip().click(function(event) { 2044 $(this).blur(); // Remove focus from button 2045 ipalRenewalOptionEditDialog.setFields(); 2046 ipalRenewalOptionEditDialog.modal.dialog('open'); 2047 }); 2048
2049 $('#ipal_renewal_option_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 2050 $(this).blur(); // Remove focus from button 2051 tableIpalRenewalOptions.reload(); 2052 }); 2053 2054 // -------------- Admin / IPAL Notifications options ---------------- 2055 $('#ipal_notification_option_add').button().tooltip().click(function(event) { 2056 $(this).blur(); // Remove focus from button 2057 ipalNotificationOptionEditDialog.setFields(); 2058 ipalNotificationOptionEditDialog.modal.dialog('open'); 2059 }); 2060 2061 $('#ipal_notification_option_download').button().tooltip().click(function(event) { 2062 $(this).blur(); // Remove focus from button 2063 2064 var message = 'This will find the ISYs which still have an active license that will be expired within 30 days. ' + 2065 'For those ISYs, the relevant users will be identified based on the account billing notification settings.<br/><br/>' + 2066 'Users that are not domain admins, or did not have their email verified are excluded.<br/><br/>' + 2067 'Press Ok to download.'; 2068 2069 informationMessageDialog(message, 'Export users', 800) 2070 .then(function () { 2071 ajaxJsonPostTimeout3M('/api/ipal/notification/download/expired') 2072 .then(function(data, status, jqXHR) { 2073 try { 2074 var isFileSaverSupported = !!new Blob; 2075 } catch (e) {} 2076 2077 if (isFileSaverSupported) { 2078 var BOM = '\uFEFF'; 2079 saveAs(new Blob([BOM + data.users], {type: "application/csv;charset=utf-8"}), 'users.csv'); 2080 // saveAs(new Blob([data.orphaned], {type: "application/csv;charset=utf-8"}), 'orphaned.csv'); 2081 } 2082 else { 2083 errorMessageDialog('Feature not supported on this browser', 'Not supported'); 2084 } 2085 }) 2086 .fail(function(jqXHR, textStatus, errorThrown) { 2087 errorMessageDialog('API error - HTTP error ' + jqXHR.status) 2088 }) 2089 .done(); 2090 2091// location.href = '/api/ipal/notification/download/expired'; 2092// window.open('/api/ipal/notification/download/expired'); // , '_blank' 2093 }) 2094 .done(); 2095 }); 2096 2097 $('#ipal_notification_option_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 2098 $(this).blur(); // Remove focus from button 2099 tableIpalNotificationOptions.reload(); 2100 }); 2101 2102 // -------------- Admin / UD Communications ---------------- 2103 $('#ud-comms-option-add').button().tooltip().click(function(event) { 2104 $(this).blur(); // Remove focus from button 2105 udCommsEditDialog.setFields({}); 2106 udCommsEditDialog.modal.dialog('open'); 2107 }); 2108 2109 $('#ud-comms-option-refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 2110 $(this).blur(); // Remove focus from button 2111 tableUdComms.reload(); 2112 }); 2113 2114 // -------------- Admin / Rate plan options ---------------- 2115 $('#rateplans_add').button().tooltip().click(function(event) { 2116 $(this).blur(); // Remove focus from button 2117 ratePlanEditDialog.setFields({refreshFunction: tableRatePlanOptions.refresh}); 2118 ratePlanEditDialog.modal.dialog('open'); 2119 }); 2120 2121 $('#rateplans_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).tooltip().click(function(event) { 2122 $(this).blur(); // Remove focus from button 2123 tableRatePlanOptions.refresh(); 2124 }); 2125 2126 $('#downloadUsers').button().tooltip().click(function(event) { 2127 $(this).blur(); // Remove focus from button 2128 downloadUsersDialog.modal.dialog('open'); 2129 }); 2130 $('#downloadUsage').button().tooltip().click(function(event) { 2131 $(this).blur(); // Remove focus from button 2132 downloadUsageDialog.modal.dialog('open'); 2133 }); 2134 $('#downloadStats').button().tooltip().click(function(event) { 2135 $(this).blur(); // Remove focus from button 2136 downloadStatsDialog.modal.dialog('open'); 2137 }); 2138} 2139 2140// The tabActivate event is not run on initial tab creation, so we trap the creation here and allow tabActivated to run. 2141function tabCreate(event, ui) { 2142 ui.newPanel = ui.panel; // The activate event works with newPanel, not panel 2143 ui.oldPanel = null; 2144 tabNavigate(event, ui); 2145} 2146 2147// This runs when we switch tabs 2148function tabNavigate(event, ui) { 2149 var activeTab = ui.newPanel.attr('id'); 2150 var oldTab; 2151 var domain; 2152 var idSubDomain; 2153 2154 if (ui.oldPanel!=null) { 2155 oldTab = ui.oldPanel.attr('id'); 2156 } 2157 2158 if (activeTab === 'tab-panel-admin') { 2159// tableConnectedIsys.load(); 2160 tableIpalRenewalOptions.load(); // This is the first tab in the admin tab. We need to load it here. 2161 } else if (activeTab === 'tab-panel-domains') { 2162 tableDomains.setDomain(registeredUser.domainInfo); // Refresh if domain has changed since last time. 2163 } else if (activeTab === 'tab-panel-users') { 2164 idSubDomain = $('#tab-panel-users .subDomainSelect').val(); 2165 updateTable('tab-panel-users', idSubDomain) 2166 } else if (activeTab === 'tab-panel-isys') { 2167 if (registeredUser) { 2168 idSubDomain = $('#tab-panel-isys .subDomainSelect').val(); 2169 updateTable('tab-panel-isys', idSubDomain) 2170 } 2171 } else if (activeTab === 'tab-panel-ee') { 2172 tabEE() 2173 } else if (activeTab === 'tab-panel-ee-admin') { 2174 tabEEAdmin() 2175 } 2176} 2177 2178// This runs when we switch tabs 2179function tabNavigateAdmin(event, ui) { 2180 var activeTab = ui.newPanel.attr('id'); 2181 2182 if (activeTab === 'tab-panel-admin-ipal-renewal') { 2183 tableIpalRenewalOptions.load(); // We don't automatically refresh! Just load once. (load instead of reload) 2184 } else if (activeTab === 'tab-panel-admin-ipal-notifications') { 2185 tableIpalNotificationOptions.load(); // We don't automatically refresh! Just load once. (load instead of reload) 2186 } else if (activeTab === 'tab-panel-admin-ud-comms') { 2187 tableUdComms.load(); // We don't automatically refresh! Just load once. (load instead of reload) 2188 } else if (activeTab === 'tab-panel-admin-rateplans') { 2189 tableRatePlanOptions.load(); // We don't automatically refresh! Just load once. (load instead of reload) 2190 } else if (activeTab === 'tab-panel-admin-connected') { 2191 tableConnectedIsys.load(); // We don't automatically refresh! Just load once. (load instead of reload) 2192 } 2193} 2194 2195/* 2196// The tabActivateAdmin event is not run on initial tab creation, so we trap the creation here and allow tabActivated to run. 2197function tabCreateAdmin(event, ui) { 2198 ui.newPanel = ui.panel; // The activate event works with newPanel, not panel 2199 ui.oldPanel = null; 2200 tabNavigateAdmin(event, ui); 2201} 2202*/ 2203 2204// Extends the Date object to provide a function to get a date string as we want it in the log 2205function initDateLog() { 2206 Date.prototype.dateLog = function() { 2207 var yyyy = this.getFullYear().toString(); 2208 var MM = (this.getMonth()+1).toString(); // getMonth() is zero-based 2209 var dd = this.getDate().toString(); 2210 var hh = this.getHours().toString(); 2211 var mm = this.getMinutes().toString();
2212 var ss = this.getSeconds().toString(); 2213 return yyyy + '/' + (MM[1]?MM:"0"+MM[0]) + '/' + (dd[1]?dd:"0"+dd[0]) + ' ' + (hh[1]?hh:"0"+hh[0]) + ':' + (mm[1]?mm:"0"+mm[0])+ ':' + (ss[1]?ss:"0"+ss[0]); 2214 }; 2215/* 2216 Date.prototype.isoDate = function() { 2217 console.log('Date before', this) 2218 var yyyy = this.getFullYear().toString(); 2219 var MM = (this.getMonth()+1).toString(); // getMonth() is zero-based 2220 var dd = this.getDate().toString(); 2221 console.log('Date After', yyyy + '-' + (MM[1]?MM:"0"+MM[0]) + '-' + (dd[1]?dd:"0"+dd[0])) 2222 2223 return yyyy + '-' + (MM[1]?MM:"0"+MM[0]) + '-' + (dd[1]?dd:"0"+dd[0]); 2224 }; 2225*/ 2226} 2227 2228// If date is not valid, returns [Invalid Date] instead of throwing 2229function dateFormatNoError(dt, mask) { 2230 var formatted; 2231 2232 try { 2233 formatted = dateFormat(dt, mask); 2234 } 2235 catch (e) { 2236 formatted = '[Invalid Date]' 2237 } 2238 2239 return formatted; 2240} 2241 2242function polyfill() { 2243 // Creates Array.prototype.find 2244 2245 if (!Array.prototype.find) { 2246 Array.prototype.find = function(predicate) { 2247 if (this == null) { 2248 throw new TypeError('Array.prototype.find called on null or undefined'); 2249 } 2250 2251 if (typeof predicate !== 'function') { 2252 throw new TypeError('predicate must be a function'); 2253 } 2254 2255 var list = Object(this); 2256 var length = list.length >>> 0; 2257 var thisArg = arguments[1]; 2258 var value; 2259 2260 for (var i = 0; i < length; i++) { 2261 value = list[i]; 2262 if (predicate.call(thisArg, value, i, list)) { 2263 return value; 2264 } 2265 } 2266 return undefined; 2267 }; 2268 } 2269 2270 if (![].includes) { 2271 Array.prototype.includes = function(searchElement /*, fromIndex*/ ) { 2272 var O = Object(this); 2273 var len = parseInt(O.length) || 0; 2274 2275 if (len === 0) { 2276 return false; 2277 } 2278 2279 var n = parseInt(arguments[1]) || 0; 2280 var k; 2281 2282 if (n >= 0) { 2283 k = n; 2284 } 2285 else { 2286 k = len + n; 2287 if (k < 0) {k = 0;} 2288 } 2289 2290 var currentElement; 2291 2292 while (k < len) { 2293 currentElement = O[k]; 2294 2295 if (searchElement === currentElement || (searchElement !== searchElement && currentElement !== currentElement)) { 2296 return true; 2297 } 2298 2299 k++; 2300 } 2301 2302 return false; 2303 }; 2304 } 2305 2306 if (!Object.assign) { 2307 Object.defineProperty(Object, 'assign', { 2308 enumerable: false, 2309 configurable: true, 2310 writable: true, 2311 value: function(target) { 2312 'use strict'; 2313 if (target === undefined || target === null) { 2314 throw new TypeError('Cannot convert first argument to object'); 2315 } 2316 2317 var to = Object(target); 2318 for (var i = 1; i < arguments.length; i++) { 2319 var nextSource = arguments[i]; 2320 if (nextSource === undefined || nextSource === null) { 2321 continue; 2322 } 2323 nextSource = Object(nextSource); 2324 2325 var keysArray = Object.keys(Object(nextSource)); 2326 for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { 2327 var nextKey = keysArray[nextIndex]; 2328 var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); 2329 if (desc !== undefined && desc.enumerable) { 2330 to[nextKey] = nextSource[nextKey]; 2331 } 2332 } 2333 } 2334 2335 return to; 2336 } 2337 }); 2338 } 2339 2340 jQuery.extend({ 2341 gup: function(param){//Retrieves get information form URL 2342 var param2 = param.replace(/[[]/,"[").replace(/[]]/,"]"); 2343 var regexS = "[?&]"+param2+"=([^&#]*)"; 2344 var regex = new RegExp(regexS); 2345 var results = regex.exec(window.location.href); 2346 if(results) { 2347 return results[1]; 2348 } 2349 } 2350 }) 2351} 2352 2353function initCustomCombobox() { 2354 // autocompleteMaxHeight works like autocomplete, but menu will scroll if it falls outside of the viewport 2355 $.widget( "custom.autocompleteMaxHeight", $.ui.autocomplete, { 2356 2357 _resizeMenu: function() { 2358 var topMenu = this.element[0].getBoundingClientRect().bottom; 2359 var windowHeigth = window.innerHeight; 2360 var maxHeight = windowHeigth-topMenu-15; 2361 2362 this.menu.element.css({height:''}); 2363 2364 if (this.menu.element.outerHeight() > maxHeight) { 2365 this.menu.element.outerHeight(maxHeight); 2366 } 2367 2368 return this._super(); 2369 }, 2370 2371 _renderItem: function(ul, item) { 2372 if (item.class) { 2373 return $("<li>").attr("class", item.class).append(item.label).appendTo(ul); 2374 } 2375 else { 2376 return $("<li>").text(item.label).appendTo(ul); // Standard autocomplete _renderItem 2377 } 2378 } 2379 }); 2380 2381 // Create combobox, which uses custom.autocompleteMaxHeight & a button to show the items 2382 $.widget("custom.combobox", { 2383 options: { 2384 inputClass: 'ui-state-default', 2385 focusout: null, 2386 source: [] // source is an array of {value: x, label, x, data: x, option: <self reference> } 2387 }, 2388 // label: shown in the dropdown list 2389 // value: Sets the input field to this value when selecting from the dropdown (this.input) 2390 // data: real data assigned to the input field (this.element) 2391 2392 // Creates the custom combobox 2393 _create: function() { 2394 this.disab = false;
2394 // disabled is a reserved method for a widget 2395 this.wrapper = $("<span>") 2396 .addClass("custom-combobox") 2397 .insertAfter(this.element); 2398 2399 this.element.hide(); 2400 this._createAutocomplete(); 2401 this._createShowAllButton(); 2402 }, 2403 2404 // Autocomplete popups up a menu when we type something 2405 _createAutocomplete: function() { 2406 this.inputWrapper1 = $("<div>") 2407 .addClass("custom-combobox-inputwrapper1") 2408 .appendTo(this.wrapper); 2409 2410 this.inputWrapper2 = $("<div>") 2411 .addClass("custom-combobox-inputwrapper2") 2412 .appendTo(this.inputWrapper1); 2413 2414 this.input = $("<input>") 2415 .appendTo(this.inputWrapper2) 2416 .val(this._dataToValue(this.element.val())) 2417 .attr("title", "") 2418 .addClass("custom-combobox-input ui-widget ui-widget-content ui-corner-left") 2419 .addClass(this.options.inputClass) 2420 .autocompleteMaxHeight({ 2421 delay: 100, 2422 minLength: 0, 2423 source: this.option('source') 2424 }) 2425 .tooltip({ 2426 tooltipClass: "ui-state-highlight" 2427 }); 2428 2429 this._on(this.input, { 2430 autocompleteselect: function(event, ui) { 2431 this._trigger("select", event, { 2432 item: ui.item.option 2433 }); 2434 }, 2435 focusout: function(event, ui) { 2436 if (this.options.focusout) { 2437 this.options.focusout(event, ui) 2438 } 2439 } 2440 }); 2441 2442 }, 2443 2444 // Set custom combobox options after initialization 2445 _setOption: function(key, value) { 2446 if (key==='data') { 2447 this.input.val(this._dataToValue(value)); // This widget's value 2448 this.element.val(value); // Real input field 2449 } 2450 else if (key==='inputClass') { 2451 this.options.inputClass = value; 2452 this.input.addClass(value) 2453 } 2454 else if (key==='focusout') { 2455 this.options.focusout = value; 2456 } 2457 else if (key==='source') { 2458 this.options.source = value; // We keep a local reference 2459 this.input.autocompleteMaxHeight("option", key, value); 2460 } 2461 else if (key==='getValue') { 2462 return this.getValue(); 2463 } 2464 else if (key==='getOption') { 2465 return this.getOption(); 2466 } 2467 else if (key==='disabled') { 2468 this.disabled(value); 2469 } 2470 else { 2471 this.input.autocompleteMaxHeight("option", key, value); 2472 } 2473 }, 2474 2475 // Creates the drop down button 2476 _createShowAllButton: function() { 2477 var input = this.input, 2478 thisWidget = this, 2479 wasOpen = false; 2480 2481 this.buttonWrapper = $("<a>") 2482 .attr("tabIndex", -1) 2483 .appendTo(this.wrapper) 2484 .button({ 2485 icons: { primary: "ui-icon-triangle-1-s" }, 2486 text: false 2487 }) 2488 .removeClass("ui-corner-all") 2489 .addClass("custom-combobox-toggle ui-corner-right") 2490 .mousedown(function() { 2491 wasOpen = input.autocompleteMaxHeight("widget").is(":visible"); 2492 }) 2493 .click(function(event) { 2494 if (!thisWidget.disab) { 2495 input.focus(); 2496 2497 // Close if already visible 2498 if (wasOpen) { 2499 return; 2500 } 2501 2502 // Pass empty string as value to search for, displaying all results 2503 input.autocompleteMaxHeight("search", ""); 2504 } 2505 }); 2506 }, 2507 2508 _valueToItem: function (value) { 2509 var valueLowerCase = value ? value.toLowerCase() : null, 2510 result = null; 2511 2512 this.options.source.forEach(function(item) { 2513 if (item.value.toLowerCase() === valueLowerCase ) { 2514 result = item; 2515 return false; 2516 } 2517 }); 2518 2519 return result; 2520 }, 2521 2522 _valueToData: function (value) { 2523 // var valueLowerCase = value.toLowerCase(), 2524 // data = null; 2525 // 2526 // this.options.source.forEach(function(item) { 2527 // if (item.value.toLowerCase() == valueLowerCase ) { 2528 // data = item.data; 2529 // return false; 2530 // } 2531 // }); 2532 // return data; 2533 2534 var item = this._valueToItem(value); 2535 return item ? item.data : null; 2536 }, 2537 2538 _dataToValue: function (data) { 2539 var dataLowerCase = data?data.toLowerCase():null, 2540 value=null; 2541 2542 if (!this.options.source) { 2543 console.log('combobox source is empty'); 2544 } 2545 2546 this.options.source.forEach(function(item) { 2547 if (item.data.toLowerCase() === dataLowerCase ) { 2548 value = item.value; 2549 return false; 2550 } 2551 }); 2552 2553 return value; 2554 }, 2555 2556 refresh: function() { 2557 var data = this._valueToData(this.input.val()); 2558 2559 if (!data) { 2560 data=''; 2561 } 2562 this.element.val(data); 2563 }, 2564 2565 disabled: function(value) { 2566 this.disab = value; 2567 this.buttonWrapper.button('option', 'disabled', value); 2568 if (value) { 2569 this.input.attr('readonly', value); 2570 } 2571 else { 2572 this.input.removeAttr('readonly') 2573 } 2574 }, 2575 2576 validate: function() { 2577 this.refresh();
2578 2579 // Search for a match (case-insensitive) 2580 var valueLowerCase = this.input.val().toLowerCase(), 2581 valid = false; 2582 2583 this.options.source.forEach(function(item) { 2584 if (item.label.toLowerCase() === valueLowerCase || item.value.toLowerCase() === valueLowerCase) { 2585 valid = true; 2586 return false; 2587 } 2588 }); 2589 2590 return valid; 2591 }, 2592 2593 getValue: function() { 2594 // this.refresh(); 2595 // Gets the text value from the input box 2596 return this.input.val(); 2597 }, 2598 2599 getItem: function() { 2600 var value = this.getValue(); 2601 return this._valueToItem(value) 2602 }, 2603 2604 _destroy: function() { 2605 this.wrapper.remove(); 2606 this.element.show(); 2607 } 2608 }); 2609 2610 // Create combobox, which uses custom.autocompleteMaxHeight & a button to show the items 2611 $.widget("custom.domainCombobox", { 2612 options: { 2613 inputClass: 'ui-state-default', 2614 focusout: null, 2615 submit: null, 2616 source: [] // source will be a function 2617 }, 2618 // label: shown in the dropdown list 2619 // value: Sets the input field to this value when selecting from the dropdown (this.input) 2620 // data: real data assigned to the input field (this.element) 2621 2622 // Creates the custom combobox 2623 _create: function() { 2624 this.disab = false;
2624 // disabled is a reserved method for a widget 2625 this.wrapper = $("<span>") 2626 .addClass("custom-domainCombobox") 2627 .insertAfter(this.element); 2628 2629 this.element.hide(); 2630 this._createAutocomplete(); 2631 // this._createShowAllButton(); 2632 }, 2633 2634 // Autocomplete popups up a menu when we type something 2635 _createAutocomplete: function() { 2636 this.inputWrapper1 = $("<div>") 2637 .addClass("custom-combobox-inputwrapper1") 2638 .appendTo(this.wrapper); 2639 2640 this.inputWrapper2 = $("<div>") 2641 .addClass("custom-combobox-inputwrapper2") 2642 .appendTo(this.inputWrapper1); 2643 2644 this.input = $("<input>") 2645 .appendTo(this.inputWrapper2) 2646 // .val(this._dataToValue(this.element.val())) 2647 .attr("title", "") 2648 .addClass("custom-combobox-input ui-widget ui-widget-content ui-corner-left") 2649 .addClass(this.options.inputClass) 2650 .autocompleteMaxHeight({ 2651 delay: 400, 2652 minLength: 1, 2653 source: this.option('source') 2654 }) 2655 .tooltip({ 2656 tooltipClass: "ui-state-highlight" 2657 }); 2658 2659 this._on(this.input, { 2660 autocompleteselect: function(event, ui) { 2661 this._trigger("select", event, { 2662 item: ui.item.option 2663 }); 2664 if (this.options.select) { 2665 this.options.select(event) 2666 } 2667 }, 2668 focusout: function(event, ui) { 2669 if (this.options.focusout) { 2670 this.options.focusout(event) 2671 } 2672 }, 2673 select: function(event, ui) { 2674 if (this.options.select) { 2675 this.options.select(event) 2676 } 2677 } 2678 }); 2679 2680 }, 2681 2682 // Set custom combobox options after initialization 2683 _setOption: function(key, value) { 2684 var self = this; 2685 if (key==='data.shortname') { 2686 if (value) { 2687 ajaxJsonGet('/api/domains/lookup/shortname', $.param({shortname: value})) 2688 .done(function (data) { 2689 if (data.data && data.data._id && data.data.shortname) { 2690 self.input.val(data.data.shortname); // This widget's value 2691 self.element.val(data.data._id); // Real input field 2692 } 2693 }) 2694 .fail(function (jqXHR, textStatus, errorThrown) { 2695 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 2696 }) 2697 .done(); 2698 } else { 2699 self.input.val(''); // This widget's value 2700 self.element.val(''); // Real input field 2701 } 2702 } 2703 else if (key==='data') { 2704 if (value) { 2705 ajaxJsonGet('/api/domains/lookup/id', $.param({ id: value })) 2706 .done(function (data) { 2707 2708 if (data.data && data.data._id && data.data.shortname) { 2709 self.input.val(data.data.shortname); // This widget's value 2710 self.element.val(data.data._id); // Real input field 2711 } 2712 }) 2713 .fail(function(jqXHR, textStatus, errorThrown) { 2714 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 2715 }) 2716 .done(); 2717 } else { 2718 self.input.val(''); // This widget's value 2719 self.element.val(''); // Real input field 2720 } 2721 } 2722 else if (key==='inputClass') { 2723 this.options.inputClass = value; 2724 this.input.addClass(value) 2725 } 2726 else if (key==='focusout') { 2727 this.options.focusout = value; 2728 } 2729 else if (key==='source') { 2730 this.options.source = value; // We keep a local reference 2731 this.input.autocompleteMaxHeight("option", key, value); 2732 } 2733 else if (key==='getValue') { 2734 return this.getValue(); 2735 } 2736 // else if (key==='getOption') { 2737 // return this.getOption(); 2738 // } 2739 else if (key==='disabled') { 2740 this.disabled(value); 2741 } 2742 // else if (key==='show') { 2743 // this.disabled(value); 2744 // } 2745 else { 2746 this.input.autocompleteMaxHeight("option", key, value); 2747 } 2748 }, 2749 2750 // // Creates the drop down button 2751 // _createShowAllButton: function() { 2752 // var input = this.input, 2753 // thisWidget = this, 2754 // wasOpen = false; 2755 // 2756 // this.buttonWrapper = $("<a>") 2757 // .attr("tabIndex", -1) 2758 // .appendTo(this.wrapper) 2759 // .button({ 2760 // icons: { primary: "ui-icon-triangle-1-s" }, 2761 // text: false 2762 // }) 2763 // .removeClass('ui-corner-all') 2764 // .addClass('custom-combobox-toggle ui-corner-right') 2765 // .mousedown(function() { 2766 // wasOpen = input.autocompleteMaxHeight('widget').is(':visible'); 2767 // }) 2768 // .click(function(event) { 2769 // if (!thisWidget.disab) { 2770 // console.log('click') 2771 // input.focus(); 2772 // 2773 // // Close if already visible 2774 // if (wasOpen) { 2775 // console.log('close!') 2776 // return; 2777 // } 2778 // 2779 // // Pass empty string as value to search for, displaying all results 2780 // input.autocompleteMaxHeight('search', ''); 2781 // } 2782 // }); 2783 // }, 2784 2785 // _valueToItem: function (value) { 2786 // var valueLowerCase = value ? value.toLowerCase() : null, 2787 // result = null; 2788 // 2789 // this.options.source.forEach(function(item) { 2790 // if (item.value.toLowerCase() == valueLowerCase ) { 2791 // result = item; 2792 // return false; 2793 // } 2794 // }); 2795 // 2796 // return result; 2797 // }, 2798 // 2799 // _valueToData: function (value) { 2800 // // var valueLowerCase = value.toLowerCase(), 2801 // // data = null; 2802 // // 2803 // // this.options.source.forEach(function(item) { 2804 // // if (item.value.toLowerCase() == valueLowerCase ) { 2805 // // data = item.data; 2806 // // return false; 2807 // // } 2808 // // }); 2809 // // return data; 2810 // 2811 // var item = this._valueToItem(value); 2812 // return item ? item.data : null; 2813 // }, 2814 // 2815 // _dataToValue: function (data) { 2816 // var dataLowerCase = data?data.toLowerCase():null, 2817 // value=null; 2818 // 2819 // if (!this.options.source) { 2820 // console.log('combobox source is empty'); 2821 // } 2822 // 2823 // this.options.source.forEach(function(item) { 2824 // if (item.data.toLowerCase() === dataLowerCase ) { 2825 // value = item.value; 2826 // return false; 2827 // } 2828 // }); 2829 // 2830 // return value; 2831 // }, 2832 2833 refresh: function() { 2834 var data = this._valueToData(this.input.val()); 2835 2836 if (!data) { 2837 data=''; 2838 } 2839 this.element.val(data); 2840 }, 2841 2842 disabled: function(value) { 2843 this.disab = value; 2844 this.buttonWrapper.button('option', 'disabled', value); 2845 if (value) { 2846 this.input.attr('readonly', value); 2847 } 2848 else { 2849 this.input.removeAttr('readonly') 2850 } 2851 }, 2852 2853 validate: function() { 2854 this.refresh();
2855 2856 // Search for a match (case-insensitive) 2857 var valueLowerCase = this.input.val().toLowerCase(), 2858 valid = false; 2859 2860 this.options.source.forEach(function(item) { 2861 if (item.label.toLowerCase() === valueLowerCase || item.value.toLowerCase() === valueLowerCase) { 2862 valid = true; 2863 return false; 2864 } 2865 }); 2866 2867 return valid; 2868 }, 2869 2870 getValue: function() { 2871 // this.refresh(); 2872 // Gets the text value from the input box 2873 return this.input.val(); 2874 }, 2875 2876 // getItem: function() { 2877 // var value = this.getValue(); 2878 // return this._valueToItem(value) 2879 // }, 2880 2881 _destroy: function() { 2882 this.wrapper.remove(); 2883 this.element.show(); 2884 } 2885 }); 2886} 2887 2888// Returns string 'time since...' 2889function since(min) { 2890 if (Math.round(min) <= 1) { 2891 return '1 minute ago'; 2892 } 2893 else if (min < 90) { // < 90 minutes 2894 return Math.round(min) + ' minutes ago'; 2895 } 2896 else if (min < 48*60) { // < 48 hr 2897 return Math.round(min/60) + ' hours ago'; 2898 } 2899 else { 2900 return Math.round(min/1440) + ' days ago'; 2901 } 2902} 2903 2904 2905var autoLogout = { 2906 // Set timeout variables. 2907 2908 timeoutWarningDelay: 840000, // Display warning in 14 Mins. 2909 timeoutGraceDelay: 60000, // Warning has been shown, give the user 1 minute to interact 2910// timeoutWarningDelay: 10000, // Display warning in 14 Mins. 2911// timeoutGraceDelay: 4000, // Warning has been shown, give the user 1 minute to interact 2912 2913 warningTimer: null, 2914 timeoutTimer: null, 2915 warningDialog: null, 2916 logout: null, 2917 2918 // Start timer & detect activity 2919 start: function (logoutFunct) { 2920 // Restart timer on any click 2921 this.resetTimer(); 2922 this.logout = logoutFunct; 2923 document.body.addEventListener('mouseup', this.resetTimer.bind(this)); 2924 }, 2925 2926 // Stop timeout & stop activity detection 2927 stop: function () { 2928 clearTimeout(this.warningTimer); 2929 clearTimeout(this.timeoutTimer); 2930 document.body.removeEventListener('mouseup', this.resetTimer.bind(this)); 2931 }, 2932 2933 // Restart inactivity timeout 2934 resetTimer: function () { 2935 clearTimeout(this.warningTimer); 2936 clearTimeout(this.timeoutTimer); 2937 this.warningTimer = setTimeout(this.idleWarning.bind(this), this.timeoutWarningDelay); 2938 2939 if (this.warningDialog) { 2940 this.warningDialog.dialog('close'); 2941 } 2942 }, 2943 2944 // Show idle timeout warning dialog. 2945 idleWarning: function () { 2946 var _this = this; 2947 2948 clearTimeout(this.warningTimer); 2949 this.timeoutTimer = setTimeout(this.idleTimeout.bind(this), this.timeoutGraceDelay); 2950 2951 var dialogOptions = { 2952 autoOpen: true, 2953 buttons: { 2954 'Yes': function() { 2955 $(this).dialog('close'); 2956 _this.resetTimer(); 2957 } 2958 }, 2959 close: function() { 2960 this.warningDialog=null; 2961 } 2962 }; 2963 2964 var html = '<div title="Inactivity timeout"><p autofocus>' + 2965 '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span><span>Stay logged in?</span></p></div>'; 2966 2967 this.warningDialog = $(html).dialog(dialogOptions); 2968 }, 2969 2970 // Logout the user. 2971 idleTimeout: function () { 2972 if (this.warningDialog) { 2973 this.warningDialog.dialog('close'); 2974 } 2975 2976 if (this.logout) { 2977 this.logout(); 2978 } 2979 } 2980}; 2981 2982function launchEisyui(uuid, isyKey, version) { 2983 // Strip build suffix (e.g. 1.2.2-1 -> 1.2.2) 2984 version = version.replace(/-\d+$/, ''); 2985 // User must have logged in normally (not with a JWT) 2986 if (!registeredUser.password) 2987 errorMessageDialog('Error launching eisy-ui: Please logout/login to portal'); 2988 2989 ajaxJsonPost('/api/jwt/create', $.param({ 2990 email: registeredUser.username, 2991 password: registeredUser.password, 2992 uuid: uuid 2993 })) 2994 .then(function(data, status, jqXHR) { 2995 if (!data.successful) { 2996 console.log('Error creating JWT: ' + data.errorMessage); 2997 return $.Deferred().reject(data.errorMessage).promise(); 2998 } 2999 3000 setLocalStorage('eisy-sso', { 3001 accessToken: data.data.accessToken, 3002 refreshToken: data.data.refreshToken, 3003 isyKey: isyKey 3004 }) 3005 3006 window.open('/eisy/' + version + '/', '_blank', 'menubar=1,location=1,resizable=1,scrollbars=1,status=1,toolbar=1'); 3007 }) 3008 .fail(function(jqXHR, textStatus, errorThrown) { 3009 errorMessageDialog('Error launching eisy-ui'); 3010 }) 3011 .done(); 3012} 3013 3014 3015function launchWebAccess(windowOption, specs) { 3016 var redirectUrl=["udajax.htm","had.htm"]; // UDAjax / HAD 3017 var redirectOption = 0; // Default Home page (0=UDAjax, 1 = HAD) 3018 var s = localStorage.getItem("redirectOption"); 3019 3020 if (s) { 3021 redirectOption = parseInt(JSON.parse(s)); 3022 } 3023 3024 if (redirectOption >
3024= redirectUrl.length) { // That would be an invalid option 3025 redirectOption = 0 3026 } 3027 3028 window.open('udajax/' + redirectUrl[redirectOption] + '?mode=corp', windowOption, specs); 3029} 3030 3031 3032function setLastConnected(lastConnected) { 3033 if (lastConnected && lastConnected[0]) { 3034 var minutes = (new Date - new Date(lastConnected[0].time))/60000; // returns diff minutes 3035 3036 $('#footer-lastconnection-label').show(); 3037 $('#footer-lastconnection-data').text(since(minutes)); 3038/* Not useful: The IP shown is from the load balancer! 3039 $('#footer-lastconnection-data').click(function () { 3040 lastConnectedDialog.setFields(registeredUser.lastConnected); 3041 lastConnectedDialog.modal.dialog('open'); 3042 }) 3043*/ 3044 } else { 3045 $('#footer-lastconnection-label').hide(); 3046 $('#footer-lastconnection-data').text(''); 3047 } 3048} 3049 3050function findInArray(arr, property, value) { 3051 return arr.find(function(item) { return item[property] === value }) 3052} 3053 3054// Zero-pad a number to a certain length 3055function pad(num, size) { 3056 var s = num+""; 3057 while (s.length < size) s = "0" + s; 3058 return s; 3059} 3060 3061function getIsPolisyOrEisy(productId) { 3062 if (!productId) { 3063 return false 3064 } 3065 3066 // ISY 994: '1110' 3067 // 52xx is an eisy 3068 return [ /^52\d\d$/ ].findIndex((regex) => regex.test(productId)) !== -1 3069} 3070 3071 3072// Returns firmware database, but will use cache if another request is within cacheDelay 3073// var firmwareDB = { 3074// cacheDelay: 3600000, // No need to refresh more often than 1 hour; 3075// data: null, 3076// lastRefresh: null, 3077// 3078// refresh: function () { 3079// var vars = this; 3080// 3081// return ajaxJsonGet('/api/firmwaredb') 3082// .then(function (xml) { 3083// vars.data = xml; 3084// vars.lastRefresh = new Date(); 3085// return vars.data; 3086// }) 3087// }, 3088// getXML: function () { 3089// if ((new Date() - this.lastRefresh) > this.cacheDelay ) { 3090// return this.refresh(); 3091// } 3092// else { 3093// return $.Deferred().resolve(this.data).promise(); 3094// } 3095// }, 3096// getVersions: function (appName, platform) { // Get available versions for appName/platform 3097// var deferred = $.Deferred(); 3098// 3099// this.getXML() 3100// .then(function (xml) { 3101// var latestVersion; 3102// var availableVersions = []; 3103// 3104// // Finds the right app 3105// $(xml).find("product>name:contains('"+ appName +"')").first().parent().each(function() { 3106// // Finds the current version 3107// latestVersion = $(this).children('currentVersion').text(); 3108// 3109// $(this).find("version").each( function() { 3110// var versionObj = {}; 3111// versionObj.version = $(this).children('number').text(); 3112// versionObj.description = $(this).children('description').text(); 3113// 3114// // Make sure the platform is found on this version before adding it to the list 3115// $(this).find("platform>name:contains('"+ platform +"')").first().each( function() { 3116// availableVersions.push(versionObj);
3117// }); 3118// }); 3119// }); 3120// 3121// availableVersions.sort(function (a, b) { 3122// if (a.version===latestVersion) { // Latest version will always be first. 3123// return -1; 3124// } 3125// else { 3126// return versionCompare(b.version, a.version) 3127// } 3128// }); 3129// 3130// deferred.resolve(availableVersions); 3131// }); 3132// 3133// return deferred.promise(); 3134// } 3135// }; 3136 3137 3138 3139// --------------- Ajax functions ------------------------- 3140 3141function ajaxJsonGet(url, query, headers) { 3142 return ajaxCall('GET', url, query, headers) 3143} 3144 3145function ajaxDownloadGet(url, query, headers) { 3146 var headers = headers || {} 3147 3148 if (registeredUser && registeredUser.basicAuth) { 3149 // It's faster to send the Authorization header from the get go 3150 headers['Authorization'] = 'Basic ' + registeredUser.basicAuth 3151 } else if (registeredUser && registeredUser.accessToken) { 3152 headers['Authorization'] = 'Bearer ' + registeredUser.accessToken 3153 } else if (registeredUser && registeredUser.jwt) { 3154 return getJwtAccessToken() 3155 .then(function (accessToken) { 3156 if (!accessToken) 3157 throw new Error('Session has expired') 3158 3159 headers['Authorization'] = 'Bearer ' + accessToken 3160 }) 3161 } 3162 3163 return new Promise((resolve, reject) => { 3164 // Construct URL with query string 3165 let fullUrl = url; 3166 if (query) { 3167 fullUrl = `${url}${url.includes('?') ? '&' : '?'}${query}`; 3168 } 3169 3170 const xhr = new XMLHttpRequest(); 3171 xhr.open('GET', fullUrl, true); 3172 xhr.responseType = 'blob'; 3173 3174 // Copy headers 3175 if (headers) { 3176 Object.keys(headers).forEach(key => { 3177 xhr.setRequestHeader(key, headers[key]); 3178 }); 3179 } 3180 3181 xhr.onload = function() { 3182 if (xhr.status === 200) { 3183 const blob = xhr.response; 3184 3185 // Get filename from Content-Disposition header 3186 let filename = 'data.dta'; // default filename 3187 const disposition = xhr.getResponseHeader('Content-Disposition'); 3188 if (disposition && disposition.indexOf('attachment') !== -1) { 3189 const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition); 3190 if (matches != null && matches[1]) { 3191 filename = matches[1].replace(/['"]/g, ''); 3192 } 3193 } 3194 3195 const url = window.URL.createObjectURL(blob); 3196 const a = document.createElement('a'); 3197 a.style.display = 'none'; 3198 a.href = url; 3199 a.download = filename; 3200 document.body.appendChild(a); 3201 a.click(); 3202 window.URL.revokeObjectURL(url); 3203 document.body.removeChild(a); 3204 resolve(); 3205 } else { 3206 reject(new Error('Download failed with status: ' + xhr.status)); 3207 } 3208 }; 3209 3210 xhr.onerror = function() { 3211 reject(new Error('Download failed')); 3212 }; 3213 3214 xhr.send(); 3215 }); 3216} 3217 3218 3219 3220function ajaxJsonDelete(url, query, headers) { 3221 return ajaxCall('DELETE', url, query, headers) 3222} 3223 3224function ajaxJsonPost(url, query, headers) { 3225 return ajaxCall('POST', url, query, headers) 3226} 3227 3228function ajaxJsonPostTimeout2M(url, query, headers) { 3229 return ajaxCall('POST', url, query, headers, { 3230 timeout:120000 3231 }) 3232} 3233 3234function ajaxJsonPostTimeout3M(url, query, headers) { 3235 return ajaxCall('POST', url, query, headers, { 3236 timeout:180000 3237 }) 3238} 3239 3240function ajaxJsonPostTimeout4M(url, query, headers) { 3241 return ajaxCall('POST', url, query, headers, { 3242 timeout:240000 3243 }) 3244} 3245 3246function ajaxJsonPut(url, query, headers) { 3247 return ajaxCall('PUT', url, query, headers) 3248} 3249 3250function ajaxIsyRestGet(url, isyKey) { 3251 return ajaxCall('GET', '/' + encodeURIComponent(isyKey) + url) 3252} 3253 3254function ajaxCall(method, url, query, customHeaders, customAjaxOptions) { 3255 var ajaxOptions = { 3256 type: method, 3257 url: url, 3258 headers: {}, 3259 }; 3260 3261 if (customAjaxOptions) { 3262 Object.assign(ajaxOptions, customAjaxOptions); 3263 } 3264 3265 if (query) { 3266 ajaxOptions.data = query; 3267 } 3268 3269 if (customHeaders) { 3270 for (var header in customHeaders) { 3271 if (Object.prototype.hasOwnProperty.call(customHeaders, header) ) { 3272 ajaxOptions.headers[header] = customHeaders[header]; 3273 } 3274 } 3275 } 3276 3277 return $.when() 3278 .then(function () { 3279 if (registeredUser && registeredUser.basicAuth) { 3280 // It's faster to send the Authorization header from the get go 3281 ajaxOptions.headers['Authorization'] = 'Basic ' + registeredUser.basicAuth 3282 } else if (registeredUser && registeredUser.accessToken) { 3283 ajaxOptions.headers['Authorization'] = 'Bearer ' + registeredUser.accessToken 3284 } else if (registeredUser && registeredUser.jwt) { 3285 return getJwtAccessToken() 3286 .then(function (accessToken) { 3287 if (!accessToken) 3288 throw new Error('Session has expired') 3289 3290 ajaxOptions.headers['Authorization'] = 'Bearer ' + accessToken 3291 }) 3292 } 3293 }) 3294 .then(function () { 3295 if (registeredUser) 3296 feedbackRequestingURL(method, url); 3297 3298 return $.ajax(ajaxOptions) 3299 .done(function(data, status, jqXHR) { 3300 setTimeout('feedback()', 800); 3301 }) 3302 .fail(function(jqXHR, textStatus, errorThrown) { 3303 feedbackErrURL(method, url); 3304 setTimeout('feedback()', 2000); 3305 }); 3306 }) 3307} 3308
3309function msgprefix(method) { 3310 var prefix = ''; 3311 3312 switch(method) { 3313 case 'GET': 3314 prefix = 'Retrieving'; 3315 break; 3316 case 'POST': 3317 prefix = 'Updating'; 3318 break; 3319 case 'PUT': 3320 prefix = 'Inserting'; 3321 break; 3322 case 'DELETE': 3323 prefix = 'Removing'; 3324 break; 3325 default: 3326 prefix = method; 3327 } 3328 return prefix; 3329} 3330 3331 3332// ------------ Message feedback functions ---------------------- 3333function feedbackRequestingURL(method, url) { 3334 feedback(msgprefix(method) + ': ' + url); 3335} 3336 3337function feedbackErrURL(method, url, errmsg) { 3338 feedback(msgprefix(method) + ': ' + url + ' (' + (errmsg?errmsg:'error returned') + ')'); 3339} 3340 3341function feedback(text, selector) { // Displays a status at the top of page 3342 if (!selector) { 3343 selector = '.feedback'; // Default feedback placeholder: any tag with the class feedback. 3344 } 3345 3346 if (!text || text === '') { 3347 text = '<br>'; 3348 } 3349 3350 $(selector).html(text); 3351} 3352 3353 3354// -------------- Data persistence across pages ------------ 3355 3356function setSessionStorage(key, content) { 3357 // Mobile browser with incognito mode will raise an error 3358 try { 3359 sessionStorage.setItem(key, JSON.stringify(content)); 3360 } 3361 catch(err) { 3362 console.log(err) 3363 } 3364} 3365 3366function deleteSessionStorage(key) { 3367 sessionStorage.removeItem(key); 3368} 3369 3370function getSessionStorage(key) { 3371 return JSON.parse(sessionStorage.getItem(key)) 3372} 3373 3374// -------------- Data persistence with no expiration ------------ 3375 3376function setLocalStorage(key, content) { 3377 // Mobile browser with incognito mode will raise an error 3378 try { 3379 localStorage.setItem(key, JSON.stringify(content)); 3380 } 3381 catch(err) { 3382 console.log(err) 3383 } 3384} 3385 3386function getLocalStorage(key) { 3387 return JSON.parse(localStorage.getItem(key)) 3388} 3389 3390function deleteLocalStorage(key) { 3391 localStorage.removeItem(key); 3392} 3393 3394 3395// returns UUID with No Colons 3396function uuidNC(uuid) { 3397 return uuid.toLowerCase().replace(uuidRegex, '$1$2$3$4$5$6'); 3398} 3399 3400// returns UUID with With Colons 3401function uuidWC(uuid) { 3402 return uuid.toLowerCase().replace(uuidRegex, '$1:$2:$3:$4:$5:$6'); 3403} 3404 3405// Compares firmware versions 3406// v2>v1 -> -1 3407// v2<v1 -> 1 3408// v2==v1 -> 0 3409function versionCompare(v1, v2) { 3410 v1 = /^([\d.]+)/.exec(v1)[0]; // Skips last portion of version that are not digits (Example: 5.0.8T2 -> 5.0.8) 3411 v2 = /^([\d.]+)/.exec(v2)[0]; 3412 3413 var v1parts = v1.split('.'); 3414 var v2parts = v2.split('.'); 3415 3416 function isValidPart(x) { 3417 return /^\d+$/.test(x); 3418 } 3419 3420 if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { 3421 return NaN; 3422 } 3423 3424 while (v1parts.length < v2parts.length) v1parts.push("0"); 3425 while (v2parts.length < v1parts.length) v2parts.push("0"); 3426 3427 v1parts = v1parts.map(Number); 3428 v2parts = v2parts.map(Number); 3429 3430 for (var i = 0; i < v1parts.length; ++i) { 3431 if (v2parts.length === i) { 3432 return 1; 3433 } 3434 3435 if (v1parts[i] === v2parts[i]) { 3436 continue; 3437 } 3438 else if (v1parts[i] > v2parts[i]) { 3439 return 1; 3440 } 3441 else { 3442 return -1; 3443 } 3444 } 3445 3446 if (v1parts.length !== v2parts.length) { 3447 return -1; 3448 } 3449 3450 return 0; 3451} 3452 3453function loadOpenadrReg(uuid, alias, pk, domain) { 3454 isyOpenadrRegDialog.setFields({ pk: pk, uuid: uuid, alias: alias }); 3455 isyOpenadrRegDialog.modal.dialog('open'); 3456 3457 ajaxJsonGet('/api/isy/oadr/retrieve', $.param({id: pk, domain: domain })) 3458 .done(function (data) { 3459 isyOpenadrRegDialog.setFields({ pk: pk, domain: domain, uuid: uuid, alias: alias, xml: data }); 3460 }) 3461 .fail(function(jqXHR, textStatus, errorThrown) { 3462 if (jqXHR.status==503) { 3463 errorMessageDialog('ISY is not online') 3464 } else if (jqXHR.status==423) { 3465 errorMessageDialog('Requested ISY not found') 3466 } else if (jqXHR.status==404) { 3467 errorMessageDialog('OpenADR configuration file not found') 3468 } else { 3469 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 3470 } 3471 }) 3472 .done(); 3473} 3474 3475// Read a page's GET URL variables and return them as an associative array. 3476function getUrlVars() { 3477 var vars = [], hash; 3478 var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); 3479 for(var i = 0; i < hashes.length; i++) 3480 { 3481 hash = hashes[i].split('='); 3482 vars.push(hash[0]); 3483 vars[hash[0]] = hash[1]; 3484 } 3485 return vars; 3486} 3487 3488function minimumVersionCheck(currentVersion, requiredVersion) { 3489 var successful = (versionCompare(currentVersion, requiredVersion) >= 0); 3490 3491 if (!successful) { 3492 errorMessageDialog('ISY Firmware version must be ' + nodeServerMinimumFirmware + ' or newer.', 'Error', 500); 3493 } 3494 3495 return successful 3496} 3497 3498 3499/* 3500function startAdminConsole(uuid, firmware) { 3501 try { 3502 var isFileSaverSupported = !!new Blob; 3503 } catch (e) {} 3504 3505 if (isFileSaverSupported) { 3506 ajaxJsonGet('/rsc/admin.tpl') 3507 .then(function(str) { 3508 str = str.replace(/\$CODEBASE/, 'http://isy.universal-devices.com/994i/'+firmware) 3509 str = str.replace(/\$UUID/, uuid) 3510 str = str.replace(/\$PORTALURL/, 'https://' + location.hostname + '/isy/' +uuidNC(uuid) +'/desc') 3511 3512 var blob = new Blob([str], {type: "text/xml;charset=utf-8"}); // application/x-java-jnlp-file 3513 saveAs(blob, "admin" + uuidNC(uuid) + ".jnlp"); 3514 }) 3515 .fail(function(jqXHR, textStatus, errorThrown) { 3516 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 3517 }) 3518 .done(); 3519 } 3520 else { 3521 errorMessageDialog('Feature not supported on this browser', 'Not supported') 3522 .then(function() { 3523 $(this).dialog('close'); 3524 }) 3525 } 3526} 3527*/ 3528 3529 3530// ********************************************************************** 3531// ***************************** Tables ******************************** 3532// ********************************************************************** 3533 3534$.widget("custom.toolMenu", $.ui.menu, { 3535// delay:2000, 3536 3537 // This is a fix to jQuery UI menu. When hovering main item, then going to a submenu item may not highlight the submenu item. 3538 // There is an extra event being fired with currentTarget tied to the parent menu item. It has to be skipped. 3539 3540 _create: function() { 3541 this._on({ 3542 "mouseenter .ui-menu-item": function( event ) { 3543 if (event.target.id > event.currentTarget.id) { 3544 event.stopImmediatePropagation() 3545 } 3546 } 3547 }); 3548 3549 this._super(); // The _create from the original ui-menu 3550 }, 3551 3552 _closeOnDocumentClick: function(event) { 3553 // If we click outside the menu, 3554 if (!($(event.target).hasClass('ui-menu-item') && $(event.target).parent().hasClass('toolMenu'))) { 3555 // Closes the menu (Not good for mobile devices) 3556 $(this.element) 3557 .hide() 3558 } 3559 3560 return false; 3561 }, 3562 3563 3564 // Like collapseAll, but with no delay. We use it to start menu with all collapsed. 3565 collapseAllNow: function(event, all) {
3566 // If we were passed an event, look for the submenu that contains the event 3567 var currentMenu = all ? this.element : 3568 $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); 3569 3570 // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway 3571 if (!currentMenu.length) { 3572 currentMenu = this.element; 3573 } 3574 3575 this._close(currentMenu); 3576 3577 this.blur(event); 3578 this.activeMenu = currentMenu; 3579 } 3580 3581}); 3582 3583 3584var gracePeriodEndDate = new Date ('2018-06-30T00:00:00Z'); 3585 3586function isExpired(ipal) { 3587 var nowValue = new Date().valueOf(); 3588 var moduleNotSet = ipal.expiry === undefined; 3589 var isPerpetual = ipal.expiry === null; 3590 var isPastExpiry = nowValue >= new Date(ipal.expiry).valueOf(); 3591 var isAfterGracePeriod = nowValue > gracePeriodEndDate.valueOf(); 3592 3593 return (!ipal || !ipal.active || moduleNotSet || (!isPerpetual && isPastExpiry && isAfterGracePeriod)) 3594} 3595 3596function checkIfExpired(isydata) { 3597 var expired = (isydata && isExpired(isydata.ipal)); 3598 var message=''; 3599 3600 if (expired && serverConfig.ipalEnabled) { 3601 if (isydata.ipal) { 3602 if (!isydata.ipal.active) { 3603 message = 'Access license is not active' 3604 } 3605 else { 3606 message = 'Access license is expired' 3607 } 3608 } 3609 else { 3610 message = 'Access license not found' 3611 } 3612 3613 errorMessageDialog(message, 'Error', 350); 3614 return false; 3615 } 3616 else { 3617 return true; 3618 } 3619} 3620 3621 3622function checkIfOnline(isydata) { 3623 var online = (isydata && isydata.online); 3624 3625 if (!online) { 3626 errorMessageDialog('ISY is not online.', 'Error', 350); 3627 return false; 3628 } 3629 else { 3630 return checkIfAllowed(isydata); 3631 } 3632} 3633 3634function checkIfAllowed(isydata) { 3635 var registered=null; 3636 3637 if (isydata && isydata.domain && isydata.domain[0]) { 3638 registered = isydata.domain[0].registered 3639 } 3640 3641 if (registered==0) { 3642 errorMessageDialog('Access denied.<br>Pending approval by owner.', 'Access denied', 350); 3643 return false; 3644 } 3645 else if (registered==-1) { 3646 errorMessageDialog('Access has been revoked by owner.', 'Access denied', 350); 3647 return false; 3648 } 3649 else { 3650 return true; 3651 } 3652} 3653 3654function isLicenseActive(webStore) { 3655 var licenseActive; 3656 3657 if (!webStore.module) { 3658 licenseActive = false; 3659 } 3660 else { 3661 // Perpetual license is installed 3662 if (webStore.expiry==null) { 3663 licenseActive = true; 3664 } 3665 // Regular license is installed 3666 else if (webStore.expiry) { 3667 var expiryDate = new Date(webStore.expiry); 3668 licenseActive = webStore.module.active && expiryDate.valueOf() > new Date().valueOf(); 3669 } 3670 } 3671 3672 return licenseActive; 3673} 3674 3675function isLicenseInstalled(webStore) { 3676 return webStore.module?!!webStore.module.installed:false; 3677} 3678 3679// Open the license window (if fromNewIsy==false) 3680function ipalDialog(uuid, alias, pk, isyDomain, fromNewIsy, online, registered, firmware, redirectUrl) { 3681 3682 if (serverConfig.ipalEnabled) { 3683 ajaxJsonGet('/api/isy/ipal', $.param({id: pk, domain: isyDomain })) 3684 .done(function (info) { 3685 var licenseActive = isLicenseActive(info.webStore); 3686 var licenseInstalled = isLicenseInstalled(info.webStore); 3687 // Open the license Window if 3688 if (!licenseActive || !fromNewIsy || redirectUrl) { 3689 isyAccessLicenseDialog.init() 3690 isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, pk: pk, domain: isyDomain, info: info, online:online, registered:registered, redirectUrl:redirectUrl }); 3691 3692 if (redirectUrl) { 3693 isyAccessLicenseDialog.setRedirectUrl(redirectUrl) 3694 } 3695 3696 isyAccessLicenseDialog.modal.dialog('open'); 3697 } else { 3698 var silentSuccess=true; // No popup if everything is good. 3699 var freeTrialAvailable = (info.webStore.module == null); 3700 3701 accessTroubleshootingMessage({ 3702 freeTrialAvailable: freeTrialAvailable, 3703 licenseActive: licenseActive, 3704 licenseInstalled: licenseInstalled, 3705 online: online, 3706 registered: registered, 3707 firmware: firmware 3708 }, silentSuccess) 3709 } 3710 }) 3711 .fail(function(jqXHR, textStatus, errorThrown) { 3712 var errorMessage = 'HTTP error ' + jqXHR.status 3713 if (redirectUrl) { 3714 redirectWithError(redirectUrl, errorMessage) 3715 } else { 3716 errorMessageDialog(errorMessage, 'Error', 350) 3717 } 3718 }) 3719 .done(); 3720 } else { 3721 // Should only be here after having added an ISY 3722 accessTroubleshootingMessage({ 3723// freeTrialAvailable: null, // Not used when licenseActive==true; 3724 licenseActive: true, // Will not show a license warning 3725 licenseInstalled: true, // Will not show a license warning 3726 online: online, 3727 registered: registered, 3728 firmware: firmware 3729 }, silentSuccess) 3730 } 3731} 3732 3733function accessTroubleshooting(uuid, alias, pk, isyDomain, online, registered, firmware) { 3734 var silentSuccess=false; 3735 3736 if (serverConfig.ipalEnabled) { 3737 ajaxJsonGet('/api/isy/ipal', $.param({id: pk, domain: isyDomain })) 3738 .done(function (info) { 3739 var licenseActive = isLicenseActive(info.webStore); 3740 var licenseInstalled = isLicenseInstalled(info.webStore); 3741 var freeTrialAvailable = (info.webStore.module == null); 3742 3743 accessTroubleshootingMessage({ 3744 freeTrialAvailable: freeTrialAvailable, 3745 licenseActive: licenseActive, 3746 licenseInstalled: licenseInstalled, 3747 online: online, 3748 registered: registered, 3749 firmware: firmware 3750 }, silentSuccess) 3751 }) 3752 .fail(function(jqXHR, textStatus, errorThrown) { 3753 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 3754 }) 3755 .done(); 3756 } 3757 else { 3758 accessTroubleshootingMessage({ 3759// freeTrialAvailable: null, // Not used when licenseActive==true; 3760 licenseActive: true, // Will not show a license warning 3761 licenseInstalled: true, // Will not show a license warning 3762 online: online, 3763 registered: registered, 3764 firmware: firmware 3765 }, silentSuccess) 3766 } 3767} 3768 3769function accessTroubleshootingMessage(conn, silentSuccess) { 3770 var message; 3771 3772 var is600AndUp = (conn.firmware && versionCompare(conn.firmware, '6.0.0') >= 0); 3773 3774 var troubleshooting='In order to use your ISY on ISY portal, please make sure to:<br><br><ol>'; 3775 var actionFound=false; 3776 3777 if (!conn.licenseActive) { 3778 troubleshooting += '<li>In ISY Portal, click on Select Tool | Information | ISY Portal Access License and buy a license' + (conn.freeTrialAvailable?' or choose Free Trial':'') + '</li>'; 3779 actionFound=true; 3780 } 3781 3782 // if (!conn.licenseActive || !conn.licenseInstalled) { 3783 // troubleshooting += '<li>In the Admin Console, click on Help | Manage modules to install the license. This will reboot ISY</li>'; 3784 // actionFound=true; 3785 // } 3786 3787 if (!conn.online || !conn.licenseActive || !conn.licenseInstalled) { 3788 troubleshooting += '<li>Wait for the ISY to come online on ISY portal. In the ISY list, you can click refresh to update the online status</li>' 3789 } 3790 3791 if (conn.registered==-1) { 3792 troubleshooting += is600AndUp ? 3793 '<li>Open eisy-ui at <a href="http://eisy.local/settings/portal">http://eisy.local/settings/portal</a>, and re-approve prove the access request</li>' : 3794 '<li>Access from this account is currently revoked. You will have to remove the ISY from this account and re-add it. Your license will be kept, but your other configuration items such as Alexa spokens will be lost</li>'; 3795 actionFound=true; 3796 } 3797 3798 if (conn.registered==0) { 3799 troubleshooting += is600AndUp ? 3800 '<li>Open eisy-ui at <a href="http://eisy.local/settings/portal">http://eisy.local/settings/portal</a>, and approve the access request</li>' : 3801 '<li>In the Admin Console, click on Configuration | Portal tab, and approve the access request for this account</li>'; 3802 actionFound=true; 3803 } 3804 3805 troubleshooting += '<li>You are done!</li></ol>'; 3806 3807 // If we have errors to handle (other than just not online) 3808 if (actionFound) { 3809 message = troubleshooting; 3810 } 3811 else if (!silentSuccess) { 3812 if (!conn.online) { 3813 message = 'Everything appears to be configured correctly, but your ISY does not appear to be online. ' + 3814 'If it does not come online after 2-3 minutes, you may try the following:<ul>' + 3815 '<li>Reboot your ISY.</li>' + 3816 '<li>Reboot your router.</li>' + 3817 '<li>In ISY Portal, on the ISY tab, click refresh a few times.</li></ul>' + 3818 'If it still does not connect, please open a ticket at:<br> <a href="https://www.universal-devices.com/contact-support/" target="_blank" class="editable_link">https://www.universal-devices.com/contact-support/</a>' 3819 } 3820 else { 3821 message = 'Everything appears to be configured correctly!'; 3822 } 3823 } 3824 3825 if (message) { 3826 informationMessageDialog(message, 'Troubleshooting tips', 800); 3827 } 3828} 3829 3830
3831var tableIsys = { 3832 api: null, 3833 currentDomain: null, // Selected Domain 3834 // isRatesAccount: false, // Set at the same time of currentDomain 3835 // isSceAccount: false, // Set at the same time of currentDomain 3836 // isSceAccountFlagEnabled: false, // Set at the same time of currentDomain 3837 showAll: null, // Show sub-accounts as well? 3838 domainIsys: null, // Domain data returned by /isys 3839 isSameDomain: false, 3840 isDomainAdmin: false, 3841 isSuperAdmin: false, 3842 isCorporate: false, 3843 isUtility: false, 3844 isPilot: false, 3845 now: null, // onDataLoad initializes it once per refresh 3846 soon: null, // onDataLoad initializes it once per refresh 3847 3848 // Triggered when json data has loaded (before table draw) 3849 onDataLoad: function (data) { 3850// console.log('data', data.data.map(function(rec) { 3851// return rec.uuid 3852// }).join('\r\n')) 3853 this.domainIsys = data.domain; 3854 this.userDomainChildren = registeredUser.domainInfo.children?registeredUser.domainInfo.children:[]; 3855 this.isSuperAdmin = registeredUser.superadmin; 3856 this.isDomainAdmin = (this.isSuperAdmin || registeredUser.domainadmin); 3857 this.isUtility = (this.isSuperAdmin || this.domainIsys.type=='0'); 3858 this.isCorporate = (this.isSuperAdmin || this.domainIsys.type=='1'); 3859 this.isPilot = pilots.includes(registeredUser.domain); 3860 this.now = new Date().valueOf(); 3861 this.soon = new Date(); 3862 this.soon.setDate(this.soon.getDate() + accessLicenseExpirationSoonDays); // 30 days from now 3863 this.soon = this.soon.valueOf(); 3864 }, 3865
3866 completeUI : function() { 3867 var table = this; 3868 table.api.columns.adjust(); 3869 3870 $('#isys_table_container').show(); 3871 $('#isys_table_container').tooltip(); 3872 3873 // Setup the tool button 3874 $('#isystable .toolMenuButton').button({ 3875 width:'12em', 3876 label: 'Select tool...', 3877 icons: { secondary: "ui-icon-triangle-1-s" } 3878 }) 3879 .unbind('click') 3880 .click(function (event) { 3881 $('#isystable .toolMenu').not($(this).next()).hide(); // Close all other menus 3882 $(this).next() 3883 .toolMenu("collapseAllNow", null, true ) // Start with collapsed menus 3884 .toggle(); // Toggle the active one 3885 event.stopPropagation(); // Otherwise, it won't open 3886 }); 3887 3888 // Setup the tool menu underneath the button 3889 $('#isystable .toolMenu').toolMenu({ 3890 width:'14em', 3891 // position: { my: "left top", at: "left+10 bottom" }, // Position of submenu relative to parent 3892 select: function(event, ui) { 3893 var tool = ui.item.attr('value'); 3894 3895 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 3896 var uuid = table.api.cell($(this).closest('tr'), 'uuid:name').data(); 3897 var isyKey = table.api.cell($(this).closest('tr'), 'isyKey:name').data(); 3898 var alias = table.api.cell($(this).closest('tr'), 'alias:name').data(); 3899 var isyDomain = table.api.cell($(this).closest('tr'), 'isyDomain:name').data(); 3900 var registered = table.api.cell($(this).closest('tr'), 'registered:name').data(); 3901 var isydata = table.api.row($(this).closest('tr')).data(); 3902 var firmware = table.api.cell($(this).closest('tr'), 'firmware:name').data(); 3903 var isWs = table.api.cell($(this).closest('tr'), 'isWs:name').data(); 3904 3905 currentISY = { alias: alias, uuid: uuid, isyKey: isyKey, domain: isyDomain }; 3906 setSessionStorage('udidevice', currentISY); 3907 3908 function checkAmazonEchoLink() { 3909 var noAmazonLink = !registeredUser.amz_email || !registeredUser.amz_email.length; // Careful: At link time, amz_user_id is not set unless we relogin! 3910 var noAmazonLinkMsg = 3911 'Your portal user profile is not linked to an Amazon account. ' + 3912 'This will prevent you from using the Alexa Smart Home skill. To link your account, please go to My profile.'; 3913 3914 if (noAmazonLink) { 3915 errorMessageDialog(noAmazonLinkMsg, 'Warning', 600); 3916 } 3917 } 3918 3919 switch(tool) { 3920 3921 case 'openadr': 3922 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 3923 window.open('openadr.htm', '_blank'); 3924 } 3925 break; 3926 3927 case 'udajax': 3928 $(this).hide(); // For some reason, we need this, otherwise the menu item stays open. 3929 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 3930 launchWebAccess('_blank', 'menubar=1,location=1,resizable=1,scrollbars=1,status=1,toolbar=1'); 3931 } 3932 break; 3933 3934 case 'eisyui': 3935 $(this).hide(); 3936 if (!isWs) { 3937 errorMessageDialog("Remote eisy-ui require eisy-ui version 1.0.0 or higher.", 'Error', 575) 3938 } else if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 3939 launchEisyui(uuid, isyKey, isydata.eisyui.version); 3940 } 3941 break; 3942 /* 3943 case 'adminconsole': 3944 if (checkIfOnline(isydata) && isydata) { 3945 var firmware = table.api.cell($(this).closest('tr'), 'firmware:name').data(); 3946 startAdminConsole(uuid, firmware); 3947 } 3948 break; 3949 */ 3950 case 'eventlog': 3951 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 3952 window.open('eventlog.htm', '_blank'); 3953 } 3954 break; 3955 3956 case 'errorlog': 3957 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 3958 var user = getSessionStorage('udiportal'); 3959 var data = getSessionStorage('udidevice'); 3960 3961 window.open('errorlog.htm', '_blank'); 3962 } 3963 break; 3964 3965 case 'activitylog': 3966 activityLogDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 3967 activityLogDialog.modal.dialog('open'); 3968 break; 3969 3970 case 'contact': 3971 var fields = {}; 3972 var domainContact; 3973 3974 if (isydata) { 3975 fields.pk = pk; 3976 fields.uuid = uuid; 3977 fields.alias = alias; 3978 3979 if (isydata.domain && isydata.domain[0]) { 3980 if (!isydata.domain[0].contact) { 3981 isydata.domain[0].contact = {}; // If no contact info entered so far, start from blank 3982 } 3983 3984 domainContact = isydata.domain[0].contact; 3985 3986 fields.name = domainContact.name; 3987 fields.phone = domainContact.phone; 3988 fields.email = domainContact.email; 3989 fields.address = domainContact.address; 3990 fields.handle = domainContact; 3991 3992 isyContactDialog.setFields(fields); 3993 isyContactDialog.setDomain(isyDomain); // It will be passed to the API 3994 isyContactDialog.modal.dialog('open'); 3995 } 3996 } 3997 3998 break; 3999 4000 case 'note': 4001 if (isydata && isydata.domain && isydata.domain[0]) { 4002 if (!isydata.domain[0].note) { 4003 isydata.domain[0].note='' 4004 } 4005 4006 isyNoteDialog.setFields({ pk: pk, note: isydata.domain[0].note, uuid: uuid, alias: alias, handle: isydata }); 4007 isyNoteDialog.setDomain(isyDomain); // It will be passed to the API 4008 isyNoteDialog.modal.dialog('open'); 4009 } 4010 4011 break; 4012 4013 // case 'firmware': 4014 // if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4015 // if (isydata && isydata.isy && isydata.isy.app && isydata.isy.platform) { 4016 // firmwareDB.getVersions(isydata.isy.app, isydata.isy.platform) 4017 // .then(function (availableVersions) { 4018 // // availableVersions.splice(1, 0, { version: "Beta version", description: 'A beta version can be uploaded.<br>Please select a firmware zip file' }); 4019 // isyFirmwareDialog.setFields({ pk: pk, uuid: uuid, alias: alias, currentVersion: isydata.isy.firmware, availableVersions: availableVersions }); 4020 // isyFirmwareDialog.setDomain(isyDomain); // It will be passed to the API 4021 // isyFirmwareDialog.modal.dialog('open'); 4022 // }) 4023 // .done(); 4024 // } 4025 // else { 4026 // errorMessageDialog('Database error.<br/>ISY registration data not found.'); 4027 // } 4028 // } 4029 // break; 4030 4031 case 'eisy': 4032 if (checkIfOnline(isydata)) { 4033 eisyProvisioningDialog.setFields({ 4034 pk: pk, 4035 uuid: uuid, 4036 alias: alias, 4037 domain: isyDomain, 4038 iotProvisioned: isydata.isy.iot.provisioned, 4039 iotProvisioningAvailable: isydata.isy.iot.provisioningAvailable, 4040 iotOnline: isydata.iotOnline, 4041 iotOnlineLastModified: isydata.iotOnlineLastModified 4042 });
4043 eisyProvisioningDialog.modal.dialog('open'); 4044 } 4045 break; 4046 4047 case 'reboot': 4048 if (checkIfOnline(isydata)) { 4049 confirmationDialog('ISY will be rebooted.<br/>Are you sure?', 'Reboot ISY?') 4050 .then(function () { 4051 ajaxJsonGet('/api/isy/reboot', $.param({id: pk, domain: isyDomain })) 4052 .done(function () { 4053 tableIsys.reload(); 4054 }) 4055 .fail(function(jqXHR, textStatus, errorThrown) { 4056 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 4057 }) 4058 }) 4059 .done(); 4060 } 4061 break; 4062 4063 case 'migrate': 4064 ajaxJsonGet('/api/isys', $.param({domain: isyDomain })) 4065 .then(function (result) { 4066 var uuidList = result.data 4067 .filter(function (item) { 4068 return item.uuid !== uuid; 4069 }) 4070 .map(function (item) { 4071 return { 4072 label: item.uuid, // Shown in the dropdown list 4073 value: item.uuid, // Sets the input field to this value when selecting from the dropdown 4074 data: item.uuid, 4075 firmware: item.isy && item.isy.firmware, 4076 option: item 4077 } 4078 }); 4079 4080 migrateDialog.setFields({pk: pk, uuid: uuid, uuidList: uuidList, domain: isyDomain }); 4081 migrateDialog.modal.dialog('open'); 4082 }) 4083 .fail(function(jqXHR, textStatus, errorThrown) { 4084 errorMessageDialog("Can't retrieve ISY list<br>HTTP error " + jqXHR.status, 'Error', 350) 4085 }) 4086 break; 4087 4088 case 'upgrade_packages': 4089 if (checkIfOnline(isydata)) { 4090 confirmationDialog('This will upgrade all of your packages.<br/><br/>Depending on the size of the upgrade, this process may take up to 30 minutes.<br/><br/>Are you sure?', 'Upgrade packages?', 800) 4091 .then(function () { 4092 ajaxJsonGet('/api/eisy/upgrade_packages', $.param({ uuid: uuid })) 4093 .done(function (content, status, jqXHR) { 4094 informationMessageDialog('System upgrade in progress.<br/><br/>please do not power off.') 4095 }) 4096 .fail(function(jqXHR, textStatus, errorThrown) { 4097 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 4098 }) 4099 .done(); 4100 }) 4101 .done(); 4102 } 4103 break; 4104 4105 4106 case 'eisyui_settings': 4107 if (checkIfOnline(isydata)) { 4108 ajaxJsonGet('/api/eisy/settings', $.param({ uuid: uuid, domain: isyDomain })) 4109 .then(function (result) { 4110 eisyuiSettingsDialog.setFields({ pk: pk, uuid: uuid, domain: isyDomain, settings: result.data }); 4111 eisyuiSettingsDialog.modal.dialog('open'); 4112 }) 4113 } 4114 break; 4115 4116 4117 case 'isyinfo': 4118 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4119 isyInfoDialog.setFields({ uuid: uuid, alias: alias }); 4120 isyInfoDialog.modal.dialog('open'); 4121 4122 ajaxJsonGet('/api/isy/info', $.param({id: pk, domain: isyDomain })) 4123 .done(function (data) { 4124 isyInfoDialog.setFields({ uuid: uuid, alias: alias, info: data }); 4125 }) 4126 .fail(function(jqXHR, textStatus, errorThrown) { 4127 if (jqXHR.status==503) { 4128 errorMessageDialog('ISY is not online') 4129 } 4130 else { 4131 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 4132 } 4133 }) 4134 .done(); 4135 } 4136 break; 4137 4138 case 'ipal': 4139 ipalDialog(uuid, alias, pk, isyDomain, false, isydata.online, registered, firmware, null); 4140 break; 4141 4142 case 'troubleshoot': 4143 accessTroubleshooting(uuid, alias, pk, isyDomain, isydata.online, registered, firmware); 4144 break; 4145 4146 case 'openadrReg': 4147 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4148 loadOpenadrReg(uuid, alias, pk, isyDomain); 4149 } 4150 break; 4151 4152 case 'echo_spoken_nodes': 4153 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4154 echoSpokenNodesDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); // , subscriptionEnabled:data.data.state, skillEnabled:data.data.skillEnabled 4155 echoSpokenNodesDialog.modal.dialog('open'); 4156 } 4157 break; 4158 4159 case 'gh_spoken_nodes': 4160 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4161 ghSpokenNodesDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 4162 ghSpokenNodesDialog.modal.dialog('open'); 4163 } 4164 break; 4165 4166 case 'ifttt_in_nodes': 4167 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4168 iftttInNodesDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 4169 iftttInNodesDialog.modal.dialog('open'); 4170 } 4171 break; 4172 4173 case 'ns_list': 4174 nsListDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 4175 nsListDialog.modal.dialog('open'); 4176 break; 4177 4178 case 'ns_reinstall': 4179 if (isydata.iotOnline) { 4180 confirmationDialog('This will update all of your Plugins in PG3x to their latest version.<br/><br/>You can expect the process to take a few minutes.<br/><br/>Are you sure?', 'Reinstall Plugins?', 800) 4181 .then(function () {
4182 ajaxJsonGet('/api/eisy/nodeserver/reinstall', $.param({ uuid: uuid })) 4183 .done(function (data) { 4184 informationMessageDialog('Plugins upgrade in progress.') 4185 }) 4186 .fail(function(jqXHR, textStatus, errorThrown) { 4187 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 4188 }) 4189 .done(); 4190 }) 4191 .done(); 4192 } else { 4193 errorMessageDialog('PG3 remote connection is not active') 4194 } 4195 break; 4196 4197 case 'hint_edit': 4198 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4199 ajaxJsonGet('/api/nodes/lookup', $.param({ uuid: uuid, domain: isyDomain })) 4200 .done(function(content, status, jqXHR){ 4201 content.data.devices.map(function (item) { 4202 var path = item.path.map(function(p) { return p.name }) 4203 .concat([ item.name ]) 4204 .join(' / '); 4205 4206 item.data = item.address; 4207 item.value = item.label = path + ' (' + item.address.replace(/ /g, '.') + ')'; 4208 item.option = item; // Self reference; 4209 }); 4210 4211 content.data.devices.sort(function(a, b) { 4212 return a.value.localeCompare(b.value) } 4213 ); 4214 4215 hintEditorDialog.setFields({ pk: pk, uuid: uuid, domain: isyDomain, lookup: content.data.devices }); 4216 hintEditorDialog.modal.dialog('open'); 4217 }) 4218 .fail(function(jqXHR, textStatus, errorThrown) { 4219 errorMessageDialog("Devices are not available", 'Error', 350) 4220 }) 4221 .done(); 4222 } 4223 break; 4224 4225 case 'nsconfig': 4226 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4227 if (minimumVersionCheck(isydata.isy.firmware, nodeServerMinimumFirmware)) { 4228 nsConfigDialog.setFields({ uuid: uuid, alias: alias, pk: pk, isyDomain: isyDomain }); 4229 nsConfigDialog.modal.dialog('open'); 4230 nsConfigDialog.refresh(); 4231 } 4232 } 4233 break; 4234 4235 case 'nsoccupancy': 4236 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4237 if (minimumVersionCheck(isydata.isy.firmware, nodeServerMinimumFirmware)) { 4238 nsOccupancyDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 4239 nsOccupancyDialog.modal.dialog('open'); 4240 } 4241 } 4242 break; 4243 4244 case 'nsoccupancy2': 4245 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4246 if (minimumVersionCheck(isydata.isy.firmware, nodeServerMinimumFirmware)) { 4247 nsOccupancy2Dialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 4248 nsOccupancy2Dialog.modal.dialog('open'); 4249 } 4250 } 4251 break; 4252 4253 case 'nssolaredge': 4254 if (checkIfOnline(isydata) && checkIfExpired(isydata)) { 4255 if (minimumVersionCheck(isydata.isy.firmware, nodeServerMinimumFirmware)) { 4256 nsSolaredgeDialog.setFields({ uuid: uuid, domain: isyDomain, alias: alias }); 4257 nsSolaredgeDialog.modal.dialog('open'); 4258 } 4259 } 4260 break; 4261 4262 default: 4263 // RATES 4264 // if (ratesMenuOptions) { 4265 // ratesMenuOptions({ 4266 // tool: tool, 4267 // isydata: isydata, 4268 // uuid: uuid, 4269 // alias: alias, 4270 // pk: pk, 4271 // isyDomain: isyDomain 4272 // }) 4273 // } 4274 4275 // SCE Pilot project 4276 // if (sceMenuOptions) { 4277 // sceMenuOptions({ 4278 // tool: tool, 4279 // isydata: isydata, 4280 // uuid: uuid, 4281 // alias: alias, 4282 // pk: pk, 4283 // isyDomain: isyDomain 4284 // }) 4285 // } 4286 } 4287 } 4288 }) 4289 .hide() 4290 .addClass('ui-front'); 4291 4292 // Only a domain admin or superadmin can delete ISY's 4293 if (this.isDomainAdmin) { 4294 $('#isys_table_container .delbutton').unbind('click').click(function (e) { 4295 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 4296 var isyDomain = table.api.cell($(this).closest('tr'), 'isyDomain:name').data(); 4297 4298 confirmationDialog('ISY will be removed from account.<br/>Are you sure?', 'Remove ISY?') 4299 .then(function () { 4300 ajaxJsonDelete('/api/isy/domain', $.param({id: pk, domain: isyDomain })) 4301 .done(function () { 4302 tableIsys.reload(); 4303 }) 4304 }) 4305 .done(); 4306 }); 4307 } 4308 4309 // Edit existing ISY 4310 $('#isys_table_container .alias').click(function () { 4311 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 4312 var uuid = table.api.cell($(this).closest('tr'), 'uuid:name').data(); 4313 var alias = table.api.cell($(this).closest('tr'), 'alias:name').data(); 4314 var isyDomain = table.api.cell($(this).closest('tr'), 'isyDomain:name').data(); 4315 4316 isyClaimDialog.setFields({ pk: pk, uuid: uuid, alias: alias }); 4317 isyClaimDialog.setDomain(isyDomain); // This will allow to refresh the list with the same domain as now 4318 isyClaimDialog.modal.dialog('open'); 4319 }); 4320 4321 // Shortcut to open the license window 4322 $('#isys_table_container .expired, #isys_table_container .warning').click(function () { 4323 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 4324 var uuid = table.api.cell($(this).closest('tr'), 'uuid:name').data(); 4325 var alias = table.api.cell($(this).closest('tr'), 'alias:name').data(); 4326 var isyDomain = table.api.cell($(this).closest('tr'), 'isyDomain:name').data(); 4327 var registered = table.api.cell($(this).closest('tr'), 'registered:name').data(); 4328 var isydata = table.api.row($(this).closest('tr')).data(); 4329 var firmware = table.api.cell($(this).closest('tr'), 'firmware:name').data(); 4330 4331 ipalDialog(uuid, alias, pk, isyDomain, false, isydata.online, registered, firmware, null) 4332 }); 4333 }, 4334 4335 // Hidden data used for sorting the online indicator 4336 dataOnline: function (row, type, set, meta) { 4337 return (typeof row.online !== 'undefined')?(row.online?1:0):-1; 4338 }, 4339 4340 renderUUIDwithDelete: function (data, type, row, meta) { 4341 var table = tableIsys; 4342 var licenseIndicator = ''; 4343 4344 if (serverConfig.ipalEnabled) { 4345 if (row.ipal) { 4346 if (!row.ipal.active) { 4347 licenseIndicator = '<span class="expired" title="Access license is not active"><span class="ui-icon ui-icon-alert" style="display:inline-block;width:16px"></span></span>' 4348 } 4349 else if (row.ipal.expiry) { 4350 var expDt = new Date(row.ipal.expiry).valueOf(); 4351 4352 if (expDt < table.now) { 4353 licenseIndicator = '<span class="expired" title="Access license is expired"><span class="ui-icon ui-icon-alert" style="display:inline-block;width:16px"></span></span>' 4354 } 4355 else if (expDt < table.soon) { 4356 licenseIndicator = '<span class="warning" title="Access license is expiring soon"><span class="ui-icon ui-icon-alert" style="display:inline-block;width:16px"></span></span>' 4357 } 4358 } 4359 } 4360 else { 4361 licenseIndicator = '<span class="expired" title="Access license not found"><span class="ui-icon ui-icon-alert" style="display:inline-block;width:16px"></span></span>' 4362 } 4363 } 4364 4365 return (registeredUser && registeredUser.domainadmin?'<span class="delbutton"><span class="ui-icon ui-icon-closethick" style="display:inline-block;width:16px"></span></span>':'') + 4366 '<span>' + data + 4367 // (tableIsys.isRatesAccount && row.facilityId?' / ' + row.facilityId:'') + 4368 // (tableIsys.isSceAccountFlagEnabled && !row.sceIsActivated?'<span title="SCE Pilot is not activated" class="ui-icon ui-icon-flag" style="display:inline-block;width:16px"></span>':'') + 4369 licenseIndicator +
4370 '</span>' 4371 }, 4372 4373 renderOnlineIndicator: function (data, type, row) { 4374 if (row.online) { 4375 if (row.domain[0].registered==-1) { 4376 return '<div class="isystatus revoked" title="Access revoked by Owner.\nOnline since ' + dateFormatNoError(row.lastModified, "dddd, mmmm dS, h:MM:ss TT") + 4377 '"><span style="display: none;">' + data + '</span></div>'; 4378 } 4379 else if (row.domain[0].registered==0) { 4380 return '<div class="isystatus pending" title="Pending approval by owner.\nOnline since ' + dateFormatNoError(row.lastModified, "dddd, mmmm dS, h:MM:ss TT") + 4381 '"><span style="display: none;">' + data + '</span></div>'; 4382 } 4383 else { 4384 return '<div class="isystatus online" title="Online since ' + dateFormatNoError(row.lastModified, "dddd, mmmm dS, h:MM:ss TT") + 4385 '"><span style="display: none;">' + data + '</span></div>'; 4386 } 4387 } 4388 else { 4389 return '<div class="isystatus offline" title="' + (row.lastModified?('Offline since ') + dateFormatNoError(row.lastModified, "dddd, mmmm dS, h:MM:ss TT"):'ISY never connected') + 4390 '"><span style="display: none;">' + data + '</span></div>'; 4391 } 4392 }, 4393 4394 renderAlias: function (data, type, row) { 4395 // row.domain[0].id + '/'+ (To display domain) 4396 return '<span class="alias editable_link">' + (data && data.length?data:"\[Empty\]") + '</span>'; 4397 }, 4398 4399 renderTools: function (data, type, row, table) { 4400 // if ISY domain == user domain 4401 var isSameDomain = (registeredUser && (row.domain[0].id === registeredUser.domain)); 4402 // if ISY domain == user domain OR ISY domain is part of the children of the user domain 4403 var isSameOrParentDomain = (isSameDomain || this.userDomainChildren.includes(row.domain[0].id)); 4404 var isIotAvailable = row.isy && row.isy.iot && row.isy.iot.provisioningAvailable 4405 var isPolisyOrEisy = getIsPolisyOrEisy(row.isy && row.isy.product && row.isy.product.id) 4406 var isEisyUi = !!(row.isy && row.isy.multiConnectionSupport) 4407 var isWs = row.isWs 4408 4409// var isRates = isRatesAccount(row.domain[0].id) 4410// var isRates = registeredUser && isRatesAccount(registeredUser.domainInfo); // We check the domain of the user, not the domain of the ISY!! We we also check that the ISY is from the same domain of the user. 4411// var isSce = registeredUser && isSceAccount(registeredUser.domainInfo); // We check the domain of the user, not the domain of the ISY!! We we also check that the ISY is from the same domain of the user. 4412 4413// console.log('Rights', isSameOrParentDomain, this.isDomainAdmin, this.isRates) 4414 return '<div><button class="toolMenuButton"></button>' + 4415 '<ul class="toolMenu">' + 4416 (isSameDomain && this.isCorporate?'<li value="udajax">ISY Web Access</li>':'') + 4417 (isSameDomain && this.isCorporate && isWs ?'<li value="eisyui">eisy-ui</li>':'') + 4418 (((isSameOrParentDomain || this.isSuperAdmin) && this.isDomainAdmin && this.isCorporate)? 4419 '<li>Connectivity<ul>' + 4420 '<li value="echo_spoken_nodes">Amazon Echo</li>' + 4421 '<li value="gh_spoken_nodes">Google Home</li>' + 4422 '<li value="ifttt_in_nodes">IFTTT / Webhooks</li>' + 4423 '<li value="hint_edit">Device Hint Editor</li>' + 4424 '</ul></li>':'') + 4425 (((isSameOrParentDomain || this.isSuperAdmin) && this.isDomainAdmin && this.isCorporate)? 4426 '<li>PG3<ul>' + 4427 '<li value="ns_list">Purchased Plugins</li>' + 4428 (isIotAvailable ? '<li value="eisy">Remote Connection</li>' : '') + 4429 (isIotAvailable ? '<li value="ns_reinstall">Reinstall all Plugins</li>' : '') + 4430 '</ul></li>':'') + 4431 // RATES 4432 // ((isSameOrParentDomain && this.isDomainAdmin && isRates)? 4433 // '<li>RATES<ul>' + 4434 // '<li value="ratesTemixAccountLinking">Temix Account Link</li>' + 4435 // '<li value="ratesConfiguration">Facility Configuration</li>' + 4436 // '<li value="ratesDevices">Devices Configuration</li>' + 4437 // '<li value="ratesDataQuery">Reports</li>' + 4438 // '</ul></li>':'') + 4439 // SCE 4440 // ((isSameOrParentDomain && this.isDomainAdmin && isSce)? 4441 // '<li>Energy Expert<ul>' + 4442 // '<li value="sceAccountActivation">Energy Expert Activation</li>' + 4443 // '<li value="sceConfiguration">Facility Configuration</li>' + 4444 // '<li value="sceDevices">Devices Configuration</li>' + 4445 // // '<li value="sceDataQuery">Reports</li>' + 4446 // '</ul></li>':'') + 4447 ((isSameOrParentDomain && this.isDomainAdmin && this.isCorporate)? // && this.isPilot 4448 '<li>Occupancy Node Server<ul>' + 4449 '<li value="nsconfig">Configuration</li>' + 4450 '<li value="nsoccupancy">Occupancy</li>' + 4451 '<li value="nsoccupancy2">Occupancy v2.0 (with UDI Mobile)</li>' + 4452// '<li value="nssolaredge">Solaredge</li>' 4453 '</ul></li>':'') + 4454 (isSameOrParentDomain && this.isUtility && row.isy && row.isy.features && row.isy.features.oadr? 4455 '<li>OpenADR<ul>' + 4456 (isSameDomain?'<li value="openadr">OpenADR Events</li>':'') + 4457 '<li value="openadrReg">OpenADR Registration</li>' + 4458 '</ul></li>':'') + 4459 4460 ((isSameOrParentDomain || this.isSuperAdmin)? 4461 '<li>Information<ul>' + 4462 '<li value="isyinfo">ISY Information</li>' + 4463 (this.isDomainAdmin? 4464 (serverConfig.ipalEnabled?'<li value="ipal">ISY Portal Access License</li>':'') + 4465 '<li value="troubleshoot">ISY Portal Access Troubleshooting</li>':'') + 4466 '<li value="contact">Contact Information</li>' + 4467 '<li value="note">Notes</li></ul></li>':'') + 4468 4469 (((isSameOrParentDomain && this.isDomainAdmin) || this.isSuperAdmin)? 4470 '<li>Logs<ul>' + 4471 '<li value="errorlog">Error Log</li>' + 4472 (isSameDomain?'<li value="eventlog">Event Log</li>':'') + 4473 // (isRates?'<li value="activitylog">
4473Activity Log</li>':'') + 4474 '</ul>':'') + 4475 4476 (this.isDomainAdmin? 4477 '<li>Maintenance<ul>' + 4478/* '<li value="firmware">Firmware Update</li>' +*/ 4479 '<li value="reboot">Reboot</li>' + 4480 '<li value="migrate">Migrate to</li>' + 4481 ((isSameDomain && isPolisyOrEisy) ? '<li value="upgrade_packages">Upgrade Packages</li>' : '') + 4482 ((isEisyUi && this.isSuperAdmin) ? '<li value="eisyui_settings">eisy-ui settings</li>' : '') + 4483 '</ul></li>':'') + 4484 '</div>' 4485 }, 4486 4487 renderIfPresent: function (data) { 4488 return data?data:''; 4489 }, 4490 4491 load: function () { 4492 var table = this; 4493 4494 if (!this.api) { 4495 this.api = $('#isystable').DataTable({ 4496 "language": { "emptyTable": "Press Add ISY to start" }, 4497 "ajax": function (data, callback, settings) { 4498 // For RATES account, we need the facilityId as well, so we call a different API specific to RATES on the RATES server 4499 var url; 4500 // if (table.isRatesAccount) { 4501 // url = '/api/rates/isys' 4502 // } else 4503 // if (table.isSceAccount) { 4504 // url = '/api/sce1/isys' 4505 // } else { 4506 url = '/api/isys'; 4507 // } 4508 ajaxJsonGet(url, $.param({domain: table.currentDomain, subaccounts: (table.showAll?'true':'false')})) 4509 .done(function(data, status, jqXHR){ 4510 table.onDataLoad(data); 4511 callback(data); 4512 }); 4513 }, 4514 "columns": [ 4515 { data: "uuid", render: this.renderUUIDwithDelete, className: "x_uuid", name: "uuid" }, 4516 { data: this.dataOnline, render: this.renderOnlineIndicator, searchable: false, className: "isystatusholder" }, 4517 { data: "domain[0].alias", render: this.renderAlias, name: "alias", defaultContent: '' }, 4518 { data: "isy.firmware", defaultContent: '', name: 'firmware' }, 4519 { render: function (d, t, r) { return table.renderTools(d, t, r, table) }, orderable: false, searchable: false }, 4520 { data: "_id", visible: false, searchable: false, name: 'pk' }, 4521 { data: "domain[0].registered", visible: false, searchable: false, name: 'registered', defaultContent: '0' }, 4522 { data: "domain[0].id", visible: false, searchable: false, name: 'isyDomain', defaultContent: {} }, 4523 { data: "isyKey", visible: false, searchable: false, name: "isyKey", defaultContent: '' }, 4524 { data: "ipal", visible: false, searchable: false, defaultContent: {} }, 4525 { data: "isy.iot", visible: false, searchable: false, defaultContent: {}, name: "iot" }, 4526 { data: "isWs", visible: false, searchable: false, defaultContent: false, name: "isWs" }, 4527 ], 4528 "drawCallback": function (settings, json) { 4529 // API might not be initialized yet, page may display 'Loading...' 4530 if (table.api) { 4531 table.completeUI(); 4532 } 4533 } 4534 }); 4535 } 4536 }, 4537 4538 reload: function () { 4539 var table = this; 4540 4541 if (!table.api) { 4542 table.load(); 4543 } 4544 else { 4545 table.api.page('first'); 4546 table.api.ajax.reload(null, false); 4547 } 4548 }, 4549 4550 setDomain: function (domain, showAll) { 4551 // this.isRatesAccount = isRatesAccount(domain); 4552 // this.isSceAccount = isSceAccount(domain); 4553 4554 // The account SCE enabled flag is enabled only for SCE account, except if we are in prod && admin account 4555 // this.isSceAccountFlagEnabled = this.isSceAccount && !(location.hostname === 'my.isy.io' && domain === '$superadmin'); 4556 4557 if (this.currentDomain!=domain || this.showAll!=showAll) { 4558 this.currentDomain = domain; 4559 this.showAll = showAll; 4560 this.reload(); 4561 } 4562 4563 // if (this.isSceAccount) { 4564 // $('#scereports').show() 4565 // } else { 4566 // $('#scereports').hide() 4567 // } 4568 }, 4569 4570 clear: function () { 4571 this.currentDomain = ''; 4572 $('#isys_table_container').hide(); 4573 } 4574}; 4575 4576var tableUsers = { 4577 api: null, 4578 currentDomain: null, 4579 showAll: null, // Show sub-accounts as well? 4580
4581 completeUI : function() { 4582 var table = this; 4583 table.api.columns.adjust(); 4584 4585 $('#users_table_container').show(); 4586 $('#users_table_container').tooltip(); 4587 4588 switch (this.currentDomain) { 4589 case '$superadmin' : 4590 this.api.column("domainadmin:name").visible(false); 4591 this.api.column("passwordchange:name").visible(true); 4592 this.api.column("uuid:name").visible(false); 4593 break; 4594 4595 default : 4596 this.api.column("domainadmin:name").visible(true); 4597 this.api.column("passwordchange:name").visible(true); 4598 this.api.column("uuid:name").visible(false); 4599 break; 4600 } 4601 4602 // Delete existing user 4603 $('#userstable .delbutton').unbind('click').click(function () { 4604 var thisbutton = this; 4605 var pk = table.api.cell($(this).closest('tr'), 'username:name').data(); 4606 4607 // We are removing ourself 4608 if (pk === registeredUser.username) { 4609 confirmationDialog('YOU ARE REMOVING YOUR OWN USER PROFILE. ARE YOU SURE?', 'Remove YOURSELF?') 4610 .then(function () { 4611 ajaxJsonDelete('/api/user', $.param({username: pk, domain: table.currentDomain})) 4612 .done(function () { 4613 table.reload(); 4614 }) 4615 }) 4616 .done(); 4617 } 4618 else { 4619 confirmationDialog('User will be removed.<br>Are you sure?', 'Remove user?') 4620 .then(function () { 4621 ajaxJsonDelete('/api/user', $.param({username: pk, domain: table.currentDomain})) 4622 .done(function () { 4623 table.reload(); 4624 }) 4625 }) 4626 .done(); 4627 } 4628 }); 4629 4630 // Edit existing user 4631 $('#userstable .username').click(function () { 4632 var username = table.api.cell($(this).closest('tr'), 'username:name').data(); 4633 var pk = username; 4634 var password = '$unchanged'; 4635 var domainadmin = table.api.cell($(this).closest('tr'), 'domainadmin:name').data(); 4636 var userDomain = table.api.cell($(this).closest('tr'), 'domain:name').data(); 4637 var passwordchange = table.api.cell($(this).closest('tr'), 'passwordchange:name').data(); 4638 var isDeveloper = table.api.cell($(this).closest('tr'), 'isDeveloper:name').data(); 4639 var isReviewer = table.api.cell($(this).closest('tr'), 'isReviewer:name').data(); 4640 var isEEAdmin = table.api.cell($(this).closest('tr'), 'isEEAdmin:name').data(); 4641 var notificationmaint = table.api.cell($(this).closest('tr'), 'notificationmaint:name').data(); 4642 var notificationnews = table.api.cell($(this).closest('tr'), 'notificationnews:name').data(); 4643 var isy = table.api.cell($(this).closest('tr'), 'isy:name').data(); 4644 4645 var data = { pk: pk, username: username, password:password, domainadmin:domainadmin, passwordchange:passwordchange, isDeveloper: isDeveloper, isReviewer: isReviewer, isEEAdmin: isEEAdmin, notificationmaint:notificationmaint, notificationnews:notificationnews, isy:isy }; 4646 4647 userDialog.setFields(data); 4648 userDialog.setDomain(userDomain); 4649 userDialog.modal.dialog('open'); 4650 }); 4651 }, 4652 4653 renderUserName: function (data, type, row, meta) { 4654 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span>' + 4655 '<span class="username editable_link">' + data + '</span>' + 4656 (row.emailVerified?'':' <span class="warning" title="email is not verified"><span class="ui-icon ui-icon-alert"></span></span>'); 4657 }, 4658 4659 renderDomainAdmin: function (data, type, row, meta) { 4660 return (row.domainadmin)?'Yes':''; 4661 }, 4662 4663 renderPasswordChange: function (data, type, row, meta) { 4664 return (row.passwordchange)?'Yes':''; 4665 }, 4666 4667 renderIfPresent: function (data) { 4668 return data?data:''; 4669 }, 4670 4671 load: function () { 4672 var table = this; 4673 4674 if (!this.api) { 4675 this.api = $('#userstable').DataTable({ 4676 "ajax": function (data, callback, settings) { 4677 ajaxJsonGet('/api/users', $.param({domain: table.currentDomain, subaccounts: (table.showAll?'true':'false') })) 4678 .done(function(data, status, jqXHR){ 4679 callback(data); 4680 }); 4681 }, 4682 "columns": [ 4683 { data: "username", render: this.renderUserName, name: "username" }, 4684 { data: "domainadmin", defaultContent: false, render: this.renderDomainAdmin, name: "domainadmin" }, 4685 { data: "passwordchange", defaultContent: false, render: this.renderPasswordChange, name: "passwordchange" }, 4686 { data: "isy", defaultContent:"", visible: true, name: "isy" }, 4687 { data: "emailVerified", defaultContent: false, visible:false, name: "emailVerified" }, 4688 { data: "domain", defaultContent:"", visible: false, name: "domain" }, 4689 { data: "isDeveloper", defaultContent: false, visible:false, name: "isDeveloper" }, 4690 { data: "isReviewer", defaultContent: false, visible:false, name: "isReviewer" }, 4691 { data: "isEEAdmin", defaultContent: false, visible:false, name: "isEEAdmin" }, 4692 { data: "notificationmaint", defaultContent: true, visible:false, name: "notificationmaint" }, 4693 { data: "notificationnews", defaultContent: true, visible:false, name: "notificationnews" } 4694 ], 4695 "drawCallback": function (settings, json) { 4696 // API might not be initialized yet, page may display 'Loading...' 4697 if (table.api) {
4698 table.completeUI(); 4699 } 4700 } 4701 }); 4702 } 4703 }, 4704 4705 reload: function () { 4706 var table = this; 4707 4708 if (!table.api) { 4709 table.load(); 4710 } 4711 else { 4712 this.api.page('first'); 4713 table.api.ajax.reload(null, false); 4714 } 4715 }, 4716 4717 setDomain: function (domain, showAll) { 4718 if (this.currentDomain!==domain || this.showAll!==showAll) { 4719 this.currentDomain = domain; 4720 this.showAll = showAll; 4721 this.reload(); 4722 } 4723 }, 4724 4725 clear: function () { 4726 this.currentDomain = ''; 4727 $('#users_table_container').hide(); 4728 } 4729}; 4730 4731var tableDomains = { 4732 api: null, 4733 currentDomain: null, // Contains the registeredUser's domain info (registeredUser.domainInfo) 4734 isUDI: false, 4735 4736 // Triggered when json data has loaded (before table draw). We do this to update the selectors if domains have changed. 4737 onDataLoad: function (data) { 4738 updateDomainSelectors(this.currentDomain) 4739 }, 4740 4741 completeUI : function() { 4742 var table = this; 4743 table.api.columns.adjust(); 4744 4745 // Delete existing domain 4746 $('#domainstable .delbutton').unbind('click').click(function () { 4747 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 4748 4749 confirmationDialog('Account will be removed.<br>Are you sure?', 'Remove account?') 4750 .then(function () { 4751 ajaxJsonDelete('/api/domain', '_id='+pk) 4752 .done(function () { 4753 table.reload(); 4754 }) 4755 .fail(function(jqXHR, textStatus, errorThrown) { 4756 if (jqXHR.status==425) { // Validation error message 4757 errorMessageDialog(jqXHR.responseText, 'Error', 350); 4758 } 4759 else { 4760 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 4761 } 4762 }) 4763 .done(); 4764 }) 4765 .done(); 4766 }); 4767 4768 // Edit existing domain 4769 $('#domainstable .shortname').click(function () { 4770 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 4771 var shortname = table.api.cell($(this).closest('tr'), 'shortname:name').data(); 4772 var description = table.api.cell($(this).closest('tr'), 'desc:name').data(); 4773 var type = table.api.cell($(this).closest('tr'), 'type:name').data(); // Raw type: '0' or '1' 4774 var website = table.api.cell($(this).closest('tr'), 'website:name').data(); 4775 var address = table.api.cell($(this).closest('tr'), 'address:name').data(); 4776 var contactname = table.api.cell($(this).closest('tr'), 'contactname:name').data(); 4777 var contactphone = table.api.cell($(this).closest('tr'), 'contactphone:name').data();
4778// var contactemail = table.api.cell($(this).closest('tr'), 'contactemail:name').data(); 4779 var parent = table.api.cell($(this).closest('tr'), 'parent:name').data(); 4780 var parentname = table.api.cell($(this).closest('tr'), 'parentname:name').data(); 4781 var billingroot = table.api.cell($(this).closest('tr'), 'billingroot:name').data(); 4782 var billingsub = table.api.cell($(this).closest('tr'), 'billingsub:name').data(); 4783 var consent = table.api.cell($(this).closest('tr'), 'consent:name').data(); 4784 4785 //parentList 4786 //parent 4787 domainDialog.setFields({ editMode: true, isUDI: table.isUDI, pk: pk, sub: (parent!=''), 4788 parent: parent, parentname: parentname, shortname: shortname, description:description, type: type, address: address, website:website, 4789 contactname: contactname, contactphone: contactphone, billingroot: billingroot, billingsub: billingsub, consent: consent/*, contactemail: contactemail*/ }); 4790 domainDialog.modal.dialog('open'); 4791 }); 4792 }, 4793 4794 dataDomainType: function (row, type, set, meta) { 4795 if (row.parent && row.parent!=='') { 4796 return 'Sub-Account'; 4797 } 4798 else { 4799 return row.type=="0"?"Utility":row.type=="1"?"Corporate":"Other"; 4800 } 4801 }, 4802 4803 renderDomainName: function (data, type, row, meta) { 4804 var domainDisplayed; 4805 4806 // 'this' is set to the global namespace ??? Need to refer to tableDomains... 4807 if (tableDomains.isUDI) { 4808 domainDisplayed = (row.parentname?row.parentname+'/':'') + data; 4809 } 4810 else { 4811 // This is the main account 4812 if (!row.parent || row.parent.length===0) { 4813 domainDisplayed = '[Main account]'; 4814 } 4815 else { 4816 domainDisplayed = data; 4817 } 4818 } 4819 4820 // If UDI admin, Delete button is deplayed on all accounts/subaccount 4821 // If not UDI admin, Delete button is deplayed on sub accounts only 4822 return ((tableDomains.isUDI||(row.parent && row.parent.length>0))?'<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span>' : '<span class="nodelbutton"></span>') + 4823 '<span class="shortname editable_link">' + domainDisplayed + '</span>'; 4824 }, 4825 load: function () { 4826 var table = this; 4827 4828 if (!this.api) { 4829 this.api = $('#domainstable').DataTable({ 4830 "ajax": function (data, callback, settings) { 4831 ajaxJsonGet('/api/domains') 4832 .done(function(data, status, jqXHR){ 4833 table.onDataLoad(data); 4834 callback(data); 4835 }); 4836 }, 4837 "columns": [ 4838 { data: "shortname", render: this.renderDomainName, name: 'shortname' }, 4839 { data: "description", name: 'desc', defaultContent: "" }, 4840 { data: this.dataDomainType, defaultContent: "" }, 4841 { data: "_id", visible: false, name: 'pk' }, 4842 { data: "type", visible: false, name: 'type', defaultContent: "" }, 4843 { data: "website", visible: false, name: 'website', defaultContent: "" }, 4844 { data: "address", visible: false, name: 'address', defaultContent: "" }, 4845 { data: "contactname", visible: false, name: 'contactname', defaultContent: "" }, 4846 { data: "contactphone", visible: false, name: 'contactphone', defaultContent: "" }, 4847// { data: "contactemail", visible: false, name: 'contactemail', defaultContent: "" }, 4848 { data: "parent", visible: false, name: 'parent', defaultContent: "" }, 4849 { data: "parentname", visible: false, name: 'parentname', defaultContent: "" }, 4850 { data: "billingroot", visible: false, name: 'billingroot', defaultContent: true }, 4851 { data: "billingsub", visible: false, name: 'billingsub', defaultContent: false }, 4852 { data: "consent", visible: false, name: 'consent', defaultContent: false }, 4853 ], 4854 "drawCallback": function (settings, json) { 4855 // API might not be initialized yet, page may display 'Loading...' 4856 if (table.api) { 4857 table.completeUI(); 4858 } 4859 } 4860 }); 4861 } 4862 }, 4863 4864 reload: function (forceReload) { 4865 var table = this; 4866 4867 if (!table.api) { 4868 table.load(); 4869 } 4870 else { 4871 table.api.ajax.reload(null, false); 4872 } 4873 }, 4874 4875 setDomain: function (domainInfo, forceReload) { 4876 if (!this.currentDomain || this.currentDomain._id !== domainInfo._id) { 4877 this.currentDomain = domainInfo; 4878 4879 this.isUDI = (this.currentDomain._id === '$superadmin'); 4880 4881 this.reload(); 4882 4883 // Type column is only visible if UDI admin 4884 this.api.column(2).visible(this.isUDI); 4885 } 4886 else if (forceReload) { 4887 this.reload(forceReload); 4888 } 4889 }, 4890}; 4891 4892var tableConnectedIsys = { 4893 api: null, 4894 4895 // Hidden data used for sorting the online indicator 4896 dataOnline: function (row, type, set, meta) { 4897 return (typeof row.online !== 'undefined')?(row.online?1:0):-1; 4898 }, 4899 4900 renderOnlineIndicator: function (data, type, row) { 4901 return '<div class="isystatus ' + (row.online?'online':'offline') + '" title="' + (row.lastModified?(row.online?'Online since ':'Offline since ') + dateFormatNoError(row.lastModified, "dddd, mmmm dS, yyyy, h:MM:ss TT"):'ISY never connected') + '"><span style="display: none;">' + data + '</span></div>'; 4902 }, 4903 4904 renderIfOnline: function (data, type, row, meta) { 4905 return (row.online && data)?data:''; 4906 }, 4907 4908 // Data will contain the first domain, a separator, then then all the list 4909 dataDomain: function (row, type, set, meta) { 4910 var str=''; 4911 4912 if (row.domain && row.domain.length>=1) { 4913 str = row.domain[0].shortname; 4914 4915 if (row.domain.length>1) { 4916 str += '|' + row.domain[0].shortname; 4917 4918 for (var i = 1; i < row.domain.length; i++) { 4919 str += ', ' + row.domain[i].shortname; 4920 } 4921 } 4922 } 4923 4924 return str; 4925 }, 4926 4927 renderDomain: function (data, type, row, meta) { 4928 var renderStr=''; 4929 var dd=data.split('|'); 4930 4931 if (dd.length===1) { 4932 renderStr = dd[0]; 4933 } 4934 else if (dd.length===2) { 4935 renderStr = dd[0] + ', <span title="' + dd[1] + '">[More...]</span>' 4936 } 4937 4938 return renderStr; 4939 }, 4940
4941 completeUI: function () { 4942 $('#isysconnectedtable').tooltip(); 4943 }, 4944 4945 load: function () { 4946 var table = this; 4947 if (!this.api) { 4948 this.api = $('#isysconnectedtable').DataTable({ 4949 "ajax": function (data, callback, settings) { 4950 ajaxJsonGet('/api/isys/all') 4951 .done(function(data, status, jqXHR){ 4952 callback(data); 4953 }); 4954 }, 4955 "columns": [ 4956 { data: "uuid", defaultContent: "" }, 4957 { data: this.dataOnline, render: this.renderOnlineIndicator, searchable: false, className: "isystatusholder" }, 4958 { data: "ip", render: this.renderIfOnline }, 4959 // { data: "cipher", render: this.renderIfOnline }, 4960 { data: "isy.firmware", defaultContent: "" }, 4961 { data: this.dataDomain, render: this.renderDomain, searchable:false }, 4962 { data: this.dataDomain, visible:false } 4963 ], 4964 "drawCallback": function (settings, json) { 4965 // API might not be initialized yet, page may display 'Loading...' 4966 if (table.api) { 4967 table.completeUI(); 4968 } 4969 } 4970 }); 4971 } 4972 }, 4973 4974 reload: function () { 4975 var table = this; 4976 4977 if (!table.api) { 4978 table.load(); 4979 } 4980 else { 4981 table.api.ajax.reload(null, false); 4982 } 4983 } 4984}; 4985 4986// Admin / ISY Portal Access License Renewal Options table 4987var tableIpalRenewalOptions = { 4988 api: null, 4989
4990 completeUI : function() { 4991 var table = this; 4992 table.api.columns.adjust(); 4993 4994 // Delete existing renewal options 4995 $('#ipal_renewals_table .delbutton').unbind('click').click(function () { 4996 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 4997 4998 confirmationDialog('Renewal option will be removed.<br>Are you sure?', 'Remove ISY Portal Renewal Option?') 4999 .then(function () { 5000 ajaxJsonDelete('/api/ipal/renewal/option', 'id='+pk) 5001 .done(function () { 5002 table.reload(); 5003 }) 5004 .fail(function(jqXHR, textStatus, errorThrown) { 5005 if (jqXHR.status==425) { // Validation error message 5006 errorMessageDialog(jqXHR.responseText, 'Error', 350); 5007 } 5008 else { 5009 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 5010 } 5011 }) 5012 .done(); 5013 }) 5014 .done(); 5015 }); 5016 5017 // Edit existing option 5018 $('#ipal_renewals_table .description').click(function () { 5019 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5020 var part = table.api.cell($(this).closest('tr'), 'part:name').data(); 5021 var isRenewal = table.api.cell($(this).closest('tr'), 'isrenewal:name').data(); 5022 var isLegacy = table.api.cell($(this).closest('tr'), 'islegacy:name').data(); 5023 var description = table.api.cell($(this).closest('tr'), 'description:name').data(); 5024 var legacyText = table.api.cell($(this).closest('tr'), 'legacytext:name').data(); 5025 var duration = table.api.cell($(this).closest('tr'), 'duration:name').data(); 5026 var price = table.api.cell($(this).closest('tr'), 'price:name').data(); 5027 5028 ipalRenewalOptionEditDialog.setFields({ editMode: true, pk:pk, isRenewal: isRenewal, isLegacy: isLegacy, part:part, description:description, legacyText: legacyText, duration:duration, price:price }); 5029 ipalRenewalOptionEditDialog.modal.dialog('open'); 5030 }); 5031 }, 5032 5033 renderPart: function (data, type, row, meta) { 5034 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span>' + 5035 '<span>' + data + '</span>'; 5036 }, 5037 5038 renderDescription: function (data, type, row, meta) { 5039 return '<span class="description editable_link">' + data + '</span>'; 5040 }, 5041 5042 renderRenewal: function (data, type, row, meta) { 5043 return data?'Renewal':'Initial'; 5044 }, 5045 5046 renderLegacy: function (data, type, row, meta) { 5047 return data?'Yes':''; 5048 }, 5049 5050 renderYears: function (data, type, row, meta) { 5051 return data?data + ' Year' + (data>1?'s':''):'Perpetual'; 5052 }, 5053
5054 renderPrice: function (data, type, row, meta) { 5055 return data.toFixed(2); 5056 }, 5057/* 5058 renderDiscount: function (data, type, row, meta) { 5059 return data + '%'; 5060 }, 5061*/ 5062 load: function () { 5063 var table = this; 5064 5065 if (!this.api) { 5066 this.api = $('#ipal_renewals_table').DataTable({ 5067 'ajax': function (data, callback, settings) { 5068 ajaxJsonGet('/api/ipal/renewal/options') 5069 .done(function(data, status, jqXHR){ 5070 callback(data); 5071 }); 5072 }, 5073 'columns': [ 5074 { data: 'part', render: this.renderPart, defaultContent:'', name: 'part' }, 5075 { data: 'isrenewal', render: this.renderRenewal, defaultContent:'', name: 'isrenewal' }, 5076 { data: 'islegacy', render: this.renderLegacy, defaultContent:'', name: 'islegacy' }, 5077 { data: 'description', render: this.renderDescription, name: 'description' }, 5078 { data: 'duration', render: this.renderYears, name: 'duration' }, 5079 { data: 'price', render: this.renderPrice, name: 'price' }, 5080 { data: 'legacytext', visible: false, defaultContent:'', name: 'legacytext' }, 5081 { data: '_id', visible: false, name: 'pk' } 5082 ], 5083 'order': [[ 1, 'asc' ], [ 3, 'asc' ]], 5084 'drawCallback': function (settings, json) { 5085 // API might not be initialized yet, page may display 'Loading...' 5086 if (table.api) { 5087 table.completeUI(); 5088 } 5089 } 5090 }); 5091 } 5092 }, 5093 5094 reload: function (forceReload) { 5095 var table = this; 5096 5097 if (!table.api) { 5098 table.load(); 5099 } 5100 else { 5101 table.api.ajax.reload(null, false); 5102 } 5103 } 5104}; 5105 5106// Admin / ISY Portal Access License Renewal Options table 5107var tableIpalNotificationOptions = { 5108 api: null, 5109 types: [ 'Trial', 'License' ], 5110
5111 completeUI : function() { 5112 var table = this; 5113 table.api.columns.adjust(); 5114 5115 // Delete existing renewal options 5116 $('#ipal_notifications_table .delbutton').unbind('click').click(function () { 5117 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5118 5119 confirmationDialog('Notification option will be removed.<br>Are you sure?', 'Remove ISY Portal Notification Option?') 5120 .then(function () { 5121 ajaxJsonDelete('/api/ipal/notification/option', 'id='+pk) 5122 .done(function () { 5123 table.reload(); 5124 }) 5125 .fail(function(jqXHR, textStatus, errorThrown) { 5126 if (jqXHR.status==425) { // Validation error message 5127 errorMessageDialog(jqXHR.responseText, 'Error', 350); 5128 } 5129 else { 5130 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 5131 } 5132 }) 5133 .done(); 5134 }) 5135 .done(); 5136 }); 5137 5138 // Edit existing option 5139 $('#ipal_notifications_table .description').click(function () { 5140 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5141 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 5142 var days = table.api.cell($(this).closest('tr'), 'days:name').data(); 5143 var skip = table.api.cell($(this).closest('tr'), 'skip:name').data(); 5144 var notification = table.api.cell($(this).closest('tr'), 'notification:name').data(); 5145 var pushContent = table.api.cell($(this).closest('tr'), 'pushContent:name').data(); 5146 var emailTitle = table.api.cell($(this).closest('tr'), 'emailTitle:name').data(); 5147 var emailBody = table.api.cell($(this).closest('tr'), 'emailBody:name').data(); 5148 var type = table.api.cell($(this).closest('tr'), 'type:name').data(); 5149 5150 ipalNotificationOptionEditDialog.setFields({ editMode: true, pk:pk, name:name, days:days, skip:skip, notification:notification, pushContent:pushContent, emailTitle:emailTitle, emailBody:emailBody, type:type }); 5151 5152 ipalNotificationOptionEditDialog.modal.dialog('open'); 5153 }); 5154 }, 5155 5156 renderName: function (data, type, row, meta) { 5157 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span>' + 5158 '<span class="description editable_link">' + data + '</span>'; 5159 }, 5160 5161 renderDays: function (data, type, row, meta) { 5162 return data?data<0?(data*-1)+' days before expiry':data+' days after expiry':'On expiry'; 5163 }, 5164 5165 renderType: function (data, type, row, meta) { 5166 return tableIpalNotificationOptions.types[data]; 5167 }, 5168 5169 load: function () { 5170 var table = this; 5171 5172 if (!this.api) { 5173 this.api = $('#ipal_notifications_table').DataTable({ 5174 'ajax': function (data, callback, settings) { 5175 ajaxJsonGet('/api/ipal/notification/options') 5176 .done(function(data, status, jqXHR){ 5177 callback(data); 5178 }); 5179 }, 5180 'columns': [ 5181 { data: 'name', render: this.renderName, name: 'name' }, 5182 { data: 'type', render: this.renderType, defaultContent:'', name: 'type', orderData: [ 5 ] }, 5183 { data: 'days', render: this.renderDays, name: 'days', orderData: [ 6 ] }, 5184 { data: 'notification', visible: false, defaultContent:'', name: 'notification' }, 5185 { data: 'pushContent', visible: false, defaultContent:'', name: 'pushContent' }, 5186 { data: 'emailTitle', visible: false, defaultContent:'', name: 'emailTitle' }, 5187 { data: 'emailBody', visible: false, defaultContent:'', name: 'emailBody' }, 5188 { data: 'type', visible: false }, // For ordering 5189 { data: 'days', visible: false }, // For ordering 5190 { data: 'skip', visible: false, defaultContent: false, name: 'skip' }, 5191 { data: '_id', visible: false, name: 'pk' } 5192 ], 5193 'order': [[ 7, 'asc' ],[ 8, 'asc' ]], // Trial first, then days 5194 'drawCallback': function (settings, json) { 5195 // API might not be initialized yet, page may display 'Loading...' 5196 if (table.api) { 5197 table.completeUI(); 5198 } 5199 } 5200 }); 5201 } 5202 }, 5203 5204 reload: function (forceReload) { 5205 var table = this; 5206 5207 if (!table.api) { 5208 table.load(); 5209 } 5210 else { 5211 table.api.ajax.reload(null, false); 5212 } 5213 } 5214}; 5215 5216 5217// Admin / Rate Plans Options table 5218// this is used for the main Admin tab only (scope=all providers) 5219var tableRatePlanOptions = { 5220 api: null, 5221 5222 refresh: function () { 5223 tableRatePlanOptions.reload(); 5224 }, 5225
5226 completeUI : function() { 5227 var table = this; 5228 table.api.columns.adjust(); 5229 5230 // Delete existing renewal options 5231 $('#rateplans_table .delbutton').unbind('click').click(function () { 5232 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5233 5234 confirmationDialog('Rate plan option will be removed.<br>Are you sure?', 'Remove Rate Plan?') 5235 .then(function () { 5236 ajaxJsonDelete('/api/rateplan', 'id='+pk) 5237 .done(function () { 5238 table.reload(); 5239 }) 5240 .fail(function(jqXHR, textStatus, errorThrown) { 5241 if (jqXHR.status==425) { // Validation error message 5242 errorMessageDialog(jqXHR.responseText, 'Error', 350); 5243 } 5244 else { 5245 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 5246 } 5247 }) 5248 .done(); 5249 }) 5250 .done(); 5251 }); 5252 5253 // Edit existing option 5254 $('#rateplans_table .rateplan').click(function () { 5255 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5256 var provider = table.api.cell($(this).closest('tr'), 'provider:name').data(); 5257 var ratePlan = table.api.cell($(this).closest('tr'), 'ratePlan:name').data(); 5258 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 5259 var altCodes = table.api.cell($(this).closest('tr'), 'altCodes:name').data(); 5260 var gridx = table.api.cell($(this).closest('tr'), 'gridx:name').data(); 5261 var active = table.api.cell($(this).closest('tr'), 'active:name').data(); 5262 var isTiered = table.api.cell($(this).closest('tr'), 'isTiered:name').data(); 5263 var sort = table.api.cell($(this).closest('tr'), 'sort:name').data(); 5264 var description = table.api.cell($(this).closest('tr'), 'description:name').data(); 5265 var details = table.api.cell($(this).closest('tr'), 'details:name').data(); 5266 var programs = table.api.cell($(this).closest('tr'), 'programs:name').data(); 5267 5268 ratePlanEditDialog.setFields({ editMode: true, pk:pk, provider:provider, 5269 ratePlan:ratePlan, name:name, altCodes: altCodes, gridx: gridx, 5270 active:active, isTiered:isTiered, sort:sort, description:description, 5271 details:details, programs: programs, 5272 refreshFunction: table.refresh 5273 }); 5274 ratePlanEditDialog.modal.dialog('open'); 5275 }); 5276 }, 5277 5278 renderProvider: function (data, type, row, meta) { 5279 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span>' + data; 5280 }, 5281 5282 renderRatePlan: function (data, type, row, meta) { 5283 return '<span class="rateplan editable_link">' + (data && data.length? data : '(Not found error msg)') + '</span>'; 5284 }, 5285 5286 renderName: function (data, type, row, meta) { 5287 return '<span class="rateplan editable_link">' + data + '</span>'; 5288 }, 5289 5290 renderActive: function (data, type, row, meta) { 5291 return data ? 'Yes' : ''; 5292 }, 5293 5294 load: function () { 5295 var table = this; 5296 5297 if (!this.api) { 5298 this.api = $('#rateplans_table').DataTable({ 5299 'ajax': function (data, callback, settings) { 5300 ajaxJsonGet('/api/rateplans') 5301 .done(function(data, status, jqXHR){ 5302 callback(data); 5303 }); 5304 }, 5305 'columns': [ 5306 { data: 'provider', render: this.renderProvider, name: 'provider' }, 5307 { data: 'ratePlan', render: this.renderRatePlan, name: 'ratePlan' }, 5308 { data: 'name', name: 'name' }, 5309 { data: 'active', render: this.renderActive, defaultContent:'', name: 'active' }, // , orderData: [ 5 ] 5310 { data: 'sort', defaultContent: '', name: 'sort' }, // , orderData: [ 6 ] 5311 { data: 'altCodes', defaultContent:'', visible: false, name: 'altCodes' }, 5312 { data: 'gridx', defaultContent:'', visible: false, name: 'gridx' }, 5313 { data: 'description', defaultContent:'', visible: false, name: 'description' }, 5314 { data: 'isTiered', defaultContent:false, visible:false, name: 'isTiered' }, 5315 { data: 'details', defaultContent:'', visible: false, name: 'details' }, 5316 { data: 'programs', defaultContent:{}, visible: false, name: 'programs' }, 5317 // { data: 'type', visible: false }, // For ordering 5318 // { data: 'days', visible: false }, // For ordering 5319 { data: '_id', visible: false, name: 'pk' } 5320 ], 5321 'order': [[ 0, 'asc' ],[ 3, 'desc' ],[ 4, 'asc' ],[ 1, 'asc' ]], // Provider, Active yes/no, sort order... 5322 'drawCallback': function (settings, json) { 5323 // API might not be initialized yet, page may display 'Loading...' 5324 if (table.api) {
5325 table.completeUI(); 5326 } 5327 } 5328 }); 5329 } 5330 }, 5331 5332 reload: function (forceReload) { 5333 var table = this; 5334 5335 if (!table.api) { 5336 table.load(); 5337 } 5338 else { 5339 table.api.ajax.reload(null, false); 5340 } 5341 } 5342}; 5343 5344 5345 5346var tableEchoSpokenNodes = { 5347 data: null, 5348 api: null, 5349 currentUuid: null, 5350 currentDomain: null, 5351 lookupDataPromise: null, 5352 lookupDataPromisePgm: null, 5353 lookupDataPromiseStateVar: null, 5354 lookupDataPromiseNetworkRsc: null, 5355 lookupDataPromiseElk: null, 5356 roomsPromise:null, 5357 userCats: { 5358 'scene': 'Scene', 5359 'light': 'Device\\Light', 5360 'switch': 'Device\\Switch', 5361 'outlet': 'Device\\Outlet', 5362 'fan': 'Device\\Fan', 5363 'openClose': 'Device\\Open-Close', 5364 'lowerRaise': 'Device\\Set-Lower-Raise', 5365 'motion': 'Device\\Motion Sensor', 5366 'contact': 'Device\\Contact Sensor', 5367 'lock': 'Device\\Lock', 5368 'thermostat': 'Device\\Thermostat', 5369 }, 5370 5371 // Triggered when json data has loaded (before table draw) 5372 onDataLoad: function (spokens) { 5373 var table = this; 5374 5375 table.data = spokens; // Keep a reference to the original data to facilitate export. 5376 5377 table.lookupDataPromise 5378 .then(function(lookups) { 5379 lookups.data.devices.forEach(function (item) { 5380 item.class=''; 5381 5382 spokens.data.forEach(function (spoken) { 5383 if (spoken.category === item.category && spoken.address === item.address) { 5384 item.class='dup'; 5385 } 5386 }) 5387 }); 5388 5389 lookups.data.scenes.forEach(function (item) { 5390 item.class=''; 5391 5392 spokens.data.forEach(function (spoken) { 5393 if (spoken.category === 'scene' && spoken.address === item.address) { 5394 item.class='dup'; 5395 } 5396 }) 5397 }) 5398 }); 5399 5400 table.lookupDataPromisePgm 5401 .then(function(lookups) { 5402 lookups.data.forEach(function (item) { 5403 item.class=''; 5404 5405 spokens.data.forEach(function (spoken) { 5406 if (spoken.category === 'program' && spoken.address === item.address) { 5407 item.class='dup'; 5408 } 5409 }) 5410 }) 5411 }); 5412
5413 table.lookupDataPromiseElk 5414 .then(function(lookups) { 5415 lookups.data.zones.forEach(function (item) { 5416 item.class=''; 5417 5418 spokens.data.forEach(function (spoken) { 5419 if (spoken.category === 'elk' && spoken.address === item.id) { 5420 item.class='dup'; 5421 } 5422 }) 5423 }) 5424 }); 5425 5426 // We want to delay screen draw till after we have processed rooms 5427 return table.roomsPromise 5428 .then(function(rooms) { 5429 spokens.data.forEach(function (spoken) { 5430 rooms.data.forEach(function (room) { 5431 if (spoken.room === room._id) { 5432 spoken.roomName=room.name; 5433 } 5434 }) 5435 }) 5436 }) 5437 // NOTE: For variables, we allow duplicates so that we can have several spokens for the same variable with different turn on/turn off values 5438 }, 5439 5440 completeUI: function () { 5441 var table = this; 5442 table.api.columns.adjust(); 5443 5444 $('#echo_spokennodestable').tooltip(); 5445 5446 // Delete spoken 5447 $('#echo_spokennodestable .delbutton').unbind('click').click(function (e) { 5448 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5449 5450 confirmationDialog('Are you sure?', 'Remove spoken?') 5451 .then(function () { 5452 ajaxJsonDelete('/api/voice/amazon/spoken/node', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 5453 .fail(function(jqXHR, textStatus, errorThrown) { 5454 if (jqXHR.status===412) { 5455 if (skillNotLinkedWarningDelete) { 5456 var skipButton = 'Skip Warning' 5457 var okButton = 'Ok' 5458 var buttons = [ skipButton, okButton ] 5459 // Delete successful, but skill is not linked 5460 optionsDialog('The Alexa skill is not linked.<br/><br/>To remove it from Alexa, please use the Alexa app and click on Devices.', 'Skill not linked', buttons) 5461 .then(function (button) { 5462 if (button === skipButton) { 5463 // On next delete, there will be no warning 5464 skillNotLinkedWarningDelete = false 5465 } 5466 }) 5467 .always(function () { 5468 tableEchoSpokenNodes.reload(); 5469 }) 5470 .done(); 5471 } else { 5472 tableEchoSpokenNodes.reload(); 5473 } 5474 } 5475 else if (jqXHR.status===428) { 5476 if (incorrectUnitWarningDelete) { 5477 var skipButton = 'Skip Warning' 5478 var okButton = 'Ok' 5479 var buttons = [ skipButton, okButton ] 5480 // Save successful, but skill is not linked 5481 optionsDialog('This user profile ' + registeredUser.username + ' cannot manage this ISY.<br/><br/>' + 5482 'If Alexa is linked with another user profile in this account, then you may have to remove this device manually from the Alexa app.<br/><br/>' + 5483 'If on the other hand Alexa is linked with this user profile and you want it to control this ISY, then click on My Profile to change the preferred ISY to this ISY.<br/><br/>', 'Updating incorrect ISY', buttons, 800) 5484 .then(function (button) { 5485 if (button === skipButton) { 5486 // On next edit, there will be no warning 5487 incorrectUnitWarningDelete = false 5488 } 5489 }) 5490 .always(function () { 5491 tableEchoSpokenNodes.reload(); 5492 }) 5493 .done(); 5494 } else { 5495 tableEchoSpokenNodes.reload(); 5496 } 5497 } 5498 else { 5499 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 5500 .always(function () { 5501 tableEchoSpokenNodes.reload(); 5502 }) 5503 .done(); 5504 } 5505 }) 5506 .done(function () { 5507 tableEchoSpokenNodes.reload(); 5508 }) 5509 }) 5510 .done(); 5511 }); 5512 5513// $('#echo_spokennodestable .spoken').unbind('click').click(function (e) { (???) 5514 // Edit existing spoken 5515 $('#echo_spokennodestable .spoken').click(function () { 5516 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5517 var address = table.api.cell($(this).closest('tr'), 'address:name').data(); 5518 var spokens = table.api.cell($(this).closest('tr'), 'spokens:name').data(); 5519 var category = table.api.cell($(this).closest('tr'), 'category:name').data(); 5520 var room = table.api.cell($(this).closest('tr'), 'room:name').data(); 5521 var colorMfr = table.api.cell($(this).closest('tr'), 'colorMfr:name').data(); 5522 var userCat = table.api.cell($(this).closest('tr'), 'userCat:name').data(); 5523 var avData = table.api.cell($(this).closest('tr'), 'av:name').data(); 5524 var spoken_link = this; 5525 5526 table.roomsPromise 5527 .done(function(rooms) { 5528 if (category==='statevar') { 5529 var turnon_value = table.api.cell($(spoken_link).closest('tr'), 'turnon_value:name').data(); 5530 var turnoff_value = table.api.cell($(spoken_link).closest('tr'), 'turnoff_value:name').data(); 5531 var motion_value = table.api.cell($(spoken_link).closest('tr'), 'motion_value:name').data(); // Can be motion or contact 5532 5533 table.lookupDataPromiseStateVar 5534 .then(function(content) { 5535 if (!content) { 5536 throw new Error('Variables not available'); 5537 } 5538 echoSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data, address: address?address:'', spokens: spokens, turnon_value: turnon_value, turnoff_value: turnoff_value, motion_value: motion_value, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5539 echoSpokenNodesEditDialog.modal.dialog('open'); 5540 }) 5541 .fail(function(jqXHR, textStatus, errorThrown) { 5542 errorMessageDialog("Variables are not available", 'Error', 350) 5543 }) 5544 .done(); 5545 } 5546 else if (category==='program') { 5547 table.lookupDataPromisePgm 5548 .then(function(content) { 5549 if (!content) { 5550 throw new Error('Programs not available'); 5551 } 5552 echoSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5553 echoSpokenNodesEditDialog.modal.dialog('open'); 5554 }) 5555 .fail(function(jqXHR, textStatus, errorThrown) { 5556 errorMessageDialog("Programs are not available", 'Error', 350) 5557 }) 5558 .done(); 5559 } 5560 else if (category==='scene') { 5561 table.lookupDataPromise 5562 .then(function(content) { 5563 if (!content) { 5564 throw new Error('Scenes not available'); 5565 } 5566 echoSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.scenes, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5567 echoSpokenNodesEditDialog.modal.dialog('open'); 5568 }) 5569 .fail(function(jqXHR, textStatus, errorThrown) { 5570 errorMessageDialog("Scenes are not available", 'Error', 350) 5571 }) 5572 .done(); 5573 } 5574 else if (category==='elk') {
5575 table.lookupDataPromiseElk 5576 .then(function(content) { 5577 if (!content) { 5578 throw new Error('ELK zones not available'); 5579 } 5580 echoSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.zones, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5581 echoSpokenNodesEditDialog.modal.dialog('open'); 5582 }) 5583 .fail(function(jqXHR, textStatus, errorThrown) { 5584 errorMessageDialog("ELK zones are not available", 'Error', 350) 5585 }) 5586 .done(); 5587 } 5588 else if (category==='av') { 5589 $.when(table.lookupDataPromise, table.lookupDataPromiseStateVar, table.lookupDataPromisePgm, table.lookupDataPromiseNetworkRsc) 5590 .then(function(nodes, vars, pgms, net) { 5591 5592 if (!nodes) { 5593 throw new Error('Nodes not available'); 5594 } 5595 5596 if (!vars) { 5597 throw new Error('State variables not available'); 5598 } 5599 5600 if (!pgms) { 5601 throw new Error('Programs not available'); 5602 } 5603 5604 if (!net) { 5605 throw new Error('Network resources not available'); 5606 } 5607/* 5608 console.log('nodes', nodes) 5609 console.log('vars', vars) 5610 console.log('net', net) 5611 console.log('rooms', rooms) 5612 console.log('room', room) 5613*/ 5614 echoSpokenNodesAVEditDialog.setFields({ uuid: table.currentUuid, domain: table.currentDomain, lookupNodes: nodes[0], lookupVars: vars[0], lookupPgms: pgms[0], lookupNet: net[0], address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, avData: avData }); 5615 echoSpokenNodesAVEditDialog.modal.dialog('open'); 5616 }) 5617 .fail(function(jqXHR, textStatus, errorThrown) { 5618 errorMessageDialog("AV resources are not available", 'Error', 350) 5619 }) 5620 .done(); 5621 } 5622 else { // Regular devices (std), thermostats & locks 5623 table.lookupDataPromise 5624 .then(function(content) { 5625 if (!content) { 5626 throw new Error('Devices not available'); 5627 } 5628 5629 echoSpokenNodesEditDialog.setFields({ category: 'std', uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.devices, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, colorMfr:colorMfr, userCat: userCat }); 5630 echoSpokenNodesEditDialog.modal.dialog('open'); 5631 }) 5632 .fail(function(jqXHR, textStatus, errorThrown) { 5633 errorMessageDialog("Devices are not available", 'Error', 350) 5634 }) 5635 .done(); 5636 } 5637 }) 5638 }); 5639 }, 5640 5641 renderNameWithDelete: function (data, type, row, meta) { 5642 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span>' + (!data?row.spoken:data) + '</span>'; 5643 }, 5644 5645 renderAddress: function (data, type, row) { 5646// return row.category!='av'?(data.replace(/ /g,".") + (row.category=='statevar'?' (Off:' + row.turnoff_value + ' On:' + row.turnon_value + ')':'')) :'N/A' 5647 return row.category!=='av'? 5648 (data.replace(/ /g,".") + (row.category==='statevar'? 5649 (row.userCat==='motion'?' (Detect:' + row.motion_value + ')':row.userCat==='contact'?' (Close:' + row.motion_value + ')':' (Off:' + row.turnoff_value + ' On:' + row.turnon_value + ')') 5650 :'')) 5651 :'N/A' 5652 }, 5653 5654 renderCategory: function (data, type, row) { 5655 return echoCategoryTxt[data]; 5656 }, 5657 5658 renderUserCat: function (data, type, row) { 5659 var userCat = tableEchoSpokenNodes.userCats[data]; 5660 5661 if (['statevar', 'program', 'scene'].includes(row.category)) { 5662 return userCat?userCat:tableEchoSpokenNodes.userCats['scene']; 5663 } 5664 else if (row.category==='lock') { 5665 return userCat?userCat:tableEchoSpokenNodes.userCats['lock']; 5666 } 5667 else if (row.category==='thermostat') { 5668 return userCat?userCat:tableEchoSpokenNodes.userCats['thermostat']; 5669 } 5670 else { 5671 return userCat?userCat:tableEchoSpokenNodes.userCats['light']; 5672 } 5673 }, 5674 5675 renderSpoken: function (data, type, row) { 5676 var spoken; 5677 if (data.length===1) { 5678 spoken = data[0]; 5679 } 5680 else if (data.length>1) { 5681 spoken = data[0] + ', [More]'; 5682 } 5683 else { 5684 spoken = "\[Empty\]"; 5685 } 5686 5687 return '<span class="spoken editable_link">' + spoken + '</span>'; 5688 }, 5689 5690 load: function () { 5691 var table = this; 5692 if (!this.api) { 5693 this.api = $('#echo_spokennodestable').DataTable({ 5694 "width": "100%", 5695 "ajax": function (data, callback, settings) {
5696 ajaxJsonGet('/api/voice/amazon/spoken/nodes', $.param({ uuid: table.currentUuid, domain: table.currentDomain })) 5697 .done(function(data, status, jqXHR){ 5698 table.onDataLoad(data) 5699 .done(function() { 5700 callback(data); 5701 }) 5702 .done(); 5703 }); 5704 }, 5705 "columns": [ 5706 { data: "name", defaultContent: "", render: this.renderNameWithDelete }, 5707 { data: "category", render: this.renderCategory, name: "category" }, 5708 { data: "userCat", defaultContent: "", render: this.renderUserCat, name: "userCat" }, 5709 { data: "address", render: this.renderAddress, name: "address" }, 5710 { data: "roomName", defaultContent: "" }, 5711 { data: "spoken", render: this.renderSpoken, name: "spokens" }, 5712 { data: "room", defaultContent: "", name: "room", visible:false }, 5713 { data: "spoken[;]", visible: false, searchable:true }, 5714 { data: "_id", visible: false, searchable: false, name: 'pk' }, 5715 { data: "turnon_value", visible: false, defaultContent: "", name: "turnon_value" }, 5716 { data: "turnoff_value", visible: false, defaultContent: "", name: "turnoff_value" }, 5717 { data: "motion_value", visible: false, defaultContent: "", name: "motion_value" }, 5718 { data: "colorMfr", visible: false, defaultContent: "", name: "colorMfr" }, 5719 { data: "av", visible: false, defaultContent: "", name: "av" } 5720 ], 5721 "drawCallback": function (settings, json) { 5722 // API might not be initialized yet, page may display 'Loading...' 5723 if (table.api) { 5724 table.completeUI(); 5725 } 5726 } 5727 }); 5728 } 5729 }, 5730 5731 reload: function () { 5732 var table = this; 5733 5734 if (!table.api) { 5735 table.load(); 5736 } 5737 else { 5738 table.api.ajax.reload(null, false); 5739 } 5740 }, 5741 5742 setUuid: function (uuid, domain, forceReload) { 5743 if (this.currentUuid!==uuid || forceReload) { 5744 this.currentUuid = uuid; 5745 this.currentDomain = domain; 5746 this.reload(); 5747 } 5748 }, 5749 5750 setLookupDataPromise: function (lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar, lookupDataPromiseNetworkRsc, lookupDataPromise
5750Elk) { 5751 this.lookupDataPromise = lookupDataPromise; // Reference to the lookup promise used to validate if we have lookup data prior to editing spoken 5752 this.lookupDataPromisePgm = lookupDataPromisePgm; 5753 this.lookupDataPromiseStateVar = lookupDataPromiseStateVar; 5754 this.lookupDataPromiseNetworkRsc = lookupDataPromiseNetworkRsc; 5755 this.lookupDataPromiseElk = lookupDataPromiseElk; 5756 }, 5757 5758 setRooms: function (roomsPromise) { 5759 this.roomsPromise = roomsPromise; 5760 } 5761}; 5762 5763var tableGHSpokenNodes = { 5764 data: null, 5765 api: null, 5766 currentUuid: null, 5767 currentDomain: null, 5768 lookupDataPromise: null, 5769 lookupDataPromisePgm: null, 5770 lookupDataPromiseStateVar: null, 5771 roomsPromise:null, 5772 userCats: { 5773 'scene': 'Scene', 5774 'light': 'Device\\Light', 5775 'switch': 'Device\\Switch', 5776 'outlet': 'Device\\Outlet', 5777 'fan': 'Device\\Fan', 5778 'lock': 'Device\\Lock', 5779 'thermostat': 'Device\\Thermostat', 5780 'openClose': 'Device\\Open Close', 5781 }, 5782 5783 // Triggered when json data has loaded (before table draw) 5784 onDataLoad: function (spokens) { 5785 var table = this; 5786 5787 table.data = spokens; // Keep a reference to the original data to facilitate export. 5788 5789 table.lookupDataPromise 5790 .then(function(lookups) { 5791 lookups.data.devices.forEach(function (item) { 5792 item.class=''; 5793 5794 spokens.data.forEach(function (spoken) { 5795 if (spoken.category === item.category && spoken.address === item.address) { 5796 item.class='dup'; 5797 } 5798 }) 5799 }); 5800 5801 lookups.data.scenes.forEach(function (item) { 5802 item.class=''; 5803 5804 spokens.data.forEach(function (spoken) { 5805 if (spoken.category === 'scene' && spoken.address === item.address) { 5806 item.class='dup'; 5807 } 5808 }) 5809 }) 5810 }); 5811 5812 table.lookupDataPromisePgm 5813 .then(function(lookups) { 5814 lookups.data.forEach(function (item) { 5815 item.class=''; 5816 5817 spokens.data.forEach(function (spoken) { 5818 if (spoken.category === 'program' && spoken.address === item.address) { 5819 item.class='dup'; 5820 } 5821 }) 5822 }) 5823 }); 5824 5825 // We want to delay screen draw till after we have processed rooms 5826 return table.roomsPromise 5827 .then(function(rooms) { 5828 spokens.data.forEach(function (spoken) { 5829 rooms.data.forEach(function (room) { 5830 if (spoken.room === room._id) { 5831 spoken.roomName=room.name; 5832 } 5833 }) 5834 }) 5835 }) 5836 // NOTE: For variables, we allow duplicates so that we can have several spokens for the same variable with different turn on/turn off values 5837 }, 5838
5839 completeUI: function () { 5840 var table = this; 5841 table.api.columns.adjust(); 5842 5843 $('#gh_spokennodestable').tooltip(); 5844 5845 // Delete spoken 5846 $('#gh_spokennodestable .delbutton').unbind('click').click(function (e) { 5847 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5848 5849 confirmationDialog('Are you sure?', 'Remove spoken?') 5850 .then(function () { 5851 ajaxJsonDelete('/api/voice/google/spoken/node', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 5852 .done(function () { 5853 tableGHSpokenNodes.reload(); 5854 }) 5855 }) 5856 .done(); 5857 }); 5858 5859// $('#gh_spokennodestable .spoken').unbind('click').click(function (e) { (???) 5860 // Edit existing spoken 5861 $('#gh_spokennodestable .spoken').click(function () { 5862 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 5863 var address = table.api.cell($(this).closest('tr'), 'address:name').data(); 5864 var spokens = table.api.cell($(this).closest('tr'), 'spokens:name').data(); 5865 var category = table.api.cell($(this).closest('tr'), 'category:name').data(); 5866 var room = table.api.cell($(this).closest('tr'), 'room:name').data(); 5867 var colorMfr = table.api.cell($(this).closest('tr'), 'colorMfr:name').data(); 5868 var userCat = table.api.cell($(this).closest('tr'), 'userCat:name').data(); 5869 var spoken_link = this; 5870 5871 table.roomsPromise 5872 .done(function(rooms) { 5873 if (category==='statevar') { 5874 var turnon_value = table.api.cell($(spoken_link).closest('tr'), 'turnon_value:name').data(); 5875 var turnoff_value = table.api.cell($(spoken_link).closest('tr'), 'turnoff_value:name').data(); 5876 5877 table.lookupDataPromiseStateVar 5878 .then(function(content) { 5879 if (!content) { 5880 throw new Error('Variables not available'); 5881 } 5882 ghSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data, address: address?address:'', spokens: spokens, turnon_value: turnon_value, turnoff_value: turnoff_value, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5883 ghSpokenNodesEditDialog.modal.dialog('open'); 5884 }) 5885 .fail(function(jqXHR, textStatus, errorThrown) { 5886 errorMessageDialog("Variables are not available", 'Error', 350) 5887 }) 5888 .done(); 5889 } 5890 else if (category==='program') { 5891 table.lookupDataPromisePgm 5892 .then(function(content) { 5893 if (!content) { 5894 throw new Error('Programs not available'); 5895 } 5896 ghSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5897 ghSpokenNodesEditDialog.modal.dialog('open'); 5898 }) 5899 .fail(function(jqXHR, textStatus, errorThrown) { 5900 errorMessageDialog("Programs are not available", 'Error', 350) 5901 }) 5902 .done(); 5903 } 5904 else if (category==='scene') { 5905 table.lookupDataPromise 5906 .then(function(content) { 5907 if (!content) { 5908 throw new Error('Scenes not available'); 5909 } 5910 ghSpokenNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.scenes, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, userCat: userCat }); 5911 ghSpokenNodesEditDialog.modal.dialog('open'); 5912 }) 5913 .fail(function(jqXHR, textStatus, errorThrown) { 5914 errorMessageDialog("Scenes are not available", 'Error', 350) 5915 }) 5916 .done(); 5917 } 5918 else { // Regular devices (std), thermostats & locks 5919 table.lookupDataPromise 5920 .then(function(content) { 5921 if (!content) { 5922 throw new Error('Devices not available'); 5923 } 5924 ghSpokenNodesEditDialog.setFields({ category: 'std', uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.devices, address: address?address:'', spokens: spokens, pk: pk, editMode: true, room: (room && room.length?room:'$noroom'), rooms: rooms.data, colorMfr:colorMfr, userCat: userCat }); 5925 ghSpokenNodesEditDialog.modal.dialog('open'); 5926 }) 5927 .fail(function(jqXHR, textStatus, errorThrown) { 5928 errorMessageDialog("Devices are not available", 'Error', 350) 5929 }) 5930 .done(); 5931 } 5932 }) 5933 }); 5934 }, 5935 5936 renderNameWithDelete: function (data, type, row, meta) { 5937 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span>' + (!data?row.spoken:data) + '</span>'; 5938 }, 5939 5940 renderAddress: function (data, type, row) { 5941 return data.replace(/ /g,".") + (row.category==='statevar'?' (Off:' + row.turnoff_value + ' On:'
5941+ row.turnon_value + ')':''); 5942 }, 5943 5944 renderCategory: function (data, type, row) { 5945 return echoCategoryTxt[data]; 5946 }, 5947 5948 renderUserCat: function (data, type, row) { 5949 var userCat = tableGHSpokenNodes.userCats[data]; 5950 5951 if (['statevar', 'program', 'scene'].includes(row.category)) { 5952 return userCat?userCat:tableGHSpokenNodes.userCats['switch']; // Default GH cat for an ISY scene is a switch 5953 } 5954 else if (row.category==='lock') { 5955 return userCat?userCat:tableGHSpokenNodes.userCats['lock']; 5956 } 5957 else if (row.category==='thermostat') { 5958 return userCat?userCat:tableGHSpokenNodes.userCats['thermostat']; 5959 } 5960 else { 5961 return userCat?userCat:tableGHSpokenNodes.userCats['light']; 5962 } 5963 }, 5964 5965 renderSpoken: function (data, type, row) { 5966 var spoken; 5967 if (data.length===1) { 5968 spoken = data[0]; 5969 } 5970 else if (data.length>1) { 5971 spoken = data[0] + ', [More]'; 5972 } 5973 else { 5974 spoken = "\[Empty\]"; 5975 } 5976 5977 return '<span class="spoken editable_link">' + spoken + '</span>'; 5978 }, 5979 5980 load: function () { 5981 var table = this; 5982 if (!this.api) { 5983 this.api = $('#gh_spokennodestable').DataTable({ 5984 "ajax": function (data, callback, settings) { 5985 ajaxJsonGet('/api/voice/google/spoken/nodes', $.param({ uuid: table.currentUuid, domain: table.currentDomain })) 5986 .done(function(data, status, jqXHR){ 5987 table.onDataLoad(data) 5988 .done(function() { 5989 callback(data); 5990 }) 5991 .done(); 5992 }); 5993 }, 5994 "columns": [ 5995 { data: "name", defaultContent: "", render: this.renderNameWithDelete }, 5996 { data: "category", render: this.renderCategory, name: "category" }, 5997 { data: "userCat", defaultContent: "", render: this.renderUserCat, name: "userCat" }, 5998 { data: "address", render: this.renderAddress, name: "address" }, 5999 { data: "roomName", defaultContent: "" }, 6000 { data: "spoken", render: this.renderSpoken, name: "spokens" }, 6001 { data: "room", defaultContent: "", name: "room", visible:false }, 6002 { data: "spoken[;]", visible: false, searchable:true }, 6003 { data: "_id", visible: false, searchable: false, name: 'pk' }, 6004 { data: "turnon_value", visible: false, defaultContent: "", name: "turnon_value" }, 6005 { data: "turnoff_value", visible: false, defaultContent: "", name: "turnoff_value" }, 6006 { data: "colorMfr", visible: false, defaultContent: "", name: "colorMfr" } 6007 ], 6008 "drawCallback": function (settings, json) { 6009 // API might not be initialized yet, page may display 'Loading...' 6010 if (table.api) { 6011 table.completeUI(); 6012 } 6013 } 6014 }); 6015 } 6016 }, 6017 6018 reload: function () { 6019 var table = this; 6020 6021 if (!table.api) { 6022 table.load(); 6023 } 6024 else { 6025 table.api.ajax.reload(null, false); 6026 } 6027 }, 6028 6029 setUuid: function (uuid, domain, forceReload) { 6030 if (this.currentUuid!==uuid || forceReload) { 6031 this.currentUuid = uuid; 6032 this.currentDomain = domain; 6033 this.reload(); 6034 } 6035 }, 6036 6037 setLookupDataPromise: function (lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar) { 6038 this.lookupDataPromise = lookupDataPromise; // Reference to the lookup promise used to validate if we have lookup data prior to editing spoken 6039 this.lookupDataPromisePgm = lookupDataPromisePgm; // Reference to the lookup promise used to validate if we have lookup data prior to editing spoken 6040 this.lookupDataPromiseStateVar = lookupDataPromiseStateVar; // Reference to the lookup promise used to validate if we have lookup data prior to editing spoken 6041 }, 6042 6043 setRooms: function (roomsPromise) { 6044 this.roomsPromise = roomsPromise; 6045 } 6046}; 6047 6048var tableNsList = { 6049 data: null, 6050 api: null, 6051 currentUuid: null, 6052 currentDomain: null, 6053
6054 completeUI: function () { 6055 var table = this; 6056 table.api.columns.adjust(); 6057 6058 $('#ns_list_table').tooltip(); 6059 }, 6060 6061 renderExpiry: function (data, type, row) { 6062 var text; 6063 if (data === null) { 6064 text = 'Perpetual' 6065 } else { 6066 return tableNsList.renderDate(data, type, row) 6067 } 6068 6069 return text; 6070 }, 6071 6072 renderDate: function (data, type, row) { 6073 var text; 6074 6075 try { 6076 var dt = new Date (data) 6077 text = pad(dt.getFullYear(), 4) + '-' + 6078 pad(dt.getMonth()+1, 2) + '-' + 6079 pad(dt.getDate(), 2) 6080 6081 } catch (e) { 6082 text = '[Invalid date]' 6083 } 6084 6085 return text; 6086 }, 6087 6088 load: function () { 6089 var table = this; 6090 if (!this.api) { 6091 this.api = $('#ns_list_table').DataTable({ 6092 "ajax": function (data, callback, settings) { 6093 ajaxJsonGet('/api/nodeserver/list/'+table.currentUuid, $.param({ name: 'true', domain: table.currentDomain })) 6094 .done(function(data, status, jqXHR){ 6095 callback(data); 6096 }); 6097 }, 6098 "columns": [ 6099 { data: "name", defaultContent: "" }, 6100 { data: "edition", defaultContent: "" }, 6101 { data: "purchase_date", render: this.renderDate }, 6102 { data: "expiry", render: this.renderExpiry } 6103 ], 6104 "drawCallback": function (settings, json) { 6105 // API might not be initialized yet, page may display 'Loading...' 6106 if (table.api) { 6107 table.completeUI(); 6108 } 6109 } 6110 }); 6111 } 6112 }, 6113 6114 reload: function () { 6115 var table = this; 6116 6117 if (!table.api) { 6118 table.load(); 6119 } 6120 else { 6121 table.api.ajax.reload(null, false); 6122 } 6123 }, 6124 6125 setUuid: function (uuid, domain, forceReload) { 6126 if (this.currentUuid!==uuid || forceReload) { 6127 this.currentUuid = uuid; 6128 this.currentDomain = domain; 6129 this.reload(); 6130 } 6131 } 6132}; 6133 6134 6135var tableIftttInNodes = { 6136 api: null, 6137 currentUuid: null, 6138 currentDomain: null, 6139 currentIftttInKey: null, 6140 lookupDataPromise : null, 6141 lookupDataPromisePgm : null, 6142 lookupDataPromiseStateVar: null, 6143 6144 // Triggered when json data has loaded (before table draw) 6145 onDataLoad: function (data) { 6146 this.currentIftttInKey = data.iftttInKey; 6147 }, 6148
6149 completeUI: function () { 6150 var table = this; 6151 table.api.columns.adjust(); 6152 $('#iftttinnodestable').tooltip(); 6153 6154 // Delete IFTTT rule 6155 $('#iftttinnodestable .delbutton').unbind('click').click(function (e) { 6156 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6157 6158 confirmationDialog('Are you sure?', 'Remove IFTTT / Webhook?') 6159 .then(function () { 6160 ajaxJsonDelete('/api/ifttt/in/node', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 6161 .done(function () { 6162 tableIftttInNodes.reload(); 6163 }) 6164 }) 6165 .done(); 6166 }); 6167 6168 // Show IFTTT Maker channel URL to use 6169 $('#iftttinnodestable .linkbutton').unbind('click').click(function (e) { 6170 var event = table.api.cell($(this).closest('tr'), 'event:name').data(); 6171 var message; 6172 6173 if (table.currentIftttInKey && table.currentIftttInKey.length) { 6174 var url = 'https://' + location.hostname + '/api/ifttt/' + encodeURIComponent(event) + '/key/' + table.currentIftttInKey; 6175 message = 'This event URL is:<br><p class="url80">'+url+'</p>'; 6176 informationMessageDialog(message, 'Event URL', 800); 6177 } 6178 else { 6179 message = 'First, you have to set an IFTTT / Webhook key.<br>Please click on "Set Key".'; 6180 informationMessageDialog(message, 'Error'); 6181 } 6182 6183 }); 6184 6185// $('#iftttinnodestable .spoken').unbind('click').click(function (e) { (???) 6186 // Edit existing spoken 6187 $('#iftttinnodestable .event').click(function () { 6188 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6189 var address = table.api.cell($(this).closest('tr'), 'address:name').data(); 6190 var event = table.api.cell($(this).closest('tr'), 'event:name').data(); 6191 var category = table.api.cell($(this).closest('tr'), 'category:name').data(); 6192 var control = table.api.cell($(this).closest('tr'), 'control:name').data(); 6193 var value = table.api.cell($(this).closest('tr'), 'value:name').data(); 6194 6195 if (category==='statevar') { 6196 table.lookupDataPromiseStateVar 6197 .then(function(content) { 6198 if (!content) { 6199 throw new Error('Variables not available'); 6200 } 6201 iftttInNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data, address: address?address:'', event: event, control: control, value: value, pk: pk, editMode: true }); 6202 iftttInNodesEditDialog.modal.dialog('open'); 6203 }) 6204 .fail(function(jqXHR, textStatus, errorThrown) { 6205 errorMessageDialog("Variables are not available", 'Error', 350) 6206 }) 6207 .done(); 6208 } 6209 else if (category==='program') { 6210 table.lookupDataPromisePgm 6211 .then(function(content) { 6212 if (!content) { 6213 throw new Error('Programs not available'); 6214 } 6215 iftttInNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data, address: address?address:'', event: event, control: control, pk: pk, editMode: true }); 6216 iftttInNodesEditDialog.modal.dialog('open'); 6217 }) 6218 .fail(function(jqXHR, textStatus, errorThrown) { 6219 errorMessageDialog("Programs are not available", 'Error', 350) 6220 }) 6221 .done(); 6222 } 6223 else if (category==='scene') { 6224 table.lookupDataPromise 6225 .then(function(content) { 6226 if (!content) { 6227 throw new Error('Scenes not available'); 6228 } 6229 iftttInNodesEditDialog.setFields({ category: category, uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.scenes, address: address?address:'', event: event, control: control, pk: pk, editMode: true }); 6230 iftttInNodesEditDialog.modal.dialog('open'); 6231 }) 6232 .fail(function(jqXHR, textStatus, errorThrown) { 6233 errorMessageDialog("Scenes are not available", 'Error', 350) 6234 }) 6235 .done(); 6236 } 6237 else { // Regular devices (std), thermostats & locks 6238 table.lookupDataPromise 6239 .then(function(content) { 6240 if (!content) { 6241 throw new Error('Devices not available'); 6242 } 6243 iftttInNodesEditDialog.setFields({ category: 'std', uuid: table.currentUuid, domain: table.currentDomain, lookup: content.data.devices, address: address?address:'', event: event, control: control, value: value, pk: pk, editMode: true });
6244 iftttInNodesEditDialog.modal.dialog('open'); 6245 }) 6246 .fail(function(jqXHR, textStatus, errorThrown) { 6247 errorMessageDialog("Devices are not available", 'Error', 350) 6248 }) 6249 .done(); 6250 } 6251 }); 6252 }, 6253 6254 renderEventWithDelete: function (data, type, row, meta) { 6255 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbutton"><span class="ui-icon ui-icon-extlink"></span></span><span class="event editable_link">' + (data && data.length?data:"\[Empty\]") + '</span>'; 6256 }, 6257 6258 renderAddress: function (data, type, row) { 6259 return data.replace(/ /g,"."); 6260 }, 6261 6262 renderCategory: function (data, type, row) { 6263 return iftttCategoryTxt[data]; 6264 }, 6265 6266 renderControl: function (data, type, row) { 6267 return iftttControlsLabels[data] + (data==='value'?(' to ' + row.value):''); 6268 }, 6269 6270 load: function () { 6271 var table = this; 6272 if (!this.api) { 6273 this.api = $('#iftttinnodestable').DataTable({ 6274 "ajax": function (data, callback, settings) { 6275 ajaxJsonGet('/api/ifttt/in/nodes', $.param({ uuid: table.currentUuid, domain: table.currentDomain })) 6276 .done(function(data, status, jqXHR){ 6277 table.onDataLoad(data); 6278 callback(data); 6279 }); 6280 }, 6281 "columns": [ 6282 { data: "event", render: this.renderEventWithDelete, name: "event" }, 6283 { data: "category", render: this.renderCategory, name: "category" }, 6284 { data: "name", defaultContent: "" }, //render: this.renderNameWithDelete 6285 { data: "address", render: this.renderAddress, name: "address" }, 6286 { data: "control", render: this.renderControl, name: "control" }, 6287 { data: "value", defaultContent: '', visible: false, searchable: true, name: 'value' }, 6288 { data: "_id", visible: false, searchable: false, name: 'pk' } 6289 ], 6290 "drawCallback": function (settings, json) { 6291 // API might not be initialized yet, page may display 'Loading...' 6292 if (table.api) { 6293 table.completeUI(); 6294 } 6295 } 6296 }); 6297 } 6298 }, 6299 6300 reload: function () { 6301 var table = this; 6302 6303 if (!table.api) { 6304 table.load(); 6305 } 6306 else { 6307 table.api.ajax.reload(null, false); 6308 } 6309 }, 6310 6311 setUuid: function (uuid, domain, forceReload) { 6312 if (this.currentUuid!==uuid || forceReload) { 6313 this.currentUuid = uuid; 6314 this.currentDomain = domain; 6315 this.reload(); 6316 } 6317 }, 6318 6319 setLookupDataPromise: function (lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar) { 6320 this.lookupDataPromise = lookupDataPromise; // Reference to the lookup promise used to validate if we have lookup data prior to editing ifttt event 6321 this.lookupDataPromisePgm = lookupDataPromisePgm; 6322 this.lookupDataPromiseStateVar = lookupDataPromiseStateVar; 6323 } 6324}; 6325 6326var activityLevels = { 6327 0: 'Error', 6328 1: 'Warn', 6329 2: 'Info' 6330}; 6331 6332var activityCategories = { 6333 'rates': 'RATES' 6334}; 6335 6336var tableActivityLog = { 6337 api: null, 6338 currentUuid: null, 6339 currentDomain: null, 6340
6341 completeUI: function () { 6342 }, 6343 6344 renderCat: function (data, type, row, meta) { 6345 var cat = activityCategories[data]; 6346 return cat?cat:data; 6347 }, 6348 6349 renderLevel: function (data, type, row, meta) { 6350 return activityLevels[data]; 6351 }, 6352 6353 renderTime: function (data, type, row, meta) { 6354 return new Date(data).dateLog(); 6355 }, 6356 6357 load: function () { 6358 var table = this; 6359 if (!this.api) { 6360 this.api = $('#activitylogtable').DataTable({ 6361 'ajax': function (data, callback, settings) { 6362 ajaxJsonGet('/api/isy/activity', $.param({ uuid: table.currentUuid, domain: table.currentDomain })) 6363 .done(function(data, status, jqXHR){ 6364 callback(data); 6365 }); 6366 }, 6367 'columns': [ 6368 { data: 'time', render: this.renderTime }, 6369 { data: 'cat', render: this.renderCat }, 6370 { data: 'level', render: this.renderLevel }, 6371 { data: 'message' } 6372 ], 6373 'order': [[ 0, 'desc' ]], // Last activity first in the table 6374 'drawCallback': function (settings, json) { 6375 // API might not be initialized yet, page may display 'Loading...' 6376 if (table.api) { 6377 table.completeUI(); 6378 } 6379 } 6380 }); 6381 } 6382 }, 6383 6384 reload: function () { 6385 var table = this; 6386 6387 if (!table.api) { 6388 table.load(); 6389 } 6390 else { 6391 table.api.ajax.reload(null, false); 6392 } 6393 }, 6394 setUuid: function (uuid, domain, forceReload) { 6395 if (this.currentUuid!==uuid || forceReload) { 6396 this.currentUuid = uuid; 6397 this.currentDomain = domain; 6398 this.reload(); 6399 } 6400 } 6401}; 6402 6403 6404var tableUdComms = { 6405 api: null, 6406 provider: null, 6407 6408 refresh: function () { 6409 this.load(); 6410 }, 6411
6412 completeUI : function() { 6413 var table = this; 6414 table.api.columns.adjust(); 6415 6416 // Delete existing message 6417 $('#ud-comms-table .delbutton').unbind('click').click(function () { 6418 var code = table.api.cell($(this).closest('tr'), 'code:name').data(); 6419 6420 confirmationDialog('Message will be removed. If notifications were sent, users will no longer be able to read the content.<br><br>Are you sure?', 'Remove Message?') 6421 .then(function () { 6422 ajaxJsonDelete('/api/ud/communication', $.param( { code:code } )) 6423 .done(function () { 6424 table.refresh(); 6425 }) 6426 .fail(function(jqXHR, textStatus, errorThrown) { 6427 if (jqXHR.status===425) { // Validation error message 6428 errorMessageDialog(jqXHR.responseText, 'Error', 350); 6429 } 6430 else { 6431 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 6432 } 6433 }) 6434 .done(); 6435 }) 6436 .done(); 6437 }); 6438 6439 // Edit existing option 6440 $('#ud-comms-table .code').click(function () { 6441 var code = table.api.cell($(this).closest('tr'), 'code:name').data(); 6442 var message = table.api.cell($(this).closest('tr'), 'message:name').data(); 6443 var type = table.api.cell($(this).closest('tr'), 'type:name').data(); 6444 var contentPush = table.api.cell($(this).closest('tr'), 'contentPush:name').data(); 6445 var contentSurvey = table.api.cell($(this).closest('tr'), 'contentSurvey:name').data(); 6446 var contentAppLink = table.api.cell($(this).closest('tr'), 'contentAppLink:name').data(); 6447 var sentCount = table.api.cell($(this).closest('tr'), 'sentCount:name').data(); 6448 var lastSentTime = table.api.cell($(this).closest('tr'), 'lastSentTime:name').data(); 6449 var filterLicense = table.api.cell($(this).closest('tr'), 'filterLicense:name').data(); 6450 var filterLicenseExpiryDays = table.api.cell($(this).closest('tr'), 'filterLicenseExpiryDays:name').data(); 6451 var filterModel = table.api.cell($(this).closest('tr'), 'filterModel:name').data(); 6452 var filterVersion = table.api.cell($(this).closest('tr'), 'filterVersion:name').data(); 6453 var filterOnline = table.api.cell($(this).closest('tr'), 'filterOnline:name').data(); 6454 var filterOnlineHours = table.api.cell($(this).closest('tr'), 'filterOnlineHours:name').data(); 6455 var separateUuid = table.api.cell($(this).closest('tr'), 'separateUuid:name').data(); 6456 var accountWithIsy = table.api.cell($(this).closest('tr'), 'accountWithIsy:name').data(); 6457 6458 udCommsEditDialog.setFields({ editMode: true, code:code, 6459 message: message, type: type, contentPush: contentPush, contentSurvey: contentSurvey, contentAppLink: contentAppLink, 6460 filterLicense: filterLicense, filterLicenseExpiryDays: filterLicenseExpiryDays, filterModel: filterModel, filterVersion: filterVersion, 6461 filterOnline: filterOnline, filterOnlineHours: filterOnlineHours, separateUuid: separateUuid, accountWithIsy: accountWithIsy, 6462 sentCount: sentCount, lastSentTime: lastSentTime 6463 }); 6464 udCommsEditDialog.modal.dialog('open'); 6465 }); 6466 6467 // Setup the tool button 6468 $('#ud-comms-table .toolMenuButton').button({ 6469 width:'12em', 6470 label: 'Select tool...', 6471 icons: { secondary: "ui-icon-triangle-1-s" } 6472 }) 6473 .unbind('click') 6474 .click(function (event) { 6475 $('#ud-comms-table .toolMenu').not($(this).next()).hide(); // Close all other menus 6476 $(this).next() 6477 .toolMenu('collapseAllNow', null, true ) // Start with collapsed menus 6478 .toggle(); // Toggle the active one 6479 event.stopPropagation(); // Otherwise, it won't open 6480 }); 6481 6482 // Setup the tool menu underneath the button 6483 $('#ud-comms-table .toolMenu').toolMenu({ 6484 width:'14em', 6485 // position: { my: "left top", at: "left+10 bottom" }, // Position of submenu relative to parent 6486 select: function(event, ui) { 6487 var tool = ui.item.attr('value'); 6488 var code = table.api.cell($(this).closest('tr'), 'code:name').data(); 6489 var contentSurvey = table.api.cell($(this).closest('tr'), 'contentSurvey:name').data(); 6490 6491 switch(tool) { 6492 case 'chart': 6493 $(this).hide(); // For some reason, we need this, otherwise the menu item stays open. 6494 udCommsDeliveryDialog.setFields({ code: code }); 6495 udCommsDeliveryDialog.modal.dialog('open'); 6496 break; 6497 6498 case 'surveyChart': 6499 $(this).hide(); // For some reason, we need this, otherwise the menu item stays open. 6500 udCommsSurveyDialog.setFields({ code: code, contentSurvey: contentSurvey }); 6501 udCommsSurveyDialog.modal.dialog('open'); 6502 break; 6503 6504 case 'surveyDownload': 6505 $(this).hide(); // For some reason, we need this, otherwise the menu item stays open.
6506 ajaxJsonGet('/api/ud/survey/download', $.param({ code: code })) 6507 .then(function(data, status, jqXHR) { 6508 try { 6509 var isFileSaverSupported = !!new Blob; 6510 } catch (e) {} 6511 6512 if (isFileSaverSupported) { 6513 var BOM = '\uFEFF'; 6514 saveAs(new Blob([BOM + data.data], {type: "application/csv;charset=utf-8"}), 'survey ' + code + '.csv'); 6515 } 6516 else { 6517 errorMessageDialog('Feature not supported on this browser', 'Not supported'); 6518 } 6519 }) 6520 .fail(function(jqXHR, textStatus, errorThrown) { 6521 updateTips(tips, 'HTTP error ' + jqXHR.status); 6522 }) 6523 .done(); 6524 break; 6525 6526 default: 6527 } 6528 } 6529 }) 6530 .hide() 6531 .addClass('ui-front'); 6532 }, 6533 6534 renderCode: function (data, type, row, meta) { 6535 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="code editable_link">' + (data && data.length? data : '(Not found error msg)') + '</span>'; 6536 }, 6537 6538 renderMessage: function (data, type, row, meta) { 6539 var keepLen = 40 6540 return '<span class="code editable_link">' + data.substring(0, keepLen) + (data.length > keepLen ? '...':'') + '</span>'; 6541 }, 6542 6543 renderType: function (t) { 6544 var types = { 6545 push: 'Push', 6546 survey: 'Survey', 6547 appLink: 'App Link' 6548 } 6549 return types[t] || t 6550 }, 6551 6552 renderTime: function (t) { 6553 return dtStr(new Date(t)) 6554 }, 6555 6556 renderSent: function (t) { 6557 if (!t) { 6558 return 'Not sent' 6559 } 6560 6561 return tableUdComms.renderTime(t) 6562 }, 6563 6564 renderTools: function (data, type, row, table) { 6565 var isSurvey = (row.type === 'survey'); 6566 6567 return '<div><button class="toolMenuButton"></button>' + 6568 '<ul class="toolMenu">' + 6569 '<li value="chart">View delivery</li>' + 6570 (isSurvey ? ('<li>Survey<ul>' + 6571 '<li value="surveyChart">View results</li>' + 6572 '<li value="surveyDownload">Download results</li>' + 6573 '</ul></li>'):'') + 6574 '</ul>' 6575 }, 6576 6577 load: function () { 6578 var table = this; 6579 6580 if (!this.api) { 6581 this.api = $('#ud-comms-table').DataTable({ 6582 'ajax': function (data, callback, settings) { 6583 ajaxJsonGet('/api/ud/communication') 6584 .done(function(data, status, jqXHR){ 6585 callback(data); 6586 }); 6587 }, 6588 'columns': [ 6589 { data: 'code', render: this.renderCode, name: 'code' }, 6590 { data: 'message', render: this.renderMessage, name: 'message' }, 6591 { data: 'type', render: this.renderType, name: 'type' }, 6592 { data: 'createdTime', render: this.renderTime, name: 'createdTime' }, 6593 { data: 'lastSentTime', render: this.renderSent, name: 'lastSentTime', defaultContent: '' }, 6594 { render: function (d, t, r) { return table.renderTools(d, t, r, table) }, orderable: false, searchable: false }, 6595 { data: 'contentPush', visible:false, name: 'contentPush' }, 6596 { data: 'contentSurvey', visible:false, name: 'contentSurvey' }, 6597 { data: 'contentAppLink', visible:false, name: 'contentAppLink' }, 6598 { data: 'sentCount', visible:false, name: 'sentCount', defaultContent: 0 }, 6599 { data: 'filterLicense', visible:false, name: 'filterLicense', defaultContent: '' }, 6600 { data: 'filterLicenseExpiryDays', visible:false, name: 'filterLicenseExpiryDays' }, 6601 { data: 'filterModel', visible:false, name: 'filterModel', defaultContent: '' }, 6602 { data: 'filterVersion', visible:false, name: 'filterVersion', defaultContent: '' }, 6603 { data: 'filterOnline', visible:false, name: 'filterOnline', defaultContent:'' }, 6604 { data: 'filterOnlineHours', visible:false, name: 'filterOnlineHours' }, 6605 { data: 'separateUuid', visible:false, name: 'separateUuid', defaultContent: false }, 6606 { data: 'accountWithIsy', visible:false, name: 'accountWithIsy', defaultContent: true } 6607 ], 6608 'order': [[ 3, 'desc' ]], // Default sort by created time, recent on top 6609 'drawCallback': function (settings, json) { 6610 // API might not be initialized yet, page may display 'Loading...' 6611 if (table.api) { 6612 table.completeUI(); 6613 } 6614 } 6615 }); 6616 } else { 6617 table.api.ajax.reload(null, false); 6618 } 6619 }, 6620 reload: function () { 6621 var table = this; 6622 6623 if (!table.api) { 6624 table.load(); 6625 } 6626 else { 6627 table.api.ajax.reload(null, false); 6628 } 6629 } 6630}; 6631 6632 6633 6634var tableNsOccupancy = { 6635 api: null, 6636 currentUuid: null, 6637 currentDomain: null,
6638 locationsPromise: null, 6639 6640 addressRight: function (address) { 6641 return address.substring(address.indexOf('_')+1) 6642 }, 6643 6644 // Triggered when json data has loaded (before table draw) 6645 onDataLoad: function (nodes) { 6646 var locations = []; 6647 6648 // Build list of locations. Used in dropdown to select location. 6649 nodes.data.forEach(function(node) { 6650 if (node.isLocation) { 6651 locations.push(node) 6652 } 6653 }); 6654 6655 // Set the name of the location, used here to display the name along with the location 6656 nodes.data.forEach(function(node) { 6657 if (!node.isLocation && node.primary!==node.address) { 6658 locations.forEach(function(location) { 6659 if (node.primary === location.address) { 6660 node.primaryName = location.name; 6661 } 6662 }); 6663 6664 if (!node.primaryName) { 6665 node.primaryName = '[Not Found]'; 6666 } 6667 } 6668 }) 6669 }, 6670 6671 completeUI: function () { 6672 var table = this; 6673 table.api.columns.adjust(); 6674 6675// $('#nsoccupancytable').tooltip(); 6676 6677 // Delete geofence 6678 $('#nsoccupancytable .delbutton').unbind('click').click(function (e) { 6679 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6680 var isLocation = table.api.cell($(this).closest('tr'), 'isLocation:name').data(); 6681 6682 if (isLocation) { 6683 confirmationDialog('Are you sure?', 'Remove location?') 6684 .then(function () { 6685 ajaxJsonDelete('/api/nsmodule/' + table.currentUuid + '/occupancy/location', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 6686 .fail(function(jqXHR, textStatus, errorThrown) { 6687 if (jqXHR.status==503) { 6688 errorMessageDialog('ISY is not online') 6689 } 6690 else if (jqXHR.status==400) { 6691 errorMessageDialog('This location has children. Please remove them first.') 6692 } 6693 else { 6694 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 6695 } 6696 }) 6697 .done(function () { 6698 tableNsOccupancy.reload(); 6699 table.locationsPromise = nsOccupancyDialog.loadLocations(table.currentUuid) 6700 }) 6701 }) 6702 .done(); 6703 } 6704 else { 6705 confirmationDialog('Are you sure?', 'Remove Geofence?') 6706 .then(function () { 6707 ajaxJsonDelete('/api/nsmodule/' + table.currentUuid + '/occupancy/node', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 6708 .fail(function(jqXHR, textStatus, errorThrown) { 6709 if (jqXHR.status==503) { 6710 errorMessageDialog('ISY is not online') 6711 } 6712 else { 6713 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 6714 } 6715 }) 6716 .done(function () { 6717 tableNsOccupancy.reload(); 6718 6719 table.locationsPromise = nsOccupancyDialog.loadLocations(table.currentUuid) 6720 }) 6721 }) 6722 .done(); 6723 } 6724 }); 6725 6726 // Show Locative URL to use 6727 $('#nsoccupancytable .linkbutton').unbind('click').click(function (e) { 6728 var key = table.api.cell($(this).closest('tr'), 'key:name').data(); 6729 nsOccupancyUrlDialog.setFields(location.hostname, key); 6730 nsOccupancyUrlDialog.modal.dialog('open'); 6731 6732 }); 6733 6734 // Edit existing device name 6735 $('#nsoccupancytable .occname').click(function () { 6736 6737 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6738 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 6739 var location = table.api.cell($(this).closest('tr'), 'primary:name').data(); 6740 var address = table.api.cell($(this).closest('tr'), 'address:name').data(); 6741 6742 table.locationsPromise 6743 .done(function(locations) { 6744 nsOccupancyEditDialog.setFields({ uuid: table.currentUuid, name: name, location: (location!==address?location:'$nolocation'), pk: pk, editMode: true, locations: locations.data }); 6745 nsOccupancyEditDialog.modal.dialog('open'); 6746 }) 6747 .fail(function(jqXHR, textStatus, errorThrown) { 6748 errorMessageDialog('Error loading locations', 'Error', 350) 6749 }) 6750 }); 6751 6752 // Edit existing location name 6753 $('#nsoccupancytable .locname').click(function () { 6754 6755 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6756 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 6757 6758 nsOccupancyLocationEditDialog.setFields({ uuid: table.currentUuid, name: name, pk: pk, editMode: true });
6759 nsOccupancyLocationEditDialog.modal.dialog('open'); 6760 }); 6761 }, 6762 6763 renderNameWithDelete: function (data, type, row, meta) { 6764 if (row.isLocation) { 6765 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbuttonspacer"></span><span class="locname editable_link">' + data + '</span>'; 6766 } 6767 else { 6768 if (row.primaryName) { 6769 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbutton"><span class="ui-icon ui-icon-extlink"></span></span>' + row.primaryName + '/<span class="occname editable_link">' + data + '</span>'; 6770 } 6771 else { 6772 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbutton"><span class="ui-icon ui-icon-extlink"></span></span><span class="occname editable_link">' + data + '</span>'; 6773 } 6774 } 6775 }, 6776 6777 renderValue: function (data, type, row, meta) { 6778 return typeof data == 'boolean'?(data?'In':'Out'):data; // Data can be a string set to "Unknown", if variable not present. 6779 }, 6780 6781 load: function () { 6782 var table = this; 6783 if (!this.api) { 6784 this.api = $('#nsoccupancytable').DataTable({ 6785 'ajax': function (data, callback, settings) { 6786 ajaxJsonGet('/api/nsmodule/' + table.currentUuid + '/occupancy/nodes', $.param({ domain: table.currentDomain })) 6787 .done(function(data, status, jqXHR){ 6788 table.onDataLoad(data); 6789 callback(data); 6790 }); 6791 }, 6792 'columns': [ 6793 { data: 'name', defaultContent: '', render: this.renderNameWithDelete, name: 'name' }, 6794 { data: 'key', defaultContent: '', name: 'key' }, 6795 { data: 'address', defaultContent: '', name: 'address' }, 6796 { data: 'props.ST.value', defaultContent: 'Unknown', render: this.renderValue }, 6797 { data: 'primary', defaultContent: '', name: 'primary', visible: false }, 6798 { data: 'isLocation', defaultContent: false, name: 'isLocation', visible: false }, 6799 { data: '_id', visible: false, searchable: false, name: 'pk' } 6800 ], 6801 'drawCallback': function (settings, json) { 6802 // API might not be initialized yet, page may display 'Loading...' 6803 if (table.api) { 6804 table.completeUI(); 6805 } 6806 } 6807 }); 6808 } 6809 }, 6810 6811 reload: function () { 6812 var table = this; 6813 6814 if (!table.api) { 6815 table.load(); 6816 } 6817 else { 6818 table.api.ajax.reload(null, false); 6819 } 6820 }, 6821 6822 setUuid: function (uuid, domain, forceReload) { 6823 if (this.currentUuid!==uuid || forceReload) { 6824 this.currentUuid = uuid; 6825 this.currentDomain = domain; 6826 this.reload(); 6827 } 6828 }, 6829 setLocations: function (locationsPromise) { 6830 this.locationsPromise = locationsPromise; 6831 }, 6832}; 6833 6834var tableNsOccupancy2Geofence = { 6835 api: null, 6836 currentUuid: null, 6837 currentDomain: null, 6838 geofencesPromise: null, 6839 devicesPromise: null, 6840 6841 addressRight: function (address) { 6842 return address.substring(address.indexOf('_')+1) 6843 }, 6844 6845 // Triggered when json data has loaded (before table draw) 6846 onDataLoad: function (nodes) { 6847 var geofences = []; 6848 6849 // Build list of geofences. Used in dropdown to select geofence. 6850 nodes.data.forEach(function(node) { 6851 if (node.isGeofence) { 6852 geofences.push(node) 6853 } 6854 }); 6855 6856 // Set the name of the geofence, used here to display the name along with the geofence 6857 nodes.data.forEach(function(node) { 6858 if (!node.isGeofence && node.primary!==node.address) { 6859 geofences.forEach(function(geofence) { 6860 if (node.primary === geofence.address) { 6861 node.primaryName = geofence.name; 6862 } 6863 }); 6864 6865 if (!node.primaryName) { 6866 node.primaryName = '[Not Found]'; 6867 } 6868 } 6869 }) 6870 }, 6871
6872 completeUI: function () { 6873 var table = this; 6874 table.api.columns.adjust(); 6875 6876 // Delete geofence 6877 $('#nsoccupancy2geofencetable .delbutton').unbind('click').click(function (e) { 6878 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6879 var isGeofence = table.api.cell($(this).closest('tr'), 'isGeofence:name').data(); 6880 6881 if (isGeofence) { 6882 confirmationDialog('Are you sure?', 'Remove Geofence?') 6883 .then(function () { 6884 ajaxJsonDelete('/api/nsmodule/' + table.currentUuid + '/occupancy2/geofence', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 6885 .fail(function(jqXHR, textStatus, errorThrown) { 6886 if (jqXHR.status==503) { 6887 errorMessageDialog('ISY is not online') 6888 } else if (jqXHR.status==400) { 6889 errorMessageDialog('This geofence has assigned mobile devices. Please unassign them first.') 6890 } else { 6891 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 6892 } 6893 }) 6894 .done(function () { 6895 tableNsOccupancy2Geofence.reload(); 6896 table.geofencesPromise = nsOccupancy2Dialog.loadGeofences(table.currentUuid) 6897 }) 6898 }) 6899 .done(); 6900 } else { 6901 confirmationDialog('Are you sure?', 'Unassign Mobile Device?') 6902 .then(function () { 6903 ajaxJsonDelete('/api/nsmodule/' + table.currentUuid + '/occupancy2/node', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 6904 .fail(function(jqXHR, textStatus, errorThrown) { 6905 if (jqXHR.status==503) { 6906 errorMessageDialog('ISY is not online') 6907 } else if (jqXHR.status==403) { 6908 errorMessageDialog('There is no Mobile Device assigned on ISY, please try "Synchronize" option below before unassigning.<br><br>In the case you want to start from scratch, please use "Remove" option bellow.') 6909 } else { 6910 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 6911 } 6912 }) 6913 .done(function () { 6914 tableNsOccupancy2Geofence.reload(); 6915 table.geofencesPromise = nsOccupancy2Dialog.loadGeofences(table.currentUuid) 6916 }) 6917 }) 6918 .done(); 6919 } 6920 }); 6921 6922 // Show Locative URL to use 6923 $('#nsoccupancy2geofencetable .linkbutton').unbind('click').click(function (e) { 6924 var key = table.api.cell($(this).closest('tr'), 'key:name').data(); 6925 nsOccupancy2GeofenceUrlDialog.setFields(location.hostname, key); 6926 nsOccupancy2GeofenceUrlDialog.modal.dialog('open'); 6927 }); 6928 6929 // Edit existing device name 6930 $('#nsoccupancy2geofencetable .occname').click(function () { 6931 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6932 var geofence = table.api.cell($(this).closest('tr'), 'primary:name').data(); 6933 var address = table.api.cell($(this).closest('tr'), 'address:name').data(); 6934 var device = table.api.cell($(this).closest('tr'), 'deviceAddress:name').data(); 6935 6936 table.geofencesPromise 6937 .done(function(geofences) { 6938 table.devicesPromise 6939 .done(function(devices) { 6940 nsOccupancy2EditDialog.setFields({ uuid: table.currentUuid, device: device, geofence: (geofence!==address?geofence:'$nogeofence'), pk: pk, editMode: true, geofences: geofences.data, devices: devices.data }); 6941 nsOccupancy2EditDialog.modal.dialog('open'); 6942 }) 6943 .fail(function(jqXHR, textStatus, errorThrown) { 6944 errorMessageDialog('Error loading geofences', 'Error', 350) 6945 }) 6946 }) 6947 .fail(function(jqXHR, textStatus, errorThrown) { 6948 errorMessageDialog('Error loading geofences', 'Error', 350) 6949 }) 6950 }); 6951 6952 // Edit existing geofence name 6953 $('#nsoccupancy2geofencetable .locname').click(function () { 6954 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 6955 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 6956 nsOccupancy2GeofenceEditDialog.setFields({ uuid: table.currentUuid, name: name, pk: pk, editMode: true }); 6957 nsOccupancy2GeofenceEditDialog.modal.dialog('open'); 6958 }); 6959 }, 6960 6961 renderNameWithDelete: function (data, type, row, meta) { 6962 if (row.isGeofence) { 6963 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbuttonspacer"></span><span class="locname editable_link">' + data + '</span>'; 6964 } else { 6965 if (row.primaryName) { 6966 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbutton"><span class="ui-icon ui-icon-extlink"></span></span>' + row.primaryName + '/<span class="occname editable_link">' + data + '</span>'; 6967 } 6968 else { 6969 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="linkbutton"><span class="ui-icon ui-icon-extlink"></span></span><span class="occname editable_link">' + data + '</span>'; 6970 } 6971 } 6972 }, 6973 6974 renderValue: function (data, type, row, meta) { 6975 return typeof data == 'boolean'?(data?'In':'Out'):data; // Data can be a string set to "Unknown", if variable not present. 6976 }, 6977 6978 load: function () { 6979 var table = this; 6980 if (!this.api) { 6981 this.api = $('#nsoccupancy2geofencetable').DataTable({ 6982 'ajax': function (data, callback, settings) {
6983 ajaxJsonGet('/api/nsmodule/' + table.currentUuid + '/occupancy2/nodes', $.param({ domain: table.currentDomain })) 6984 .done(function(data, status, jqXHR){ 6985 table.onDataLoad(data); 6986 table.devicesPromise = nsOccupancy2Dialog.loadDevices(table.currentUuid); 6987 callback(data); 6988 }); 6989 }, 6990 'columns': [ 6991 { data: 'name', defaultContent: '', render: this.renderNameWithDelete, name: 'name' }, 6992 { data: 'key', defaultContent: '', name: 'key' }, 6993 { data: 'address', defaultContent: '', name: 'address' }, 6994 { data: 'props.ST.value', defaultContent: 'Unknown', render: this.renderValue }, 6995 { data: 'primary', defaultContent: '', name: 'primary', visible: false }, 6996 { data: 'isGeofence', defaultContent: false, name: 'isGeofence', visible: false }, 6997 { data: 'deviceAddress', defaultContent: '', name: 'deviceAddress', visible: false }, 6998 { data: '_id', visible: false, searchable: false, name: 'pk' } 6999 ], 7000 'drawCallback': function (settings, json) { 7001 // API might not be initialized yet, page may display 'Loading...' 7002 if (table.api) { 7003 table.completeUI(); 7004 } 7005 } 7006 }) 7007 } 7008 }, 7009 7010 reload: function () { 7011 var table = this; 7012 if (!table.api) { 7013 table.load(); 7014 } else { 7015 table.api.ajax.reload(null, false); 7016 } 7017 }, 7018 7019 setUuid: function (uuid, domain, forceReload) { 7020 if (this.currentUuid!==uuid || forceReload) { 7021 this.currentUuid = uuid; 7022 this.currentDomain = domain; 7023 this.reload(); 7024 } 7025 }, 7026 7027 setGeofences: function (geofencesPromise) { 7028 this.geofencesPromise = geofencesPromise; 7029 }, 7030 7031 setDevices: function (devicesPromise) { 7032 this.devicesPromise = devicesPromise; 7033 }, 7034}; 7035 7036var tableNsOccupancy2Device = { 7037 api: null, 7038 currentUuid: null, 7039 currentDomain: null, 7040 devicesPromise: null, 7041 7042 addressRight: function (address) { 7043 return address.substring(address.indexOf('_')+1) 7044 }, 7045 7046 // Triggered when json data has loaded (before table draw) 7047 onDataLoad: function (devices) { 7048 }, 7049 7050 completeUI: function () { 7051 var table = this; 7052 table.api.columns.adjust(); 7053 7054 // Delete Mobile Device 7055 $('#nsoccupancy2devicetable .delbutton').unbind('click').click(function (e) { 7056 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 7057 confirmationDialog('Are you sure?', 'Remove Mobile Device?') 7058 .then(function () { 7059 ajaxJsonDelete('/api/nsmodule/' + table.currentUuid + '/occupancy2/device', $.param({id: pk, uuid: table.currentUuid, domain: table.currentDomain })) 7060 .fail(function(jqXHR, textStatus, errorThrown) { 7061 if (jqXHR.status==503) { 7062 errorMessageDialog('ISY is not online') 7063 } 7064 else if (jqXHR.status==400) { 7065 errorMessageDialog('This mobile device has assigned geofences. Please unassign them first.') 7066 } 7067 else { 7068 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 7069 } 7070 }) 7071 .done(function () { 7072 tableNsOccupancy2Device.reload(); 7073 table.devicesPromise = nsOccupancy2Dialog.loadDevices(table.currentUuid) 7074 }) 7075 }) 7076 .done(); 7077 7078 }); 7079 7080 // Link Mobile Device with UDI Mobile 7081 $('#nsoccupancy2devicetable .linkbutton').unbind('click').click(function (e) { 7082 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 7083 var pinCode = table.api.cell($(this).closest('tr'), 'pinCode:name').data(); 7084 var pinValid = table.api.cell($(this).closest('tr'), 'pinValid:name').data(); 7085 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 7086 if(!pinCode){ 7087 pinCode = ''; 7088 pinValid = ''; 7089 } 7090 7091 nsOccupancy2DevicePinDialog.setFields({uuid: table.currentUuid, pk: pk, name: name, pinCode: pinCode, pinValid: pinValid}); 7092 nsOccupancy2DevicePinDialog.modal.dialog('open'); 7093 }); 7094 7095 // Unlink Mobile Device with UDI Mobile 7096 $('#nsoccupancy2devicetable .unlinkbutton').unbind('click').click(function (e) { 7097 var key = table.api.cell($(this).closest('tr'), 'key:name').data(); 7098 var token = table.api.cell($(this).closest('tr'), 'token:name').data(); 7099 confirmationDialog('Please confirm if you want to Unlink this Mobile Device with UDI Mobile.<br><br>WARNING: UDI Mobile would not report any geofence status for this Mobile Device after this process.', 'Unlink Mobile Device') 7100 .then(function () {
7101 ajaxJsonDelete('/api/nsmodule/' + table.currentUuid + '/occupancy2/device/' + key + '/link','',{apitoken: token}) 7102 .fail(function(jqXHR, textStatus, errorThrown) { 7103 if (jqXHR.status==503) { 7104 errorMessageDialog('ISY is not online') 7105 } 7106 else if (jqXHR.status==400) { 7107 errorMessageDialog(jqXHR.responseText) 7108 } 7109 else { 7110 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 7111 } 7112 }) 7113 .done(function () { 7114 tableNsOccupancy2Device.reload(); 7115 table.devicesPromise = nsOccupancy2Dialog.loadDevices(table.currentUuid) 7116 }) 7117 }) 7118 .done() 7119 }); 7120 7121 // Edit existing Mobile Device name 7122 $('#nsoccupancy2devicetable .devname').click(function () { 7123 7124 var pk = table.api.cell($(this).closest('tr'), 'pk:name').data(); 7125 var name = table.api.cell($(this).closest('tr'), 'name:name').data(); 7126 var profile = table.api.cell($(this).closest('tr'), 'profile:name').data(); 7127 7128 nsOccupancy2DeviceEditDialog.setFields({ uuid: table.currentUuid, name: name, profile: profile, pk: pk, editMode: true }); 7129 nsOccupancy2DeviceEditDialog.modal.dialog('open'); 7130 }); 7131 }, 7132 7133 renderNameWithDelete: function (data, type, row, meta) { 7134 return '<span class="delbutton"><span class="ui-icon ui-icon-closethick"></span></span><span class="devname editable_link">' + data + '</span>'; 7135 }, 7136 7137 renderValue: function (data, type, row, meta) { 7138 return typeof data == 'boolean'?(data?'Yes':'No'):data; // Data can be a string set to "Unknown", if variable not present. 7139 }, 7140 7141 renderToken: function (data, type, row, meta) { 7142 var linked = (data!=''); 7143 return (linked?'<span class="unlinkbutton"><span class="ui-icon ui-icon-closethick"></span></span>Yes':'<span class="linkbutton"><span class="ui-icon ui-icon-extlink"></span></span><span class="linkbutton editable_link">No</span>'); 7144 }, 7145 7146 load: function () { 7147 var table = this; 7148 if (!this.api) { 7149 this.api = $('#nsoccupancy2devicetable').DataTable({ 7150 'ajax': function (data, callback, settings) { 7151 ajaxJsonGet('/api/nsmodule/' + table.currentUuid + '/occupancy2/devices', $.param({ domain: table.currentDomain })) 7152 .done(function(data, status, jqXHR){ 7153 table.onDataLoad(data); 7154 callback(data); 7155 }); 7156 }, 7157 'columns': [ 7158 { data: 'name', defaultContent: '', render: this.renderNameWithDelete, name: 'name' }, 7159 { data: 'key', defaultContent: '', name: 'key' }, 7160 { data: 'address', defaultContent: '', name: 'address' }, 7161 { data: 'token', defaultContent: '', render: this.renderToken, name: 'token' }, 7162 { data: 'profile', defaultContent: '', render: this.renderValue, name: 'profile' }, 7163 { data: 'pinCode', defaultContent: '', visible: false, name: 'pinCode' }, 7164 { data: 'pinValid', defaultContent: '', visible: false, name: 'pinValid' }, 7165 { data: '_id', visible: false, searchable: false, name: 'pk' } 7166 ], 7167 'drawCallback': function (settings, json) { 7168 // API might not be initialized yet, page may display 'Loading...' 7169 if (table.api) { 7170 table.completeUI(); 7171} 7172} 7173 }); 7174} 7175 }, 7176 7177 reload: function () { 7178 var table = this; 7179 if (!table.api) { 7180 table.load(); 7181 } 7182 else { 7183 table.api.ajax.reload(null, false); 7184 } 7185 }, 7186 7187 setUuid: function (uuid, domain, forceReload) { 7188 if (this.currentUuid!=uuid || forceReload) { 7189 this.currentUuid = uuid; 7190 this.currentDomain = domain; 7191 this.reload(); 7192 } 7193 }, 7194 setDevices: function (devicesPromise) { 7195 this.devicesPromise = devicesPromise; 7196 }, 7197}; 7198 7199// ********************************************************************** 7200// ***************************** Dialogs ******************************** 7201// ********************************************************************** 7202 7203// ----------------- Generic dialogs ------------------------- 7204 7205// We store temporary dialog handles so we can close them if we autoLogout 7206function addTempDialog(dialog) { 7207 tempDialogs.push(dialog); 7208} 7209 7210function removeTempDialog(dialog) { 7211 tempDialogs = tempDialogs.filter(function(dialogItem) { 7212 return dialogItem !== dialog; 7213 }) 7214} 7215 7216 7217// Generic error message dialog 7218function errorMessageDialog(message, title, width) { 7219 var defer = $.Deferred(); 7220 var dialog = null; 7221 7222 var dialogOptions = { 7223 autoOpen: true, 7224 modal: true, 7225 width: width?width:450, 7226 buttons: { 7227 'Ok': function() { 7228 defer.resolve(); 7229 $(this).dialog('close'); 7230 } 7231 }, 7232 close: function() { 7233 removeTempDialog(dialog); 7234 defer.reject(); 7235 } 7236 }; 7237 7238 var html = '<div title="' + (title?title:"Error") + '"><p autofocus>' + 7239 '<table><tr><td style="vertical-align:top"><span class="ui-icon ui-icon-alert" style="margin-right: 7px;"></span></td><td>
7239<span">' + message + '</span></td></tr></table></p></div>'; 7240 7241 // Initializes the dialog 7242 dialog = $(html).dialog(dialogOptions); 7243 addTempDialog(dialog); 7244 7245 return defer.promise(); 7246} 7247 7248// Generic information message dialog 7249function informationMessageDialog(message, title, width) { 7250 var defer = $.Deferred(); 7251 var dialog = null; 7252 7253 var dialogOptions = { 7254 autoOpen: true, 7255 modal: true, 7256 width: width?width:450, 7257 buttons: { 7258 'Ok': function() { 7259 defer.resolve(); 7260 $(this).dialog('close'); 7261 } 7262 }, 7263 close: function() { 7264 removeTempDialog(dialog); 7265 defer.reject(); 7266 } 7267 }; 7268 7269 var html = '<div title="' + (title?title:'Information') + '"><p autofocus><span>' + message + '</span></p></div>'; 7270 7271 dialog = $(html).dialog(dialogOptions); 7272 addTempDialog(dialog); 7273 7274 return defer.promise(); 7275} 7276 7277// Generic error message dialog 7278function confirmationDialog(message, title, width) { 7279 var defer = $.Deferred(); 7280 var dialog = null; 7281 7282 var dialogOptions = { 7283 autoOpen: true, 7284 modal: true, 7285 width: width?width:450, 7286 buttons: { 7287 'Yes': function() { 7288 defer.resolve(); 7289 $(this).dialog('close'); 7290 }, 7291 Cancel: function() { 7292 defer.reject(); 7293 $(this).dialog('close'); 7294 } 7295 }, 7296 close: function() { 7297 removeTempDialog(dialog); 7298 defer.reject(); 7299 } 7300 }; 7301 7302 var html = '<div title="' + (title?title:'Please confirm') + '"><p autofocus>' + 7303 '<table><tr><td style="vertical-align:top"><span class="ui-icon ui-icon-alert" style="margin-right: 7px;"></span></td><td><span">' + message + '</span></td></tr></table></p></div>'; 7304 7305 7306 dialog = $(html).dialog(dialogOptions); 7307 addTempDialog(dialog); 7308 7309 return defer.promise(); 7310} 7311 7312// Dialog which has multiple buttons at the bottom. Each of them resolves the promise with the button text used 7313// buttons is an array of string 7314function optionsDialog(message, title, buttons, width) { 7315 var defer = $.Deferred(); 7316 7317 var dialogOptions = { 7318 autoOpen: true, 7319 modal: true, 7320 width: width?width:450, 7321 buttons: Object.fromEntries(buttons.map(function (txt) { 7322 return [ 7323 txt, 7324 function() { 7325 defer.resolve(txt); 7326 $(this).dialog('close'); 7327 } 7328 ] 7329 })), 7330 close: function() { 7331 removeTempDialog(dialog); 7332 defer.reject(); 7333 } 7334 }; 7335 7336 var html = '<div title="' + (title?title:'Please select an option') + '"><p autofocus>' + 7337 '<table><tr><td style="vertical-align:top"><span class="ui-icon ui-icon-alert" style="margin-right: 7px;"></span></td><td><span">' + message + '</span></td></tr></table></p></div>'; 7338 7339 var dialog = $(html).dialog(dialogOptions); 7340 addTempDialog(dialog); 7341 7342 return defer.promise(); 7343} 7344 7345// Live message update dialog 7346function liveMessageDialogInit() { 7347 var defer, 7348 dialog; 7349 7350 function setMessage(msg) { 7351 $('#live_message_text').html(msg); 7352 } 7353 7354 function setTitle(title) { 7355 $('#live_message_dialog').attr('title', title); 7356 } 7357 7358 function restartDefer(title) { 7359 defer = $.Deferred(); 7360 return defer.promise(); 7361 } 7362 7363 var dialogOptions = { 7364 autoOpen: false, 7365 modal: true, 7366 //width: width?width:450, 7367 buttons: { 7368 Cancel: function() { 7369 $(this).dialog('close'); 7370 } 7371 }, 7372 close: function() { 7373 if (defer) { 7374 defer.reject(); 7375 } 7376 } 7377 }; 7378 7379 // Initializes the dialog 7380 dialog = $('#live_message_dialog').dialog(dialogOptions); 7381 7382 return { modal: dialog, setTitle: setTitle, setMessage: setMessage, restartDefer: restartDefer }; 7383} 7384 7385// Generic pin request dialog 7386function pinDialog(message, length, title, width) { 7387 var defer = $.Deferred(); 7388 var dialog = null; 7389 var regx; 7390 7391 function closeDialog() { 7392 // A close will leave the html in the dom 7393 dialog.dialog('destroy').remove(); 7394 } 7395 7396 function isValid(pin) { 7397 return regx && regx.test(pin); 7398 } 7399 7400 function submit() { 7401 var pin = $('#dialog_pin_code').val(); 7402 7403 if (isValid(pin)) { 7404 // console.log('pin is valid', pin) 7405 defer.resolve(pin); 7406 } else {
7407 // console.log('pin not valid', pin) 7408 defer.reject(pin); 7409 } 7410 7411 closeDialog(); 7412 } 7413 7414 var dialogOptions = { 7415 autoOpen: true, 7416 modal: true, 7417 width: width?width:450, 7418 buttons: { 7419 'Submit': function() { 7420 submit(); 7421 }, 7422 Cancel: function() { 7423 defer.reject(); 7424 closeDialog(); 7425 } 7426 }, 7427 close: function() { 7428 removeTempDialog(dialog); 7429 defer.reject(); 7430 closeDialog(); 7431 } 7432 }; 7433 7434 if (length) { 7435 regx = new RegExp(Array(length).fill('\\d').join('')); 7436 } 7437 7438 var html = '<div class="modal ui-widget-content" title="' + (title?title:'PIN Required') + '"><p>' + 7439 '<table><tr><td style="vertical-align:top"><span class="ui-icon ui-icon-alert" style="margin-right: 7px;"></span></td><td><span">' + 7440 message + '</span></td></tr></table></p>' + 7441 '<form>' + 7442 '<input type="text" ' + (length ? 'maxLength="' + length + '" ' : ' ') + ' name="pin" id="dialog_pin_code" value="" class="text ui-widget-content ui-corner-all">' + 7443 '<input type="submit" class="submit">' + 7444 '</form>' + 7445 '</div>'; 7446 7447 dialog = $(html).dialog(dialogOptions); 7448 addTempDialog(dialog); 7449 7450 // Lastpass fix 7451 dialog.find('form input').on('keydown', function(e) { 7452 if (e.keyCode===13) { 7453 event.stopPropagation(); 7454 event.preventDefault(); 7455 submit(); 7456 } 7457 }); 7458 7459 // Handles keyboad enter (submit event) 7460 // dialog.find('form').submit(function(e) { 7461 // e.preventDefault(); 7462 // submit(); 7463 // }); 7464 7465 return defer.promise(); 7466} 7467 7468/* 7469<!-- Generic PIN dialog --> 7470<div id="pin_dialog" class="modal ui-widget-content" title="PIN Required" style="display:none;"> 7471 <div class="validateTipsContainer"><p class="validateTips"></p></div> 7472 <div> 7473 <form> 7474 <fieldset> 7475 <label for="pin" id="pin_label"></label> 7476 <input type="number" name="pin" id="pin" value="" class="text serialize ui-widget-content ui-corner-all"> 7477 </fieldset> 7478 </form> 7479 </div> 7480</div> 7481 7482*/ 7483 7484// ------------------- Custom Dialog utilities ---------------------------- 7485 7486// Displays error message in 'tips' placeholder 7487function updateTips(tips, txt) { 7488 tips.text(txt); 7489 tips.hide().show('fade'); 7490} 7491 7492// Validates if there is a value selected 7493function checkSelection(tips, field, name, text) { 7494 if (field.val() === name ) { 7495 field.addClass('fieldfocus'); 7496 field.focus(); 7497 updateTips(tips, 'Please select or create a new '+text+'.'); 7498 return false; 7499 } 7500 else { 7501 return true; 7502 } 7503} 7504 7505// Validates field length 7506function checkLength(tips, field, name, min, max) { 7507 if (field.val().length > max || field.val().length < min ) { 7508 field.addClass('fieldfocus'); 7509 field.focus(); 7510 updateTips(tips, name + ' must be between ' + min + ' and ' + max + ' characters.' ); 7511 return false; 7512 } 7513 else { 7514 return true; 7515 } 7516} 7517 7518// Max value 2147483647 (int32) 7519 7520 7521function checkNumber(tips, field, name, min, max) { 7522 var value = field.val(); 7523 7524 if (!/^-?\d+$/.test(value)) { 7525 field.addClass('fieldfocus'); 7526 field.focus(); 7527 updateTips(tips, name + ' must be a number.' ); 7528 return false; 7529 } 7530 else if (max && (parseInt(value)<min || parseInt(value)>max)) { // (Dont validate min, usually will be 0) 7531 field.addClass('fieldfocus'); 7532 field.focus(); 7533 updateTips(tips, name + ' must be between ' + min + ' and ' + max + '.'); 7534 return false; 7535 } 7536 else { 7537 return true; 7538 } 7539} 7540 7541function checkNumberOrDelta(tips, field, name) { 7542 var value = field.val(); 7543 7544 // Allowed: Number, optionnally preceded by + or - 7545 if (!/^[\+\-]*\d+$/.test(value)) { 7546 field.addClass('fieldfocus'); 7547 field.focus(); 7548 updateTips(tips, name + ' must be a number.' ); 7549 return false; 7550 } 7551 else { 7552 return true; 7553 } 7554} 7555 7556function checkFloat(tips, field, name, min, max) { 7557 var value = field.val(); 7558 7559 if (!/^(\d|\.)+$/.test(value)) { 7560 field.addClass('fieldfocus'); 7561 field.focus(); 7562 updateTips(tips, name + ' must be a number.' ); 7563 return false; 7564 } 7565 else if (max && (parseFloat(value)<min || parseFloat(value)>max)) { // (Dont validate min, usually will be 0) 7566 field.addClass('fieldfocus'); 7567 field.focus(); 7568 updateTips(tips, name + ' must be between ' + min + ' and ' + max + '.'); 7569 return false; 7570 } 7571 else { 7572 return true; 7573 } 7574} 7575 7576function checkNotEmpty(tips, field, name) { 7577 if (!field.val().length) { 7578 field.addClass('fieldfocus'); 7579 field.focus(); 7580 updateTips(tips, name + ' is required.'); 7581 return false; 7582 } 7583 else { 7584 return true; 7585 } 7586} 7587 7588// Validates against regexp 7589function checkRegexp(tips, field, regexp, txt) { 7590 if (!(regexp.test(field.val()))) { 7591 field.addClass('fieldfocus'); 7592 field.focus(); 7593 updateTips(tips, txt); 7594 return false; 7595 } 7596 else { 7597 return true; 7598 } 7599} 7600 7601function checkRegexpUuid(tips, o, n) { 7602 var uuid = o.val(); 7603 7604 if (!uuidRegex.test(uuid)) { 7605 o.addClass('fieldfocus'); 7606 o.focus(); 7607 updateTips(tips, n); 7608 return false; 7609 } 7610 else { // Rewrite the regex 7611 o.val(uuid.toLowerCase().replace(uuidRegex, '$1:$2:$3:$4:$5:$6')); 7612 return true; 7613 } 7614} 7615 7616function checkPwd(tips, password) { 7617 var valid=true; 7618 7619 if (password.val()!=='$unchanged') { 7620 valid = valid && checkLength(tips, password, 'password', 8, 20); 7621 valid = valid && checkRegexp(tips, password, passwordRegex, 'Password must have letters and have at least 1 digit or special character'); 7622 } 7623 7624 return valid; 7625} 7626 7627//Validates password and passwordconf are equal 7628function checkPwdEqual(tips, p1, p2, txt) { 7629 if ((p1.val().localeCompare(p2.val()))) { // returns 0 if strings equal 7630 p2.addClass('fieldfocus'); 7631 p2.focus(); 7632 updateTips(tips, txt); 7633 return false; 7634 } 7635 else { 7636 return true; 7637 } 7638} 7639 7640//Validates Radiobutton is selected 7641function checkRadio(tips, selector, txt) { 7642 if ($(selector).length === 0) { 7643 updateTips(tips, txt); 7644 return false; 7645 } 7646 else { 7647 return true; 7648 } 7649} 7650 7651function serializeFormWithCheckboxes($find) {
7652 var data = $find.filter(':not([type="checkbox"])').serialize(); 7653 7654 var checkboxes = $find.filter('[type="checkbox"]').map(function() { 7655 return this.name + "=" + this.checked; 7656 }) 7657 .get() 7658 .join("&"); 7659 7660 if (checkboxes!=="" && data!=="") { 7661 data += "&" + checkboxes; 7662 } 7663 else { 7664 data += checkboxes; 7665 } 7666 7667 return data; 7668} 7669 7670function openMyProfile() { 7671 var isyLookupPromise = ajaxJsonGet('/api/isys/lookup', $.param({domain: registeredUser.domain})) 7672 .fail(function(jqXHR, textStatus, errorThrown) { 7673 errorMessageDialog("Can't retrieve ISY list<br>HTTP error " + jqXHR.status, 'Error', 350) 7674 }); 7675 7676 var userProfilePromise = ajaxJsonGet('/api/profile', $.param({domain: registeredUser.domain})) 7677 .fail(function(jqXHR, textStatus, errorThrown) { 7678 errorMessageDialog("Can't retrieve user profile<br>HTTP error " + jqXHR.status, 'Error', 350) 7679 }); 7680 7681 $.when(isyLookupPromise, userProfilePromise) 7682 .then(function(isyLookupXHR, userProfileXHR) { 7683 // Alter the data to be able to use it in a selectmenu 7684 var userProfile = userProfileXHR[0]; // The response is the first item in the array 7685 var uuidList = isyLookupXHR[0].data.map(function (item) { 7686 return { 7687 label: item.uuid, // Shown in the dropdown list 7688 value: item.uuid, // Sets the input field to this value when selecting from the dropdown 7689 data: item.uuid, 7690 option: item 7691 } 7692 }); 7693 7694 myProfileDialog.setFields({ 7695 username: userProfile.username, 7696// amz_email: registeredUser.amz_email, 7697 uuid: userProfile.preferredIsy, 7698 uuidList: uuidList, 7699 voiceSelector: userProfile.voiceSelector, 7700 notificationmaint: userProfile.notificationMaintenance, 7701 notificationnews: userProfile.notificationNews 7702 }); 7703 7704 myProfileDialog.modal.dialog('open'); 7705 }) 7706 .fail(function() { 7707 console.log('Could not open My Profile') 7708 }) 7709 .done() 7710 7711} 7712 7713// Works with uuid, key, or none. 7714function openLicense(uuid, isyKey, redirectUrl) { 7715 var errorMessage; 7716 7717 ajaxJsonGet('/api/isys', $.param({domain: registeredUser.domain, subaccounts:true })) 7718 .done(function(result, status, jqXHR) { 7719 var isysFound; 7720 7721 if (uuid) { 7722 isysFound = result.data.filter(function(isyRecord) { 7723 return isyRecord.uuid === uuid; 7724 }) 7725 } else if (isyKey) { 7726 isysFound = result.data.filter(function(isyRecord) { 7727 return isyRecord.isyKey === isyKey; 7728 }) 7729 } else { 7730 if (!result.data.length) { 7731 errorMessage = 'You have no ISYs in your account.' 7732 7733 if (redirectUrl) { 7734 redirectWithError(redirectUrl, errorMessage) 7735 } else { 7736 errorMessageDialog(errorMessage) 7737 } 7738 } else if (result.data.length>1) { 7739 errorMessage = 'You have more than one ISY in your account.' 7740 7741 if (redirectUrl) { 7742 redirectWithError(redirectUrl, errorMessage) 7743 } else { 7744 errorMessageDialog(errorMessage + '<br/><br/>In order to see the license information for an ISY, please click on:<br/> Select tool | Information | ISY Portal Access License.', 'Choose ISY', 800) 7745 } 7746 } 7747 else { 7748 isysFound = result.data; 7749 } 7750 } 7751 7752 if (isysFound && !isysFound.length) { 7753 errorMessage = 'Requested ISY not found.'; 7754 7755 if (redirectUrl) { 7756 redirectWithError(redirectUrl, errorMessage) 7757 } else { 7758 errorMessageDialog(errorMessage + '<br/><br/>In order to see the license information for an ISY, please click on:<br/>Select tool | Information | ISY Portal Access License.', 'Choose ISY', 800) 7759 } 7760 } else { 7761 var pk = isysFound[0]._id.toString(); 7762 var uuidFound = isysFound[0].uuid; 7763 var online = isysFound[0].online; 7764 var isyDomain = isysFound[0].domain[0].id; 7765 var alias = isysFound[0].domain[0].alias; 7766 var registered = isysFound[0].domain[0].registered; 7767 var firmware = isysFound[0].isy && isysFound[0].isy.firmware 7768 7769 ipalDialog(uuidFound, alias, pk, isyDomain, false, online, registered, firmware, redirectUrl) 7770 } 7771 }) 7772 .fail(function(jqXHR, textStatus, errorThrown) { 7773 var errorMessage = "Can't retrieve ISY list<br>HTTP error " + jqXHR.status 7774 if (redirectUrl) { 7775 redirectWithError(redirectUrl, errorMessage) 7776 } else { 7777 errorMessageDialog(errorMessage, 'Error', 350) 7778 } 7779 }) 7780 .done(); 7781 7782/* 7783 if (registeredUser) { 7784 var uuid="uuid"
7785 var alias="alias" 7786 var pk="54bdadc5a64ada4ece8bd0d5" 7787 var isyDomain="$superadmin" 7788 7789 isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, pk: pk, domain: isyDomain }); 7790 isyAccessLicenseDialog.modal.dialog('open'); 7791 7792 ajaxJsonGet('/api/isy/ipal', $.param({id: pk, domain: isyDomain })) 7793 .done(function (data) { 7794 isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, info: data, pk: pk, domain: isyDomain }); 7795 }) 7796 .fail(function(jqXHR, textStatus, errorThrown) { 7797 if (jqXHR.status==503) { 7798 errorMessageDialog('ISY is not online') 7799 } 7800 else { 7801 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 7802 } 7803 }) 7804 .done(); 7805 } 7806*/ 7807} 7808 7809 7810// Plugin module purchase process 7811// Called from Polisy 7812function nodeserverPurchase(pid, nsid, redirectUrl, requestBeta, isTest) { 7813 7814 if (redirectUrl) { 7815 if (!pid) { 7816 redirectWithError(redirectUrl, 'System error: No pid provided') 7817 } else if (!uuidRegex.test(pid)) { 7818 redirectWithError(redirectUrl, 'System error: pid is not valid') 7819 } else if (!nsid) { 7820 redirectWithError(redirectUrl, 'System error: No nsid provided') 7821 } 7822 ajaxJsonGet('/api/nodeserver/purchase/' + pid + '/' + nsid, $.param({ beta: requestBeta })) 7823 .then(function(result, status, jqXHR) { 7824 if (result.data) { 7825 var nodeserverInfo = result.data.ns 7826 var licenseInfo = result.data.license 7827 7828 ajaxJsonGet('/api/isys', $.param({domain: registeredUser.superadmin ? '$superadmin' : registeredUser.domain, subaccounts:true })) 7829 .then(function(result, status, jqXHR) { 7830 var isysFound = result.data.filter(function(isyRecord) { 7831 return isyRecord.uuid === pid; 7832 }) 7833 7834 if (isysFound.length === 1) { 7835 var isyRecord = isysFound[0]; 7836 var alias = isyRecord.domain.length && isyRecord.domain[0].alias || '' 7837 nsPurchaseDialogStart(pid, alias, nsid, nodeserverInfo, licenseInfo, redirectUrl, requestBeta, isTest) 7838 } else { 7839 confirmationDialog('Device ' + pid + ' not found in your account.<br/><br/>Do you want to add it to your account?', 'Add to your account?') 7840 .then(function () { 7841 var alias = 'Added to account during plugin purchase' 7842 ajaxJsonPut('/api/isy/domain', $.param({ 7843 uuid:pid, 7844 alias: alias, 7845 domain: registeredUser.domain 7846 })) 7847 .then(function(data, status, jqXHR) { 7848 tableIsys.reload(); 7849 nsPurchaseDialogStart(pid, alias, nsid, nodeserverInfo, redirectUrl, requestBeta, isTest) 7850 }) 7851 .fail(function(jqXHR, textStatus, errorThrown) { 7852 redirectWithError(redirectUrl, 'pid could not be added to ISY Portal account.') 7853 }) 7854 .done(); 7855 }) 7856 .fail(function(jqXHR, textStatus, errorThrown) { 7857 redirectWithError(redirectUrl, 'pid is not in ISY Portal account.') 7858 }) 7859 .done(); 7860 } 7861 }) 7862 .fail(function(jqXHR, textStatus, errorThrown) { 7863 redirectWithError(redirectUrl, "Can't retrieve ISY list. HTTP error " + jqXHR.status) 7864 }) 7865 } else { 7866 redirectWithError(redirectUrl, 'nsid is not valid.') 7867 } 7868 }) 7869 .fail(function(jqXHR, textStatus, errorThrown) { 7870 redirectWithError(redirectUrl, 'Plugin info is not valid.') 7871 }) 7872 .done(); 7873 } else { 7874 // We can't redirect, so we just leave a message here 7875 errorMessageDialog('System error: redirect url is missing'); 7876 } 7877} 7878 7879// Open the nodeserver purchase dialog 7880// Can be from the purchase process, or eventual menu option 7881function nsPurchaseDialogStart(pid, alias, nsid, nodeserverInfo, licenseInfo, redirectUrl, requestBeta, isTest) { 7882 nsPurchaseDialog.setFields({ pid: pid, alias: alias, nsid: nsid, nodeserverInfo: nodeserverInfo, installed:licenseInfo, redirectUrl: redirectUrl, requestBeta: requestBeta, isTest: isTest}); 7883 nsPurchaseDialog.modal.dialog('open'); 7884} 7885 7886 7887function redirectWithError(redirectUrl, err) { 7888 var redirect = redirectUrl + '?' + 7889 $.param({ 7890 successful: false, 7891 error: err instanceof Error ? err.message : err 7892 }).replace(/\+/g, '%20'); 7893 7894 window.location.replace(redirect); 7895} 7896 7897 7898// opt = {tag:, message:, content:, uuid:, subTag: } 7899function sendLog(opt) {
7900 if (typeof opt.content!='string') { 7901 if (opt.content instanceof Error) { 7902 if (opt.content.stack && false) { 7903 opt.content = opt.content.stack; 7904 } 7905 else { 7906 opt.content = opt.content.toString(); 7907 } 7908 } 7909 else { 7910 opt.content = JSON.stringify(opt.content); 7911 } 7912 } 7913 7914 return ajaxJsonPost('/api/log', $.param( { 7915 tag: opt.tag, 7916 message: opt.message, 7917 content: opt.content, 7918 uuid: opt.uuid, 7919 subTag: opt.subTag } 7920 )) 7921} 7922 7923// ------------------- Dialogs ------------------------- 7924 7925// Domain Dialog initialization 7926var domainDialogInit = function () { 7927 var dialog, form, pk, 7928 sub = $('#domainsub'), 7929 parent = $('#domainparent'), 7930 parentreadonly = $('#domainparent-readonly'), 7931 type = $('#domaintype'), 7932 shortname = $('#domainshortname'), 7933 description = $('#domaindescription'), 7934 address = $('#domainaddress'), 7935 website = $('#domainwebsite'), 7936 contactname = $('#domaincontactname'), 7937 contactphone = $('#domaincontactphone'), 7938 billingroot = $('#domain_billing_root'), 7939 billingsub = $('#domain_billing_sub'), 7940 consent = $('#domain_consent'), 7941 isRootAccount, 7942 isUDI, 7943 editMode, 7944 shortNameEditable; 7945// contactemail = $('#domaincontactemail'), 7946 7947 // Used only to remove focus 7948 var allFields = $([]).add(sub).add(parent).add(parentreadonly).add(type).add(shortname).add(description).add(address).add(website).add(contactname).add(contactphone).add(billingroot).add(billingsub).add(consent),//.add(contactemail), 7949 tips = $('#domain_dialog .validateTips'); 7950 7951 function resetForm() { 7952 tips.text('Please enter account information.'); 7953 allFields.removeClass('fieldfocus'); 7954 form[0].reset(); 7955 pk=null; 7956 } 7957 7958 function setFields(obj) { 7959 resetForm(); 7960 7961 isRootAccount = !obj.sub; 7962 isUDI = obj.isUDI; 7963 editMode = obj.editMode; 7964 shortNameReadonly = !obj.isUDI && !obj.sub && obj.editMode 7965 7966 sub.prop('checked', obj.sub); 7967 sub.prop('disabled', obj.editMode); 7968 7969 shortname.prop('readonly', shortNameReadonly); 7970 7971 // Adjusts field visibility based on sub-account checkbox 7972 subChangeEvent(obj.sub, obj.isUDI); 7973 7974 if (obj.editMode) { 7975 parentreadonly.show(); 7976 parent.next().hide(); // next is the actual combobox 7977 7978 if (obj.parentname) { 7979 parentreadonly.val(obj.parentname); 7980 } 7981 7982 billingroot.prop('checked', obj.billingroot); 7983 billingsub.prop('checked', obj.billingsub); 7984 consent.prop('checked', obj.consent); 7985 } 7986 else { 7987 parent.next().show(); // next is the actual combobox 7988 parentreadonly.hide(); 7989 7990 // Enable the combobox with the parent id and the combobox source 7991 if (obj.parentList) { 7992 parent.combobox("option", "source", obj.parentList); 7993 } 7994 7995 if (obj.parent) { 7996 parent.combobox("option", "data", obj.parent); 7997 } 7998 7999 // Default values 8000 billingroot.prop('checked', true); 8001 billingsub.prop('checked', false); 8002 consent.prop('checked', false); 8003 } 8004 8005 if (obj.shortname) { 8006 shortname.val(obj.shortname); 8007 } 8008 8009 if (obj.description) { 8010 description.val(obj.description); 8011 } 8012 8013 if (obj.address) { 8014 address.val(obj.address); 8015 } 8016 8017 if (obj.website) { 8018 website.val(obj.website); 8019 } 8020 8021 if (obj.contactname) { 8022 contactname.val(obj.contactname); 8023 } 8024 8025 if (obj.contactphone) { 8026 contactphone.val(obj.contactphone); 8027 } 8028/* 8029 if (obj.contactemail) { 8030 contactemail.val(obj.contactemail); 8031 } 8032*/ 8033 if (obj.type) { 8034 $('#domaintype input[name=type]').val([obj.type]); 8035 } 8036 8037 if (obj.pk) { 8038 pk=obj.pk 8039 } 8040 } 8041 8042 function subChangeEvent(isSub, isUDI) { 8043 if (isUDI) { 8044 $('#domain_dialog .udionly').show(); 8045 8046 if (isSub) { 8047 $('#domain_dialog .subtrue').show(); 8048 $('#domain_dialog .subfalse').hide(); 8049 } 8050 else { 8051 $('#domain_dialog .subtrue').hide(); 8052 $('#domain_dialog .subfalse').show(); 8053 } 8054 } 8055 else { 8056 if (isSub) { 8057 $('#domain_dialog .subtrue:not(.udionly)').show(); 8058 $('#domain_dialog .subfalse').hide(); 8059 } 8060 else { 8061 $('#domain_dialog .subtrue').hide(); 8062 $('#domain_dialog .subfalse:not(.udionly)').show(); 8063 } 8064 8065 $('#domain_dialog .udionly').hide(); 8066 } 8067 } 8068 8069 function domainSubmit() {
8070 var valid = true; 8071 allFields.removeClass('fieldfocus'); 8072 parent.combobox("refresh"); // Makes sure the real input field is up to date 8073 valid = valid && (sub.prop('checked') || checkRadio(tips, 'input[name="type"]:checked', "Domain type must be selected")); 8074 valid = valid && (!sub.prop('checked') || checkNotEmpty(tips, parent, 'Parent') || isRootAccount || editMode || !isUDI); 8075 valid = valid && checkLength(tips, description, 'Description', 1, 50); 8076 8077 if (!shortNameReadonly) { 8078 valid = valid && checkLength(tips, shortname, 'Short name', 1, 20); 8079 } 8080 8081 // valid = valid && (contactemail.val()=='' || checkRegexp(tips, contactemail, emailRegexWithSpace, 'email address is not valid.' )); 8082 8083 if (valid) { 8084// contactemail.val(contactemail.val().trim()); 8085 var data = serializeFormWithCheckboxes(form.find('input, textarea')); 8086 8087 // if pk is there this will be an insert instead of an update, we need pk (this is the primary key) 8088 if (pk) { 8089 data += '&_id='+pk; 8090 } 8091 8092 if (!sub.prop('checked') && !billingroot.prop('checked') && !billingsub.prop('checked')) { 8093 confirmationDialog('Access License renewal notifications will not be sent.<br/>Are you sure?', 'No License Notifications?') 8094 .then(function () { 8095 save(data) 8096 }) 8097 .done(); 8098 } 8099 else { 8100 save(data) 8101 } 8102 } 8103 } 8104 8105 function save(data) { 8106 // If inserting, call ajaxJsonPut, else ajaxJsonPost 8107 (pk?ajaxJsonPost('/api/domain', data):ajaxJsonPut('/api/domain', data)) 8108 .then(function(domainRecord, status, jqXHR) { 8109 // If we added a children to our own domain, then add it to our children 8110 // This is a fix to the Select Tools not showing the options after adding a domain. 8111 setRegisteredUserAddChildrenDomain(domainRecord); 8112 8113 dialog.dialog('close'); 8114 tableDomains.reload(); 8115 }) 8116 .fail(function(jqXHR, textStatus, errorThrown) { 8117 if (jqXHR.status==420) { 8118 updateTips(tips, 'Domain already exists'); 8119 } 8120 else { 8121 updateTips(tips, 'HTTP error ' + jqXHR.status); 8122 } 8123 }) 8124 .done(); 8125 } 8126 8127 var isyOptions = { 8128 autoOpen: false, 8129 //height: 400, 8130 width: 750, 8131 modal: true, 8132 buttons: { 8133 'Save': domainSubmit, 8134 Cancel: function() { 8135 resetForm(); 8136 $(this).dialog('close'); 8137 } 8138 }, 8139 close: function() { 8140 resetForm(); 8141 }, 8142 open: function(event, ui) { 8143 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8144 } 8145 }; 8146 8147 // Initializes the dialog 8148 dialog = $('#domain_dialog').dialog(isyOptions); 8149 8150 parent.combobox({inputClass: ''}); 8151 8152 sub.change(function() { 8153 isRootAccount = !this.checked; 8154 subChangeEvent(this.checked, isUDI); 8155 }); 8156 8157 $('#consentHelp').click(function() { 8158 informationMessageDialog('Help us improve!<br/><br/> By enabling this option, you allow us to collect anonymized system configuration to enhance your experience. We do not collect personal or sensitive information. You can change this setting anytime.', 'Help', 800) 8159 }); 8160 8161 // Handles keyboad enter (submit event) 8162 form = dialog.find('form').submit(function(event) { 8163 event.preventDefault(); 8164 domainSubmit(); 8165 }); 8166 8167 return { modal: dialog, setFields: setFields, resetForm: resetForm }; 8168};
8169 8170// User Dialog initialization 8171var userDialogInit = function () { 8172 var dialog, form, pk, 8173 username = $('#user_name'), 8174 password = $('#user_password'), 8175 isy = $('#user_isy'), 8176 isDeveloper = $('#user_isDeveloper'), 8177 isReviewer = $('#user_isReviewer'), 8178 isEEAdmin = $('#user_isEEAdmin'), 8179 notificationmaint = $('#user_notificationmaint'), 8180 notificationnews = $('#user_notificationnews'), 8181 passwordconf = $('#user_passwordconf'), 8182 domainadmin = $('#user_domainadmin'), 8183 passwordchange = $('#user_passwordchange'), 8184 allFields = $([]).add(username).add(password).add(passwordconf).add(domainadmin).add(passwordchange).add(isDeveloper).add(isReviewer).add(isEEAdmin).add(notificationmaint).add(notificationnews), 8185 tips = $('#user_dialog .validateTips'), 8186 activeDomain, 8187 domainAdminBeforeChange; 8188 8189 function resetForm() { 8190 tips.text('Please enter user information.'); 8191 allFields.removeClass('fieldfocus'); 8192 form[0].reset(); 8193 pk=null; 8194 activeDomain=null; 8195 8196 username.prop('readonly', false); // If we set fields, we are in edit mode, email is not editable 8197 username.removeProp('tabindex'); 8198 8199 $('#user_email_invitation_span').show(); 8200 } 8201 8202 function setFields(obj) { 8203 resetForm(); 8204 username.val(obj.username); 8205 password.val(obj.password); 8206 passwordconf.val(obj.password); 8207 isy.val(obj.isy); 8208 8209 domainadmin.prop('checked', obj.domainadmin); 8210 passwordchange.prop('checked', obj.passwordchange); 8211 isDeveloper.prop('checked', obj.isDeveloper); 8212 isReviewer.prop('checked', obj.isReviewer); 8213 isEEAdmin.prop('checked', obj.isEEAdmin); 8214 notificationmaint.prop('checked', obj.notificationmaint); 8215 notificationnews.prop('checked', obj.notificationnews); 8216 domainAdminBeforeChange = obj.domainadmin; 8217 8218 isDeveloper.closest('.checkbox').toggle(registeredUser.superadmin); 8219 isReviewer.closest('.checkbox').toggle(registeredUser.superadmin); 8220 isEEAdmin.closest('.checkbox').toggle(registeredUser.superadmin); 8221 8222 if (obj.pk) { 8223 pk=obj.pk; 8224 username.prop('readonly', true); // If we set fields, we are in edit mode, email is not editable 8225 username.prop('tabindex', -1); 8226 $('#user_email_invitation_span').hide(); 8227 } 8228 else { 8229 pk=null; 8230 } 8231 } 8232 8233 function setDomain(id) { 8234 activeDomain = id; 8235 8236 if (id==='$superadmin') { 8237 // All users in $superadmin are implicitly domain admins 8238 $('#user_domainadmin_span').hide(); 8239 domainadmin.prop('checked', true); 8240 } 8241 else { 8242 $('#user_domainadmin_span').show(); 8243 } 8244 } 8245 8246 function userSubmit() { 8247 var valid = true; 8248 8249 allFields.removeClass('fieldfocus'); 8250 valid = valid && checkLength(tips, username, 'email', 6, 80); 8251 valid = valid && checkRegexp(tips, username, emailRegexWithSpace, 'email address is not valid.' ); 8252 valid = valid && checkPwd(tips, password); 8253 valid = valid && checkPwdEqual(tips, password, passwordconf, 'Password confirmation does not match.' ); 8254 valid = valid && (!isy.val().length || checkRegexpUuid(tips, isy, 'ISY must be in the format 00:21:b9:99:99:99')); 8255 8256 if (valid) { 8257 username.val(username.val().trim()); 8258 8259 var currentDomainAdmin = domainadmin.prop('checked'); 8260 8261 var data = serializeFormWithCheckboxes(form.find('input')); 8262 8263 if (activeDomain) { 8264 data += '&' + $.param({domain: activeDomain}) 8265 } 8266 8267 // We are removing domain admin rights to our own user profile 8268 if (pk == registeredUser.username && domainAdminBeforeChange && !currentDomainAdmin) { 8269 confirmationDialog('You are removing your own account admin rights. Are you sure?', 'Are you sure?') 8270 .then(function () { 8271 userSubmitProcess(data); 8272 }) 8273 } 8274 else { 8275 userSubmitProcess(data); 8276 } 8277 } 8278 } 8279 8280 function userSubmitProcess(data, isRetry) { 8281 // If we are a UDI admin, the activeDomain comes from the Domain roller (if adding) or the actual user record (if editing). 8282 8283 // if pk is there this will be an insert instead of an update (pk is only a flag in this case) 8284 (pk?ajaxJsonPost('/api/user', data):ajaxJsonPut('/api/user', data)) 8285 .then(function(data, status, jqXHR) { 8286 8287 // We are changing our own profile... 8288 if (pk == registeredUser.username) { 8289 if (password.val()=='$unchanged') { 8290 // If we did not change the password, we don't know it, so just update the profile with same creds 8291 setRegisteredUserProfile(data); 8292 } 8293 else { 8294 // Profile is updated along with creds 8295 // We do it in 2 steps, so we can keep domainInfo on registeredUser 8296 setRegisteredUserProfile(data); 8297 setRegisteredUserCredentials({ password: password.val() }); 8298 } 8299 } 8300 8301 tableUsers.reload(); 8302 dialog.dialog('close'); 8303 }) 8304 .fail(function(jqXHR, textStatus, errorThrown) { 8305 if (jqXHR.status==420) { 8306 updateTips(tips, 'User already exists (Possibly in another account)'); 8307 } 8308 else if (jqXHR.status==424) { 8309 updateTips(tips, 'Password is too easy. Please choose a more complex one.'); 8310 } 8311 else if (jqXHR.status==425) { // Custom server error message (Typically for uuid index error) 8312 updateTips(tips, jqXHR.responseText); 8313 } 8314 // NOTE: THIS IS NO LONGER RELEVANT. Push info is on the user profile 8315 // else if (jqXHR.status==426) { // Preferred ISY change warning 8316 // if (!isRetry) { 8317 // confirmationDialog(preferredIsyChangeWarning, 'Preferred ISY Change', 800) 8318 // .then(function () { 8319 // // Retry the request, bypass the preferred ISY change validation 8320 // userSubmitProcess(data+'&confirmed=true', true) 8321 // }) 8322 // .fail(function() { 8323 // // Clicked cancel 8324 // }) 8325 // .done(); 8326 // } 8327 // else { 8328 // errorMessageDialog('Error updating the preferred ISY', 'Error', 350) 8329 // } 8330 // } 8331 else { 8332 updateTips(tips, 'HTTP error ' + jqXHR.status); 8333 } 8334 }) 8335 .done(); 8336 } 8337 8338 var userOptions = { 8339 autoOpen: false, 8340 //height: 600, 8341 width: 400, 8342 modal: true, 8343 buttons: { 8344 'Save': userSubmit, 8345 Cancel: function() { 8346 resetForm(); 8347 $(this).dialog('close'); 8348 } 8349 }, 8350 close: function() { 8351 resetForm(); 8352 }, 8353 open: function(event, ui) { 8354 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8355 } 8356 }; 8357 8358 // Initializes the dialog 8359 dialog = $('#user_dialog').dialog(userOptions); 8360 8361 // Handles keyboad enter (submit event) 8362 form = dialog.find('form').submit(function(event) { 8363 event.preventDefault(); 8364 userSubmit(); 8365 }); 8366 8367 return { modal: dialog, setFields: setFields, setDomain: setDomain, resetForm: resetForm }; 8368};
8369 8370// ISY Dialog for uuid & alias 8371var isyClaimDialogInit = function () { 8372 var dialog, form, pk, 8373 uuid = $('#isy_claim_uuid'), 8374 alias = $('#isy_claim_alias'), 8375 allFields = $([]).add(uuid).add(alias), 8376 tips = $('#isy_claim_dialog .validateTips'), 8377 activeDomain; 8378 8379 function resetForm() { 8380 tips.text('Please enter ISY UUID and alias.'); 8381 allFields.removeClass('fieldfocus'); 8382 form[0].reset(); 8383 pk=null; 8384 activeDomain=null; 8385 8386 uuid.prop('readonly', false); 8387 uuid.removeProp('tabindex'); 8388 8389 } 8390 8391 function setFields(obj) { 8392 resetForm(); 8393 uuid.val(obj.uuid); 8394 alias.val(obj.alias); 8395 8396 if (obj.pk) { 8397 pk=obj.pk; 8398 uuid.prop('readonly', true); // If we set fields, we are in edit mode, uuid is not editable 8399 uuid.prop('tabindex', -1); 8400 } 8401 else { 8402 pk=null; 8403 } 8404 } 8405 8406 function setDomain(id) { 8407 activeDomain = id; 8408 } 8409 8410 function isySubmit() { 8411 var valid = true; 8412 8413 allFields.removeClass('fieldfocus'); 8414 valid = valid && checkRegexpUuid(tips, uuid, 'UUID is not valid'); 8415 8416 if (valid) { 8417 var data = form.find('input').serialize(); 8418 8419 data += '&' + $.param({id: pk}); // The API required the isy id (not the uuid) 8420 8421 // If we are a UDI admin, the activeDomain comes from the Domain roller. 8422 if (activeDomain) { 8423 data += '&' + $.param({domain: activeDomain}) 8424 } 8425 8426 // if pk is there this will be an update instead of an upsert (pk is only a flag in this case) 8427 (pk?ajaxJsonPost('/api/isy/domain', data):ajaxJsonPut('/api/isy/domain', data)) 8428 .then(function(data, status, jqXHR) { 8429 var firmware = data.isy && data.isy.firmware 8430 8431 tableIsys.reload(); 8432 8433 // If we are adding the ISY in the account (Not updating the alias) 8434 if (!pk) { 8435 // Automatically open the license dialog, if license is not active. 8436 ipalDialog(uuid.val(), alias.val(), data._id, activeDomain, true, data.online, 0, firmware, null) // registered status is necessarily 0. 8437 } 8438 8439 dialog.dialog('close'); // Needs to be done after, otherwise activeDomain is null! 8440 }) 8441 .fail(function(jqXHR, textStatus, errorThrown) { 8442 if (jqXHR.status==420) { 8443 updateTips(tips, 'ISY already entered'); 8444 } 8445 else { 8446 updateTips(tips, 'HTTP error ' + jqXHR.status); 8447 } 8448 }) 8449 .done(); 8450 } 8451 } 8452 8453 var isyOptions = { 8454 autoOpen: false, 8455 width: 400, 8456 modal: true, 8457 buttons: { 8458 'Save': isySubmit, 8459 Cancel: function() { 8460 resetForm(); 8461 $(this).dialog('close'); 8462 } 8463 }, 8464 close: function() { 8465 resetForm(); 8466 }, 8467 open: function(event, ui) { 8468 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8469 } 8470 }; 8471 8472 // Initializes the dialog 8473 dialog = $('#isy_claim_dialog').dialog(isyOptions); 8474 8475 // Handles keyboad enter (submit event) 8476 form = dialog.find('form').submit(function(event) { 8477 event.preventDefault(); 8478 isySubmit(); 8479 }); 8480 8481 return { modal: dialog, setFields: setFields, setDomain: setDomain, resetForm: resetForm }; 8482};
8483 8484// ISY Dialog for Notes 8485var isyNoteDialogInit = function () { 8486 var dialog, form, pk, 8487 note = $('#isy_note'), 8488 allFields = $([]).add(note), 8489 tips = $('#isy_note_dialog .validateTips'), 8490 isyObject, // Points to the ISY object obtained from get 8491 activeDomain; 8492 8493 function resetForm() { 8494 tips.text('Please enter a note.'); 8495 allFields.removeClass('fieldfocus'); 8496 form[0].reset(); 8497 pk=null; 8498 activeDomain=null; 8499 isyObject = null; 8500 8501 } 8502 8503 function setFields(obj) { 8504 resetForm(); 8505 $('#isy_note_uuid').text(obj.uuid); 8506 $('#isy_note_alias').text(obj.alias); 8507 isyObject = obj.handle; 8508 note.val(obj.note); 8509 pk=obj.pk 8510 } 8511 8512 function setDomain(id) { 8513 activeDomain = id; 8514 } 8515 8516 function isySubmit() { 8517 var valid = true; 8518 8519 allFields.removeClass('fieldfocus'); 8520 8521 if (valid) { 8522 var data = form.find('input, textarea, select').serialize(); 8523 8524 data += '&' + $.param({id: pk}); // The API requires the isy id (not the uuid) 8525 8526 // If we are a UDI admin, the activeDomain comes from the Domain roller. 8527 if (activeDomain) { 8528 data += '&' + $.param({domain: activeDomain}) 8529 } 8530 8531 ajaxJsonPost('/api/isy/domain', data) 8532 .then(function(data, status, jqXHR) { 8533 8534 // We need this because we don't refresh the isy table 8535 isyObject.domain[0].note = form.find('textarea').val(); // Update the copy we have in memory (coming from get /rest...) 8536 dialog.dialog('close'); 8537 }) 8538 .fail(function(jqXHR, textStatus, errorThrown) { 8539 updateTips(tips, 'HTTP error ' + jqXHR.status); 8540 }) 8541 .done(); 8542 } 8543 } 8544 8545 var isyOptions = { 8546 autoOpen: false, 8547 width: 700, 8548 modal: true, 8549 buttons: { 8550 'Save': isySubmit, 8551 Cancel: function() { 8552 resetForm(); 8553 $(this).dialog('close'); 8554 } 8555 }, 8556 close: function() { 8557 resetForm(); 8558 }, 8559 open: function(event, ui) { 8560 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8561 } 8562 }; 8563 8564 // Initializes the dialog 8565 dialog = $('#isy_note_dialog').dialog(isyOptions); 8566 8567 // Handles keyboad enter (submit event) 8568 form = dialog.find('form').submit(function(event) { 8569 event.preventDefault(); 8570 isySubmit(); 8571 }); 8572 8573 return { modal: dialog, setFields: setFields, setDomain: setDomain, resetForm: resetForm }; 8574};
8575 8576// ISY Dialog for Firmware upgrades 8577// var isyFirmwareDialogInit = function () { 8578// var dialog, pk, 8579// activeDomain, 8580// enableUpgradeMonitor, 8581// targetVersion; 8582// 8583// function updateDescription(description) { 8584// var urlRegex = /(https?:\/\/[\da-z\.-\?\-]+)/; 8585// var newdesc = description.replace(urlRegex, ' <a href="$1" target="_blank" class="editable_link">$1</a> '); 8586// $('#isy_fw_description').html(newdesc); 8587// } 8588// 8589// function setButtons(currentVersion, targetVersion) { 8590// switch (versionCompare(currentVersion, targetVersion)) { 8591// case -1: dialog.dialog('option', 'buttons', { 8592// 'Upgrade': function() { 8593// startUpgrade(targetVersion); 8594// }, 8595// Cancel: function() { 8596// $(this).dialog('close'); 8597// } 8598// }); 8599// break; 8600// 8601// case 1: dialog.dialog('option', 'buttons', { 8602// 'Downgrade': function() { 8603// $(this).dialog('close'); 8604// confirmationDialog('Are you sure you want to DOWNGRADE?', 'Are you sure?') 8605// .then(function() { 8606// startUpgrade(targetVersion); 8607// }) 8608// }, 8609// Cancel: function() { 8610// $(this).dialog('close'); 8611// } 8612// }); 8613// break; 8614// 8615// case 0: dialog.dialog('option', 'buttons', { 8616// Cancel: function() { 8617// $(this).dialog('close'); 8618// } 8619// }); 8620// break; 8621// } 8622// } 8623// 8624// function setFields(obj) { 8625// $('#isy_fw_uuid').text(obj.uuid); 8626// $('#isy_fw_alias').text(obj.alias); 8627// $('#isy_fw_current').text(obj.currentVersion); 8628// $('#firmwareupload_row').hide(); 8629// 8630// // Build the firmware select 8631// $('#isy_fw_select') 8632// .setTemplateElement('firmwareSelectorTemplate') 8633// .processTemplate(obj.availableVersions); 8634// 8635// // Firmware selector 8636// $('.firmwareSelect').selectmenu({ 8637// width:'10em', 8638// 8639// change: function(event, ui) { 8640// var index = $('#isy_fw_select .firmwareSelect')[0].selectedIndex; 8641// targetVersion = ui.item.value; 8642// updateDescription(obj.availableVersions[index].description); 8643// 8644// if (targetVersion == 'Beta version') { 8645// setButtons(obj.currentVersion, '999'); // e.g. 'Upgrade' button. 8646// $('#firmwareupload_row').show() 8647// } 8648// else { 8649// setButtons(obj.currentVersion, targetVersion); 8650// $('#firmwareupload_row').hide() 8651// } 8652// } 8653// }); 8654// 8655// // Set Initial version, description and buttons 8656// if (obj.availableVersions.length) { 8657// targetVersion = obj.availableVersions[0].version; 8658// updateDescription(obj.availableVersions[0].description); 8659// setButtons(obj.currentVersion, targetVersion) 8660// } 8661// 8662// pk=obj.pk 8663// } 8664// 8665// function setDomain(id) { 8666// activeDomain = id; 8667// } 8668// 8669// function startUpgrade(targetVersion) { 8670// var data = $.param({id: pk}); // The API requires the isy id (not the uuid) 8671// 8672// // If we are a UDI admin, the activeDomain comes from the Domain roller. 8673// if (activeDomain) { 8674// data += '&' + $.param({domain: activeDomain, version: targetVersion}) 8675// } 8676// 8677// dialog.dialog('close'); // Close main firmware upgrade dialog 8678// enableUpgradeMonitor=true; 8679// liveMessageDialog.setTitle('Firmware update'); 8680// liveMessageDialog.setMessage('Firmware upload in progress...'); 8681// liveMessageDialog.restartDefer() 8682// .fail(function () { 8683// enableUpgradeMonitor=false; // A cancel on the popup will stop the monitor 8684// }) 8685// .done(); 8686// 8687// liveMessageDialog.modal.dialog('open'); 8688// 8689// ajaxJsonPost('/api/isy/firmwareupgrade/', data) 8690// .then(function(data, status, jqXHR) { 8691// if (enableUpgradeMonitor) { 8692// liveMessageDialog.setMessage('ISY reboot in progress...'); 8693// monitorReboot(); 8694// } 8695// }) 8696// .fail(function(jqXHR, textStatus, errorThrown) { 8697// var promise; 8698// 8699// liveMessageDialog.modal.dialog('close'); 8700// 8701// if (jqXHR.status==425) { // Normally due to zip not found 8702// promise = errorMessageDialog(jqXHR.responseText, 'Error', 350); 8703// } 8704// else { 8705// promise = errorMessageDialog("Firmware upgrade FAILED!<br/>HTTP error " + jqXHR.status + "<br/>" + jqXHR.responseText, "Failure") 8706// } 8707// promise.then(function () { 8708// tableIsys.reload(); 8709// }) 8710// }) 8711// .done(); 8712// } 8713// 8714// function monitorReboot() { 8715// var data = $.param({id: pk}); // The API requires the isy id (not the uuid) 8716// 8717// // If we are a UDI admin, the activeDomain comes from the Domain roller. 8718// if (activeDomain) { 8719// data += '&' + $.param({domain: activeDomain}) 8720// } 8721//
8722// ajaxJsonPost('/api/isy/firmwareupgrade/status', data) 8723// .then(function (data) { 8724// switch (data.state) { 8725// case 'rebooting' : 8726// if (enableUpgradeMonitor) { 8727// setTimeout(monitorReboot, 3000); 8728// } 8729// break; 8730// 8731// case 'successful' : 8732// liveMessageDialog.modal.dialog('close'); 8733// 8734// informationMessageDialog("ISY firmware update Successful!", "Success") 8735// .then(function () { 8736// tableIsys.reload(); 8737// }); 8738// break; 8739// 8740// case 'timeout' : 8741// liveMessageDialog.modal.dialog('close'); 8742// 8743// errorMessageDialog("ISY firmware update timeout", "Failure") 8744// .then(function () { 8745// tableIsys.reload(); 8746// }); 8747// break; 8748// 8749// } 8750// }) 8751// .fail(function(jqXHR, textStatus, errorThrown) { 8752// liveMessageDialog.modal.dialog('close'); 8753// 8754// errorMessageDialog("Failure while checking firmware update status<br/>HTTP error " + jqXHR.status + "<br/>" + jqXHR.responseText, "Failure") 8755// .then(function () { 8756// tableIsys.reload(); 8757// }) 8758// }) 8759// .done(); 8760// } 8761// 8762// var isyOptions = { 8763// autoOpen: false, 8764// width: 700, 8765// modal: true, 8766// /* Buttons are set during setFields 8767// buttons: { 8768// 'Upgrade': isySubmit, 8769// Cancel: function() { 8770// $(this).dialog('close'); 8771// } 8772// }, 8773// */ 8774// open: function(event, ui) { 8775// dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8776// } 8777// }; 8778// 8779// // Initializes the dialog 8780// dialog = $('#isy_firmware_dialog').dialog(isyOptions); 8781// 8782// $('#isy_fw_select_label').tooltip(); 8783// 8784// return { modal: dialog, setFields: setFields, setDomain: setDomain }; 8785// }; 8786 8787// ISY Dialog for Contact 8788var isyContactDialogInit = function () { 8789 var dialog, form, pk, 8790 name = $('#contact_name'), 8791 phone = $('#contact_phone'), 8792 email = $('#contact_email'), 8793 address = $('#contact_address'), 8794 allFields = $([]).add(name).add(phone).add(email).add(address), 8795 tips = $('#isy_contact_dialog .validateTips'), 8796 isyObject, // Points to the ISY object obtained from get 8797 activeDomain; 8798 8799 function resetForm() { 8800 tips.text('Please enter ISY contact information.'); 8801 allFields.removeClass('fieldfocus'); 8802 form[0].reset(); 8803 pk=null; 8804 activeDomain=null; 8805 isyObject = null; 8806 } 8807 8808 function setFields(obj) { 8809 resetForm(); 8810 $('#isy_contact_uuid').text(obj.uuid); 8811 $('#isy_contact_alias').text(obj.alias); 8812 isyObject = obj.handle; 8813 name.val(obj.name); 8814 phone.val(obj.phone); 8815 email.val(obj.email); 8816 address.val(obj.address); 8817 pk=obj.pk 8818 } 8819 8820 function setDomain(id) { 8821 activeDomain = id; 8822 } 8823 8824 function isySubmit() { 8825 var valid = true; 8826 8827 allFields.removeClass('fieldfocus'); 8828 valid = valid && (email.val().length==0 || checkRegexp(tips, email, emailRegexWithSpace, 'email address is not valid.')); 8829 8830 if (valid) { 8831 email.val(email.val().trim()); 8832 8833 var data = form.find('input, textarea, select').serialize(); 8834 8835 data += '&' + $.param({id: pk}); // The API requires the isy id (not the uuid) 8836 8837 // If we are a UDI admin, the activeDomain comes from the Domain roller. 8838 if (activeDomain) { 8839 data += '&' + $.param({domain: activeDomain}) 8840 } 8841 8842 ajaxJsonPost('/api/isy/domain', data) 8843 .then(function(data, status, jqXHR) { 8844 // We need this because we don't refresh 8845 isyObject.name = name.val(); // Update the copy we have in memory (coming from get /rest...) 8846 isyObject.phone = phone.val(); 8847 isyObject.email = email.val(); 8848 isyObject.address = address.val(); 8849 dialog.dialog('close'); 8850 }) 8851 .fail(function(jqXHR, textStatus, errorThrown) { 8852 updateTips(tips, 'HTTP error ' + jqXHR.status); 8853 }) 8854 .done(); 8855 } 8856 } 8857 8858 var isyOptions = { 8859 autoOpen: false, 8860 width: 550, 8861 modal: true, 8862 buttons: { 8863 'Save': isySubmit, 8864 Cancel: function() { 8865 resetForm(); 8866 $(this).dialog('close'); 8867 } 8868 }, 8869 close: function() { 8870 resetForm(); 8871 }, 8872 open: function(event, ui) { 8873 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8874 } 8875 }; 8876 8877 // Initializes the dialog
8878 dialog = $('#isy_contact_dialog').dialog(isyOptions); 8879 8880 // Handles keyboad enter (submit event) 8881 form = dialog.find('form').submit(function(event) { 8882 event.preventDefault(); 8883 isySubmit(); 8884 }); 8885 8886 return { modal: dialog, setFields: setFields, setDomain: setDomain, resetForm: resetForm }; 8887}; 8888 8889// ISY Information 8890var isyInfoDialogInit = function () { 8891 var dialog; 8892 8893 function setFields(obj) { 8894 $('#isy_info_uuid').text(obj.uuid); 8895 $('#isy_info_alias').text(obj.alias); 8896 8897 if (obj.info) { 8898 var isyBaseUrl = location.origin + '/isy/' + encodeURIComponent(obj.info.isyKey) 8899 8900 $('#isy_info_product').text(obj.info.product && (obj.info.product.id + ' ' + obj.info.product.desc)); 8901 $('#isy_info_ipexternal').text(obj.info.ipExternal); 8902 $('#isy_info_ipinternal').text(obj.info.ipInternal); 8903 // $('#isy_info_cipher').text(obj.info.cipher); 8904 $('#isy_info_multi').text( 8905 obj.info.multiConnectionSupport ? 8906 'eisy-ui service (' + (obj.info.isWs ? 'Multi Connections' : obj.info.multiConnectionCount + ' connection') + ')' : 8907 'ISY'); 8908 $('#isy_info_admin_console').text(isyBaseUrl+'/desc') 8909 $('#isy_info_base_url').text(isyBaseUrl); 8910 8911 // Build the firmware select 8912 $('#isyfeatures_placeholder') 8913 .setTemplateElement('isyFeaturesTemplate') 8914 .processTemplate(obj.info.features); 8915 } 8916 else { 8917 $('#isy_info_product').text(); 8918 $('#isy_info_ipexternal').text(''); 8919 $('#isy_info_ipinternal').text(''); 8920 // $('#isy_info_cipher').text(''); 8921 $('#isyfeatures_placeholder').text(''); 8922 } 8923 } 8924 8925 $('#multiConnectionsHelp').click(function() { 8926 informationMessageDialog('Multi-Connections is an upcoming feature of eISY allowing multiple connections to portal which will increase communication speed.', 'Help', 500) 8927 }); 8928 8929 8930 var isyOptions = { 8931 autoOpen: false, 8932 width: 1060, 8933 modal: true, 8934 buttons: { 8935 'Close': function() { 8936 $(this).dialog('close'); 8937 } 8938 }, 8939 open: function(event, ui) { 8940 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 8941 } 8942 }; 8943 8944 // Initializes the dialog 8945 dialog = $('#isy_info_dialog').dialog(isyOptions); 8946 // Tooltip removed - Causing issues as the scope is too wide 8947 //$('[title]').tooltip() 8948 8949 if (!Clipboard.isSupported()) { 8950 $('#isy_info_dialog .copy_clip').hide(); 8951 } 8952 8953 var clipboard = new Clipboard('#isy_info_dialog .copy_clip'); 8954 8955 clipboard.on('success', function(e) { 8956 var target = $(e.trigger).attr('data-clipboard-target'); 8957 if($(target).tooltip('option', 'disabled')) { 8958 $(target).tooltip('option', {disabled: false, content: 'Copied'}).tooltip('open') 8959 } 8960 setTimeout(function(){ 8961 e.clearSelection(); 8962 $(target).tooltip('option', {disabled: true}).tooltip('close') 8963 }, 800); 8964 }); 8965 8966 clipboard.on('error', function(e) { 8967 var target = $(e.trigger).attr('data-clipboard-target'); 8968 if ($(target).tooltip('option', 'disabled')) { 8969 $(target).tooltip('option', { disabled: false, content: 'COPY FAILED' }).tooltip('open') 8970 } 8971 setTimeout(function () { 8972 e.clearSelection(); 8973 $(target).tooltip('option', { disabled: true }).tooltip('close') 8974 }, 2000); 8975 }) 8976 8977 // $('#isy_info_dialog .copy_clip').button() // { icons: {primary: 'ui-icon-copy'}, text: false} 8978 8979 return { modal: dialog, setFields: setFields }; 8980}; 8981 8982 8983// ISY Portal Access License main dialog 8984var isyAccessLicenseDialogInit = function () { 8985 var dialog, 8986 pk, 8987 isyDomain, 8988 uuid, 8989 alias, 8990 lookupRenewOptions, 8991 licenseActive, 8992 licenseInstalled, 8993 online, 8994 registered, // Registered status of the device. (0=pending approval, 1=approved, -1=revoked) 8995 info, // Setfield obj.info 8996 redirectUrl, 8997 firmware, 8998 purchaseWasSuccessful 8999 9000 function init() { 9001 redirectUrl = null; 9002 purchaseWasSuccessful = null; // When we start, we don't have a purchase tried yet 9003 } 9004 9005 function setFields(obj) { 9006 firmware = obj.firmware; 9007 $('#isy_license_uuid').text(obj.uuid); 9008 $('#isy_license_alias').text(obj.alias); 9009 9010 if (obj.info) { 9011 var licenseExpiryHtml=''; 9012 var licenseActiveHtml=''; 9013 var buttons; 9014 9015 // Keep a copy for when we go to the renew dialog 9016 uuid = obj.uuid; 9017 alias = obj.alias; 9018 info = obj.info; 9019 pk = obj.pk; 9020 online = obj.online; 9021 registered = obj.registered; 9022 isyDomain = obj.domain; 9023
9024// console.log('domain %s pk %s alias %s uuid %s registered %s', isyDomain, pk, alias, uuid, registered) 9025 if (!obj.info.webStore.module || !obj.info.webStore.expiry) { 9026 // There is no license yet 9027 if (!obj.info.webStore.module) { 9028 dialog.dialog('option', 'buttons', [trialButton, buyButton, closeButton]); // Set the dialog buttons 9029 } else { 9030 dialog.dialog('option', 'buttons', [buyButton, closeButton]); // Set the dialog buttons 9031 } 9032 9033 licenseActiveHtml = '<span title="ISY Portal License has to be purchased.">No</span>'; 9034 licenseExpiryHtml = '<span title="ISY Portal License has to be purchased.">No active license</span>'; 9035 licenseActive = false; 9036 licenseInstalled = false; 9037 } else { 9038 // Renewals are eligibles only when we have an initial or renewal already in place, and the module is still active 9039 var enableRenew = obj.info.webStore.type>=1 && obj.info.webStore.module && obj.info.webStore.module.active; 9040 9041 buttons = [enableRenew?renewButton:buyButton, closeButton]; 9042 9043 if (registeredUser.superadmin) { 9044 buttons.unshift(revokeButton) 9045 } 9046 9047 dialog.dialog('option', 'buttons', buttons); // Set the dialog buttons 9048 9049 var expiryDate = new Date(obj.info.webStore.expiry); 9050 9051 // Set access license expired flag 9052 var expired = expiryDate.valueOf() < new Date().valueOf(); 9053 9054 // Set access license will be expired soon flag 9055 var soon = new Date(); 9056 soon.setDate(soon.getDate() + accessLicenseExpirationSoonDays); 9057 var soonExpired = !expired && expiryDate.valueOf() < soon.valueOf(); 9058 9059 licenseExpiryHtml = '<span>' + dateFormatNoError(expiryDate, "yyyy-mm-dd") +'</span>' + 9060 (expired?'<span class="red expired" title="ISY Portal License is expired.">expired</span>':'') + 9061 (soonExpired?'<span class="warning expired" title="ISY Portal License is expiring soon."><span class="ui-icon ui-icon-alert"></span></span>':''); 9062 9063 licenseActive = obj.info.webStore.module.active && !expired; 9064 licenseInstalled = !!obj.info.webStore.module.installed; 9065 9066 licenseActiveHtml = '<span>' + (licenseActive?'Yes':'No') + '</span>' + 9067 (licenseActive && !licenseInstalled?'<span class="red expired" title="Please open the admin console and click on Help | Manage Modules to install the license.">(License is not installed)</span>':'') 9068 } 9069 9070 // if (!obj.info.webStore.uuidValid) { 9071 // $('#isy_license_uuid').html(obj.uuid + '<span class="warning" title="This UUID does not appear to be valid."><span class="ui-icon ui-icon-alert"></span></span>').tooltip(); 9072 // } 9073 9074 $('#isy_license_active').html(licenseActiveHtml).tooltip(); 9075 $('#isy_license_expiry').html(licenseExpiryHtml).tooltip(); 9076 9077 var history = []; 9078 9079 if (obj.info.webStore.module) { 9080 history.push(obj.info.webStore.module); 9081 history = history.concat(obj.info.webStore.renewals) 9082 } 9083 9084 history = history.map(function(entry) { 9085 if (!entry.start_data && entry.install_date) { 9086 entry.start_date = entry.dpurch; 9087 } 9088 9089 return { 9090 // We convert all ISO string to a date back then to a local date 9091 dpurch: entry.txid === 'trial' ? 'Free trial' : dateFormatNoError(new Date(entry.dpurch), "yyyy-mm-dd"), 9092 start_date: dateFormatNoError(new Date(entry.start_date), "yyyy-mm-dd"), 9093 expiry: entry.expiry==null?'License inactive':dateFormatNoError(new Date(entry.expiry), "yyyy-mm-dd"), 9094 } 9095 }); 9096 9097 if (history.length) { 9098 $('#isy_license_history').show(); 9099 9100 // Build the History 9101 $('#isy_license_history_placeholder') 9102 .setTemplateElement('ipalHistoryTemplate') 9103 .processTemplate(history); 9104 } 9105 else { 9106 $('#isy_license_history').hide(); 9107 } 9108 } 9109 else { 9110 $('#isy_license_expiry').text(''); 9111 $('#isy_license_active').text(''); 9112 $('#isy_license_history_placeholder').empty(); 9113 $('#isy_license_history').hide(); 9114 dialog.dialog('option', 'buttons', [closeButton]); // Set the dialog buttons 9115 } 9116 9117 // Pre-fetch list of options 9118 lookupRenewOptions = ajaxJsonGet('/api/ipal/renewal/options') 9119 .fail(function(jqXHR, textStatus, errorThrown) { 9120 errorMessageDialog('Error retrieving renewal options: HTTP error ' + jqXHR.status, 'Error', 350) 9121 }) 9122 } 9123 9124 // We set it outside of setfields because we also use setfields from the children renew dialog, and we don't know there if we have a redirectUrl in this dialog 9125 function setRedirectUrl(u) { 9126 redirectUrl = u 9127 } 9128
9129 // This is set by the purchasing dialog (isyAccessLicenseRenewDialog) 9130 // We use it to know if we return success or failure on closing 9131 function setPurchaseSuccess(result) { 9132 purchaseWasSuccessful = result 9133 } 9134 // function uuidNotValidError() { 9135 // var message = 'The UUID ' + uuid + ' does not appear to be valid.<br><br>' + 9136 // 'Please contact [email protected].'; 9137 // 9138 // errorMessageDialog(message, 'Error', 800); 9139 // } 9140 9141 var mobilincWarningMessage = 'Please note that the installation of the ISY Portal module will deactivate the MobiLinc module. ' + 9142 'You will be able to configure the MobiLinc app to connect to your ISY through ISY Portal by following these instructions:<br><br> ' + 9143 '<a href="https://wiki.universal-devices.com/index.php?title=ISY_Portal_MobiLinc_Configuration" class="url80 editable_link" target="_blank">https://wiki.universal-devices.com/index.php?title=ISY_Portal_MobiLinc_Configuration</a><br><br>' + 9144 'Continue?'; 9145 9146 function adminRevoke() { 9147 var message = 'This will revoke the last transaction. This will set the last transaction expiry to today, or completely remove the transaction if not started. ' + 9148 'If after removing the last transaction the license is expired, the module active flag will be set to false, the module will be removed from ISY, and it will be rebooted.<br><br>' + 9149 'Continue?'; 9150 9151 confirmationDialog(message, 'Revoke last transaction', 800) 9152 .then(function () { 9153 ajaxJsonDelete('/api/isy/ipal/payment/revoke', $.param({ 9154 id: pk, // ISY ID 9155 domain: isyDomain, 9156 })) 9157 .then(function(data, status, jqXHR) { 9158 var message = 'ISY Portal Access License has been revoked successfully.'; 9159 9160 informationMessageDialog(message) 9161 .done(function() { 9162 // Refresh main access licence dialog with the new license info 9163 isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, info: data, pk: pk, domain: isyDomain }); 9164 }) 9165 }) 9166 .fail(function(jqXHR, textStatus, errorThrown) { 9167 var message = 'Error trying to revoke the last transaction.<br><br>' + 9168 (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':''); 9169 9170 errorMessageDialog(message, 'Error', 800); 9171 }) 9172 .done(); 9173 }) 9174 .done(); 9175 } 9176 9177 function redirectSuccess() { 9178 if (redirectUrl) { 9179 window.location.replace(redirectUrl + '?successful=true'); 9180 } 9181 } 9182 9183 function redirectError(errMessage) { 9184 if (redirectUrl) { 9185 var redirect = redirectUrl + '?successful=false' + (errMessage && '&error=' + errMessage) 9186 window.location.replace(redirect); 9187 } 9188 } 9189 9190 function trial() { 9191 9192 if (!('productId' in info) && registered==='0') { 9193 errorMessageDialog('This unit is not registeded.<br/>Please use the Admin Console, go to the configuration tab and click on "Approve" to allow access from this account.', null, 750) 9194 } else { 9195 // if (info.webStore.uuidValid || true) { 9196 var message = 'Do you want to enable the ISY Portal free trial?'; 9197 9198 // Give warning if the Mobilinc app will be deactivated 9199 if (info.webStore.mobilincModule && (info.webStore.mobilincModule.active || info.webStore.mobilincModule.installed)) { 9200 message += '<br><br>' + mobilincWarningMessage 9201 } 9202 9203 confirmationDialog(message, 'Enable trial?', 800) 9204 .then(function () { 9205 ajaxJsonPost('/api/isy/ipal/trial', $.param({ 9206 id: pk, // ISY ID 9207 domain: isyDomain 9208 })) 9209 .then(function(data, status, jqXHR) { 9210 var message = 'ISY Portal free trial access license has been added successfully.' + 9211 (data.webStore.adminConsoleWarning?'<br><br>Please open the admin console and click on Help | Manage Modules to install the license.':''); 9212 9213 informationMessageDialog(message) 9214 .done(function() { 9215 // When we close the dialog, we will return success (if redirect is used) 9216 setPurchaseSuccess(true) 9217 9218 // Refresh main access licence dialog with the new license info 9219 isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, info: data, pk: pk, domain: isyDomain }); 9220 9221 //Refresh the table behind 9222 tableIsys.reload(); 9223 }) 9224 }) 9225 .fail(function(jqXHR, textStatus, errorThrown) { 9226 var message = 'There was an error processing the free trial.<br><br>' + 9227 (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':''); 9228 9229 errorMessageDialog(message, 'Error', 800); 9230 }) 9231 .done(); 9232 }) 9233 .done(); 9234 // } 9235 // else { 9236 // uuidNotValidError() 9237 // } 9238 } 9239 } 9240 9241 9242 function buy() { 9243 if (info.webStore.module || getIsPolisyOrEisy(info.productId)) { 9244 // Give warning if the Mobilinc app will be deactivated 9245 if (info.webStore.mobilincModule && info.webStore.mobilincModule.active) { 9246 confirmationDialog(mobilincWarningMessage, 'MobiLinc warning', 800) 9247 .then(function () { 9248 openRenewalWindow(); 9249 }) 9250 .done(); 9251 } 9252 else { 9253 openRenewalWindow(); 9254 } 9255 } else if (!('productId' in info) && registered==='0') { 9256 errorMessageDialog('This unit is not registeded.<br/>Please use the Admin Console, go to the configuration tab and click on "Approve" to allow access from this account.', null, 750) 9257 } else { 9258 errorMessageDialog('This unit is no longer supported.<br/>A license can\'t be added.') 9259 } 9260 } 9261 9262 function openRenewalWindow() { 9263 var isLegacy = !getIsPolisyOrEisy(info.productId); 9264 9265 // Renewals are eligibles only when we have an initial or renewal already in place, and the module is still active 9266 var showRenew = info.webStore.type>=1 && info.webStore.module && info.webStore.module.active; 9267 9268 lookupRenewOptions 9269 .then(function(renewOptions) { 9270 renewOptions = renewOptions.data 9271 .filter(function (opt) { return (!!opt.islegacy) === isLegacy }) // renewOption.islegacy can be undefined, which means false 9272 .filter(function (opt) { 9273 return (showRenew && opt.isrenewal) || (!showRenew && !opt.isrenewal) // If we have an initial term or renewal which is active, we show renewals 9274 }) 9275 9276 if (renewOptions.length) {
9277 isyAccessLicenseRenewDialog.setFields({ 9278 uuid: uuid, 9279 alias: alias, 9280 pk: pk, 9281 domain: isyDomain, 9282 info: info, 9283 renewOptions: renewOptions, 9284 isLegacy: isLegacy 9285 }); 9286 isyAccessLicenseRenewDialog.modal.dialog('open'); 9287 } else { 9288 errorMessageDialog('There are no purchase options available for your unit as it is discontinued.', 'Error', 550) 9289 } 9290 }) 9291 .fail(function(jqXHR, textStatus, errorThrown) { 9292 errorMessageDialog('Error retrieving renewal options: HTTP error ' + jqXHR.status, 'Error', 350) 9293 }) 9294 } 9295 9296 var revokeButton = { 9297 text: 'Revoke last transaction', 9298 click: adminRevoke 9299 }; 9300 9301 var trialButton = { 9302 text: 'Free trial', 9303 click: trial 9304 }; 9305 9306 var buyButton = { 9307 text: 'Buy License', 9308 click: buy 9309 }; 9310 9311 var renewButton = { 9312 text: 'Renew License', 9313 click: buy // Even if it's a "renewal", we check if the mobilinc module is active. ISY Portal module might exist but be deactivated while mobilinc is active! 9314 }; 9315 9316 var closeButton = { 9317 text: 'Close', 9318 click: function() { 9319 $(this).dialog('close'); // Will trigger the close event, and redirect if required 9320 } 9321 }; 9322 9323 var isyOptions = { 9324 autoOpen: false, 9325 width: 700, 9326 modal: true, 9327 // buttons defined dynamically 9328 open: function(event, ui) { 9329 dialog.dialog('option', 'position', { my: 'center', at: 'center', of: window, collision: 'fit' }); 9330 }, 9331 close: function() { 9332 var silentSuccess=true; 9333 var freeTrialAvailable = (info.webStore.module == null); 9334 9335 if (redirectUrl) { 9336 if (purchaseWasSuccessful) { 9337 redirectSuccess() 9338 } else { 9339 redirectError('Cancelled'); 9340 } 9341 } else if (licenseActive) { 9342 // Don't show instructions if we don't even have a license. It was obvious in the popup. 9343 9344 accessTroubleshootingMessage({ 9345 freeTrialAvailable: freeTrialAvailable, 9346 licenseActive: licenseActive, 9347 licenseInstalled: licenseInstalled, 9348 online: online, 9349 registered: registered, 9350 firmware: firmware 9351 }, silentSuccess) 9352 } 9353 } 9354 }; 9355 9356 // Initializes the dialog 9357 dialog = $('#isy_license_dialog').dialog(isyOptions); 9358 9359 return { 9360 modal: dialog, 9361 init: init, 9362 setFields: setFields, 9363 setRedirectUrl: setRedirectUrl, 9364 setPurchaseSuccess: setPurchaseSuccess 9365 }; 9366}; 9367 9368// ISY Portal Access License main dialog 9369var isyAccessLicenseRenewDialogInit = function () { 9370 var paypalButtonTarget = '#isy_license_paypal_button', 9371 dialog, 9372 pk, 9373 uuid, // Kept to refresh isyAccessLicenseDialog 9374 alias, // Kept to refresh isyAccessLicenseDialog 9375 isyDomain, 9376 renewOptions, 9377 apiIsyIpalResult, 9378 expiryDate, 9379 renewalStartDate, 9380 ppCurrentEnv, 9381 isLegacy; 9382 9383 function setFields(obj) { 9384 pk = obj.pk; 9385 uuid = obj.uuid; 9386 alias = obj.alias; 9387 isyDomain = obj.domain; 9388 isLegacy = obj.isLegacy; 9389 renewOptions = obj.renewOptions; 9390 apiIsyIpalResult = obj.info; 9391 ppCurrentEnv = serverConfig.ppEnv; // Reset to default env specified on server 9392 9393 $('#isy_license_renew_uuid').text(obj.uuid); 9394 $('#isy_license_renew_alias').text(obj.alias); 9395 9396 var licenseExpiryHtml=''; 9397 var licenseActiveHtml=''; 9398 9399 if (!obj.info.webStore.module) { 9400 licenseExpiryHtml = '<span title="ISY Portal License has to be purchased.">No active license</span>'; 9401 licenseActiveHtml='<span title="ISY Portal License has to be purchased.">No</span>' 9402 } 9403 else { 9404 if (obj.info.webStore.expiry==null) { 9405 licenseExpiryHtml = '<span>No expiration</span>' 9406 } 9407 else if (obj.info.webStore.expiry) { 9408 var expiryDate = new Date(obj.info.webStore.expiry); 9409 9410 // Set access license expired flag 9411 var expired = expiryDate.valueOf() < new Date().valueOf(); 9412 9413 // Set access license will be expired soon flag 9414 var soon = new Date(); 9415 soon.setDate(soon.getDate() + accessLicenseExpirationSoonDays); 9416 var soonExpired = !expired && expiryDate.valueOf() < soon.valueOf(); 9417 9418 licenseExpiryHtml = '<span>' + dateFormatNoError(expiryDate, "yyyy-mm-dd") +'</span>' + 9419 (expired?'<span class="red expired" title="ISY Portal License is expired.">expired</span>':'') + 9420 (soonExpired?'<span class="warning expired" title="ISY Portal License is expiring soon."><span class="ui-icon ui-icon-alert"></span></span>':'') 9421 } 9422 9423 var licenseActive = obj.info.webStore.module.active && !expired; 9424 var licenseInstalled = obj.info.webStore.module.installed; 9425 9426 licenseActiveHtml = '<span>' + (licenseActive?'Yes':'No') + '</span>' + 9427 (licenseActive && !licenseInstalled?'<span class="red expired" title="Please open the admin console and click on Help | Manage Modules to install the license.">(License is not installed)</span>':'') 9428 } 9429 9430 // if (!obj.info.webStore.uuidValid) { 9431 // $('#isy_license_renew_uuid').html(obj.uuid + '<span class="warning" title="This UUID does not appear to be valid."><span class="ui-icon ui-icon-alert"></span></span>').tooltip(); 9432 // } 9433 9434 $('#isy_license_renew_active').html(licenseActiveHtml).tooltip(); 9435 $('#isy_license_renew_expiry').html(licenseExpiryHtml).tooltip(); 9436 9437 renewalStartDate = new Date(apiIsyIpalResult.renewalStartDate); 9438 9439 var len = renewOptions.reduce(function(summary, element) { 9440 return Math.max(summary, element.description.length) 9441 }, 0); 9442 9443 renewOptions.sort(function(a, b) { 9444 // if (!a.duration) { return 1; } 9445 // if (!b.duration) { return -1; } 9446 return a.duration > b.duration; 9447 }); 9448 9449 var selectOptions = renewOptions.map(function(option) { 9450 return { 9451 value: option._id, 9452 description: option.description 9453 } 9454 }); 9455 9456 var em = (len*0.55)+5; // Formula found by trial and error 9457 9458 buildSelect(selectOptions, '#isy_license_renew_selector', ''+em+'em', function(event, ui) { 9459 displayPaymentOptions(ui.item.value) 9460 }); 9461 9462 if (selectOptions.length>=1) { 9463 displayPaymentOptions(selectOptions[0].value) 9464 } else { 9465 displayPaymentOptions(null); 9466 } 9467 } 9468 9469 // Called when the renewal option changes. 9470 function displayPaymentOptions(id) { 9471 var options = renewOptions.filter(function(option) { 9472 return option._id===id; 9473 }); 9474 9475 var option = options.length?options[0]:null; 9476 9477 if (option) { 9478 var newExpiry; 9479 var newExpiryStr; 9480 9481 if (option.duration) { 9482 newExpiry = new Date(renewalStartDate); 9483 newExpiry.setFullYear(newExpiry.getFullYear() + option.duration); 9484 newExpiryStr = dateFormatNoError(newExpiry, "yyyy-mm-dd") 9485 } else { 9486 newExpiryStr = ''; 9487 } 9488 9489 var startDate = dateFormatNoError(renewalStartDate, "yyyy-mm-dd"); 9490 $('#isy_license_renew_start').html(startDate); 9491 $('#isy_license_renew_end').html(newExpiryStr); //.tooltip(); 9492 $('#isy_license_renew_price').text(option.price.toFixed(2)+'$'); 9493 9494 var displayLegacyText = isLegacy && option.legacytext && option.legacytext.length>0 9495 $('#isy_license_renew_legacytext_display').toggle(displayLegacyText) 9496 $('#isy_license_renew_legacytext').html(option.legacytext) 9497 9498 var globalDescription = 'ISY Portal License' + (option.isrenewal?' Renewal':'') + ' for uuid:' + uuid + ' from ' + startDate + ' to ' + newExpiryStr; 9499 updatePaypalButton(option, globalDescription); 9500 } 9501 else { 9502 $('#isy_license_renew_start').empty(); 9503 $('#isy_license_renew_end').empty(); 9504 $('#isy_license_renew_price').empty(); 9505 } 9506 } 9507 9508 // We pass the selector option 9509 function updatePaypalButton(option, globalDescription) { 9510 $('#isy_license_paypal_sandbox_indicator').text(ppCurrentEnv==='sandbox'?'[SANDBOX ENVIRONMENT]':''); 9511 9512 $(paypalButtonTarget).empty(); // Each time we change the option, we rebuild the button 9513 9514 paypal.Buttons({ 9515 // LEGACY PAYPAL WEB UI 9516 // createOrder: function(data, actions) { 9517 // 9518 // // option example: 9519 // // description: "1 Year renewal" 9520 // // duration: 1 9521 // // isrenewal: true 9522 // // part: "LIC21075-1Y-RNW" 9523 // // price: 0.01 9524 // 9525 // sendLog({ 9526 // tag: 'ipal', 9527 // subTag: 'ppcreate', 9528 // uuid: uuid, 9529 // message: 'Paypal Create', 9530 // content: { 9531 // option: option, 9532 // sku: option.part 9533 // } 9534 // }); 9535 // 9536 // var orderRequest = { 9537 // intent: 'CAPTURE', 9538 // purchase_units: [{ 9539 // // custom_id: option.part, 9540 // description: option.part, // Looks like this is not used 9541 // amount: { 9542 // currency_code: 'USD', 9543 // value: option.price, 9544 // breakdown: { 9545 // item_total: { 9546 // currency_code: 'USD', 9547 // value: option.price 9548 // } 9549 // } 9550 // }, 9551 // items: [{ 9552 // sku: option.part, // Second line on the receipt: "numéro de l'object <sku>" 9553 // name: 'ISY Portal License - ' + uuid, // First line on the receipt 9554 // description: (option.description + ' uuid: ' + uuid).substring(0,127), // Does not show on the receipt 9555 // unit_amount: { 9556 // currency_code: 'USD', 9557 // value: option.price 9558 // }, 9559 // quantity: 1 9560 // }] 9561 // }], 9562 // application_context: { 9563 // shipping_preference: 'NO_SHIPPING' 9564 // } 9565 // } 9566 // 9567 // // console.log('order', orderRequest) 9568 // 9569 // return actions.order.create(orderRequest) 9570 // .then(function (orderId) { 9571 // return sendLog({ 9572 // tag: 'ipal', 9573 // subTag: 'ppcreated', 9574 // uuid: uuid, 9575 // message: 'Paypal orderId created', 9576 // content: { 9577 // option: option, 9578 // sku: option.part, 9579 // orderId: orderId 9580 // } 9581 // }) 9582 // .then(function () { 9583 // return orderId 9584 // }) 9585 // }) 9586 // }, 9587 // 9588 // // Finalize the transaction after payer approval 9589 // onApprove: function(data, actions) { 9590 // return actions.order.capture().then(function(orderData) {
9591 // // Successful capture! 9592 // // console.log('Capture result', orderData, JSON.stringify(orderData, null, 2)); 9593 // var orderID = orderData.id 9594 // // var payerID = orderData.payer.payer_id; 9595 // var transaction = orderData.purchase_units[0].payments.captures[0]; 9596 // var paymentID = transaction.id; // Need this in case of error 9597 // 9598 // if (registeredUser) { 9599 // sendLog({ 9600 // tag: 'ipal', 9601 // subTag: 'ppapprove', 9602 // uuid: uuid, 9603 // message: 'Paypal Approve', 9604 // content: { 9605 // data: orderData, 9606 // } 9607 // }) 9608 // .then(function () { 9609 // return ajaxJsonPost('/api/isy/ipal/payment/execute', $.param({ 9610 // id: pk, // ISY ID 9611 // domain: isyDomain, 9612 // optionID: option._id, // We use it to verify the transaction amount has not been tampered with 9613 // orderID: orderID, 9614 // paymentID: paymentID, 9615 // env: ppCurrentEnv 9616 // })) 9617 // }) 9618 // .then(function(data, status, jqXHR) { 9619 // var message = 'ISY Portal Access License purchase has been completed successfully.' + 9620 // (data.webStore.adminConsoleWarning?'<br><br>Please open the admin console and click on Help | Manage Modules to install the license.':''); 9621 // 9622 // informationMessageDialog(message) 9623 // .done(function() { 9624 // // When we close isyAccessLicenseDialog, we will return success (if redirect is used) 9625 // isyAccessLicenseDialog.setPurchaseSuccess(true) 9626 // 9627 // // Refresh main access licence dialog with the new license info 9628 // isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, info: data, pk: pk, domain: isyDomain }); 9629 // 9630 // //Refresh the table behind 9631 // tableIsys.reload(); 9632 // 9633 // // And close this dialog 9634 // dialog.dialog('close'); 9635 // }) 9636 // }) 9637 // .fail(function(jqXHR, textStatus, errorThrown) { 9638 // sendLog({ 9639 // tag: 'ipal', 9640 // subTag: 'ppapproveFailure', 9641 // uuid: uuid, 9642 // message: 'Paypal Approve Failure', 9643 // content: { 9644 // textStatus: textStatus, 9645 // status: jqXHR && jqXHR.status, 9646 // responseText: jqXHR && jqXHR.responseText, 9647 // error: errorThrown 9648 // } 9649 // }); 9650 // 9651 // var message = 'Paypal transaction completed, but we could not update your access license.<br><br>' + 9652 // (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':'') + 9653 // 'Please contact [email protected] and provide your transaction ID:<br>' + paymentID; 9654 // 9655 // errorMessageDialog(message, 'Error', 800); 9656 // }) 9657 // .done(); 9658 // } else { 9659 // errorMessageDialog('You are logged out of ISY Portal.<br>Transaction is cancelled.') 9660 // .always(function() { 9661 // redirectError('Transaction is cancelled'); 9662 // }) 9663 // } 9664 // }); 9665 // } 9666 9667 // New server side order create 9668 // Ref: https://developer.paypal.com/sdk/js/reference/#createorder 9669 createOrder: function(data) { 9670 console.log('Creating order. Payment source selected: ' + data.paymentSource) 9671 9672 // New server side createOrder 9673 return ajaxJsonPost('/api/isy/ipal/order/create', $.param({ 9674 id: pk, // ISY ID 9675 domain: isyDomain, 9676 sku: option.part, 9677 paymentSource: data.paymentSource, 9678 env: ppCurrentEnv 9679 })) 9680 .then(function(data, status, jqXHR) { 9681 return data.id 9682 }) 9683 .fail(function(jqXHR, textStatus, errorThrown) { 9684 console.log('Order creation failed: ' + jqXHR.responseText) 9685 }) 9686 }, 9687 9688 // New server side order capture 9689 onApprove: function(data) { 9690 var orderID = data.orderID 9691 var paymentID = data.paymentID 9692 // var payerID = data.payerID 9693 var paymentSource = data.paymentSource 9694 9695 if (registeredUser) { 9696 // New server side createOrder 9697 return ajaxJsonPost('/api/isy/ipal/order/capture', $.param({ 9698 id: pk, // ISY ID 9699 domain: isyDomain, 9700 env: ppCurrentEnv, 9701 orderID, 9702 // payerID, 9703 paymentID, 9704 paymentSource 9705 })) 9706 .then(function(data, status, jqXHR) { 9707 var message = 'ISY Portal Access License purchase has been completed successfully.' + 9708 (data.webStore.adminConsoleWarning?'<br><br>Please open the admin console and click on Help | Manage Modules to install the license.':''); 9709 9710 informationMessageDialog(message) 9711 .done(function() { 9712 // When we close isyAccessLicenseDialog, we will return success (if redirect is used) 9713 isyAccessLicenseDialog.setPurchaseSuccess(true) 9714 9715 // Refresh main access licence dialog with the new license info 9716 isyAccessLicenseDialog.setFields({ uuid: uuid, alias: alias, info: data, pk: pk, domain: isyDomain }); 9717 9718 //Refresh the table behind 9719 tableIsys.reload(); 9720 9721 // And close this dialog 9722 dialog.dialog('close'); 9723 }) 9724 }) 9725 .fail(function(jqXHR, textStatus, errorThrown) { 9726 var message = 'Transaction failed.<br><br>' + 9727 (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':'') 9728 9729 errorMessageDialog(message, 'Error', 800); 9730 }) 9731 } else { 9732 errorMessageDialog('You are logged out.<br>Transaction is cancelled.') 9733 } 9734 }, 9735 9736 onError: function(err) { 9737 console.log('Order error', err) 9738 9739 sendLog({ 9740 tag: 'ipal', 9741 subTag: 'orderError', 9742 uuid: uuid, 9743 message: 'Order error: ' + (err && err.message), 9744 content: {} 9745 }); 9746 }, 9747 9748 onCancel: function(data) { 9749 sendLog({ 9750 tag: 'ipal', 9751 subTag: 'orderCancelled', 9752 uuid: uuid, 9753 message: 'Order cancelled: ' + data.orderID, 9754 content: { 9755 orderID: data.orderID 9756 } 9757 }); 9758 9759 console.log('Order cancelled') 9760 } 9761 }).render(paypalButtonTarget); 9762 } 9763 9764 // Keypressed, active only when this dialog is open. 9765 // function keyEvent(e) { 9766 // if( e.which === 90 && e.ctrlKey && !e.shiftKey ){ 9767 // var refreshPaypalButton=false; 9768 // 9769 // if (ppCurrentEnv=='sandbox') { 9770 // if (serverConfig.ppClientIdProduction) { 9771 // ppCurrentEnv='production'; 9772 // refreshPaypalButton=true; 9773 // } 9774 // else { 9775 // errorMessageDialog('Production environment is not configured'); 9776 // } 9777 // } 9778 // else if (ppCurrentEnv=='production') { 9779 // if (serverConfig.ppClientIdSandbox) { 9780 // ppCurrentEnv='sandbox'; 9781 // refreshPaypalButton=true; 9782 // } 9783 // else { 9784 // errorMessageDialog('Sandbox environment is not configured'); 9785 // } 9786 // } 9787 // 9788 // if (refreshPaypalButton) { 9789 // displayPaymentOptions($('#isy_license_renew_selector select').val()) 9790 // } 9791 // } 9792 // } 9793 9794 var isyOptions = { 9795 autoOpen: false, 9796 width: 700, 9797 modal: true, 9798 buttons: { 9799 'Close': function() { 9800 $(this).dialog('close'); 9801 } 9802 }, 9803 open: function(event, ui) { 9804 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 9805 // $(document).on('keydown.pp', keyEvent); 9806 }, 9807 close: function( event, ui ) { 9808 // $(document).off('keydown.pp'); 9809 } 9810 }; 9811 9812 // Initializes the dialog
9813 dialog = $('#isy_license_renew_dialog').dialog(isyOptions); 9814 9815 return { modal: dialog, setFields: setFields }; 9816}; 9817 9818 9819// OpenADR Registration 9820var isyOpenadrRegDialogInit = function () { 9821 var dialog, 9822 xmlData, 9823 uuid, 9824 alias, 9825 pk, 9826 domain; 9827 9828 9829 function setFields(obj) { 9830 $('#isy_openadrReg_uuid').text(obj.uuid); 9831 $('#isy_openadrReg_alias').text(obj.alias); 9832 9833 uuid = obj.uuid; 9834 pk = obj.pk; 9835 alias = obj.alias; 9836 domain = obj.domain; 9837 9838 if (obj.xml) { 9839 xmlData = obj.xml; 9840 var $xml = $(obj.xml); 9841 9842 $('#isy_openadrReg_vtnid').text($xml.find('VtnID').text()); 9843 $('#isy_openadrReg_venid').text($xml.find('VenID').text()); 9844 $('#isy_openadrReg_partyid').text($xml.find('PartyID').text()); 9845 $('#isy_openadrReg_resourceid').text($xml.find('ResourceID').text()); 9846 $('#isy_openadrReg_groupid').text($xml.find('GroupID').text()); 9847 $('#isy_openadrReg_marketcontext').text($xml.find('MarketContext').text()); 9848 $('#isy_openadrReg_registrationid').text($xml.find('RegistrationID').text()); 9849 } 9850 else { 9851 xmlData=null; 9852 uuid=null; 9853 $('#isy_openadrReg_vtnid').text(''); 9854 $('#isy_openadrReg_venid').text(''); 9855 $('#isy_openadrReg_partyid').text(''); 9856 $('#isy_openadrReg_resourceid').text(''); 9857 $('#isy_openadrReg_groupid').text(''); 9858 $('#isy_openadrReg_marketcontext').text(''); 9859 $('#isy_openadrReg_registrationid').text(''); 9860 } 9861 } 9862 9863 function saveToDisk() { 9864 try { 9865 var isFileSaverSupported = !!new Blob; 9866 } catch (e) {} 9867 9868 if (isFileSaverSupported) { 9869 var str = new XMLSerializer().serializeToString(xmlData); 9870 var blob = new Blob([str], {type: "text/xml;charset=utf-8"}); 9871 saveAs(blob, "profile " + uuidNC(uuid) + ".xml"); 9872 $(this).dialog('close'); 9873 } 9874 else { 9875 errorMessageDialog('Feature not supported on this browser', 'Not supported') 9876 .then(function() { 9877 $(this).dialog('close'); 9878 }) 9879 } 9880 } 9881 9882 var isyOptions = { 9883 autoOpen: false, 9884 width: 450, 9885 modal: true, 9886 buttons: { 9887 'Save to disk': saveToDisk, 9888 'Upload new profile': function() { 9889 $(this).dialog('close'); 9890 isyOpenadrRegUploadDialog.setFields({ pk: pk, uuid: uuid, domain: domain, alias: alias }); 9891 isyOpenadrRegUploadDialog.modal.dialog('open'); 9892 } 9893 }, 9894 open: function(event, ui) { 9895 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 9896 } 9897 }; 9898 9899 // Initializes the dialog 9900 dialog = $('#isy_openadrReg_dialog').dialog(isyOptions); 9901 9902 return { modal: dialog, setFields: setFields }; 9903}; 9904 9905// OpenADR Registration Profile upload 9906var isyOpenadrRegUploadDialogInit = function () { 9907 var dialog, 9908 uploader, 9909 pk, 9910 uuid, 9911 alias, 9912 domain, 9913 $uploadButton; 9914 9915 function resetUI() { 9916 $('#openadr_profile_upload_filename').text(' '); 9917 $uploadButton.hide(); 9918 } 9919 9920 function setFields(obj) { 9921 pk = obj.pk; 9922 9923 domain = obj.domain; 9924 uuid = obj.uuid; 9925 alias = obj.alias; 9926 uploader.setData({ id: pk }); 9927 uploader.setOptions({ customHeaders: { Authorization: 'Basic ' + registeredUser.basicAuth }}); 9928 } 9929 9930 var isyOptions = { 9931 autoOpen: false, 9932 width: 450, 9933 modal: true, 9934 buttons: { 9935 'Choose a file': function () {}, 9936 'Upload new profile': function () { 9937 $uploadButton.blur(); 9938 uploader.submit(); 9939 } 9940 }, 9941 open: function(event, ui) { 9942 resetUI(); 9943 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 9944 } 9945 }; 9946 9947 // Initializes the dialog 9948 dialog = $('#isy_openadrRegUpload_dialog').dialog(isyOptions); 9949 9950 // Set id on first button to allow uploader to hook to it 9951 var $buttons = $('#isy_openadrRegUpload_dialog').parent().find('.ui-dialog-buttonset button'); 9952 9953 var buttonId = 'profile_upload' 9954 $buttons.first().attr('id', buttonId); 9955 9956 $uploadButton = $buttons.eq(1); // Second button 9957 9958 uploader = new ss.SimpleUpload({ 9959 button: buttonId, // file upload button 9960 url: '/api/isy/oadr/save', // server side handler 9961 name: 'profile', // upload parameter name 9962 autoSubmit: false, 9963 maxSize: 3072, // Standard file is < 1k 9964 multipart: true, 9965// responseType: 'json', 9966 dragClass: 'ui-state-hover', 9967 hoverClass: 'ui-state-hover', 9968 focusClass: 'ui-state-focus', 9969 disabledClass: 'ui-state-disabled', 9970 allowedExtensions: ['xml'], 9971 dropzone: 'openadr_profile_upload_filename', 9972 onChange: function(filename, extension) { 9973 $('#openadr_profile_upload_filename').text(filename); 9974 $uploadButton.show(); 9975 }, 9976 onExtError: function(filename, extension) { 9977 errorMessageDialog('File must be an XML with extension .xml', 'Error') 9978 }, 9979 onSizeError: function(filename, size) { 9980 errorMessageDialog('File is too large', 'Error') 9981 }, 9982 onError: function(filename, errorType, status, statusText, response, uploadBtn) { 9983 errorMessageDialog('Error uploading file<br>HTTP code ' + status, 'Error') 9984 .then(function () { 9985 resetUI(); 9986 }) 9987 }, 9988 onComplete: function(filename, response) { 9989 informationMessageDialog('Profile uploaded successfully', 'Profile uploaded') 9990 .then(function () { 9991 dialog.dialog('close'); 9992 loadOpenadrReg(uuid, alias, pk, domain); 9993 }) 9994 } 9995 }); 9996 9997 return { modal: dialog, setFields: setFields }; 9998}; 9999 10000// Last Connected Form 10001var lastConnectedDialogInit = function () { 10002 var dialog; 10003 10004 function setFields(data) { 10005 // Build the table 10006 $('#lastconnected_placeholder') 10007 .setTemplateElement('lastConnectedTemplate') 10008 .processTemplate(data); 10009 } 10010 10011 var isyOptions = { 10012 autoOpen: false, 10013 width: 700, 10014 modal: true, 10015 buttons: { 10016 'Close': function() { 10017 $(this).dialog('close'); 10018 } 10019 }, 10020 open: function(event, ui) { 10021 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10022 } 10023 }; 10024 10025 // Initializes the dialog 10026 dialog = $('#lastconnected_dialog').dialog(isyOptions); 10027 10028 return { modal: dialog, setFields: setFields }; 10029}; 10030 10031function loginDialogInit() { 10032 var dialog, form, currentMode=0, cbAfterLogin, 10033 lastLoginAction, 10034 cookieInfo, 10035 email = $('#email'), 10036 password = $('#password'), 10037 rememberMeCheckbox = $('#login_rememberme'), 10038 allFields = $([]).add(email).add(password).add(rememberMeCheckbox), 10039 tips = $('#logindialog .validateTips'); 10040 10041 function resetForm() { 10042 tips.text('Please enter your email and password.'); 10043 allFields.removeClass('fieldfocus'); 10044 form[0].reset(); 10045 } 10046 10047 // Initializes the callback (afterLogin) 10048 function setFields(obj) { 10049 lastLoginAction = obj.lastLoginAction?obj.lastLoginAction:'portal'; 10050 rewriteButton(); 10051 10052 rememberMeCheckbox.prop('checked', obj.rememberMeCheckbox); 10053 10054 cbAfterLogin=obj.cb; 10055 cookieInfo = obj.cookieInfo; 10056 10057 if (cookieInfo) { 10058 email.val(cookieInfo.username); 10059 password.val(cookieInfo.password) 10060 } 10061 10062 if (obj.mode) { 10063 switchMode(obj.mode); // 0=login, 2=forgot your password 4=password change 10064 } 10065 } 10066 10067 function login() {
10068 var valid = true; 10069 allFields.removeClass('fieldfocus'); 10070 valid = valid && checkLength(tips, email, 'email', 6, 80); 10071 valid = valid && checkRegexp(tips, email, emailRegexWithSpace, 'email address is not valid.' ); 10072 valid = valid && checkNotEmpty(tips, password, 'password'); 10073 10074 if (valid) { 10075 email.val(email.val().trim()); 10076 var password_value = password.val(); 10077 10078 ajaxJsonPost('/api/login', form.find('.login').serialize()) 10079 .then(function(data, status, jqXHR) { 10080 if (data) { 10081 setLastConnected(data.user.lastConnected); 10082 10083 if (rememberMeCheckbox.prop('checked')) { 10084 setLocalStorage('udi_remember_me', true); 10085 10086 if (data.rememberMeToken) { 10087 Cookies.set('udi_user', data.rememberMeToken, { expires: 30 }); 10088 } 10089 } else { 10090 deleteLocalStorage('udi_remember_me'); 10091 Cookies.remove('udi_user'); 10092 } 10093 10094 if (data.user.passwordchange) { 10095 var afterPwChange = function (new_password_value) { 10096 // Sets the registeredUser with the new password only if the change was successful 10097 setRegisteredUser(data.user, { password: new_password_value?new_password_value:password_value }, data.account); 10098 10099 if (cbAfterLogin) { 10100 cbAfterLogin(data, lastLoginAction); 10101 } 10102 }; 10103 10104 passwordChangeDialog.setFields({ pk: data.user._id, cb: afterPwChange }); 10105 passwordChangeDialog.modal.dialog('open'); 10106 } else { 10107 setRegisteredUser(data.user, { password: password_value }, data.account); 10108 10109 if (cbAfterLogin) { 10110 cbAfterLogin(data, lastLoginAction); 10111 } 10112 } 10113 } 10114 10115 dialog.dialog('close'); 10116 }) 10117 .fail(function(jqXHR, textStatus, errorThrown) { 10118 if (jqXHR.status==403) { 10119 updateTips(tips, 'Access denied'); 10120 } 10121 else { 10122 updateTips(tips, 'HTTP error ' + jqXHR.status); 10123 } 10124 }) 10125 .done(); 10126 } 10127 } 10128 10129 function gsi_login(loginParams) { 10130 ajaxJsonPost('/api/gsi/login', new URLSearchParams(loginParams).toString()) 10131 .then(function(data, status, jqXHR) { 10132 if (data) { 10133 setLastConnected(data.user.lastConnected); 10134 10135 // This is only for password pased login 10136 deleteLocalStorage('udi_remember_me'); 10137 Cookies.remove('udi_user'); 10138 10139 setRegisteredUser(data.user, { jwt: data.jwtCredentials }, data.account); 10140 10141 if (cb) { 10142 cb(data, lastLoginAction); 10143 } 10144 } 10145 10146 dialog.dialog('close'); 10147 }) 10148 .fail(function(jqXHR, textStatus, errorThrown) { 10149 if (jqXHR.status==403) { 10150 updateTips(tips, 'Access denied'); 10151 } 10152 else { 10153 updateTips(tips, 'HTTP error ' + jqXHR.status); 10154 } 10155 }) 10156 .done(); 10157 } 10158 10159 function forgotYourPassword() { 10160 var valid = true; 10161 var o2Location = null 10162 10163 allFields.removeClass('fieldfocus'); 10164 valid = valid && checkLength(tips, email, 'email', 6, 80); 10165 valid = valid && checkRegexp(tips, email, emailRegexWithSpace, 'email address is not valid.' ); 10166 10167 if (valid) { 10168 var username = email.val().trim() 10169 var data = 'username='+username; 10170 10171 // If we have a client_id, it's a redirect from the authorization page. 10172 var isFromAuthPage = $.gup('client_id') 10173 10174 // We have to capture the query string to use it after the user is verified. 10175 if (isFromAuthPage) { 10176 o2Location = window.location.search 10177 } 10178 10179 ajaxJsonPost('/api/pin/request', data) 10180 .done(function () { 10181 dialog.dialog('close'); 10182 10183 // Callback: Runs after pin successfully entered (when forget your password is from the login page) 10184 var afterPin = function (verifyResponse, pin) { 10185 // verifyResponse is the result of /api/pin/verify. It's the same as /login 10186 10187 console.log('Pin verification is successful') 10188 10189 var afterPwChange = function (new_password_value) { 10190 // Sets the registeredUser with the new password only if the change was successful 10191 setRegisteredUser(verifyResponse.user, { password: new_password_value }, verifyResponse.account); 10192 10193 if (isFromAuthPage) { 10194 window.location.replace('/o2/authorize' + o2Location + '&' + 10195 $.param({ email: registeredUser.username, password: registeredUser.password, new:true })); 10196 } if (cbAfterLogin) { 10197 cbAfterLogin(verifyResponse, 'portal'); 10198 } 10199 }; 10200 10201 passwordChangeDialog.setFields({ pk: verifyResponse.user._id, pin: pin, cb: afterPwChange }); 10202 passwordChangeDialog.modal.dialog('open'); 10203 } 10204 10205 pinDialog.setFields({ username: username, cb: afterPin }) 10206 pinDialog.modal.dialog('open') 10207 }) 10208 .fail(function(jqXHR, textStatus, errorThrown) { 10209 // Should only happen in case of system error (server not responding or username not passed to api) 10210 informationMessageDialog("Password reset error.<br>HTTP error code " + jqXHR.status, "Error") 10211 }) 10212 .done(); 10213 } 10214 } 10215 10216 var buttonLabels = { 10217 'portal' : 'Portal Login', 10218 'isy' : 'Go to my ISY' 10219 }; 10220 10221 function rewriteButton() { 10222 $('#rerun').button('option', 'label', buttonLabels[lastLoginAction]) 10223 } 10224 10225 function loginSplitButton() { 10226 // Rewrite the button pane as we need it 10227 var $buttonset = $("#logindialog").parent().find('.ui-dialog-buttonset') 10228 .addClass('ui-dialog-buttonset-fixedheigth') // Prevents the menu from extending dialog heigth 10229 .html('<div class="modalbuttonset"><button id="rerun">' + 10230 buttonLabels[lastLoginAction] + 10231 '</button><button id="select"></button></div>' + 10232 '<ul class="ui-front"><li id="splitMenuPortal">Portal Login</li><li id="splitMenuISY">Go to my ISY</li></ul>'); 10233 10234 $('#rerun').button().click(function() { 10235 login(); 10236 }); 10237 10238 $('#select').button({ 10239 text: false, 10240 icons: { 10241 primary: "ui-icon-triangle-1-s" 10242 }}) 10243 .click(function() { 10244 var menu = $(this).parent().next().toggle().position({ 10245 my: "left top", 10246 at: "left bottom", 10247 of: this 10248 }); 10249 10250 $(document).one("click", function() { 10251 menu.hide(); 10252 }); 10253 10254 return false; 10255 }) 10256 .parent().buttonset().next().hide().menu(); 10257 10258 $('#splitMenuPortal').click(function() { 10259 lastLoginAction='portal'; 10260 rewriteButton(); 10261 login(); 10262 }); 10263 10264 $('#splitMenuISY').click(function() { 10265 lastLoginAction='isy'; 10266 rewriteButton(); 10267 login(); 10268 }) 10269 } 10270 10271 function switchMode(mode) { // 0=login, 2=forgot your password 4=password change 10272 allFields.removeClass('fieldfocus'); 10273 currentMode=mode; 10274 10275 switch (mode) { 10276 case 0: dialog.dialog('option', loginOptions); 10277 // Login 10278 loginSplitButton(); 10279 $('[aria-describedby="logindialog"] .ui-dialog-title').text('Login'); 10280 tips.text('Please enter your email and password.'); 10281 $('#logindialog fieldset :not(.login)').hide(); 10282 $('#logindialog fieldset .login').show(); 10283 $('#loginforgot').show(); 10284 $('#logincreate').show(); 10285 $('.g_id_signin').show(); 10286 $('#email').prop('readonly', false); 10287 $('#email').focus(); 10288 $('#email').removeProp('tabindex'); 10289 break; 10290 10291 // Forgot your password 10292 case 2: dialog.dialog('option', fypOptions); 10293 $('[aria-describedby="logindialog"] .ui-dialog-title').text('Forgot your password?'); 10294 tips.text('Enter your email address to receive a password reset pin.'); 10295 $('#logindialog fieldset :not(.fyp)').hide(); 10296 $('#logindialog fieldset .fyp').show(); 10297 $('#loginforgot').hide(); 10298 $('#logincreate').hide(); 10299 $('#logindeleteprofile').hide(); 10300 $('.g_id_signin').hide(); 10301 10302 10303 $('#email').prop('readonly', false); 10304 $('#email').focus(); 10305 $('#email').removeProp('tabindex'); 10306 break; 10307 } 10308 10309 $('#logindialog .submit').show(); // In all cases, it has to be visible for IE! (CSS hides it well) 10310 } 10311 10312 var loginOptions = { 10313 autoOpen: false, 10314 width: 450, 10315 modal: true, 10316 buttons: [{}], // Button pane created with dummy empty button, will be rewritten by loginSplitButton
10317 close: function() { 10318 form[0].reset(); 10319 allFields.removeClass('fieldfocus'); 10320 }, 10321 open: function(event, ui) { 10322 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10323 } 10324 }; 10325 10326 var fypOptions = { 10327 autoOpen: false, 10328 width: 350, 10329 modal: true, 10330 buttons: { 10331 'Send': forgotYourPassword, 10332 Cancel: function() { 10333 switchMode(0); 10334 } 10335 }, 10336 close: function() { 10337 form[0].reset(); 10338 allFields.removeClass('fieldfocus'); 10339 }, 10340 open: function(event, ui) { 10341 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10342 } 10343 }; 10344 10345 // Initializes the dialog 10346 dialog = $('#logindialog').dialog(loginOptions); 10347 loginSplitButton(); 10348 10349 // Forgot Your password link 10350 dialog.find('#loginforgot').click(function(event) { 10351 switchMode(2); 10352 }); 10353 10354 // Create new account link 10355 dialog.find('#logincreate').click(function(event) { 10356 selfRegistrationDialog.setFields({ email: email.val(), cb: afterLogin }); 10357 dialog.dialog('close'); 10358 selfRegistrationDialog.modal.dialog('open'); 10359 }); 10360 10361 // Handles keyboad enter (submit event) 10362 form = dialog.find('form').submit(function(event) { 10363 event.preventDefault(); 10364 10365 switch (currentMode) { 10366 case 0: login(); 10367 break; 10368 10369 case 2: forgotYourPassword(); 10370 break; 10371 } 10372 }); 10373 10374 // Enables mouseover on top right tabs 10375 $('#loginreg .loginnav').hover( 10376 function() { 10377 $(this).addClass('ui-state-hover'); 10378 }, 10379 function() { 10380 $(this).removeClass('ui-state-hover'); 10381 } 10382 ); 10383 10384 // Setup onclick for top-right tabs 10385 $('#loginbutton').click(function() { 10386 switchMode(0); 10387 dialog.dialog('close'); // Required, in case it is already open. Otherwise, focus will not be set properly. 10388 10389 loginDialog.resetForm(); 10390 loginDialog.setFields({ cb: afterLogin, lastLoginAction: 'portal', rememberMeCheckbox: rememberMeActive(), cookieInfo: rememberMe() }); 10391 loginDialog.modal.dialog('open'); 10392 }); 10393 10394 $('#logoutbutton').click(function() { 10395 logout(); 10396 }); 10397 10398 // Setup onclick for top-right tabs 10399 $('#myprofilebutton').click(function() { 10400 openMyProfile(); 10401 }); 10402 10403 // Setup onclick for top-right tabs 10404 // $('#openpolyglot').click(function() { 10405 // window.open('https://polyglot.isy.io', '_blank', 'menubar=1,location=1,resizable=1,scrollbars=1,status=1,toolbar=1'); 10406 // }); 10407 10408 // Show main top-right toolbar 10409 $('#loginreg').show(); 10410 10411 return { modal: dialog, resetForm: resetForm, setFields: setFields, gsi_login: gsi_login }; 10412} 10413 10414// Password change dialog 10415function passwordChangeDialogInit() { 10416 var dialog, form, pk, cb=null, pin, 10417 oldpassword = $('#change_oldpassword'), 10418 password = $('#change_password'), 10419 passwordconf = $('#change_passwordconf'), 10420 allFields = $([]).add(oldpassword).add(password).add(passwordconf), 10421 tips = $('.validateTips'), 10422 new_password; // Contains the new password if submit successful. 10423 10424 // We change the type here instead the html to avoid password tools issues 10425 oldpassword.attr('type', 'password'); 10426 password.attr('type', 'password'); 10427 passwordconf.attr('type', 'password'); 10428 10429 function resetForm() { 10430 allFields.removeClass('fieldfocus'); 10431 form[0].reset(); 10432 pk=null; 10433 cb=null; 10434 pin=null; 10435 new_password=null; 10436 } 10437 10438 function setFields(obj) { 10439 resetForm(); 10440 cb=obj.cb 10441 pin=obj.pin 10442 pk=obj.pk 10443 10444 if (pin) 10445 tips.text('Please enter a new password.'); 10446 else 10447 tips.text('Please enter old and new password.'); 10448 10449 // If we authenticate with a pin, hide the old password field 10450 oldpassword.toggle(!pin) 10451 } 10452 10453 function submit() { 10454 var valid = true; 10455 allFields.removeClass('fieldfocus'); 10456 valid = valid && checkPwd(tips, password); 10457 valid = valid && checkPwdEqual(tips, password, passwordconf, 'Passwords confirmation is not identical'); 10458 10459 if (valid) { 10460 var data = form.find('input').serialize(); 10461 10462 if (pk) { 10463 data += '&id='+pk; 10464 } 10465 10466 if (pin) { 10467 data += '&pin='+pin; 10468 } 10469 10470 ajaxJsonPost('/api/password-change', data) 10471 .then(function(data, status, jqXHR) { 10472 // Closing the window also triggers the callback (afterLogin). See dialog options. 10473 new_password = password.val(); 10474 dialog.dialog('close'); 10475 informationMessageDialog("Password change successful", "Success") 10476 }) 10477 .fail(function(jqXHR, textStatus, errorThrown) { 10478 if (jqXHR.status==404) { 10479 updateTips(tips, 'User code not found'); 10480 } 10481 else if (jqXHR.status==403) { 10482 updateTips(tips, 'Old password does not match'); 10483 } 10484 else if (jqXHR.status==424) { 10485 updateTips(tips, 'New password is too easy. Please choose a more complex one.'); 10486 } 10487 else { 10488 updateTips(tips, 'HTTP error ' + jqXHR.status); 10489 } 10490 }) 10491 .done(); 10492 } 10493 } 10494 10495 var dialogOptions = { 10496 autoOpen: false, 10497 modal: true, 10498 width: 350, 10499 closeOnEscape: false, 10500 dialogClass: "no-close", 10501 buttons: { 10502 'Save': function() { 10503 submit(); 10504 }, 10505 Cancel: function() { 10506 $(this).dialog('close'); 10507 } 10508 }, 10509 close: function() { 10510 if (cb) { 10511 cb(new_password);
10511 // Contains new password only if password change was successful 10512 } 10513 }, 10514 open: function(event, ui) { 10515 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10516 } 10517 }; 10518 10519 // Initializes the dialog 10520 dialog = $('#passwordchangedialog').dialog(dialogOptions); 10521 10522 password.focus(); 10523 10524 // Handles keyboad enter (submit event) 10525 form = dialog.find('form').submit(function(event) { 10526 event.preventDefault(); 10527 submit(); 10528 }); 10529 10530 // Setup onclick for My Profile Dialog 10531 $('#myprofile_changepassword').click(function() { 10532 myProfileDialog.modal.dialog('close'); 10533 var afterPwChange = function (new_password_value) { 10534 // Sets the registeredUser with the new password only if the change was successful 10535 if (new_password_value) { 10536 setRegisteredUserCredentials({ password: new_password_value }); 10537 } 10538 }; 10539 10540 resetForm(); 10541 setFields({ pk: registeredUser._id, cb: afterPwChange }); 10542 dialog.dialog('open'); 10543 }); 10544 10545 return { modal: dialog, setFields: setFields, resetForm: resetForm }; 10546} 10547 10548// Pin dialog - used for password reset 10549function pinDialogInit() { 10550 var dialog, form, cb, username 10551 var form = document.getElementById('pin-form'); 10552 var inputs = form.querySelectorAll('input'); 10553 10554 // Handle input behavior 10555 inputs.forEach((input, index) => { 10556 input.addEventListener('keyup', (e) => { 10557 if (e.key >= 0 && e.key <= 9) { 10558 if (index < inputs.length - 1) { 10559 inputs[index + 1].focus(); 10560 } else { 10561 // When entering the last digit, submit! 10562 submit() 10563 } 10564 } else if (e.key === 'Backspace') { 10565 if (index > 0) { 10566 inputs[index - 1].value = ''; 10567 inputs[index - 1].focus(); 10568 } 10569 } 10570 }); 10571 10572 // Prevent non-numeric inputs 10573 input.addEventListener('input', (e) => { 10574 e.target.value = e.target.value.replace(/[^0-9]/g, ''); 10575 }); 10576 }); 10577 10578 function resetForm() { 10579 inputs.forEach(input => input.value = ''); 10580 10581 // When user had an incorrect pin, we need the setTimeout so the information dialog 10582 // is closed. Otherwise the input field is hidden and focus doesn't work. 10583 setTimeout(function () { 10584 inputs[0].focus(); 10585 }, 0); 10586 } 10587 10588 function setFields(obj) { 10589 resetForm() 10590 10591 if (obj.username) { 10592 username = obj.username 10593 } 10594 10595 if (obj.cb) { 10596 cb = obj.cb; 10597 } 10598 } 10599 10600 function submit() { 10601 var pin = Array.from(inputs) 10602 .map(input => input.value) 10603 .join(''); 10604 10605 // Should never happen as we submit on the last digit entered 10606 if (pin.length !== 6) { 10607 informationMessageDialog('Please enter the 6-digits PIN', 'Error') 10608 .then(function () { 10609 resetForm() 10610 }) 10611 10612 return 10613 } 10614 10615 var data = 'pin=' + pin + '&username=' + username 10616 10617 ajaxJsonPost('/api/pin/verify', data) 10618 .then(function(verifyData, status, jqXHR) { 10619 dialog.dialog('close'); 10620 10621 if (cb) { 10622 cb(verifyData, pin); 10623 } 10624 }) 10625 .fail(function(jqXHR, textStatus, errorThrown) { 10626 informationMessageDialog('Pin verification failed.', 'Error') 10627 .then(function () { 10628 resetForm() 10629 }) 10630 }) 10631 .done(); 10632 } 10633 10634 var dialogOptions = { 10635 autoOpen: false, 10636 modal: true, 10637 width: 400, 10638 // closeOnEscape: false, 10639 // dialogClass: 'no-close', 10640 buttons: { 10641 Cancel: function() { 10642 $(this).dialog('close'); 10643 loginDialog.modal.dialog('open'); 10644 } 10645 }, 10646 // close: function() {}, 10647 open: function(event, ui) { 10648 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10649 } 10650 }; 10651 10652 // Initializes the dialog 10653 dialog = $('#pin-dialog').dialog(dialogOptions); 10654 10655 // Handles keyboad enter (submit event) 10656 form = dialog.find('form').submit(function(event) { 10657 event.preventDefault(); 10658 save(); 10659 }); 10660 10661 return { modal: dialog, setFields: setFields }; 10662} 10663 10664 10665 10666// This needs to be global for recaptcha to work 10667function selfRegistrationRecaptchaSubmit(token) { 10668 selfRegistrationDialog.submit(token) 10669} 10670 10671// Self-Registration dialog 10672function selfRegistrationDialogInit() { 10673 var dialog, form, cb, 10674 fname = $('#selfr_fname'), 10675 lname = $('#selfr_lname'), 10676 company = $('#selfr_company'), 10677 email = $('#selfr_email'), 10678 password = $('#selfr_password'), 10679 passwordconf = $('#selfr_passwordconf'), 10680 allFields = $([]).add(fname).add(lname).add(company).add(email).add(password).add(passwordconf), 10681 tips = $('#selfregistrationdialog .validateTips'); 10682 10683 // We change the type here instead of the html to avoid password tools issues 10684 password.attr('type', 'password'); 10685 passwordconf.attr('type', 'password'); 10686 10687 function resetForm() { 10688 tips.text('Please enter new account information.'); 10689 allFields.removeClass('fieldfocus'); 10690 form[0].reset(); 10691 } 10692 10693 function setFields(obj) { 10694 resetForm(); 10695 10696 if (obj.email) { 10697 email.val(obj.email); 10698 } 10699 10700 if (obj.cb) { 10701 cb = obj.cb; 10702 } 10703 } 10704 10705 function save() {
10706 var valid = true; 10707 allFields.removeClass('fieldfocus'); 10708 valid = valid && checkLength(tips, fname, 'First name', 2, 50); 10709 valid = valid && checkLength(tips, lname, 'Last name', 2, 50); 10710 valid = valid && checkLength(tips, email, 'email', 6, 80); 10711 valid = valid && checkRegexp(tips, email, emailRegexWithSpace, 'email address is not valid.' ); 10712 valid = valid && checkPwd(tips, password); 10713 valid = valid && checkPwdEqual(tips, password, passwordconf, 'Passwords confirmation is not identical'); 10714 10715 if (valid) { 10716 grecaptcha.execute(); 10717 } 10718 } 10719 10720 function submit(token) { 10721 email.val(email.val().trim()); 10722 10723 var data = form.find('input').serialize(); 10724 10725 data += "&token=" + token; 10726 10727 // If we have a client_id, it's a redirect from the authorization page. 10728 // We have to capture the query string to use it after the user is verified. 10729 if ($.gup('client_id')) { 10730 data += "&o2=" + btoa(window.location.search) 10731 } 10732 10733 var emailVerificationToken = $.gup('emailverification'); 10734 10735 ajaxJsonPut('/api/account/self_register', data) 10736 .then(function(data, status, jqXHR) { 10737 dialog.dialog('close'); 10738 10739 setLastConnected([]); 10740 setRegisteredUser(data.user, { password: password.val() }, data.account); 10741 10742 informationMessageDialog("Account created successfully.<br>In order to verify your email address, please check your inbox and follow the instructions.", "Success") 10743 .then(function () { 10744// if (cb) { 10745// cb(data, 'portal'); 10746// } 10747 }) 10748 }) 10749 .fail(function(jqXHR, textStatus, errorThrown) { 10750 if (jqXHR.status==426) { 10751 updateTips(tips, 'reCAPTCHA validation failed.'); 10752 } 10753 else { 10754 if (jqXHR.status==420) { 10755 updateTips(tips, 'Email address is already in use.'); 10756 } 10757 else if (jqXHR.status==424) { 10758 updateTips(tips, 'New password is too easy. Please choose a more complex one.'); 10759 } 10760 else if (jqXHR.status==425) { 10761 updateTips(tips, jqXHR.responseText); 10762 } 10763 else { 10764 updateTips(tips, 'HTTP error ' + jqXHR.status); 10765 } 10766 10767 // Otherwise user would not be able to submit if let's say email address was already in use 10768 grecaptcha.reset() 10769 } 10770 10771 }) 10772 .done(); 10773 } 10774 10775 var dialogOptions = { 10776 autoOpen: false, 10777 modal: true, 10778 width: 400, 10779 closeOnEscape: false, 10780 dialogClass: "no-close", 10781 buttons: { 10782 'Save': function() { 10783 save(); 10784 }, 10785 Cancel: function() { 10786 $(this).dialog('close'); 10787 loginDialog.modal.dialog('open'); 10788 } 10789 }, 10790 close: function() { 10791 }, 10792 open: function(event, ui) { 10793 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10794 } 10795 }; 10796 10797 // Initializes the dialog 10798 dialog = $('#selfregistrationdialog').dialog(dialogOptions); 10799 10800 // Handles keyboad enter (submit event) 10801 form = dialog.find('form').submit(function(event) { 10802 event.preventDefault(); 10803 save(); 10804 }); 10805 10806 return { modal: dialog, setFields: setFields, resetForm: resetForm, submit:submit }; 10807} 10808 10809// My Profile 10810var myProfileDialogInit = function () { 10811 var dialog, 10812 form, 10813 isy = $('#preferred_isy'), 10814 username = $('#myprofile_user_name'), 10815// amz_email = $('#myprofile_amz_email'), 10816 notificationmaint = $('#myprofile_notificationmaint'), 10817 notificationnews = $('#myprofile_notificationnews'), 10818 allFields = $([]).add(isy).add(username).add(notificationmaint).add(notificationnews), 10819 tips = $('#myprofile_dialog .validateTips'), 10820 voiceSelector = '#myprofile_voice_isy_selector', 10821 voiceSelectorSelect = '#myprofile_voice_isy_selector' + ' select'; 10822 10823 10824 function resetForm() { 10825 tips.text('You can change your profile'); 10826 allFields.removeClass('fieldfocus'); 10827 form[0].reset(); 10828 } 10829/* 10830 function setAmzField(value) { 10831 amz_email.val(value); 10832 10833 if (value && value.length) { 10834 $('#linkAmazonWrapper').hide(); 10835 $('#unlinkAmazon').show(); 10836 } 10837 else { 10838 $('#linkAmazonWrapper').show(); 10839 $('#unlinkAmazon').hide(); 10840 } 10841 } 10842*/ 10843 function setFields(obj) { 10844 resetForm(); 10845 username.val(obj.username); 10846 username.prop('readonly', true); 10847// setAmzField(obj.amz_email); 10848 notificationmaint.prop('checked', obj.notificationmaint); 10849 notificationnews.prop('checked', obj.notificationnews); 10850 isy.combobox("option", "source", obj.uuidList); 10851 isy.combobox("option", "data", obj.uuid); 10852 10853 if (obj.voiceSelector) { 10854 $(voiceSelectorSelect).val(obj.voiceSelector); // Set user voice selector 10855 } 10856 // resetForm sets it ot the first option. 10857 // else { 10858 // // Selects the first option by default 10859 // $(voiceSelectorSelect).val($(select + ' option:first').val()); 10860 // } 10861 10862 $(voiceSelectorSelect).selectmenu("refresh"); 10863 } 10864 10865 function submit() {
10866 var valid = true; 10867 10868 allFields.removeClass('fieldfocus'); 10869 10870 isy.combobox("refresh"); // Makes sure the real input field is up to date 10871 10872 if (isy.val().length && !isy.combobox("validate")) { 10873 updateTips(tips, 'UUID is not allowed') 10874 } 10875 else { 10876// var data = form.find('input').serialize(); 10877 var data = serializeFormWithCheckboxes(form.find('input')); 10878 data += '&voiceSelector=' + $(voiceSelectorSelect).val(); 10879 10880 submitProcess(data, false) 10881 } 10882 } 10883 10884 function submitProcess(data, isRetry) { 10885 ajaxJsonPost('/api/user', data) 10886 .then(function(data, status, jqXHR) { 10887 setRegisteredUserProfile(data); 10888 dialog.dialog('close'); 10889 10890 // If table is initialized, make it refresh 10891 if (tableUsers.api) { 10892 tableUsers.reload(); 10893 } 10894 }) 10895 .fail(function(jqXHR, textStatus, errorThrown) { 10896 // Preferred ISY changed 10897 // if (jqXHR.status==426) { 10898 // if (!isRetry) { 10899 // confirmationDialog(preferredIsyChangeWarning, 'Preferred ISY Change', 800) 10900 // .then(function () { 10901 // // Retry the request, bypass the preferred ISY change validation 10902 // submitProcess(data+'&confirmed=true', true) 10903 // }) 10904 // .fail(function() { 10905 // 10906 // }) 10907 // .done(); 10908 // } 10909 // else { 10910 // errorMessageDialog('Error updating the preferred ISY', 'Error', 350) 10911 // } 10912 // } 10913 // else { 10914 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 10915 // } 10916 }) 10917 .done(); 10918 } 10919 10920 var isyOptions = { 10921 autoOpen: false, 10922 width: 450, 10923 height: 500, 10924 modal: true, 10925 buttons: { 10926 'Save': function() { 10927 submit(); 10928 }, 10929 Cancel: function() { 10930 $(this).dialog('close'); 10931 } 10932 }, 10933 open: function(event, ui) { 10934 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 10935 $('.custom-combobox-input').focus(); 10936 } 10937 }; 10938 10939 // Initializes the dialog 10940 dialog = $('#myprofile_dialog').dialog(isyOptions); 10941 10942 isy.combobox({inputClass: ''}); 10943 10944 // Handles keyboad enter (submit event) 10945 form = dialog.find('form').submit(function(event) { 10946 event.preventDefault(); 10947 submit(); 10948 }); 10949/* 10950 $('#linkAmazon').click(function() { 10951 amazon.Login.authorize({ scope : 'profile' }, 'https://' + location.hostname + '/amazon_link.htm'); 10952 }); 10953 10954 $('#unlinkAmazon').click(function() { 10955 ajaxJsonPost('/api/voice/amazon/unlink') 10956 .then(function(data, status, jqXHR) { 10957 registeredUser.amz_email=''; 10958 registeredUser.amz_user_id=''; 10959 setSessionStorage('udiportal', { registeredUser: registeredUser }); 10960 setAmzField(''); 10961 }) 10962 .fail(function(jqXHR, textStatus, errorThrown) { 10963 errorMessageDialog("Error unlinking Amazon user profile<br/>HTTP error " + jqXHR.status, "Failure") 10964 }) 10965 .done(); 10966 }); 10967*/ 10968 $('#preferredIsyHelp').click(function() { 10969 informationMessageDialog('The preferred ISY is used during login when you select "Go to my ISY". It is also used by Amazon Echo and Google Home to direct request to your preferred ISY.', 'Help', 500) 10970 }); 10971 10972 var voiceOptions = [ 10973 { 10974 value: '$preferred', 10975 description: 'Use preferred ISY' 10976 }, 10977 { 10978 value: '$all', 10979 description: 'Use all ISYs in account' 10980 } 10981 ] 10982 10983 buildSelect(voiceOptions, voiceSelector, '350px', function(event, ui) {}); 10984 10985 /* 10986 $('#linkAmazonHelp').click(function() { 10987 //If you want to use your ISY via the connected-home feature of the Amazon echo 10988 informationMessageDialog("With the ISY Smart Home skill V3, you no longer need to link here. This is now deprecated.", 'Help', 500) 10989 }); 10990 */ 10991 return { modal: dialog, setFields: setFields }; 10992}; 10993 10994 10995// Migrate ISY 10996var migrateDialogInit = function () { 10997 var dialog, 10998 form, 10999 domain, 11000 isyList, 11001 migrate_from = $('#migrate_from'), 11002 isy = $('#migrate_to'), 11003 migrate_alexa = $('#migrate_alexa'), 11004 migrate_gh = $('#migrate_gh'), 11005 migrate_ifttt = $('#migrate_ifttt'), 11006 migrate_lic = $('#migrate_lic'), 11007 migrate_NS = $('#migrate_NS'), 11008 update_preferred = $('#update_preferred'), 11009 allFields = $([]).add(isy), 11010 tips = $('#migrate_dialog .validateTips'); 11011 11012 function resetForm() { 11013 tips.text('Please select which ISY you want to migrate to'); 11014 allFields.removeClass('fieldfocus'); 11015 migrate_alexa.prop('checked', true); // We migrate the license by default 11016 migrate_gh.prop('checked', true); // We migrate the license by default 11017 migrate_ifttt.prop('checked', true); // We migrate the license by default 11018 migrate_lic.prop('checked', true); // We migrate the license by default 11019 migrate_NS.prop('checked', true); // We migrate the nodeservers by default 11020 update_preferred.prop('checked', true); // We migrate the license by default 11021 form[0].reset(); 11022 } 11023 11024 function setFields(obj) { 11025 resetForm(); 11026 domain = obj.domain 11027 migrate_from.val(obj.uuid) 11028 isyList = obj.uuidList 11029 isy.combobox("option", "source", obj.uuidList); 11030 isy.combobox("option", "data", ""); 11031 } 11032 11033 function isUuidDuplicate(uuid) { 11034 // This range of uuid was issued twice. It got corrected by an update. 11035 // We need to prevent migration to these uuid if they are below 6.0.4 11036 var DUP_RANGE_START_HEX=0x0021b90271fc 11037 var DUP_RANGE_END_HEX=0x0021b90272f5 11038 var uuidHex = parseInt(uuid.replace(/:/g,''), 16) 11039 return uuidHex >= DUP_RANGE_START_HEX && uuidHex <= DUP_RANGE_END_HEX 11040 } 11041 11042 function isFirmwareOld(firmware) { 11043 var is604AndUp = (firmware && versionCompare(firmware, '6.0.4') >= 0); 11044 return !is604AndUp 11045 } 11046 11047 11048 11049 function submit() {
11050 var valid = true; 11051 11052 allFields.removeClass('fieldfocus'); 11053 11054 isy.combobox('refresh'); // Makes sure the real input field is up to date 11055 11056 var migrateToItem = isyList 11057 .find(function (item) { return item.data === isy.val() }) 11058 var migrateToFirmware = migrateToItem && migrateToItem.firmware 11059 11060 if (!isy.val().length || !isy.combobox("validate")) { 11061 updateTips(tips, 'Please select an ISY to migrate to.') 11062 } else if (isUuidDuplicate(isy.val()) && isFirmwareOld(migrateToFirmware)) { 11063 errorMessageDialog('Before migrating, the unit you are migrating to needs to be upgraded. Please use "Upgrade packages" in the Admin Console, eisy-ui or portal.', 'Error', 750) 11064 } else if (!(migrate_alexa.prop('checked') || migrate_gh.prop('checked') || migrate_ifttt.prop('checked') || migrate_lic.prop('checked') || migrate_NS.prop('checked') || update_preferred.prop('checked'))) { 11065 updateTips(tips, 'Please select one or more migration options.') 11066 } else { 11067 var data = serializeFormWithCheckboxes(form.find('input')); 11068 data += '&migrate_from=' + $(migrate_from).val(); 11069 data += '&domain=' + domain; 11070 11071 submitProcess(data, false) 11072 } 11073 } 11074 11075 function migrationResultString(result) { 11076 var migrated = result.total - result.dup - result.failed; 11077 var failed = result.dup + result.failed; 11078 var resultStr = '' + migrated; 11079 11080 // Applicable to IFTTT only (null===no key to migrate, false=key not migrated, true=migrated successfully) 11081 var keyNotMigratedStr = result.keyNotMigrated ? 'Key not migrated':'' 11082 11083 if (failed) { 11084 // If failures are only duplicates 11085 if (result.dup && !result.failed) { 11086 resultStr += ' migrated (' + result.dup + ' duplicate' + (keyNotMigratedStr?' - '+keyNotMigratedStr:'') + ')'; 11087 } else { 11088 resultStr += ' migrated (' + (result.dup + result.failed) + ' failed' + (keyNotMigratedStr?' - '+keyNotMigratedStr:'') + ')'; 11089 } 11090 } else { 11091 resultStr += ' migrated' + (keyNotMigratedStr?' ('+keyNotMigratedStr+')':''); 11092 } 11093 11094 return resultStr 11095 } 11096 11097 function submitProcess(data, isConfirmed) { 11098 ajaxJsonPost('/api/isy/migrate', data + (isConfirmed ? '&merge_confirmation=true' : '')) 11099 .then(function(data, status, jqXHR) { 11100 var msg = '<table>' + 11101 (migrate_alexa.prop('checked') ? '<tr><td>Amazon Echo spokens:</td><td> ' + migrationResultString(data.spokenAlexa) + '</td></tr>' : '') + 11102 (migrate_gh.prop('checked') ? '<tr><td>Google Home spokens:</td><td> ' + migrationResultString(data.spokenGH) + '</td></tr>' : '') + 11103 (migrate_ifttt.prop('checked') ? '<tr><td>IFTTT / Webhooks:</td><td> ' + migrationResultString(data.iftttIn) + '</td></tr>' : '') + 11104 (migrate_NS.prop('checked') ? '<tr><td>Nodeserver Licences:</td><td> ' + migrationResultString(data.ns) + '</td></tr>' : '') + 11105 (migrate_lic.prop('checked') ? '<tr><td>Access Licences:</td><td>
11105 ' + (data.license.migrated? 11106 ('Migrated successfully' + (data.license.trialActivated ? ' </td></tr><tr><td/><td>(Free trial was also automatically applied)</td></tr>':'')):'Not migrated') + '</td></tr>' : 11107 '' 11108 ) + 11109 (update_preferred.prop('checked') ? '<tr><td>Preferred ISY:</td><td> ' + (data.preferredISY.updated?'Updated':'Not updated') + '</td></tr>' : '') + 11110 '</table>' 11111 11112 // msg += '<br/>Please don\'t forget to migrate your Node Servers.' 11113 11114 informationMessageDialog(msg, 'Success', 750) 11115 .then(function() { 11116 dialog.dialog('close'); 11117 }) 11118 .done() 11119 }) 11120 .fail(function(jqXHR, textStatus, errorThrown) { 11121 if (jqXHR.status===425) { // Passthrough message 11122 errorMessageDialog(jqXHR.responseText) 11123 } 11124 // Warning destination has records 11125 else if (jqXHR.status===424) { 11126 var response = JSON.parse(jqXHR.responseText) 11127 var fromExpired = response.fromExpired 11128 var licType = response.destinationLicenseType 11129 var licExpiry = response.destinationLicenseExpiry && new Date(response.destinationLicenseExpiry) 11130 // null: "no license", 11131 // 0: 'a trial', 11132 // 1: 'a license', 11133 // 2: 'renewals' 11134 11135 // Start a chain of promises 11136 $.Deferred().resolve().promise() 11137 .then(function () { 11138 // We display only one of the 2 warnings below 11139 if (fromExpired) { 11140 return errorMessageDialog('WARNING: The ISY you are migrating from has no ISY Portal license to migrate.</br></br>Continue with the other items?', 'Error', 600) 11141 } 11142 11143 // If destination ISY has a non-expired license, we are essentially merging licences. 11144 if ((licType === 1 || licType === 2) && licExpiry && licExpiry.getTime() > Date.now()) { 11145 return errorMessageDialog('WARNING: The ISY Portal license will be merged with your existing portal license permanently. You will not be able to migrate it back as is.</br></br>Continue and merge licenses?', 'Error', 600) 11146 } 11147 }) 11148 .then(function () { 11149 if (response.merge.length) { 11150 var merges = response.merge 11151 .map(function (item) { 11152 return item.count + ' ' + item.name 11153 }) 11154 11155 var last = merges.pop(); 11156 var mergeMessage = 'Destination ISY already has ' + 11157 merges.join(', ') + (merges.length ?' and ' : ' ') + last + 11158 '.' 11159 11160 return confirmationDialog('WARNING: ' + mergeMessage + '</br></br>Continue and merge?', 'Warning', 600) 11161 } 11162 }) 11163 .then(function () { 11164 submitProcess(data, true) 11165 }) 11166 .done(); 11167 } 11168 else { 11169 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 11170 } 11171 }) 11172 .done(); 11173 } 11174 11175 var isyOptions = { 11176 autoOpen: false, 11177 width: 450, 11178 height: 500, 11179 modal: true, 11180 buttons: { 11181 'Migrate': function() { 11182 submit(); 11183 }, 11184 Cancel: function() { 11185 $(this).dialog('close'); 11186 } 11187 }, 11188 open: function(event, ui) { 11189 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 11190 $('.custom-combobox-input').focus(); 11191 } 11192 }; 11193 11194 // Initializes the dialog 11195 dialog = $('#migrate_dialog').dialog(isyOptions); 11196 11197 isy.combobox({inputClass: ''}); 11198 11199 // Handles keyboad enter (submit event) 11200 form = dialog.find('form').submit(function(event) { 11201 event.preventDefault(); 11202 submit(); 11203 }); 11204 11205 $('#migrateHelp').click(function() { 11206 informationMessageDialog('ISY Migration allows to move ISY Portal configurations & access licence to a new ISY.<br/><br/>' + 11207 'Please keep in mind that a license move is permanent. It can\'t be migrated back to the original ISY.<br/><br/>' + 11208 'For more informations, please refer to the <a href="https://wiki.universal-devices.com/index.php?title=ISY_Portal_Migrate_configurations_to_a_new_ISY" target="_blank">Wiki instructions</a>', 'Migration Help', 600) 11209 }); 11210 11211 return { modal: dialog, setFields: setFields }; 11212}; 11213 11214 11215 11216// eisy-ui settings 11217var eisyuiSettingsDialogInit = function () { 11218 var dialog, 11219 form, 11220 uuid, 11221 domain, 11222 remoteLogging = $('#remoteLogging') 11223 11224 function setFields(obj) { 11225 domain = obj.domain 11226 uuid = obj.uuid 11227 remoteLogging.prop('checked', obj.settings.remoteLogging); 11228 } 11229 11230 function submit() {
11231 var valid = true; 11232 11233 var data = { 11234 uuid: uuid, 11235 domain: domain 11236 } 11237 11238 form.find('input').filter('[type="checkbox"]').each(function() { 11239 data[this.name] = this.checked; 11240 }) 11241 11242 ajaxJsonPost('/api/eisy/settings', data) 11243 .then(function(data, status, jqXHR) { 11244 informationMessageDialog('Settings saved', 'Success') 11245 .then(function() { 11246 dialog.dialog('close'); 11247 }) 11248 .done() 11249 }) 11250 .fail(function(jqXHR, textStatus, errorThrown) { 11251 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 11252 }) 11253 .done(); 11254 } 11255 11256 var isyOptions = { 11257 autoOpen: false, 11258 width: 450, 11259 height: 200, 11260 modal: true, 11261 buttons: { 11262 'Save': function() { 11263 submit(); 11264 }, 11265 Cancel: function() { 11266 $(this).dialog('close'); 11267 } 11268 }, 11269 open: function(event, ui) { 11270 dialog.dialog( 'option', 'position', { my: 'center', at: 'center', of: window, collision: 'fit' }); 11271 } 11272 }; 11273 11274 // Initializes the dialog 11275 dialog = $('#eisyui_settings_dialog').dialog(isyOptions); 11276 11277 // Handles keyboad enter (submit event) 11278 form = dialog.find('form').submit(function(event) { 11279 event.preventDefault(); 11280 submit(); 11281 }); 11282 11283 return { modal: dialog, setFields: setFields }; 11284}; 11285 11286 11287 11288// AMAZON ECHO Main Spoken Devices & Scenes mapping 11289var echoSpokenNodesDialogInit = function () { 11290 var dialog, 11291 uuid, 11292 domain, 11293 roomsPromise, 11294 lookupDataPromise, 11295 lookupDataPromisePgm, 11296 lookupDataPromiseStateVar, 11297 lookupDataPromiseNetworkRsc,
11298 lookupDataPromiseElk; 11299 11300 // var echoSubscriptionCheckbox = $('#echo_spoken_nodes_subscription'); 11301 11302 function loadRooms(uuid) { 11303 roomsPromise = ajaxJsonGet('/api/voice/amazon/spoken/rooms', $.param({ uuid: uuid, domain: domain })); 11304 11305 roomsPromise 11306 .fail(function(jqXHR, textStatus, errorThrown) { 11307 if (jqXHR.status==503) { 11308 errorMessageDialog('ISY is not online') 11309 } 11310 else if (jqXHR.status==504) { 11311 errorMessageDialog('Timeout occured. Please try again.') 11312 } 11313 else if (jqXHR.status==550) { // Custom error message 11314 var data = JSON.parse(jqXHR.responseText); 11315 11316 if (data.message) { 11317 errorMessageDialog(data.message) 11318 } 11319 else { 11320 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 11321 } 11322 } 11323 else { 11324 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 11325 } 11326 }) 11327 .done(); 11328 11329 return roomsPromise; 11330 } 11331 11332 function setFields(obj) { 11333 $('#echo_spoken_nodes_uuid').text(obj.uuid); 11334 $('#echo_spoken_nodes_alias').text(obj.alias); 11335 11336 // echoSubscriptionCheckbox.prop('checked', obj.subscriptionEnabled); 11337 // echoSubscriptionCheckbox.button('refresh'); 11338 // setSubscriptionButton(); 11339 11340 uuid = obj.uuid; 11341 domain = obj.domain; 11342 11343 lookupDataPromise = ajaxJsonGet('/api/nodes/lookup', $.param({ uuid: uuid, domain: domain })) 11344 .done(function(content, status, jqXHR){ 11345 content.data.devices.map(function (item) { 11346 var path = item.path.map(function(p) { return p.name }) 11347 .concat([ item.name ]) 11348 .join(' / '); 11349 11350 item.data = item.address; 11351 item.value = item.label = path + ' (' + item.address.replace(/ /g, '.') + ')'; 11352 item.option = item; // Self reference; 11353 }); 11354 11355 content.data.devices.sort(function(a, b) { 11356 return a.value.localeCompare(b.value) } 11357 ); 11358 11359 content.data.scenes.map(function (item) { 11360 item.data = item.address; 11361 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 11362 item.option = item; // Self reference; 11363 }); 11364 11365 content.data.scenes.sort(function(a, b) { 11366 return a.name.localeCompare(b.name) } 11367 ); 11368 }) 11369 .fail(function (jqXHR, textStatus, errorThrown) { 11370 jqXHR.requested = 'Devices'; // Track which request it is in the jqXHR object 11371 }); 11372 11373 lookupDataPromisePgm = ajaxJsonGet('/api/programs/lookup', $.param({uuid: uuid, domain: domain})) 11374 .done(function(content, status, jqXHR){ 11375 content.data.map(function (item) { 11376 item.data = item.address; 11377 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 11378 item.option = item; // Self reference; 11379 }); 11380 11381 content.data.sort(function(a, b) { 11382 return a.name.localeCompare(b.name) } 11383 ); 11384 }) 11385 .fail(function (jqXHR, textStatus, errorThrown) { 11386 jqXHR.requested = 'Programs'; // Track which request it is in the jqXHR object 11387 }); 11388 11389 lookupDataPromiseStateVar = ajaxJsonGet('/api/statevars/lookup', $.param({uuid: uuid, domain: domain})) 11390 .done(function(content, status, jqXHR){ 11391 content.data.map(function (item) { 11392 item.data = item.address; 11393 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 11394 item.option = item; // Self reference; 11395 }); 11396 11397 content.data.sort(function(a, b) { 11398 return a.name.localeCompare(b.name) } 11399 ); 11400 }) 11401 .fail(function (jqXHR, textStatus, errorThrown) { 11402 jqXHR.requested = 'State variables'; // Track which request it is in the jqXHR object 11403 jqXHR.skipErrorMessage = true; 11404 }); 11405 11406 lookupDataPromiseNetworkRsc = ajaxJsonGet('/api/networkrsc/lookup', $.param({uuid: uuid, domain: domain})) 11407 .done(function(content, status, jqXHR){ 11408 content.data.map(function (item) { 11409 item.data = item.address; 11410 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 11411 item.option = item; // Self reference; 11412 }); 11413 11414 content.data.sort(function(a, b) { 11415 return a.name.localeCompare(b.name) } 11416 ); 11417 }) 11418 .fail(function (jqXHR, textStatus, errorThrown) { 11419 jqXHR.requested = 'Network resources'; // Track which request it is in the jqXHR object 11420 jqXHR.skipErrorMessage = true; 11421 }); 11422
11423 lookupDataPromiseElk = ajaxJsonGet('/api/elk/lookup', $.param({uuid: uuid, domain: domain})) 11424 .done(function(content, status, jqXHR){ 11425 content.data.zones.map(function (item) { 11426 item.data = item.id; 11427 item.value = item.label = 'Zone ' + item.id + ' ' + item.name; 11428 item.option = item; // Self reference; 11429 }) 11430 11431// No need to sort, it's always by the zone number, which is good. 11432// content.data.sort(function(a, b) { 11433// return a.name.localeCompare(b.name) } 11434// ); 11435 }) 11436 .fail(function (jqXHR, textStatus, errorThrown) { 11437 jqXHR.requested = 'ELK topology'; // Track which request it is in the jqXHR object 11438 jqXHR.skipErrorMessage = true; 11439 }); 11440 11441 $.when(lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar, lookupDataPromiseNetworkRsc, lookupDataPromiseElk) 11442 .done(function() { 11443 // if (!obj.skillEnabled) { 11444 // errorMessageDialog('The Alexa smart home skill appears to be inactive. To enable it, go to the Alexa app and enable the skill named "ISY Optimized for Smart Home V3".', 'Alexa skill is not enabled', 650) 11445 // } 11446 }) 11447 .fail(function(jqXHR, textStatus, errorThrown) { 11448 if (jqXHR.status==503) { 11449 errorMessageDialog('ISY is not online') 11450 } 11451 else if (jqXHR.status==404) { 11452 // No need to warn the user about state variables not loading. It may be normal. 11453 if (!jqXHR.skipErrorMessage) { 11454 errorMessageDialog(jqXHR.requested + ' are not available') 11455 } 11456 } 11457 else { 11458 errorMessageDialog(jqXHR.requested + ' are not available<br>' + 'HTTP error ' + jqXHR.status, 'Error', 400) 11459 } 11460 }); 11461 11462 loadRooms(uuid); 11463 tableEchoSpokenNodes.setRooms(roomsPromise); 11464 tableEchoSpokenNodes.setLookupDataPromise(lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar, lookupDataPromiseNetworkRsc, lookupDataPromise
11464Elk); // Pass a reference to the promises, so that we can check it when we try to edit a spoken. 11465 11466 // setRooms && setLookupDataPromise must have been done first 11467 tableEchoSpokenNodes.setUuid(uuid, domain, true); 11468 } 11469 11470 // function setSubscriptionButton() { 11471 // if (echoSubscriptionCheckbox.is(":checked")) { 11472 // echoSubscriptionCheckbox.button('option', 'icons', { primary: "ui-icon-check" } ); 11473 // } 11474 // else { 11475 // echoSubscriptionCheckbox.button('option', 'icons', { primary: "ui-icon-closethick" } ); 11476 // } 11477 // } 11478 11479 var isyOptions = { 11480 autoOpen: false, 11481 width: 900, 11482 modal: true, 11483 buttons: { 11484 'Close': function() { 11485 $(this).dialog('close'); 11486 } 11487 }, 11488 open: function(event, ui) { 11489 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 11490 } 11491 }; 11492 11493 // Initializes the dialog 11494 dialog = $('#echo_spoken_nodes_dialog').dialog(isyOptions); 11495 11496 $("#echo_spoken_nodes_add").buttonset({items: 'button'}); 11497 11498 $('#echo_spoken_nodes_adddevice').click(function () { 11499 $(this).blur(); // Remove focus from button 11500 11501 $.when(lookupDataPromise, roomsPromise) 11502 .then(function(contentAjax, roomsAjax) { 11503 var content=contentAjax[0]; 11504 var rooms=roomsAjax[0]; 11505 11506 if (!content) { 11507 throw new Error('Devices not available'); 11508 } 11509 11510 echoSpokenNodesEditDialog.setFields({ category: 'std', uuid: uuid, domain: domain, lookup: content.data.devices, address: '', editMode:false, rooms: rooms.data }); 11511 echoSpokenNodesEditDialog.modal.dialog('open'); 11512 }) 11513 .fail(function(jqXHR, textStatus, errorThrown) { 11514 errorMessageDialog("Devices are not available", 'Error', 350) 11515 }) 11516 .done(); 11517 }); 11518 11519 $('#echo_spoken_nodes_addscene').click(function () { 11520 $(this).blur(); // Remove focus from button 11521 11522 $.when(lookupDataPromise, roomsPromise) 11523 .then(function(contentAjax, roomsAjax) { 11524 var content=contentAjax[0]; 11525 var rooms=roomsAjax[0]; 11526 11527 if (!content) { 11528 throw new Error('Devices not available'); 11529 } 11530 echoSpokenNodesEditDialog.setFields({ category: 'scene', uuid: uuid, domain: domain, lookup: content.data.scenes, address: '', editMode:false, rooms: rooms.data }); 11531 echoSpokenNodesEditDialog.modal.dialog('open'); 11532 }) 11533 .fail(function(jqXHR, textStatus, errorThrown) { 11534 errorMessageDialog("Devices are not available", 'Error', 350) 11535 }) 11536 .done(); 11537 }); 11538 11539 $('#echo_spoken_nodes_addprogram').click(function () { 11540 $(this).blur(); // Remove focus from button 11541 11542 $.when(lookupDataPromisePgm, roomsPromise) 11543 .then(function(contentAjax, roomsAjax) { 11544 var content=contentAjax[0]; 11545 var rooms=roomsAjax[0]; 11546 11547 if (!content) { 11548 throw new Error('Programs not available'); 11549 } 11550 echoSpokenNodesEditDialog.setFields({ category: 'program', uuid: uuid, domain: domain, lookup: content.data, address: '', editMode:false, rooms: rooms.data }); 11551 echoSpokenNodesEditDialog.modal.dialog('open'); 11552 }) 11553 .fail(function(jqXHR, textStatus, errorThrown) { 11554 errorMessageDialog("Programs are not available", 'Error', 350) 11555 }) 11556 .done(); 11557 }); 11558 11559 $('#echo_spoken_nodes_addstatevar').click(function () { 11560 $(this).blur(); // Remove focus from button 11561 11562 $.when(lookupDataPromiseStateVar, roomsPromise) 11563 .then(function(contentAjax, roomsAjax) { 11564 var content=contentAjax[0]; 11565 var rooms=roomsAjax[0]; 11566 11567 if (!content) { 11568 throw new Error('State Variables not available'); 11569 } 11570 11571 echoSpokenNodesEditDialog.setFields({ category: 'statevar', uuid: uuid, domain: domain, lookup: content.data, address: '', editMode:false, rooms: rooms.data }); 11572 echoSpokenNodesEditDialog.modal.dialog('open'); 11573 }) 11574 .fail(function(jqXHR, textStatus, errorThrown) { 11575 errorMessageDialog("State variables are not available", 'Error', 350) 11576 }) 11577 .done(); 11578 }); 11579 11580 $('#echo_spoken_nodes_addelk').click(function () { 11581 $(this).blur(); // Remove focus from button 11582
11583 $.when(lookupDataPromiseElk, roomsPromise) 11584 .then(function(contentAjax, roomsAjax) { 11585 var content=contentAjax[0]; 11586 var rooms=roomsAjax[0]; 11587 11588// if (!content.zones) { 11589// throw new Error('ELK Zones not available'); 11590// } 11591 11592 echoSpokenNodesEditDialog.setFields({ category: 'elk', uuid: uuid, domain: domain, lookup: content.data.zones, address: '', editMode:false, rooms: rooms.data }); 11593 echoSpokenNodesEditDialog.modal.dialog('open'); 11594 }) 11595 .fail(function(jqXHR, textStatus, errorThrown) { 11596 errorMessageDialog("ELK zones are not available", 'Error', 350) 11597 }) 11598 .done(); 11599 }); 11600 11601 $('#echo_spoken_nodes_add_av').click(function () { 11602 $(this).blur(); // Remove focus from button 11603 11604 $.when(lookupDataPromiseStateVar, lookupDataPromisePgm, lookupDataPromiseNetworkRsc, roomsPromise) // lookupDataPromise, 11605 .then(function(vars, pgms, net, rooms) { 11606// var content=contentNodes[0]; 11607// var rooms=roomsAjax[0]; 11608 11609 echoSpokenNodesAVEditDialog.setFields({ category: 'av', uuid: uuid, domain: domain, lookupVars: vars[0], lookupPgms: pgms[0], lookupNet: net[0], rooms: rooms[0].data, editMode:false }); // lookupNodes: nodes[0], 11610 echoSpokenNodesAVEditDialog.modal.dialog('open'); 11611 }) 11612 .fail(function(jqXHR, textStatus, errorThrown) { 11613 11614 lookupDataPromiseStateVar 11615 .fail(function() { 11616 errorMessageDialog("State variables are not available", 'Error', 350) 11617 }) 11618 .done(); 11619 11620 lookupDataPromisePgm 11621 .fail(function() { 11622 errorMessageDialog("Programs are not available", 'Error', 350) 11623 }) 11624 .done(); 11625 11626 lookupDataPromiseNetworkRsc 11627 .fail(function() { 11628 errorMessageDialog("Network resources are not available", 'Error', 350) 11629 }) 11630 .done(); 11631 }) 11632 .done(); 11633 }); 11634 11635 $('#echo_spoken_nodes_deleteall').button({ icons: {primary: 'ui-icon-custom-delete'}, text: false}).click(function () { 11636 $(this).blur(); // Remove focus from button 11637 11638 confirmationDialog('All of your Echo spokens for this ISY will be deleted. Are you sure?', 'Delete spokens?') 11639 .then(function () { 11640 ajaxJsonDelete('/api/voice/amazon/spoken/nodes/deleteall', $.param({uuid: uuid, domain: domain })) 11641 .done(function () { 11642 tableEchoSpokenNodes.reload(); 11643 }) 11644 .fail(function(jqXHR, textStatus, errorThrown) { 11645 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 11646 }) 11647 }) 11648 .done(); 11649 }); 11650 11651 $('#echo_spoken_nodes_download').button({ icons: {primary: 'ui-icon-custom-download'}, text: false}).click(function () { 11652 $(this).blur(); // Remove focus from button 11653 11654 informationMessageDialog('Press Ok to download to a CSV file.', 'Export spokens') 11655 .then(function () { 11656 try { 11657 var isFileSaverSupported = !!new Blob; 11658 } catch (e) {} 11659 11660 if (isFileSaverSupported) { 11661 var str = 'Device name,Address,Category,Room,Spoken,Spoken-2,Spoken-3,Spoken-4,Spoken-5\r\n'; 11662 11663 tableEchoSpokenNodes.data.data.forEach(function(spoken) { 11664 str += [spoken.name, spoken.address, spoken.category==='std'?'device':spoken.category, spoken.roomName].concat(spoken.spoken).join(',') + '\r\n' 11665 }); 11666 11667 var BOM = '\uFEFF'; 11668 var blob = new Blob([BOM + str], {type: "application/csv;charset=utf-8"}); 11669 saveAs(blob, "Spokens_" + uuidNC(uuid) + ".csv"); 11670 } 11671 else { 11672 errorMessageDialog('Feature not supported on this browser', 'Not supported'); 11673 } 11674 }) 11675 .done(); 11676 }); 11677 11678 $('#echo_spoken_nodes_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 11679 $(this).blur(); // Remove focus from button 11680 tableEchoSpokenNodes.reload(); 11681 }); 11682 11683 // echoSubscriptionCheckbox.button({ label: "Live update" }).click(function(event) { 11684 // setSubscriptionButton(); 11685 // ajaxJsonPost('/api/voice/amazon/spoken/subscription/state', $.param({ 11686 // uuid: uuid, 11687 // domain: domain, 11688 // state: echoSubscriptionCheckbox.is(":checked") 11689 // })) 11690 // .fail(function(jqXHR, textStatus, errorThrown) { 11691 // if (jqXHR.status==404) { 11692 // echoSubscriptionCheckbox.prop('checked', false); 11693 // echoSubscriptionCheckbox.button('refresh'); 11694 // setSubscriptionButton(); 11695 // errorMessageDialog('The Alexa smart home skill appears to be inactive. To enable it, go to the Alexa app and enable the skill named "ISY Optimized for Smart Home V3".<br/><br/>Once the skill is enabled and devices has been discovered by Alexa, you can enable Live update.', 'Live update error', 650) 11696 // } 11697 // else { 11698 // errorMessageDialog('API error; HTTP error ' + jqXHR.status) 11699 // } 11700 // }) 11701 // .done(); 11702 // $(this).blur(); // Remove focus from button 11703 // }); 11704 11705 // $('#echo_spoken_nodes_subscription_help').click(function() { 11706 // informationMessageDialog('When live update is enabled, your devices status will be sent in real-time to your echo app. This is required to get motion and contact sensors to trigger Alexa routines.', 'Live update', 650) 11707 // }); 11708 11709 return { modal: dialog, setFields: setFields, loadRooms: loadRooms }; 11710}; 11711 11712// AMAZON ECHO Spoken Devices & Scenes mapping 11713var echoSpokenNodesEditDialogInit = function () { 11714 var dialog, 11715 form, 11716 pk, 11717 uuid, 11718 domain, 11719 currentRooms, 11720 category, 11721 stdLookup, 11722 editMode, 11723 userCat, // Follows the user cat selector 11724 device = $('#echo_spoken_address'), 11725 room = $('#echo_spoken_room'), 11726 turnon_value = $('#echo_spoken_turnon_value'), 11727 turnoff_value = $('#echo_spoken_turnoff_value'), 11728 motion_value = $('#echo_spoken_motion_value'), 11729 colorManufacturer = $('#echo_spoken_color_input_selector'), 11730 spokens = [], 11731 allFields, 11732 tips = $('#echo_spoken_nodes_edit_dialog .validateTips'), 11733 isColor, 11734 colorMfr, // Manufacturer - selected option 11735 colorMfrOptions = [ 11736 { value: '$select', description: 'Select one...' }, 11737 { value: 'AEOTEC', description: 'Aeotec' }, 11738 { value: 'FIBARO', description: 'Fibaro' }, 11739 { value: 'ZWGEN', description: 'Z-Wave Generic' } 11740 ]; 11741 11742 spokens[0] = $('#echo_spoken_spoken') 11743 spokens[1] = $('#echo_spoken_spoken2') 11744 spokens[2] = $('#echo_spoken_spoken3') 11745 spokens[3] = $('#echo_spoken_spoken4') 11746 spokens[4] = $('#echo_spoken_spoken5') 11747 11748 allFields = $([]).add(device).add(room).add(turnon_value).add(turnoff_value).add(motion_value).add(colorManufacturer).add(spokens[0]).add(spokens[1]).add(spokens[2]).add(spokens[3]).add(spokens[4]).add(spokens[5]); 11749 11750 var categoryLabel = { 11751 'std' : 'device',
11752 'scene' : 'scene', 11753 'program' : 'program', 11754 'statevar' : 'state variable', 11755 'elk' : 'ELK zone' 11756 }; 11757 11758 // Updates the room selector 11759 function updateRoomSelector(rooms, choice) { 11760 // label: shown in the selections 11761 // value: displayed in the combobox input field (this.input) 11762 // data: real data assigned to the input field (this.element) 11763 11764 rooms = rooms.map(function(item) { 11765 return {label: item.name, value: item.name, data: item._id} 11766 }); 11767 11768 rooms.sort(function(a, b) { 11769 return a.label.localeCompare(b.label) 11770 }); 11771 11772 rooms.unshift({ label: '(No room)', value: '', data: '$noroom'}); 11773 11774 room.combobox("option", "source", rooms); 11775 room.combobox("option", "data", choice); 11776 11777 currentRooms = rooms; 11778 } 11779 11780 // Set isColor based on the address and Show/Hide the colorMfr selectmenu 11781 function setIsColor(address) { 11782 if (address) { 11783 var addressSplit = address.split('_'); 11784 isColor = (addressSplit.length===2 && addressSplit[1].slice(0,3)==='186') 11785 } 11786 else { 11787 isColor=false; 11788 } 11789 11790 if (isColor) { 11791 $('#echo_spoken_color_input').show(); 11792 } 11793 else { 11794 $('#echo_spoken_color_input').hide(); 11795 } 11796 } 11797 11798 function displayValueFields(userCat) { 11799 if (userCat==='motion' || userCat==='contact') { 11800 $('#echo_spoken_statevar_input_onoff').hide(); 11801 $('#echo_spoken_statevar_input_motion').show(); 11802 $('#echo_spoken_motion_label').text(userCat==='motion'?'Motion detected value':'Contact closed value') 11803 } 11804 else { 11805 $('#echo_spoken_statevar_input_onoff').show(); 11806 $('#echo_spoken_statevar_input_motion').hide(); 11807 } 11808 } 11809 11810 // Initialize form with new values 11811 function setFields(obj) { 11812 var label; 11813 11814 uuid = obj.uuid; 11815 domain = obj.domain; 11816 pk = obj.pk; 11817 category = obj.category; // 4 possibilities: 'statevar', 'program', 'scene', 'std' 11818 stdLookup = obj.lookup; 11819 11820 form[0].reset(); 11821 device.combobox("option", "source", obj.lookup); 11822 device.combobox("option", "data", obj.address); 11823 turnon_value.val(obj.turnon_value); 11824 turnoff_value.val(obj.turnoff_value); 11825 motion_value.val(obj.motion_value); 11826 11827 for (var i=0; i<5; i++) { 11828 if (obj.spokens && i<=obj.spokens.length) { 11829 spokens[i].val(obj.spokens[i]); 11830 } 11831 else { 11832 spokens[i].val(''); 11833 } 11834 } 11835 11836 editMode = obj.editMode; 11837 11838 // Initially, the userCat selectmenu is not available for standard devices. 11839 $('#echo_spoken_user_cat_input').show(); 11840 11841 // statevars, programs and scenes used to be a scene by default. 11842 // If userCat is not available, and we are editing, set the userCat default to 'scene' 11843 if (['statevar', 'program', 'scene'].includes(category) && !obj.userCat) { 11844 obj.userCat = 'scene'; 11845 } 11846 11847 buildUserCatSelect(categoryOverride(category, stdLookup, obj.address, isColor), obj.userCat); 11848 11849 if (editMode) { 11850 tips.text('Please choose a spoken for this ' + categoryLabel[category] + '.'); 11851 spokens[0].focus(); 11852 11853 setIsColor(obj.address); 11854 if (isColor) { 11855 colorMfr=obj.colorMfr?obj.colorMfr:'$select'; 11856 } 11857 } 11858 else { 11859 tips.text('Please choose a ' + categoryLabel[category] +' and enter a corresponding spoken.'); 11860 setIsColor(false); 11861 colorMfr='$select'; 11862 } 11863 11864 // Takes the one loaded, or the one by default if new 11865 // userCat = $('#echo_spoken_user_cat_selector select').val(); // Take the one from the initialize 11866 11867 if (category==='statevar') { 11868 displayValueFields(userCat); // If user cat is not set, we will display the turn on/off fields, not motion 11869 $('#echo_spoken_statevar_input').show(); 11870 } 11871 else { 11872 $('#echo_spoken_statevar_input').hide(); 11873 } 11874 11875 $('#echo_spoken_color_input_selector select').val(colorMfr); 11876 $('#echo_spoken_color_input_selector select').selectmenu('refresh'); 11877 11878 // Disable field if editing 11879 device.combobox("disabled", editMode); 11880 11881 $('#echo_spoken_lookup_label').text('My ' + categoryLabel[category]); 11882 allFields.removeClass('fieldfocus'); 11883 11884 if (!editMode) { 11885 obj.room = '$noroom'; 11886 } 11887 11888 updateRoomSelector(obj.rooms, obj.room); 11889 } 11890 11891 // Restricts the userCat select to valid categories. (If the device is a thermostat, lock or color bulb) 11892 function categoryOverride(category, stdLookup, stdAddress, isColor) { 11893 if (category==='std') { 11894 11895 // See if the individual device is a lock or thermostat, and use that device category instead ('lock' or 'thermostat') 11896 var option = stdLookup.find(function(item) { 11897 return item.address === stdAddress; 11898 }); 11899 11900 if (option) { 11901 category = option.category; 11902 } 11903 11904 // Check if current device is a color bulb 11905 if (isColor) { 11906 category = 'color' 11907 } 11908 } 11909 11910 return category; 11911 } 11912 11913 function buildUserCatSelect(category, setUserCatTo) { 11914 // The default option is the first one. 11915 var userCats = { 11916 'std': [ { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'fan', desc: 'Fan' }, { option:'openClose', desc: 'Device with Open/Close syntax' }, { option:'lowerRaise', desc: 'Device with Set/Lower/Raise syntax' }, { option:'motion', desc: 'Motion Sensor' }, { option:'contact', desc: 'Contact Sensor' } ], 11917 'color': [ { option:'light', desc: 'Light' } ], 11918 'lock': [ { option:'lock', desc: 'Lock' } ], 11919 'thermostat': [ { option:'thermostat', desc: 'Thermostat' } ], 11920 'scene': [ { option:'scene', desc: 'Scene' }, { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'openClose', desc: 'Device with Open/Close syntax' } ], 11921 'program': [ { option:'scene', desc: 'Scene' }, { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'openClose', desc: 'Device with Open/Close syntax' } ], 11922 'statevar': [ { option:'scene', desc: 'Scene' }, { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'openClose', desc: 'Device with Open/Close syntax' }, { option:'lowerRaise', desc: 'Device with Lower/Raise syntax' }, { option:'motion', desc: 'Motion Sensor' }, { option:'contact', desc: 'Contact Sensor' } ], 11923 'elk': [ { option:'motion', desc: 'Motion Sensor' }, { option:'contact', desc: 'Contact Sensor' } ] 11924 }; 11925 11926 if (userCats[category]) { 11927 var $select = $('<select></select>'); 11928
11929 userCats[category].forEach(function(item) { 11930 $select.append('<option value="' + item.option + '">' + item.desc + '</option>'); 11931 }); 11932 11933 // is the userCat selector changing? 11934 if (!selectHasIdenticalOptions($select, $('#echo_spoken_user_cat_selector'))) { 11935 // Default option is the first 11936 $('#echo_spoken_user_cat_selector').empty().append($select.clone()); 11937 $('#echo_spoken_user_cat_selector select').selectmenu({ 11938 width:'22em', 11939 change: function (event, ui) { 11940 userCat = ui.item.value; 11941 displayValueFields(userCat) 11942 } 11943 }); 11944 11945 if (setUserCatTo) { 11946 setUserCatSelect(setUserCatTo) 11947 } 11948 } 11949 else { 11950 if (setUserCatTo) { 11951 setUserCatSelect(setUserCatTo) 11952 } else { 11953 // Default to first option 11954 setUserCatSelect($('#echo_spoken_user_cat_selector option').eq(0).attr('value')) 11955 } 11956 } 11957 } 11958 else { 11959 $('#echo_spoken_user_cat_input').hide(); 11960 $('#echo_spoken_user_cat_selector').empty(); // Destroy the selector. 11961 } 11962 } 11963 11964 function setUserCatSelect(option) { 11965 var select = '#echo_spoken_user_cat_selector select'; 11966 11967 if (option && option!=='') { 11968 $(select).val(option); // Set user category select 11969 $(select).selectmenu("refresh"); 11970 11971 // If option is not valid 11972 if (!$(select).val()) { 11973 // Selects the first option 11974 $(select).val($(select + ' option:first').val()); 11975 } 11976 11977 } 11978 else { 11979 // Selects the first option by default 11980 $(select).val($(select + ' option:first').val()); 11981 } 11982 11983 $(select).selectmenu('refresh'); 11984 userCat = $(select).val(); 11985 } 11986 11987 // Validation function 11988 function checkColorMfr(tips, field) { 11989 if (isColor && colorMfr==='$select') { 11990 field.focus(); 11991 updateTips(tips, 'Device manufacturer is required.'); 11992 return false; 11993 } 11994 else { 11995 return true; 11996 } 11997 } 11998 11999 // Validate and save! 12000 function submit() { 12001 var valid = true; 12002 12003 device.combobox("refresh"); // Makes sure the real input field is up to date 12004 if (!device.combobox("validate")) { 12005 updateTips(tips, 'Device is not allowed'); 12006 valid=false; 12007 } 12008 12009 room.combobox("refresh"); // Makes sure the real input field is up to date 12010 if (!room.combobox("validate")) { 12011 12012 var name = room.combobox("getValue"); 12013 12014 confirmationDialog('Room ' + name + ' does not exists.<br/><br/>Create it?', 'Create room?') 12015 .then(function () { 12016 ajaxJsonPut('/api/voice/amazon/spoken/room', $.param({domain: domain, uuid: uuid, name: name })) 12017 .done(function (record) { 12018 var roomsPromise = echoSpokenNodesDialog.loadRooms(uuid); 12019 12020 roomsPromise 12021 .then(function(data) { 12022 updateRoomSelector(data.data, record.data._id); 12023 }); 12024 12025 tableEchoSpokenNodes.setRooms(roomsPromise); 12026 }) 12027 .fail(function(jqXHR, textStatus, errorThrown) { 12028 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 12029 }) 12030 }) 12031 .done(); 12032 12033 12034 valid=false; 12035 } 12036 12037 allFields.removeClass('fieldfocus'); 12038 12039 if (category==='statevar') { 12040 if (userCat==='motion') { 12041 valid = valid && checkNotEmpty(tips, motion_value, 'Motion detected value'); 12042 valid = valid && checkNumber(tips, motion_value, 'Motion detected value', 0, 2147483647); 12043 } 12044 else if (userCat==='contact') { 12045 valid = valid && checkNotEmpty(tips, motion_value, 'Contact closed value'); 12046 valid = valid && checkNumber(tips, motion_value, 'Contact closed value', 0, 2147483647); 12047 } 12048 else { 12049 valid = valid && checkNotEmpty(tips, turnoff_value, 'Turn off value'); 12050 valid = valid && checkNumber(tips, turnoff_value, 'Turn off value', 0, 2147483647); 12051 valid = valid && checkNotEmpty(tips, turnon_value, 'Turn on value'); 12052 valid = valid && checkNumber(tips, turnon_value, 'Turn on value', 0, 2147483647); 12053 } 12054 } 12055 12056 valid = valid && checkColorMfr(tips, colorManufacturer); 12057 valid = valid && checkLength(tips, spokens[0], 'Spoken', 2, 40); 12058 valid = valid && checkRegexp(tips, spokens[0], spokenRegex, 'Spoken must contain only letters with spaces'); 12059 valid = valid && checkLength(tips, spokens[1], 'Alternate spoken 1', 0, 40); 12060 valid = valid && checkRegexp(tips, spokens[1], spokenRegex, 'Spoken must contain only letters with spaces'); 12061 valid = valid && checkLength(tips, spokens[2], 'Alternate spoken 2', 0, 40); 12062 valid = valid && checkRegexp(tips, spokens[2], spokenRegex, 'Spoken must contain only letters with spaces'); 12063 valid = valid && checkLength(tips, spokens[3], 'Alternate spoken 3', 0, 40); 12064 valid = valid && checkRegexp(tips, spokens[3], spokenRegex, 'Spoken must contain only letters with spaces'); 12065 valid = valid && checkLength(tips, spokens[4], 'Alternate spoken 4', 0, 40); 12066 valid = valid && checkRegexp(tips, spokens[4], spokenRegex, 'Spoken must contain only letters with spaces'); 12067 12068 if (valid) { 12069 var data = form.find('input').serialize(); 12070 data += '&uuid=' + uuid + '&domain=' + domain + (isColor?'&colorMfr='+colorMfr:''); 12071 12072 if (editMode) { 12073 data += '&id=' + pk; 12074 } 12075 12076 data += '&category=' + category; // valid values: program, scene, std 12077 12078 if (userCat) { 12079 data += '&userCat=' + userCat; 12080 } 12081
12082 (editMode?ajaxJsonPost('/api/voice/amazon/spoken/node', data):ajaxJsonPut('/api/voice/amazon/spoken/node', data)) 12083 .then(function(data, status, jqXHR) { 12084 dialog.dialog('close'); 12085 tableEchoSpokenNodes.reload(); 12086 }) 12087 .fail(function(jqXHR, textStatus, errorThrown) { 12088 if (jqXHR.status==503) { 12089 updateTips(tips, 'ISY is not online'); 12090 } 12091 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 12092 var data = JSON.parse(jqXHR.responseText); 12093 12094 if (data.message) { 12095 updateTips(tips, data.message); 12096 } 12097 else { 12098 updateTips(tips, 'HTTP error ' + jqXHR.status); 12099 } 12100 } 12101 else if (jqXHR.status==400) { 12102 updateTips(tips, 'HTTP error ' + jqXHR.status + ' : ' + jqXHR.responseText); 12103 } 12104 else if (jqXHR.status===412) { 12105 if (skillNotLinkedWarningAddUpdate) { 12106 var skipButton = 'Skip Warning' 12107 var okButton = 'Ok' 12108 var buttons = [ skipButton, okButton ] 12109 // Save successful, but skill is not linked 12110 optionsDialog('The Alexa skill is not linked.<br/><br/>You can continue to add/edit your spoken mapping. When you are ready to link the skill, your devices configured here will be synchronized to Alexa.', 'Skill not linked', buttons) 12111 .then(function (button) { 12112 if (button === skipButton) { 12113 // On next edit, there will be no warning 12114 skillNotLinkedWarningAddUpdate = false 12115 } 12116 }) 12117 .always(function () { 12118 // Note: If dialog was closed, promise is rejected. 12119 dialog.dialog('close'); 12120 tableEchoSpokenNodes.reload(); 12121 }) 12122 .done(); 12123 } else { 12124 dialog.dialog('close'); 12125 tableEchoSpokenNodes.reload(); 12126 } 12127 } 12128 else if (jqXHR.status===428) { 12129 if (incorrectUnitWarningAddUpdate) { 12130 var skipButton = 'Skip Warning' 12131 var okButton = 'Ok' 12132 var buttons = [ skipButton, okButton ] 12133 // Save successful, but skill is not linked 12134 optionsDialog('This user profile ' + registeredUser.username + ' cannot manage this ISY.<br/><br/>' + 12135 'If Alexa is linked with another user profile in this account, then you can ask Alexa "Discover my devices" to reflect the changes you made in Alexa.<br/><br/>' + 12136 'If on the other hand Alexa is linked with this user profile and you want it to control this ISY, then click on My Profile to change the preferred ISY to this ISY.<br/><br/>', 'Updating incorrect ISY', buttons, 800) 12137 .then(function (button) { 12138 if (button === skipButton) { 12139 // On next edit, there will be no warning 12140 incorrectUnitWarningAddUpdate = false 12141 } 12142 }) 12143 .always(function () { 12144 // Note: If dialog was closed, promise is rejected. 12145 dialog.dialog('close'); 12146 tableEchoSpokenNodes.reload(); 12147 }) 12148 .done(); 12149 } else { 12150 dialog.dialog('close'); 12151 tableEchoSpokenNodes.reload(); 12152 } 12153 } 12154 else { 12155 updateTips(tips, 'HTTP error ' + jqXHR.status); 12156 } 12157 }) 12158 .done(); 12159 } 12160 } 12161 12162 var isyOptions = { 12163 autoOpen: false, 12164 width: 950, 12165 modal: true, 12166 buttons: { 12167 'Save': function() { 12168 submit(); 12169 } 12170 }, 12171 open: function(event, ui) { 12172 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 12173 } 12174 }; 12175 12176 // Initializes the dialog 12177 dialog = $('#echo_spoken_nodes_edit_dialog').dialog(isyOptions); 12178 12179 // Handles keyboad enter (submit event) 12180 form = dialog.find('form').submit(function(event) { 12181 event.preventDefault(); 12182 submit(); 12183 }); 12184 12185 $('#echo_spoken_room_add').button({ icons: {primary: 'ui-icon-plus'}, text: false}).tooltip().click(function(event) { 12186 $(this).blur(); // Remove focus from button 12187 echoSpokenRoomEditDialog.setFields({ uuid: uuid, editMode: false, domain: domain }); 12188 echoSpokenRoomEditDialog.modal.dialog('open'); 12189 }); 12190 12191 $('#echo_spoken_room_edit').button({ icons: {primary: 'ui-icon-pencil'}, text: false}).tooltip().click(function(event) { 12192 $(this).blur(); // Remove focus from button 12193 12194 room.combobox("refresh"); // Makes sure the real input field is up to date 12195 var pk = room.val(); 12196 12197 var item = currentRooms.find(function(item) { 12198 return item.data === pk; 12199 }); 12200 12201 if (pk!=='$noroom' && item) { 12202 echoSpokenRoomEditDialog.setFields({ uuid: uuid, editMode: true, domain: domain, pk: pk, name: item.label }); 12203 echoSpokenRoomEditDialog.modal.dialog('open'); 12204 } 12205 else { 12206 informationMessageDialog("This can't be edited.") 12207 } 12208 }); 12209 12210 $('#echo_spoken_room_delete').button({ icons: {primary: 'ui-icon-trash'}, text: false}).tooltip().click(function(event) { 12211 $(this).blur(); // Remove focus from button 12212 12213 room.combobox("refresh"); // Makes sure the real input field is up to date 12214 var pk = room.val(); 12215 12216 var item = currentRooms.find(function(item) { 12217 return item.data === pk; 12218 }); 12219 12220 if (pk!=='$noroom' && item) { 12221 confirmationDialog('Delete room ' + item.label + '.<br/><br/>Are you sure?', 'Delete room?') 12222 .then(function () {
12223 ajaxJsonDelete('/api/voice/amazon/spoken/room', $.param({id: pk, domain: domain, uuid: uuid })) 12224 .done(function () { 12225 var roomsPromise = echoSpokenNodesDialog.loadRooms(uuid); 12226 12227 roomsPromise 12228 .then(function(data) { 12229 echoSpokenNodesEditDialog.updateRoomSelector(data.data, '$noroom'); 12230 echoSpokenNodesAVEditDialog.updateRoomSelector(data.data, '$noroom'); 12231 }); 12232 12233 tableEchoSpokenNodes.setRooms(roomsPromise); 12234 tableEchoSpokenNodes.reload(); 12235 }) 12236 .fail(function(jqXHR, textStatus, errorThrown) { 12237 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 12238 }) 12239 }) 12240 .done(); 12241 } 12242 else { 12243 informationMessageDialog("This can't be removed.") 12244 } 12245 }); 12246 12247 $('#echo_spokenStatevarHelp').click(function() { 12248 informationMessageDialog('When you turn on or turn off this "device", the state variable will be set to the one of these values. However, if you turn on to X percent, the variable will be set to the percent value.', 'Help', 550) 12249 }); 12250 12251 $('#echo_spokenStatevarMotionHelp').click(function() { 12252 var message = userCat=='motion'?'When the state variable is set to the motion detected value, the Alexa motion detector will be triggered.': 12253 'When the state variable is set to the Contact closed value, the Alexa contact sensor will be closed. Any other value means the contact is open.'; 12254 12255 informationMessageDialog(message, 'Help', 550) 12256 }); 12257 12258 device.combobox({ 12259 inputClass: '', 12260 focusout: function(event) { 12261 device.combobox('refresh'); 12262 12263 if (category==='std') { 12264 if (deviceHasColorNode(device.val(), stdLookup)) { 12265 var colorAddress = device.val().split('_')[0]+'_186'; 12266 12267 informationMessageDialog('This device supports color. However, to enable color support, you need to choose the device whose address ends with _186. Do you want to use the device ' + colorAddress + ' instead?', 'Color support?', 600) 12268 .then(function () { 12269 device.combobox("option", "data", colorAddress); 12270 device.combobox("refresh"); // Makes sure the real input field is up to date 12271 setIsColor(colorAddress); 12272 }) 12273 .done(); 12274 } 12275 else { 12276 setIsColor(device.val()); 12277 } 12278 12279 // If the device has change, see if this is a lock or thermostat, and if so, enforce the user cat to be lock or thermostat. 12280 if (device.combobox("validate")) { 12281 buildUserCatSelect(categoryOverride(category, stdLookup, $('#echo_spoken_address').val(), isColor), userCat); 12282 } 12283 } 12284 } 12285 }); 12286 room.combobox({inputClass: ''}); 12287 12288 buildSelect(colorMfrOptions, '#echo_spoken_color_input_selector', '15em', function(event, ui) { 12289 colorMfr = ui.item.value; 12290 }); 12291 12292 return { modal: dialog, setFields: setFields, updateRoomSelector: updateRoomSelector }; 12293}; 12294 12295// AMAZON ECHO Spoken room edit dialog 12296var echoSpokenRoomEditDialogInit = function () { 12297 var dialog, 12298 form, 12299 pk, 12300 uuid, 12301 domain, 12302 editMode, 12303 name = $('#echo_spoken_room_name'), 12304 allFields = $([]).add(name), 12305 tips = $('#echo_spoken_room_edit_dialog .validateTips'); 12306 12307 function setFields(obj) { 12308 var label; 12309 12310 uuid = obj.uuid; 12311 domain = obj.domain; 12312 pk = obj.pk; 12313 12314 form[0].reset(); 12315 name.val(obj.name); 12316 12317 editMode = obj.editMode; 12318 12319 tips.text('Please enter a room name'); 12320 12321 allFields.removeClass('fieldfocus'); 12322 } 12323 12324 function submit() { 12325 var valid = true; 12326 12327 allFields.removeClass('fieldfocus'); 12328 12329 valid = valid && checkLength(tips, name, 'Name', 2, 40); 12330 valid = valid && checkRegexp(tips, name, roomNameRegex, 'room name is not valid.' ); 12331 12332 if (valid) { 12333 var data = form.find('input').serialize(); 12334 data += '&domain=' + domain + '&uuid=' + uuid; 12335 12336 if (editMode) { 12337 data += '&id=' + pk; 12338 } 12339 12340 var url = '/api/voice/amazon/spoken/room'; 12341 12342 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 12343 .then(function(roomRecord, status, jqXHR) { 12344 12345 tableEchoSpokenNodes.reload(); 12346 12347 var roomsPromise = echoSpokenNodesDialog.loadRooms(uuid); 12348 tableEchoSpokenNodes.setRooms(roomsPromise); // Will be available when we edit! 12349 12350 roomsPromise.done(function(data) { 12351 var newRoom=null; 12352 12353 if (roomRecord.data && roomRecord.data._id) { 12354 newRoom = roomRecord.data._id; 12355 } 12356 12357 echoSpokenNodesEditDialog.updateRoomSelector(data.data, newRoom); 12358 echoSpokenNodesAVEditDialog.updateRoomSelector(data.data, newRoom); 12359 dialog.dialog('close'); 12360 }) 12361 }) 12362 .fail(function(jqXHR, textStatus, errorThrown) { 12363 if (jqXHR.status==503) { 12364 updateTips(tips, 'ISY is not online'); 12365 } 12366 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 12367 var data = JSON.parse(jqXHR.responseText); 12368 12369 if (data.message) { 12370 updateTips(tips, data.message); 12371 } 12372 else { 12373 updateTips(tips, 'HTTP error ' + jqXHR.status); 12374 } 12375 } 12376 else { 12377 updateTips(tips, 'HTTP error ' + jqXHR.status); 12378 } 12379 }) 12380 .done(); 12381 } 12382 } 12383 12384 var isyOptions = { 12385 autoOpen: false, 12386 width: 400, 12387 modal: true, 12388 buttons: { 12389 'Save': function() { 12390 submit(); 12391 } 12392 }, 12393 open: function(event, ui) { 12394 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 12395 } 12396 }; 12397 12398 // Initializes the dialog 12399 dialog = $('#echo_spoken_room_edit_dialog').dialog(isyOptions); 12400 12401 // Handles keyboad enter (submit event) 12402 form = dialog.find('form').submit(function(event) { 12403 event.preventDefault(); 12404 submit(); 12405 }); 12406 12407 return { modal: dialog, setFields: setFields }; //, updateRoomSelector: updateRoomSelector 12408}; 12409 12410 12411// GOOGLE HOME Main Spoken Devices & Scenes mapping 12412var ghSpokenNodesDialogInit = function () { 12413 var dialog, 12414 uuid, 12415 domain, 12416 roomsPromise, 12417 lookupDataPromise, 12418 lookupDataPromisePgm, 12419 lookupDataPromiseStateVar; 12420 12421 function loadRooms(uuid) { 12422 roomsPromise = ajaxJsonGet('/api/voice/google/spoken/rooms', $.param({ uuid: uuid, domain: domain })); 12423 12424 roomsPromise 12425 .fail(function(jqXHR, textStatus, errorThrown) { 12426 if (jqXHR.status==503) { 12427 errorMessageDialog('ISY is not online') 12428 } 12429 else if (jqXHR.status==504) { 12430 errorMessageDialog('Timeout occured. Please try again.') 12431 } 12432 else if (jqXHR.status==550) { // Custom error message 12433 var data = JSON.parse(jqXHR.responseText); 12434 12435 if (data.message) { 12436 errorMessageDialog(data.message) 12437 } 12438 else { 12439 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 12440 } 12441 } 12442 else { 12443 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 12444 } 12445 }) 12446 .done(); 12447 12448 return roomsPromise; 12449 } 12450 12451 function setFields(obj) { 12452 $('#gh_spoken_nodes_uuid').text(obj.uuid); 12453 $('#gh_spoken_nodes_alias').text(obj.alias); 12454 12455 uuid = obj.uuid; 12456 domain = obj.domain; 12457 12458 lookupDataPromise = ajaxJsonGet('/api/nodes/lookup', $.param({ uuid: uuid, domain: domain })) 12459 .done(function(content, status, jqXHR){
12460 content.data.devices.map(function (item) { 12461 var path = item.path.map(function(p) { return p.name }) 12462 .concat([ item.name ]) 12463 .join(' / '); 12464 12465 item.data = item.address; 12466 item.value = item.label = path + ' (' + item.address.replace(/ /g, '.') + ')'; 12467 item.option = item; // Self reference; 12468 }); 12469/* 12470 content.data.devices = content.data.devices.filter(function(item) { 12471 var addressSplit = item.address.split('_'); 12472 if (addressSplit.length==2) { // This is a ZWave device 12473 if (!['1'].includes(addressSplit[1])) { 12474 return false // We exclude subdevices like color bulb (186) 12475 } 12476 } 12477 return true; 12478 }) 12479*/ 12480 content.data.devices.sort(function(a, b) { 12481 return a.value.localeCompare(b.value) } 12482 ); 12483 12484 content.data.scenes.map(function (item) { 12485 item.data = item.address; 12486 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 12487 item.option = item; // Self reference; 12488 }); 12489 12490 content.data.scenes.sort(function(a, b) { 12491 return a.name.localeCompare(b.name) } 12492 ); 12493 }) 12494 .fail(function (jqXHR, textStatus, errorThrown) { 12495 jqXHR.requested = 'Devices'; // Track which request it is in the jqXHR object 12496 }); 12497 12498 lookupDataPromisePgm = ajaxJsonGet('/api/programs/lookup', $.param({uuid: uuid, domain: domain})) 12499 .done(function(content, status, jqXHR){ 12500 content.data.map(function (item) { 12501 item.data = item.address; 12502 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 12503 item.option = item; // Self reference; 12504 }); 12505 12506 content.data.sort(function(a, b) { 12507 return a.name.localeCompare(b.name) } 12508 ); 12509 }) 12510 .fail(function (jqXHR, textStatus, errorThrown) { 12511 jqXHR.requested = 'Programs'; // Track which request it is in the jqXHR object 12512 }); 12513 12514 lookupDataPromiseStateVar = ajaxJsonGet('/api/statevars/lookup', $.param({uuid: uuid, domain: domain})) 12515 .done(function(content, status, jqXHR){ 12516 content.data.map(function (item) { 12517 item.data = item.address; 12518 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 12519 item.option = item; // Self reference; 12520 }); 12521 12522 content.data.sort(function(a, b) { 12523 return a.name.localeCompare(b.name) } 12524 ); 12525 }) 12526 .fail(function (jqXHR, textStatus, errorThrown) { 12527 jqXHR.requested = 'State variables'; // Track which request it is in the jqXHR object 12528 jqXHR.skipErrorMessage = true; 12529 }); 12530 12531 $.when(lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar) 12532 .fail(function(jqXHR, textStatus, errorThrown) { 12533 if (jqXHR.status==503) { 12534 errorMessageDialog('ISY is not online') 12535 } 12536 else if (jqXHR.status==404) { 12537 // No need to warn the user about state variables not loading. It may be normal. 12538 if (!jqXHR.skipErrorMessage) { 12539 errorMessageDialog(jqXHR.requested + ' are not available') 12540 } 12541 } 12542 else { 12543 errorMessageDialog(jqXHR.requested + ' are not available<br>' + 'HTTP error ' + jqXHR.status, 'Error', 400) 12544 } 12545 }); 12546 12547 loadRooms(uuid); 12548 tableGHSpokenNodes.setRooms(roomsPromise); 12549 tableGHSpokenNodes.setLookupDataPromise(lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar); // Pass a reference to the promises, so that we can check it when we try to edit a spoken. 12550 12551 // setRooms && setLookupDataPromise must have been done first 12552 tableGHSpokenNodes.setUuid(uuid, domain, true); 12553 } 12554 12555 var isyOptions = { 12556 autoOpen: false, 12557 width: 900, 12558 modal: true, 12559 buttons: { 12560 'Close': function() { 12561 $(this).dialog('close'); 12562 } 12563 }, 12564 open: function(event, ui) { 12565 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 12566 } 12567 }; 12568 12569 // Initializes the dialog 12570 dialog = $('#gh_spoken_nodes_dialog').dialog(isyOptions); 12571 12572 $("#gh_spoken_nodes_add").buttonset({items: 'button'}); 12573 12574 $('#gh_spoken_nodes_adddevice').click(function () { 12575 $(this).blur(); // Remove focus from button 12576 12577 $.when(lookupDataPromise, roomsPromise) 12578 .then(function(contentAjax, roomsAjax) { 12579 var content=contentAjax[0]; 12580 var rooms=roomsAjax[0]; 12581 12582 if (!content) { 12583 throw new Error('Devices not available'); 12584 } 12585 12586 ghSpokenNodesEditDialog.setFields({ category: 'std', uuid: uuid, domain: domain, lookup: content.data.devices, address: '', editMode:false, rooms: rooms.data }); 12587 ghSpokenNodesEditDialog.modal.dialog('open'); 12588 }) 12589 .fail(function(jqXHR, textStatus, errorThrown) { 12590 errorMessageDialog("Devices are not available", 'Error', 350) 12591 }) 12592 .done(); 12593 }); 12594 12595 $('#gh_spoken_nodes_addscene').click(function () { 12596 $(this).blur(); // Remove focus from button 12597 12598 $.when(lookupDataPromise, roomsPromise) 12599 .then(function(contentAjax, roomsAjax) { 12600 var content=contentAjax[0]; 12601 var rooms=roomsAjax[0]; 12602 12603 if (!content) { 12604 throw new Error('Devices not available'); 12605 } 12606 ghSpokenNodesEditDialog.setFields({ category: 'scene', uuid: uuid, domain: domain, lookup: content.data.scenes, address: '', editMode:false, rooms: rooms.data }); 12607 ghSpokenNodesEditDialog.modal.dialog('open'); 12608 }) 12609 .fail(function(jqXHR, textStatus, errorThrown) { 12610 errorMessageDialog("Devices are not available", 'Error', 350) 12611 }) 12612 .done(); 12613 }); 12614 12615 $('#gh_spoken_nodes_addprogram').click(function () { 12616 $(this).blur(); // Remove focus from button 12617 12618 $.when(lookupDataPromisePgm, roomsPromise) 12619 .then(function(contentAjax, roomsAjax) { 12620 var content=contentAjax[0]; 12621 var rooms=roomsAjax[0]; 12622 12623 if (!content) { 12624 throw new Error('Programs not available'); 12625 } 12626 ghSpokenNodesEditDialog.setFields({ category: 'program', uuid: uuid, domain: domain, lookup: content.data, address: '', editMode:false, rooms: rooms.data }); 12627 ghSpokenNodesEditDialog.modal.dialog('open'); 12628 }) 12629 .fail(function(jqXHR, textStatus, errorThrown) { 12630 errorMessageDialog("Programs are not available", 'Error', 350) 12631 }) 12632 .done(); 12633 }); 12634
12635 $('#gh_spoken_nodes_addstatevar').click(function () { 12636 $(this).blur(); // Remove focus from button 12637 12638 $.when(lookupDataPromiseStateVar, roomsPromise) 12639 .then(function(contentAjax, roomsAjax) { 12640 var content=contentAjax[0]; 12641 var rooms=roomsAjax[0]; 12642 12643 if (!content) { 12644 throw new Error('State Variables not available'); 12645 } 12646 12647 ghSpokenNodesEditDialog.setFields({ category: 'statevar', uuid: uuid, domain: domain, lookup: content.data, address: '', editMode:false, rooms: rooms.data }); 12648 ghSpokenNodesEditDialog.modal.dialog('open'); 12649 }) 12650 .fail(function(jqXHR, textStatus, errorThrown) { 12651 errorMessageDialog("State variables are not available", 'Error', 350) 12652 }) 12653 .done(); 12654 }); 12655 12656 $('#gh_spoken_nodes_deleteall').button({ icons: {primary: 'ui-icon-custom-delete'}, text: false}).click(function () { 12657 $(this).blur(); // Remove focus from button 12658 12659 confirmationDialog('All of your Google Home spokens for this ISY will be deleted. Are you sure?', 'Delete spokens?') 12660 .then(function () { 12661 ajaxJsonDelete('/api/voice/google/spoken/nodes/deleteall', $.param({uuid: uuid, domain: domain })) 12662 .done(function () { 12663 tableGHSpokenNodes.reload(); 12664 }) 12665 .fail(function(jqXHR, textStatus, errorThrown) { 12666 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 12667 }) 12668 }) 12669 .done(); 12670 }); 12671 12672 $('#gh_spoken_nodes_download').button({ icons: {primary: 'ui-icon-custom-download'}, text: false}).click(function () { 12673 $(this).blur(); // Remove focus from button 12674 12675 informationMessageDialog('Press Ok to download to a CSV file.', 'Export spokens') 12676 .then(function () { 12677 try { 12678 var isFileSaverSupported = !!new Blob; 12679 } catch (e) {} 12680 12681 if (isFileSaverSupported) { 12682 var str = 'Device name,Address,ISY Cat.,GH Cat.,Room,Spoken,Spoken-2,Spoken-3,Spoken-4,Spoken-5\r\n'; 12683 12684 tableGHSpokenNodes.data.data.forEach(function(spoken) { 12685 str += [spoken.name, spoken.address, spoken.category==='std'?'device':spoken.category, spoken.userCat, spoken.roomName].concat(spoken.spoken).join(',') + '\r\n' 12686 }); 12687 12688 var BOM = '\uFEFF'; 12689 var blob = new Blob([BOM + str], {type: "application/csv;charset=utf-8"}); 12690 saveAs(blob, "Spokens_" + uuidNC(uuid) + ".csv"); 12691 } 12692 else { 12693 errorMessageDialog('Feature not supported on this browser', 'Not supported'); 12694 } 12695 }) 12696 .done(); 12697 }); 12698 12699 $('#gh_spoken_nodes_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 12700 $(this).blur(); // Remove focus from button 12701 tableGHSpokenNodes.reload(); 12702 }); 12703 12704 $('#gh_spoken_nodes_sync').button({ icons: {primary: 'ui-icon-custom-talk'}, text: false}).click(function () { 12705 $(this).blur(); // Remove focus from button 12706 12707 informationMessageDialog('Press Ok to send your spokens to Google Home.', 'Send spokens?') 12708 .then(function () { 12709 ajaxJsonPost('/api/voice/google/spoken/nodes/sync', $.param({uuid: uuid, domain: domain })) 12710 .then(function() { 12711 informationMessageDialog('Spokens successfully sent to Google Home.', 'Success', 350) 12712 }) 12713 .fail(function(jqXHR, textStatus, errorThrown) { 12714 if (jqXHR.status==425) { 12715 errorMessageDialog(jqXHR.responseText, 'Error', 650); 12716 } 12717 else { 12718 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 12719 } 12720 }) 12721 }) 12722 .done(); 12723 }); 12724 12725 12726 return { modal: dialog, setFields: setFields, loadRooms: loadRooms }; 12727}; 12728 12729// GOOGLE HOME Spoken Devices & Scenes mapping 12730var ghSpokenNodesEditDialogInit = function () { 12731 var dialog, 12732 form, 12733 pk, 12734 uuid, 12735 domain, 12736 currentRooms, 12737 category, 12738 stdLookup, 12739 editMode, 12740 device = $('#gh_spoken_address'), 12741 room = $('#gh_spoken_room'), 12742 turnon_value = $('#gh_spoken_turnon_value'), 12743 turnoff_value = $('#gh_spoken_turnoff_value'), 12744 colorManufacturer = $('#gh_spoken_color_input_selector'), 12745 spokens = [], 12746 allFields, 12747 tips = $('#gh_spoken_nodes_edit_dialog .validateTips'), 12748 isColor, 12749 colorMfr, // Manufacturer - selected option 12750 colorMfrOptions = [ 12751 { value: '$select', description: 'Select one...' }, 12752 { value: 'AEOTEC', description: 'Aeotec' }, 12753 { value: 'FIBARO', description: 'Fibaro' }, 12754 { value: 'ZWGEN', description: 'Z-Wave Generic' } 12755 ]; 12756 12757 spokens[0] = $('#gh_spoken_spoken'); 12758 spokens[1] = $('#gh_spoken_spoken2'); 12759 spokens[2] = $('#gh_spoken_spoken3'); 12760 spokens[3] = $('#gh_spoken_spoken4'); 12761 spokens[4] = $('#gh_spoken_spoken5'); 12762 12763 allFields = $([]).add(device).add(room).add(turnon_value).add(turnoff_value).add(colorManufacturer).add(spokens[0]).add(spokens[1]).add(spokens[2]).add(spokens[3]).add(spokens[4]).add(spokens[5]); 12764 12765 var categoryLabel = { 12766 'std' : 'device',
12767 'scene' : 'scene', 12768 'program' : 'program', 12769 'statevar' : 'state variable' 12770 }; 12771 12772 function updateRoomSelector(rooms, choice) { 12773 // label: shown in the selections 12774 // value: displayed in the combobox input field (this.input) 12775 // data: real data assigned to the input field (this.element) 12776 12777 rooms = rooms.map(function(item) { 12778 return {label: item.name, value: item.name, data: item._id} 12779 }); 12780 12781 rooms.sort(function(a, b) { 12782 return a.label.localeCompare(b.label) 12783 }); 12784 12785 rooms.unshift({ label: '(No room)', value: '', data: '$noroom'}); 12786 12787 room.combobox("option", "source", rooms); 12788 room.combobox("option", "data", choice); 12789 12790 currentRooms = rooms; 12791 } 12792 12793 // Set isColor based on the address and Show/Hide the colorMfr selectmenu 12794 function setIsColor(address) { 12795 if (address) { 12796 var addressSplit = address.split('_'); 12797 isColor = (addressSplit.length===2 && addressSplit[1].slice(0,3)==='186') 12798 } 12799 else { 12800 isColor=false; 12801 } 12802 12803 if (isColor) { 12804 $('#gh_spoken_color_input').show(); 12805 } 12806 else { 12807 $('#gh_spoken_color_input').hide(); 12808 } 12809 } 12810 12811 function setFields(obj) { 12812 var label; 12813 12814 uuid = obj.uuid; 12815 domain = obj.domain; 12816 pk = obj.pk; 12817 category = obj.category; // 4 possibilities: 'statevar', 'program', 'scene', 'std' 12818 stdLookup = obj.lookup; 12819 12820 form[0].reset(); 12821 device.combobox("option", "source", obj.lookup); 12822 device.combobox("option", "data", obj.address); 12823 turnon_value.val(obj.turnon_value); 12824 turnoff_value.val(obj.turnoff_value); 12825 12826 for (var i=0; i<5; i++) { 12827 if (obj.spokens && i<=obj.spokens.length) { 12828 spokens[i].val(obj.spokens[i]); 12829 } 12830 else { 12831 spokens[i].val(''); 12832 } 12833 } 12834 12835 if (category==='statevar') { 12836 $('#gh_spoken_statevar_input').show(); 12837 } 12838 else { 12839 $('#gh_spoken_statevar_input').hide(); 12840 } 12841 12842 // Initially, the userCat selectmenu is not available for standard devices. 12843 $('#gh_spoken_user_cat_input').show(); 12844 12845 editMode = obj.editMode; 12846 12847 buildUserCatSelect(categoryOverride(category, stdLookup, obj.address, isColor), true); 12848 12849 if (editMode) { 12850 tips.text('Please choose a spoken for this ' + categoryLabel[category] + '.'); 12851 spokens[0].focus(); 12852 12853 setIsColor(obj.address); 12854 if (isColor) { 12855 colorMfr=obj.colorMfr?obj.colorMfr:'$select'; 12856 } 12857 12858 // statevars, programs and scenes used to be a light by default. 12859 // If userCat is not available, and we are editing, set the userCat default to 'light' 12860 if (['statevar', 'program', 'scene'].includes(category) && !obj.userCat) { 12861 obj.userCat = 'switch' 12862 } 12863 12864 if (obj.userCat) { 12865 setUserCatSelect(obj.userCat) 12866 } 12867 } 12868 else { 12869 tips.text('Please choose a ' + categoryLabel[category] +' and enter a corresponding spoken.'); 12870 setIsColor(false); 12871 colorMfr='$select'; 12872 } 12873 12874 $('#gh_spoken_color_input_selector select').val(colorMfr); 12875 $('#gh_spoken_color_input_selector select').selectmenu('refresh'); 12876 12877 // Disable field if editing 12878 device.combobox("disabled", editMode); 12879 12880 $('#gh_spoken_lookup_label').text('My ' + categoryLabel[category]); 12881 allFields.removeClass('fieldfocus'); 12882 12883 if (!editMode) { 12884 obj.room = '$noroom'; 12885 } 12886 12887 updateRoomSelector(obj.rooms, obj.room); 12888 } 12889 12890 // Restricts the userCat select to valid categories. (If the device is a thermostat, lock or color bulb) 12891 function categoryOverride(category, stdLookup, stdAddress, isColor) { 12892 if (category==='std') { 12893 12894 // See if the individual device is a lock or thermostat, and use that device category instead ('lock' or 'thermostat') 12895 var option = stdLookup.find(function(item) { 12896 return item.address === stdAddress; 12897 }); 12898 12899 if (option) { 12900 category = option.category; 12901 } 12902 12903 // Check if current device is a color bulb 12904 if (isColor) { 12905 category = 'color' 12906 } 12907 } 12908 12909 return category; 12910 } 12911 12912 function buildUserCatSelect(category, defaultToFirstOption) { 12913 // The default option is the first one. 12914 var userCats = { 12915 'std': [ { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'fan', desc: 'Fan' }, { option:'openClose', desc: 'Device with open/close syntax' } ], 12916 'color': [ { option:'light', desc: 'Light' } ], 12917 'lock': [ { option:'lock', desc: 'Lock' } ], 12918 'thermostat': [ { option:'thermostat', desc: 'Thermostat' } ], 12919 'scene': [ { option:'scene', desc: 'Scene' }, { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'openClose', desc: 'Device with open/close syntax' } ], 12920 'program': [ { option:'scene', desc: 'Scene' }, { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'openClose', desc: 'Device with open/close syntax' } ], 12921 'statevar': [ { option:'scene', desc: 'Scene' }, { option:'light', desc: 'Light' }, { option:'switch', desc: 'Switch' }, { option:'outlet', desc: 'Outlet' }, { option:'openClose', desc: 'Device with open/close syntax' } ] 12922 }; 12923 12924 if (userCats[category]) { 12925 var $select = $('<select></select>'); 12926
12927 userCats[category].forEach(function(item) { 12928 $select.append('<option value="' + item.option + '">' + item.desc + '</option>'); 12929 }); 12930 12931 if (!selectHasIdenticalOptions($select, $('#gh_spoken_user_cat_selector'))) { 12932 // Default option is the first 12933 $('#gh_spoken_user_cat_selector').empty().append($select.clone()); 12934 $('#gh_spoken_user_cat_selector select').selectmenu({ width:'20em' }); 12935 } 12936 else { 12937 // If we don't rewrite, set the default option to the first one when we setField. 12938 if (defaultToFirstOption) { 12939 setUserCatSelect($('#gh_spoken_user_cat_selector option').eq(0).attr('value')) 12940 } 12941 } 12942 } 12943 else { 12944 $('#gh_spoken_user_cat_input').hide(); 12945 $('#gh_spoken_user_cat_selector').empty(); // Destroy the selector. 12946 } 12947 } 12948 12949 function setUserCatSelect(option) { 12950 $('#gh_spoken_user_cat_selector select').val(option); // Set user category select 12951 $('#gh_spoken_user_cat_selector select').selectmenu("refresh"); 12952 } 12953 12954 // Validation function 12955 function checkColorMfr(tips, field) { 12956 if (isColor && colorMfr==='$select') { 12957 field.focus(); 12958 updateTips(tips, 'Device manufacturer is required.'); 12959 return false; 12960 } 12961 else { 12962 return true; 12963 } 12964 } 12965 12966 function submit() { 12967 var valid = true; 12968 12969 device.combobox("refresh"); // Makes sure the real input field is up to date 12970 if (!device.combobox("validate")) { 12971 updateTips(tips, 'Device is not allowed'); 12972 valid=false; 12973 } 12974 12975 room.combobox("refresh"); // Makes sure the real input field is up to date 12976 if (!room.combobox("validate")) { 12977 12978 var name = room.combobox("getValue"); 12979 12980 confirmationDialog('Room ' + name + ' does not exists.<br/><br/>Create it?', 'Create room?') 12981 .then(function () { 12982 ajaxJsonPut('/api/voice/google/spoken/room', $.param({domain: domain, uuid: uuid, name: name })) 12983 .done(function (record) { 12984 var roomsPromise = ghSpokenNodesDialog.loadRooms(uuid); 12985 12986 roomsPromise 12987 .then(function(data) { 12988 updateRoomSelector(data.data, record.data._id); 12989 }); 12990 12991 tableGHSpokenNodes.setRooms(roomsPromise); 12992 }) 12993 .fail(function(jqXHR, textStatus, errorThrown) { 12994 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 12995 }) 12996 }) 12997 .done(); 12998 12999 13000 valid=false; 13001 } 13002 13003 allFields.removeClass('fieldfocus'); 13004 13005 if (category==='statevar') { 13006 valid = valid && checkNotEmpty(tips, turnoff_value, 'Turn off value'); 13007 valid = valid && checkNumber(tips, turnoff_value, 'Turn off value', 0, 2147483647); 13008 valid = valid && checkNotEmpty(tips, turnon_value, 'Turn on value'); 13009 valid = valid && checkNumber(tips, turnon_value, 'Turn on value', 0, 2147483647); 13010 } 13011 13012 valid = valid && checkColorMfr(tips, colorManufacturer); 13013 valid = valid && checkLength(tips, spokens[0], 'Spoken', 2, 40); 13014 valid = valid && checkRegexp(tips, spokens[0], spokenRegex, 'Spoken must contain only letters with spaces'); 13015 valid = valid && checkLength(tips, spokens[1], 'Alternate spoken 1', 0, 40); 13016 valid = valid && checkRegexp(tips, spokens[1], spokenRegex, 'Spoken must contain only letters with spaces'); 13017 valid = valid && checkLength(tips, spokens[2], 'Alternate spoken 2', 0, 40); 13018 valid = valid && checkRegexp(tips, spokens[2], spokenRegex, 'Spoken must contain only letters with spaces'); 13019 valid = valid && checkLength(tips, spokens[3], 'Alternate spoken 3', 0, 40); 13020 valid = valid && checkRegexp(tips, spokens[3], spokenRegex, 'Spoken must contain only letters with spaces'); 13021 valid = valid && checkLength(tips, spokens[4], 'Alternate spoken 4', 0, 40); 13022 valid = valid && checkRegexp(tips, spokens[4], spokenRegex, 'Spoken must contain only letters with spaces'); 13023 13024 if (valid) { 13025 var data = form.find('input').serialize(); 13026 data += '&uuid=' + uuid + '&domain=' + domain + (isColor?'&colorMfr='+colorMfr:''); 13027 13028 if (editMode) { 13029 data += '&id=' + pk; 13030 } 13031 13032 data += '&category=' + category; // valid values: program, scene, std 13033 13034 var userCat = $('#gh_spoken_user_cat_selector select').val(); 13035 if (userCat) { 13036 data += '&userCat=' + userCat; 13037 } 13038 13039 (editMode?ajaxJsonPost('/api/voice/google/spoken/node', data):ajaxJsonPut('/api/voice/google/spoken/node', data)) 13040 .then(function(data, status, jqXHR) { 13041 dialog.dialog('close');
13042 tableGHSpokenNodes.reload(); 13043 }) 13044 .fail(function(jqXHR, textStatus, errorThrown) { 13045 if (jqXHR.status==503) { 13046 updateTips(tips, 'ISY is not online'); 13047 } 13048 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 13049 var data = JSON.parse(jqXHR.responseText); 13050 13051 if (data.message) { 13052 updateTips(tips, data.message); 13053 } 13054 else { 13055 updateTips(tips, 'HTTP error ' + jqXHR.status); 13056 } 13057 } 13058 else if (jqXHR.status==400) { 13059 updateTips(tips, 'HTTP error ' + jqXHR.status + ' : ' + jqXHR.responseText); 13060 } 13061 else { 13062 updateTips(tips, 'HTTP error ' + jqXHR.status); 13063 } 13064 }) 13065 .done(); 13066 } 13067 } 13068 13069 var isyOptions = { 13070 autoOpen: false, 13071 width: 950, 13072 modal: true, 13073 buttons: { 13074 'Save': function() { 13075 submit(); 13076 } 13077 }, 13078 open: function(event, ui) { 13079 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 13080 } 13081 }; 13082 13083 // Initializes the dialog 13084 dialog = $('#gh_spoken_nodes_edit_dialog').dialog(isyOptions); 13085 13086 // Handles keyboad enter (submit event) 13087 form = dialog.find('form').submit(function(event) { 13088 event.preventDefault(); 13089 submit(); 13090 }); 13091 13092 $('#gh_spoken_room_add').button({ icons: {primary: 'ui-icon-plus'}, text: false}).tooltip().click(function(event) { 13093 $(this).blur(); // Remove focus from button 13094 ghSpokenRoomEditDialog.setFields({ uuid: uuid, editMode: false, domain: domain }); 13095 ghSpokenRoomEditDialog.modal.dialog('open'); 13096 }); 13097 13098 $('#gh_spoken_room_edit').button({ icons: {primary: 'ui-icon-pencil'}, text: false}).tooltip().click(function(event) { 13099 $(this).blur(); // Remove focus from button 13100 13101 room.combobox("refresh"); // Makes sure the real input field is up to date 13102 var pk = room.val(); 13103 13104 var item = currentRooms.find(function(item) { 13105 return item.data === pk; 13106 }); 13107 13108 if (pk!=='$noroom' && item) { 13109 ghSpokenRoomEditDialog.setFields({ uuid: uuid, editMode: true, domain: domain, pk: pk, name: item.label }); 13110 ghSpokenRoomEditDialog.modal.dialog('open'); 13111 } 13112 else { 13113 informationMessageDialog("This can't be edited.") 13114 } 13115 }); 13116 13117 $('#gh_spoken_room_delete').button({ icons: {primary: 'ui-icon-trash'}, text: false}).tooltip().click(function(event) { 13118 $(this).blur(); // Remove focus from button 13119 13120 room.combobox("refresh"); // Makes sure the real input field is up to date 13121 var pk = room.val(); 13122 13123 var item = currentRooms.find(function(item) { 13124 return item.data === pk; 13125 }); 13126 13127 if (pk!=='$noroom' && item) { 13128 confirmationDialog('Delete room ' + item.label + '.<br/><br/>Are you sure?', 'Delete room?') 13129 .then(function () { 13130 ajaxJsonDelete('/api/voice/google/spoken/room', $.param({id: pk, domain: domain, uuid: uuid })) 13131 .done(function () { 13132 var roomsPromise = ghSpokenNodesDialog.loadRooms(uuid); 13133 13134 roomsPromise 13135 .then(function(data) { 13136 ghSpokenNodesEditDialog.updateRoomSelector(data.data, '$noroom'); 13137 }); 13138 13139 tableGHSpokenNodes.setRooms(roomsPromise); 13140 tableGHSpokenNodes.reload(); 13141 }) 13142 .fail(function(jqXHR, textStatus, errorThrown) { 13143 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 13144 }) 13145 }) 13146 .done(); 13147 } 13148 else { 13149 informationMessageDialog("This can't be removed.") 13150 } 13151 }); 13152 13153 $('#gh_spokenStatevarHelp').click(function() { 13154 informationMessageDialog('When you turn on or turn off this "device", the state variable will be set to the one of these values. However, if you turn on to X percent, the variable will be set to the percent value.', 'Help', 550) 13155 }); 13156 13157 device.combobox({ 13158 inputClass: '', 13159 focusout: function(event) { 13160 device.combobox('refresh'); 13161 13162 if (category==='std') { 13163 if (deviceHasColorNode(device.val(), stdLookup)) { 13164 var colorAddress = device.val().split('_')[0]+'_186'; 13165 13166 informationMessageDialog('This device supports color. However, to enable color support, you need to choose the device whose address ends with _186. Do you want to use the device ' + colorAddress + ' instead?', 'Color support?', 600) 13167 .then(function () { 13168 device.combobox("option", "data", colorAddress); 13169 device.combobox("refresh"); // Makes sure the real input field is up to date 13170 setIsColor(colorAddress); 13171 }) 13172 .done(); 13173 } 13174 else { 13175 setIsColor(device.val()); 13176 } 13177 13178 // If the device has change, see if this is a lock or thermostat, and if so, enforce the user cat to be lock or thermostat. 13179 if (device.combobox("validate")) { 13180 var catOverride = categoryOverride(category, stdLookup, $('#gh_spoken_address').val(), isColor); 13181 buildUserCatSelect(catOverride, false); 13182 13183 // if (catOverride=='lock') { 13184 // errorMessageDialog('Locks are not yet supported by Google Home. They can be saved, but they are not sent to Google.', 'Error', 450) 13185 // } 13186 } 13187 } 13188 } 13189 }); 13190 room.combobox({inputClass: ''}); 13191 13192 buildSelect(colorMfrOptions, '#gh_spoken_color_input_selector', '15em', function(event, ui) { 13193 colorMfr = ui.item.value; 13194 }); 13195 13196 return { modal: dialog, setFields: setFields, updateRoomSelector: updateRoomSelector }; 13197};
13198 13199// GOOGLE HOME Spoken room edit dialog 13200var ghSpokenRoomEditDialogInit = function () { 13201 var dialog, 13202 form, 13203 pk, 13204 uuid, 13205 domain, 13206 editMode, 13207 name = $('#gh_spoken_room_name'), 13208 allFields = $([]).add(name), 13209 tips = $('#gh_spoken_room_edit_dialog .validateTips'); 13210 13211 function setFields(obj) { 13212 var label; 13213 13214 uuid = obj.uuid; 13215 domain = obj.domain; 13216 pk = obj.pk; 13217 13218 form[0].reset(); 13219 name.val(obj.name); 13220 13221 editMode = obj.editMode; 13222 13223 tips.text('Please enter a room name'); 13224 13225 allFields.removeClass('fieldfocus'); 13226 } 13227 13228 function submit() { 13229 var valid = true; 13230 13231 allFields.removeClass('fieldfocus'); 13232 13233 valid = valid && checkLength(tips, name, 'Name', 2, 40); 13234 valid = valid && checkRegexp(tips, name, roomNameRegex, 'room name is not valid.' ); 13235 13236 if (valid) { 13237 var data = form.find('input').serialize(); 13238 data += '&domain=' + domain + '&uuid=' + uuid; 13239 13240 if (editMode) { 13241 data += '&id=' + pk; 13242 } 13243 13244 var url = '/api/voice/google/spoken/room'; 13245 13246 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 13247 .then(function(roomRecord, status, jqXHR) { 13248 13249 tableGHSpokenNodes.reload(); 13250 13251 var roomsPromise = ghSpokenNodesDialog.loadRooms(uuid); 13252 tableGHSpokenNodes.setRooms(roomsPromise); // Will be available when we edit! 13253 13254 roomsPromise.done(function(data) { 13255 var newRoom=null; 13256 13257 if (roomRecord.data && roomRecord.data._id) { 13258 newRoom = roomRecord.data._id; 13259 } 13260 13261 ghSpokenNodesEditDialog.updateRoomSelector(data.data, newRoom); 13262 dialog.dialog('close'); 13263 }) 13264 }) 13265 .fail(function(jqXHR, textStatus, errorThrown) { 13266 if (jqXHR.status==503) { 13267 updateTips(tips, 'ISY is not online'); 13268 } 13269 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 13270 var data = JSON.parse(jqXHR.responseText); 13271 13272 if (data.message) { 13273 updateTips(tips, data.message); 13274 } 13275 else { 13276 updateTips(tips, 'HTTP error ' + jqXHR.status); 13277 } 13278 } 13279 else { 13280 updateTips(tips, 'HTTP error ' + jqXHR.status); 13281 } 13282 }) 13283 .done(); 13284 } 13285 } 13286 13287 var isyOptions = { 13288 autoOpen: false, 13289 width: 400, 13290 modal: true, 13291 buttons: { 13292 'Save': function() { 13293 submit(); 13294 } 13295 }, 13296 open: function(event, ui) { 13297 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 13298 } 13299 }; 13300 13301 // Initializes the dialog 13302 dialog = $('#gh_spoken_room_edit_dialog').dialog(isyOptions); 13303 13304 // Handles keyboad enter (submit event) 13305 form = dialog.find('form').submit(function(event) { 13306 event.preventDefault(); 13307 submit(); 13308 }); 13309 13310 return { modal: dialog, setFields: setFields }; //, updateRoomSelector: updateRoomSelector 13311}; 13312 13313// Node Server List main dialog 13314var nsListDialogInit = function () { 13315 var dialog, 13316 uuid, 13317 domain 13318 13319 function setFields(obj) { 13320 $('#ns_list_uuid').text(obj.uuid); 13321 $('#ns_list_alias').text(obj.alias); 13322 13323 uuid = obj.uuid; 13324 domain = obj.domain; 13325 13326 // setRooms && setLookupDataPromise must have been done first 13327 tableNsList.setUuid(uuid, domain, true); 13328 } 13329 13330 var isyOptions = { 13331 autoOpen: false, 13332 width: 900, 13333 modal: true, 13334 buttons: { 13335 'Close': function() { 13336 $(this).dialog('close'); 13337 } 13338 }, 13339 open: function(event, ui) { 13340 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 13341 } 13342 }; 13343 13344 // Initializes the dialog 13345 dialog = $('#ns_list_dialog').dialog(isyOptions); 13346 13347 $('#ns_list_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 13348 $(this).blur(); // Remove focus from button 13349 tableNsList.reload(); 13350 }); 13351 13352 return { modal: dialog, setFields: setFields }; 13353}; 13354 13355 13356// IFTTT Inbound events 13357var iftttInNodesDialogInit = function () { 13358 var dialog, 13359 uuid, 13360 domain, 13361 lookupDataPromise, 13362 lookupDataPromisePgm, 13363 lookupDataPromiseStateVar; 13364 13365 function setFields(obj) { 13366 $('#ifttt_in_nodes_uuid').text(obj.uuid); 13367 $('#ifttt_in_nodes_alias').text(obj.alias); 13368 13369 uuid = obj.uuid; 13370 domain = obj.domain; 13371 13372 tableIftttInNodes.setUuid(uuid, domain, true); 13373 13374 lookupDataPromise = ajaxJsonGet('/api/nodes/lookup', $.param({ uuid: uuid, domain: domain })) 13375 .done(function(content, status, jqXHR){ 13376 13377 content.data.devices = content.data.devices.filter(function (element) { 13378 return element.category === 'std'; // Removes thermostats and locks, keep only std 13379 }); 13380 13381 content.data.devices.map(function (item) { 13382 var path = item.path.map(function(p) { return p.name }) 13383 .concat([ item.name ]) 13384 .join(' / '); 13385 13386 item.data = item.address; 13387 item.value = item.label = path + ' (' + item.address.replace(/ /g, '.') + ')'; 13388 item.option = item; // Self reference; 13389 }); 13390 13391 content.data.devices.sort(function(a, b) { 13392 return a.value.localeCompare(b.value) } 13393 ); 13394 13395 content.data.scenes.map(function (item) { 13396 item.data = item.address; 13397 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 13398 item.option = item; // Self reference; 13399 }); 13400 13401 content.data.scenes.sort(function(a, b) { 13402 return a.name.localeCompare(b.name) } 13403 ); 13404 }) 13405 .fail(function (jqXHR, textStatus, errorThrown) { 13406 jqXHR.requested = 'Devices'; // Track which request it is in the jqXHR object 13407 }); 13408 13409 lookupDataPromiseStateVar = ajaxJsonGet('/api/statevars/lookup', $.param({uuid: uuid, domain: domain})) 13410 .done(function(content, status, jqXHR){
13411 content.data.map(function (item) { 13412 item.data = item.address; 13413 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 13414 item.option = item; // Self reference; 13415 }); 13416 13417 content.data.sort(function(a, b) { 13418 return a.name.localeCompare(b.name) } 13419 ); 13420 }) 13421 .fail(function (jqXHR, textStatus, errorThrown) { 13422 jqXHR.requested = 'State variables'; // Track which request it is in the jqXHR object 13423 jqXHR.skipErrorMessage = true; 13424 }); 13425 13426 lookupDataPromisePgm = ajaxJsonGet('/api/programs/lookup', $.param({uuid: uuid, domain: domain})) 13427 .done(function(content, status, jqXHR){ 13428 content.data.map(function (item) { 13429 item.data = item.address; 13430 item.value = item.label = item.name + ' (' + item.address.replace(/ /g, '.') + ')'; 13431 item.option = item; // Self reference; 13432 }); 13433 13434 content.data.sort(function(a, b) { 13435 return a.name.localeCompare(b.name) } 13436 ); 13437 }) 13438 .fail(function (jqXHR, textStatus, errorThrown) { 13439 jqXHR.requested = 'Programs'; // Track which request it is in the jqXHR object 13440 }); 13441 13442 $.when(lookupDataPromise, lookupDataPromiseStateVar, lookupDataPromisePgm) 13443 .fail(function(jqXHR, textStatus, errorThrown) { 13444 if (jqXHR.status==503) { 13445 errorMessageDialog('ISY is not online') 13446 } 13447 else if (jqXHR.status==404) { 13448 // No need to warn the user in the case of state vars. It may be normal 13449 if (!jqXHR.skipErrorMessage) { 13450 errorMessageDialog(jqXHR.requested + ' are not available') 13451 } 13452 } 13453 else { 13454 errorMessageDialog(jqXHR.requested + ' are not available<br>' + 'HTTP error ' + jqXHR.status, 'Error', 400) 13455 } 13456 }); 13457 13458 tableIftttInNodes.setLookupDataPromise(lookupDataPromise, lookupDataPromisePgm, lookupDataPromiseStateVar); // Pass a reference to the promises, so that we can check it when we try to edit an event. 13459 } 13460 13461 function onlineHelp() { 13462 informationMessageDialog( 13463 '<div class="online-help"><p>The IFTTT / Webhook integration allows you to trigger ISY events from the cloud using services such as IFTTT. ' + 13464 'This is possible using the <a href="http://ifttt.com/maker" target="_blank">IFTTT Maker Channel</a> in the THAT portion of your IFTTT recipe.</p>' + 13465 '<p>First, you need to click on "Set Key" to create a key that will be used to authenticate your IFTTT / Webhook requests. ' + 13466 'Then, you need to create the events that the IFTTT Maker Channel or other cloud service will use.</p>' + 13467 '<p>To configure your Maker channel, first click <span class="ui-icon ui-icon-extlink linkbutton"></span> beside your event configured here to retrieve the URL to use. ' + 13468 'In the maker channel, use the url with the "POST" method, and you are ready to go.</p>' + 13469 '<p>In addition, you can pass values from IFTTT events to a state variable or a dimmable device. To do so, set your event with the action "IFTTT Value". ' + 13470 'Then in the maker channel, you can pass the value in the "Body". Just make sure to set the Content Type to "text/plain".</p>' + 13471 '<p>Values can be passed as an absolute, or relative value. To send a relative value, just precede the value with a + or -. Example: +10</p></div>', 13472 'IFTTT / Webhook Online Help', 820) 13473 } 13474 13475 var isyOptions = { 13476 autoOpen: false, 13477 width: 900, 13478 modal: true, 13479 buttons: { 13480 'Close': function() { 13481 $(this).dialog('close'); 13482 } 13483 }, 13484 open: function(event, ui) { 13485 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 13486 } 13487 }; 13488 13489 // Initializes the dialog 13490 dialog = $('#ifttt_in_nodes_dialog').dialog(isyOptions); 13491 // Tooltip removed - Causing issues as the scope is too wide 13492// $('[title]').tooltip(); 13493 13494 $("#ifttt_in_nodes_add").buttonset({items: 'button'}); 13495 13496 $('#ifttt_in_nodes_adddevice').click(function () { 13497 $(this).blur(); // Remove focus from button 13498 13499 lookupDataPromise 13500 .then(function(content) { 13501 if (!content) { 13502 throw new Error('Devices not available'); 13503 } 13504 iftttInNodesEditDialog.setFields({ category: 'std', uuid: uuid, domain: domain, lookup: content.data.devices, address: '', editMode:false }); 13505 iftttInNodesEditDialog.modal.dialog('open'); 13506 }) 13507 .fail(function(jqXHR, textStatus, errorThrown) { 13508 errorMessageDialog("Devices are not available", 'Error', 350) 13509 }) 13510 .done(); 13511 }); 13512
13513 $('#ifttt_in_nodes_addscene').click(function () { 13514 $(this).blur(); // Remove focus from button 13515 13516 lookupDataPromise 13517 .then(function(content) { 13518 if (!content) { 13519 throw new Error('Scenes not available'); 13520 } 13521 iftttInNodesEditDialog.setFields({ category: 'scene', uuid: uuid, domain: domain, lookup: content.data.scenes, address: '', editMode:false }); 13522 iftttInNodesEditDialog.modal.dialog('open'); 13523 }) 13524 .fail(function(jqXHR, textStatus, errorThrown) { 13525 errorMessageDialog("Scenes are not available", 'Error', 350) 13526 }) 13527 .done(); 13528 }); 13529 13530 $('#ifttt_in_nodes_addprogram').click(function () { 13531 $(this).blur(); // Remove focus from button 13532 13533 lookupDataPromisePgm 13534 .then(function(content) { 13535 if (!content) { 13536 throw new Error('Programs not available'); 13537 } 13538 iftttInNodesEditDialog.setFields({ category: 'program', uuid: uuid, domain: domain, lookup: content.data, address: '', editMode:false }); 13539 iftttInNodesEditDialog.modal.dialog('open'); 13540 }) 13541 .fail(function(jqXHR, textStatus, errorThrown) { 13542 errorMessageDialog("Programs are not available", 'Error', 350) 13543 }) 13544 .done(); 13545 }); 13546 13547 $('#ifttt_in_nodes_addstatevar').click(function () { 13548 $(this).blur(); // Remove focus from button 13549 13550 lookupDataPromiseStateVar 13551 .then(function(content) { 13552 if (!content) { 13553 throw new Error('State Variables not available'); 13554 } 13555 iftttInNodesEditDialog.setFields({ category: 'statevar', uuid: uuid, domain: domain, lookup: content.data, address: '', editMode:false }); 13556 iftttInNodesEditDialog.modal.dialog('open'); 13557 }) 13558 .fail(function(jqXHR, textStatus, errorThrown) { 13559 errorMessageDialog("State variables are not available", 'Error', 350) 13560 }) 13561 .done(); 13562 }); 13563 13564 // Set IFTTT key button 13565 $('#ifttt_in_nodes_key').button().click(function(event) { 13566 $(this).blur(); // Remove focus from button 13567 ajaxJsonGet('/api/ifttt/in/key', $.param({ uuid: uuid, domain: domain})) 13568 .then(function (data) { 13569 iftttInNodesKeyDialog.setFields({ uuid: uuid, domain: domain, key: data.data.key}); 13570 iftttInNodesKeyDialog.modal.dialog('open'); 13571 }) 13572 }); 13573 13574 $('#ifttt_in_nodes_help').button().click(function(event) { 13575 $(this).blur(); // Remove focus from button 13576 onlineHelp(); 13577 }); 13578 13579 $('#ifttt_in_nodes_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 13580 $(this).blur(); // Remove focus from button 13581 tableIftttInNodes.reload(); 13582 }); 13583 13584 return { modal: dialog, setFields: setFields }; 13585}; 13586 13587var echoCategoryTxt = { 13588 'std' : 'Device', 13589 'scene' : 'Scene', 13590 'program' : 'Program', 13591 'statevar' : 'Variable', 13592 'lock' : 'Lock', 13593 'thermostat' : 'Thermostat', 13594 'av' : 'A/V Device' , 13595 'elk' : 'ELK Zone' 13596}; 13597 13598var iftttCategoryTxt = { 13599 'std' : 'Device', 13600 'scene' : 'Scene', 13601 'program' : 'Program', 13602 'statevar' : 'Variable' 13603// 'lock' : 'Lock', // Only valid for spokens, not IFTTT! 13604// 'thermostat' : 'Thermostat' // Only valid for spokens, not IFTTT! 13605}; 13606 13607var iftttCategoryLabel = { 13608 'std' : 'My device', 13609 'scene' : 'My scene', 13610 'program' : 'My program', 13611 'statevar' : 'My state variable', 13612}; 13613 13614var iftttControls = { 13615 'std': ['on', 'off', 'brighten', 'dim', 'value', 'parm' ], 13616 'scene': ['on', 'off', 'brighten', 'dim' ], 13617 'program': ['if', 'then', 'else', 'stop'], 13618 'statevar': ['value', 'parm' ], 13619}; 13620 13621var iftttControlsLabels = { 13622 'on': 'On', 13623 'off': 'Off', 13624 'brighten': 'Brighten', 13625 'dim': 'Dim', 13626 'value': 'Set value', 13627 'parm': 'IFTTT value', 13628 'if':'Run If', 13629 'then':'Run Then', 13630 'else': 'Run else', 13631 'stop': 'Stop program' 13632}; 13633 13634// Spoken Devices & Scenes mapping 13635var iftttInNodesEditDialogInit = function () { 13636 var dialog, 13637 form, 13638 pk, 13639 uuid, 13640 domain, 13641 category, 13642 editMode, 13643 lookup, 13644 device = $('#ifttt_in_address'), 13645 event = $('#ifttt_in_event'), 13646 value = $('#ifttt_in_value'), 13647 allFields = $([]).add(device).add(event).add(value), 13648 tips = $('#ifttt_in_nodes_edit_dialog .validateTips'); 13649 13650 function displayValueIf(condition) { 13651 if (condition) { 13652 $('#ifttt_in_value_container').show(); 13653 } 13654 else { 13655 $('#ifttt_in_value_container').hide(); 13656 } 13657 } 13658 13659 function buildControlSelect(control) { 13660 var $select = $('<select></select>'); 13661 13662 iftttControls[control].forEach(function(item) { 13663 $select.append('<option value="' + item + '">' + iftttControlsLabels[item] + '</option>'); 13664 }); 13665 13666 $('#ifttt_in_control_selector').empty().append($select.clone()); 13667 13668 $('#ifttt_in_control_selector select').selectmenu({ 13669 width:'13em',
13670 13671 change: function(event, ui) { 13672 var id = ui.item.value; 13673 13674 displayValueIf(id==='value'); 13675 13676 if (id!=='value') { 13677 value.val(''); 13678 } 13679 } 13680 }); 13681 } 13682 13683 function setFields(obj) { 13684 var label; 13685 13686 uuid = obj.uuid; 13687 domain = obj.domain; 13688 pk = obj.pk; 13689 category = obj.category; 13690 lookup = obj.lookup; 13691 editMode = obj.editMode; 13692 13693 $('#ifttt_in_lookup_label').text(iftttCategoryLabel[category]); 13694 13695 // Set event name 13696 event.val(obj.event?obj.event:''); 13697 13698 // set combobox 13699 device.combobox("option", "source", lookup); 13700 device.combobox("option", "data", obj.address); 13701 13702 buildControlSelect(category); 13703 13704 if (editMode) { 13705 tips.text('Please update event conditions.'); 13706 13707 $('#ifttt_in_control_selector select').val(obj.control); // Set control select 13708 $('#ifttt_in_control_selector select').selectmenu("refresh"); 13709 13710 if (obj.control==='value') { 13711 value.val(obj.value!=null?obj.value.toString():'') 13712 } 13713 } 13714 else { 13715 tips.text('Please enter an IFTTT / Webhook event.'); 13716 value.val(''); 13717 } 13718 13719 displayValueIf($('#ifttt_in_control_selector select').val()==='value'); 13720 13721 event.focus(); 13722 allFields.removeClass('fieldfocus'); 13723 } 13724 13725 function submit() { 13726 var control = $('#ifttt_in_control_selector select').val(); 13727 var valid = true; 13728 13729 valid = valid && checkLength(tips, event, 'Event name', 1, 40); 13730 valid = valid && checkRegexp(tips, event, /^[A-Za-z0-9_.~\-]*$/, 'Event name contains invalid characters.'); 13731 13732 device.combobox("refresh"); // Makes sure the real input field is up to date 13733 if (valid && (!device.val().length || !device.combobox("validate"))) { 13734 updateTips(tips, iftttCategoryTxt[category] + ' is not valid'); 13735 valid=false; 13736 } 13737 13738 switch(category) { 13739 case 'std': 13740 13741 var devData = findInArray(lookup, 'address', device.val()); 13742 if (valid && !devData) { 13743 updateTips(tips, 'Device is not valid'); // Should not happen 13744 valid=false; 13745 } 13746 13747 if (valid && !devData.dimscale && !['on', 'off'].includes(control)) { 13748 updateTips(tips, 'This device can only be set to On or Off'); 13749 valid=false; 13750 } 13751 13752 if (control==='value') { 13753 valid = valid && checkRegexp(tips, value, /^(\+|-)?([1-9][0-9]?|100)$/, 'Value must be from 1 to 100. It may be preceded with + or -'); // /^[1-9][0-9]?$|^100$/ 13754 } 13755 13756 break; 13757 13758 case 'scene': 13759 break; 13760 case 'program': 13761 break; 13762 case 'statevar': 13763 if (control==='value') { 13764 valid = valid && checkRegexp(tips, value, /^(\+|-)?[0-9]{1,9}$/, 'Value must be from 0 to 999999999. It may be preceded with + or -'); 13765 } 13766 break; 13767 } 13768 13769 allFields.removeClass('fieldfocus'); 13770 13771 if (valid) { 13772 var data = form.find('input').serialize(); 13773 data += '&uuid=' + uuid + '&domain=' + domain; 13774 13775 if (editMode) { 13776 data += '&id=' + pk; 13777 } 13778 13779 data += '&category=' + category; // valid values: program, scene, std, statevar 13780 data += '&control=' + control; // valid values depends on category 13781 13782 (editMode?ajaxJsonPost('/api/ifttt/in/node', data):ajaxJsonPut('/api/ifttt/in/node', data)) 13783 .then(function(data, status, jqXHR) { 13784 dialog.dialog('close'); 13785 tableIftttInNodes.reload(); 13786 }) 13787 .fail(function(jqXHR, textStatus, errorThrown) { 13788 if (jqXHR.status===503) { 13789 updateTips(tips, 'ISY is not online'); 13790 } 13791 else if (jqXHR.status===420) { // Dupkey error with Custom server error message 13792 var data = JSON.parse(jqXHR.responseText); 13793 13794 if (data.message) { 13795 updateTips(tips, data.message); 13796 } 13797 else { 13798 updateTips(tips, 'HTTP error ' + jqXHR.status); 13799 } 13800 } 13801 else if (jqXHR.status==400) { 13802 updateTips(tips, 'HTTP error ' + jqXHR.status + ' : ' + jqXHR.responseText); 13803 } 13804 else { 13805 updateTips(tips, 'HTTP error ' + jqXHR.status); 13806 } 13807 }) 13808 .done(); 13809 } 13810 } 13811 13812 var isyOptions = { 13813 autoOpen: false, 13814 width: 950, 13815 modal: true, 13816 buttons: { 13817 'Save': function() { 13818 submit(); 13819 } 13820 }, 13821 open: function(event, ui) { 13822 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 13823 } 13824 }; 13825 13826 // Initializes the dialog
13827 dialog = $('#ifttt_in_nodes_edit_dialog').dialog(isyOptions); 13828// Tooltip removed - Causing issues as the scope is too wide 13829// $('[title]').tooltip(); 13830 13831 // Handles keyboad enter (submit event) 13832 form = dialog.find('form').submit(function(event) { 13833 event.preventDefault(); 13834 submit(); 13835 }); 13836 13837 device.combobox({inputClass: ''}); 13838 13839 return { modal: dialog, setFields: setFields }; 13840}; 13841 13842// IFTTT Key edit/change 13843var iftttInNodesKeyDialogInit = function () { 13844 var dialog, 13845 form, 13846 uuid, 13847 domain, 13848 key = $('#ifttt_in_key'), 13849 tips = $('#ifttt_in_nodes_key_dialog .validateTips'); 13850 13851 function setFields(obj) { 13852 tips.text('Click Create key to generate a new key.'); 13853 13854 uuid = obj.uuid; 13855 domain = obj.domain; 13856 key.val(obj.key) 13857 } 13858 13859 function createKey() { 13860 ajaxJsonPost('/api/ifttt/in/key', $.param( { uuid: uuid, domain: domain } )) 13861 .then(function(data, status, jqXHR) { 13862 if (data.data && data.data.key) { 13863 key.val(data.data.key) 13864 } 13865 tableIftttInNodes.reload(); // The table needs to get the new key and refresh 13866 informationMessageDialog('New IFTTT / Webhook key created successfully.', 'Successful') 13867 }) 13868 .fail(function(jqXHR, textStatus, errorThrown) { 13869 updateTips(tips, 'HTTP error ' + jqXHR.status); 13870 }) 13871 .done(); 13872 } 13873 13874 var isyOptions = { 13875 autoOpen: false, 13876 width: 500, 13877 modal: true, 13878 buttons: { 13879 'Create key': function() { 13880 if (key.val().length) { 13881 confirmationDialog('Creating new IFTTT / Webhook key.<br/>Are you sure?', 'Create new IFTTT / Webhook key?') 13882 .then(createKey) 13883 } 13884 else { 13885 createKey(); 13886 } 13887 }, 13888 'Close': function() { 13889 $(this).dialog('close'); 13890 } 13891 }, 13892 open: function(event, ui) { 13893 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 13894 } 13895 }; 13896 13897 // Initializes the dialog 13898 dialog = $('#ifttt_in_nodes_key_dialog').dialog(isyOptions); 13899 13900 form = dialog.find('form'); 13901 13902 return { modal: dialog, setFields: setFields }; 13903}; 13904 13905// Hint edit 13906var hintEditorDialogInit = function () { 13907 var dialog, 13908 form, 13909 pk, 13910 uuid, 13911 domain, 13912 lookup, 13913 device = $('#hint_address'), 13914 hint = $('#hint_value'), 13915 allFields = $([]).add(device).add(hint), 13916 tips = $('#hint_edit_dialog .validateTips'); 13917 13918 function isNsNode(address) { 13919 return /^n\d\d\d_/.test(address); 13920 } 13921 13922 function setFields(obj) { 13923 pk = obj.pk; 13924 uuid = obj.uuid; 13925 domain = obj.domain; 13926 lookup = obj.lookup; 13927 13928 // Initial hint value will come from the device selection 13929 hint.val(''); 13930 13931 // set combobox 13932 device.combobox("option", "source", lookup); 13933 device.combobox("option", "data", ''); // Empty the box 13934 13935 tips.text('Please choose a device to set his hint.'); 13936 13937 // device.focus(); 13938 allFields.removeClass('fieldfocus'); 13939 } 13940 13941 function submit() { 13942 var valid = true; 13943 13944 device.combobox("refresh"); // Makes sure the real input field is up to date 13945 if (valid && (!device.val().length || !device.combobox("validate"))) { 13946 updateTips(tips, 'Device is not valid'); 13947 valid=false; 13948 } 13949 13950 // Hint can be in the n.n.n.n or 0x00000000 formats 13951 if (valid && !/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hint.val()) && !/^0x[0-9a-f]{8}$/.test(hint.val())) { 13952 updateTips(tips, 'Hint format is not valid.'); 13953 valid=false; 13954 } 13955 13956 var devData = findInArray(lookup, 'address', device.val()); 13957 if (valid && !devData) { 13958 updateTips(tips, 'Device is not valid'); // Should not happen 13959 valid=false; 13960 } 13961 13962 // node server node's type & hint are the same. But for those, we prefer to use the type for backward compatibility 13963 var currentHint = isNsNode(device.val()) ? devData.type : (devData.hint || '0.0.0.0') 13964 13965 // Compare the form hint field with the current hint 13966 if (valid && currentHint === hint.val()) { 13967 updateTips(tips, 'This device hint is already set to ' + hint.val()); 13968 valid=false; 13969 } 13970 13971 allFields.removeClass('fieldfocus'); 13972 13973 if (valid) { 13974 confirmationDialog('Your device hint will be changed from ' + currentHint + ' to ' + hint.val() + 13975 '.<br/><br/>Please take note of your existing hint in case you need to change it back to its original value.<br/><br/>Are you sure you want to proceed?', 'Change Hint?', 700) 13976 .then(function () { 13977 var data = form.find('input').serialize(); 13978 data += '&id=' + pk + '&domain=' + domain + '&oldhint=' + currentHint; // oldhint is only used for logging 13979
13980 ajaxJsonPost('/api/isy/hint/set', data) 13981 .then(function(data, status, jqXHR) { 13982 confirmationDialog('Your device hint was changed from ' + currentHint + ' to ' + hint.val() + ' successfully.', 'Hint updated', 800) 13983 .then(function() { 13984 dialog.dialog('close'); 13985 }) 13986 .done(); 13987 }) 13988 .fail(function(jqXHR, textStatus, errorThrown) { 13989 if (jqXHR.status==503) { 13990 updateTips(tips, 'ISY is not online'); 13991 } else if (jqXHR.status==425) { // Validation error message 13992 updateTips(tips, jqXHR.responseText); 13993 } else { 13994 updateTips(tips, 'HTTP error ' + jqXHR.status + ' ' + jqXHR.responseText); 13995 } 13996 }) 13997 .done(); 13998 }) 13999 .done(); 14000 14001 } 14002 } 14003 14004 function help() { 14005 informationMessageDialog( 14006 '<div class="online-help"><p>The Hint Editor allows you to update the "hint" of a nodeserver, ' + 14007 'which helps ISY Portal and other REST API based softwares identify the features of a node. ' + 14008 'This is for example what helps ISY Portal know if a device is a lock, a thermostat, a regular light, etc.</p>' + 14009 '<p>Some Nodeservers sets the hint correctly, whereas others might leave the hint to a default value of 0.0.0.0. ' + 14010 'This is where the Hint Editor comes into play.</p>' + 14011 '<p>You can specify the hint using the decimal format (0.0.0.0) or the hex format (0x00000000).</p>' + 14012 '<p>Here\'s a reference to the <a href="https://github.com/UniversalDevicesInc/hints" target="_blank">Hints Specifications</a>.</p>' + 14013 '<p>Please be aware that if the node is deleted and re-created, the node will lose its hint. ' + 14014 'Whenever possible, it\'s better to have the nodeserver developer update the nodeserver code to automatically set the hint when the node is created.</p>', 14015 'Hint Editor Help', 820) 14016 } 14017 14018 var isyOptions = { 14019 autoOpen: false, 14020 width: 750, 14021 modal: true, 14022 buttons: { 14023 'Help': function() { 14024 help(); 14025 }, 14026 'Change Hint': function() { 14027 submit(); 14028 }, 14029 Cancel: function() { 14030 $(this).dialog('close'); 14031 } 14032 }, 14033 open: function(event, ui) { 14034 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 14035 } 14036 }; 14037 14038 // Initializes the dialog 14039 dialog = $('#hint_edit_dialog').dialog(isyOptions); 14040// Tooltip removed - Causing issues as the scope is too wide 14041// $('[title]').tooltip(); 14042 14043 // Handles keyboad enter (submit event) 14044 form = dialog.find('form').submit(function(event) { 14045 event.preventDefault(); 14046 submit(); 14047 }); 14048 device.combobox({ 14049 inputClass: '', 14050 }); 14051 14052 device.combobox({ 14053 // When pressing tab to go to the hint field 14054 focusout: function(event) { 14055 device.combobox('refresh'); 14056 14057 // Set the hint field to the existing hint 14058 var devData = findInArray(lookup, 'address', device.val()); 14059 if (devData) { 14060 // If this is a node server node, use the type field as default. We do this for backward compatibility (hint field exists only since 5.7.
140601) 14061 hint.val(isNsNode(devData.address) ? devData.type : (devData.hint || '0.0.0.0')); 14062 } 14063 }, 14064 inputClass: '' 14065 }); 14066 14067 return { modal: dialog, setFields: setFields }; 14068}; 14069 14070 14071// ISY Portal Activity log dialog 14072var activityLogDialogInit = function () { 14073 var dialog, 14074 uuid, 14075 domain; 14076 14077 function setFields(obj) { 14078 $('#activitylog_uuid').text(obj.uuid); 14079 $('#activitylog_alias').text(obj.alias); 14080 14081 uuid = obj.uuid; 14082 domain = obj.domain; 14083 tableActivityLog.setUuid(uuid, domain, true); 14084 } 14085 14086 var isyOptions = { 14087 autoOpen: false, 14088 width: 1200, 14089 modal: true, 14090 buttons: { 14091 'Close': function() { 14092 $(this).dialog('close'); 14093 } 14094 }, 14095 open: function(event, ui) { 14096 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 14097 } 14098 }; 14099 14100 // Initializes the dialog 14101 dialog = $('#activitylog_dialog').dialog(isyOptions); 14102 14103 $('#activitylog_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 14104 $(this).blur(); // Remove focus from button 14105 tableActivityLog.reload(); 14106 }); 14107 14108 return { modal: dialog, setFields: setFields }; 14109}; 14110 14111// ISY Portal Access License Renewal Option edit dialog 14112var ipalRenewalOptionEditDialogInit = function () { 14113 var dialog, 14114 form, 14115 pk, 14116 editMode, 14117 part = $('#ipal_renewal_option_part'), 14118 isRenewal = $('#ipal_renewal_option_isrenewal'), 14119 isLegacy = $('#ipal_renewal_option_islegacy'), // For 994s 14120 description = $('#ipal_renewal_option_description'), 14121 legacyText = $('#ipal_renewal_option_legacytext'), 14122 duration = $('#ipal_renewal_option_duration'), 14123 price = $('#ipal_renewal_option_price'), 14124 allFields = $([]).add(isRenewal).add(isLegacy).add(description).add(legacyText).add(duration).add(price), 14125 tips = $('#ipal_renewal_option_edit_dialog .validateTips'); 14126 14127 function setFields(obj) { 14128 var label; 14129 14130 form[0].reset(); 14131 editMode = obj && obj.editMode; 14132 14133 if (editMode) { 14134 pk = obj.pk; 14135 isRenewal.prop('checked', obj.isRenewal).prop('disabled', true); 14136 isLegacy.prop('checked', obj.isLegacy).prop('disabled', true); 14137 part.val(obj.part).prop('readonly', true); 14138 duration.val(obj.duration).prop('readonly', true); 14139 price.val(obj.price).prop('readonly', false); 14140 description.val(obj.description); 14141 legacyText.val(obj.legacyText); 14142 } 14143 else { 14144 pk=null; 14145 isRenewal.prop('disabled', false); 14146 isLegacy.prop('disabled', false); 14147 part.prop('readonly', false); 14148 duration.prop('readonly', false); 14149 price.prop('readonly', false); 14150 } 14151 14152 tips.text('Please enter the renewal option'); 14153 14154 allFields.removeClass('fieldfocus'); 14155 } 14156 14157 function submit() { 14158 var valid = true; 14159 14160 allFields.removeClass('fieldfocus'); 14161 14162 valid = valid && checkLength(tips, description, 'Description', 2, 60); 14163 valid = valid && checkNumber(tips, duration, 'Duration', 0, 100); 14164 valid = valid && checkFloat(tips, price, 'Price', 0, 1000000); 14165 14166 if (valid) { 14167 var data = serializeFormWithCheckboxes(form.find('input, textarea')); 14168 14169 if (editMode) { 14170 data += '&id=' + pk; 14171 } 14172 14173 var url = '/api/ipal/renewal/option'; 14174 14175 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 14176 .then(function(ipalOptionRecord, status, jqXHR) { 14177 tableIpalRenewalOptions.reload(); 14178 dialog.dialog('close'); 14179 }) 14180 .fail(function(jqXHR, textStatus, errorThrown) { 14181 if (jqXHR.status==420) { 14182 updateTips(tips, 'Part number already exists.'); 14183 } 14184 else { 14185 updateTips(tips, 'HTTP error ' + jqXHR.status); 14186 } 14187 }) 14188 .done(); 14189 } 14190 } 14191 14192 var isyOptions = { 14193 autoOpen: false, 14194 width: 600, 14195 modal: true, 14196 buttons: { 14197 'Save': function() { 14198 submit(); 14199 } 14200 }, 14201 open: function(event, ui) { 14202 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 14203 } 14204 }; 14205 14206 // Initializes the dialog 14207 dialog = $('#ipal_renewal_option_edit_dialog').dialog(isyOptions); 14208// Tooltip removed - Causing issues as the scope is too wide 14209// $('[title]').tooltip(); 14210 14211 // Handles keyboad enter (submit event) 14212 form = dialog.find('form').submit(function(event) { 14213 event.preventDefault(); 14214 submit(); 14215 }); 14216 14217 return { modal: dialog, setFields: setFields }; 14218}; 14219 14220// ISY Portal Access License Notification Option edit dialog 14221var ipalNotificationOptionEditDialogInit = function () { 14222 var dialog, 14223 form, 14224 pk, 14225 editMode,
14226 name = $('#ipal_notification_option_name'), 14227 days = $('#ipal_notification_option_days'), 14228 skip = $('#ipal_notification_option_skip'), 14229 notification = $('#ipal_notification_option_notification'), 14230 pushContent = $('#ipal_notification_option_pushContent'), 14231 emailTitle = $('#ipal_notification_option_emailTitle'), 14232 emailBody = $('#ipal_notification_option_emailBody'), 14233 // sendTest = $('#ipal_notification_option_sendtest'), 14234 allFields = $([]).add(name).add(days).add(skip).add(notification).add(pushContent), 14235 tips = $('#ipal_notification_option_edit_dialog .validateTips'); 14236 14237 function setFields(obj) { 14238 var label 14239 var typeSelect = $('#ipal_notification_option_type select') 14240 14241 loadTinymce() 14242 14243 form[0].reset(); 14244 // sendTest.prop('checked', false); 14245 editMode = obj && obj.editMode; 14246 14247 $('#ipal_notification_option_tabs').tabs('option', 'active', 0); 14248 14249 if (editMode) { 14250 pk = obj.pk; 14251 name.val(obj.name); 14252 days.val(obj.days); 14253 skip.prop('checked', obj.skip); 14254 notification.val(obj.notification); 14255 pushContent.val(obj.pushContent); 14256 emailTitle.val(obj.emailTitle); 14257 emailBody.val(obj.emailBody); 14258 typeSelect.val(obj.type); 14259 14260 displayNotificationAndPushContent(!obj.skip) 14261 } 14262 else { 14263 pk=null; 14264 typeSelect.val(0); 14265 displayNotificationAndPushContent(true) 14266 } 14267 14268 typeSelect.selectmenu('refresh'); 14269 14270 tips.text('Please enter the notification option'); 14271 14272 allFields.removeClass('fieldfocus'); 14273 } 14274 14275 function displayNotificationAndPushContent(display) { 14276 // Disable the online hours field if filterOnline is set to no filter 14277 $('#ipal_notification_option_tabs').toggle(display) 14278 } 14279 14280 14281 function submit(sendTestType) { 14282 var valid = true; 14283 14284 allFields.removeClass('fieldfocus'); 14285 14286 valid = valid && checkLength(tips, name, 'Name', 1, 40); 14287 valid = valid && checkNumberOrDelta(tips, days, 'Days', -90, 90); 14288 14289 if (!skip.is(":checked")) { 14290 valid = valid && checkLength(tips, notification, 'Push notification', 1, 100); 14291 valid = valid && checkLength(tips, pushContent, 'Notification content', 1, 4096); 14292 valid = valid && checkLength(tips, emailTitle, 'Email title', 1, 100); 14293 valid = valid && checkLength(tips, emailBody, 'Email body', 1, 4096); 14294 } 14295 14296 if (valid) { 14297 var data = serializeFormWithCheckboxes(form.find('input, textarea')); 14298 14299 data += '&type=' + $('#ipal_notification_option_type select').val(); 14300 14301 if (editMode) { 14302 data += '&id=' + pk; 14303 } 14304 14305 var url = '/api/ipal/notification/option'; 14306 14307 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 14308 .then(function(ipalOptionRecord, status, jqXHR) { 14309 tableIpalNotificationOptions.reload(); 14310 14311 dialog.dialog('close'); 14312 14313 if (sendTestType) { 14314 var url = '/api/ipal/notification/option/test/' + sendTestType 14315 14316 if (!skip.is(":checked")) { 14317 ajaxJsonGet(url, $.param({id: pk})) 14318 } else { 14319 errorMessageDialog('Test can\'t be sent for this notification option.') 14320 } 14321 } 14322 }) 14323 .fail(function(jqXHR, textStatus, errorThrown) { 14324 if (jqXHR.status==420) { // Dupkey error with Custom server error message 14325 var data = JSON.parse(jqXHR.responseText); 14326 14327 if (data.message) { 14328 updateTips(tips, data.message); 14329 } 14330 else { 14331 updateTips(tips, 'HTTP error ' + jqXHR.status); 14332 } 14333 } 14334 else { 14335 updateTips(tips, 'HTTP error ' + jqXHR.status); 14336 } 14337 }) 14338 .done(); 14339 } 14340 } 14341 14342 function onlineHelp() { 14343 informationMessageDialog( 14344 '<div class="online-help"><p>Tips for creating notification message:<br/>' + 14345 'You can embed the following variables:<br/>' + 14346 ' ● {expiryDate} The expiry date in the format YYYY-MM-DD.<br/>' + 14347 ' ● {renewalUrl} The link to renew. Example use: <a href="{renewal-url}">Click here</a>.<br/>' + 14348 ' ● {days} The number of days before or after (absolute value).<br/>' + 14349 ' ● {uuid} The UUID of the ISY.<br/>' + 14350 ' ● {contact} The ISY contact, or account contact. (If empty, defaults to "Customer").', 14351 'Notification Online Help', 850) 14352 } 14353 14354 var isyOptions = { 14355 autoOpen: false, 14356 width: 1000, 14357 modal: true, 14358 buttons: { 14359 'Help': function() { 14360 onlineHelp(); 14361 }, 14362 'Send test push': function() { 14363 submit('push'); 14364 }, 14365 'Send test email': function() { 14366 submit('email'); 14367 }, 14368 'Save': function() { 14369 submit(); 14370 } 14371 }, 14372 open: function(event, ui) { 14373 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 14374 } 14375 }; 14376 14377 // Initializes the dialog
14378 dialog = $('#ipal_notification_option_edit_dialog').dialog(isyOptions); 14379// Tooltip removed - Causing issues as the scope is too wide 14380// $('#ipal_notification_option_istrial_tip').tooltip(); 14381 14382 var selectOptions = [ 14383 { value: 0, description: 'Trial' }, 14384 { value: 1, description: 'Regular license' } 14385 // { value: 2, description: 'Renewal' } 14386 ]; 14387 14388 buildSelect(selectOptions, '#ipal_notification_option_type', '15em', function(event, ui) { 14389// displayPaymentOptions(ui.item.value) 14390 }); 14391 14392 $('#ipal_notification_option_tabs').tabs() 14393 14394 skip.change(function(){ 14395 displayNotificationAndPushContent(!$(this).is(':checked')) 14396 }) 14397 14398 // Handles keyboad enter (submit event) 14399 form = dialog.find('form').submit(function(event) { 14400 event.preventDefault(); 14401 submit(); 14402 }); 14403 14404 return { modal: dialog, setFields: setFields }; 14405}; 14406 14407 14408// OpenADR Rate Plan edit dialog 14409var ratePlanEditDialogInit = function () { 14410 var dialog, 14411 form, 14412 pk, 14413 editMode, 14414 ratePlanTabForm = '#tabs-rateplan-rates-form', 14415 code = $('#rateplan_code'), 14416 name = $('#rateplan_name'), 14417 altCodes = $('#rateplan_altcodes'), 14418 gridx = $('#rateplan_gridx'), // PG&E Gridx code 14419 description = $('#rateplan_description'), 14420 details = $('#rateplan_details'), 14421 active = $('#rateplan_active'), 14422 isTiered = $('#rateplan_isTiered'), 14423 sort = $('#rateplan_sort'), 14424 allFields = $([]).add(code).add(name).add(sort).add(description).add(details), 14425 tableRefreshFunction = null, 14426 tips = $('#rateplan_edit_dialog .validateTips'); 14427 14428 // Used for the selectmenu 14429 var providerOptions = [ 14430 { value: 'SCE', description: 'SCE' }, 14431 // { value: 'HECO', description: 'HECO' }, 14432 // { value: 'LADWP', description: 'LADWP' }, 14433 // { value: 'NVE', description: 'NVE' }, 14434 // { value: 'ONCOR', description: 'ONCOR' }, 14435 { value: 'PGE', description: 'PG&E' }, 14436 // { value: 'PJM', description: 'PJM' }, 14437 // { value: 'SDGE', description: 'SDGE' } 14438 ]; 14439 14440 var ratesDef = { 14441 PGE: { 14442 program_season_1: { 14443 month: { label: 'Summer Start date - Month', type: 'month' }, 14444 day: { label: 'Day', type: 'day' }, 14445 }, 14446 program_season_1_wd_1: { 14447 time: { label: 'Summer weekday start time', type: 'time' }, 14448 rate: { label: 'Rate', type: 'rate' }, 14449 text: { label: 'Comment', type: 'text' }, 14450 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14451 }, 14452 program_season_1_wd_2: { 14453 time: { label: 'Summer weekday start time', type: 'time' }, 14454 rate: { label: 'Rate', type: 'rate' }, 14455 text: { label: 'Comment', type: 'text' }, 14456 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14457 }, 14458 program_season_1_wd_3: { 14459 time: { label: 'Summer weekday start time', type: 'time' }, 14460 rate: { label: 'Rate', type: 'rate' }, 14461 text: { label: 'Comment', type: 'text' }, 14462 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14463 }, 14464 program_season_1_wd_4: { 14465 time: { label: 'Summer weekday start time', type: 'time' }, 14466 rate: { label: 'Rate', type: 'rate' }, 14467 text: { label: 'Comment', type: 'text' }, 14468 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14469 }, 14470 program_season_1_wd_5: { 14471 time: { label: 'Summer weekday start time', type: 'time' }, 14472 rate: { label: 'Rate', type: 'rate' }, 14473 text: { label: 'Comment', type: 'text' }, 14474 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14475 }, 14476 program_season_1_we_1: { 14477 time: { label: 'Summer week end start time', type: 'time' }, 14478 rate: { label: 'Rate', type: 'rate' }, 14479 text: { label: 'Comment', type: 'text' }, 14480 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14481 }, 14482 program_season_1_we_2: { 14483 time: { label: 'Summer week end start time', type: 'time' }, 14484 rate: { label: 'Rate', type: 'rate' }, 14485 text: { label: 'Comment', type: 'text' }, 14486 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14487 }, 14488 program_season_1_we_3: { 14489 time: { label: 'Summer week end start time', type: 'time' }, 14490 rate: { label: 'Rate', type: 'rate' }, 14491 text: { label: 'Comment', type: 'text' }, 14492 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14493 }, 14494 program_season_1_we_4: { 14495 time: { label: 'Summer week end start time', type: 'time' }, 14496 rate: { label: 'Rate', type: 'rate' }, 14497 text: { label: 'Comment', type: 'text' }, 14498 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14499 }, 14500 program_season_1_holiday: { 14501 checkbox: { label: 'On Holidays use this rate', type: 'checkbox' }, 14502 rate: { label: 'Rate', type: 'rate' }, 14503 text: { label: 'Comment', type: 'text' }, 14504 }, 14505 14506 program_season_2: { 14507 month: { label: 'Winter Start date - Month', type: 'month' }, 14508 day: { label: 'Day', type: 'day' }, 14509 }, 14510 program_season_2_wd_1: { 14511 time: { label: 'Winter weekday start time', type: 'time' }, 14512 rate: { label: 'Rate', type: 'rate' }, 14513 text: { label: 'Comment', type: 'text' }, 14514 }, 14515 program_season_2_wd_2: { 14516 time: { label: 'Winter weekday start time', type: 'time' }, 14517 rate: { label: 'Rate', type: 'rate' }, 14518 text: { label: 'Comment', type: 'text' }, 14519 }, 14520 program_season_2_wd_3: { 14521 time: { label: 'Winter weekday start time', type: 'time' }, 14522 rate: { label: 'Rate', type: 'rate' }, 14523 text: { label: 'Comment', type: 'text' }, 14524 }, 14525 program_season_2_wd_4: { 14526 time: { label: 'Winter weekday start time', type: 'time' }, 14527 rate: { label: 'Rate', type: 'rate' }, 14528 text: { label: 'Comment', type: 'text' }, 14529 }, 14530 program_season_2_wd_5: { 14531 time: { label: 'Winter weekday start time', type: 'time' }, 14532 rate: { label: 'Rate', type: 'rate' }, 14533 text: { label: 'Comment', type: 'text' }, 14534 }, 14535 program_season_2_we_1: { 14536 time: { label: 'Winter week end start time', type: 'time' }, 14537 rate: { label: 'Rate', type: 'rate' }, 14538 text: { label: 'Comment', type: 'text' }, 14539 }, 14540 program_season_2_we_2: { 14541 time: { label: 'Winter week end start time', type: 'time' }, 14542 rate: { label: 'Rate', type: 'rate' }, 14543 text: { label: 'Comment', type: 'text' }, 14544 }, 14545 program_season_2_we_3: { 14546 time: { label: 'Winter week end start time', type: 'time' }, 14547 rate: { label: 'Rate', type: 'rate' }, 14548 text: { label: 'Comment', type: 'text' }, 14549 }, 14550 program_season_2_we_4: { 14551 time: { label: 'Winter week end start time', type: 'time' }, 14552 rate: { label: 'Rate', type: 'rate' }, 14553 text: { label: 'Comment', type: 'text' }, 14554 }, 14555 program_season_2_holiday: { 14556 checkbox: { label: 'On Holidays use this rate', type: 'checkbox' }, 14557 rate: { label: 'Rate', type: 'rate' }, 14558 text: { label: 'Comment', type: 'text' }, 14559 }, 14560 program_holidaywe: { 14561 checkbox: { label: 'Treat Holiday as weed-end', type: 'checkbox' }, 14562 }, 14563 }, 14564 SCE: { 14565 program_season_1: { 14566 month: { label: 'Summer Start date - Month', type: 'month' }, 14567 day: { label: 'Day', type: 'day' }, 14568 }, 14569 program_season_1_wd_1: { 14570 time: { label: 'Summer weekday start time', type: 'time' }, 14571 rate: { label: 'Rate', type: 'rate' }, 14572 text: { label: 'Comment', type: 'text' }, 14573 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14574 }, 14575 program_season_1_wd_2: { 14576 time: { label: 'Summer weekday start time', type: 'time' }, 14577 rate: { label: 'Rate', type: 'rate' }, 14578 text: { label: 'Comment', type: 'text' }, 14579 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14580 }, 14581 program_season_1_wd_3: { 14582 time: { label: 'Summer weekday start time', type: 'time' }, 14583 rate: { label: 'Rate', type: 'rate' }, 14584 text: { label: 'Comment', type: 'text' }, 14585 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14586 }, 14587 program_season_1_wd_4: { 14588 time: { label: 'Summer weekday start time', type: 'time' }, 14589 rate: { label: 'Rate', type: 'rate' }, 14590 text: { label: 'Comment', type: 'text' }, 14591 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14592 }, 14593 program_season_1_wd_5: { 14594 time: { label: 'Summer weekday start time', type: 'time' }, 14595 rate: { label: 'Rate', type: 'rate' }, 14596 text: { label: 'Comment', type: 'text' }, 14597 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14598 }, 14599 program_season_1_we_1: { 14600 time: { label: 'Summer week end start time', type: 'time' }, 14601 rate: { label: 'Rate', type: 'rate' }, 14602 text: { label: 'Comment', type: 'text' }, 14603 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14604 }, 14605 program_season_1_we_2: { 14606 time: { label: 'Summer week end start time', type: 'time' }, 14607 rate: { label: 'Rate', type: 'rate' }, 14608 text: { label: 'Comment', type: 'text' }, 14609 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14610 }, 14611 program_season_1_we_3: { 14612 time: { label: 'Summer week end start time', type: 'time' }, 14613 rate: { label: 'Rate', type: 'rate' }, 14614 text: { label: 'Comment', type: 'text' }, 14615 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14616 }, 14617 program_season_1_we_4: { 14618 time: { label: 'Summer week end start time', type: 'time' }, 14619 rate: { label: 'Rate', type: 'rate' }, 14620 text: { label: 'Comment', type: 'text' }, 14621 // highPrice: { label: 'Smart day high price', type: 'checkbox', class:'smartday' }, 14622 }, 14623 program_season_1_holiday: { 14624 checkbox: { label: 'On Holidays use this rate', type: 'checkbox' }, 14625 rate: { label: 'Rate', type: 'rate' }, 14626 text: { label: 'Comment', type: 'text' }, 14627 }, 14628 14629 program_season_2: { 14630 month: { label: 'Winter Start date - Month', type: 'month' }, 14631 day: { label: 'Day', type: 'day' }, 14632 }, 14633 program_season_2_wd_1: { 14634 time: { label: 'Winter weekday start time', type: 'time' }, 14635 rate: { label: 'Rate', type: 'rate' }, 14636 text: { label: 'Comment', type: 'text' }, 14637 }, 14638 program_season_2_wd_2: { 14639 time: { label: 'Winter weekday start time', type: 'time' }, 14640 rate: { label: 'Rate', type: 'rate' }, 14641 text: { label: 'Comment', type: 'text' }, 14642 }, 14643 program_season_2_wd_3: { 14644 time: { label: 'Winter weekday start time', type: 'time' }, 14645 rate: { label: 'Rate', type: 'rate' }, 14646 text: { label: 'Comment', type: 'text' }, 14647 }, 14648 program_season_2_wd_4: { 14649 time: { label: 'Winter weekday start time', type: 'time' }, 14650 rate: { label: 'Rate', type: 'rate' }, 14651 text: { label: 'Comment', type: 'text' }, 14652 }, 14653 program_season_2_wd_5: { 14654 time: { label: 'Winter weekday start time', type: 'time' }, 14655 rate: { label: 'Rate', type: 'rate' }, 14656 text: { label: 'Comment', type: 'text' }, 14657 }, 14658 program_season_2_we_1: { 14659 time: { label: 'Winter week end start time', type: 'time' }, 14660 rate: { label: 'Rate', type: 'rate' }, 14661 text: { label: 'Comment', type: 'text' }, 14662 }, 14663 program_season_2_we_2: { 14664 time: { label: 'Winter week end start time', type: 'time' }, 14665 rate: { label: 'Rate', type: 'rate' }, 14666 text: { label: 'Comment', type: 'text' }, 14667 }, 14668 program_season_2_we_3: { 14669 time: { label: 'Winter week end start time', type: 'time' }, 14670 rate: { label: 'Rate', type: 'rate' }, 14671 text: { label: 'Comment', type: 'text' }, 14672 }, 14673 program_season_2_we_4: { 14674 time: { label: 'Winter week end start time', type: 'time' }, 14675 rate: { label: 'Rate', type: 'rate' }, 14676 text: { label: 'Comment', type: 'text' }, 14677 }, 14678 program_season_2_holiday: { 14679 checkbox: { label: 'On Holidays use this rate', type: 'checkbox' }, 14680 rate: { label: 'Rate', type: 'rate' }, 14681 text: { label: 'Comment', type: 'text' }, 14682 }, 14683 program_holidaywe: { 14684 checkbox: { label: 'Treat Holiday as weed-end', type: 'checkbox' }, 14685 }, 14686 }, 14687 } 14688 14689 // function showHideSmartDayCheckboxes() { 14690 // // If Rate plan ends with _SD, enable the smart day high price checkboxes 14691 // var name = $('#rateplan_code').val() 14692 // $('#rateplan_edit_dialog .smartday').toggle(/.*_SD/.test(name)) 14693 // } 14694 14695 function setFields(obj) { 14696 var label; 14697 14698 form[0].reset(); 14699 editMode = obj && obj.editMode; 14700 14701 tableRefreshFunction = obj.refreshFunction || null; 14702 14703 $('#rateplan_provider_show').toggle(!obj.hideProvider) 14704 14705 if (editMode) { 14706 pk = obj.pk;
14707 code.val(obj.ratePlan); 14708 name.val(obj.name); 14709 altCodes.val(obj.altCodes); 14710 gridx.val(obj.gridx); 14711 description.val(obj.description); 14712 details.val(obj.details); 14713 sort.val(obj.sort); 14714 active.prop('checked', obj.active); 14715 isTiered.prop('checked', obj.isTiered); 14716 buildRatesForm(obj.provider) 14717 14718 $('#rateplan_provider select').val(obj.provider); 14719 $('#rateplan_provider select').selectmenu('refresh'); 14720 14721 // showHideSmartDayCheckboxes() 14722 14723 if (obj.programs) { 14724 Object.keys(obj.programs).forEach(function(field) { 14725 if (['checkbox', 'highPrice'].includes(field.split('_').pop())) { 14726 $('#rate_' + field).prop('checked', obj.programs[field]); 14727 } else { 14728 $('#rate_' + field).val(obj.programs[field]); 14729 } 14730 }) 14731 } 14732 } 14733 else { 14734 pk=null 14735 14736 // provider may be specified when adding 14737 var provider = obj.provider ? obj.provider : providerOptions[0].value 14738 $('#rateplan_provider select').val(provider); 14739 $('#rateplan_provider select').selectmenu('refresh'); 14740 14741 active.prop('checked', false); 14742 isTiered.prop('checked', false); 14743 buildRatesForm(provider) 14744 14745 // When adding, always start with the first tab 14746 $('#rateplan-tabs').tabs('option', 'active', 0); 14747 } 14748 14749 // $('#rateplan_provider select').selectmenu('refresh'); // Needs a refresh to show 14750 14751 tips.text('Please enter a rate plan'); 14752 14753 allFields.removeClass('fieldfocus'); 14754 } 14755 14756 function genField(field, fieldOpt) { 14757 var html; 14758 switch (fieldOpt.type) { 14759 case 'text': 14760 html = '<td class="col1"><label htmlFor="rate_' + field +'">' + fieldOpt.label + ':</label></td>' + 14761 '<td><input type="text" name="' + field + '" id="rate_' + field + '" value="" class="text-thin ui-widget-content ui-corner-all" style="width:130px"></td>' 14762 break; 14763 14764 case 'rate': 14765 html = '<td class="col1"><label htmlFor="rate_' + field +'">' + fieldOpt.label + ':</label></td>' + 14766 '<td><input type="number" step="0.01" name="' + field + '" id="rate_' + field + '" value="" class="text-thin ui-widget-content ui-corner-all" style="width:130px"></td>' 14767 break; 14768 14769 case 'month': 14770 html = '<td class="col1"><label htmlFor="rate_' + field +'">' + fieldOpt.label + ':</label></td>' + 14771 '<td><input type="number" min="1" max="12" step="1" name="' + field + '" id="rate_' + field + '" value="" class="text-thin ui-widget-content ui-corner-all" style="width:50px"></td>' 14772 break; 14773 14774 case 'day': 14775 html = '<td class="col1"><label htmlFor="rate_' + field +'">' + fieldOpt.label + ':</label></td>' + 14776 '<td><input type="number" min="1" max="31" step="1" name="' + field + '" id="rate_' + field + '" value="" class="text-thin ui-widget-content ui-corner-all" style="width:50px"></td>' 14777 break; 14778 14779 case 'time': 14780 html = '<td class="col1"><label htmlFor="rate_' + field +'">' + fieldOpt.label + ':</label></td>' + 14781 '<td><input type="text" name="' + field + '" id="rate_' + field + '" value="" class="text-thin time-field ui-widget-content ui-corner-all"></td>' 14782 break; 14783 14784 case 'checkbox': 14785 html = '<td' + (fieldOpt.class ? ' class="' + fieldOpt.class+ '"': '') + '><div><input type="checkbox" name="' + field + '" value="true" id="rate_' + field + '"><label>' + fieldOpt.label + '</label></div></td><td/>' 14786 break; 14787 14788 default: 14789 html = '' 14790 } 14791 14792 return html 14793 } 14794 14795 // Build a dynamic jQuery Rate Plan Rates form 14796 function buildRatesForm(provider) { 14797 // Rates first tab - Show only sections specific to provider 14798 $('#tabs-rateplan-main .provider-filter').hide() 14799 $('#tabs-rateplan-main .provider-filter.' + provider).show(); 14800 14801 // Rates details 14802 var $ratesTable = $('<table></table>'); 14803 14804 var options = ratesDef[provider] 14805 if (options) { 14806 Object.keys(options).forEach(function(field) { 14807 var row = '<tr>' 14808 var fieldOpt = options[field]; 14809 14810 // This is a standard field 14811 if (fieldOpt.label && fieldOpt.type) { 14812 row += genField(field, fieldOpt) 14813 } else { 14814 // this is for multiple columns 14815 Object.keys(fieldOpt).forEach(function(subField) { 14816 row += genField(field + '_' + subField, options[field][subField]) 14817 }) 14818 } 14819 14820 row += '</tr>' 14821 $ratesTable.append(row); 14822 }); 14823 } 14824 14825 $(ratePlanTabForm).empty().append($ratesTable.clone()); 14826 14827 $(ratePlanTabForm + ' .time-field').timepicker({ 14828 timeFormat: 'H:i', // :s 14829 step: 60, 14830 forceRoundTime: true,
14831 roundingFunction: function(sec) { 14832 return (sec/60)*60; 14833 }, 14834 noneOption: [{ 14835 label: 'None', 14836 value: '' 14837 }] 14838 }) 14839 } 14840 14841 14842 function submit() { 14843 var valid = true; 14844 14845 allFields.removeClass('fieldfocus'); 14846 14847 valid = valid && checkLength(tips, code, 'Rate plan', 0, 256); // 0 length = error message! 14848 valid = valid && checkLength(tips, name, 'Name', 1, 60); 14849 14850 if (active.prop('checked')) { 14851 valid = valid && checkNumber(tips, sort, 'Sort order', 0, 100); 14852 } 14853 14854 // If not an error message 14855 if (code.val().length) { 14856 valid = valid && checkLength(tips, description, 'Description', 1, 10000); 14857 valid = valid && checkLength(tips, details, 'Details', 1, 10000); 14858 } 14859 14860 if (valid) { 14861 var data = serializeFormWithCheckboxes(form.find('input, textarea')); 14862 14863 data += '&provider=' + $('#rateplan_provider select').val(); 14864 14865 if (editMode) { 14866 data += '&id=' + pk; 14867 } 14868 14869 var url = '/api/rateplan'; 14870 14871 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 14872 .then(function(ratePlanRecord, status, jqXHR) { 14873 // Must be supplied by setfield, whether we are editing or adding a rate plan 14874 if (tableRefreshFunction) { 14875 tableRefreshFunction() 14876 } 14877 dialog.dialog('close'); 14878 }) 14879 .fail(function(jqXHR, textStatus, errorThrown) { 14880 if (jqXHR.status==420) { // Dupkey error with Custom server error message 14881 updateTips(tips, 'Record already exists'); 14882 // var data = JSON.parse(jqXHR.responseText); 14883 // 14884 // if (data.message) { 14885 // updateTips(tips, data.message); 14886 // } 14887 // else { 14888 // updateTips(tips, 'HTTP error ' + jqXHR.status); 14889 // } 14890 } 14891 else { 14892 updateTips(tips, 'HTTP error ' + jqXHR.status); 14893 } 14894 }) 14895 .done(); 14896 } 14897 } 14898 14899 var isyOptions = { 14900 autoOpen: false, 14901 width: 1000, 14902 modal: true, 14903 buttons: { 14904 'Save': function() { 14905 submit(); 14906 } 14907 }, 14908 open: function(event, ui) { 14909 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 14910 } 14911 }; 14912 14913 // Initializes the dialog 14914 dialog = $('#rateplan_edit_dialog').dialog(isyOptions); 14915// Tooltip removed - Causing issues as the scope is too wide 14916// $('#ipal_notification_option_istrial_tip').tooltip(); 14917 14918 buildSelect(providerOptions, '#rateplan_provider', '10em', function(event, ui) { 14919 buildRatesForm(ui.item.value) 14920// displayPaymentOptions(ui.item.value) 14921 }); 14922 14923 $('#rateplan-tabs').tabs() 14924 14925 // Add/remove checkboxes when name ends with _SD or not 14926 // code.focusout(function () { 14927 // showHideSmartDayCheckboxes() 14928 // }) 14929 14930 // Handles keyboad enter (submit event) 14931 form = dialog.find('form').submit(function(event) { 14932 event.preventDefault(); 14933 submit(); 14934 }); 14935 14936 return { modal: dialog, setFields: setFields }; 14937}; 14938 14939 14940 14941 14942// Node Server Configuration Dialog 14943var nsConfigDialogInit = function () { 14944 var dialog; 14945 var minimumVersion = '5.0.8'; 14946 var uuid; 14947 var profileNumber; 14948 var profileVersion; 14949 var pk; 14950 var isyDomain; 14951 14952 function setFields(obj) { 14953 // Not there during a refresh 14954 if (obj.uuid) { 14955 uuid = obj.uuid; 14956 $('#nsconfig_uuid').text(obj.uuid); 14957 } 14958 14959 // Not there during a refresh 14960 if (obj.alias) { 14961 $('#nsconfig_alias').text(obj.alias); 14962 } 14963 14964 if (obj.pk) { 14965 pk = obj.pk; 14966 } 14967 14968 if (obj.isyDomain) { 14969 isyDomain = obj.isyDomain; 14970 } 14971 14972 // For refresh only 14973 if (obj.info) { 14974 profileNumber = obj.info.profileNumber; 14975 profileVersion = obj.info.profileVersion; 14976 $('#nsconfig_configured').html(obj.info.profileNumber?'Configured using profile ID #'+obj.info.profileNumber + '<br>User profile is ' + obj.info.user:'Not configured'); 14977 $('#nsconfig_installed').text(obj.info.profileVersion?'Version installed is '+obj.info.profileVersion:'Not installed'); 14978 } 14979 else { 14980 $('#nsconfig_configured').text(''); 14981 $('#nsconfig_installed').text(''); 14982 } 14983 } 14984 14985 function refresh(reboot) { 14986 ajaxJsonGet('/api/nsmanage/' + uuid + '/config/status') 14987 .done(function (data) { 14988 setFields({ info: data.data }); 14989 // Used after we have uploaded files. We refresh, then reboot. 14990 if (reboot) {
14991 ajaxJsonGet('/api/isy/reboot', $.param({id: pk, domain: isyDomain })) 14992 } 14993 }) 14994 .fail(function(jqXHR, textStatus, errorThrown) { 14995 if (jqXHR.status==503) { 14996 errorMessageDialog('ISY is not online') 14997 } 14998 else if (jqXHR.status==550) { // Custom error message 14999 var data = JSON.parse(jqXHR.responseText); 15000 15001 if (data.message) { 15002 errorMessageDialog(data.message) 15003 } 15004 else { 15005 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15006 } 15007 } 15008 else { 15009 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15010 } 15011 }) 15012 .done(); 15013 } 15014 15015 function onlineHelp() { 15016 informationMessageDialog( 15017 '<div class="online-help"><p>In order to configure an ISY Portal Nodes server for your ISY, you need to:<br>' + 15018 '1 - Configure a connection on your ISY.<br>2 - Upload the nodes definitions.<br><br> ' + 15019 'Once this is done you can use nodes server modules like Occupancy.<br><br>' + 15020 'Whenever you change your ISY Portal password, you will need to change it for the node server connection too. ' + 15021 'You can simply click on configure connection again, or manually update it in the admin console.<br><br>' + 15022 'When new ISY Portal Nodes server modules will be released, you will need to refresh the nodes definitions by re-uploading the latest version. ' + 15023 'Please note that the upload of the nodes definitions requires a reboot of the ISY.<br><br>' + 15024 'If you wish to remove all Nodes server configurations from your ISY, including the connection, the nodes definitions, and this nodes server devices, click on Remove.', 15025 'Nodes Server Configuration Online Help', 850) 15026 } 15027 15028 var isyOptions = { 15029 autoOpen: false, 15030 width: 825, 15031 modal: true, 15032 15033 buttons: { 15034 'Configure connection': function() { 15035 $buttonConfigure.blur(); // Remove focus from button 15036 confirmationDialog('This will ' + (profileNumber?'update your existing':'create a new') + ' ISY Node server connection.<br/><br/>Are you sure?', 'Configure node server connection?') 15037 .then(function () { 15038// ajaxJsonGet('/api/nsmanage/' + uuid + '/configure', $.param({ user: registeredUser.username, password: registeredUser.password })) 15039 ajaxJsonGet('/api/nsmanage/' + uuid + '/configure') // , $.param({ user: registeredUser.username, password: registeredUser.password }) 15040 .done(function () { 15041 refresh(); 15042 informationMessageDialog('Configuration completed successfully.') 15043 .done(); 15044 }) 15045 .fail(function(jqXHR, textStatus, errorThrown) { 15046 if (jqXHR.status==503) { 15047 errorMessageDialog('ISY is not online') 15048 } 15049 else if (jqXHR.status==550) { // Custom error message 15050 var data = JSON.parse(jqXHR.responseText); 15051 15052 if (data.message) { 15053 errorMessageDialog(data.message) 15054 } 15055 else { 15056 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15057 } 15058 } 15059 else { 15060 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15061 } 15062 }) 15063 .done(); 15064 }) 15065 .done(); 15066 }, 15067 'Upload nodes definitions': function() { 15068 var self = this; 15069 $buttonInstall.blur(); // Remove focus from button 15070 confirmationDialog('This will upload the latest ISY portal nodes definitions to your ISY. When the upload is completed, your ISY will be rebooted.<br/><br/>Are you sure?', 'Upload nodes definitions?') 15071 .then(function () { 15072 ajaxJsonGet('/api/nsmanage/' + uuid + '/install') 15073 .done(function () { 15074 refresh(true); // Reboot after refresh 15075 informationMessageDialog('Upload completed successfully. ISY is currently rebooting.') 15076 .done(function () { 15077 $(self).dialog('close'); 15078 tableIsys.reload(); 15079 }) 15080 .done(); 15081 }) 15082 .fail(function(jqXHR, textStatus, errorThrown) { 15083 if (jqXHR.status==503) { 15084 errorMessageDialog('ISY is not online') 15085 } 15086 else if (jqXHR.status==504) { 15087 errorMessageDialog('Timeout occured. Please try again.') 15088 } 15089 else if (jqXHR.status==550) { // Custom error message 15090 var data = JSON.parse(jqXHR.responseText); 15091 15092 if (data.message) { 15093 errorMessageDialog(data.message) 15094 } 15095 else { 15096 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15097 } 15098 } 15099 else { 15100 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15101 } 15102 }) 15103 .done(); 15104 }) 15105 .done(); 15106 }, 15107 'Remove': function() { 15108 $buttonRemove.blur(); // Remove focus from button 15109 confirmationDialog('WARNING: This will remove the connection, the nodes definitions, and also all of your ISY Portal node server nodes.<br/><br/>Are you sure?', 'Remove Node server configuration?') 15110 .then(function () {
15111 ajaxJsonGet('/api/nsmanage/' + uuid + '/removeall') 15112 .done(function () { 15113 refresh(false); // NO Reboot after refresh 15114 informationMessageDialog('Configuration removal completed successfully.') 15115 .done(function () { 15116 tableIsys.reload(); 15117 }) 15118 .done(); 15119 }) 15120 .fail(function(jqXHR, textStatus, errorThrown) { 15121 if (jqXHR.status==503) { 15122 errorMessageDialog('ISY is not online') 15123 } 15124 else if (jqXHR.status==504) { 15125 errorMessageDialog('Timeout occured. Please try again.') 15126 } 15127 else if (jqXHR.status==550) { // Custom error message 15128 var data = JSON.parse(jqXHR.responseText); 15129 15130 if (data.message) { 15131 errorMessageDialog(data.message) 15132 } 15133 else { 15134 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15135 } 15136 } 15137 else { 15138 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15139 } 15140 }) 15141 .done(); 15142 }) 15143 .done(); 15144 }, 15145 'Help' : function() { 15146 $buttonHelp.blur(); // Remove focus from button 15147 onlineHelp(); 15148 }, 15149 'Close': function() { 15150 $(this).dialog('close'); 15151 } 15152 }, 15153 15154 open: function(event, ui) { 15155 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 15156 } 15157 }; 15158 15159 // Initializes the dialog 15160 dialog = $('#nsconfig_dialog').dialog(isyOptions); 15161 // Tooltip removed - Causing issues as the scope is too wide 15162// $('[title]').tooltip(); 15163 15164 var $buttons = $('#nsconfig_dialog').parent().find('.ui-dialog-buttonset button'); 15165 var $buttonConfigure = $buttons.first(); 15166 var $buttonInstall = $buttons.eq(1); // Second button 15167 var $buttonRemove = $buttons.eq(2); // Third button 15168 var $buttonHelp = $buttons.eq(3); 15169 15170 return { modal: dialog, setFields: setFields, refresh: refresh }; 15171}; 15172 15173// Main Occupancu dialog (with the device table) 15174var nsOccupancyDialogInit = function () { 15175 var dialog, 15176 uuid, 15177 domain, 15178 validationPromise, 15179 locationsPromise; 15180 15181 function loadLocations(uuid) { 15182 locationsPromise = ajaxJsonGet('/api/nsmodule/' + uuid + '/occupancy/locations'); 15183
15184 // Only display locationsPromise error if validationPromise passed. 15185 validationPromise 15186 .done(function() { 15187 15188 locationsPromise 15189 .fail(function(jqXHR, textStatus, errorThrown) { 15190 if (jqXHR.status==503) { 15191 errorMessageDialog('ISY is not online') 15192 } 15193 else if (jqXHR.status==504) { 15194 errorMessageDialog('Timeout occured. Please try again.') 15195 } 15196 else if (jqXHR.status==550) { // Custom error message 15197 var data = JSON.parse(jqXHR.responseText); 15198 15199 if (data.message) { 15200 errorMessageDialog(data.message) 15201 } 15202 else { 15203 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15204 } 15205 } 15206 else { 15207 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15208 } 15209 }) 15210 .done(); 15211 }); 15212 15213 return locationsPromise; 15214 } 15215 15216 function setFields(obj) { 15217 $('#nsoccupancy_uuid').text(obj.uuid); 15218 $('#nsoccupancy_alias').text(obj.alias); 15219 15220 uuid = obj.uuid; 15221 domain = obj.domain; 15222 15223 // Detect if Node server is properly configured, connection and profile files 15224 validationPromise = ajaxJsonGet('/api/nsmanage/' + uuid + '/config/status'); 15225 15226 validationPromise 15227 .fail(function(jqXHR, textStatus, errorThrown) { 15228 if (jqXHR.status==503) { 15229 errorMessageDialog('ISY is not online') 15230 } 15231 else if (jqXHR.status==504) { 15232 errorMessageDialog('Timeout occured. Please try again.') 15233 } 15234 else if (jqXHR.status==550) { // Custom error message 15235 var data = JSON.parse(jqXHR.responseText); 15236 15237 if (data.message) { 15238 errorMessageDialog(data.message) 15239 } 15240 else { 15241 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15242 } 15243 } 15244 else { 15245 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15246 } 15247 }) 15248 .done(); 15249 15250 tableNsOccupancy.setUuid(uuid, domain, true); 15251 15252 loadLocations(uuid); 15253 tableNsOccupancy.setLocations(locationsPromise); 15254 } 15255 15256 function onlineHelp() { 15257 informationMessageDialog( 15258 '<div class="online-help"><p>The occupancy module allows your ISY programs to detect if your home is Occupied, and adjust the temperature or any other devices accordingly. This works using geofencing with the help of a mobile app on your household members' + "'" + ' mobile devices.<br><br>' + 15259 'The recommended application is "Locative" and is available on iOS and Android. But any app that can trigger a URL based on location will work.<br><br>' + 15260 'First, you need to "Add a geofence" for each of your mobile devices. This will create a corresponding node on your ISY for each one, along with an optional location node. ' + 15261 'The location node will be true (in) if any of the geofence nodes created in the location is true (in). ' + 15262 'A typical location would be "Home". So, this allows your ISY to have a global home occupancy flag telling you if anyone is home, or not.<br><br>' + 15263 'In order to configure your mobile app, choose a node in the list and click on <span class="linkbutton"><span class="ui-icon ui-icon-extlink"/></span>. This will give you the URL to use to report in or out status for each of your geofences. ' + 15264 'In your mobile app, configure your geofence with this URL, using the GET or POST method.<br><br>' + 15265 'If you accidentally delete a node in the admin console, you can recreate it using the Synchronize button. You can also click Remove to delete all occupancy related nodes.</p>', 15266 'Nodes Server Occupancy module Online Help', 825) 15267 } 15268 15269 var isyOptions = { 15270 autoOpen: false, 15271 width: 800, 15272 modal: true, 15273 buttons: { 15274 'Synchronize': function() { 15275 $buttonSynchronize.blur(); // Remove focus from button 15276 15277 validationPromise.then(function(data) { 15278 if (data.data.profileNumber && data.data.profileVersion) { 15279 confirmationDialog('This will resynchronize your geofences with ISY.<br/><br/>Are you sure?', 'Synchronize?') 15280 .then(function () { 15281 ajaxJsonGet('/api/nsmanage/' + uuid + '/synchronize/occupancy') 15282 .done(function () { 15283 informationMessageDialog('Synchronize completed successfully.') 15284 .done(function () { 15285 tableNsOccupancy.reload(); 15286 }) 15287 .done(); 15288 }) 15289 .fail(function(jqXHR, textStatus, errorThrown) { 15290 if (jqXHR.status==503) { 15291 errorMessageDialog('ISY is not online') 15292 } 15293 else if (jqXHR.status==504) { 15294 errorMessageDialog('Timeout occured. Please try again.') 15295 } 15296 else if (jqXHR.status==550) { // Custom error message 15297 var data = JSON.parse(jqXHR.responseText); 15298 15299 if (data.message) { 15300 errorMessageDialog(data.message) 15301 } 15302 else { 15303 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15304 } 15305 } 15306 else { 15307 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15308 } 15309 }) 15310 .done(); 15311 }) 15312 .done(); 15313 } 15314 else { 15315 errorMessageDialog('Node Server is not configured.', 'Error') 15316 } 15317 }) 15318 }, 15319 'Remove': function() { 15320 $buttonRemove.blur(); // Remove focus from button 15321 15322 validationPromise.then(function(data) { 15323 if (data.data.profileNumber && data.data.profileVersion) { 15324 confirmationDialog('This will remove all of your geofences from your configuration and ISY.<br/><br/>Are you sure?', 'Remove geofences?') 15325 .then(function () {
15326 ajaxJsonGet('/api/nsmanage/' + uuid + '/remove/occupancy') 15327 .done(function () { 15328 informationMessageDialog('Removal completed successfully.') 15329 .done(function () { 15330 tableNsOccupancy.reload(); 15331 loadLocations(uuid); // No need update the tableNsOccupancy promise, there is nothing more to edit 15332 }) 15333 .done(); 15334 }) 15335 .fail(function(jqXHR, textStatus, errorThrown) { 15336 if (jqXHR.status==503) { 15337 errorMessageDialog('ISY is not online') 15338 } 15339 else if (jqXHR.status==504) { 15340 errorMessageDialog('Timeout occured. Please try again.') 15341 } 15342 else if (jqXHR.status==550) { // Custom error message 15343 var data = JSON.parse(jqXHR.responseText); 15344 15345 if (data.message) { 15346 errorMessageDialog(data.message) 15347 } 15348 else { 15349 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15350 } 15351 } 15352 else { 15353 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15354 } 15355 }) 15356 .done(); 15357 }) 15358 .done(); 15359 } 15360 else { 15361 errorMessageDialog('Node Server is not configured.', 'Error') 15362 } 15363 }) 15364 }, 15365 'Help' : function() { 15366 $buttonHelp.blur(); // Remove focus from button 15367 onlineHelp(); 15368 }, 15369 'Close': function() { 15370 $(this).dialog('close'); 15371 } 15372 }, 15373 open: function(event, ui) { 15374 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 15375 } 15376 }; 15377 15378 // Initializes the dialog 15379 dialog = $('#nsoccupancy_dialog').dialog(isyOptions); 15380 // Tooltip removed - Causing issues as the scope is too wide 15381// $('[title]').tooltip(); 15382 15383 var $buttons = $('#nsoccupancy_dialog').parent().find('.ui-dialog-buttonset button'); 15384 var $buttonSynchronize = $buttons.first(); // First button 15385 var $buttonRemove = $buttons.eq(1); // Second button 15386 var $buttonHelp = $buttons.eq(2); 15387 15388 $('#nsoccupancy_add').button().click(function () { 15389 $(this).blur(); // Remove focus from button 15390 15391 validationPromise.then(function(data) { 15392 if (data.data.profileNumber && data.data.profileVersion) { 15393 locationsPromise 15394 .done(function(locations) { 15395 nsOccupancyEditDialog.setFields({ uuid: uuid, domain: domain, editMode:false, locations: locations.data }); 15396 nsOccupancyEditDialog.modal.dialog('open'); 15397 }) 15398 .fail(function(jqXHR, textStatus, errorThrown) { 15399 errorMessageDialog('Error loading locations', 'Error', 350) 15400 }) 15401 } 15402 else { 15403 errorMessageDialog('Node Server is not configured.', 'Error') 15404 } 15405 }) 15406 }); 15407 15408 $('#nsoccupancy_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 15409 $(this).blur(); // Remove focus from button 15410 tableNsOccupancy.reload(); 15411 }); 15412 15413 return { modal: dialog, setFields: setFields, loadLocations: loadLocations }; 15414}; 15415 15416// Mobile devices edit dialog 15417var nsOccupancyEditDialogInit = function () { 15418 var dialog, 15419 form, 15420 pk, 15421 uuid, 15422 domain, 15423 editMode, 15424 locations, 15425 name = $('#nsoccupancy_name'), 15426 allFields = $([]).add(name), 15427 tips = $('#nsoccupancy_edit_dialog .validateTips'); 15428 15429 function updateSelector(locations, location) { 15430 var $select = $('<select><option value="$nolocation">(No location)</option></select>'); 15431 15432 locations.sort(function(a, b) { 15433 return a.name.localeCompare(b.name) } 15434 ); 15435 15436 locations.forEach(function(location) { 15437 $select.append('<option value="' + location.address + '">' + location.name + ' (' + location.address + ')</option>'); 15438 }); 15439 15440 if (!location) { 15441 location = $('#nsoccupancy_locations_selector select').val(); 15442 } 15443 15444 $('#nsoccupancy_locations_selector').empty().append($select.clone()); 15445 15446 if (location) { // If no location is specified, and selector did not exist, it would be set to null 15447 $('#nsoccupancy_locations_selector select').val(location); 15448 } 15449 15450 $('#nsoccupancy_locations_selector select').selectmenu({ 15451 width: '80%' 15452 }); 15453 } 15454 15455 function setFields(obj) { 15456 var label; 15457 15458 uuid = obj.uuid; 15459 domain = obj.domain; 15460 pk = obj.pk; 15461 15462 form[0].reset(); 15463 name.val(obj.name); 15464 15465 editMode = obj.editMode; 15466 15467 tips.text('Please enter a name for this geofence'); 15468 15469 allFields.removeClass('fieldfocus'); 15470 15471 if (!editMode) { 15472 obj.location = '$nolocation'; 15473 } 15474 15475 updateSelector(obj.locations, obj.location); 15476 } 15477 15478 function submit() {
15479 var valid = true; 15480 15481 allFields.removeClass('fieldfocus'); 15482 15483 valid = valid && checkLength(tips, name, 'Name', 2, 40); 15484 valid = valid && checkRegexp(tips, name, nodeNameRegex, 'node name is not valid.' ); 15485 15486 if (valid) { 15487 var data = form.find('input').serialize(); 15488 data += '&location=' + $('#nsoccupancy_locations_selector select').val(); 15489 data += '&domain=' + domain; 15490 15491 if (editMode) { 15492 data += '&id=' + pk; 15493 } 15494 15495 var url = '/api/nsmodule/' + uuid + '/occupancy/node'; 15496 15497 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 15498 .then(function(data, status, jqXHR) { 15499 dialog.dialog('close'); 15500 tableNsOccupancy.reload(); 15501 }) 15502 .fail(function(jqXHR, textStatus, errorThrown) { 15503 if (jqXHR.status==503) { 15504 updateTips(tips, 'ISY is not online'); 15505 } 15506 else if (jqXHR.status==404) { // Can happen if device was just deleted and recreated shortly with same address 15507 updateTips(tips, 'Could not create device on ISY (HTTP 404 returned). Please try again later.'); 15508 } 15509 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 15510 var data = JSON.parse(jqXHR.responseText); 15511 15512 if (data.message) { 15513 updateTips(tips, data.message); 15514 } 15515 else { 15516 updateTips(tips, 'HTTP error ' + jqXHR.status); 15517 } 15518 } 15519 else { 15520 updateTips(tips, 'HTTP error ' + jqXHR.status); 15521 } 15522 }) 15523 .done(); 15524 } 15525 } 15526 15527 var isyOptions = { 15528 autoOpen: false, 15529 width: 500, 15530 modal: true, 15531 buttons: { 15532 'Save': function() { 15533 submit(); 15534 } 15535 }, 15536 open: function(event, ui) { 15537 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 15538 } 15539 }; 15540 15541 // Initializes the dialog 15542 dialog = $('#nsoccupancy_edit_dialog').dialog(isyOptions); 15543 // Tooltip removed - Causing issues as the scope is too wide 15544// $('[title]').tooltip(); 15545 15546 $('#nsoccupancy_locationadd').button({ icons: {primary: 'ui-icon-plus'}, text: false}).tooltip().click(function(event) { 15547 $(this).blur(); // Remove focus from button 15548 nsOccupancyLocationEditDialog.setFields({ uuid: uuid, editMode: false }); 15549 nsOccupancyLocationEditDialog.modal.dialog('open'); 15550 }); 15551 15552 // Handles keyboad enter (submit event) 15553 form = dialog.find('form').submit(function(event) { 15554 event.preventDefault(); 15555 submit(); 15556 }); 15557 15558 return { modal: dialog, setFields: setFields, updateSelector: updateSelector }; 15559}; 15560 15561// Occupancy location edit dialog 15562var nsOccupancyLocationEditDialogInit = function () { 15563 var dialog, 15564 form, 15565 pk, 15566 uuid, 15567 domain, 15568 editMode, 15569 name = $('#nsoccupancy_location_name'), 15570 allFields = $([]).add(name), 15571 tips = $('#nsoccupancy_location_edit_dialog .validateTips'); 15572 15573 function setFields(obj) { 15574 var label; 15575 15576 uuid = obj.uuid; 15577 domain = obj.domain; 15578 pk = obj.pk; 15579 15580 form[0].reset(); 15581 name.val(obj.name); 15582 15583 editMode = obj.editMode; 15584 15585 tips.text('Please enter a location name'); 15586 15587 allFields.removeClass('fieldfocus'); 15588 } 15589 15590 function submit() { 15591 var valid = true; 15592 15593 allFields.removeClass('fieldfocus'); 15594 15595 valid = valid && checkLength(tips, name, 'Name', 2, 40); 15596 valid = valid && checkRegexp(tips, name, nodeNameRegex, 'location name is not valid.' ); 15597 15598 if (valid) { 15599 var data = form.find('input').serialize(); 15600 data += '&domain=' + domain; 15601 15602 if (editMode) { 15603 data += '&id=' + pk; 15604 } 15605 15606 var url = '/api/nsmodule/' + uuid + '/occupancy/location'; 15607 15608 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 15609 .then(function(locationRecord, status, jqXHR) { 15610 tableNsOccupancy.reload(); 15611 15612 var locationsPromise = nsOccupancyDialog.loadLocations(uuid); 15613 tableNsOccupancy.setLocations(locationsPromise); // Will be available when we edit! 15614 15615 locationsPromise.done(function(data) { 15616 var newLocation=null; 15617 15618 if (locationRecord.data && locationRecord.data.address) { 15619 newLocation = locationRecord.data.address; 15620 } 15621 15622 nsOccupancyEditDialog.updateSelector(data.data, newLocation); 15623 dialog.dialog('close'); 15624 }) 15625 }) 15626 .fail(function(jqXHR, textStatus, errorThrown) { 15627 if (jqXHR.status==503) { 15628 updateTips(tips, 'ISY is not online'); 15629 } 15630 else if (jqXHR.status==404) { // Can happen if device was just deleted and recreated shortly with same address 15631 updateTips(tips, 'Could not create location on ISY (HTTP 404 returned). Please try again later.'); 15632 } 15633 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 15634 var data = JSON.parse(jqXHR.responseText); 15635 15636 if (data.message) { 15637 updateTips(tips, data.message); 15638 } 15639 else { 15640 updateTips(tips, 'HTTP error ' + jqXHR.status); 15641 } 15642 } 15643 else { 15644 updateTips(tips, 'HTTP error ' + jqXHR.status); 15645 } 15646 }) 15647 .done(); 15648 } 15649 } 15650 15651 var isyOptions = { 15652 autoOpen: false, 15653 width: 400, 15654 modal: true, 15655 buttons: { 15656 'Save': function() { 15657 submit(); 15658 } 15659 }, 15660 open: function(event, ui) { 15661 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 15662 } 15663 }; 15664 15665 // Initializes the dialog 15666 dialog = $('#nsoccupancy_location_edit_dialog').dialog(isyOptions); 15667 // Tooltip removed - Causing issues as the scope is too wide 15668// $('[title]').tooltip(); 15669 15670 // Handles keyboad enter (submit event) 15671 form = dialog.find('form').submit(function(event) { 15672 event.preventDefault(); 15673 submit(); 15674 }); 15675 15676 return { modal: dialog, setFields: setFields }; 15677}; 15678 15679var nsOccupancyUrlDialogInit = function() { 15680 var dialog; 15681 15682 function setFields(hostname, key) { 15683 var url = 'https://' + hostname + '/api/location/' + key + '/report/'; 15684 $('#report_url_in').text(url+'in'); 15685 $('#report_url_out').text(url+'out'); 15686 } 15687 15688 var isyOptions = { 15689 autoOpen: false, 15690 modal: true, 15691 width: 875, 15692 buttons: { 15693 'Ok': function() { 15694 $(this).dialog('close'); 15695 } 15696 }, 15697 close: function() { 15698 } 15699 }; 15700 dialog = $('#occupancy_url_dialog').dialog(isyOptions); 15701 15702 $('#occupancy_url_dialog .copy_clip').button().click(function() { 15703 $(this).blur(); 15704 }); 15705 15706 if (!Clipboard.isSupported()) { 15707 $('#occupancy_url_dialog .copy_clip').hide(); 15708 } 15709 15710 var clipboard = new Clipboard('#occupancy_url_dialog .copy_clip'); 15711 15712 clipboard.on('success', function(e) { 15713 var target = $(e.trigger).attr('data-clipboard-target'); 15714 if($(target).tooltip('option', 'disabled')) { 15715 $(target).tooltip('option', {disabled: false, content: 'Copied'}).tooltip('open') 15716 } 15717 setTimeout(function(){ 15718 e.clearSelection(); 15719 $(target).tooltip('option', {disabled: true}).tooltip('close') 15720 }, 800); 15721 }); 15722 15723 clipboard.on('error', function(e) { 15724 var target = $(e.trigger).attr('data-clipboard-target'); 15725 if($(target).tooltip('option', 'disabled')) { 15726 $(target).tooltip('option', {disabled: false, content: 'COPY FAILED'}).tooltip('open') 15727 } 15728 setTimeout(function(){ 15729 e.clearSelection(); 15730 $(target).tooltip('option', {disabled: true}).tooltip('close') 15731 }, 2000); 15732 }); 15733 15734 $(".copied").tooltip({ 15735 disabled: true, 15736 position: { my: "center", at: "center", collision: "flipfit" } 15737 }) 15738 .hover(function(){ 15739 $(this).tooltip('option', {disabled: true}).tooltip('close'); 15740 }); 15741 15742 return { modal: dialog, setFields: setFields }; 15743}; 15744 15745// Occupancy2 Main dialog (with Geofences table and Mobile Devices table) 15746var nsOccupancy2DialogInit = function () { 15747 var dialog, 15748 uuid, 15749 domain, 15750 validationPromise, 15751 geofencesPromise, 15752 devicesPromise; 15753 15754 function loadGeofences(uuid) { 15755 geofencesPromise = ajaxJsonGet('/api/nsmodule/' + uuid + '/occupancy2/geofences'); 15756
15757 // Only display geofencesPromise error if validationPromise passed. 15758 validationPromise 15759 .done(function() { 15760 15761 geofencesPromise 15762 .fail(function(jqXHR, textStatus, errorThrown) { 15763 if (jqXHR.status==503) { 15764 errorMessageDialog('ISY is not online') 15765 } 15766 else if (jqXHR.status==504) { 15767 errorMessageDialog('Timeout occured. Please try again.') 15768 } 15769 else if (jqXHR.status==550) { // Custom error message 15770 var data = JSON.parse(jqXHR.responseText); 15771 15772 if (data.message) { 15773 errorMessageDialog(data.message) 15774 } 15775 else { 15776 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15777 } 15778 } 15779 else { 15780 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15781 } 15782 }) 15783 .done(); 15784 }); 15785 15786 return geofencesPromise; 15787 } 15788 15789 function loadDevices(uuid) { 15790 devicesPromise = ajaxJsonGet('/api/nsmodule/' + uuid + '/occupancy2/devices'); 15791
15792 // Only display devicesPromise error if validationPromise passed. 15793 validationPromise 15794 .done(function() { 15795 15796 devicesPromise 15797 .fail(function(jqXHR, textStatus, errorThrown) { 15798 if (jqXHR.status==503) { 15799 errorMessageDialog('ISY is not online') 15800 } 15801 else if (jqXHR.status==504) { 15802 errorMessageDialog('Timeout occured. Please try again.') 15803 } 15804 else if (jqXHR.status==550) { // Custom error message 15805 var data = JSON.parse(jqXHR.responseText); 15806 15807 if (data.message) { 15808 errorMessageDialog(data.message) 15809 } 15810 else { 15811 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15812 } 15813 } 15814 else { 15815 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15816 } 15817 }) 15818 .done(); 15819 }); 15820 15821 return devicesPromise; 15822 } 15823 15824 function setFields(obj) { 15825 $('#nsoccupancy2_uuid').text(obj.uuid); 15826 $('#nsoccupancy2_alias').text(obj.alias); 15827 15828 uuid = obj.uuid; 15829 domain = obj.domain; 15830 15831 // Detect if Node server is properly configured, connection and profile files 15832 validationPromise = ajaxJsonGet('/api/nsmanage/' + uuid + '/config/status'); 15833 15834 validationPromise 15835 .fail(function(jqXHR, textStatus, errorThrown) { 15836 if (jqXHR.status==503) { 15837 errorMessageDialog('ISY is not online') 15838 } 15839 else if (jqXHR.status==504) { 15840 errorMessageDialog('Timeout occured. Please try again.') 15841 } 15842 else if (jqXHR.status==550) { // Custom error message 15843 var data = JSON.parse(jqXHR.responseText); 15844 15845 if (data.message) { 15846 errorMessageDialog(data.message) 15847 } 15848 else { 15849 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15850 } 15851 } 15852 else { 15853 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15854 } 15855 }) 15856 .done(); 15857 15858 tableNsOccupancy2Geofence.setUuid(uuid, domain, true); 15859 tableNsOccupancy2Device.setUuid(uuid, domain, true); 15860 15861 loadGeofences(uuid); 15862 loadDevices(uuid); 15863 tableNsOccupancy2Geofence.setGeofences(geofencesPromise); 15864 tableNsOccupancy2Geofence.setDevices(devicesPromise); 15865 tableNsOccupancy2Device.setDevices(devicesPromise); 15866 } 15867 15868 function onlineHelp() { 15869 informationMessageDialog( 15870 '<div class="online-help"><p>The Occupancy module v2.0 allows your ISY programs to detect if your "Geofence" is Occupied, and adjust thermostat or any other device accordingly. This works using Geofencing with the help of a Mobile App on your household members mobile devices.<br><br>' + 15871 'The recommended application is "UDI Mobile" and is available on iOS and Android. But any app that can trigger a URL based on Geofence will work.<br><br>' + 15872 'First, you need to "Add Geofence" for each of your locations you want to monitor Occupancy (ie. "Home"). This will create a corresponding node on your ISY, allowing to have a global "Home" occupancy flag telling you if anyone is at this location, or not.<br><br>' + 15873 'Next, you need to "Add a Mobile Device" for each of your household members mobile devices (ie. "Dadâs iPhone"). Additionally, for "UDI Mobile" you can choose a profile for each mobile device: User or Editor (ie. Dad can edit geofence data but Kids canât).<br><br>' + 15874 'And then, you need to "Assign Mobile Device" to each "Geofence" in order to monitor Occupancy (ie. "Dadâs iPhone" is at "Home"). This will create a corresponding node on your ISY for each one. The node will be true (in) if the mobile device enters the assigned geofence and will be false (out) if leaves.<br><br>' + 15875 'In order to configure "UDI Mobile" in your mobile device, choose a "Mobile Device" in ISY Portal list and click on <span class="linkbutton"><span class="ui-icon ui-icon-extlink"/></span> in "Linked" column. This will give you a 6-digit Pin that you have to enter the first time you open "UDI Mobile" in order to Link your mobile device with ISY Portal, then the app will obtain all geofences assigned for this mobile device and (if allowed) let you edit geofence data. From this point, the app will report to ISY portal each time mobile device enters o leaves each geofence.<br><br>' + 15876 'In order to configure any other mobile app, choose a node in the list and click on <span class="linkbutton"><span class="ui-icon ui-icon-extlink"/></span>. This will give you the URL to use to report in or out status for each of your geofences. In your mobile app, configure your geofence with this URL, using the GET or POST method.<br><br>' + 15877 'If you accidentally delete a node in the admin console, you can recreate it using the Synchronize button. You can also click Remove to delete all occupancy related nodes (Geofences) and Mobile Devices.</p>',
15878 'Nodes Server Occupancy module v2.0 Online Help', 825) 15879 } 15880 15881 var isyOptions = { 15882 autoOpen: false, 15883 width: 800, 15884 modal: true, 15885 buttons: { 15886 'Synchronize': function() { 15887 $buttonSynchronize.blur(); // Remove focus from button 15888 15889 validationPromise.then(function(data) { 15890 if (data.data.profileNumber && data.data.profileVersion) { 15891 confirmationDialog('This will resynchronize your geofences with ISY.<br/><br/>Are you sure?', 'Synchronize?') 15892 .then(function () { 15893 ajaxJsonGet('/api/nsmanage/' + uuid + '/synchronize/occ2_geofence') 15894 .done(function () { 15895 ajaxJsonGet('/api/nsmanage/' + uuid + '/synchronize/occ2_device') 15896 .done(function () { 15897 informationMessageDialog('Synchronize completed successfully.') 15898 .done(function () { 15899 tableNsOccupancy2Geofence.reload(); 15900 tableNsOccupancy2Device.reload(); 15901 }) 15902 .done(); 15903 }) 15904 .fail(function(jqXHR, textStatus, errorThrown) { 15905 if (jqXHR.status==503) { 15906 errorMessageDialog('ISY is not online') 15907 } 15908 else if (jqXHR.status==504) { 15909 errorMessageDialog('Timeout occured. Please try again.') 15910 } 15911 else if (jqXHR.status==550) { // Custom error message 15912 var data = JSON.parse(jqXHR.responseText); 15913 15914 if (data.message) { 15915 errorMessageDialog(data.message) 15916 } 15917 else { 15918 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15919 } 15920 } 15921 else { 15922 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15923 } 15924 }) 15925 .done(); 15926 }) 15927 .fail(function(jqXHR, textStatus, errorThrown) { 15928 if (jqXHR.status==503) { 15929 errorMessageDialog('ISY is not online') 15930 } 15931 else if (jqXHR.status==504) { 15932 errorMessageDialog('Timeout occured. Please try again.') 15933 } 15934 else if (jqXHR.status==550) { // Custom error message 15935 var data = JSON.parse(jqXHR.responseText); 15936 15937 if (data.message) { 15938 errorMessageDialog(data.message) 15939 } 15940 else { 15941 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15942 } 15943 } 15944 else { 15945 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15946 } 15947 }) 15948 .done(); 15949 }) 15950 .done(); 15951 } 15952 else { 15953 errorMessageDialog('Node Server is not configured.', 'Error') 15954 } 15955 }) 15956 }, 15957 'Remove': function() { 15958 $buttonRemove.blur(); // Remove focus from button 15959 15960 validationPromise.then(function(data) { 15961 if (data.data.profileNumber && data.data.profileVersion) { 15962 confirmationDialog('This will remove all of your geofences from your configuration and ISY.<br/><br/>Are you sure?', 'Remove geofences?') 15963 .then(function () {
15964 ajaxJsonGet('/api/nsmanage/' + uuid + '/remove/occ2_device') 15965 .done(function () { 15966 ajaxJsonGet('/api/nsmanage/' + uuid + '/remove/occ2_geofence') 15967 .done(function () { 15968 ajaxJsonDelete('/api/nsmodule/' + uuid + '/occupancy2/devices', $.param({uuid: uuid })) 15969 .fail(function(jqXHR, textStatus, errorThrown) { 15970 if (jqXHR.status==503) { 15971 errorMessageDialog('ISY is not online') 15972 } 15973 else if (jqXHR.status==400) { 15974 errorMessageDialog('Mobile devices has assigned geofences. Please unassign them first.') 15975 } 15976 else { 15977 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 15978 } 15979 }) 15980 .done(function () { 15981 informationMessageDialog('Removal completed successfully.') 15982 .done(function () { 15983 tableNsOccupancy2Geofence.reload(); 15984 tableNsOccupancy2Device.reload(); 15985 loadGeofences(uuid); // No need update the tableNsOccupancy2Geofence promise, there is nothing more to edit 15986 loadDevices(uuid); // No need update the tableNsOccupancy2Device promise, there is nothing more to edit 15987 }) 15988 .done(); 15989 }) 15990 }) 15991 .fail(function(jqXHR, textStatus, errorThrown) { 15992 if (jqXHR.status==503) { 15993 errorMessageDialog('ISY is not online') 15994 } 15995 else if (jqXHR.status==504) { 15996 errorMessageDialog('Timeout occured. Please try again.') 15997 } 15998 else if (jqXHR.status==550) { // Custom error message 15999 var data = JSON.parse(jqXHR.responseText); 16000 16001 if (data.message) { 16002 errorMessageDialog(data.message) 16003 } 16004 else { 16005 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 16006 } 16007 } 16008 else { 16009 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 16010 } 16011 }) 16012 .done() 16013 }) 16014 .fail(function(jqXHR, textStatus, errorThrown) { 16015 if (jqXHR.status==503) { 16016 errorMessageDialog('ISY is not online') 16017 } 16018 else if (jqXHR.status==504) { 16019 errorMessageDialog('Timeout occured. Please try again.') 16020 } 16021 else if (jqXHR.status==550) { // Custom error message 16022 var data = JSON.parse(jqXHR.responseText); 16023 16024 if (data.message) { 16025 errorMessageDialog(data.message) 16026 } 16027 else { 16028 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 16029 } 16030 } 16031 else { 16032 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 16033 } 16034 }) 16035 .done(); 16036 }) 16037 .done(); 16038 } 16039 else { 16040 errorMessageDialog('Node Server is not configured.', 'Error') 16041 } 16042 }) 16043 }, 16044 'Help' : function() { 16045 $buttonHelp.blur(); // Remove focus from button 16046 onlineHelp(); 16047 }, 16048 'Close': function() { 16049 $(this).dialog('close'); 16050 } 16051 }, 16052 open: function(event, ui) { 16053 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 16054 } 16055 }; 16056 16057 // Initializes the dialog 16058 dialog = $('#nsoccupancy2_dialog').dialog(isyOptions); 16059// Tooltip removed - Causing issues as the scope is too wide 16060// $('[title]').tooltip(); 16061 $("#nsoccupancy2_tabs").tabs(); 16062 16063 var $buttons = $('#nsoccupancy2_dialog').parent().find('.ui-dialog-buttonset button'); 16064 var $buttonSynchronize = $buttons.first(); // First button 16065 var $buttonRemove = $buttons.eq(1); // Second button 16066 var $buttonHelp = $buttons.eq(2); 16067 16068 $('#nsoccupancy2_geofenceadd').button().click(function () { 16069 $(this).blur(); // Remove focus from button 16070 validationPromise.then(function(data) { 16071 if (data.data.profileNumber && data.data.profileVersion) { 16072 nsOccupancy2GeofenceEditDialog.setFields({ uuid: uuid, editMode: false }); 16073 nsOccupancy2GeofenceEditDialog.modal.dialog('open'); 16074 } 16075 else { 16076 errorMessageDialog('Node Server is not configured.', 'Error') 16077 } 16078 }) 16079 });
16080 16081 $('#nsoccupancy2_deviceassign').button().click(function () { 16082 $(this).blur(); // Remove focus from button 16083 16084 validationPromise.then(function(data) { 16085 if (data.data.profileNumber && data.data.profileVersion) { 16086 geofencesPromise 16087 .done(function(geofences) { 16088 devicesPromise 16089 .done(function(devices) { 16090 nsOccupancy2EditDialog.setFields({ uuid: uuid, domain: domain, editMode:false, geofences: geofences.data, devices: devices.data }); 16091 nsOccupancy2EditDialog.modal.dialog('open'); 16092 }) 16093 .fail(function(jqXHR, textStatus, errorThrown) { 16094 errorMessageDialog('Error loading devices', 'Error', 350) 16095 }) 16096 }) 16097 .fail(function(jqXHR, textStatus, errorThrown) { 16098 errorMessageDialog('Error loading geofences', 'Error', 350) 16099 }) 16100 } 16101 else { 16102 errorMessageDialog('Node Server is not configured.', 'Error') 16103 } 16104 }) 16105 }); 16106 16107 $('#nsoccupancy2_refresh').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 16108 $(this).blur(); // Remove focus from button 16109 tableNsOccupancy2Geofence.reload(); 16110 tableNsOccupancy2Device.reload(); 16111 }); 16112 16113 $('#nsoccupancy2_deviceadd').button().click(function () { 16114 $(this).blur(); // Remove focus from button 16115 validationPromise.then(function(data) { 16116 if (data.data.profileNumber && data.data.profileVersion) { 16117 nsOccupancy2DeviceEditDialog.setFields({ uuid: uuid, editMode: false }); 16118 nsOccupancy2DeviceEditDialog.modal.dialog('open'); 16119 } 16120 else { 16121 errorMessageDialog('Node Server is not configured.', 'Error') 16122 } 16123 }) 16124 }); 16125 16126 $('#nsoccupancy2_refresh2').button({ icons: {primary: 'ui-icon-custom-refresh'}, text: false}).click(function(event) { 16127 $(this).blur(); // Remove focus from button 16128 tableNsOccupancy2Geofence.reload(); 16129 tableNsOccupancy2Device.reload(); 16130 }); 16131 16132 return { modal: dialog, setFields: setFields, loadGeofences: loadGeofences, loadDevices: loadDevices }; 16133}; 16134 16135// Occupancy2 Edit dialog - Assign Mobile Device to a Geofence 16136var nsOccupancy2EditDialogInit = function () { 16137 var dialog, 16138 form, 16139 pk, 16140 uuid, 16141 domain, 16142 editMode, 16143 geofences, 16144 devices, 16145 allFields = $([]), 16146 tips = $('#nsoccupancy2_edit_dialog .validateTips'); 16147 16148 function updateGeofenceSelector(geofences, geofence) { 16149 var $select = $('<select><option value="$nogeofence">(Select)</option></select>'); 16150 16151 geofences.sort(function(a, b) { 16152 return a.name.localeCompare(b.name) } 16153 ); 16154 16155 geofences.forEach(function(geofence) { 16156 $select.append('<option value="' + geofence.address + '">' + geofence.name + ' (' + geofence.address + ')</option>'); 16157 }); 16158 16159 if (!geofence) { 16160 geofence = $('#nsoccupancy2_geofences_selector select').val(); 16161 } 16162 16163 $('#nsoccupancy2_geofences_selector').empty().append($select.clone()); 16164 16165 if (geofence) { // If no geofence is specified, and selector did not exist, it would be set to null 16166 $('#nsoccupancy2_geofences_selector select').val(geofence); 16167 } 16168 16169 $('#nsoccupancy2_geofences_selector select').selectmenu({ 16170 width: '80%' 16171 }); 16172 } 16173 16174 function updateDeviceSelector(devices, device) { 16175 16176 if (!devices) devices = []; 16177 16178 var $select = $('<select><option value="$nodevice">(Select)</option></select>'); 16179 16180 devices.sort(function(a, b) { 16181 return a.name.localeCompare(b.name) } 16182 ); 16183 16184 devices.forEach(function(device) { 16185 $select.append('<option value="' + device.address + '">' + device.name + ' (' + device.address + ')</option>'); 16186 }); 16187 16188 if (!device) { 16189 device = $('#nsoccupancy2_devices_selector select').val(); 16190 } 16191 16192 $('#nsoccupancy2_devices_selector').empty().append($select.clone()); 16193 16194 if (device) { // If no mobile device is specified, and selector did not exist, it would be set to null 16195 $('#nsoccupancy2_devices_selector select').val(device); 16196 } 16197 16198 $('#nsoccupancy2_devices_selector select').selectmenu({ 16199 width: '80%' 16200 }); 16201 } 16202 16203 function setFields(obj) { 16204 var label; 16205 16206 uuid = obj.uuid; 16207 domain = obj.domain; 16208 pk = obj.pk; 16209 16210 form[0].reset(); 16211 16212 editMode = obj.editMode; 16213 16214 tips.text('Please select a Geofence and then assign a Mobile Device'); 16215 16216 allFields.removeClass('fieldfocus'); 16217 16218 if (!editMode) { 16219 obj.geofence = '$nogeofence'; 16220 obj.device = '$nodevice'; 16221 } 16222 16223 updateGeofenceSelector(obj.geofences, obj.geofence); 16224 updateDeviceSelector(obj.devices, obj.device); 16225 } 16226 16227 function submit() {
16228 var valid = true; 16229 16230 allFields.removeClass('fieldfocus'); 16231 16232 valid = valid && checkSelection(tips, $('#nsoccupancy2_geofences_selector select'), '$nogeofence', 'Geofence' ); 16233 valid = valid && checkSelection(tips, $('#nsoccupancy2_devices_selector select'), '$nodevice', 'Mobile Device' ); 16234 16235 if (valid) { 16236 var data = 'geofence=' + $('#nsoccupancy2_geofences_selector select').val(); 16237 data += '&deviceAddress=' + $('#nsoccupancy2_devices_selector select').val(); 16238 data += '&domain=' + domain; 16239 16240 if (editMode) { 16241 data += '&id=' + pk; 16242 } 16243 16244 var url = '/api/nsmodule/' + uuid + '/occupancy2/node'; 16245 16246 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 16247 .then(function(data, status, jqXHR) { 16248 dialog.dialog('close'); 16249 tableNsOccupancy2Geofence.reload(); 16250 tableNsOccupancy2Device.reload(); 16251 }) 16252 .fail(function(jqXHR, textStatus, errorThrown) { 16253 if (jqXHR.status==503) { 16254 updateTips(tips, 'ISY is not online'); 16255 } 16256 else if (jqXHR.status==400) { // Mobile Device already assigned to this Geofence 16257 updateTips(tips, jqXHR.responseText); 16258 } 16259 else if (jqXHR.status==404) { // Can happen if device was just deleted and recreated shortly with same address 16260 updateTips(tips, 'Could not create device on ISY (HTTP 404 returned). Please try again later.'); 16261 } 16262 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 16263 var data = JSON.parse(jqXHR.responseText); 16264 16265 if (data.message) { 16266 updateTips(tips, data.message); 16267 } 16268 else { 16269 updateTips(tips, 'HTTP error ' + jqXHR.status); 16270 } 16271 } 16272 else { 16273 updateTips(tips, 'HTTP error ' + jqXHR.status); 16274 } 16275 }) 16276 .done(); 16277 } 16278 } 16279 16280 var isyOptions = { 16281 autoOpen: false, 16282 width: 500, 16283 modal: true, 16284 buttons: { 16285 'Save': function() { 16286 submit(); 16287 } 16288 }, 16289 open: function(event, ui) { 16290 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 16291 } 16292 }; 16293 16294 // Initializes the dialog 16295 dialog = $('#nsoccupancy2_edit_dialog').dialog(isyOptions); 16296 // Tooltip removed - Causing issues as the scope is too wide 16297// $('[title]').tooltip(); 16298 16299 $('#nsoccupancy2_geofenceadd2').button({ icons: {primary: 'ui-icon-plus'}, text: false}).tooltip().click(function(event) { 16300 $(this).blur(); // Remove focus from button 16301 nsOccupancy2GeofenceEditDialog.setFields({ uuid: uuid, editMode: false }); 16302 nsOccupancy2GeofenceEditDialog.modal.dialog('open'); 16303 }); 16304 16305 $('#nsoccupancy2_deviceadd2').button({ icons: {primary: 'ui-icon-plus'}, text: false}).tooltip().click(function(event) { 16306 $(this).blur(); // Remove focus from button 16307 nsOccupancy2DeviceEditDialog.setFields({ uuid: uuid, editMode: false }); 16308 nsOccupancy2DeviceEditDialog.modal.dialog('open'); 16309 }); 16310 16311 // Handles keyboad enter (submit event) 16312 form = dialog.find('form').submit(function(event) { 16313 event.preventDefault(); 16314 submit(); 16315 }); 16316 16317 return { modal: dialog, setFields: setFields, updateGeofenceSelector: updateGeofenceSelector, updateDeviceSelector: updateDeviceSelector }; 16318}; 16319 16320// Occupancy2 Edit dialog - Geofence 16321var nsOccupancy2GeofenceEditDialogInit = function () { 16322 var dialog, 16323 form, 16324 pk, 16325 uuid, 16326 domain, 16327 editMode, 16328 name = $('#nsoccupancy2_geofence_name'), 16329 allFields = $([]).add(name), 16330 tips = $('#nsoccupancy2_geofence_edit_dialog .validateTips'); 16331 16332 function setFields(obj) { 16333 16334 var label; 16335 16336 uuid = obj.uuid; 16337 domain = obj.domain; 16338 pk = obj.pk; 16339 16340 form[0].reset(); 16341 name.val(obj.name); 16342 16343 editMode = obj.editMode; 16344 16345 tips.text('Please enter a Geofence name'); 16346 16347 allFields.removeClass('fieldfocus'); 16348 } 16349 16350 function submit() {
16351 var valid = true; 16352 16353 allFields.removeClass('fieldfocus'); 16354 16355 valid = valid && checkLength(tips, name, 'Name', 2, 40); 16356 valid = valid && checkRegexp(tips, name, nodeNameRegex, 'Geofence name is not valid, please use only alphanumeric characters.' ); 16357 16358 if (valid) { 16359 var data = form.find('input').serialize(); 16360 data += '&domain=' + domain; 16361 16362 if (editMode) { 16363 data += '&id=' + pk; 16364 } 16365 16366 var url = '/api/nsmodule/' + uuid + '/occupancy2/geofence'; 16367 16368 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 16369 .then(function(geofenceRecord, status, jqXHR) { 16370 tableNsOccupancy2Geofence.reload(); 16371 16372 var geofencesPromise = nsOccupancy2Dialog.loadGeofences(uuid); 16373 tableNsOccupancy2Geofence.setGeofences(geofencesPromise); // Will be available when we edit! 16374 16375 geofencesPromise.done(function(data) { 16376 var newGeofence=null; 16377 16378 if (geofenceRecord.data && geofenceRecord.data.address) { 16379 newGeofence = geofenceRecord.data.address; 16380 } 16381 16382 nsOccupancy2EditDialog.updateGeofenceSelector(data.data, newGeofence); 16383 dialog.dialog('close'); 16384 }) 16385 }) 16386 .fail(function(jqXHR, textStatus, errorThrown) { 16387 if (jqXHR.status==503) { 16388 updateTips(tips, 'ISY is not online'); 16389 } 16390 else if (jqXHR.status==404) { // Can happen if device was just deleted and recreated shortly with same address 16391 updateTips(tips, 'Could not create geofence on ISY (HTTP 404 returned). Please try again later.'); 16392 } 16393 else if (jqXHR.status==400) { // Duplicated Geofence Name 16394 updateTips(tips, jqXHR.responseText); 16395 } 16396 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 16397 var data = JSON.parse(jqXHR.responseText); 16398 16399 if (data.message) { 16400 updateTips(tips, data.message); 16401 } 16402 else { 16403 updateTips(tips, 'HTTP error ' + jqXHR.status); 16404 } 16405 } 16406 else { 16407 updateTips(tips, 'HTTP error ' + jqXHR.status); 16408 } 16409 }) 16410 .done(); 16411 } 16412 } 16413 16414 var isyOptions = { 16415 autoOpen: false, 16416 width: 400, 16417 modal: true, 16418 buttons: { 16419 'Save': function() { 16420 submit(); 16421 } 16422 }, 16423 open: function(event, ui) { 16424 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 16425 } 16426 }; 16427 16428 // Initializes the dialog 16429 dialog = $('#nsoccupancy2_geofence_edit_dialog').dialog(isyOptions); 16430// Tooltip removed - Causing issues as the scope is too wide 16431//$('[title]').tooltip(); 16432 16433 // Handles keyboad enter (submit event) 16434 form = dialog.find('form').submit(function(event) { 16435 event.preventDefault(); 16436 submit(); 16437 }); 16438 16439 return { modal: dialog, setFields: setFields }; 16440}; 16441 16442// Occupancy2 Edit dialog - Mobile Device 16443var nsOccupancy2DeviceEditDialogInit = function () { 16444 var dialog, 16445 form, 16446 pk, 16447 uuid, 16448 domain, 16449 editMode, 16450 name = $('#nsoccupancy2_device_name'), 16451 allFields = $([]).add(name), 16452 tips = $('#nsoccupancy2_device_edit_dialog .validateTips'); 16453 16454 function updateProfileSelector(profile) { 16455 if (typeof profile!='string') profile = 'User'; 16456 if (profile==='' || !profile) profile = 'User'; 16457 var $select = $('<select><option value="User">User</option><option value="Editor">Editor</option></select>'); //<option value="Admin">Admin</option> 16458 $('#nsoccupancy2_profile_selector').empty().append($select.clone()); 16459 $('#nsoccupancy2_profile_selector select').val(profile); 16460 $('#nsoccupancy2_profile_selector select').selectmenu({ 16461 width: '40%' 16462 }); 16463 } 16464 16465 function setFields(obj) { 16466 16467 var label; 16468 16469 uuid = obj.uuid; 16470 domain = obj.domain; 16471 pk = obj.pk; 16472 16473 form[0].reset(); 16474 name.val(obj.name); 16475 16476 editMode = obj.editMode; 16477 16478 tips.text('Please enter required information:'); 16479 16480 allFields.removeClass('fieldfocus'); 16481 16482 updateProfileSelector(obj.profile); 16483 16484 } 16485 16486 function submit() {
16487 var valid = true; 16488 16489 allFields.removeClass('fieldfocus'); 16490 16491 valid = valid && checkLength(tips, name, 'Name', 2, 40); 16492 valid = valid && checkRegexp(tips, name, nodeNameRegex, 'Mobile Device name is not valid, please use only alphanumeric characters.' ); 16493 16494 if (valid) { 16495 var data = form.find('input').serialize(); 16496 data += '&profile=' + $('#nsoccupancy2_profile_selector select').val(); 16497 data += '&domain=' + domain; 16498 16499 if (editMode) { 16500 data += '&id=' + pk; 16501 } 16502 16503 var url = '/api/nsmodule/' + uuid + '/occupancy2/device'; 16504 16505 (editMode?ajaxJsonPost(url, data):ajaxJsonPut(url, data)) 16506 .then(function(deviceRecord, status, jqXHR) { 16507 tableNsOccupancy2Geofence.reload(); 16508 tableNsOccupancy2Device.reload(); 16509 var devicesPromise = nsOccupancy2Dialog.loadDevices(uuid); 16510 tableNsOccupancy2Geofence.setDevices(devicesPromise); // Will be available when we edit! 16511 tableNsOccupancy2Device.setDevices(devicesPromise); // Will be available when we edit! 16512 16513 devicesPromise.done(function(data) { 16514 var newDevice=null; 16515 16516 if (deviceRecord.data && deviceRecord.data.address) { 16517 newDevice = deviceRecord.data.address; 16518 } 16519 16520 nsOccupancy2EditDialog.updateDeviceSelector(data.data, newDevice); 16521 dialog.dialog('close'); 16522 }) 16523 }) 16524 .fail(function(jqXHR, textStatus, errorThrown) { 16525 if (jqXHR.status==503) { 16526 updateTips(tips, 'ISY is not online'); 16527 } 16528 else if (jqXHR.status==404) { // Can happen if device was just deleted and recreated shortly with same address 16529 updateTips(tips, 'Could not create geofence on ISY (HTTP 404 returned). Please try again later.'); 16530 } 16531 else if (jqXHR.status==400) { // Duplicated Mobile Device Name 16532 updateTips(tips, jqXHR.responseText); 16533 } 16534 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 16535 var data = JSON.parse(jqXHR.responseText); 16536 16537 if (data.message) { 16538 updateTips(tips, data.message); 16539 } 16540 else { 16541 updateTips(tips, 'HTTP error ' + jqXHR.status); 16542 } 16543 } 16544 else { 16545 updateTips(tips, 'HTTP error ' + jqXHR.status); 16546 } 16547 }) 16548 .done(); 16549 } 16550 } 16551 16552 var isyOptions = { 16553 autoOpen: false, 16554 width: 400, 16555 modal: true, 16556 buttons: { 16557 'Save': function() { 16558 submit(); 16559 } 16560 }, 16561 open: function(event, ui) { 16562 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 16563 } 16564 }; 16565 16566 // Initializes the dialog 16567 dialog = $('#nsoccupancy2_device_edit_dialog').dialog(isyOptions); 16568 // Tooltip removed - Causing issues as the scope is too wide 16569// $('[title]').tooltip(); 16570 16571 // Handles keyboad enter (submit event) 16572 form = dialog.find('form').submit(function(event) { 16573 event.preventDefault(); 16574 submit(); 16575 }); 16576 16577 return { modal: dialog, setFields: setFields }; 16578}; 16579 16580// Occupancy2 dialog - Geofence report URL 16581var nsOccupancy2GeofenceUrlDialogInit = function() { 16582 var dialog; 16583 16584 function setFields(hostname, key) { 16585 var url = 'https://' + hostname + '/api/occupancy2/node/' + key + '/report/'; 16586 $('#report_url_in2').text(url+'in'); 16587 $('#report_url_out2').text(url+'out'); 16588 } 16589 16590 var isyOptions = { 16591 autoOpen: false, 16592 modal: true, 16593 width: 875, 16594 buttons: { 16595 'Ok': function() { 16596 $(this).dialog('close'); 16597 } 16598 }, 16599 close: function() { 16600 } 16601 }; 16602 dialog = $('#occupancy2_geofence_url_dialog').dialog(isyOptions); 16603 16604 $('#occupancy2_geofence_url_dialog .copy_clip').button().click(function() { 16605 $(this).blur(); 16606 });
16607 16608 if (!Clipboard.isSupported()) { 16609 $('#occupancy2_geofence_url_dialog .copy_clip').hide(); 16610 } 16611 16612 var clipboard = new Clipboard('#occupancy2_geofence_url_dialog .copy_clip'); 16613 16614 clipboard.on('success', function(e) { 16615 var target = $(e.trigger).attr('data-clipboard-target'); 16616 if($(target).tooltip('option', 'disabled')) { 16617 $(target).tooltip('option', {disabled: false, content: 'Copied'}).tooltip('open') 16618 } 16619 setTimeout(function(){ 16620 e.clearSelection(); 16621 $(target).tooltip('option', {disabled: true}).tooltip('close') 16622 }, 800); 16623 }); 16624 16625 clipboard.on('error', function(e) { 16626 var target = $(e.trigger).attr('data-clipboard-target'); 16627 if($(target).tooltip('option', 'disabled')) { 16628 $(target).tooltip('option', {disabled: false, content: 'COPY FAILED'}).tooltip('open') 16629 } 16630 setTimeout(function(){ 16631 e.clearSelection(); 16632 $(target).tooltip('option', {disabled: true}).tooltip('close') 16633 }, 2000); 16634 }); 16635 16636 $(".copied").tooltip({ 16637 disabled: true, 16638 position: { my: "center", at: "center", collision: "flipfit" } 16639 }) 16640 .hover(function(){ 16641 $(this).tooltip('option', {disabled: true}).tooltip('close'); 16642 }); 16643 16644 return { modal: dialog, setFields: setFields }; 16645}; 16646 16647// Occupancy2 dialog - Mobile Device 6-digit pin 16648var nsOccupancy2DevicePinDialogInit = function() { 16649 16650 var dialog, 16651 pk, name, uuid, pinCode, pinValid; 16652 16653 function setFields(obj) { 16654 uuid = obj.uuid; 16655 pk = obj.pk; 16656 name = obj.name; 16657 this.setPin(obj); 16658 } 16659 16660 function setPin(obj) { 16661 var validDate = new Date(obj.pinValid); 16662 var checkDate = new Date(); 16663 16664 pinCode = obj.pinCode; 16665 pinValid = validDate.toLocaleString("en-US",{timeZone:"America/New_York"}); //toLocaleTimeString('en-US'); 16666 16667 if(validDate<checkDate) pinCode=''; 16668 16669 if(pinCode==='') $('#nsoccupancy2_pin_code').hide(); 16670 else $('#nsoccupancy2_pin_code').show(); 16671 $('#device_pin_name').text(name); 16672 $('#device_pin_code').text(pinCode); 16673 $('#device_pin_valid').text(pinValid); 16674 } 16675 16676 var isyOptions = { 16677 autoOpen: false, 16678 modal: true, 16679 width: 875, 16680 buttons: { 16681 'Ok': function() { 16682 $(this).dialog('close'); 16683 } 16684 }, 16685 close: function() { 16686 } 16687 }; 16688 dialog = $('#occupancy2_device_pin_dialog').dialog(isyOptions); 16689 16690 $('#nsoccupancy2_pin_generate').button().click(function () { 16691 $(this).blur(); // Remove focus from button 16692 confirmationDialog('Please confirm if you want to Generate new 6-digit pin.', 'Generate pin') 16693 .then(function () { 16694 ajaxJsonPost('/api/nsmodule/' + uuid + '/occupancy2/pin', $.param({id: pk})) 16695 .done(function (data) { 16696 nsOccupancy2DevicePinDialog.setPin(data); 16697 tableNsOccupancy2Device.reload(); 16698 }) 16699 .fail(function(jqXHR, textStatus, errorThrown) { 16700 if (jqXHR.status==503) { 16701 errorMessageDialog('ISY is not online') 16702 } 16703 else if (jqXHR.status==400) { // Pin assigned to another Mobile Devices (after 100 iterations) 16704 errorMessageDialog(jqXHR.responseText); 16705 } 16706 else if (jqXHR.status==504) { 16707 errorMessageDialog('Timeout occured. Please try again.') 16708 } 16709 else if (jqXHR.status==550) { // Custom error message 16710 var data = JSON.parse(jqXHR.responseText); 16711 16712 if (data.message) { 16713 errorMessageDialog(data.message) 16714 } 16715 else { 16716 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 16717 } 16718 } 16719 else { 16720 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350) 16721 } 16722 }) 16723 .done(); 16724 }) 16725 .done(); 16726 });
16727 16728 $('#occupancy2_device_pin_dialog .copy_clip').button().click(function() { 16729 $(this).blur(); 16730 }); 16731 16732 if (!Clipboard.isSupported()) { 16733 $('#occupancy2_device_pin_dialog .copy_clip').hide(); 16734 } 16735 16736 var clipboard = new Clipboard('#occupancy2_device_pin_dialog .copy_clip'); 16737 16738 clipboard.on('success', function(e) { 16739 var target = $(e.trigger).attr('data-clipboard-target'); 16740 if($(target).tooltip('option', 'disabled')) { 16741 $(target).tooltip('option', {disabled: false, content: 'Copied'}).tooltip('open') 16742 } 16743 setTimeout(function(){ 16744 e.clearSelection(); 16745 $(target).tooltip('option', {disabled: true}).tooltip('close') 16746 }, 800); 16747 }); 16748 16749 clipboard.on('error', function(e) { 16750 var target = $(e.trigger).attr('data-clipboard-target'); 16751 if($(target).tooltip('option', 'disabled')) { 16752 $(target).tooltip('option', {disabled: false, content: 'COPY FAILED'}).tooltip('open') 16753 } 16754 setTimeout(function(){ 16755 e.clearSelection(); 16756 $(target).tooltip('option', {disabled: true}).tooltip('close') 16757 }, 2000); 16758 }); 16759 16760 $(".copied").tooltip({ 16761 disabled: true, 16762 position: { my: "center", at: "center", collision: "flipfit" } 16763 }) 16764 .hover(function(){ 16765 $(this).tooltip('option', {disabled: true}).tooltip('close'); 16766 }); 16767 16768 return { modal: dialog, setFields: setFields, setPin: setPin }; 16769}; 16770 16771// AMAZON ECHO Spoken Devices & Scenes mapping 16772var echoSpokenNodesAVEditDialogInit = function () { 16773 var dialog, form, pk, uuid, domain, currentRooms, editMode, allFields, 16774 tips = $('#echo_av_dialog .validateTips'), 16775 room = $('#echo_av_room'), 16776 spokens = [$('#echo_av_spoken'), $('#echo_av_spoken2'), $('#echo_av_spoken3'), $('#echo_av_spoken4'), $('#echo_av_spoken5') ], 16777 16778 allFields = $([]).add(room).add(spokens[0]).add(spokens[1]).add(spokens[2]).add(spokens[3]).add(spokens[4]).add(spokens[5]); 16779 16780 var fieldGroups = [ { 16781 target: '#echo_av_tab2', 16782 fields: [ 16783 { label: 'Turn On', baseField: 'turnon', type: 1, help: 'Alexa, turn on <device>' }, 16784 { label: 'Turn Off', baseField: 'turnoff', type: 1, help: 'Alexa, turn off <device>' }, 16785 { label: 'Set Channel To', baseField: 'setchannel', type: 2, class_label:'vspacer', help: 'Alexa, set channel to <number> on <device>' }, 16786 { label: 'Channel Up', baseField: 'channelup', type: 1, help: 'Alexa, channel up on <device>' }, 16787 { label: 'Channel Down', baseField: 'channeldown', type: 1, help: 'Alexa, channel down on <device>' } 16788 ] 16789 }, { 16790 target: '#echo_av_tab3', 16791 fields: [ 16792 { label: 'Set Volume To', baseField: 'setvolume', type: 2, help: 'Alexa, set volume to <number> on <device>' }, 16793 { label: 'Volume Up', baseField: 'volumeup', type: 1, help: 'Alexa, volume up on <device>' }, 16794 { label: 'Volume Down', baseField: 'volumedown', type: 1, help: 'Alexa, volume down on <device>' }, 16795 { label: 'Mute', baseField: 'mute', type: 1, class_label:'vspacer', help: 'Alexa, mute <device>' }, 16796 { label: 'Unmute', baseField: 'unmute', type: 1, help: 'Alexa, unmute <device>' } 16797 ] 16798 }, { 16799 target: '#echo_av_tab4', 16800 fields: [ 16801 { label: 'Play', baseField: 'play', type: 1, help: 'Alexa, play on <device>' }, 16802 { label: 'Pause', baseField: 'pause', type: 1, help: 'Alexa, pause on <device>' }, 16803 { label: 'Stop', baseField: 'stop', type: 1, help: 'Alexa, stop on <device>' }, 16804 { label: 'Fast Forward', baseField: 'fastforward', type: 1, help: 'Alexa, fast forward on <device>' }, 16805 { label: 'Rewind', baseField: 'rewind', type: 1, help: 'Alexa, rewind on <device>' }, 16806 { label: 'Next', baseField: 'next', type: 1, help: 'Alexa, next on <device>' }, 16807 { label: 'Previous', baseField: 'previous', type: 1, help: 'Alexa, previous on <device>' }, 16808 { label: 'Start Over', baseField: 'startover', type: 1, help: 'Alexa, start over on <device>' } 16809 ] 16810 }, { 16811 target: '#echo_av_tab5', 16812 fields: [
16813 { label: 'Input 1', baseField: 'input1', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16814 { label: 'Input 2', baseField: 'input2', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16815 { label: 'Input 3', baseField: 'input3', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16816 { label: 'Input 4', baseField: 'input4', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16817 { label: 'Input 5', baseField: 'input5', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16818 { label: 'Input 6', baseField: 'input6', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16819 { label: 'Input 7', baseField: 'input7', type: 3, help: 'Alexa, change input to <input name> on <device>' }, 16820 { label: 'Input 8', baseField: 'input8', type: 3, help: 'Alexa, change input to <input name> on <device>' } 16821 ] 16822 } 16823 ]; 16824 16825 function displayField(item) { 16826 var $item = $(item); 16827 var option = $item.find('select').val(); 16828 16829 $item.find('.av_opt').hide(); 16830 16831 if (option!=='$disabled') { 16832 $item.find('.av_opt_'+option).show(); 16833 } 16834 } 16835 16836 var fieldBuilder = { 16837 1: function (baseField) { 16838 return '<span><select class="av_entry_selector">' + 16839 '<option value="$disabled">Disabled</option>' + 16840 '<option value="svar">State Variable</option>' + 16841 '<option value="net">Network resource</option>' + 16842 '<option value="pgm">Program</option>' + 16843 '</select></span>' + 16844 '<span class="av_opt av_opt_svar"><div class="combobox-container av_selector_svar"></div><input id="' + baseField + '_svar_value" name="' + baseField + '_svar_value" class="text ui-widget-content ui-corner-all" placeholder="Set to" value=""></span>' + 16845 '<span class="av_opt av_opt_net"><div class="combobox-container av_selector_net"></div></span>'+ 16846 '<span class="av_opt av_opt_pgm"><div class="combobox-container av_selector_pgm"></div></span>'; 16847 }, 16848 16849 2: function (baseField) { 16850 return '<span><select class="av_entry_selector">' + 16851 '<option value="$disabled">Disabled</option>' + 16852 '<option value="svar">State Variable</option>' + 16853 '</select></span>' + 16854 '<span class="av_opt av_opt_svar"><div class="combobox-container av_selector_svar"></div></span>'// + 16855 }, 16856 16857 3: function (baseField) { 16858 return '<span><select class="av_entry_selector">' + 16859 '<option value="$disabled">Disabled</option>' + 16860 '<option value="svar">State Variable</option>' + 16861 '<option value="net">Network resource</option>' + 16862 '<option value="pgm">Program</option>' + 16863 '</select></span>' + 16864 '<span class="av_opt av_opt_svar"><div class="combobox-container av_selector_svar"></div></span>' + // <input id="' + baseField + '_input_name" name="' + baseField + '_input_name" class="text inputSelectName ui-widget-content ui-corner-all" placeholder="Input Name" value=""> 16865 '<span class="av_opt av_opt_net"><div class="combobox-container av_selector_net"></div></span>'+ 16866 '<span class="av_opt av_opt_pgm"><div class="combobox-container av_selector_pgm"></div></span>' + 16867 '<input id="' + baseField + '_input_name" name="' + baseField + '_input_name" class="text inputSelectName av_opt av_opt_svar av_opt_net av_opt_pgm ui-widget-content ui-corner-all" placeholder="Input Name" value="">'; 16868 } 16869 }; 16870 16871 function initUI() { 16872 16873 fieldGroups.forEach(function (fieldGroup) { 16874 var target = fieldGroup.target; 16875// $(target).empty(); 16876 16877 // Add all the fields to their tabs 16878 fieldGroup.fields.forEach(function(field) { 16879 var entry = '<div' + (field.class_label?' class="' + field.class_label + '"':'') + (field.help?' title="' + field.help + '"':'') + '><label>' + field.label + '</label></div>' + 16880 '<div class="av_opt_wrapper" basefield="' + field.baseField + '" fieldtype="' + field.type + '">' + 16881 fieldBuilder[field.type](field.baseField) + 16882 '</div>'; 16883 16884 $(target).append(entry); 16885 }); 16886 16887 // Initialize the selectors 16888 $(target + ' select.av_entry_selector').selectmenu({ 16889 width: 200, 16890 change: function(event, ui) { 16891 var $wrapper = $(ui.item.element).closest('.av_opt_wrapper'); 16892 displayField($wrapper); // When the selector changes, show/hide the relevant fields. 16893 } 16894 }); 16895 16896 // Initialize all of the combobox! 16897 $(target + ' .combobox-container').combobox({inputClass: ''}); 16898 16899 // Display/Hide fields based on the selector (actually hides all of them, bacause the selector is set to $disabled) 16900 $(target + ' .av_opt_wrapper').each(function() { 16901 displayField(this); // When initializing, hide detail fields. (The selector is disabled by default) 16902 }); 16903 16904 // Auto-Capitalize input 16905 $(target + ' .av_opt_wrapper .inputSelectName').focusout(function() { 16906 $(this).val($(this).val().toUpperCase());
16907 $(this).val($(this).val().replace(/^HDMI /, 'HDMI')); 16908 }); 16909 16910 // Adds all input fields (standard inputs, or combobox input field) 16911 allFields = allFields.add(target + ' input'); 16912 }) 16913 } 16914 16915 // Updates the room selector 16916 function updateRoomSelector(rooms, choice) { 16917 // label: shown in the selections 16918 // value: displayed in the combobox input field (this.input) 16919 // data: real data assigned to the input field (this.element) 16920 16921 rooms = rooms.map(function(item) { 16922 return {label: item.name, value: item.name, data: item._id} 16923 }); 16924 16925 rooms.sort(function(a, b) { 16926 return a.label.localeCompare(b.label) 16927 }); 16928 16929 rooms.unshift({ label: '(No room)', value: '', data: '$noroom'}); 16930 16931 room.combobox("option", "source", rooms); 16932 room.combobox("option", "data", choice); 16933 16934 currentRooms = rooms; 16935 } 16936 16937 // Initialize form with new values 16938 function setFields(obj) { 16939 var label; 16940 16941 uuid = obj.uuid; 16942 domain = obj.domain; 16943 pk = obj.pk; 16944 16945 form[0].reset(); 16946 spokens[0].focus(); 16947 16948 editMode = obj.editMode; 16949 16950 // Set the source for all of the combobox 16951// $('#echo_av_dialog .av_selector_node').combobox("option", "source", obj.lookupNodes.data.devices); 16952 $('#echo_av_dialog .av_selector_svar').combobox("option", "source", obj.lookupVars.data); 16953 $('#echo_av_dialog .av_selector_pgm').combobox("option", "source", obj.lookupPgms.data); 16954 $('#echo_av_dialog .av_selector_net').combobox("option", "source", obj.lookupNet.data); 16955 16956 // Load the fields with avData (or empty the fields) 16957 // This is Ok for editing or not. (obj.avParam will be undefined) 16958 $('#echo_av_dialog .av_opt_wrapper').each(function() { 16959 var $this = $(this); 16960 var baseField = $this.attr('basefield'); 16961 var fieldType = parseInt($this.attr('fieldtype')); //$this.find('select.av_entry_selector').val() 16962 16963 var avParam = obj.avData && obj.avData[baseField]; 16964 16965 if (!avParam) { 16966 avParam = {type: "$disabled"}; // If we don't have the configuration parameter, the default is disabled. 16967 } 16968 16969 var $selector = $this.find('select.av_entry_selector'); 16970 var $svarCombo; 16971 var $svarValueField; 16972 var $inputName; 16973 var $pgmCombo; 16974 var $netCombo; 16975 16976 $selector.val(avParam.type); 16977 $selector.selectmenu('refresh'); 16978 displayField($this); 16979 16980 switch (fieldType) { 16981 case 1: 16982 $svarCombo = $this.find('.av_opt_svar .av_selector_svar'); 16983 $svarValueField = $this.find('#'+ baseField + '_svar_value'); 16984 $pgmCombo = $this.find('.av_opt_pgm .av_selector_pgm'); 16985 $netCombo = $this.find('.av_opt_net .av_selector_net'); 16986 16987 switch (avParam.type) { 16988 case 'svar': 16989 $svarCombo.combobox("option", "data", avParam.svar); 16990 $pgmCombo.combobox("option", "data", ''); 16991 $netCombo.combobox("option", "data", ''); 16992 if (!isNaN(avParam.value)) { 16993 $svarValueField.val(avParam.value) 16994 } 16995 else if (!isNaN(avParam.valueDelta)) { 16996 $svarValueField.val(avParam.valueDelta>0?'+'+avParam.valueDelta:avParam.valueDelta) 16997 } 16998 else { 16999 $svarValueField.val('') 17000 } 17001 break; 17002 17003 case 'pgm': 17004 $svarCombo.combobox("option", "data", ''); 17005 $pgmCombo.combobox("option", "data", avParam.pgm); 17006 $netCombo.combobox("option", "data", ''); 17007 $svarValueField.val(''); 17008 break; 17009 17010 case 'net': 17011 $svarCombo.combobox("option", "data", ''); 17012 $pgmCombo.combobox("option", "data", ''); 17013 $netCombo.combobox("option", "data", avParam.net); 17014 $svarValueField.val(''); 17015 break; 17016 17017 case '$disabled': 17018 default: 17019 $svarCombo.combobox("option", "data", ''); 17020 $pgmCombo.combobox("option", "data", ''); 17021 $netCombo.combobox("option", "data", ''); 17022 $svarValueField.val(''); 17023 break; 17024 } 17025 break; 17026 17027 case 2: 17028 $svarCombo = $this.find('.av_opt_svar .av_selector_svar'); 17029 switch (avParam.type) { 17030 case 'svar': 17031 $svarCombo.combobox("option", "data", avParam.svar); 17032 break; 17033 17034 case '$disabled': 17035 default: 17036 $svarCombo.combobox("option", "data", ''); 17037 break; 17038 } 17039 break; 17040 17041 case 3: 17042 $svarCombo = $this.find('.av_opt_svar .av_selector_svar'); 17043 $inputName = $this.find('#'+ baseField + '_input_name'); 17044 $pgmCombo = $this.find('.av_opt_pgm .av_selector_pgm'); 17045 $netCombo = $this.find('.av_opt_net .av_selector_net'); 17046 17047 if (avParam.input_name && avParam.input_name.length) { 17048 $inputName.val(avParam.input_name) 17049 } 17050 else { 17051 $inputName.val('') 17052 } 17053 17054 switch (avParam.type) { 17055 case 'svar': 17056 $svarCombo.combobox("option", "data", avParam.svar); 17057 $pgmCombo.combobox("option", "data", ''); 17058 $netCombo.combobox("option", "data", ''); 17059 break; 17060 17061 case 'pgm': 17062 $svarCombo.combobox("option", "data", ''); 17063 $pgmCombo.combobox("option", "data", avParam.pgm); 17064 $netCombo.combobox("option", "data", ''); 17065 break; 17066 17067 case 'net': 17068 $svarCombo.combobox("option", "data", ''); 17069 $pgmCombo.combobox("option", "data", ''); 17070 $netCombo.combobox("option", "data", avParam.net); 17071 break; 17072 17073 case '$disabled': 17074 default: 17075 $svarCombo.combobox("option", "data", ''); 17076 $pgmCombo.combobox("option", "data", ''); 17077 $netCombo.combobox("option", "data", ''); 17078 break; 17079 } 17080 break; 17081 17082 } 17083 }); 17084 17085 if (editMode) { 17086 tips.text('Please update your A/V mapping.'); 17087 17088 for (var i=0; i<5; i++) { 17089 if (obj.spokens && i<=obj.spokens.length) { 17090 spokens[i].val(obj.spokens[i]); 17091 } 17092 else { 17093 spokens[i].val(''); 17094 } 17095 } 17096 } 17097 else { 17098 tips.text('Please enter your A/V mapping.'); 17099 obj.room = '$noroom'; 17100 } 17101 17102 updateRoomSelector(obj.rooms, obj.room); 17103 allFields.removeClass('fieldfocus'); 17104 } 17105 17106 // Validate and save! 17107 function submit() {
17108 var valid = true; 17109 17110 if (!room.combobox("validate")) { 17111 17112 $("#echo_av_tabs").tabs("option", "active", 0); 17113 var name = room.combobox("getValue"); 17114 17115 confirmationDialog('Room ' + name + ' does not exists.<br/><br/>Create it?', 'Create room?') 17116 .then(function () { 17117 ajaxJsonPut('/api/voice/amazon/spoken/room', $.param({domain: domain, uuid: uuid, name: name })) 17118 .done(function (record) { 17119 var roomsPromise = echoSpokenNodesDialog.loadRooms(uuid); 17120 17121 roomsPromise 17122 .then(function(data) { 17123 updateRoomSelector(data.data, record.data._id); 17124 }); 17125 17126 tableEchoSpokenNodes.setRooms(roomsPromise); 17127 }) 17128 .fail(function(jqXHR, textStatus, errorThrown) { 17129 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 17130 }) 17131 }) 17132 .done(); 17133 17134 valid=false; 17135 } 17136 17137 allFields.removeClass('fieldfocus'); 17138 $("#echo_av_tabs").tabs("option", "active", 0); // We need to be on the first tab, otherwise the field won't have the focus. 17139 17140 valid = valid && checkLength(tips, spokens[0], 'Spoken', 2, 40); 17141 valid = valid && checkRegexp(tips, spokens[0], spokenRegex, 'Spoken must contain only letters with spaces'); 17142 valid = valid && checkLength(tips, spokens[1], 'Alternate spoken 1', 0, 40); 17143 valid = valid && checkRegexp(tips, spokens[1], spokenRegex, 'Spoken must contain only letters with spaces'); 17144 valid = valid && checkLength(tips, spokens[2], 'Alternate spoken 2', 0, 40); 17145 valid = valid && checkRegexp(tips, spokens[2], spokenRegex, 'Spoken must contain only letters with spaces'); 17146 valid = valid && checkLength(tips, spokens[3], 'Alternate spoken 3', 0, 40); 17147 valid = valid && checkRegexp(tips, spokens[3], spokenRegex, 'Spoken must contain only letters with spaces'); 17148 valid = valid && checkLength(tips, spokens[4], 'Alternate spoken 4', 0, 40); 17149 valid = valid && checkRegexp(tips, spokens[4], spokenRegex, 'Spoken must contain only letters with spaces'); 17150 17151 var avParameters = {}; 17152 var $fieldHasError=null; // We track which field has an error. We need to switch to the appropriate tab before putting the focus on the field. 17153 var avOptionsCount=0; 17154 17155 $('#echo_av_dialog .av_opt_wrapper').each(function() { 17156 var $this = $(this); 17157 17158 if (valid) { 17159 var baseField = $this.attr('basefield'); 17160 var type = $this.find('select.av_entry_selector').val(); 17161 17162 var fieldType = parseInt($this.attr('fieldtype')); 17163 var $combo; 17164 17165 switch (fieldType) { 17166 case 1: 17167 switch (type) { 17168 case 'svar': 17169 $combo = $this.find('.av_opt_svar .av_selector_svar'); 17170 17171 if (!$combo.combobox("validate")) { 17172 updateTips(tips, 'State variable is not valid.'); 17173 $fieldHasError = $this.find('.av_opt_svar .custom-combobox input'); 17174 valid=false; 17175 } 17176 17177 var $valueField = $this.find('#'+ $this.attr('basefield') + '_svar_value'); 17178 17179 valid = checkNumberOrDelta(tips, $valueField, 'State variable value'); 17180 17181 if (valid) { 17182 avOptionsCount++; 17183 avParameters[baseField] = { 17184 type: type, 17185 svar: $combo.val(), 17186 svarname: $combo.combobox("getValue") 17187 }; 17188 17189 if (/^[\+\-]/.test($valueField.val())) { 17190 avParameters[baseField].valueDelta = parseInt($valueField.val()) 17191 } 17192 else { 17193 avParameters[baseField].value = parseInt($valueField.val()) 17194 } 17195 } 17196 else { 17197 $fieldHasError = $valueField 17198 } 17199 break; 17200 17201 case 'pgm': 17202 $combo = $this.find('.av_opt_pgm .av_selector_pgm'); 17203 if (!$combo.combobox('validate')) { 17204 updateTips(tips, 'Program is not valid.'); 17205 $fieldHasError = $this.find('.av_opt_pgm .custom-combobox input'); 17206 valid=false; 17207 } 17208 else { 17209 avOptionsCount++; 17210 avParameters[baseField] = { 17211 type: type, 17212 pgm: $combo.val(), 17213 pgmname: $combo.combobox("getValue") 17214 } 17215 } 17216 break; 17217 17218 case 'net': 17219 $combo = $this.find('.av_opt_net .av_selector_net'); 17220 if (!$combo.combobox('validate')) {
17221 updateTips(tips, 'Network resource is not valid.'); 17222 $fieldHasError = $this.find('.av_opt_net .custom-combobox input'); 17223 valid=false; 17224 } 17225 else { 17226 avOptionsCount++; 17227 avParameters[baseField] = { 17228 type: type, 17229 net: $combo.val(), 17230 netname: $combo.combobox("getValue") 17231 } 17232 } 17233 break; 17234 17235 default: 17236 avParameters[baseField] = { 17237 type: '$disabled', 17238 } 17239 } 17240 break; 17241 17242 case 2: 17243 switch (type) { 17244 case 'svar': 17245 var $combo = $this.find('.av_opt_svar .av_selector_svar'); 17246 17247 if (!$combo.combobox("validate")) { 17248 updateTips(tips, 'State variable is not valid.'); 17249 $fieldHasError = $this.find('.av_opt_svar .custom-combobox input'); 17250 valid=false; 17251 } 17252 else { 17253 avOptionsCount++; 17254 avParameters[baseField] = { 17255 type: type, 17256 svar: $combo.val(), 17257 svarname: $combo.combobox("getValue") 17258 } 17259 } 17260 break; 17261 17262 default: 17263 avParameters[baseField] = { 17264 type: '$disabled', 17265 } 17266 } 17267 break; 17268 17269 case 3: 17270 switch (type) { 17271 case 'svar': 17272 var $combo = $this.find('.av_opt_svar .av_selector_svar'); 17273 17274 if (!$combo.combobox("validate")) { 17275 updateTips(tips, 'State variable is not valid.'); 17276 $fieldHasError = $this.find('.av_opt_svar .custom-combobox input'); 17277 valid=false; 17278 } 17279 else { 17280 avOptionsCount++; 17281 avParameters[baseField] = { 17282 type: type, 17283 svar: $combo.val(), 17284 svarname: $combo.combobox("getValue") 17285 } 17286 } 17287 break; 17288 17289 case 'pgm': 17290 var $combo = $this.find('.av_opt_pgm .av_selector_pgm'); 17291 if (!$combo.combobox('validate')) { 17292 updateTips(tips, 'Program is not valid.'); 17293 $fieldHasError = $this.find('.av_opt_pgm .custom-combobox input'); 17294 valid=false; 17295 } 17296 else { 17297 avOptionsCount++; 17298 avParameters[baseField] = { 17299 type: type, 17300 pgm: $combo.val(), 17301 pgmname: $combo.combobox("getValue") 17302 } 17303 } 17304 break; 17305 17306 case 'net': 17307 var $combo = $this.find('.av_opt_net .av_selector_net'); 17308 if (!$combo.combobox('validate')) { 17309 updateTips(tips, 'Network resource is not valid.'); 17310 $fieldHasError = $this.find('.av_opt_net .custom-combobox input'); 17311 valid=false; 17312 } 17313 else { 17314 avOptionsCount++; 17315 avParameters[baseField] = { 17316 type: type, 17317 net: $combo.val(), 17318 netname: $combo.combobox("getValue") 17319 } 17320 } 17321 break; 17322 17323 default: 17324 avParameters[baseField] = { 17325 type: '$disabled', 17326 } 17327 } 17328 17329 if (valid && type!=='$disabled') { 17330 var $inputNameField = $this.find('#'+ $this.attr('basefield') + '_input_name'); 17331 valid = checkLength(tips, $inputNameField, 'Input name', 2, 10); 17332 17333 if (valid) { 17334 avParameters[baseField].input_name = $inputNameField.val(); 17335 } 17336 else { 17337 $fieldHasError = $inputNameField; 17338 } 17339 } 17340 break; 17341 17342 default: 17343 } 17344 } 17345 }); 17346 17347 // We need to have at least one option set 17348 if (valid && !avOptionsCount) { 17349 valid=false; 17350 tips.text('You need to configure at least one of the A/V options.'); 17351 } 17352 17353 if (!valid && $fieldHasError) { 17354 var tabIndex = $fieldHasError.closest(".av_tab").attr('tabidx'); // It is set on purpose in index.htm so we can find the tab index 17355 $("#echo_av_tabs").tabs("option", "active", tabIndex); 17356 17357 $fieldHasError.addClass('fieldfocus').focus(); 17358 } 17359 17360 if (valid) { 17361 var data = $('#echo_av_tab1 input').serialize(); 17362 data += '&uuid=' + uuid + '&domain=' + domain + '&category=av&' + $.param({ avJson: JSON.stringify(avParameters) }); 17363 17364 if (editMode) { 17365 data += '&id=' + pk; 17366 } 17367
17368 (editMode?ajaxJsonPost('/api/voice/amazon/spoken/node', data):ajaxJsonPut('/api/voice/amazon/spoken/node', data)) 17369 .then(function(data, status, jqXHR) { 17370 dialog.dialog('close'); 17371 tableEchoSpokenNodes.reload(); 17372 }) 17373 .fail(function(jqXHR, textStatus, errorThrown) { 17374 if (jqXHR.status==503) { 17375 updateTips(tips, 'ISY is not online'); 17376 } 17377 else if (jqXHR.status==420) { // Dupkey error with Custom server error message 17378 var data = JSON.parse(jqXHR.responseText); 17379 17380 if (data.message) { 17381 updateTips(tips, data.message); 17382 } 17383 else { 17384 updateTips(tips, 'HTTP error ' + jqXHR.status); 17385 } 17386 } 17387 else { 17388 updateTips(tips, 'HTTP error ' + jqXHR.status); 17389 } 17390 }) 17391 .done(); 17392 } 17393 } 17394 17395 function onlineHelp() { 17396 informationMessageDialog( 17397 '<div class="online-help"><p>A/V Virtual Device is a feature available with the upcoming ISY Optimized for Smart Home V3. ' + 17398 'It allows to create virtual Alexa A/V devices that maps to a combination of ISY state variables, network resources and/or programs.<br><br> ' + 17399 'Configuration tips:<br><ul>' + 17400 '<li>Create as many virtual A/V devices as you have physical A/V devices. Example; one for the TV, one for the Home theater, etc.</li>' + 17401 '<li>All fields are optional.</li>' + 17402 '<li>Under Basic, use either Set Channel To, or Channel Up/Channel Down, not both.</li>' + 17403 '<li>Under Volume, you can use Set Volume To, Volume Up/Volume Down, or both. If you use both, use the same State Variable.</li>' + 17404 '<li>When entering a State Variable value, you can specify an absolute value, or a delta by entering + or - before the number. This can be especially useful for the Volume Up/Volume Down.</li>' + 17405 '<li>When you hover on a field label, you get an example of how to use it.</li>' + 17406 '</ul>', 17407 'A/V Virtual Device Online Help', 850) 17408 } 17409 17410 var isyOptions = { 17411 autoOpen: false, 17412 width: 825, 17413 modal: true, 17414 buttons: { 17415 'Help' : function() { 17416 $buttonHelp.blur(); // Remove focus from button 17417 onlineHelp(); 17418 }, 17419 'Save': function() { 17420 submit(); 17421 } 17422 }, 17423 open: function(event, ui) { 17424 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 17425 } 17426 }; 17427 17428 var $buttons = $('#echo_av_dialog').parent().find('.ui-dialog-buttonset button'); 17429 var $buttonHelp = $buttons.first(); // First button 17430 17431 // Initializes the dialog 17432 dialog = $('#echo_av_dialog').dialog(isyOptions); 17433 17434 // Handles keyboad enter (submit event) 17435 form = dialog.find('form').submit(function(event) { 17436 document.activeElement.blur(); // Triggers the current field focus out; allows to capitalize & fix the input_name 17437 event.preventDefault(); 17438 submit(); 17439 }); 17440 17441 $("#echo_av_tabs").tabs(); 17442 17443 initUI(); 17444// Tooltip removed - Causing issues as the scope is too wide 17445// $('[title]').tooltip(); 17446 17447 $('#echo_av_room_add').button({ icons: {primary: 'ui-icon-plus'}, text: false}).tooltip().click(function(event) { 17448 $(this).blur(); // Remove focus from button 17449 echoSpokenRoomEditDialog.setFields({ uuid: uuid, editMode: false, domain: domain }); 17450 echoSpokenRoomEditDialog.modal.dialog('open'); 17451 }); 17452 17453 $('#echo_av_room_edit').button({ icons: {primary: 'ui-icon-pencil'}, text: false}).tooltip().click(function(event) { 17454 $(this).blur(); // Remove focus from button 17455 17456 room.combobox("refresh"); // Makes sure the real input field is up to date 17457 var pk = room.val(); 17458 17459 var item = currentRooms.find(function(item) { 17460 return item.data === pk; 17461 }); 17462 17463 if (pk!='$noroom' && item) { 17464 echoSpokenRoomEditDialog.setFields({ uuid: uuid, editMode: true, domain: domain, pk: pk, name: item.label }); 17465 echoSpokenRoomEditDialog.modal.dialog('open'); 17466 } 17467 else { 17468 informationMessageDialog("This can't be edited.") 17469 } 17470 }); 17471 17472 $('#echo_av_room_delete').button({ icons: {primary: 'ui-icon-trash'}, text: false}).tooltip().click(function(event) { 17473 $(this).blur(); // Remove focus from button 17474 17475 room.combobox("refresh"); // Makes sure the real input field is up to date 17476 var pk = room.val(); 17477 17478 var item = currentRooms.find(function(item) { 17479 return item.data === pk; 17480 }); 17481 17482 if (pk!=='$noroom' && item) { 17483 confirmationDialog('Delete room ' + item.label + '.<br/><br/>Are you sure?', 'Delete room?') 17484 .then(function () {
17485 ajaxJsonDelete('/api/voice/amazon/spoken/room', $.param({id: pk, domain: domain, uuid: uuid })) 17486 .done(function () { 17487 var roomsPromise = echoSpokenNodesDialog.loadRooms(uuid); 17488 17489 roomsPromise 17490 .then(function(data) { 17491 echoSpokenNodesEditDialog.updateRoomSelector(data.data, '$noroom'); 17492 echoSpokenNodesAVEditDialog.updateRoomSelector(data.data, '$noroom'); 17493 }); 17494 17495 tableEchoSpokenNodes.setRooms(roomsPromise); 17496 tableEchoSpokenNodes.reload(); 17497 }) 17498 .fail(function(jqXHR, textStatus, errorThrown) { 17499 errorMessageDialog('HTTP error ' + jqXHR.status, 'Error', 350); 17500 }) 17501 }) 17502 .done(); 17503 } 17504 else { 17505 informationMessageDialog("This can't be removed.") 17506 } 17507 }); 17508 17509 room.combobox({inputClass: ''}); 17510 17511 return { modal: dialog, setFields: setFields, updateRoomSelector: updateRoomSelector }; 17512}; 17513 17514// Download user list 17515var downloadUsersDialogInit = function () { 17516 var dialog, 17517 form, 17518// notificationmaint = $('#myprofile_notificationmaint'), 17519// notificationnews = $('#myprofile_notificationnews'), 17520 allFields = $([]);//.add(isy).add(username).add(notificationmaint).add(notificationnews), 17521 var tips = $('#downloadUsers_dialog .validateTips'); 17522 var selector = '#downloadUsers_accountType_selector'; 17523 17524 function resetForm() { 17525 tips.text('Select the download options'); 17526 allFields.removeClass('fieldfocus'); 17527 form[0].reset(); 17528 $(selector + ' select').selectmenu('refresh'); // Needs a refresh to show 17529 } 17530 17531 function submit() { 17532 var valid = true; 17533 17534 allFields.removeClass('fieldfocus'); 17535 17536 if (valid) { 17537 var data = serializeFormWithCheckboxes(form.find('input')); 17538 data += '&accountType=' + $(selector + ' select').val(); 17539 17540 17541 ajaxJsonPost('/api/download/userlist', data) 17542 .then(function(data, status, jqXHR) { 17543 dialog.dialog('close'); 17544 try { 17545 var isFileSaverSupported = !!new Blob; 17546 } catch (e) {} 17547 17548 if (isFileSaverSupported) { 17549 var BOM = '\uFEFF'; 17550 saveAs(new Blob([BOM + data.users], {type: "application/csv;charset=utf-8"}), 'users.csv'); 17551 } 17552 else { 17553 errorMessageDialog('Feature not supported on this browser', 'Not supported'); 17554 } 17555 }) 17556 .fail(function(jqXHR, textStatus, errorThrown) { 17557 updateTips(tips, 'HTTP error ' + jqXHR.status); 17558 }) 17559 .done(); 17560 } 17561 } 17562 17563 var isyOptions = { 17564 autoOpen: false, 17565 width: 650, 17566// height: 445, 17567 modal: true, 17568 buttons: { 17569 'Download': function() { 17570 submit(); 17571 }, 17572 Cancel: function() { 17573 $(this).dialog('close'); 17574 } 17575 }, 17576 open: function(event, ui) { 17577 resetForm(); 17578 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 17579 $('.custom-combobox-input').focus(); 17580 } 17581 }; 17582 17583 // Initializes the dialog 17584 dialog = $('#downloadUsers_dialog').dialog(isyOptions); 17585 17586 // Handles keyboad enter (submit event) 17587 form = dialog.find('form').submit(function(event) { 17588 event.preventDefault(); 17589 submit(); 17590 }); 17591 17592 var selectOptions = [ 17593 { value: 'all', description: 'Include all accounts' }, 17594 { value: 'root', description: 'Include root account users only' }, 17595 { value: 'sub', description: 'Include sub-account users only' }, 17596 { value: 'account', description: 'Include license notification users specified on account' } 17597 ]; 17598 17599 buildSelect(selectOptions, selector, '35em'); 17600 17601 return { modal: dialog }; 17602}; 17603 17604var downloadUsageDialogInit = function () { 17605 var dialog, form; 17606 17607 function submit() { 17608 var data = serializeFormWithCheckboxes(form.find('input')); 17609 17610 ajaxDownloadGet('/api/download/usage', data) 17611 } 17612 17613 var isyOptions = { 17614 autoOpen: false, 17615 width: 650, 17616 modal: true, 17617 buttons: { 17618 'Download': function() { 17619 submit(); 17620 }, 17621 Cancel: function() { 17622 $(this).dialog('close'); 17623 } 17624 }, 17625 open: function(event, ui) { 17626 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 17627 $('.custom-combobox-input').focus(); 17628 } 17629 }; 17630 17631 // Initializes the dialog 17632 dialog = $('#downloadUsage_dialog').dialog(isyOptions); 17633 17634 // Handles keyboad enter (submit event) 17635 form = dialog.find('form').submit(function(event) { 17636 event.preventDefault(); 17637 submit(); 17638 }); 17639 17640 return { modal: dialog }; 17641};
17642 17643 17644var downloadStatsDialogInit = function () { 17645 var dialog, form; 17646 17647 function submit() { 17648 ajaxDownloadGet('/api/isy/stats/download') 17649 } 17650 17651 var isyOptions = { 17652 autoOpen: false, 17653 width: 450, 17654 modal: true, 17655 buttons: { 17656 'Download': function() { 17657 submit(); 17658 }, 17659 Cancel: function() { 17660 $(this).dialog('close'); 17661 } 17662 }, 17663 open: function(event, ui) { 17664 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 17665 $('.custom-combobox-input').focus(); 17666 } 17667 }; 17668 17669 // Initializes the dialog 17670 dialog = $('#downloadStats_dialog').dialog(isyOptions); 17671 17672 // Handles keyboad enter (submit event) 17673 form = dialog.find('form').submit(function(event) { 17674 event.preventDefault(); 17675 submit(); 17676 }); 17677 17678 return { modal: dialog }; 17679}; 17680 17681 17682 17683 17684// ISY Portal Plugin purchase dialog 17685var nsPurchaseDialogInit = function () { 17686 var paypalButtonTarget = '#nodeserver_purchase_paypal_button', 17687 dialog, 17688 pid, nsid, 17689 redirectUrl, 17690 nodeserverInfo, 17691 allOptions, // All the available purchase options from the API 17692 editionOptions, // Valid edition options (Free, Subscription, Perpetual...) 17693 purchaseOptions, // Valid purchase ptions (based on selected edition and current license) 17694 installed, // Current nodeServer installed. None if first. 17695 renewalStartDate, 17696 requestBeta, 17697 ppCurrentEnv=serverConfig.ppEnv; 17698 17699 function setFields(obj) { 17700 pid = obj.pid; 17701 nsid = obj.nsid; 17702 redirectUrl = obj.redirectUrl; 17703 nodeserverInfo = obj.nodeserverInfo; 17704 requestBeta = obj.requestBeta; 17705 installed = obj.installed; 17706 17707 // ppCurrentEnv = (typeof obj.isTest === 'boolean')? 17708 // (obj.isTest ? 'sandbox' : 'production') 17709 // : serverConfig.ppEnv; // If not specified, will take the paypal env from the server config 17710 17711 $('#nodeserver_purchase_pid').text(obj.pid); 17712 $('#nodeserver_purchase_alias').text(obj.alias); 17713 17714 var licenseExpiryHtml=''; 17715 var licenseActiveHtml=''; 17716 var editionHtml=''; 17717 var versionHtml=''; 17718 17719 // If we don't have a license 17720 if (!obj.installed) { 17721 editionHtml = versionHtml = licenseExpiryHtml = '<span title="Plugin has to be purchased.">[No License]</span>'; 17722 licenseActiveHtml='<span title="Plugin has to be purchased.">No</span>' 17723 } else { 17724 if (obj.installed.expiry===null) { 17725 licenseExpiryHtml = '<span>No expiration</span>' 17726 } else if (obj.installed.expiry) { 17727 var expiryDate = new Date(obj.installed.expiry); 17728 17729 // Set access license expired flag 17730 var expired = expiryDate.valueOf() < new Date().valueOf(); 17731 17732 // Set access license will be expired soon flag 17733 var soon = new Date(); 17734 soon.setDate(soon.getDate() + accessLicenseExpirationSoonDays); 17735 var soonExpired = !expired && expiryDate.valueOf() < soon.valueOf(); 17736 17737 licenseExpiryHtml = '<span>' + dateFormatNoError(expiryDate, "yyyy-mm-dd") +'</span>' + 17738 (expired?'<span class="red expired" title="Plugin license is expired.">expired</span>':'') + 17739 (soonExpired?'<span class="warning expired" title="Plugin License is expiring soon."><span class="ui-icon ui-icon-alert"></span></span>':'') 17740 } 17741 17742 var licenseActive = obj.installed.active && !expired; 17743 17744 licenseActiveHtml = '<span>' + (licenseActive?'Yes':'No') + '</span>' 17745 editionHtml = '<span>' + obj.installed.edition + '</span>' 17746 versionHtml = '<span>' + obj.installed.version + '</span>' 17747 } 17748 17749 $('#nodeserver_name').html(obj.nodeserverInfo.desc + (nodeserverInfo.isBeta ? ' (Beta)' : '')).tooltip(); 17750 $('#nodeserver_purchase_edition').html(editionHtml).tooltip(); 17751 $('#nodeserver_purchase_version').html(versionHtml).tooltip(); 17752 $('#nodeserver_purchase_active').html(licenseActiveHtml).tooltip(); 17753 $('#nodeserver_purchase_expiry').html(licenseExpiryHtml).tooltip(); 17754 17755 renewalStartDate = (installed && installed.expiry && new Date(installed.expiry)) || new Date(); 17756 17757 // Extend allOption. 17758 allOptions = obj.nodeserverInfo.purchaseOptions.map(function (option, index) { 17759 return Object.assign({}, option, { optionID: index }) 17760 }) 17761 .map(function (option) { 17762 if (!option.description) { 17763 // option.recurring is supposed to be an integer. If a string, the end date will be miscalculated.
17764 if (typeof option.recurring === 'string' && !option.isPerpetual) { 17765 option.recurring = parseInt(option.recurring); 17766 } 17767 17768 if ('price' in option && typeof option.price === 'string') { 17769 option.price = parseFloat(option.price); 17770 } 17771 17772 17773 // Adjust the descriptions 17774 if (option.isTrial) { 17775 option.description = 'Trial License (' + option.recurring + ' ' + option.recurringPeriod + (option.recurring>1?'s':'') + ')' 17776 option.descriptionForSku = option.description 17777 } else if (option.isPerpetual) { 17778 if (option.isUpgrade) { 17779 option.description = 'Perpetual License (Version upgrade to ' + option.version + ')' 17780 option.descriptionForSku = 'Perpetual-Upgrade' 17781 } else if (option.isTierUpgrade) { 17782 option.description = 'Perpetual License (Tier upgrade to ' + option.to + ')' 17783 option.descriptionForSku = 'Perpetual-TierUpgrade' 17784 } else { 17785 option.description = 'Perpetual License - Version ' + option.version 17786 option.descriptionForSku = 'Perpetual License' 17787 } 17788 } else if (option.isSubscription) { 17789 // Example 90 days 17790 var recurringStr = option.recurring + ' ' + option.recurringPeriod + (option.recurring>1?'s':'') 17791 17792 if (option.isUpgrade) { 17793 option.description = 'Subscription (' + recurringStr + ' - Version upgrade to ' + option.version + ')' 17794 option.descriptionForSku = 'Subscription-Upg (' + option.recurring + ' ' + option.recurringPeriod + (option.recurring>1?'s':'') + ')' 17795 } else if (option.isTierUpgrade) { 17796 option.description = 'Subscription (' + recurringStr + ' - Tier upgrade to ' + option.to + ')' 17797 option.descriptionForSku = 'Subscription-TierUpg (' + option.recurring + ' ' + option.recurringPeriod + (option.recurring>1?'s':'') + ')' 17798 } else { 17799 option.description = 'Subscription for version ' + option.version + ' (' + recurringStr + ')' 17800 option.descriptionForSku = 'Subscription (' + option.recurring + ' ' + option.recurringPeriod + (option.recurring>1?'s':'') + ')' 17801 } 17802 } else { 17803 option.description = '(Invalid option)' 17804 } 17805 } 17806 17807 if (option.isTrial) { 17808 option.price = 0; 17809 } 17810 17811 option.part = nsid; 17812 17813 // option.descriptionForSku is the description we were previously using before adding support for version & tier upgrades 17814 // We keep using this for the partDescription so it matches previous descriptions in QB Online 17815 option.partDescription = nodeserverInfo.name + ' - ' + option.descriptionForSku; 17816 17817 return option 17818 }) 17819 17820 // This should match all the possible edition options 17821 // We could rely on the content of the options only, but we want to sort in that order 17822 var allEditions = [ 'Free', 'Standard', 'Professional' ] 17823 17824 // If for some reasons we have more edition options, add them 17825 // allOptions.forEach(function (opt) { 17826 // if (!allEditions.includes(opt.edition)) { 17827 // editions.push(opt.edition) 17828 // } 17829 // }) 17830 17831 editionOptions = allEditions 17832 // Keep only if we have an option with the edition 17833 .filter(function(edition) { 17834 return allOptions 17835 .filter(function(option) { 17836 // We control on the server side which options are available 17837 return option.isAvailable 17838 }) 17839 .find(function (p) { 17840 return p.edition === edition 17841 }) 17842 }) 17843 .map(function(edition, index) { 17844 return { 17845 value: index, 17846 description: edition, 17847 edition: edition // We use it to display the correct purchase options for this edition 17848 } 17849 }); 17850 17851 var lenEdition = editionOptions.reduce(function(summary, element) { 17852 return Math.max(summary, element.description.length) 17853 }, 0); 17854 var emEdition = (lenEdition*0.55)+5; // Formula found by trial and error 17855 17856 buildSelect(editionOptions, '#nodeserver_edition_selector', ''+emEdition+'em', function(event, ui) { 17857 displayPurchaseOptions(ui.item.value) 17858 }); 17859 17860 if (editionOptions.length) { 17861 // Always take the first edition to start with 17862 displayPurchaseOptions(editionOptions[0].value) 17863 } else { 17864 errorMessageDialog('There are no purchase options available.') 17865 throw Error('No purchase options available') // Prevent displaying the purchase dialog 17866 } 17867 } 17868 17869 // Called when the edition changes 17870 function displayPurchaseOptions(id) { 17871 var edition = editionOptions[id].edition; // id is the index in the array 17872 17873 purchaseOptions = allOptions 17874 .filter(function(option) { 17875 // We control on the server side which options are available 17876 return option.isAvailable 17877 }) 17878 // Remove trial option if this nodeserver was already used for this pid 17879 .filter(function(opt) { 17880 return opt.edition === edition 17881 }) 17882 .map(function(option) { 17883 return { 17884 value: option.optionID, 17885 description: option.description 17886 } 17887 }) 17888 .sort(function (a, b) { 17889 return a.description.localeCompare(b.description) 17890 }) 17891 17892 var lenPurchase = purchaseOptions.reduce(function(summary, element) { 17893 return Math.max(summary, element.description.length) 17894 }, 0); 17895 17896 var emPurchase = (lenPurchase*0.55)+5; // Formula found by trial and error 17897 17898 buildSelect(purchaseOptions, '#nodeserver_purchase_selector', ''+emPurchase+'em', function(event, ui) { 17899 displayPaymentInfo(ui.item.value) 17900 }); 17901 17902 // Always take the first option 17903 displayPaymentInfo(purchaseOptions[0].value); 17904 } 17905 17906 // Called when the renewal option changes. 17907 function displayPaymentInfo(id) { 17908 var option = allOptions[id]; // id is the index in the array 17909 17910 if (option) { 17911 var newExpiry 17912 var newExpiryStr 17913 var startDateStr 17914 17915 if (option.isPerpetual) { 17916 startDateStr = dateFormatNoError(new Date(), "yyyy-mm-dd"); 17917 newExpiryStr = '[No Expiry]' 17918 } else { 17919 // If trial or subcription 17920 newExpiry = new Date(renewalStartDate); 17921 17922 // If it's an upgrade or tier upgrade, we keep the same expiry date but sell at lower prorated price 17923 if (!option.isTierUpgrade && !option.isUpgrade) { 17924 startDateStr = dateFormatNoError(new Date(renewalStartDate), "yyyy-mm-dd"); 17925 17926 switch (option.recurringPeriod) { 17927 case 'day': 17928 newExpiry.setDate(newExpiry.getDate() + option.recurring); 17929 break; 17930 17931 case 'month': 17932 newExpiry.setMonth(newExpiry.getMonth() + option.recurring); 17933 break; 17934 17935 case 'year': 17936 newExpiry.setFullYear(newExpiry.getFullYear() + option.recurring); 17937 break; 17938 17939 default:
17940 console.log('recurringPeriod is invalid: ' + option.recurringPeriod) 17941 } 17942 } else { 17943 startDateStr = dateFormatNoError(new Date(), "yyyy-mm-dd"); 17944 } 17945 17946 newExpiryStr = dateFormatNoError(newExpiry, "yyyy-mm-dd") 17947 } 17948 17949 var startDate = dateFormatNoError(renewalStartDate, "yyyy-mm-dd"); 17950 $('#nodeserver_purchase_start').html(startDateStr); 17951 $('#nodeserver_purchase_end').html(newExpiryStr); //.tooltip(); 17952 17953 if (option.isSubscription && (option.isTierUpgrade || option.isUpgrade) && option.proratedPrice) { 17954 $('#nodeserver_purchase_price').text(option.proratedPrice.toFixed(2) + '$ (Prorated)'); 17955 } else { 17956 $('#nodeserver_purchase_price').text(option.price.toFixed(2) + '$'); 17957 } 17958 17959 $('#nodeserver_purchase_price_row').toggle(!option.isTrial) 17960 $('#nodeserver_purchase_sandbox_indicator').toggle(!option.isTrial) 17961 17962 if (option.isTrial) { 17963 createTrialButton(option); 17964 } else if (option.isPerpetual && option.price === 0) { 17965 createFreeButton(option); 17966 } else { 17967 createPaypalButton(option); 17968 } 17969 } 17970 else { 17971 $('#nodeserver_purchase_start').empty(); 17972 $('#nodeserver_purchase_end').empty(); 17973 $('#nodeserver_purchase_price').empty(); 17974 } 17975 } 17976 17977 // We pass the selector option 17978 function createPaypalButton(option) { 17979 $('#nodeserver_purchase_sandbox_indicator').text(ppCurrentEnv === 'sandbox' ? '[SANDBOX ENVIRONMENT]' : ''); 17980 17981 $(paypalButtonTarget).empty(); // Each time we change the option, we rebuild the button 17982 17983 paypal.Buttons({ 17984 17985 // NODE SERVER LEGACY PAYPAL WEB UI 17986 // createOrder: function(data, actions) { 17987 // sendLog({ 17988 // tag: 'nsPurchase', 17989 // subTag: 'ppcreate', 17990 // uuid: pid, 17991 // message: 'Paypal Create', 17992 // content: { 17993 // option: option, 17994 // sku: option.partDescription, 17995 // name: option.part 17996 // } 17997 // }); 17998 // 17999 // // Sample option 18000 // // { 18001 // // description: "Perpetual License" 18002 // // index: 1 18003 // // isPerpetual: true 18004 // // part: "0bec5267-b1c0-44e3-aa60-e1f84d1c5291" 18005 // // partDescription: "Airscape - Perpetual License" 18006 // // price: 10 18007 // // } 18008 // 18009 // var orderRequest = { 18010 // intent: 'CAPTURE', 18011 // purchase_units: [{ 18012 // // custom_id: option.part, 18013 // description: option.part, // Looks like this is not used 18014 // amount: { 18015 // currency_code: 'USD', 18016 // value: option.price, 18017 // breakdown: { 18018 // item_total: { 18019 // currency_code: 'USD', 18020 // value: option.price 18021 // } 18022 // } 18023 // }, 18024 // items: [{ 18025 // sku: option.partDescription, // Second line on the receipt: "numéro de l'object <sku>" 18026 // name: option.part + ' - ' + pid, // First line on the receipt 18027 // description: (option.description + ' pid: ' + pid).substring(0,127), // Does not show on the receipt 18028 // unit_amount: { 18029 // currency_code: 'USD', 18030 // value: option.price 18031 // }, 18032 // quantity: 1 18033 // }] 18034 // }], 18035 // application_context: { 18036 // shipping_preference: 'NO_SHIPPING' 18037 // } 18038 // } 18039 // 18040 // // console.log('order', orderRequest) 18041 // 18042 // return actions.order.create(orderRequest) 18043 // }, 18044 // 18045 // // Finalize the transaction after payer approval 18046 // onApprove: function(data, actions) { 18047 // return actions.order.capture().then(function(orderData) { 18048 // // Successful capture! 18049 // // console.log('Capture result', orderData, JSON.stringify(orderData, null, 2)); 18050 // var orderID = orderData.id 18051 // // var payerID = orderData.payer.payer_id; 18052 // var transaction = orderData.purchase_units[0].payments.captures[0]; 18053 // var paymentID = transaction.id; // Need this in case of error 18054 // 18055 // if (registeredUser) { 18056 // sendLog({ 18057 // tag: 'nsPurchase', 18058 // subTag: 'ppapprove', 18059 // uuid: pid, 18060 // message: 'Paypal Approve', 18061 // content: { 18062 // data: orderData, 18063 // } 18064 // }); 18065 //
18066 // ajaxJsonPost('/api/nodeserver/payment/execute', $.param({ 18067 // pid: pid, 18068 // nsid: nsid, 18069 // optionID: option.optionID, // We use it to verify the transaction amount has not been tampered with 18070 // orderID: orderID, 18071 // paymentID: paymentID, 18072 // beta: nodeserverInfo.isBeta, 18073 // env: ppCurrentEnv 18074 // })) 18075 // .then(function(data, status, jqXHR) { 18076 // var message = 'Node Server transaction has been completed successfully.<br/><br/>Press Ok to continue.' 18077 // 18078 // informationMessageDialog(message) 18079 // .always(function() { 18080 // // dialog.dialog('close'); 18081 // redirectSuccess(); 18082 // }) 18083 // }) 18084 // .fail(function(jqXHR, textStatus, errorThrown) { 18085 // sendLog({ 18086 // tag: 'nsPurchase', 18087 // subTag: 'ppapproveFailure', 18088 // uuid: pid, 18089 // message: 'Paypal Approve Failure', 18090 // content: { 18091 // textStatus: textStatus, 18092 // status: jqXHR && jqXHR.status, 18093 // responseText: jqXHR && jqXHR.responseText, 18094 // error: errorThrown 18095 // } 18096 // }); 18097 // 18098 // var message = 'Paypal transaction completed, but we could not update your Node Server purchase.<br><br>' + 18099 // (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':'') + 18100 // 'Please contact [email protected] and provide your transaction ID:<br>' + paymentID; 18101 // 18102 // errorMessageDialog(message, 'Error', 800) 18103 // // .always(function() { 18104 // // redirectError('Authorization failure'); 18105 // // }) 18106 // }) 18107 // .done(); 18108 // } 18109 // else { 18110 // errorMessageDialog('You are logged out of ISY Portal.<br>Transaction is cancelled.') 18111 // .always(function() { 18112 // redirectError('Transaction is cancelled'); 18113 // }) 18114 // } 18115 // }); 18116 // } 18117 18118 createOrder: function(data) { 18119 var optionID = option.optionID 18120 var edition = option.edition 18121 console.log('Creating order for edition ' + edition + '. Payment source selected: ' + data.paymentSource) 18122 18123 // New server side createOrder 18124 return ajaxJsonPost('/api/nodeserver/order/create', $.param({ 18125 uuid: pid, 18126 nsid: nsid, 18127 edition: edition, 18128 optionID: optionID, 18129 // This is to facilitate the receipt only 18130 // See setfields for details (allOptions map) 18131 partDescription: option.partDescription, // ns name + sku short desc 18132 part: option.part, // nsid 18133 description: option.description, // Longer description 18134 18135 // optionID: optionID, 18136 paymentSource: data.paymentSource, 18137 beta: nodeserverInfo.isBeta, 18138 env: ppCurrentEnv 18139 })) 18140 .then(function(data, status, jqXHR) { 18141 return data.id 18142 }) 18143 .fail(function(jqXHR, textStatus, errorThrown) { 18144 console.log('Order creation failed: ' + jqXHR.responseText) 18145 }) 18146 }, 18147 18148 // New server side order capture 18149 onApprove: function(data) { 18150 var orderID = data.orderID 18151 var paymentID = data.paymentID 18152 // var payerID = data.payerID 18153 var paymentSource = data.paymentSource 18154 18155 if (registeredUser) { 18156 return ajaxJsonPost('/api/nodeserver/order/capture', $.param({ 18157 uuid: pid, 18158 paymentSource: paymentSource, 18159 env: ppCurrentEnv, 18160 orderID: orderID, 18161 // payerID, 18162 paymentID: paymentID, 18163 paymentSource: paymentSource 18164 })) 18165 .then(function(data, status, jqXHR) { 18166 var message = 'Transaction has been completed successfully.<br/><br/>Press Ok to continue.' 18167 18168 informationMessageDialog(message) 18169 .always(function() { 18170 redirectSuccess(); 18171 }) 18172 }) 18173 .fail(function(jqXHR, textStatus, errorThrown) { 18174 var message = 'Transaction failed.<br><br>' + 18175 (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':'') 18176 18177 errorMessageDialog(message, 'Error', 800) 18178 // .always(function() { 18179 // redirectError('Authorization failure'); 18180 // }) 18181 }) 18182 } else { 18183 errorMessageDialog('You are logged out.<br>Transaction is cancelled.') 18184 .always(function() { 18185 redirectError('Transaction is cancelled'); 18186 }) 18187 } 18188 }, 18189 18190 onError: function(err) { 18191 console.log('Order error', err) 18192 18193 sendLog({ 18194 tag: 'nsPurchase', 18195 subTag: 'orderError', 18196 uuid: pid, 18197 message: 'Order error: ' + (err && err.message), 18198 content: {} 18199 }); 18200 }, 18201 18202 onCancel: function(data) { 18203 console.log('Order cancelled') 18204 18205 sendLog({ 18206 tag: 'nsPurchase', 18207 subTag: 'orderCancelled', 18208 uuid: pid, 18209 message: 'Order cancelled: ' + data.orderID, 18210 content: { 18211 orderID: data.orderID 18212 } 18213 }); 18214 } 18215 }).render(paypalButtonTarget); 18216 } 18217 18218 function redirectSuccess() { 18219 window.location.replace(redirectUrl + '?successful=true'); 18220 } 18221 18222 function redirectError(errMessage) {
18223 var redirect = redirectUrl + '?successful=false' + (errMessage && '&error=' + errMessage) 18224 window.location.replace(redirect); 18225 } 18226 18227 function createTrialButton(option) { 18228 var $input = $('<input type="button" value="Start trial now" />'); 18229 $input.appendTo($(paypalButtonTarget).empty()); 18230 $(paypalButtonTarget + ' input').button().click(function() { 18231 ajaxJsonPost('/api/nodeserver/trial', $.param({ 18232 pid: pid, 18233 nsid: nsid, 18234 // domain: isyDomain, 18235 beta: nodeserverInfo.isBeta, 18236 optionID: option.optionID // We use it to verify the transaction amount has not been tampered with 18237 })) 18238 .then(function(data, status, jqXHR) { 18239 informationMessageDialog('Plugin trial activated successfully.<br/><br/>Press Ok to continue.') 18240 .always(function() { 18241 dialog.dialog('close'); 18242 redirectSuccess(); 18243 }) 18244 }) 18245 .fail(function(jqXHR, textStatus, errorThrown) { 18246 var message = 'Trial activation failed.<br><br>' + 18247 (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':'') + 18248 'Please contact [email protected] for support.'; 18249 18250 errorMessageDialog(message, 'Error', 800) 18251 // .always(function () { 18252 // redirectError('Error activating trial.'); 18253 // }) 18254 }) 18255 .done(); 18256 }); 18257 } 18258 18259 function createFreeButton(option) { 18260 var $input = $('<input type="button" value="Activate free license" />'); 18261 $input.appendTo($(paypalButtonTarget).empty()); 18262 $(paypalButtonTarget + ' input').button().click(function() { 18263 ajaxJsonPost('/api/nodeserver/free', $.param({ 18264 pid: pid, 18265 nsid: nsid, 18266 // domain: isyDomain, 18267 beta: nodeserverInfo.isBeta, 18268 optionID: option.optionID // We use it to verify the transaction amount has not been tampered with 18269 })) 18270 .then(function(data, status, jqXHR) { 18271 informationMessageDialog('Plugin free license activated successfully.<br/><br/>Press Ok to continue.') 18272 .always(function() { 18273 dialog.dialog('close'); 18274 redirectSuccess(); 18275 }) 18276 }) 18277 .fail(function(jqXHR, textStatus, errorThrown) { 18278 var message = 'Free license activation failed.<br><br>' + 18279 (jqXHR.status==425?'Error: ' + jqXHR.responseText + '<br><br>':'') + 18280 'Please contact [email protected] for support.'; 18281 18282 errorMessageDialog(message, 'Error', 800) 18283 // .always(function () { 18284 // redirectError('Error activating trial.'); 18285 // }) 18286 }) 18287 .done(); 18288 }); 18289 } 18290 18291 // Keypressed, active only when this dialog is open. 18292 // Switch between prod and sandbox 18293 // function keyEvent(e) { 18294 // // Ctrl-z 18295 // if (e.which === 90 && e.ctrlKey && !e.shiftKey){ 18296 // var refreshPaypalButton=false; 18297 // 18298 // if (ppCurrentEnv === 'sandbox') { 18299 // if (serverConfig.ppClientIdProduction) { 18300 // ppCurrentEnv='production'; 18301 // refreshPaypalButton=true; 18302 // } 18303 // else { 18304 // errorMessageDialog('Production environment is not configured'); 18305 // } 18306 // } 18307 // else if (ppCurrentEnv === 'production') { 18308 // if (serverConfig.ppClientIdSandbox) { 18309 // ppCurrentEnv='sandbox'; 18310 // refreshPaypalButton=true; 18311 // } 18312 // else { 18313 // errorMessageDialog('Sandbox environment is not configured'); 18314 // } 18315 // } 18316 // 18317 // if (refreshPaypalButton) { 18318 // displayPaymentOptions($('#nodeserver_purchase_selector select').val()) 18319 // } 18320 // } 18321 // } 18322 18323 var isyOptions = { 18324 autoOpen: false, 18325 width: 850, 18326 modal: true, 18327 buttons: { 18328 'Cancel': function() { 18329 $(this).dialog('close'); 18330 } 18331 }, 18332 open: function(event, ui) { 18333 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 18334 // $(document).on('keydown.pp', keyEvent); 18335 }, 18336 close: function( event, ui ) { 18337 // $(document).off('keydown.pp'); 18338 redirectError('Cancelled'); 18339 } 18340 }; 18341 18342 // Initializes the dialog 18343 dialog = $('#nodeserver_purchase_dialog').dialog(isyOptions); 18344 18345 return { modal: dialog, setFields: setFields }; 18346}; 18347 18348var udCommsEditDialogInit = function () { 18349 var dialog, 18350 form, 18351 editMode, 18352 typeTarget = '#ud-comms-type', 18353 appLinkTargetId = '#ud-comms-content-appLink-target', 18354 // commsTabForm = '#tabs-rateplan-rates-form',
18355 code = $('#ud-comms-code'), 18356 message = $('#ud-comms-message'), 18357 type = $(typeTarget), 18358 filterLicense = $('#ud-comms-filterLicense'), 18359 filterLicenseExpiryDays = $('#ud-comms-filterLicenseExpiryDays'), 18360 filterModel = $('#ud-comms-filterModel'), 18361 filterVersion = $('#ud-comms-filterVersion'), 18362 filterOnline = $('#ud-comms-filterOnline'), 18363 filterOnlineHours = $('#ud-comms-filterOnlineHours'), 18364 separateUuid = $('#ud-comms-separateUuid'), 18365 accountWithIsy = $('#ud-comms-accountWithIsy'), 18366 contentPush = $('#ud-comms-content-push'), 18367 surveyHeader = $('#ud-comms-content-survey-header'), 18368 surveyContainer = $('#ud-comms-content-survey-items'), 18369 contentAppLinkTextHeader = $('#ud-comms-content-appLink-text-header'), 18370 contentAppLinkTextFooter = $('#ud-comms-content-appLink-text-footer'), 18371 contentAppLinkTargetText = $('#ud-comms-content-appLink-target-text'), 18372 contentAppLinkTarget = $(appLinkTargetId), 18373 allFields = $([]).add(code).add(message).add(type) 18374 .add(filterLicense).add(filterModel).add(filterVersion).add(filterLicenseExpiryDays) 18375 .add(contentPush).add(surveyHeader), 18376 tableRefreshFunction = null, 18377 tips = $('#comms_edit_dialog .validateTips'), 18378 sentCount, 18379 lastSentTime, 18380 saveUrl = '/api/ud/communication', 18381 getCountUrl = '/api/ud/communication/count', 18382 sendUrl = '/api/ud/communication/send', 18383 testUrl = '/api/ud/communication/test' 18384 18385 // Used for the selectmenu 18386 var typesOptions = [ 18387 { value: 'push', description: 'Push', tabs: ['push'] }, 18388 { value: 'survey', description: 'Push Survey', tabs: ['survey'] }, 18389 { value: 'appLink', description: 'Push with App Link', tabs: ['appLink'] }, 18390 ]; 18391 18392 var appLinkOptions = [ 18393 { value: 'home', description: 'Home' }, 18394 { value: 'notificationSettings', description: 'Notification Settings' }, 18395 { value: 'settings', description: 'Settings' }, 18396 { value: 'account', description: 'Account' }, 18397 { value: 'license', description: 'License' } 18398 ]; 18399 18400 var typeTabIds = { 18401 push: '#tab-ud-comms-push', 18402 survey: '#tab-ud-comms-survey', 18403 appLink: '#tab-ud-comms-appLink' 18404 } 18405 18406 function setFields(obj) { 18407 loadTinymce() 18408 18409 form[0].reset(); 18410 editMode = obj && obj.editMode; 18411 18412 code.prop('readonly', obj && obj.editMode===true) 18413 18414 clearSurveyItems(); 18415 18416 sentCount = obj.sentCount; 18417 lastSentTime = obj.lastSentTime; 18418 18419 if (editMode) { 18420 code.val(obj.code); 18421 message.val(obj.message);
18422 contentPush.val(obj.contentPush); 18423 filterLicenseExpiryDays.val(obj.filterLicenseExpiryDays); 18424 filterOnlineHours.val(obj.filterOnlineHours); 18425 filterModel.val(obj.filterModel); 18426 filterVersion.val(obj.filterVersion); 18427 18428 $(typeTarget + ' select').val(obj.type); 18429 $(typeTarget + ' select').selectmenu('refresh'); 18430 18431 displayExpiredDaysField(obj.filterLicense) 18432 displayOnlineHoursField(obj.filterOnline) 18433 displayIsyFilters(obj.accountWithIsy) 18434 18435 $('#ud-comms-filterLicense input[name=filterLicense]').val([obj.filterLicense]) 18436 $('#ud-comms-filterOnline input[name=filterOnline]').val([obj.filterOnline]) 18437 18438 separateUuid.prop('checked', obj.separateUuid); 18439 accountWithIsy.prop('checked', obj.accountWithIsy); 18440 18441 try { 18442 var survey = obj.contentSurvey && JSON.parse(obj.contentSurvey) 18443 if (survey) { 18444 surveyHeader.val(survey.header) 18445 18446 survey.questions.forEach(function (element) { 18447 addSurveyItem(element) 18448 }) 18449 } else { 18450 surveyHeader.val('') 18451 } 18452 } catch(err) { 18453 console.log('Error parsing survey data', err) 18454 } 18455 18456 try { 18457 var appLink = obj.contentAppLink && JSON.parse(obj.contentAppLink) 18458 if (appLink) { 18459 contentAppLinkTextHeader.val(appLink.contentHeader) 18460 contentAppLinkTextFooter.val(appLink.contentFooter) 18461 contentAppLinkTargetText.val(appLink.appTargetText) 18462 $(appLinkTargetId + ' select').val(appLink.appTarget); 18463 $(appLinkTargetId + ' select').selectmenu('refresh'); 18464 } else { 18465 contentAppLinkTextHeader.val('') 18466 contentAppLinkTextFooter.val('') 18467 contentAppLinkTargetText.val('') 18468 $(appLinkTargetId + ' select').val(appLinkOptions[0].value); 18469 $(appLinkTargetId + ' select').selectmenu('refresh'); 18470 } 18471 } catch(err) { 18472 console.log('Error parsing appLink data', err) 18473 } 18474 18475 tips.text(''); 18476 } else { 18477 $(typeTarget + ' select').val(typesOptions[0].value); 18478 $(typeTarget + ' select').selectmenu('refresh'); 18479 18480 contentAppLinkTextHeader.val('') 18481 contentAppLinkTextFooter.val('') 18482 contentAppLinkTargetText.val('') 18483 $(appLinkTargetId + ' select').val(appLinkOptions[0].value); 18484 $(appLinkTargetId + ' select').selectmenu('refresh'); 18485 18486 $('#ee-comms-filterLicense input[name=filterLicense]').val(['']) 18487 $('#ee-comms-filterModel input[name=filterModel]').val(['']) 18488 $('#ee-comms-filterVersion input[name=filterVersion]').val(['']) 18489 18490 displayExpiredDaysField('') 18491 displayOnlineHoursField('') 18492 displayIsyFilters(true) 18493 18494 // When adding, always start with the first tab 18495 $('#ee-comms-tabs').tabs('option', 'active', 0); 18496 18497 tips.text('Please enter the communication details'); 18498 } 18499 18500 refreshTabs($(typeTarget + ' select').val()) 18501 18502 allFields.removeClass('fieldfocus'); 18503 } 18504 18505 function validatePrepareSaveData() { 18506 var valid = true; 18507 var surveyElements = [] 18508 var $field 18509 18510 allFields.removeClass('fieldfocus'); 18511 18512 valid = valid && checkLength(tips, code, 'Code', 1, 50); // 0 length = error message! 18513 valid = valid && checkLength(tips, message, 'Message', 1, 178); // 0 length = error message! 18514 18515 if (valid) { 18516 surveyContainer.find('li.surveyElement').each(function () { 18517 var element = {} 18518 18519 $field = $(this).find('[data-id="question"]') 18520 element.question = $field.val(); 18521 valid = valid && checkLength(tips, $field, 'Question', 1, 120); 18522 18523 element.type = $(this).find('[data-id="type"]').find(':selected').val(); 18524 18525 switch (element.type) { 18526 case 'radio': 18527 case 'checkbox': 18528 $field = $(this).find('[data-id="options"]') 18529 element.options = $field.val().split(/[;]/).map(function(opt) { return opt.trim() }); // Splits using , or ; 18530 valid = valid && checkRegexp(tips, $field, /[;]/, 'Options must have semicolons as a separator.' ); 18531 break; 18532 18533 case 'rating': 18534 element.min = $(this).find('[data-id="min"]').val(); 18535 element.max = $(this).find('[data-id="max"]').val(); 18536 } 18537 18538 surveyElements.push(element) 18539 }); 18540 } 18541 18542 if (valid) { 18543 var contentSurvey = { 18544 header: surveyHeader.val(), 18545 questions: surveyElements 18546 } 18547 var contentAppLink = { 18548 contentHeader: contentAppLinkTextHeader.val(), 18549 contentFooter: contentAppLinkTextFooter.val(), 18550 appTarget: $(appLinkTargetId + ' select').val(), 18551 appTargetText: contentAppLinkTargetText.val(), 18552 } 18553 return serializeFormWithCheckboxes(form.find('input, textarea')) + 18554 '&type=' + $(typeTarget + ' select').val() + 18555 '&contentSurvey=' + encodeURIComponent(JSON.stringify(contentSurvey)) + 18556 '&contentAppLink=' + encodeURIComponent(JSON.stringify(contentAppLink)) 18557 } else { 18558 return null 18559 } 18560 } 18561 18562 // Used for save and send 18563 function saveFailure (jqXHR, textStatus, errorThrown) { 18564 updateTips(tips, 'HTTP error ' + jqXHR.status); 18565 } 18566 18567 // Save as draft 18568 function submit() { 18569 var data = validatePrepareSaveData() 18570 18571 if (data) { 18572 (editMode?ajaxJsonPost(saveUrl, data):ajaxJsonPut(saveUrl, data)) 18573 .then(function(result, status, jqXHR) { 18574 tableUdComms.reload() 18575 if (!result.successful) { 18576 errorMessageDialog(result.errorMessage, 'Error') 18577 } else { 18578 dialog.dialog('close') 18579 } 18580 }) 18581 .fail(saveFailure) 18582 .done(); 18583 } 18584 } 18585 18586 // Hide/Display the days field, based on filterLicense 18587 function displayExpiredDaysField(filterLicense) { 18588 $('#ud-comms-filterLicenseExpiryDaysUI').toggle(['expiryIn', 'expiredSince'].includes(filterLicense)) 18589 } 18590 18591 function displayOnlineHoursField(filterOnline) { 18592 // Disable the online hours field if filterOnline is set to no filter 18593 $('#ud-comms-filterOnlineHoursUI').toggle(typeof filterOnline === 'string' && filterOnline!=='') 18594 } 18595 18596 function displayIsyFilters(accountWithIsy) { 18597 // Disable the online hours field if filterOnline is set to no filter 18598 $('#ud-comms-isyFilters').toggle(accountWithIsy) 18599 } 18600 function send() { 18601 var data = validatePrepareSaveData() 18602 18603 if (data) { 18604 // First save the record 18605 (editMode?ajaxJsonPost(saveUrl, data):ajaxJsonPut(saveUrl, data)) 18606 .then(function(result, status, jqXHR) { 18607 tableUdComms.reload() 18608 18609 if (result.successful) { 18610 if (sentCount) { 18611 confirmationDialog('WARNING: This communication was already sent on ' + dtStr(new Date(lastSentTime)) + ' to ' + sentCount + ' accounts.<br/><br/>Do you want to continue?', 'Send this communication?', 600) 18612 .then(function () { 18613 sendContinue() 18614 }) 18615 .done() 18616 } else { 18617 sendContinue() 18618 } 18619 } else { 18620 errorMessageDialog(result.errorMessage, 'Error') 18621 // Leave the dialog open. 18622 } 18623 }) 18624 .fail(saveFailure) 18625 .done(); 18626 } 18627 } 18628 18629 function sendContinue() { 18630 ajaxJsonGet(getCountUrl, $.param({ code: code.val() })) 18631 .then(function(result, status, jqXHR) { 18632 if (result.successful) { 18633 var accountCount = result.data.accountCount 18634 18635 if (accountCount>1) { 18636 confirmationDialog('This communication will be sent to ' + accountCount + (accountCount>1?' accounts':' account') + 18637 '.<br/><br/>Do you want to continue?', 'Send this communication?', 600) 18638 .then(function () { 18639 // If user confirms he wants to send, go ahead 18640 // TODO: When testing, no notifications are sent except to test devTestAccounts
18641 ajaxJsonPost(sendUrl, $.param({ code: code.val(), test: false })) 18642 .then(function(result, status, jqXHR) { 18643 if (result.successful) { 18644 var successMessage = 'Communication successfully sent to ' + result.data.accountCount + ' accounts.' 18645 18646 informationMessageDialog(successMessage, 'Success', 600) 18647 .then(function () { 18648 tableUdComms.reload() // Refresh the last Sent date 18649 dialog.dialog('close') 18650 }) 18651 } else { 18652 errorMessageDialog(result.errorMessage, 'Error') 18653 // Leave the dialog open. 18654 } 18655 }) 18656 .fail(saveFailure) 18657 .done(); 18658 }) 18659 .done(); 18660 } else { 18661 errorMessageDialog('There are no accounts matching your criterias.', 'Error') 18662 // Leave the dialog open. 18663 } 18664 } else { 18665 errorMessageDialog(result.errorMessage, 'Error') 18666 // Leave the dialog open. 18667 } 18668 }) 18669 .fail(saveFailure) 18670 .done(); 18671 } 18672 18673 // Sends a notification to UDI admins only 18674 function test() { 18675 var data = validatePrepareSaveData() 18676 18677 if (data) { 18678 // First save the record 18679 (editMode?ajaxJsonPost(saveUrl, data):ajaxJsonPut(saveUrl, data)) 18680 .then(function(result, status, jqXHR) { 18681 tableUdComms.reload() 18682 18683 if (result.successful) { 18684 ajaxJsonPost(testUrl, $.param({ code: code.val() })) 18685 .then(function(result, status, jqXHR) { 18686 if (result.successful) { 18687 var successMessage = 'Communication successfully sent to ' + result.data.accountCount + ' account.' 18688 18689 informationMessageDialog(successMessage, 'Success', 600) 18690 .then(function () { 18691 tableUdComms.reload() // Refresh the last Sent date 18692 dialog.dialog('close') 18693 }) 18694 } else { 18695 errorMessageDialog(result.errorMessage, 'Error') 18696 // Leave the dialog open. 18697 } 18698 }) 18699 .fail(saveFailure) 18700 .done(); 18701 } else { 18702 errorMessageDialog(result.errorMessage, 'Error') 18703 // Leave the dialog open. 18704 } 18705 }) 18706 .fail(saveFailure) 18707 .done(); 18708 } 18709 } 18710 18711 function simulation() { 18712 var data = validatePrepareSaveData() 18713 18714 if (data) { 18715 // First save the record 18716 (editMode?ajaxJsonPost(saveUrl, data):ajaxJsonPut(saveUrl, data)) 18717 .then(function(result, status, jqXHR) { 18718 tableUdComms.reload() 18719 18720 if (result.successful) { 18721 confirmationDialog('This will download the list of accounts matching your filter criterias.<br/><br/>Do you want to continue?', 'Download account list?', 600) 18722 .then(function () { 18723 ajaxJsonPost(sendUrl, $.param({ code: code.val(), simulation: 'true' })) 18724 .then(function (result, status, jqXHR) { 18725 if (result.successful) { 18726 try { 18727 var isFileSaverSupported = !!new Blob; 18728 } catch (e) {} 18729 18730 if (isFileSaverSupported) { 18731 var str = 'accountId,Account Short Name, Customer Contact, Notification Count, uuids\r\n'; 18732 18733 result.data.list.forEach(function(f) { 18734 str += `${f.id},"${f.shortname}","${f.contactname}",${f.deviceCount},${f.uuids && f.uuids.join(',') || ''}\r\n` 18735 }); 18736 18737 var BOM = '\uFEFF'; 18738 var blob = new Blob([BOM + str], {type: "application/csv;charset=utf-8"}); 18739 saveAs(blob, "AccountList.csv"); 18740 } 18741 else { 18742 errorMessageDialog('Feature not supported on this browser', 'Not supported'); 18743 } 18744 } else { 18745 errorMessageDialog(result.errorMessage, 'Error') 18746 // Leave the dialog open. 18747 } 18748 }) 18749 .fail(saveFailure) 18750 .done(); 18751 }) 18752 .done(); 18753 } else { 18754 errorMessageDialog(result.errorMessage, 'Error') 18755 } 18756 }) 18757 .fail(saveFailure) 18758 .done(); 18759 } 18760 } 18761 18762 function help () { 18763 var message = ['Message ID: Enter a unique ID of your choice. You can use letters and numbers.', 18764 'Message: This is the content of the push notification, alexa message and/or text message.', 18765 'Channel: Choose how you want the notification to be delivered:', 18766 ].map(function (s) { return '<p class="littleMargin5">' + s + '</p>'} ).join('') + 18767 '<ul class="noMargin">' + 18768 [ 'Push: Send a push notification to accounts who have UD Communications enabled', 18769 'Push Survey: Sends a push notification asking for a survey', 18770 'Push with App link: Sends a push notification with a link inside the app', 18771 ].map(function (s) { return '<li>' + s + '</li>'} ).join('') + '</ul>' + 18772 [ 'Filter License: You can send only to accounts who have an ISY with a license, or not.', 18773 'Filter Online: You can send only to accounts with ISYs online or not. Enter hours to include ISYs recently online.', 18774 'Filter Model: You can send only to accounts with a specific model. Separate them with comma. You can use ? and *.', 18775 'Include only accounts with ISYs: Deselect to include all portal accounts.', 18776 'Send separate notifications for each uuid: Deselect to prevent multiple notifications to the same users.', 18777 'Push content: This is what will be shown in the App when the user clicks on the push notification.', 18778 'Push Survey: Contains the survey header, along with a series of questions. If using a Radio Button or checkbox, please enter the options separated with a semicolon.', 18779 ' ', 18780 'Send button: This will tell you the number of accounts applicable, and ask you for confirmation before sending.', 18781 'Simulation button: Allows you to download the list of accounts applicable.', 18782 'Save as draft: Save the communication for later use.', 18783 ].map(function (s) { return '<p class="littleMargin5">' + s + '</p>'} ).join('') 18784 18785 informationMessageDialog(message, 'Help', 1100) 18786 } 18787 18788 function refreshTabs(selected) { 18789 // List of tabs to enable/disable based on chosen type 18790 var typeTabsToEnable = typesOptions.find(function (typeOption) { 18791 return typeOption.value === selected 18792 }).tabs 18793 18794 // For all possible tabs
18795 Object.keys(typeTabIds).forEach(function (key) { 18796 $(typeTabIds[key]).toggle(typeTabsToEnable.includes(key)); 18797 }) 18798 18799 // When switching type, show the main tab 18800 $('#ud-comms-tabs').tabs('option', 'active', 0); 18801 } 18802 18803 function clearSurveyItems() { 18804 surveyContainer.empty(); 18805 } 18806 18807 function addSurveyItem(itemOptions) { 18808 var $content = $(` 18809 <li class="ui-state-default surveyElement"> 18810 <div> 18811 <div> 18812 <div class="floatLeft label label1"><label>Question:</label></div> 18813 <div class="floatRight deleteButton"><div><span class="ui-icon ui-icon-closethick"></span></div></div> 18814 <div class="remainingSpace"><input class="width100" type="text" data-id="question" value="${itemOptions && itemOptions.question || ''}"></div> 18815 </div> 18816 <div> 18817 <div class="floatLeft label label1"><label>Type:</label></div> 18818 <select class="floatLeft" data-id="type"> 18819 <option value="textbox" ${itemOptions && itemOptions.type==='textbox' && 'selected'}>Textbox</option> 18820 <option value="radio" ${itemOptions && itemOptions.type==='radio' && 'selected'}>Radio Button</option> 18821 <option value="checkbox" ${itemOptions && itemOptions.type==='checkbox' && 'selected'}>Checkbox</option> 18822 <option value="rating" ${itemOptions && itemOptions.type==='rating' && 'selected'}>Rating</option> 18823 </select> 18824 <span class="typeDependent type-radio type-checkbox" style="display:none;"> 18825 <div class="floatLeft label"><label>Options:</label></div> 18826 <div class="remainingSpace"><input class="width100" data-id="options" type="text" value="${itemOptions && itemOptions.options && itemOptions.options.join(';') || ''}"></div> 18827 </span> 18828 <span class="typeDependent type-rating" style="display:none;"> 18829 <div class="floatLeft label"><label>From:</label></div> 18830 <div class="floatLeft"><input type="number" min="0" max="1" step="1" data-id="min" value="${itemOptions && itemOptions.min || '1'}"></div> 18831 <div class="floatLeft label"><label>To:</label></div> 18832 <div class="floatLeft"><input type="number" min="1" max="10" step="1" data-id="max" value="${itemOptions && itemOptions.max || '5'}"></div> 18833 </span> 18834 </div> 18835 </div> 18836 </li>`); 18837 18838 $content.find('.deleteButton').click(function (event) { 18839 event.preventDefault(); // Prevent submitting the form 18840 18841 confirmationDialog('Do you want to remove this question?', 'Delete?') 18842 .then(function () { 18843 $(event.target).closest('li.surveyElement').remove() 18844 }) 18845 .done(); 18846 }) 18847 18848 $content.find('select').change(function (event) { 18849 var option = this.value; 18850 var selector = '.type-' + option 18851 $(event.target).closest('.surveyElement').find('.typeDependent').not(selector).hide(); 18852 $(event.target).closest('.surveyElement').find(selector).show(); 18853 }); 18854 18855 // Trigger a change event so that appropriate options are displayed 18856 $content.find('select').change() 18857 18858 surveyContainer.append($content); 18859 } 18860 18861 var dialogOptions = { 18862 autoOpen: false, 18863 width: 1000, 18864 modal: true, 18865 buttons: { 18866 'Help': help, 18867 'Send': send, 18868 'Test': test, 18869 'Simulation': simulation, 18870 'Save as draft': submit, 18871 }, 18872 open: function(event, ui) { 18873 dialog.dialog( "option", "position", { my: "center", at: "center+10", of: window }); 18874 } 18875 }; 18876 18877 // Initializes the dialog 18878 dialog = $('#ud-comms-edit-dialog').dialog(dialogOptions); 18879 18880 buildSelect(typesOptions, typeTarget, '20em', function(event, ui) { 18881 refreshTabs(ui.item.value) 18882 }); 18883 18884 buildSelect(appLinkOptions, appLinkTargetId, '20em'); 18885 18886 $('#ud-comms-tabs').tabs() 18887 18888 // // Push notifications editor 18889 // $('textarea#ud-comms-content-push, textarea#ud-comms-content-appLink-text-header, textarea#ud-comms-content-appLink-text-footer, textarea#ipal_notification_option_pushContent').tinymce({ 18890 // selector: 'textarea#ud-comms-content-push', 18891 // plugins: 'lists link', 18892 // toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright al
18892ignjustify | outdent indent | link', 18893 // // convert_newlines_to_brs : true, 18894 // // forced_root_block : false 18895 // // toolbar: 'bullist numlist', 18896 // // inline: true 18897 // // plugins: 'a11ychecker advcode casechange export formatpainter image editimage linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tableofcontents tinycomments tinymcespellchecker', 18898 // // toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter image editimage pageembed permanentpen table tableofcontents', 18899 // // toolbar_mode: 'floating', 18900 // // tinycomments_mode: 'embedded', 18901 // // tinycomments_author: 'Author name', 18902 // }); 18903 // 18904 // // Editor for emails 18905 // $('textarea#ipal_notification_option_emailBody').tinymce({ 18906 // selector: 'textarea#ud-comms-content-push', 18907 // plugins: 'lists', 18908 // toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright alignjustify | outdent indent | link', 18909 // plugins: 'link', 18910 // // br_in_pre: false 18911 // convert_newlines_to_brs : true, 18912 // // forced_root_block : false 18913 // // toolbar: 'bullist numlist', 18914 // // inline: true 18915 // // plugins: 'a11ychecker advcode casechange export formatpainter image editimage linkchecker autolink lists checklist media mediaembed pageembed permanentpen powerpaste table advtable tableofcontents tinycomments tinymcespellchecker', 18916 // // toolbar: 'a11ycheck addcomment showcomments casechange checklist code export formatpainter image editimage pageembed permanentpen table tableofcontents', 18917 // // toolbar_mode: 'floating', 18918 // // tinycomments_mode: 'embedded', 18919 // // tinycomments_author: 'Author name', 18920 // }); 18921 18922 18923 // $('textarea#ud-comms-content-survey-header').tinymce({ 18924 // plugins: 'lists', 18925 // toolbar: 'undo redo | styles | bold italic | bullist numlist | alignleft aligncenter alignright al
18925ignjustify | outdent indent', 18926 // height : '200' 18927 // }); 18928 18929 $('#ud-comms-content-survey-items').sortable(); 18930 18931 // Add a question 18932 $('#ud-comms-content-survey-items-add').button().click(function (event) { 18933 event.preventDefault(); // Prevent submitting the form 18934 addSurveyItem() 18935 }); 18936 18937 // Remove a question 18938 // $('#comms-content-survey-items .deleteButton').click(deleteHandler); 18939 18940 $('#ud-comms-filterLicense input[type=radio]').change(function(){ 18941 displayExpiredDaysField($(this).val()) 18942 }) 18943 18944 $('#ud-comms-filterOnline input[type=radio]').change(function(){ 18945 displayOnlineHoursField($(this).val()) 18946 }) 18947 18948 accountWithIsy.change(function(){ 18949 displayIsyFilters($(this).is(':checked')) 18950 }) 18951 18952 // Handles keyboad enter (submit event) 18953 form = dialog.find('form').submit(function(event) { 18954 event.preventDefault(); 18955 submit(); 18956 }); 18957 18958 return { modal: dialog, setFields: setFields }; 18959}; 18960 18961var udCommsSurveyDialogInit = function () { 18962 var tabsId = '#ud-comms-surveys-tabs' 18963 var tabsContentId = '#ud-comms-surveys-tab' 18964 var dialog 18965 18966 function setFields(obj) { 18967 createTabs(tabsId, tabsContentId, [ 'Notifications' ]) 18968 createTabCanvas(tabsContentId, 0, 'Survey notifications summary') 18969 18970 ajaxJsonGet('/api/ud/survey/stats', $.param({ code: obj.code, stats: true, data:false })) 18971 .done(function(apiData, status, jqXHR){ 18972 var questions = apiData.data.stats.filter(function (q) { return q.type !== 'textbox'}) 18973 addTabs(tabsId, tabsContentId, questions.map(function (q, index) { return 'Question#' + (index+1)})) 18974 18975 // Notifications tab 18976 createChart(tabsContentId, 0, { 18977 type: 'pie', 18978 data: { 18979 labels: [ 18980 'Not acknowledged', 18981 'Acknowledged but not responded', 18982 'Survey received' 18983 ], 18984 datasets: [{ 18985 data: [apiData.data.stage.total - apiData.data.stage.acknowledged, 18986 apiData.data.stage.acknowledged - apiData.data.stage.responded, 18987 apiData.data.stage.responded], 18988 backgroundColor: [ 18989 'rgb(187,19,43, 0.8)', // Red - Not acknowledged 18990 'rgb(236,233,57, 0.8)', // Yellow - Acknowledged but not responded 18991 'rgb(90,211,80, 0.8)' // Green - Survey received 18992 ], 18993 hoverOffset: 4 18994 }] 18995 }, 18996 }) 18997 18998 for (var i=0; i < questions.length; i++) { 18999 createTabCanvas(tabsContentId, i+1, questions[i].question) 19000 19001 // Question tabs 19002 createChart(tabsContentId, i+1, { 19003 type: 'bar', 19004 data: { 19005 labels: Object.keys(questions[i].answers), 19006 datasets: [{ 19007 data: Object.values(questions[i].answers), 19008 backgroundColor: [ 19009 'rgba(255, 99, 132, 0.4)', 19010 'rgba(255, 159, 64, 0.4)', 19011 'rgba(255, 205, 86, 0.4)', 19012 'rgba(75, 192, 192, 0.4)', 19013 'rgba(54, 162, 235, 0.4)', 19014 'rgba(153, 102, 255, 0.4)', 19015 'rgba(201, 203, 207, 0.4)', 19016 'rgba(255, 99, 132, 0.4)', 19017 'rgba(255, 159, 64, 0.4)', 19018 'rgba(255, 205, 86, 0.4)', 19019 'rgba(75, 192, 192, 0.4)', 19020 'rgba(54, 162, 235, 0.4)', 19021 'rgba(153, 102, 255, 0.4)', 19022 'rgba(201, 203, 207, 0.4)', 19023 'rgba(255, 99, 132, 0.4)', 19024 'rgba(255, 159, 64, 0.4)', 19025 'rgba(255, 205, 86, 0.4)', 19026 'rgba(75, 192, 192, 0.4)', 19027 'rgba(54, 162, 235, 0.4)', 19028 'rgba(153, 102, 255, 0.4)', 19029 'rgba(201, 203, 207, 0.4)' 19030 ], 19031 borderColor: [ 19032 'rgb(255, 99, 132)', 19033 'rgb(255, 159, 64)', 19034 'rgb(255, 205, 86)', 19035 'rgb(75, 192, 192)', 19036 'rgb(54, 162, 235)', 19037 'rgb(153, 102, 255)', 19038 'rgb(201, 203, 207)', 19039 'rgb(255, 99, 132)', 19040 'rgb(255, 159, 64)', 19041 'rgb(255, 205, 86)', 19042 'rgb(75, 192, 192)', 19043 'rgb(54, 162, 235)', 19044 'rgb(153, 102, 255)', 19045 'rgb(201, 203, 207)', 19046 'rgb(255, 99, 132)', 19047 'rgb(255, 159, 64)', 19048 'rgb(255, 205, 86)', 19049 'rgb(75, 192, 192)', 19050 'rgb(54, 162, 235)', 19051 'rgb(153, 102, 255)', 19052 'rgb(201, 203, 207)' 19053 ], 19054 borderWidth: 1, 19055 hoverOffset: 4 19056 }] 19057 }, 19058 options: { 19059 plugins: { 19060 legend: { 19061 display: false 19062 } 19063 }, 19064 scales: { 19065 // yAxes: [{ 19066 // ticks: { 19067 // // beginAtZero: true, 19068 // callback: function(value) {if (value % 1 === 0) {return value;}} 19069 // } 19070 // }] 19071 y: { 19072 beginAtZero: true, 19073 ticks: { 19074 // For a category axis, the val is the index so the lookup via getLabelForValue is needed 19075 // callback: function(val, index) { 19076 // // Hide every 2nd tick label 19077 // return index % 2 === 0 ? this.getLabelForValue(val)
19077: ''; 19078 // }, 19079 callback: function(value) {if (value % 1 === 0) {return value;}} 19080 // color: 'red', 19081 } 19082 } 19083 } 19084 }, 19085 }) 19086 } 19087 }); 19088 } 19089 19090 var dialogOptions = { 19091 autoOpen: false, 19092 width: 700, 19093 height: 850, 19094 modal: true, 19095 buttons: { 19096 }, 19097 // Done after drawing the canvas 19098 open: function(event, ui) { 19099 dialog.dialog( 'option', 'position', { my: 'center', at: 'center', of: window, collision: 'fit' }); 19100 }, 19101 close: function(event, ui) { 19102 $(tabsId).tabs('destroy') 19103 } 19104 }; 19105 19106 // Initializes the dialog 19107 dialog = $('#ud-comms-survey-dialog').dialog(dialogOptions); 19108 19109 return { modal: dialog, setFields: setFields }; 19110}; 19111 19112 19113var udCommsDeliveryDialogInit = function () { 19114 var tabsId = '#ud-comms-delivery-tabs' 19115 var tabsContentId = '#ud-comms-delivery-tab' 19116 var dialog 19117 19118 function setFields(obj) { 19119 createTabs(tabsId, tabsContentId, [ 'Delivery' ]) 19120 createTabCanvas(tabsContentId, 0, 'Notifications delivery summary') 19121 19122 ajaxJsonGet('/api/ud/communication/delivery/stats', $.param({ code: obj.code, stats: true, data:false })) 19123 .done(function(apiData, status, jqXHR){ 19124 19125 var push = apiData.data.push 19126 var account = apiData.data.account 19127 19128 function labelFormatter(value, ctx) { 19129 if (!value) { return null } 19130 19131 var sum = datasets.reduce(function (sum, dataset) { return sum+dataset.data[ctx.dataIndex] }, 0) 19132 return Math.round(value / sum * 100) + '%' 19133 } 19134 19135 var datasets = [ 19136 { 19137 label: 'Not delivered', 19138 data: [ 19139 push.total - push.sent, 19140 account.total - account.sent 19141 ], 19142 backgroundColor: 'rgb(194,64,91)', 19143 datalabels: { 19144 color: '#ffffff', 19145 formatter: labelFormatter 19146 } 19147 }, 19148 { 19149 label: 'Sent', 19150 data: [ 19151 push.sent - push.ack, 19152 account.sent - account.ack 19153 ], 19154 backgroundColor: 'rgb(236,233,57)', 19155 datalabels: { 19156 color: '#ffffff', 19157 formatter: labelFormatter 19158 } 19159 }, 19160 { 19161 label: 'Sent & Ack', 19162 data: [
19163 push.ack - push.archived, 19164 account.ack - account.archived 19165 ], 19166 backgroundColor: 'rgb(79,180,71)', 19167 datalabels: { 19168 color: '#020202', 19169 formatter: labelFormatter 19170 } 19171 }, 19172 { 19173 label: 'Sent, Ack & Archived', 19174 data: [ 19175 push.archived, 19176 account.archived 19177 ], 19178 backgroundColor: 'rgb(2,2,2)', 19179 datalabels: { 19180 color: '#ffffff', 19181 formatter: labelFormatter 19182 } 19183 }, 19184 ] 19185 19186 // Notifications tab 19187 createChart(tabsContentId, 0, { 19188 type: 'bar', 19189 responsive: true, 19190 plugins: [ChartDataLabels], 19191 options: { 19192 scales: { 19193 x: { 19194 stacked: true, 19195 }, 19196 y: { 19197 stacked: true 19198 } 19199 } 19200 }, 19201 data: { 19202 labels: [ 19203 'Individual push notifications', 19204 'Account level notifications' 19205 ], 19206 datasets: datasets 19207 }, 19208 }) 19209 }); 19210 } 19211 19212 var dialogOptions = { 19213 autoOpen: false, 19214 width: 700, 19215 height: 850, 19216 modal: true, 19217 buttons: { 19218 }, 19219 // Done after drawing the canvas 19220 open: function(event, ui) { 19221 dialog.dialog( 'option', 'position', { my: 'center', at: 'center', of: window, collision: 'fit' }); 19222 }, 19223 close: function(event, ui) { 19224 $(tabsId).tabs('destroy') 19225 } 19226 }; 19227 19228 // Initializes the dialog 19229 dialog = $('#ud-comms-delivery-dialog').dialog(dialogOptions); 19230 19231 return { modal: dialog, setFields: setFields }; 19232}; 19233 19234 19235// eisy Provisioning Dialog - AWS IoT connectivity 19236 19237// iotProvisioned: isydata.isy.iot.provisioned, 19238// iotProvisioningAvailable: isydata.isy.iot.provisioningAvailable, 19239// iotOnline: isydata.iotOnline, 19240// iotOnlineLastModified: isydata.iotOnlineLastModified 19241 19242// PG3 remote connection 19243var eisyProvisioningDialogInit = function () { 19244 var dialog, uuid, domain 19245 19246 var provisionButton = { 19247 text: 'Configure', 19248 click: provision 19249 } 19250 19251 var reprovisionButton = { 19252 text: 'Reconfigure', 19253 click: provision 19254 } 19255 19256 var deprovisionButton = { 19257 text: 'Remove configuration', 19258 click: deprovision 19259 } 19260 19261 var closeButton = { 19262 text: 'Close', 19263 click: function() { 19264 $(this).dialog('close'); 19265 } 19266 } 19267 19268 var refreshButton = { 19269 text: 'Refresh', 19270 click: refresh 19271 } 19272 19273 var helpButton = { 19274 text: 'Help', 19275 click: help 19276 } 19277 19278 function help() { 19279 informationMessageDialog( 19280 '<div class="online-help"><p>PG3 remote connection establish a permanent MQTT connection with UDI. ' + 19281 'This is particularly useful if you want to use UD Mobile remotely, and for nodeservers using webhooks
19281such as Ring.</p>' + 19282 'Connection status:<ul>' + 19283 '<li><b>Remote connection feature available:</b> Tells you if your device supports remote connections.</li>' + 19284 '<li><b>Remote connection configured:</b> Tells you if your device is configured to connect to UDI.</li>' + 19285 '<li><b>Remote connection active:</b> Tells you if your device is actually connected to UDI.</li>' + 19286 '</ul>', 19287 19288 'PG3 remote connection help', 820) 19289 } 19290 19291 19292 var buttonsProvisioned = [ reprovisionButton, deprovisionButton, refreshButton, helpButton, closeButton ] 19293 var buttonsNotProvisioned = [ provisionButton, refreshButton, helpButton, closeButton ] 19294 var buttonsProvisioningNotAvailable = [ refreshButton, helpButton, closeButton ] // Should not be displayed as the option is hidden 19295 19296 function setButtons(obj) { 19297 dialog.dialog('option', 'buttons', obj.iotProvisioningAvailable ? 19298 (obj.iotProvisioned ? buttonsProvisioned : buttonsNotProvisioned) : 19299 buttonsProvisioningNotAvailable); 19300 } 19301 19302 19303 function displayStatus(obj) { 19304 if (obj) { 19305 $('#eisy_provisioning_available').text(obj.iotProvisioningAvailable ? 'Yes' : 'No' ).hide().show('fade') 19306 $('#eisy_provisioning_provisioned').text(obj.iotProvisioned ? 'Yes' : 'No' ).hide().show('fade') 19307 $('#eisy_provisioning_online').text(obj.iotOnline ? 'Yes' : 'No' ).hide().show('fade') 19308 } 19309 } 19310 19311 function setFields(obj) { 19312 uuid=obj.uuid 19313 domain=obj.domain 19314 19315 $('#eisy_provisioning_uuid').text(obj.uuid); 19316 $('#eisy_provisioning_alias').text(obj.alias); 19317 displayStatus(obj) 19318 setButtons(obj) 19319 refresh() 19320 } 19321 19322 function refresh() { 19323 ajaxJsonGet('/api/eisy/iot/provisioning', $.param({ uuid: uuid, domain: domain })) 19324 .then(function(data, status, jqXHR) { 19325 displayStatus(data.data) 19326 setButtons(data.data) // It may change based on status! 19327 }) 19328 .done() 19329 } 19330 19331 function provision() { 19332 ajaxJsonPost('/api/eisy/iot/provision', $.param({ uuid: uuid, domain: domain })) 19333 .then(function(data, status, jqXHR) { 19334 var message, title 19335 if (data.successful) { 19336 message = 'Request to configure was successful.<br/><br/>Please wait a few minutes while we reconfigure your system.' 19337 title = 'Successful' 19338 } else { 19339 message = 'Configuration failed: ' + data.errorMessage 19340 title = 'Configuration failed' 19341 } 19342 19343 informationMessageDialog(message, title) 19344 .then(function () { 19345 refresh() 19346 }) 19347 .done() 19348 }) 19349 .done(); 19350 } 19351 19352 function deprovision() { 19353 ajaxJsonDelete('/api/eisy/iot/provision', $.param({ uuid: uuid, domain: domain })) 19354 .then(function(data, status, jqXHR) { 19355 var message, title 19356 if (data.successful) { 19357 message = 'Removal of configuration was successful.<br/><br/>Please wait a few minutes while we reconfigure your system.' 19358 title = 'Successful' 19359 } else { 19360 message = 'Removal of configuration failed: ' + data.errorMessage 19361 title = 'Configuration failed' 19362 } 19363 19364 informationMessageDialog(message, title) 19365 .then(function () { 19366 refresh() 19367 }) 19368 .done() 19369 }) 19370 .done(); 19371 } 19372 19373 var isyOptions = { 19374 autoOpen: false, 19375 width: 700, 19376 modal: true, 19377 // Buttons are set during setFields 19378 open: function(event, ui) { 19379 dialog.dialog( "option", "position", { my: "center", at: "center", of: window, collision: "fit" }); 19380 } 19381 }; 19382 19383 // Initializes the dialog 19384 dialog = $('#eisy_provisioning_dialog').dialog(isyOptions); 19385 19386 return { modal: dialog, setFields: setFields }; 19387}; 19388 19389// ISY CSV Import 19390var isyImportDialogInit = function () { 19391 var dialog, 19392 uploader, 19393 domain, 19394 $uploadButton; 19395 19396 function resetUI() { 19397 $('#isy-import-upload_filename').text(' '); 19398 $uploadButton.hide(); 19399 } 19400 19401 function setFields(obj) { 19402 domain = obj.domain; 19403 uploader.setData({ domain }); 19404 uploader.setOptions({ customHeaders: { Authorization: 'Basic ' + registeredUser.basicAuth }}); 19405 } 19406 19407 function help() { 19408 informationMessageDialog( 19409 '<div class="online-help"><p>The ISY import allows you to import a list of uuids in the account using a CSV file.<br/><br/> ' + 19410 'You can click on "Choose a file" to select a file, or drag a file to the input field. Once the file is selected, click on Import CSV.<br/><br/>' + 19411 'The CSV must have a header with the following columns: uuid, alias', 19412 'ISY Import Help', 820) 19413 } 19414 19415 var isyOptions = { 19416 autoOpen: false, 19417 width: 450, 19418 modal: true, 19419 buttons: { 19420 'Choose a file': function () {}, 19421 'Import CSV': function () { 19422 $uploadButton.blur(); 19423 uploader.submit(); 19424 }, 19425 'Help': function() { 19426 help(); 19427 }, 19428 }, 19429 open: function(event, ui) { 19430 resetUI(); 19431 dialog.dialog('option', 'position', { my: 'center', at: 'center', of: window, collision: 'fit'}); 19432 } 19433 }; 19434 19435 // Initializes the dialog
19436 dialog = $('#isy-import-dialog').dialog(isyOptions); 19437 19438 // Set id on first button to allow uploader to hook to it 19439 var $buttons = $('#isy-import-dialog').parent().find('.ui-dialog-buttonset button'); 19440 var buttonId = 'isyimport_upload' 19441 $buttons.first().attr('id', buttonId); 19442 19443 $uploadButton = $buttons.eq(1); // Second button 19444 19445 uploader = new ss.SimpleUpload({ 19446 button: buttonId, // file upload button 19447 url: '/api/isy/import', // server side handler 19448 name: 'file', // upload parameter name 19449 autoSubmit: false, 19450 maxSize: 80000, 19451 multipart: true, 19452// responseType: 'json', 19453 dragClass: 'ui-state-hover', 19454 hoverClass: 'ui-state-hover', 19455 focusClass: 'ui-state-focus', 19456 disabledClass: 'ui-state-disabled', 19457 allowedExtensions: ['csv'], 19458 dropzone: 'isy-import-upload_filename', 19459 onChange: function(filename, extension) { 19460 $('#isy-import-upload_filename').text(filename); 19461 $uploadButton.show(); 19462 }, 19463 onExtError: function(filename, extension) { 19464 errorMessageDialog('File must be a CSV with extension .csv', 'Error') 19465 }, 19466 onSizeError: function(filename, size) { 19467 errorMessageDialog('File is too large', 'Error') 19468 }, 19469 onError: function(filename, errorType, status, statusText, response, uploadBtn) { 19470 var message 19471 19472 if (status === 426) { 19473 message = 'Error: ' + JSON.parse(response).message 19474 } else { 19475 message = 'Error: ' + response 19476 } 19477 19478 errorMessageDialog(message, 'Error') 19479 .then(function () { 19480 resetUI(); 19481 }) 19482 }, 19483 onComplete: function(filename, response) { 19484 const { total, imported, licensed } = JSON.parse(response) 19485 19486 informationMessageDialog(`CSV processed successfully.<br/><br/>` + 19487 `${imported}/${total} ISYs were imported in the account.<br/><br/>` + 19488 `${licensed}/${total} ISYs were automatically licensed.`, 19489 'CSV processed', 600) 19490 .then(function () { 19491 tableIsys.reload(); 19492 dialog.dialog('close'); 19493 }) 19494 } 19495 }); 19496 19497 return { modal: dialog, setFields: setFields }; 19498}; 19499 19500
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.