% if notifier: <% import json from plexpy import notifiers, users from plexpy.helpers import checked available_notification_actions = notifiers.available_notification_actions(agent_id=notifier['agent_id']) user_emails = [{'user': u['friendly_name'] or u['username'], 'email': u['email']} for u in users.Users().get_users() if u['email']] sorted(user_emails, key=lambda u: u['user']) %> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button> <h4 class="modal-title" id="notifier-config-modal-header">${notifier['agent_label']} Settings <small><span class="notifier_id">(Notifier ID: ${notifier['id']}${' - ' + notifier['friendly_name'] if notifier['friendly_name'] else ''})</span></small></h4> </div> <div class="modal-body"> <div class="container-fluid"> <div class="row"> <ul class="nav nav-tabs list-unstyled" role="tablist"> <li role="presentation" class="active"><a href="#tabs-notifier_config" aria-controls="tabs-notifier_config" role="tab" data-toggle="tab">Configuration</a></li> <li role="presentation"><a href="#tabs-notify_triggers" aria-controls="tabs-notify_triggers" role="tab" data-toggle="tab">Triggers</a></li> <li role="presentation"><a href="#tabs-notify_conditions" aria-controls="tabs-notify_conditions" role="tab" data-toggle="tab">Conditions</a></li> % if notifier['agent_name'] == 'scripts': <li role="presentation"><a href="#tabs-notify_text" aria-controls="tabs-notify_text" role="tab" data-toggle="tab">Arguments</a></li> % elif notifier['agent_name'] == 'webhook': <li role="presentation"><a href="#tabs-notify_text" aria-controls="tabs-notify_text" role="tab" data-toggle="tab">Data</a></li> % elif notifier['agent_name'] != 'plexmobileapp': <li role="presentation"><a href="#tabs-notify_text" aria-controls="tabs-notify_text" role="tab" data-toggle="tab">Text</a></li> % endif <li role="presentation"><a href="#tabs-test_notifications" aria-controls="tabs-test_notifications" role="tab" data-toggle="tab">Test Notifications</a></li> </ul> </div> <form action="set_notifier_config" method="post" class="form" id="set_notifier_config" data-parsley-validate> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="tabs-notifier_config"> <div class="row"> <div class="col-md-12"> <input type="hidden" id="notifier_id" name="notifier_id" value="${notifier['id']}" /> <input type="hidden" id="agent_id" name="agent_id" value="${notifier['agent_id']}" /> % for item in notifier['config_options']: % if item['input_type'] == 'help': <div class="form-group"> <label>${item['label']}</label> <p class="help-block">${item['description'] | n}</p> </div> % elif item['input_type'] == 'text' or item['input_type'] == 'password': <div class="form-group"> <label for="${item['name']}">${item['label']}</label> <div class="row"> <div class="col-md-12"> % if notifier['agent_name'] == 'scripts' and item['name'] == 'scripts_script_folder': <div class="input-group"> <input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" autocomplete="off" ${'readonly' if item.get('readonly') else ''}> <span class="input-group-btn"> <button class="btn btn-form" type="button" id="${item['name']}_browse" data-toggle="browse" data-filter=".folderonly" data-target="#${item['name']}">Browse</button> </span> </div> % else: <input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" autocomplete="off" ${'readonly' if item.get('readonly') else ''}> % endif </div> </div> <p class="help-block">${item['description'] | n}</p> </div> % elif item['input_type'] == 'token': <div class="form-group"> <label for="${item['name']}">${item['label']}</label> <div class="row"> <div class="col-md-12"> <div class="input-group"> <span class="input-group-btn"> <button class="btn btn-form reveal-token" type="button"><i class="fa fa-eye-slash"></i></button> </span> <input type="password" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" autocomplete="off" ${'readonly' if item.get('readonly') else ''}> </div> </div> </div> <p class="help-block">${item['description'] | n}</p> </div> % elif item['input_type'] == 'number': <div class="form-group"> <label for="${item['name']}">${item['label']}</label> <div class="row"> <div class="col-md-3"> <input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" autocomplete="off"> </div> </div> <p class="help-block">${item['description'] | n}</p> </div> % elif item['input_type'] == 'button': <div class="form-group"> <label for="${item['name']}">${item['label']}</label> <div class="row"> <div class="col-md-12"> <input type="button" class="btn btn-bright" id="${item['name']}" name="${item['name']}" value="${item['value']}"> </div> </div> <p class="help-block">${item['description'] | n}</p> </div> % elif item['input_type'] == 'checkbox': <div class="checkbox"> <label> <input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" autocomplete="off" ${checked(item['value'])}> ${item['label']} </label> <p class="help-block">${item['description'] | n}</p> <input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}"> </div> % elif item['input_type'] == 'select': <div class="form-group"> <label for="${item['name']}">${item['label']}</label> <div class="row"> <div class="col-md-12"> <select class="form-control" id="${item['name']}" name="${item['name']}" autocomplete="off"> % for key, value in sorted(item['select_options'].items()): % if isinstance(value, list): <optgroup label="${key}"> % for option in sorted(value, key=lambda x: x['text'].lower()): <option value="${option['value']}" ${'selected' if option['value'] == item['value'] else ''}>${option['text']}</option> % endfor </optgroup> % else: <option value="${key}" ${'selected' if key == item['value'] else ''}>${value}</option> % endif % endfor </select> </div> </div> <p class="help-block">${item['description'] | n}</p> </div> % elif item['input_type'] == 'selectize': <div class="form-group"> <label for="${item['name']}">${item['label']}</label> <div class="row"> <div class="col-md-12"> <select class="form-control" id="${item['name']}" name="${item['name']}" autocomplete="off"> % if item['select_all']: <option value="select-all">Select All</option> <option value="remove-all">Remove All</option> % endif % if isinstance(item['select_options'], dict): % for section, options in item['select_options'].items(): <optgroup label="${section}"> % for option in sorted(options, key=lambda x: x['text'].lower()): <option value="${option['value']}">${option['text']}</option> % endfor </optgroup> % endfor % else: % if item['select_all']: <option value="border-all"></option> % endif % for option in sorted(item['select_options'], key=lambda x: x['text'].lower()): <option value="${option['value']}">${option['text']}</option> % endfor % endif </select> </div> </div> <p class="help-block">${item['description'] | n}</p> </div> % endif % endfor </div> <div class="col-md-12 modal-config-section"> <div class="form-group"> <label for="friendly_name">Description</label> <div class="row"> <div class="col-md-12"> <input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${notifier['friendly_name']}" size="30" autocomplete="off"> </div> </div> <p class="help-block">Optional: Enter a description to help identify this agent in the notification agents list.</p> </div> </div> </div> </div> <div role="tabpanel" class="tab-pane" id="tabs-notify_triggers"> <div class="row"> <div class="col-md-12"> <label>Notification Triggers</label> <p class="help-block"> Select items that will trigger a notification for this ${notifier['agent_label']} notification agent. </p> % for action in available_notification_actions: <div class="checkbox"> <label> <input type="checkbox" data-id="${action['name']}" class="checkboxes" value="1" autocomplete="off" ${checked(notifier['actions'][action['name']])}> ${action['label']} </label> <p class="help-block">${action['description'] | n}</p> <input type="hidden" id="${action['name']}" name="${action['name']}" value="${notifier['actions'][action['name']]}"> </div> % endfor </div> </div> </div> <div role="tabpanel" class="tab-pane" id="tabs-notify_conditions"> <label>Notification Conditions</label> <p class="help-block"> Add custom conditions to only <em>allow certain notifications</em>. By default, all notifications will be sent if there are no conditions. <a href="#notify-text-sub-modal" data-toggle="modal">Click here</a> for a description of all the parameters. </p> <p class="help-block"> A tilde (<span class="inline-pre">~</span>) can be entered as a value to indicate an empty string. </p> <div id="condition-widget"></div> <input type="hidden" id="custom_conditions" name="custom_conditions" /> <div class="form-group"> <label for="custom_conditions_logic">Condition Logic</label> <input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" autocomplete="off" /> <div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div> <p class="help-block"> Optional: Enter custom logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>). Leave blank for implicit <span class="inline-pre">and</span> between all conditions. </p> <p class="help-block"> Note: Only the keywords <span class="inline-pre">and</span>/<span class="inline-pre">or</span> and brackets <span class="inline-pre">()</span> are supported. For order of operations, <span class="inline-pre">and</span> is evaluated before <span class="inline-pre">or</span>. </p> </div> </div> <div role="tabpanel" class="tab-pane" id="tabs-notify_text"> <label>Notification Text</label> <p class="help-block"> % if notifier['agent_name'] == 'scripts': Set the custom arguments passed to the script for each type of notification. % elif notifier['agent_name'] == 'webhook': Set the custom JSON data sent to the webhook for each type of notification. % else: Set the custom formatted text for each type of notification. % endif <a href="#notify-text-sub-modal" data-toggle="modal">Click here</a> for a list of available parameters which can be used. </p> <p class="help-block"> You can also add text modifiers to change the case or slice parameters with a list of items. <a href="#notify-text-modifiers-modal" data-toggle="modal">Click here</a> to view usage information. </p> <p class="help-block"> You can also add tags to exclude certain text depending on the media type. <a href="#notify-text-tags-modal" data-toggle="modal">Click here</a> to view usage information. </p> <br /> <ul id="accordion-notify_text" class="accordion list-unstyled"> % if notifier['agent_name'] == 'scripts': % for action in available_notification_actions: <li> <div class="link"> <span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span> ${action['label']} <span class="toggle-right"><i class="fa fa-chevron-down"></i></span> </div> <ul class="submenu"> <li> <div class="form-group"> <label for="${action['name']}_subject">Script Arguments</label> <input class="form-control" type="text" id="${action['name']}_subject" name="${action['name']}_subject" value="${notifier['notify_text'][action['name']]['subject']}" autocomplete="off" data-parsley-trigger="change" required> <p class="help-block">Set custom arguments passed to the script.</p> </div> <div class="form-group"> <div class="row"> <div class="col-md-12"> <input type="button" class="btn btn-bright notifier-text-preview" data-action="${action['name']}" value="Preview Arguments"> </div> </div> </div> </li> </ul> </li> % endfor % elif notifier['agent_name'] == 'webhook': % for action in available_notification_actions: <li> <div class="link"> <span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span> ${action['label']} <span class="toggle-right"><i class="fa fa-chevron-down"></i></span> </div> <ul class="submenu"> <li> <div class="form-group"> <label for="${action['name']}_subject">JSON Headers</label> <textarea class="form-control" id="${action['name']}_subject" name="${action['name']}_subject" data-parsley-trigger="change" autocomplete="off" data-autoresize required>${notifier['notify_text'][action['name']]['subject']}</textarea> <p class="help-block">Set custom JSON headers.</p> </div> <div class="form-group"> <label for="${action['name']}_body">JSON Data</label> <textarea class="form-control" id="${action['name']}_body" name="${action['name']}_body" data-parsley-trigger="change" autocomplete="off" data-autoresize required>${notifier['notify_text'][action['name']]['body']}</textarea> <p class="help-block">Set custom JSON data.</p> </div> <div class="form-group"> <div class="row"> <div class="col-md-12"> <input type="button" class="btn btn-bright notifier-text-preview" data-action="${action['name']}" value="Preview JSON Data"> </div> </div> </div> </li> </ul> </li> % endfor % else: % for action in available_notification_actions: <li> <div class="link"> <span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span> ${action['label']} <span class="toggle-right"><i class="fa fa-chevron-down"></i></span> </div> <ul class="submenu"> <li> <div class="form-group"> <label for="${action['name']}_subject">Subject Line</label> <input class="form-control" type="text" id="${action['name']}_subject" name="${action['name']}_subject" value="${notifier['notify_text'][action['name']]['subject']}" autocomplete="off" data-parsley-trigger="change" required> <p class="help-block">Set a custom subject line.</p> </div> <div class="form-group"> <label for="${action['name']}_body">Message Body</label> <textarea class="form-control" id="${action['name']}_body" name="${action['name']}_body" data-parsley-trigger="change" autocomplete="off" data-autoresize required>${notifier['notify_text'][action['name']]['body']}</textarea> <p class="help-block">Set a custom body.</p> </div> <div class="form-group"> <div class="row"> <div class="col-md-12"> <input type="button" class="btn btn-bright notifier-text-preview" data-action="${action['name']}" value="Preview Text"> </div> </div> </div> </li> </ul> </li> % endfor % endif </ul> </div> <div role="tabpanel" class="tab-pane" id="tabs-test_notifications"> <label>Test Notifications</label> <p class="help-block"> Test if ${notifier['agent_label']} notifications are working. Check the <a href="logs">logs</a> for troubleshooting. </p> <p class="help-block"> Note: Test with the real values since parameters will not be substituted. (i.e. Use <span class="inline-pre">"Game of Thrones"</span> not <span class="inline-pre">{title}</span>) </p> % if notifier['agent_name'] == 'scripts': <div class="form-group"> <label for="test_script">Script</label> <div class="row"> <div class="col-md-12"> <select class="form-control" id="test_script" name="test_script" autocomplete="off"> % for key, value in sorted(notifier['config_options'][2]['select_options'].items()): <option value="${key}">${value}</option> % endfor </select> </div> </div> <p class="help-block">Choose the script to test.</p> </div> <div class="form-group"> <label for="test_script_args">Script Arguments</label> <div class="row"> <div class="col-md-12"> <input class="form-control" type="text" id="test_script_args" name="test_script_args" value="" autocomplete="off"> </div> </div> <p class="help-block">Set custom arguments passed to the script.</p> </div> % elif notifier['agent_name'] == 'webhook': <div class="form-group"> <label for="test_subject">JSON Headers</label> <div class="row"> <div class="col-md-12"> <textarea class="form-control" id="test_subject" name="test_subject" autocomplete="off" data-autoresize></textarea> </div> </div> <p class="help-block">Set custom JSON headers sent to the webhook.</p> </div> <div class="form-group"> <label for="test_body">JSON Data</label> <div class="row"> <div class="col-md-12"> <textarea class="form-control" id="test_body" name="test_body" autocomplete="off" data-autoresize></textarea> </div> </div> <p class="help-block">Set custom JSON data sent to the webhook.</p> </div> % else: <div class="form-group"> <label for="test_subject">Subject Line</label> <div class="row"> <div class="col-md-12"> <input class="form-control" type="text" id="test_subject" name="test_subject" value="Tautulli" autocomplete="off"> </div> </div> <p class="help-block">Set a custom subject line.</p> </div> <div class="form-group"> <label for="test_body">Message Body</label> <div class="row"> <div class="col-md-12"> <textarea class="form-control" id="test_body" name="test_body" autocomplete="off" data-autoresize>Test Notification</textarea> </div> </div> <p class="help-block">Set a custom body.</p> </div> % endif <div class="form-group"> <div class="row"> <div class="col-md-12"> <input type="button" class="btn btn-bright" id="test_notifier" name="test_notifier" value="Test ${notifier['agent_label']}"> </div> </div> </div> </div> </div> </form> </div> </div> <div class="modal-footer"> <input type="button" id="delete-notifier-item" class="btn btn-danger btn-edit" style="float:left;" value="Delete"> <input type="button" id="duplicate-notifier-item" class="btn btn-dark btn-edit" style="float:left;" value="Duplicate"> <input type="button" id="save-notifier-item" class="btn btn-bright" value="Save"> </div> </div> </div> <script src="${http_root}js/filterer.jquery.js"></script> <script> $('#notifier-config-modal').unbind('hidden.bs.modal'); // Need this for setting conditions since conditions contain the character " $('#custom_conditions').val(JSON.stringify(${json.dumps(notifier["custom_conditions"]) | n})); $('#condition-widget').filterer({ parameters: ${json.dumps(parameters) | n}, conditions: ${json.dumps(notifier["custom_conditions"]) | n}, updateConditions: function(newConditions){ $('#custom_conditions').val(JSON.stringify(newConditions)); } }); function reloadModal() { $.ajax({ url: 'get_notifier_config_modal', data: { notifier_id: '${notifier["id"]}' }, cache: false, async: true, complete: function (xhr, status) { $('#notifier-config-modal').html(xhr.responseText); } }); } function saveCallback(jqXHR) { if (jqXHR) { var result = $.parseJSON(jqXHR.responseText); var msg = result.message; if (result.result === 'success') { showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000) } else { showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true) } } getNotifiersTable(); toggleRevealTokens(); } function deleteCallback() { $('#notifier-config-modal').modal('hide'); getNotifiersTable(); } function duplicateCallback(result) { // Set new notifier id $('#notifier_id').val(result.notifier_id); // Clear friendly name $('#friendly_name').val(""); // Uncheck all triggers $('#tabs-notify_triggers input[id^=on_]').val(0); saveNotifier(); $('#notifier-config-modal').on('hidden.bs.modal', function () { loadNotifierConfig(result.notifier_id); }); $('#notifier-config-modal').modal('hide'); } function saveNotifier() { // Trim all text inputs before saving $('input[type=text]').val(function(_, value) { return $.trim(value); }); if (validateLogic()) { // Reload modal to update certain fields doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback); } } $('#delete-notifier-item').click(function () { var msg = 'Are you sure you want to delete this <strong>${notifier["agent_label"]}</strong> notification agent?'; var url = 'delete_notifier'; confirmAjaxCall(url, msg, { notifier_id: '${notifier["id"]}' }, null, deleteCallback); }); $('#duplicate-notifier-item').click(function() { var msg = 'Are you sure you want to duplicate this <strong>${notifier["agent_label"]}</strong> notification agent?'; if ($('#set_notifier_config .form-control[id$=_password]').val()) { msg += '<br><br>Note: Saved passwords will not be copied over and must be re-entered.'; } var url = 'add_notifier_config'; confirmAjaxCall(url, msg, { agent_id: '${notifier["agent_id"]}' }, null, duplicateCallback); }); $('#save-notifier-item').click(function () { saveNotifier(); }); % if notifier['agent_name'] == 'facebook': if (location.protocol !== 'https:') { $('#tabs-config .form-group:first').prepend( '<div class="form-group">' + '<label>Warning</label>' + '<p class="help-block" style="color: #eb8600;">Facebook requires HTTPS for authorization. ' + 'Please enable HTTPS for Tautulli under <a data-tab-destination="web_interface" data-dismiss="modal" data-target="enable_https">Web Interface</a>.</p>' + '</div>' ); $('#facebook_redirect_uri').val('HTTPS not enabled'); } else { $('#facebook_redirect_uri').val(location.href.split('/settings')[0] + '/facebook_redirect'); } function disableFacebookRequest() { if ($('#facebook_app_id').val() !== '' && $('#facebook_app_secret').val() !== '') { $('#facebook_facebook_auth').prop('disabled', false); } else { $('#facebook_facebook_auth').prop('disabled', true); } } disableFacebookRequest(); $('#facebook_app_id, #facebook_app_secret').on('change', function () { disableFacebookRequest(); }); $('#facebook_facebook_auth').click(function () { // Remove trailing '/' from Facebook redirect URI if ($('#facebook_redirect_uri') && $('#facebook_redirect_uri').val().endsWith('/')) { $('#facebook_redirect_uri').val($('#facebook_redirect_uri').val().slice(0, -1)); } var facebook_token; $.ajax({ url: 'facebook_auth', data: { app_id: $('#facebook_app_id').val(), app_secret: $('#facebook_app_secret').val(), redirect_uri: $('#facebook_redirect_uri').val() }, cache: false, async: true, complete: function (xhr, status) { var result = $.parseJSON(xhr.responseText); var msg = result.msg; if (result.result === 'success') { showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000); window.open(result.url); (function retrieve_token(){ facebook_token = null; setTimeout(function() { $.ajax({ url: 'facebook_retrieve_token', type: 'GET', success: function(data) { if (data.result === 'success') { facebook_token = true; showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000); $('#facebook_access_token').val(data.access_token); } else if (data.result === 'error') { facebook_token = false; showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true); } }, complete: function() { if (facebook_token === null) { retrieve_token(); } }, timeout: 1000 }); }, 1000); })(); } else { showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true); } } }); }); $('#notifier-config-modal').on('hidden.bs.modal', function () { facebook_token = false; }); % elif notifier['agent_name'] == 'browser': $('#browser_allow_browser').click(function () { PNotify.desktop.permission(); }); % elif notifier['agent_name'] == 'osx': $('#osx_notify_register').click(function () { var osx_notify_app = $('#osx_notify_app').val(); $.get('osxnotifyregister', { 'app': osx_notify_app }, function (data) { showMsg('<i class="fa fa-check"></i> ' + data, false, true, 3000); }); }); % elif notifier['agent_name'] == 'email': var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' + '(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)'; var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({ plugins: ['remove_button'], maxItems: null, searchField: ['text', 'value'], render: { item: function(item, escape) { return '<div>' + (item.text ? '<span class="item-text">' + escape(item.text) + '</span>' : '') + (item.value ? '<span class="item-value">' + escape(item.value) + '</span>' : '') + '</div>'; }, option: function(item, escape) { var label = item.text || item.value; var caption = item.text ? item.value : null; if (item.value.endsWith('-all')) { return '<div class="' + item.value + '">' + escape(label) + '</div>' } return '<div>' + escape(label) + (caption ? '<span class="caption">' + escape(caption) + '</span>' : '') + '</div>'; } }, onItemAdd: function(value) { if (value === 'select-all') { var all_keys = $.map(this.options, function(option){ return option.value.endsWith('-all') ? null : option.value; }); this.setValue(all_keys); } else if (value === 'remove-all') { this.clear(); this.refreshOptions(); this.positionDropdown(); } }, createFilter: function(input) { var match, regex; // email@address.com regex = new RegExp('^' + REGEX_EMAIL + '$', 'i'); match = input.match(regex); if (match) return !this.options.hasOwnProperty(match[0]); // user <email@address.com> regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i'); match = input.match(regex); if (match) return !this.options.hasOwnProperty(match[2]); return false; }, create: function(input) { if ((new RegExp('^' + REGEX_EMAIL + '$', 'i')).test(input)) { return {value: input}; } var match = input.match(new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i')); if (match) { return { value : match[2], text : $.trim(match[1]) }; } return false; } }); var email_to = $email_selectors[0].selectize; var email_cc = $email_selectors[1].selectize; var email_bcc = $email_selectors[2].selectize; email_to.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_to'), [])) | n}); email_cc.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_cc'), [])) | n}); email_bcc.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_bcc'), [])) | n}); % elif notifier['agent_name'] == 'join': var $join_device_names = $('#join_device_names').selectize({ plugins: ['remove_button'], maxItems: null, create: true }); var join_device_names = $join_device_names[0].selectize; join_device_names.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'join_device_names'), [])) | n}); % elif notifier['agent_name'] == 'zapier': $('#zapier_test_hook').click(function () { $.get('zapier_test_hook', { 'zapier_hook': $('#zapier_hook').val() }, function (data) { if (data.result === 'success') { showMsg('<i class="fa fa-check"></i> ' + data.msg, false, true, 5000); } else { showMsg('<i class="fa fa-times"></i> ' + data.msg, false, true, 5000, true); } }); }); % elif notifier['agent_name'] == 'pushover': function pushoverPriority() { if ($('#pushover_priority').val() == '2') { $('#pushover_retry').closest('.form-group').show(); $('#pushover_expire').closest('.form-group').show(); } else { $('#pushover_retry').closest('.form-group').hide(); $('#pushover_expire').closest('.form-group').hide(); } } pushoverPriority(); $('#pushover_priority').change( function () { pushoverPriority(); }); var $pushover_sound = $('#pushover_sound').selectize({ create: true }); var pushover_sound = $pushover_sound[0].selectize; pushover_sound.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'pushover_sound'), [])) | n}); % elif notifier['agent_name'] == 'plexmobileapp': var $plexmobileapp_user_ids = $('#plexmobileapp_user_ids').selectize({ plugins: ['remove_button'], maxItems: null, create: true }); var plexmobileapp_user_ids = $plexmobileapp_user_ids[0].selectize; plexmobileapp_user_ids.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'plexmobileapp_user_ids'), [])) | n}); % endif function validateLogic() { const valid_tokens = /(\(|\)|and|or)/g; const conditions_pattern = /{\d+}/g; var custom_conditions = $.parseJSON($('#custom_conditions').val()); var custom_conditions_logic = $('#custom_conditions_logic').val(); var num_cond = custom_conditions.length; var tokens = $.map(custom_conditions_logic.toLowerCase().split(valid_tokens), $.trim).filter(String); var stack = [[]]; var temp; var cond_next = true; var bool_next = false; var open_bracket_next = true; var close_bracket_next = false; var nest_and = 0; var nest_nest_and = 0; try { $.each(tokens, function(i, x) { if (open_bracket_next && x === '(') { stack[stack.length-1].push([]); temp = stack[stack.length-1]; stack.push(temp[temp.length-1]); cond_next = true; bool_next = false; open_bracket_next = true; close_bracket_next = false; if (nest_and) { nest_nest_and += 1 } } else if (close_bracket_next && x === ')') { stack.pop(); if (stack.length === 0) { throw 'opening bracket is missing'; } cond_next = false; bool_next = true; open_bracket_next = false; close_bracket_next = true; if (nest_and > 0 && nest_nest_and > 0 && nest_and === nest_nest_and) { stack.pop(); nest_and -= 1; nest_nest_and -= 1; } } else if (cond_next && x.match(conditions_pattern)) { if (isNaN(x.slice(1, -1))) { throw 'invalid condition logic' } else { var num = parseInt(x.slice(1, -1)); } if (!(0 < num && num <= num_cond)) { throw 'invalid condition number in condition logic' } stack[stack.length-1].push(num); cond_next = false; bool_next = true; open_bracket_next = false; close_bracket_next = true; if (nest_and > nest_nest_and) { stack.pop(); nest_and -= 1; } } else if (bool_next && x === 'and' && i < tokens.length-1) { stack[stack.length-1].push([]); temp = stack[stack.length-1]; stack.push(temp[temp.length-1]); temp = stack[stack.length-2]; stack[stack.length-1].push(temp.splice(0, temp.length-2) + temp.splice(temp.length-2, temp.length-1)); stack[stack.length-1].push(x); cond_next = true; bool_next = false; open_bracket_next = true; close_bracket_next = false; nest_and += 1; } else if (bool_next && x === 'or' && i < tokens.length-1) { stack[stack.length-1].push(x); cond_next = true; bool_next = false; open_bracket_next = true; close_bracket_next = false; } else { throw 'invalid condition logic'; } }); if (stack.length > 1) { throw 'closing bracket is missing'; } $('#custom_conditions_logic_error').hide(); return true; } catch (e) { $('#custom_conditions_logic_error span').text(e); $('#custom_conditions_logic_error').show(); showMsg('<i class="fa fa-times"></i> Failed to save notifier. Invalid condition logic.', false, true, 5000, true); } } $('.notifier-text-preview').click(function () { var action = $(this).data('action'); var subject = $('#' + action + '_subject').val(); var body = $('#' + action + '_body').val(); $.ajax({ url: 'get_notify_text_preview', type: 'POST', data: { notify_action: action, subject: subject, body: body, agent_id: "${notifier['agent_id']}", agent_name: "${notifier['agent_name']}" }, cache: false, async: true, complete: function (xhr, status) { $("#notifier-text-preview-modal").html(xhr.responseText).modal('show'); } }); }); $('#test_notifier').click(function () { doAjaxCall('set_notifier_config', $(this), 'tabs', true, false, sendTestNotification); }); function sendTestNotification() { showMsg('<i class="fa fa-refresh fa-spin"></i> Sending Notification', false); if ('${notifier["agent_name"]}' !== 'browser') { $.ajax({ url: 'send_notification', data: { notifier_id: $('#notifier_id').val(), subject: $('#test_subject').val(), body: $('#test_body').val(), script: $('#test_script').val(), script_args: $('#test_script_args').val(), notify_action: 'test' }, cache: false, async: true, success: function (data) { if (data.result === 'success') { showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000); } else { showMsg('<i class="fa fa-exclamation-circle"></i> ' + data.message, false, true, 5000, true); } } }); } else { if ($('#browser_auto_hide_delay').val() === "0") { PNotify.prototype.options.hide = false; } else { PNotify.prototype.options.hide = true; PNotify.prototype.options.delay = $('#browser_auto_hide_delay').val() * 1000; } displayPNotify($('#test_subject').val(), $('#test_body').val()); showMsg('<i class="fa fa-check"></i> Notification sent.', false, true, 5000); } } $("${', '.join(['#' + c['name'] for c in notifier['config_options'] if c.get('refresh')])}").on('change', function () { // Reload modal to update certain fields doAjaxCall('set_notifier_config', $(this), 'tabs', true, false, reloadModal); return false; }); // Never send checkbox values directly, always substitute value in hidden input. $('.checkboxes').click(function () { var configToggle = $(this).data('id'); if ($(this).is(':checked')) { $('#'+configToggle).val(1); } else { $('#'+configToggle).val(0); } }); var accordion_session = new Accordion($('#accordion-notify_text'), false); // auto resizing textarea for custom notification message body $('textarea[data-autoresize]').each(function () { var modal_body = $(this).closest('.modal-body'); var offset = this.offsetHeight - this.clientHeight; var resizeTextarea = function (el) { var modal_offset = modal_body.scrollTop(); $(el).css('height', 'auto').css('height', el.scrollHeight + offset); modal_body.scrollTop(modal_offset); }; $(this).on('focus keyup input', function () { resizeTextarea(this); }).removeAttr('data-autoresize'); }); toggleRevealTokens(); </script> % else: <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button> <h4 class="modal-title" id="notifier-config-modal-header">Error</h4> </div> <div class="modal-body" style="text-align: center"> <strong> <i class="fa fa-exclamation-circle"></i> Failed to retrieve notifier configuration. Check the <a href="logs">logs</a> for more info. </strong> </div> <div class="modal-footer"> </div> </div> </div> % endif