initial separation of content settings and domain settings

This commit is contained in:
Stephen Birarda 2018-01-30 15:37:10 -08:00
parent 6187731fdc
commit 6240181474
20 changed files with 2485 additions and 2237 deletions

View file

@ -935,6 +935,7 @@
"name": "persistent_scripts",
"type": "table",
"label": "Persistent Scripts",
"content_setting": true,
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
"can_add_new_rows": true,
"columns": [
@ -1105,6 +1106,7 @@
"label": "Zones",
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
"numbered": false,
"content_setting": true,
"can_add_new_rows": true,
"key": {
"name": "name",
@ -1155,6 +1157,7 @@
"type": "table",
"label": "Attenuation Coefficients",
"help": "In this table you can set custom attenuation coefficients between audio zones",
"content_setting": true,
"numbered": true,
"can_order": true,
"can_add_new_rows": true,
@ -1185,6 +1188,7 @@
"label": "Reverb Settings",
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
"numbered": true,
"content_setting": true,
"can_add_new_rows": true,
"columns": [
{

View file

@ -0,0 +1,7 @@
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/form2js.min.js'></script>
<script src='/js/bootstrap-switch.min.js'></script>
<script src='/js/shared.js'></script>
<script src='/js/base-settings.js'></script>

View file

@ -0,0 +1,51 @@
<div class="col-md-10 col-md-offset-1 col-xs-12">
<div class="row">
<div class="col-xs-12">
<div class="alert" style="display:none;"></div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span class="alert-link">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
</span>
</div>
<form id="settings-form" role="form">
<script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% if (!group.hidden) { %>
<% var settings = _.partition(group.settings, function(value, index) { %>
<% return !value.deprecated %>
<% })[0] %>
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.html_id %>
<div id="<%- panelID %>_group" class="anchor"></div>
<div class="panel panel-default<%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
<span class="badge"></span>
</div>
<div class="panel-body">
<% _.each(settings, function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false) %>
<% }); %>
</div>
</div>
<% } %>
<% }); %>
</script>
<div id="panels"></div>
</form>
</div>
</div>
</div>

View file

@ -1,45 +1,19 @@
<!--#include virtual="header.html"-->
<div class="col-md-10 col-md-offset-1">
<div class="row">
<div class="col-xs-12">
<div class="alert" style="display:none;"></div>
</div>
</div>
<script type="text/javascript">
var Settings = {
content_settings: true,
endpoint: "/content-settings.json",
path: "/content/"
};
</script>
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Upload Entities File</h3>
</div>
<form id="upload-form" action="upload" enctype="multipart/form-data" method="post">
<div class="panel-body">
<p>
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
</p>
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
<label class="control-label">Windows</label>
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
<label class="control-label">OSX</label>
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
<label class="control-label">Linux</label>
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
<br>
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
<br>
</div>
<div class="panel-footer">
<input type="submit" class="btn btn-info" value="Upload">
</div>
</form>
</div>
</div>
</div>
</div>
<!--#include virtual="base-settings.html"-->
<!--#include virtual="footer.html"-->
<script src='js/content.js'></script>
<script src='/js/sweetalert.min.js'></script>
<!--#include virtual="base-settings-scripts.html"-->
<script src="js/content.js"></script>
<!--#include virtual="page-end.html"-->

View file

