diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 0fb9d25b5f..d4e45e0204 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -6,11 +6,11 @@ setup_hifi_project(Network) # remove and then copy the files for the webserver add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E remove_directory - $/resources/web) + $/resources) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/resources/web" - $/resources/web) + "${PROJECT_SOURCE_DIR}/resources" + $/resources) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) diff --git a/domain-server/resources/web/settings/describe-settings.json b/domain-server/resources/describe-settings.json similarity index 55% rename from domain-server/resources/web/settings/describe-settings.json rename to domain-server/resources/describe-settings.json index fee7ff21fc..16cea0a585 100644 --- a/domain-server/resources/web/settings/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,74 +1,119 @@ -{ - "audio": { +[ + { + "name": "metaverse", + "label": "Metaverse Registration", + "settings": [ + { + "name": "access-token", + "label": "High Fidelity Access Token", + "help": "This is an access token generated on the My Tokens page of your High Fidelity account.
Generate a token with the 'domains' scope and paste it here.
This is required to associate this domain-server with a domain in your account." + }, + { + "name": "id", + "label": "Domain ID", + "help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank." + } + ] + }, + { + "name": "basic", + "label": "Basic", + "settings": [ + { + "name": "http-username", + "label": "HTTP Username", + "help": "Username used for basic HTTP authentication" + }, + { + "name": "http-password", + "label": "HTTP Password", + "type": "password", + "help": "Password used for basic HTTP authentication" + } + ] + }, + { + "name": "audio", "label": "Audio", "assignment-types": [0], - "settings": { - "A-dynamic-jitter-buffer": { + "settings": [ + { + "name": "enable-filter", "type": "checkbox", - "label": "Dynamic Jitter Buffers", - "help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing", + "label": "Enable Positional Filter", + "help": "If enabled, positional audio stream uses lowpass filter", "default": false }, - "B-static-desired-jitter-buffer-frames": { - "label": "Static Desired Jitter Buffer Frames", - "help": "If dynamic jitter buffers is disabled, this determines the target number of frames maintained by the AudioMixer's jitter buffers", - "placeholder": "1", - "default": "1" - }, - "C-max-frames-over-desired": { - "label": "Max Frames Over Desired", - "help": "The highest number of frames an AudioMixer's ringbuffer can exceed the desired jitter buffer frames by", - "placeholder": "10", - "default": "10" - }, - "D-use-stdev-for-desired-calc": { - "type": "checkbox", - "label": "Use Stdev for Desired Jitter Frames Calc:", - "help": "If checked, Philip's method (stdev of timegaps) is used to calculate desired jitter frames. Otherwise, Fred's method (max timegap) is used", - "default": false - }, - "E-window-starve-threshold": { - "label": "Window Starve Threshold", - "help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.", - "placeholder": "3", - "default": "3" - }, - "F-window-seconds-for-desired-calc-on-too-many-starves": { - "label": "Timegaps Window (A) Seconds:", - "help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.", - "placeholder": "50", - "default": "50" - }, - "G-window-seconds-for-desired-reduction": { - "label": "Timegaps Window (B) Seconds:", - "help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.", - "placeholder": "10", - "default": "10" - }, - "H-repetition-with-fade": { - "type": "checkbox", - "label": "Repetition with Fade:", - "help": "If enabled, dropped frames and mixing during starves will repeat the last frame, eventually fading to silence", - "default": false - }, - "I-print-stream-stats": { - "type": "checkbox", - "label": "Print Stream Stats:", - "help": "If enabled, audio upstream and downstream stats of each agent will be printed each second to stdout", - "default": false - }, - "Z-unattenuated-zone": { + { + "name": "unattenuated-zone", "label": "Unattenuated Zone", "help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)", "placeholder": "no zone", "default": "" }, - "J-enable-filter": { - "type": "checkbox", - "label": "Enable Positional Filter", - "help": "If enabled, positional audio stream uses lowpass filter", + { + "name": "dynamic-jitter-buffer", + "type": "checkbox", + "label": "Dynamic Jitter Buffers", + "help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing", "default": false - } - } + }, + { + "name": "static-desired-jitter-buffer-frames", + "label": "Static Desired Jitter Buffer Frames", + "help": "If dynamic jitter buffers is disabled, this determines the target number of frames maintained by the AudioMixer's jitter buffers", + "placeholder": "1", + "default": "1" + }, + { + "name": "max-frames-over-desired", + "label": "Max Frames Over Desired", + "help": "The highest number of frames an AudioMixer's ringbuffer can exceed the desired jitter buffer frames by", + "placeholder": "10", + "default": "10" + }, + { + "name": "use-stdev-for-desired-calc", + "type": "checkbox", + "label": "Use Stdev for Desired Jitter Frames Calc:", + "help": "If checked, Philip's method (stdev of timegaps) is used to calculate desired jitter frames. Otherwise, Fred's method (max timegap) is used", + "default": false + }, + { + "name": "window-starve-threshold", + "label": "Window Starve Threshold", + "help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.", + "placeholder": "3", + "default": "3" + }, + { + "name": "window-seconds-for-desired-calc-on-too-many-starves", + "label": "Timegaps Window (A) Seconds:", + "help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.", + "placeholder": "50", + "default": "50" + }, + { + "name": "window-seconds-for-desired-reduction", + "label": "Timegaps Window (B) Seconds:", + "help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.", + "placeholder": "10", + "default": "10" + }, + { + "name": "repetition-with-fade", + "type": "checkbox", + "label": "Repetition with Fade:", + "help": "If enabled, dropped frames and mixing during starves will repeat the last frame, eventually fading to silence", + "default": false + }, + { + "name": "I-print-stream-stats", + "type": "checkbox", + "label": "Print Stream Stats:", + "help": "If enabled, audio upstream and downstream stats of each agent will be printed each second to stdout", + "default": false + } + ] } -} +] \ No newline at end of file diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index f3c2f6d209..5abf8fa028 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -32,8 +32,6 @@
  • Settings
  • -
  • Setup
  • - diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index 487ec5b296..224ed37311 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -1,15 +1,40 @@ var Settings = {}; $(document).ready(function(){ - var source = $('#settings-template').html(); - Settings.template = _.template(source); + /* + * 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); + }); + + + var panelsSource = $('#panels-template').html(); + Settings.panelsTemplate = _.template(panelsSource); + + var sidebarTemplate = $('#list-group-template').html(); + Settings.sidebarTemplate = _.template(sidebarTemplate) reloadSettings(); }); function reloadSettings() { $.getJSON('/settings.json', function(data){ - $('#settings').html(Settings.template(data)); + $('.list-group').html(Settings.sidebarTemplate(data)) + $('#panels').html(Settings.panelsTemplate(data)); }); } diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index bd3e00eb61..e97071a25f 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -1,50 +1,73 @@ -

    Settings

    -
    - -
    + +
    +
    + +
    - + +
    -
    - <% _.each(group.settings, function(setting, setting_key){ %> -
    - <% var setting_id = group_key + "." + setting_key %> - -
    - <% if (setting.type === "checkbox") { %> - <% var checked_box = _.has(values, group_key) ? values[group_key][setting_key] : setting.default %> - > - <% } else { %> - <% if (setting.input_addon) { %> -
    -
    <%- setting.input_addon %>
    - <% } %> - - - <% if (setting.input_addon) { %> -
    - <% } %> - <% } %> + + +
    +
    + +
    + + + + + +
    + +
    +
    -
    - - diff --git a/domain-server/resources/web/setup/describe-setup.json b/domain-server/resources/web/setup/describe-setup.json deleted file mode 100644 index 95370a5ff7..0000000000 --- a/domain-server/resources/web/setup/describe-setup.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "descriptions": { - "basic": { - "label": "Basic", - "settings": { - "http-username": { - "label": "HTTP Username", - "help": "Username used for basic HTTP authentication" - }, - "http-password": { - "label": "HTTP Password", - "type": "password", - "help": "Password used for basic HTTP authentication" - } - } - }, - "metaverse": { - "label": "Metaverse Registration", - "settings": { - "access-token": { - "label": "High Fidelity Access Token", - "help": "This is an access token generated on the My Tokens page of your High Fidelity account.
    Generate a token with the 'domains' scope and paste it here.
    This is required to associate this domain-server with a domain in your account." - }, - "id": { - "label": "Domain ID", - "help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank." - } - } - } - } -} diff --git a/domain-server/resources/web/setup/index.shtml b/domain-server/resources/web/setup/index.shtml deleted file mode 100644 index 0abda73e16..0000000000 --- a/domain-server/resources/web/setup/index.shtml +++ /dev/null @@ -1,75 +0,0 @@ - -
    -
    - -
    - -
    -
    -
    -
    -

    Metaverse Registration

    -
    -
    -
    - - - - This is an access token generated on the My Tokens page of your High Fidelity account.
    - Generate a token with the 'domains' scope and paste it here.
    - This is required to associate this domain-server with a domain in your. -
    -
    -
    - - - This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank. - -
    -
    -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 746d775beb..b13e8eb38c 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -21,18 +21,18 @@ #include "DomainServerSettingsManager.h" -const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/web/settings/describe-settings.json"; +const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; const QString SETTINGS_JSON_FILE_RELATIVE_PATH = "/resources/settings.json"; DomainServerSettingsManager::DomainServerSettingsManager() : - _descriptionObject(), + _descriptionArray(), _settingsMap() { // load the description object from the settings description QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); descriptionFile.open(QIODevice::ReadOnly); - _descriptionObject = QJsonDocument::fromJson(descriptionFile.readAll()).object(); + _descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array(); // load the existing config file to get the current values QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH); @@ -46,6 +46,7 @@ DomainServerSettingsManager::DomainServerSettingsManager() : const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; +const QString SETTINGS_GROUP_KEY_NAME = "key"; bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) { if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") { @@ -60,7 +61,7 @@ bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connec if (typeValue.isEmpty()) { // combine the description object and our current settings map - responseObject["descriptions"] = _descriptionObject; + responseObject["descriptions"] = _descriptionArray; responseObject["values"] = QJsonDocument::fromVariant(_settingsMap).object(); } else { // convert the string type value to a QJsonValue @@ -69,8 +70,9 @@ bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connec const QString AFFECTED_TYPES_JSON_KEY = "assignment-types"; // enumerate the groups in the description object to find which settings to pass - foreach(const QString& group, _descriptionObject.keys()) { - QJsonObject groupObject = _descriptionObject[group].toObject(); + foreach(const QJsonValue& groupValue, _descriptionArray) { + QJsonObject groupObject = groupValue.toObject(); + QString groupKey = groupObject[SETTINGS_GROUP_KEY_NAME].toString(); QJsonObject groupSettingsObject = groupObject[DESCRIPTION_SETTINGS_KEY].toObject(); QJsonObject groupResponseObject; @@ -89,7 +91,7 @@ bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connec // we need to check if the settings map has a value for this setting QVariant variantValue; - QVariant settingsMapGroupValue = _settingsMap.value(group); + QVariant settingsMapGroupValue = _settingsMap.value(groupObject[SETTINGS_GROUP_KEY_NAME].toString()); if (!settingsMapGroupValue.isNull()) { variantValue = settingsMapGroupValue.toMap().value(settingKey); @@ -106,7 +108,7 @@ bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connec if (!groupResponseObject.isEmpty()) { // set this group's object to the constructed object - responseObject[group] = groupResponseObject; + responseObject[groupKey] = groupResponseObject; } } @@ -126,7 +128,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection QJsonObject postedObject = postedDocument.object(); // we recurse one level deep below each group for the appropriate setting - recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject); + recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionArray); // store whatever the current _settingsMap is to file persistToFile(); @@ -145,46 +147,49 @@ const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant, - QJsonObject descriptionObject) { + QJsonArray descriptionArray) { foreach(const QString& key, postedObject.keys()) { QJsonValue rootValue = postedObject[key]; // we don't continue if this key is not present in our descriptionObject - if (descriptionObject.contains(key)) { - if (rootValue.isString()) { - if (rootValue.toString().isEmpty()) { - // this is an empty value, clear it in settings variant so the default is sent - settingsVariant.remove(key); - } else { - if (descriptionObject[key].toObject().contains(SETTING_DESCRIPTION_TYPE_KEY)) { - // for now this means that this is a double, so set it as a double - settingsVariant[key] = rootValue.toString().toDouble(); - } else { - settingsVariant[key] = rootValue.toString(); - } - } - } else if (rootValue.isBool()) { - settingsVariant[key] = rootValue.toBool(); - } else if (rootValue.isObject()) { - // there's a JSON Object to explore, so attempt to recurse into it - QJsonObject nextDescriptionObject = descriptionObject[key].toObject(); - - if (nextDescriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { - if (!settingsVariant.contains(key)) { - // we don't have a map below this key yet, so set it up now - settingsVariant[key] = QVariantMap(); - } - - QVariantMap& thisMap = *reinterpret_cast(settingsVariant[key].data()); - - recurseJSONObjectAndOverwriteSettings(rootValue.toObject(), - thisMap, - nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toObject()); - - if (thisMap.isEmpty()) { - // we've cleared all of the settings below this value, so remove this one too + foreach(const QJsonValue& groupValue, descriptionArray) { + if (groupValue.toObject()[SETTINGS_GROUP_KEY_NAME].toString() == key) { + QJsonObject groupObject = groupValue.toObject(); + if (rootValue.isString()) { + if (rootValue.toString().isEmpty()) { + // this is an empty value, clear it in settings variant so the default is sent settingsVariant.remove(key); + } else { + if (groupObject.contains(SETTING_DESCRIPTION_TYPE_KEY)) { + // for now this means that this is a double, so set it as a double + settingsVariant[key] = rootValue.toString().toDouble(); + } else { + settingsVariant[key] = rootValue.toString(); + } + } + } else if (rootValue.isBool()) { + settingsVariant[key] = rootValue.toBool(); + } else if (rootValue.isObject()) { + // there's a JSON Object to explore, so attempt to recurse into it + QJsonObject nextDescriptionObject = groupObject; + + if (nextDescriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { + if (!settingsVariant.contains(key)) { + // we don't have a map below this key yet, so set it up now + settingsVariant[key] = QVariantMap(); + } + + QVariantMap& thisMap = *reinterpret_cast(settingsVariant[key].data()); + + recurseJSONObjectAndOverwriteSettings(rootValue.toObject(), + thisMap, + nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toArray()); + + if (thisMap.isEmpty()) { + // we've cleared all of the settings below this value, so remove this one too + settingsVariant.remove(key); + } } } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 26bfe57ab4..9fd9df908c 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -12,6 +12,7 @@ #ifndef hifi_DomainServerSettingsManager_h #define hifi_DomainServerSettingsManager_h +#include #include #include @@ -26,10 +27,10 @@ public: QByteArray getJSONSettingsMap() const; private: void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant, - QJsonObject descriptionObject); + QJsonArray descriptionArray); void persistToFile(); - QJsonObject _descriptionObject; + QJsonArray _descriptionArray; QVariantMap _settingsMap; };