var Settings = { showAdvanced: false, METAVERSE_URL: 'https://metaverse.highfidelity.com', ADVANCED_CLASS: 'advanced-setting', DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', DATA_COL_CLASS: 'value-col', DATA_CATEGORY_CLASS: 'value-category', ADD_ROW_BUTTON_CLASS: 'add-row', ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row', DEL_ROW_BUTTON_CLASS: 'del-row', DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row', ADD_CATEGORY_BUTTON_CLASS: 'add-category', ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category', TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category', TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon', TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon', TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom', TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right', DEL_CATEGORY_BUTTON_CLASS: 'del-category', DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category', MOVE_UP_BUTTON_CLASS: 'move-up', MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up', MOVE_DOWN_BUTTON_CLASS: 'move-down', MOVE_DOWN_SPAN_CLASSES: 'glyphicon glyphicon-chevron-down move-down', TABLE_BUTTONS_CLASS: 'buttons', ADD_DEL_BUTTONS_CLASS: 'add-del-buttons', ADD_DEL_BUTTONS_CLASSES: 'buttons add-del-buttons', REORDER_BUTTONS_CLASS: 'reorder-buttons', REORDER_BUTTONS_CLASSES: 'buttons reorder-buttons', NEW_ROW_CLASS: 'new-row', CONNECT_ACCOUNT_BTN_ID: 'connect-account-btn', DISCONNECT_ACCOUNT_BTN_ID: 'disconnect-account-btn', CREATE_DOMAIN_ID_BTN_ID: 'create-domain-btn', CHOOSE_DOMAIN_ID_BTN_ID: 'choose-domain-btn', GET_TEMPORARY_NAME_BTN_ID: 'get-temp-name-btn', DOMAIN_ID_SELECTOR: '[name="metaverse.id"]', ACCESS_TOKEN_SELECTOR: '[name="metaverse.access_token"]', PLACES_TABLE_ID: 'places-table', FORM_ID: 'settings-form', INVALID_ROW_CLASS: 'invalid-input' }; var viewHelpers = { getFormGroup: function(keypath, setting, values, isAdvanced) { form_group = "
"; setting_value = _(values).valueForKeyPath(keypath); if (_.isUndefined(setting_value) || _.isNull(setting_value)) { if (_.has(setting, 'default')) { setting_value = setting.default; } else { setting_value = ""; } } label_class = 'control-label'; function common_attrs(extra_classes) { extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" + setting.name + "' name='" + keypath + "' " + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; } if (setting.type === 'checkbox') { if (setting.label) { form_group += "" } form_group += "
" form_group += "" if (setting.help) { form_group += "" + setting.help + ""; } form_group += "
" } else { input_type = _.has(setting, 'type') ? setting.type : "text" if (setting.label) { form_group += ""; } if (input_type === 'table') { form_group += makeTable(setting, keypath, setting_value) } else { if (input_type === 'select') { form_group += "" form_group += "" } else if (input_type === 'button') { // Is this a button that should link to something directly? // If so, we use an anchor tag instead of a button tag if (setting.href) { form_group += "" + setting.button_label + ""; } else { form_group += ""; } } else { if (input_type == 'integer') { input_type = "text" } form_group += "" } form_group += "" + setting.help + "" } } form_group += "
" return form_group } } var qs = (function(a) { if (a == "") return {}; var b = {}; for (var i = 0; i < a.length; ++i) { var p=a[i].split('=', 2); if (p.length == 1) b[p[0]] = ""; else b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); } return b; })(window.location.search.substr(1).split('&')); $(document).ready(function(){ /* * Clamped-width. * Usage: *
This long content will force clamped width
* * Author: LV */ $('[data-clampedwidth]').each(function () { var elem = $(this); var parentPanel = elem.data('clampedwidth'); var resizeFn = function () { var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth')); elem.css('width', sideBarNavWidth); }; resizeFn(); $(window).resize(resizeFn); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ addTableRow($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ deleteTableRow($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ addTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ deleteTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ toggleTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ moveTableRow($(this).closest('tr'), true); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ moveTableRow($(this).closest('tr'), false); }); $('#' + Settings.FORM_ID).on('keyup', function(e){ var $target = $(e.target); if (e.keyCode == 13) { if ($target.is('table input')) { // capture enter in table input // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click sibling = $target.parent('td').next(); if (sibling.hasClass(Settings.DATA_COL_CLASS)) { // set focus to next input sibling.find('input').focus(); } else { // jump over the re-order row, if that's what we're on if (sibling.hasClass(Settings.REORDER_BUTTONS_CLASS)) { sibling = sibling.next(); } // for tables with categories we add the entry and setup the new row on enter if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); // set focus to the first input in the new row $target.closest('table').find('tr.inputs input:first').focus(); } var tableRows = sibling.parent(); var tableBody = tableRows.parent(); // if theres no more siblings, we should jump to a new row if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); } } } else if ($target.is('input')) { $target.change().blur(); } e.preventDefault(); } }); $('#' + Settings.FORM_ID).on('keypress', function(e){ if (e.keyCode == 13) { e.preventDefault(); } }); $('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ // this input was changed, add the changed data attribute to it $(this).attr('data-changed', true); badgeSidebarForDifferences($(this)); }); $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){ // this checkbox was changed, add the changed data attribute to it $(this).attr('data-changed', true); badgeSidebarForDifferences($(this)); }); // Bootstrap switch in table $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { // Bootstrap switches in table: set the changed data attribute for all rows in table. var row = $(this).closest('tr'); if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); updateDataChangedForSiblingRows(row, true); badgeSidebarForDifferences($(this)); } }); $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { // Bootstrap switches in table: set the changed data attribute for all rows in table. var row = $(this).closest('tr'); if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); updateDataChangedForSiblingRows(row, true); badgeSidebarForDifferences($(this)); } }); $('.advanced-toggle').click(function(){ Settings.showAdvanced = !Settings.showAdvanced var advancedSelector = $('.' + Settings.ADVANCED_CLASS) if (Settings.showAdvanced) { advancedSelector.show(); $(this).html("Hide advanced") } else { advancedSelector.hide(); $(this).html("Show advanced") } $(this).blur(); }) $('#' + Settings.FORM_ID).on('click', '#' + Settings.CREATE_DOMAIN_ID_BTN_ID, function(){ $(this).blur(); showDomainCreationAlert(false); }) $('#' + Settings.FORM_ID).on('click', '#' + Settings.CHOOSE_DOMAIN_ID_BTN_ID, function(){ $(this).blur(); chooseFromHighFidelityDomains($(this)) }); $('#' + Settings.FORM_ID).on('click', '#' + Settings.GET_TEMPORARY_NAME_BTN_ID, function(){ $(this).blur(); createTemporaryDomain(); }); $('#' + Settings.FORM_ID).on('change', 'select', function(){ $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change() }); $('#' + Settings.FORM_ID).on('click', '#' + Settings.DISCONNECT_ACCOUNT_BTN_ID, function(e){ $(this).blur(); disonnectHighFidelityAccount(); e.preventDefault(); }); $('#' + Settings.FORM_ID).on('click', '#' + Settings.CONNECT_ACCOUNT_BTN_ID, function(e){ $(this).blur(); prepareAccessTokenPrompt(); }); var panelsSource = $('#panels-template').html() Settings.panelsTemplate = _.template(panelsSource) var sidebarTemplate = $('#list-group-template').html() Settings.sidebarTemplate = _.template(sidebarTemplate) // $('body').scrollspy({ target: '#setup-sidebar'}) reloadSettings(function(success){ if (success) { handleAction(); } else { swal({ title: '', type: 'error', text: "There was a problem loading the domain settings.\nPlease refresh the page to try again.", }); } }); }); function handleAction() { // check if we were passed an action to handle var action = qs["action"]; if (action == "share") { // figure out if we already have a stored domain ID if (Settings.data.values.metaverse.id.length > 0) { // we need to ask the API what a shareable name for this domain is getDomainFromAPI(function(data){ // check if we have owner_places (for a real domain) or a name (for a temporary domain) if (data && data.status == "success") { var shareName; if (data.domain.owner_places) { shareName = data.domain.owner_places[0].name } else if (data.domain.name) { shareName = data.domain.name; } var shareLink = "hifi://" + shareName; console.log(shareLink); // show a dialog with a copiable share URL swal({ title: "Share", type: "input", inputPlaceholder: shareLink, inputValue: shareLink, text: "Copy this URL to invite friends to your domain.", closeOnConfirm: true }); $('.sweet-alert input').select(); } else { // show an error alert swal({ title: '', type: 'error', text: "There was a problem retreiving domain information from High Fidelity API.", confirmButtonText: 'Try again', showCancelButton: true, closeOnConfirm: false }, function(isConfirm){ if (isConfirm) { // they want to try getting domain share info again showSpinnerAlert("Requesting domain information...") handleAction(); } else { swal.close(); } }); } }); } else { // no domain ID present, just show the share dialog createTemporaryDomain(); } } } function dynamicButton(button_id, text) { return $(""); } function postSettings(jsonSettings) { // POST the form JSON to the domain-server settings.json endpoint so the settings are saved $.ajax('/settings.json', { data: JSON.stringify(jsonSettings), contentType: 'application/json', type: 'POST' }).done(function(data){ if (data.status == "success") { showRestartModal(); } else { showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) reloadSettings(); } }).fail(function(){ showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) reloadSettings(); }); } function setupHFAccountButton() { // figure out how we should handle the HF connect button var accessToken = Settings.data.values.metaverse.access_token; // setup an object for the settings we want our button to have var buttonSetting = { type: 'button', name: 'connected_account', label: 'Connected Account', } var hasAccessToken = accessToken.length > 0; if (hasAccessToken) { buttonSetting.help = "Click the button above to clear your OAuth token and disconnect your High Fidelity account."; buttonSetting.classes = "btn-danger"; buttonSetting.button_label = "Disconnect High Fidelity Account"; buttonSetting.html_id = Settings.DISCONNECT_ACCOUNT_BTN_ID; } else { buttonSetting.help = "Click the button above to connect your High Fidelity account."; buttonSetting.classes = "btn-primary"; buttonSetting.button_label = "Connect High Fidelity Account"; buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID; buttonSetting.href = Settings.METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; // since we do not have an access token we change hide domain ID and auto networking settings // without an access token niether of them can do anything $("[data-keypath='metaverse.id']").hide(); $("[data-keypath='metaverse.automatic_networking']").hide(); } // use the existing getFormGroup helper to ask for a button var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values); // add the button group to the top of the metaverse panel $('#metaverse .panel-body').prepend(buttonGroup); } function disonnectHighFidelityAccount() { // the user clicked on the disconnect account btn - give them a sweet alert to make sure this is what they want to do swal({ title: "Are you sure?", text: "This will remove your domain-server OAuth access token." + "

This could cause your domain to appear offline and no longer be reachable via any place names.", type: "warning", html: true, showCancelButton: true }, function(){ // we need to post to settings to clear the access-token $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); // reset the domain id to get a new temporary name $(Settings.DOMAIN_ID_SELECTOR).val('').change(); saveSettings(); }); } function prepareAccessTokenPrompt() { swal({ title: "Connect Account", type: "input", text: "Paste your created access token here." + "

If you did not successfully create an access token click cancel below and attempt to connect your account again.

", showCancelButton: true, closeOnConfirm: false, html: true }, function(inputValue){ if (inputValue === false) return false; if (inputValue === "") { swal.showInputError("Please paste your access token in the input field.") return false } // we have an input value - set the access token input with this and save settings $(Settings.ACCESS_TOKEN_SELECTOR).val(inputValue).change(); // if the user doesn't have a domain ID set, give them the option to create one now if (!Settings.data.values.metaverse.id) { // show domain ID selection alert showDomainIDChoiceAlert(); } else { swal.close(); saveSettings(); } }); } function showDomainIDChoiceAlert() { swal({ title: 'Domain ID', type: 'info', text: "You do not currently have a domain ID." + "

This is required to point place names at your domain and to use automatic networking.

" + "Would you like to create a domain ID via the Metaverse API?

", showCancelButton: true, confirmButtonText: "Create new domain ID", cancelButtonText: "Skip", closeOnConfirm: false, html: true }, function(isConfirm){ if (isConfirm) { // show the swal to create a new domain via API showDomainCreationAlert(true); } else { // user cancelled, close this swal and save the access token we got swal.close(); saveSettings(); } }); } function showSpinnerAlert(title) { swal({ title: title, text: '
', html: true, showConfirmButton: false, allowEscapeKey: false }); } function showDomainCreationAlert(justConnected) { swal({ title: 'Create new domain ID', type: 'input', text: 'Enter a short description for this machine.

This will help you identify which domain ID belongs to which machine.

', showCancelButton: true, confirmButtonText: "Create", closeOnConfirm: false, html: true }, function(inputValue){ if (inputValue === false) { swal.close(); // user cancelled domain ID creation - if we're supposed to save after cancel then save here if (justConnected) { saveSettings(); } } else { // we're going to change the alert to a new one with a spinner while we create this domain showSpinnerAlert('Creating domain ID'); createNewDomainID(inputValue, justConnected); } }); } function createNewDomainID(description, justConnected) { // get the JSON object ready that we'll use to create a new domain var domainJSON = { "domain": { "private_description": description }, "access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val() } $.post(Settings.METAVERSE_URL + "/api/v1/domains", domainJSON, function(data){ if (data.status == "success") { // we successfully created a domain ID, set it on that field var domainID = data.domain.id; $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); if (justConnected) { var successText = "We connnected your High Fidelity account and created a new domain ID for this machine." } else { var successText = "We created a new domain ID for this machine." } successText += "

Click the button below to save your new settings and restart your domain-server."; // show a sweet alert to say we are all finished up and that we need to save swal({ title: 'Success!', type: 'success', text: successText, html: true, confirmButtonText: 'Save' }, function(){ saveSettings(); }); } }).fail(function(){ var errorText = "There was a problem creating your new domain ID. Do you want to try again or"; if (justConnected) { errorText += " just save your new access token?

You can always create a new domain ID later."; } else { errorText += " cancel?" } // we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel swal({ title: '', type: 'error', text: errorText, html: true, confirmButtonText: 'Try again', showCancelButton: true, closeOnConfirm: false }, function(isConfirm){ if (isConfirm) { // they want to try creating a domain ID again showDomainCreationAlert(justConnected); } else { // they want to cancel if (justConnected) { // since they just connected we need to save the access token here saveSettings(); } } }); }); } function setupPlacesTable() { // create a dummy table using our view helper var placesTableSetting = { type: 'table', name: 'places', label: 'Places', html_id: Settings.PLACES_TABLE_ID, help: "The following places currently point to this domain.
To point places to this domain, " + " go to the My Places " + "page in your High Fidelity Metaverse account.", read_only: true, columns: [ { "name": "name", "label": "Name" }, { "name": "path", "label": "Path" }, { "name": "edit", "label": "", "class": "buttons" } ] } // get a table for the places var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); // append the places table in the right place $('#places_paths .panel-body').prepend(placesTableGroup); // do we have a domain ID? if (Settings.data.values.metaverse.id.length > 0) { // now, ask the API for what places, if any, point to this domain reloadPlacesOrTemporaryName(); } else { // we don't have a domain ID - add a button to offer the user a chance to get a temporary one var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); } } function placeTableRow(name, path, isTemporary) { var name_link = "" + (isTemporary ? name + " (temporary)" : name) + ""; if (isTemporary) { var editColumn = ""; } else { var editColumn = ""; } return "" + name_link + "" + path + "" + editColumn + ""; } function placeTableRowForPlaceObject(place) { var placePathOrIndex = (place.path ? place.path : "/"); return placeTableRow(place.name, placePathOrIndex, false); } function getDomainFromAPI(callback) { // we only need to do this if we have a current domain ID var domainID = Settings.data.values.metaverse.id; if (domainID.length > 0) { var domainURL = Settings.METAVERSE_URL + "/api/v1/domains/" + domainID; $.getJSON(domainURL, callback).fail(callback); } } function reloadPlacesOrTemporaryName() { getDomainFromAPI(function(data){ // check if we have owner_places (for a real domain) or a name (for a temporary domain) if (data.status == "success") { if (data.domain.owner_places) { // add a table row for each of these names _.each(data.domain.owner_places, function(place){ $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); }); } else if (data.domain.name) { // add a table row for this temporary domain name $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); } } }) } function appendDomainIDButtons() { var domainIDInput = $(Settings.DOMAIN_ID_SELECTOR); var createButton = dynamicButton(Settings.CREATE_DOMAIN_ID_BTN_ID, "Create new domain ID"); createButton.css('margin-top', '10px'); var chooseButton = dynamicButton(Settings.CHOOSE_DOMAIN_ID_BTN_ID, "Choose from my domains"); chooseButton.css('margin', '10px 0px 0px 10px'); domainIDInput.after(chooseButton); domainIDInput.after(createButton); } function chooseFromHighFidelityDomains(clickedButton) { // setup the modal to help user pick their domain if (Settings.initialValues.metaverse.access_token) { // add a spinner to the choose button clickedButton.html("Loading domains...") clickedButton.attr('disabled', 'disabled') // get a list of user domains from data-web data_web_domains_url = Settings.METAVERSE_URL + "/api/v1/domains?access_token=" $.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){ modal_buttons = { cancel: { label: 'Cancel', className: 'btn-default' } } if (data.data.domains.length) { // setup a select box for the returned domains modal_body = "

Choose the High Fidelity domain you want this domain-server to represent.
This will set your domain ID on the settings page.

" domain_select = $("") _.each(data.data.domains, function(domain){ var domainString = ""; if (domain.private_description) { domainString += '"' + domain.private_description + '" - '; } domainString += domain.id; domain_select.append(""); }) modal_body += "" + domain_select[0].outerHTML modal_buttons["success"] = { label: 'Choose domain', callback: function() { domainID = $('#domain-name-select').val() // set the domain ID on the form $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); } } } else { modal_buttons["success"] = { label: 'Create new domain', callback: function() { window.open(Settings.METAVERSE_URL + "/user/domains", '_blank'); } } modal_body = "

You do not have any domains in your High Fidelity account." + "

Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.

" } bootbox.dialog({ title: "Choose matching domain", message: modal_body, buttons: modal_buttons }) // remove the spinner from the choose button clickedButton.html("Choose from my domains") clickedButton.removeAttr('disabled') }) } else { bootbox.alert({ message: "You must have an access token to query your High Fidelity domains.

" + "Please follow the instructions on the settings page to add an access token.", title: "Access token required" }) } } function createTemporaryDomain() { swal({ title: 'Create temporary place name', text: "This will create a temporary place name and domain ID" + " so other users can easily connect to your domain.

" + "In order to make your domain reachable, this will also enable full automatic networking.", showCancelButton: true, confirmButtonText: 'Create', closeOnConfirm: false, html: true }, function(isConfirm){ if (isConfirm) { showSpinnerAlert('Creating temporary place name'); // make a get request to get a temporary domain $.post(Settings.METAVERSE_URL + '/api/v1/domains/temporary', function(data){ if (data.status == "success") { var domain = data.data.domain; // we should have a new domain ID - set it on the domain ID value $(Settings.DOMAIN_ID_SELECTOR).val(domain.id).change(); // we also need to make sure auto networking is set to full $('[data-hidden-input="metaverse.automatic_networking"]').val("full").change(); swal({ type: 'success', title: 'Success!', text: "We have created a temporary name and domain ID for you.

" + "Your temporary place name is " + domain.name + ".

" + "Press the button below to save your new settings and restart your domain-server.", confirmButtonText: 'Save', html: true }, function(){ saveSettings(); }); } }); } }); } function reloadSettings(callback) { $.getJSON('/settings.json', function(data){ _.extend(data, viewHelpers) $('.nav-stacked').html(Settings.sidebarTemplate(data)) $('#panels').html(Settings.panelsTemplate(data)) Settings.data = data; Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); // append the domain selection modal appendDomainIDButtons(); // call our method to setup the HF account button setupHFAccountButton(); // call our method to setup the place names table setupPlacesTable(); // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); $('[data-toggle="tooltip"]').tooltip(); // call the callback now that settings are loaded callback(true); }).fail(function() { // call the failure object since settings load faild callback(false) }); } function validateInputs() { // check if any new values are bad var tables = $('table'); var inputsValid = true; var tables = $('table'); // clear any current invalid rows $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); function markParentRowInvalid(rowChild) { $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); } _.each(tables, function(table) { // validate keys specificially for spaces and equality to an existing key var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); var keyWithSpaces = false; var empty = false; var duplicateKey = false; _.each(newKeys, function(keyCell) { var keyVal = $(keyCell).children('input').val(); if (keyVal.indexOf(' ') !== -1) { keyWithSpaces = true; markParentRowInvalid(keyCell); return; } // make sure the key isn't empty if (keyVal.length === 0) { empty = true markParentRowInvalid(input); return; } // make sure we don't have duplicate keys in the table var otherKeys = $(table).find('td.key').not(keyCell); _.each(otherKeys, function(otherKeyCell) { var keyInput = $(otherKeyCell).children('input'); if (keyInput.length) { if ($(keyInput).val() == keyVal) { duplicateKey = true; } } else if ($(otherKeyCell).html() == keyVal) { duplicateKey = true; } if (duplicateKey) { markParentRowInvalid(keyCell); return; } }); }); if (keyWithSpaces) { showErrorMessage("Error", "Key contains spaces"); inputsValid = false; return } if (empty) { showErrorMessage("Error", "Empty field(s)"); inputsValid = false; return } if (duplicateKey) { showErrorMessage("Error", "Two keys cannot be identical"); inputsValid = false; return; } }); return inputsValid; } var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; function saveSettings() { if (validateInputs()) { // POST the form JSON to the domain-server settings.json endpoint so the settings are saved var canPost = true; // disable any inputs not changed $("input:not([data-changed])").each(function () { $(this).prop('disabled', true); }); // grab a JSON representation of the form via form2js var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); // check if we've set the basic http password if (formJSON["security"]) { var password = formJSON["security"]["http_password"]; var verify_password = formJSON["security"]["verify_http_password"]; // if they've only emptied out the default password field, we should go ahead and acknowledge // the verify password field if (password != undefined && verify_password == undefined) { verify_password = ""; } // if we have a password and its verification, convert it to sha256 for comparison if (password != undefined && verify_password != undefined) { formJSON["security"]["http_password"] = sha256_digest(password); formJSON["security"]["verify_http_password"] = sha256_digest(verify_password); if (password == verify_password) { delete formJSON["security"]["verify_http_password"]; } else { bootbox.alert({ "message": "Passwords must match!", "title": "Password Error" }); canPost = false; } } } console.log("----- SAVING ------"); console.log(formJSON); // re-enable all inputs $("input").each(function () { $(this).prop('disabled', false); }); // remove focus from the button $(this).blur(); if (canPost) { if (formJSON["security"]) { var username = formJSON["security"]["http_username"]; var password = formJSON["security"]["http_password"]; if ((password == sha256_digest("")) && (username == undefined || (username && username.length != 0))) { swal({ title: "Are you sure?", text: "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", type: "warning", showCancelButton: true, confirmButtonColor: "#5cb85c", confirmButtonText: "Yes!", closeOnConfirm: true }, function () { formJSON["security"]["http_password"] = ""; postSettings(formJSON); }); return; } } // POST the form JSON to the domain-server settings.json endpoint so the settings are saved postSettings(formJSON); } } } $('body').on('click', '.save-button', function(e){ saveSettings(); return false; }); function makeTable(setting, keypath, setting_value) { var isArray = !_.has(setting, 'key'); var categoryKey = setting.categorize_by_key; var isCategorized = !!categoryKey && isArray; if (!isArray && setting.can_order) { setting.can_order = false; } var html = ""; if (setting.help) { html += "" + setting.help + "" } var nonDeletableRowKey = setting["non-deletable-row-key"]; var nonDeletableRowValues = setting["non-deletable-row-values"]; html += ""; if (setting.caption) { html += "" } // Column groups if (setting.groups) { html += "" _.each(setting.groups, function (group) { html += "" }) if (!setting.read_only) { if (setting.can_order) { html += ""; } html += "" } html += "" } // Column names html += "" if (setting.numbered === true) { html += "" // Row number } if (setting.key) { html += "" // Key } var numVisibleColumns = 0; _.each(setting.columns, function(col) { if (!col.hidden) numVisibleColumns++; html += "" // Data }) if (!setting.read_only) { if (setting.can_order) { numVisibleColumns++; html += ""; } numVisibleColumns++; html += ""; } // populate rows in the table from existing values var row_num = 1; if (keypath.length > 0 && _.size(setting_value) > 0) { var rowIsObject = setting.columns.length > 1; _.each(setting_value, function(row, rowIndexOrName) { var categoryPair = {}; var categoryValue = ""; if (isCategorized) { categoryValue = rowIsObject ? row[categoryKey] : row; categoryPair[categoryKey] = categoryValue; if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); } } html += ""; if (setting.numbered === true) { html += "" } if (setting.key) { html += "" } var isNonDeletableRow = !setting.can_add_new_rows; _.each(setting.columns, function(col) { var colValue, colName; if (isArray) { colValue = rowIsObject ? row[col.name] : row; colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); } else { colValue = row[col.name]; colName = keypath + "." + rowIndexOrName + "." + col.name; } isNonDeletableRow = isNonDeletableRow || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); if (isArray && col.type === "checkbox" && col.editable) { html += ""; } else if (isArray && col.type === "time" && col.editable) { html += ""; } else { // Use a hidden input so that the values are posted. html += ""; } }); if (!setting.read_only) { if (setting.can_order) { html += "" } if (isNonDeletableRow) { html += ""; } else { html += ""; } } html += "" if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { html += makeTableInputs(setting, categoryPair, categoryValue); } row_num++ }); } // populate inputs in the table for new values if (!setting.read_only) { if (setting.can_add_new_categories) { html += makeTableCategoryInput(setting, numVisibleColumns); } if (setting.can_add_new_rows || setting.can_add_new_categories) { html += makeTableHiddenInputs(setting, {}, ""); } } html += "
" + setting.caption + "
" + group.label + "
#" + setting.key.label + "" + col.label + "
" + row_num + "" + rowIndexOrName + "" + "" + "" + "" + "" + colValue + "" + "" + "
" return html; } function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { var html = "" + "" + "" + "" + categoryValue + "" + "" + ((canRemove) ? ( "" + "
" + "" ) : ( "" )) + ""; return html; } function makeTableHiddenInputs(setting, initialValues, categoryValue) { var html = ""; if (setting.numbered === true) { html += ""; } if (setting.key) { html += "\ \ " } _.each(setting.columns, function(col) { var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; if (col.type === "checkbox") { html += "" + "" + ""; } else if (col.type === "select") { html += "" html += ""; html += ""; } else { html += "" + "" + ""; } }) if (setting.can_order) { html += "" } html += "" html += "" return html } function makeTableCategoryInput(setting, numVisibleColumns) { var canAddRows = setting.can_add_new_rows; var categoryKey = setting.categorize_by_key; var placeholder = setting.new_category_placeholder || ""; var message = setting.new_category_message || ""; var html = "" + "" + "" + "" + "" + "" + "" + ""; return html; } function badgeSidebarForDifferences(changedElement) { // figure out which group this input is in var panelParentID = changedElement.closest('.panel').attr('id'); // if the panel contains non-grouped settings, the initial value is Settings.initialValues var isGrouped = $('#' + panelParentID).hasClass('grouped'); if (isGrouped) { var initialPanelJSON = Settings.initialValues[panelParentID] ? Settings.initialValues[panelParentID] : {}; // get a JSON representation of that section var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]; } else { var initialPanelJSON = Settings.initialValues; // get a JSON representation of that section var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); } var badgeValue = 0 // badge for any settings we have that are not the same or are not present in initialValues for (var setting in panelJSON) { if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") || (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) { badgeValue += 1 } } // update the list-group-item badge to have the new value if (badgeValue == 0) { badgeValue = "" } $("a[href='#" + panelParentID + "'] .badge").html(badgeValue); } function addTableRow(row) { var table = row.parents('table'); var isArray = table.data('setting-type') === 'array'; var keepField = row.data("keep-field"); var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); var input_clone = row.clone(); if (!isArray) { // show the key input var keyInput = row.children(".key").children("input"); } // Change input row to data row var table = row.parents("table"); var setting_name = table.attr("name"); row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); var focusChanged = false; _.each(row.children(), function(element) { if ($(element).hasClass("numbered")) { // Index row var numbers = columns.children(".numbered") if (numbers.length > 0) { $(element).html(parseInt(numbers.last().text()) + 1) } else { $(element).html(1) } } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { $(element).html("") } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { // Change buttons var anchor = $(element).children("a") anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) } else if ($(element).hasClass("key")) { var input = $(element).children("input") input.show(); } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { // show inputs var input = $(element).find("input"); input.show(); var isCheckbox = input.hasClass("table-checkbox"); var isDropdown = input.hasClass("table-dropdown"); if (isArray) { var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length var key = $(element).attr('name'); // are there multiple columns or just one? // with multiple we have an array of Objects, with one we have an array of whatever the value type is var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length var newName = setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""); if (isCheckbox) { input.attr("name", newName) } else { if (isDropdown) { $(element).children("select").attr("data-hidden-input", newName); } input.attr("name", newName); } } else { // because the name of the setting in question requires the key // setup a hook to change the HTML name of the element whenever the key changes var colName = $(element).attr("name"); keyInput.on('change', function(){ input.attr("name", setting_name + "." + $(this).val() + "." + colName); }); } if (!focusChanged) { input.focus(); focusChanged = true; } // if we are adding a dropdown, we should go ahead and make its select // element is visible if (isDropdown) { $(element).children("select").attr("style", ""); } if (isCheckbox) { $(input).find("input").attr("data-changed", "true"); } else { input.attr("data-changed", "true"); } } else { console.log("Unknown table element"); } }); input_clone.children('td').each(function () { if ($(this).attr("name") !== keepField) { $(this).find("input").val($(this).children('input').attr('data-default')); } }); if (isArray) { updateDataChangedForSiblingRows(row, true) // the addition of any table row should remove the empty-array-row row.siblings('.empty-array-row').remove() } badgeSidebarForDifferences($(table)) row.after(input_clone) } function deleteTableRow($row) { var $table = $row.closest('table'); var categoryName = $row.data("category"); var isArray = $table.data('setting-type') === 'array'; $row.empty(); if (!isArray) { if ($row.attr('name')) { $row.html(""); } else { // for rows that didn't have a key, simply remove the row $row.remove(); } } else { if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { // This is the last row of the category, so delete the header $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); } if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { updateDataChangedForSiblingRows($row); // this isn't the last row - we can just remove it $row.remove(); } else { // this is the last row, we can't remove it completely since we need to post an empty array $row .removeClass(Settings.DATA_ROW_CLASS) .removeClass(Settings.NEW_ROW_CLASS) .removeAttr("data-category") .addClass('empty-array-row') .html(""); } } // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated badgeSidebarForDifferences($table); } function addTableCategory($categoryInputRow) { var $input = $categoryInputRow.find("input").first(); var categoryValue = $input.prop("value"); if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { $categoryInputRow.addClass("has-warning"); setTimeout(function () { $categoryInputRow.removeClass("has-warning"); }, 400); return; } var $rowInput = $categoryInputRow.next(".inputs").clone(); if (!$rowInput) { console.error("Error cloning inputs"); } var canAddRows = $categoryInputRow.data("can-add-rows"); var message = $categoryInputRow.data("message"); var categoryKey = $categoryInputRow.data("key"); var width = 0; $categoryInputRow .children("td") .each(function () { width += $(this).prop("colSpan") || 1; }); $input .prop("value", "") .focus(); $rowInput.find("td[name='" + categoryKey + "'] > input").first() .prop("value", categoryValue); $rowInput .attr("data-category", categoryValue) .addClass(Settings.NEW_ROW_CLASS); var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); $categoryInputRow .before($newCategoryRow) .before($rowInput); if (canAddRows) { $rowInput.removeAttr("hidden"); } else { addTableRow($rowInput); } } function deleteTableCategory($categoryHeaderRow) { var categoryName = $categoryHeaderRow.data("category"); $categoryHeaderRow .closest("table") .find("tr[data-category='" + categoryName + "']") .each(function () { if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { deleteTableRow($(this)); } else { $(this).remove(); } }); } function toggleTableCategory($categoryHeaderRow) { var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); var categoryName = $categoryHeaderRow.data("category"); var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); if (wasExpanded) { $icon .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); } else { $icon .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); } $categoryHeaderRow .closest("table") .find("tr[data-category='" + categoryName + "']") .toggleClass("contracted", wasExpanded); } function moveTableRow(row, move_up) { var table = $(row).closest('table') var isArray = table.data('setting-type') === 'array' if (!isArray) { return; } if (move_up) { var prev_row = row.prev() if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) { prev_row.before(row) } } else { var next_row = row.next() if (next_row.hasClass(Settings.DATA_ROW_CLASS)) { next_row.after(row) } } // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated badgeSidebarForDifferences($(table)) } function updateDataChangedForSiblingRows(row, forceTrue) { // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true // unless it matches the inital set of values if (!forceTrue) { // figure out which group this row is in var panelParentID = row.closest('.panel').attr('id') // get the short name for the setting from the table var tableShortName = row.closest('table').data('short-name') // get a JSON representation of that section var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] if (Settings.initialValues[panelParentID]) { var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] } else { var initialPanelSettingJSON = {}; } // if they are equal, we don't need data-changed isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON) } else { isTrue = true } row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') if (isTrue) { hiddenInput.attr('data-changed', isTrue) } else { hiddenInput.removeAttr('data-changed') } }) } function showRestartModal() { $('#restart-modal').modal({ backdrop: 'static', keyboard: false }); var secondsElapsed = 0; var numberOfSecondsToWait = 3; var refreshSpan = $('span#refresh-time') refreshSpan.html(numberOfSecondsToWait + " seconds"); // call ourselves every 1 second to countdown var refreshCountdown = setInterval(function(){ secondsElapsed++; secondsLeft = numberOfSecondsToWait - secondsElapsed refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds")) if (secondsElapsed == numberOfSecondsToWait) { location.reload(true); clearInterval(refreshCountdown); } }, 1000); } function cleanupFormValues(node) { if (node.type && node.type === 'checkbox') { return { name: node.name, value: node.checked ? true : false }; } else { return false; } } function showErrorMessage(title, message) { swal(title, message) }