@ -1,5 +1,7 @@
$(document).ready(function(){
Settings.afterReloadActions = function() {};
function showSpinnerAlert(title) {
swal({
title: title,

View file

@ -18,6 +18,14 @@ body {
margin-top: 70px;
}
/* unfortunate hack so that anchors go to the right place with fixed navbar */
:target:before {
content: " ";
display: block;
height: 70px;
margin-top: -70px;
}
[hidden] {
display: none !important;
}
@ -345,3 +353,75 @@ table .headers + .headers td {
text-align: center;
margin-top: 20px;
}
@media (min-width: 768px) {
ul.nav li.dropdown-on-hover:hover ul.dropdown-menu {
display: block;
}
}
ul.nav li.dropdown ul.dropdown-menu {
padding: 0px 0px;
}
ul.nav li.dropdown li a {
padding-top: 7px;
padding-bottom: 7px;
}
ul.nav li.dropdown li a:hover {
color: white;
background-color: #337ab7;
}
ul.nav li.dropdown ul.dropdown-menu .divider {
margin: 0px 0;
}
#visit-domain-link {
background-color: transparent;
}
.navbar-btn {
margin-left: 10px;
}
#save-settings-xs-button {
float: right;
margin-right: 10px;
}
#button-bars {
display: inline-block;
float: left;
}
#hamburger-badge {
position: relative;
top: -2px;
float: left;
margin-right: 10px;
margin-left: 0px;
}
#restart-server {
margin-left: 0px;
}
#restart-server:hover {
text-decoration: none;
}
.badge {
margin-left: 5px;
}
.panel-title {
display: inline-block;
}
#visit-hmd-icon {
width: 25px;
position: relative;
top: -1px;
}

View file

@ -17,30 +17,47 @@
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#collapsed-navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span id="hamburger-badge" class="badge"></span>
<div id="button-bars">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</div>
</button>
<button id="save-settings-xs-button" class="save-button btn btn-success navbar-btn hidden-sm hidden-md hidden-lg" role="button" style="display: none;" disabled>Save</button>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<div class="collapse navbar-collapse" id="collapsed-navbar">
<ul class="nav navbar-nav">
<li><a href="/">Nodes</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/assignment">New Assignment</a></li>
</ul>
<li><a href="/assignment">Assignment</a></li>
<li class="dropdown dropdown-on-hover">
<a href="/content/" class="hidden-xs">Content <span class="content-settings-badge badge"></span> <span class="caret"></span></a>
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Content <span class="content-badge badge"></span> <span class="caret"></span></a>
<ul id="content-settings-nav-dropdown" class="dropdown-menu" role="menu">
</ul>
</li>
<li class="dropdown dropdown-on-hover">
<a href="/settings/" class="hidden-xs">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
<ul id="domain-settings-nav-dropdown" class="dropdown-menu" role="menu">
</ul>
</li>
<li><a href="/content/">Content</a></li>
<li><a href="/settings/">Settings</a></li>
</ul>
<ul class="nav navbar-right navbar-nav">
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
<a id="visit-domain-link" class="btn btn-default navbar-btn" role="button" target="_blank" style="display: none;">
<img id="visit-hmd-icon" src="/images/hmd-w-eyes.svg" alt="Head-mounted display" />
Visit in VR
</a>
<button id="save-settings-button" class="save-button btn btn-success navbar-btn hidden-xs" role="button" style="display: none;" disabled>Save</button>
<a href="#" id="restart-server" class="navbar-btn btn btn-link"><span class="glyphicon glyphicon-refresh"></span> Restart</a>
</ul>
</div>
</div><!-- /.container-fluid -->

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 456 244" style="enable-background:new 0 0 456 244;" xml:space="preserve">
<path d="M352.8,3.4h-250C49.6,3.4,6.3,46.2,6.3,98.9v44.9c0,52.7,43.3,95.5,96.5,95.5h67.6c19.4,0,31.9-17.9,42.9-33.6
c4.2-6.1,11.1-15.9,14.8-17.9c3.6,2.1,10.4,12.2,14.5,18.4c10.4,15.5,22.1,33.1,40.3,33.1h69.9c53.3,0,96.5-42.9,96.5-95.5V98.9
C449.3,46.2,406,3.4,352.8,3.4z M129.6,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
S151.9,157.6,129.6,157.6z M328.4,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
S350.8,157.6,328.4,157.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 862 B

File diff suppressed because it is too large Load diff

View file

