summaryrefslogtreecommitdiffstats
path: root/apps/files_external
diff options
context:
space:
mode:
authorRobin McCorkell <rmccorkell@karoshi.org.uk>2015-03-26 21:36:34 +0000
committerRobin McCorkell <rmccorkell@karoshi.org.uk>2015-03-26 21:36:34 +0000
commitc2909355bf66c92e46c9fbea39497ba91dbe37a0 (patch)
treec832f4ba40ba1728ddd82bc5684ca6b4aa9eb3b6 /apps/files_external
parentfc59a37ae7e28ad8ef7a0a383e87024ff942408d (diff)
parent58b4c2c0e53ff5ef6e261426ecb2b94d9791da71 (diff)
downloadnextcloud-server-c2909355bf66c92e46c9fbea39497ba91dbe37a0.tar.gz
nextcloud-server-c2909355bf66c92e46c9fbea39497ba91dbe37a0.zip
Merge pull request #14925 from owncloud/ext-mountoptions-ui
Mount options GUI for external storage
Diffstat (limited to 'apps/files_external')
-rw-r--r--apps/files_external/css/settings.css13
-rw-r--r--apps/files_external/js/settings.js179
-rw-r--r--apps/files_external/lib/config.php17
-rw-r--r--apps/files_external/templates/settings.php24
-rw-r--r--apps/files_external/tests/js/settingsSpec.js88
-rw-r--r--apps/files_external/tests/mountconfig.php51
6 files changed, 345 insertions, 27 deletions
diff --git a/apps/files_external/css/settings.css b/apps/files_external/css/settings.css
index 93689f78c52..fb7789a537d 100644
--- a/apps/files_external/css/settings.css
+++ b/apps/files_external/css/settings.css
@@ -6,10 +6,11 @@ td.status > span {
}
td.mountPoint, td.backend { width:160px; }
-td.remove>img { visibility:hidden; padding-top:7px; }
-tr:hover>td.remove>img { visibility:visible; cursor:pointer; }
+#externalStorage td>img.action { visibility:hidden; padding-top:7px; }
+#externalStorage tr:hover>td>img.action { visibility:visible; cursor:pointer; }
#addMountPoint>td { border:none; }
#addMountPoint>td.applicable { visibility:hidden; }
+#addMountPoint>td.hidden { visibility:hidden; }
#selectBackend {
margin-left: -10px;
@@ -67,3 +68,11 @@ tr:hover>td.remove>img { visibility:visible; cursor:pointer; }
top: 6px;
position: relative;
}
+
+#externalStorage .mountOptionsToggle .dropdown {
+ width: auto;
+}
+
+#externalStorage .mountOptionsDropdown {
+ margin-right: 40px;
+}
diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js
index 254bab30c56..ae3aef4ef35 100644
--- a/apps/files_external/js/settings.js
+++ b/apps/files_external/js/settings.js
@@ -7,8 +7,27 @@
* See the COPYING-README file.
*
*/
+
(function(){
+// TODO: move to a separate file
+var MOUNT_OPTIONS_DROPDOWN_TEMPLATE =
+ '<div class="drop dropdown mountOptionsDropdown">' +
+ // FIXME: options are hard-coded for now
+ ' <div class="optionRow">' +
+ ' <label for="mountOptionsPreviews">{{t "files_external" "Enable previews"}}</label>' +
+ ' <input id="mountOptionsPreviews" name="previews" type="checkbox" value="true" checked="checked"/>' +
+ ' </div>' +
+ ' <div class="optionRow">' +
+ ' <label for="mountOptionsFilesystemCheck">{{t "files_external" "Check for changes"}}</label>' +
+ ' <select id="mountOptionsFilesystemCheck" name="filesystem_check_changes" data-type="int">' +
+ ' <option value="0">{{t "files_external" "Never"}}</option>' +
+ ' <option value="1" selected="selected">{{t "files_external" "Once every direct access"}}</option>' +
+ ' <option value="2">{{t "files_external" "Every time the filesystem is used"}}</option>' +
+ ' </select>' +
+ ' </div>' +
+ '</div>';
+
/**
* Returns the selection of applicable users in the given configuration row
*
@@ -219,7 +238,8 @@ StorageConfig.prototype = {
$.ajax({
type: method,
url: url,
- data: this.getData(),
+ contentType: 'application/json',
+ data: JSON.stringify(this.getData()),
success: function(result) {
self.id = result.id;
if (_.isFunction(options.success)) {
@@ -285,7 +305,6 @@ StorageConfig.prototype = {
}
return;
}
- var self = this;
$.ajax({
type: 'DELETE',
url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
@@ -379,6 +398,110 @@ UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
});
/**
+ * @class OCA.External.Settings.MountOptionsDropdown
+ *
+ * @classdesc Dropdown for mount options
+ *
+ * @param {Object} $container container DOM object
+ */
+var MountOptionsDropdown = function() {
+};
+/**
+ * @memberof OCA.External.Settings
+ */
+MountOptionsDropdown.prototype = {
+ /**
+ * Dropdown element
+ *
+ * @var Object
+ */
+ $el: null,
+
+ /**
+ * Show dropdown
+ *
+ * @param {Object} $container container
+ * @param {Object} mountOptions mount options
+ */
+ show: function($container, mountOptions) {
+ if (MountOptionsDropdown._last) {
+ MountOptionsDropdown._last.hide();
+ }
+
+ var template = MountOptionsDropdown._template;
+ if (!template) {
+ template = Handlebars.compile(MOUNT_OPTIONS_DROPDOWN_TEMPLATE);
+ MountOptionsDropdown._template = template;
+ }
+
+ var $el = $(template());
+ this.$el = $el;
+ $el.addClass('hidden');
+
+ this.setOptions(mountOptions);
+
+ this.$el.appendTo($container);
+ MountOptionsDropdown._last = this;
+
+ this.$el.trigger('show');
+ },
+
+ hide: function() {
+ if (this.$el) {
+ this.$el.trigger('hide');
+ this.$el.remove();
+ this.$el = null;
+ MountOptionsDropdown._last = null;
+ }
+ },
+
+ /**
+ * Returns the mount options from the dropdown controls
+ *
+ * @return {Object} options mount options
+ */
+ getOptions: function() {
+ var options = {};
+
+ this.$el.find('input, select').each(function() {
+ var $this = $(this);
+ var key = $this.attr('name');
+ var value = null;
+ if ($this.attr('type') === 'checkbox') {
+ value = $this.prop('checked');
+ } else {
+ value = $this.val();
+ }
+ if ($this.attr('data-type') === 'int') {
+ value = parseInt(value, 10);
+ }
+ options[key] = value;
+ });
+ return options;
+ },
+
+ /**
+ * Sets the mount options to the dropdown controls
+ *
+ * @param {Object} options mount options
+ */
+ setOptions: function(options) {
+ var $el = this.$el;
+ _.each(options, function(value, key) {
+ var $optionEl = $el.find('input, select').filterAttr('name', key);
+ if ($optionEl.attr('type') === 'checkbox') {
+ if (_.isString(value)) {
+ value = (value === 'true');
+ }
+ $optionEl.prop('checked', !!value);
+ } else {
+ $optionEl.val(value);
+ }
+ });
+ }
+};
+
+/**
* @class OCA.External.Settings.MountConfigListView
*
* @classdesc Mount configuration list view
@@ -503,12 +626,20 @@ MountConfigListView.prototype = {
self.deleteStorageConfig($(this).closest('tr'));
});
+ this.$el.on('click', 'td.mountOptionsToggle>img', function() {
+ self._showMountOptionsDropdown($(this).closest('tr'));
+ });
+
this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
},
_onChange: function(event) {
var self = this;
var $target = $(event.target);
+ if ($target.closest('.dropdown').length) {
+ // ignore dropdown events
+ return;
+ }
highlightInput($target);
var $tr = $target.closest('tr');
@@ -569,6 +700,7 @@ MountConfigListView.prototype = {
}
});
$tr.find('td').last().attr('class', 'remove');
+ $tr.find('td.mountOptionsToggle').removeClass('hidden');
$tr.find('td').last().removeAttr('style');
$tr.removeAttr('id');
$target.remove();
@@ -643,7 +775,7 @@ MountConfigListView.prototype = {
storage.applicableUsers = users;
storage.applicableGroups = groups;
- storage.priority = $tr.find('input.priority').val();
+ storage.priority = parseInt($tr.find('input.priority').val() || '100', 10);
}
var mountOptions = $tr.find('input.mountOptions').val();
@@ -786,6 +918,47 @@ MountConfigListView.prototype = {
}
}
return defaultMountPoint + append;
+ },
+
+ /**
+ * Toggles the mount options dropdown
+ *
+ * @param {Object} $tr configuration row
+ */
+ _showMountOptionsDropdown: function($tr) {
+ if (this._preventNextDropdown) {
+ // prevented because the click was on the toggle
+ this._preventNextDropdown = false;
+ return;
+ }
+ var self = this;
+ var storage = this.getStorageConfig($tr);
+ var $toggle = $tr.find('.mountOptionsToggle');
+ var dropDown = new MountOptionsDropdown();
+ dropDown.show($toggle, storage.mountOptions || []);
+
+ $('body').on('mouseup.mountOptionsDropdown', function(event) {
+ var $target = $(event.target);
+ if ($toggle.has($target).length) {
+ // why is it always so hard to make dropdowns behave ?
+ // this prevents the click on the toggle to cause
+ // the dropdown to reopen itself
+ // (preventDefault doesn't work here because the click
+ // event is already in the queue and cannot be cancelled)
+ self._preventNextDropdown = true;
+ }
+ if ($target.closest('.dropdown').length) {
+ return;
+ }
+ dropDown.hide();
+ });
+
+ dropDown.$el.on('hide', function() {
+ var mountOptions = dropDown.getOptions();
+ $('body').off('mouseup.mountOptionsDropdown');
+ $tr.find('input.mountOptions').val(JSON.stringify(mountOptions));
+ self.saveStorageConfig($tr);
+ });
}
};
diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php
index 103d98cd1bf..40e9a75790f 100644
--- a/apps/files_external/lib/config.php
+++ b/apps/files_external/lib/config.php
@@ -156,8 +156,6 @@ class OC_Mount_Config {
public static function getAbsoluteMountPoints($user) {
$mountPoints = array();
- $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data");
-
$backends = self::getBackends();
// Load system mount points
@@ -287,12 +285,21 @@ class OC_Mount_Config {
/**
* fill in the correct values for $user
*
- * @param string $user
- * @param string $input
+ * @param string $user user value
+ * @param string|array $input
* @return string
*/
private static function setUserVars($user, $input) {
- return str_replace('$user', $user, $input);
+ if (is_array($input)) {
+ foreach ($input as &$value) {
+ if (is_string($value)) {
+ $value = str_replace('$user', $user, $value);
+ }
+ }
+ } else {
+ $input = str_replace('$user', $user, $input);
+ }
+ return $input;
}
diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php
index 4866d177634..967c59debe6 100644
--- a/apps/files_external/templates/settings.php
+++ b/apps/files_external/templates/settings.php
@@ -10,6 +10,7 @@
<th><?php p($l->t('Configuration')); ?></th>
<?php if ($_['isAdminPage']) print_unescaped('<th>'.$l->t('Available for').'</th>'); ?>
<th>&nbsp;</th>
+ <th>&nbsp;</th>
</tr>
</thead>
<tbody>
@@ -78,14 +79,6 @@
<?php OCP\Util::addScript('files_external', $_['backends'][$mount['class']]['custom']); ?>
<?php endif; ?>
<?php endif; ?>
- <?php if (isset($mount['mountOptions'])): ?>
- <input type="hidden" class="mountOptions" value="<?php p(json_encode($mount['mountOptions'])) ?>" />
- <?php endif; ?>
- <?php if ($_['isAdminPage']): ?>
- <?php if (isset($mount['priority'])): ?>
- <input type="hidden" class="priority" value="<?php p($mount['priority']) ?>" />
- <?php endif; ?>
- <?php endif; ?>
</td>
<?php if ($_['isAdminPage']): ?>
<td class="applicable"
@@ -97,8 +90,21 @@
<input type="hidden" class="applicableUsers" style="width:20em;" value=""/>
</td>
<?php endif; ?>
+ <td class="mountOptionsToggle <?php if (!isset($mount['mountpoint'])) { p('hidden'); } ?>"
+ ><img
+ class="svg action"
+ title="<?php p($l->t('Advanced settings')); ?>"
+ alt="<?php p($l->t('Advanced settings')); ?>"
+ src="<?php print_unescaped(image_path('core', 'actions/settings.svg')); ?>" />
+ <input type="hidden" class="mountOptions" value="<?php isset($mount['mountOptions']) ? p(json_encode($mount['mountOptions'])) : '' ?>" />
+ <?php if ($_['isAdminPage']): ?>
+ <?php if (isset($mount['priority'])): ?>
+ <input type="hidden" class="priority" value="<?php p($mount['priority']) ?>" />
+ <?php endif; ?>
+ <?php endif; ?>
+ </td>
<td <?php if (isset($mount['mountpoint'])): ?>class="remove"
- <?php else: ?>style="visibility:hidden;"
+ <?php else: ?>class="hidden"
<?php endif ?>><img alt="<?php p($l->t('Delete')); ?>"
title="<?php p($l->t('Delete')); ?>"
class="svg action"
diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js
index 5a3ee2cb5f1..f030965835a 100644
--- a/apps/files_external/tests/js/settingsSpec.js
+++ b/apps/files_external/tests/js/settingsSpec.js
@@ -43,6 +43,7 @@ describe('OCA.External.Settings tests', function() {
'<td class="applicable">' +
'<input type="hidden" class="applicableUsers">' +
'</td>' +
+ '<td class="mountOptionsToggle"><input type="hidden" class="mountOptions"/><img class="svg action"/></td>' +
'<td><img alt="Delete" title="Delete" class="svg action"/></td>' +
'</tr>' +
'</tbody>' +
@@ -116,30 +117,57 @@ describe('OCA.External.Settings tests', function() {
// TODO: test suggested mount point logic
});
describe('saving storages', function() {
- it('saves storage after editing config', function() {
- var $tr = view.$el.find('tr:first');
- selectBackend('\\OC\\TestBackend');
+ var $tr;
+ beforeEach(function() {
+ $tr = view.$el.find('tr:first');
+ selectBackend('\\OC\\TestBackend');
+ });
+ it('saves storage after editing config', function() {
var $field1 = $tr.find('input[data-parameter=field1]');
expect($field1.length).toEqual(1);
$field1.val('test');
$field1.trigger(new $.Event('keyup', {keyCode: 97}));
+ var $mountOptionsField = $tr.find('input.mountOptions');
+ expect($mountOptionsField.length).toEqual(1);
+ $mountOptionsField.val(JSON.stringify({previews:true}));
+
clock.tick(4000);
expect(fakeServer.requests.length).toEqual(1);
var request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_external/globalstorages');
- expect(OC.parseQueryString(request.requestBody)).toEqual({
+ expect(JSON.parse(request.requestBody)).toEqual({
backendClass: '\\OC\\TestBackend',
- 'backendOptions[field1]': 'test',
- 'backendOptions[field2]': '',
+ backendOptions: {
+ 'field1': 'test',
+ 'field2': ''
+ },
mountPoint: 'TestBackend',
- priority: '11'
+ priority: 11,
+ applicableUsers: [],
+ applicableGroups: [],
+ mountOptions: {
+ 'previews': true
+ }
});
// TODO: respond and check data-id
});
+ it('saves storage after closing mount options dropdown', function() {
+ $tr.find('.mountOptionsToggle img').click();
+ $tr.find('[name=previews]').trigger(new $.Event('keyup', {keyCode: 97}));
+ $tr.find('input[data-parameter=field1]').val('test');
+
+ // does not save inside the dropdown
+ expect(fakeServer.requests.length).toEqual(0);
+
+ $('body').mouseup();
+
+ // but after closing the dropdown
+ expect(fakeServer.requests.length).toEqual(1);
+ });
// TODO: tests with "applicableUsers" and "applicableGroups"
// TODO: test with non-optional config parameters
// TODO: test with missing mount point value
@@ -157,6 +185,52 @@ describe('OCA.External.Settings tests', function() {
describe('recheck storages', function() {
// TODO
});
+ describe('mount options dropdown', function() {
+ var $tr;
+ var $td;
+
+ beforeEach(function() {
+ $tr = view.$el.find('tr:first');
+ $td = $tr.find('.mountOptionsToggle');
+ selectBackend('\\OC\\TestBackend');
+ });
+
+ it('shows dropdown when clicking on toggle button, hides when clicking outside', function() {
+ $td.find('img').click();
+
+ expect($td.find('.dropdown').length).toEqual(1);
+
+ $('body').mouseup();
+
+ expect($td.find('.dropdown').length).toEqual(0);
+ });
+
+ it('reads config from mountOptions field', function() {
+ $tr.find('input.mountOptions').val(JSON.stringify({previews:false}));
+
+ $td.find('img').click();
+ expect($td.find('.dropdown [name=previews]').prop('checked')).toEqual(false);
+ $('body').mouseup();
+
+ $tr.find('input.mountOptions').val(JSON.stringify({previews:true}));
+ $td.find('img').click();
+ expect($td.find('.dropdown [name=previews]').prop('checked')).toEqual(true);
+ });
+
+ it('writes config into mountOptions field', function() {
+ $td.find('img').click();
+ // defaults to true
+ var $field = $td.find('.dropdown [name=previews]');
+ expect($field.prop('checked')).toEqual(true);
+ $td.find('.dropdown [name=filesystem_check_changes]').val(2);
+ $('body').mouseup();
+
+ expect(JSON.parse($tr.find('input.mountOptions').val())).toEqual({
+ previews: true,
+ filesystem_check_changes: 2
+ });
+ });
+ });
});
describe('applicable user list', function() {
// TODO: test select2 retrieval logic
diff --git a/apps/files_external/tests/mountconfig.php b/apps/files_external/tests/mountconfig.php
index a3b993f5018..b76ba0a39a6 100644
--- a/apps/files_external/tests/mountconfig.php
+++ b/apps/files_external/tests/mountconfig.php
@@ -123,7 +123,6 @@ class Test_Mount_Config extends \Test\TestCase {
private $dataDir;
private $userHome;
private $oldAllowedBackends;
- private $allBackends;
const TEST_USER1 = 'user1';
const TEST_USER2 = 'user2';
@@ -213,6 +212,11 @@ class Test_Mount_Config extends \Test\TestCase {
return json_decode(file_get_contents($configFile), true);
}
+ private function writeGlobalConfig($config) {
+ $configFile = $this->dataDir . '/mount.json';
+ file_put_contents($configFile, json_encode($config));
+ }
+
/**
* Reads the user config, for checking
*/
@@ -630,6 +634,51 @@ class Test_Mount_Config extends \Test\TestCase {
$this->assertEquals($mountConfig, $savedMountConfig);
}
+ public function testVariableSubstitution() {
+ $legacyBackendOptions = [
+ 'user' => 'someuser',
+ 'password' => 'somepassword',
+ 'replacethis' => '$user',
+ ];
+ $legacyBackendOptions = \OC_Mount_Config::encryptPasswords($legacyBackendOptions);
+
+ $legacyConfig = [
+ 'class' => '\OC\Files\Storage\SMB',
+ 'options' => $legacyBackendOptions,
+ 'mountOptions' => ['preview' => false, 'int' => 1],
+ ];
+ // different mount options
+ $legacyConfig2 = [
+ 'class' => '\OC\Files\Storage\SMB',
+ 'options' => $legacyBackendOptions,
+ 'mountOptions' => ['preview' => true, 'string' => 'abc'],
+ ];
+
+ $json = [
+ 'user' => [
+ self::TEST_USER1 => [
+ '/$user/files/somemount' => $legacyConfig,
+ '/$user/files/anothermount' => $legacyConfig2,
+ ],
+ ],
+ ];
+
+ $this->writeGlobalConfig($json);
+
+ // re-read config, password was read correctly
+ $config = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1);
+
+ $config1 = $config['/' . self::TEST_USER1 . '/files/somemount'];
+ $config2 = $config['/' . self::TEST_USER1 . '/files/anothermount'];
+
+ $this->assertSame(self::TEST_USER1, $config1['options']['replacethis']);
+ $this->assertSame(self::TEST_USER1, $config1['options']['replacethis']);
+ $this->assertSame(1, $config1['mountOptions']['int']);
+ $this->assertSame(true, $config2['mountOptions']['preview']);
+ $this->assertSame('abc', $config2['mountOptions']['string']);
+ }
+
+
public function mountDataProvider() {
return array(
// Tests for visible mount points