@ -23,16 +23,22 @@ function showRestartModal() {
}, 1000);
}
function settingsGroupAnchor(base, html_id) {
return base + "#" + html_id + "_group"
}
$(document).ready(function(){
var url = window.location;
// Will only work if string in href matches with location
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
// Will also work for relative and absolute hrefs
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e) {
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e) {
swal( {
title: "Are you sure?",
text: "This will restart your domain server, causing your domain to be briefly offline.",
@ -45,4 +51,35 @@ $(document).ready(function(){
});
return false;
});
var $contentDropdown = $('#content-settings-nav-dropdown');
var $settingsDropdown = $('#domain-settings-nav-dropdown');
// for pages that have the settings dropdowns
if ($contentDropdown.length && $settingsDropdown.length) {
// make a JSON request to get the dropdown menus for content and settings
// we don't error handle here because the top level menu is still clickable and usables if this fails
$.getJSON('/settings-menu-groups.json', function(data){
function makeGroupDropdownElement(group, base) {
var html_id = group.html_id ? group.html_id : group.name;
return "<li class='setting-group'><a href='" + settingsGroupAnchor(base, html_id) + "'>" + group.label + "<span class='badge'></span></a></li>";
}
$.each(data.content_settings, function(index, group){
if (index > 0) {
$contentDropdown.append("<li role='separator' class='divider'></li>");
}
$contentDropdown.append(makeGroupDropdownElement(group, "/content/"));
});
$.each(data.domain_settings, function(index, group){
if (index > 0) {
$settingsDropdown.append("<li role='separator' class='divider'></li>");
}
$settingsDropdown.append(makeGroupDropdownElement(group, "/settings/"));
});
});
}
});

View file

@ -1,6 +1,4 @@
var Settings = {
showAdvanced: false,
ADVANCED_CLASS: 'advanced-setting',
Object.assign(Settings, {
DEPRECATED_CLASS: 'deprecated-setting',
TRIGGER_CHANGE_CLASS: 'trigger-change',
DATA_ROW_CLASS: 'value-row',
@ -41,7 +39,7 @@ var Settings = {
FORM_ID: 'settings-form',
INVALID_ROW_CLASS: 'invalid-input',
DATA_ROW_INDEX: 'data-row-index'
};
});
var URLs = {
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com

File diff suppressed because one or more lines are too long

View file

@ -1,104 +1,21 @@
<!--#include virtual="header.html"-->
<div class="col-md-10 col-md-offset-1">
<div class="row">
<div class="col-md-12">
<div class="alert" style="display:none;"></div>
</div>
</div>
<script type="text/javascript">
var Settings = {
content_settings: false,
endpoint: "/settings.json",
path: "/settings/"
};
</script>
<div class="row">
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col">
<script id="list-group-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% if (!group.hidden) { %>
<% panelID = group.name ? group.name : group.html_id %>
<li>
<a href="#<%- panelID %>" class="list-group-item">
<span class="badge"></span>
<%- group.label %>
</a>
</li>
<% } %>
<% }); %>
</script>
<ul class="nav nav-pills nav-stacked">
</ul>
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
<button class="btn btn-success save-button" disabled>Save</button>
<div id="manage-cloud-domains-link" style="display: none;">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
</div>
</div>
</div>
<div class="col-md-9 col-sm-9 col-xs-12">
<div class="col-xs-12">
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span class="alert-link">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
</span>
</div>
<form id="settings-form" role="form">
<script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% if (!group.hidden) { %>
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
<% if (isAdvanced) { %>
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
<% } %>
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.html_id %>
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% _.each(split_settings[0], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false) %>
<% }); %>
<% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true) %>
<% }); %>
<% }%>
</div>
</div>
<% } %>
<% }); %>
</script>
<div id="panels"></div>
</form>
</div>
</div>
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
<button class="btn btn-success save-button" id="small-save-button">Save</button>
</div>
</div>
<!--#include virtual="base-settings.html"-->
<!--#include virtual="footer.html"-->
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/sha256.js'></script>
<script src='js/form2js.min.js'></script>
<script src='js/bootstrap-switch.min.js'></script>
<script src='/js/shared.js'></script>
<script src='js/settings.js'></script>
<!--#include virtual="base-settings-scripts.html"-->
<script src="js/settings.js"></script>
<!--#include virtual="page-end.html"-->

File diff suppressed because it is too large Load diff

View file

@ -501,7 +501,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
// store the new domain ID and auto network setting immediately
QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id);
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object());
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings);
// store the new ID and auto networking setting on disk
_settingsManager.persistToFile();

View file

@ -39,8 +39,10 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
const QString DESCRIPTION_GROUP_LABEL_KEY = "label";
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
const QString DESCRIPTION_COLUMNS_KEY = "columns";
const QString CONTENT_SETTING_FLAG_KEY = "content_setting";
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
@ -63,6 +65,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
splitSettingsDescription();
return;
}
}
@ -78,6 +82,91 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE));
}
void DomainServerSettingsManager::splitSettingsDescription() {
// construct separate description arrays for domain settings and content settings
// since they are displayed on different pages
// along the way we also construct one object that holds the groups separated by domain settings
// and content settings, so that the DS can setup dropdown menus below "Content" and "Settings"
// headers to jump directly to a settings group on the page of either
QJsonArray domainSettingsMenuGroups;
QJsonArray contentSettingsMenuGroups;
foreach(const QJsonValue& group, _descriptionArray) {
QJsonObject groupObject = group.toObject();
static const QString HIDDEN_GROUP_KEY = "hidden";
bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool();
QJsonArray domainSettingArray;
QJsonArray contentSettingArray;
foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
QJsonObject settingDescriptionObject = settingDescription.toObject();
bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY)
&& settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool();
if (isContentSetting) {
// push the setting description to the pending content setting array
contentSettingArray.push_back(settingDescriptionObject);
} else {
// push the setting description to the pending domain setting array
domainSettingArray.push_back(settingDescriptionObject);
}
}
if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) {
// we know for sure we'll have something to add to our settings menu groups
// so setup that object for the group now, as long as the group isn't hidden alltogether
QJsonObject settingsDropdownGroup;
if (!groupHidden) {
settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY];
settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY];
static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id";
if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) {
settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY];
}
}
if (!domainSettingArray.isEmpty()) {
// we have some domain settings from this group, add the group with the filtered settings
QJsonObject filteredGroupObject = groupObject;
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray;
_domainSettingsDescription.push_back(filteredGroupObject);
// if the group isn't hidden, add its information to the domain settings menu groups
if (!groupHidden) {
domainSettingsMenuGroups.push_back(settingsDropdownGroup);
}
}
if (!contentSettingArray.isEmpty()) {
// we have some content settings from this group, add the group with the filtered settings
QJsonObject filteredGroupObject = groupObject;
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray;
_contentSettingsDescription.push_back(filteredGroupObject);
// if the group isn't hidden, add its information to the content settings menu groups
if (!groupHidden) {
contentSettingsMenuGroups.push_back(settingsDropdownGroup);
}
}
}
}
// populate the settings menu groups with what we've collected
static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
_settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups;
_settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;
}
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) {
Assignment::Type type;
message->readPrimitive(&type);
@ -986,48 +1075,72 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
}
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
if (connection->requestOperation() == QNetworkAccessManager::PostOperation) {
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
SettingsType endpointType = url.path() == SETTINGS_PATH_JSON ? DomainSettings : ContentSettings;
// store whatever the current _settingsMap is to file
persistToFile();
// we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
// store whatever the current _settingsMap is to file
persistToFile();
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
if (restartRequired) {
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
} else {
unpackPermissions();
apiRefreshGroupInformation();
emit updateNodePermissions();
emit settingsUpdated();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
if (restartRequired) {
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
} else {
unpackPermissions();
apiRefreshGroupInformation();
emit updateNodePermissions();
emit settingsUpdated();
}
return true;
}
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
static const QString SETTINGS_MENU_GROUPS_PATH = "/settings-menu-groups.json";
return true;
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
QJsonObject rootObject;
bool forDomainSettings = (url.path() == SETTINGS_PATH_JSON);
bool forContentSettings = (url.path() == CONTENT_SETTINGS_PATH_JSON);;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = forDomainSettings
? _domainSettingsDescription : _contentSettingsDescription;
// grab a domain settings object for all types, filtered for the right class of settings
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true, forDomainSettings, forContentSettings);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
return true;
} else if (url.path() == SETTINGS_MENU_GROUPS_PATH) {
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json");
return true;
}
}
return false;
}
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated,
bool includeDomainSettings, bool includeContentSettings) {
QJsonObject responseObject;
if (!typeValue.isEmpty() || isAuthenticated) {
@ -1036,8 +1149,15 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
QJsonArray& filteredDescriptionArray = _descriptionArray;
if (includeDomainSettings && !includeContentSettings) {
filteredDescriptionArray = _domainSettingsDescription;
} else if (includeContentSettings && !includeDomainSettings) {
filteredDescriptionArray = _contentSettingsDescription;
}
// enumerate the groups in the description object to find which settings to pass
foreach(const QJsonValue& groupValue, _descriptionArray) {
foreach(const QJsonValue& groupValue, filteredDescriptionArray) {
QJsonObject groupObject = groupValue.toObject();
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
@ -1045,10 +1165,13 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
QJsonObject groupResponseObject;
foreach(const QJsonValue& settingValue, groupSettingsArray) {
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
QJsonObject settingObject = settingValue.toObject();
// consider this setting as long as it isn't hidden
// and we've been asked to include this type (domain setting or content setting)
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
if (affectedTypesArray.isEmpty()) {
@ -1212,7 +1335,8 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
return QJsonObject();
}
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
SettingsType settingsType) {
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
@ -1222,6 +1346,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
auto& settingsVariant = _configMap.getConfig();
bool needRestart = false;
auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription;
// Iterate on the setting groups
foreach(const QString& rootKey, postedObject.keys()) {
const QJsonValue& rootValue = postedObject[rootKey];
@ -1236,7 +1362,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
QJsonObject groupDescriptionObject;
// we need to check the description array to see if this is a root setting or a group setting
foreach(const QJsonValue& groupValue, _descriptionArray) {
foreach(const QJsonValue& groupValue, filteredDescriptionArray) {
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
// we matched a group - keep this since we'll use it below to update the settings
groupDescriptionObject = groupValue.toObject();
@ -1269,6 +1395,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
needRestart = true;
@ -1286,6 +1413,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
settingKey == AC_SUBNET_WHITELIST_KEY) {

View file

@ -13,6 +13,7 @@
#define hifi_DomainServerSettingsManager_h
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply>
@ -28,6 +29,7 @@ const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
@ -38,6 +40,10 @@ const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
enum SettingsType {
DomainSettings,
ContentSettings
};
class DomainServerSettingsManager : public QObject {
Q_OBJECT
@ -123,8 +129,10 @@ private slots:
private:
QStringList _argumentList;
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
QJsonArray filteredDescriptionArray(bool isContentSettings);
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false,
bool includeDomainSettings = true, bool includeContentSettings = true);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);
@ -132,8 +140,15 @@ private:
void sortPermissions();
void persistToFile();
void splitSettingsDescription();
double _descriptionVersion;
QJsonArray _descriptionArray;
QJsonArray _domainSettingsDescription;
QJsonArray _contentSettingsDescription;
QJsonObject _settingsMenuGroups;
HifiConfigVariantMap _configMap;
friend class DomainServer;

View file

@ -79,7 +79,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
QHash<QByteArray, QByteArray> redirectHeader;
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader);
}
// if the last thing is a trailing slash then we want to look for index file