nextcloud/apps/files_external/src/settings.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1589 lines
43 KiB
JavaScript
Raw Normal View History

/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2012-2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import axios, { isAxiosError } from '@nextcloud/axios'
import jQuery from 'jquery'
addPasswordConfirmationInterceptors(axios)
/**
* Returns the selection of applicable users in the given configuration row
*
* @param $row configuration row
* @return array array of user names
*/
2014-08-21 09:11:21 -04:00
function getSelection($row) {
let values = $row.find('.applicableUsers').select2('val')
2014-08-21 09:11:21 -04:00
if (!values || values.length === 0) {
values = []
2014-08-21 09:11:21 -04:00
}
return values
2014-08-21 09:11:21 -04:00
}
/**
*
* @param $row
*/
function getSelectedApplicable($row) {
const users = []
const groups = []
const multiselect = getSelection($row)
$.each(multiselect, function(index, value) {
// FIXME: don't rely on string parts to detect groups...
const pos = (value.indexOf) ? value.indexOf('(group)') : -1
if (pos !== -1) {
groups.push(value.substr(0, pos))
} else {
users.push(value)
}
})
// FIXME: this should be done in the multiselect change event instead
$row.find('.applicable')
.data('applicable-groups', groups)
.data('applicable-users', users)
return { users, groups }
}
/**
*
* @param $element
* @param highlight
*/
function highlightBorder($element, highlight) {
$element.toggleClass('warning-input', highlight)
return highlight
}
/**
*
* @param $input
*/
function isInputValid($input) {
const optional = $input.hasClass('optional')
switch ($input.attr('type')) {
case 'text':
case 'password':
if ($input.val() === '' && !optional) {
return false
}
break
}
return true
}
/**
*
* @param $input
*/
function highlightInput($input) {
switch ($input.attr('type')) {
case 'text':
case 'password':
return highlightBorder($input, !isInputValid($input))
}
}
/**
* Initialize select2 plugin on the given elements
*
* @param {Array<object>} array of jQuery elements
* @param $elements
* @param {number} userListLimit page size for result list
*/
function initApplicableUsersMultiselect($elements, userListLimit) {
const escapeHTML = function(text) {
return text.toString()
.split('&').join('&amp;')
.split('<').join('&lt;')
.split('>').join('&gt;')
.split('"').join('&quot;')
.split('\'').join('&#039;')
}
if (!$elements.length) {
return
}
return $elements.select2({
placeholder: t('files_external', 'Type to select account or group.'),
allowClear: true,
multiple: true,
toggleSelect: true,
dropdownCssClass: 'files-external-select2',
// minimumInputLength: 1,
ajax: {
url: OC.generateUrl('apps/files_external/applicable'),
dataType: 'json',
quietMillis: 100,
data(term, page) { // page is the one-based page number tracked by Select2
return {
pattern: term, // search term
limit: userListLimit, // page size
offset: userListLimit * (page - 1), // page number starts with 0
}
},
results(data) {
if (data.status === 'success') {
const results = []
let userCount = 0 // users is an object
// add groups
$.each(data.groups, function(gid, group) {
results.push({ name: gid + '(group)', displayname: group, type: 'group' })
})
// add users
$.each(data.users, function(id, user) {
userCount++
results.push({ name: id, displayname: user, type: 'user' })
})
const more = (userCount >= userListLimit) || (data.groups.length >= userListLimit)
return { results, more }
} else {
// FIXME add error handling
}
},
},
initSelection(element, callback) {
const users = {}
users.users = []
const toSplit = element.val().split(',')
for (let i = 0; i < toSplit.length; i++) {
users.users.push(toSplit[i])
}
$.ajax(OC.generateUrl('displaynames'), {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(users),
dataType: 'json',
}).done(function(data) {
const results = []
if (data.status === 'success') {
$.each(data.users, function(user, displayname) {
if (displayname !== false) {
results.push({ name: user, displayname, type: 'user' })
}
})
callback(results)
} else {
// FIXME add error handling
}
})
},
id(element) {
return element.name
},
formatResult(element) {
const $result = $('<span><div class="avatardiv"></div><span>' + escapeHTML(element.displayname) + '</span></span>')
const $div = $result.find('.avatardiv')
.attr('data-type', element.type)
.attr('data-name', element.name)
.attr('data-displayname', element.displayname)
if (element.type === 'group') {
const url = OC.imagePath('core', 'actions/group')
$div.html('<img width="32" height="32" src="' + url + '">')
}
return $result.get(0).outerHTML
},
formatSelection(element) {
if (element.type === 'group') {
return '<span title="' + escapeHTML(element.name) + '" class="group">' + escapeHTML(element.displayname + ' ' + t('files_external', '(Group)')) + '</span>'
} else {
return '<span title="' + escapeHTML(element.name) + '" class="user">' + escapeHTML(element.displayname) + '</span>'
}
},
escapeMarkup(m) { return m }, // we escape the markup in formatResult and formatSelection
}).on('select2-loaded', function() {
$.each($('.avatardiv'), function(i, div) {
const $div = $(div)
if ($div.data('type') === 'user') {
$div.avatar($div.data('name'), 32)
}
})
}).on('change', function(event) {
highlightBorder($(event.target).closest('.applicableUsersContainer').find('.select2-choices'), !event.val.length)
})
}
/**
* @param id
* @class OCA.Files_External.Settings.StorageConfig
*
* @classdesc External storage config
*/
const StorageConfig = function(id) {
this.id = id
this.backendOptions = {}
}
// Keep this in sync with \OCA\Files_External\MountConfig::STATUS_*
StorageConfig.Status = {
IN_PROGRESS: -1,
SUCCESS: 0,
ERROR: 1,
INDETERMINATE: 2,
}
2016-01-26 07:22:27 -05:00
StorageConfig.Visibility = {
NONE: 0,
PERSONAL: 1,
ADMIN: 2,
DEFAULT: 3,
}
/**
* @memberof OCA.Files_External.Settings
*/
StorageConfig.prototype = {
_url: null,
/**
* Storage id
*
* @type int
*/
id: null,
/**
* Mount point
*
* @type string
*/
mountPoint: '',
/**
* Backend
*
* @type string
*/
backend: null,
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
/**
* Authentication mechanism
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
*
* @type string
*/
authMechanism: null,
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
/**
* Backend-specific configuration
*
* @type Object.<string,object>
*/
backendOptions: null,
/**
* Mount-specific options
*
* @type Object.<string,object>
*/
mountOptions: null,
/**
* Creates or saves the storage.
*
* @param {Function} [options.success] success callback, receives result as argument
* @param {Function} [options.error] error callback
* @param options
*/
save(options) {
let url = OC.generateUrl(this._url)
let method = 'POST'
if (_.isNumber(this.id)) {
method = 'PUT'
url = OC.generateUrl(this._url + '/{id}', { id: this.id })
}
this._save(method, url, options)
},
/**
* Private implementation of the save function (called after potential password confirmation)
* @param {string} method
* @param {string} url
* @param {{success: Function, error: Function}} options
*/
async _save(method, url, options) {
try {
const response = await axios.request({
confirmPassword: PwdConfirmationMode.Strict,
method,
url,
data: this.getData(),
})
const result = response.data
this.id = result.id
options.success(result)
} catch (error) {
options.error(error)
}
},
/**
* Returns the data from this object
*
* @return {Array} JSON array of the data
*/
getData() {
const data = {
mountPoint: this.mountPoint,
backend: this.backend,
authMechanism: this.authMechanism,
backendOptions: this.backendOptions,
testOnly: true,
}
if (this.id) {
data.id = this.id
}
if (this.mountOptions) {
data.mountOptions = this.mountOptions
}
return data
},
/**
* Recheck the storage
*
* @param {Function} [options.success] success callback, receives result as argument
* @param {Function} [options.error] error callback
* @param options
*/
recheck(options) {
if (!_.isNumber(this.id)) {
if (_.isFunction(options.error)) {
options.error()
}
return
}
$.ajax({
type: 'GET',
url: OC.generateUrl(this._url + '/{id}', { id: this.id }),
data: { testOnly: true },
success: options.success,
error: options.error,
})
},
/**
* Deletes the storage
*
* @param {Function} [options.success] success callback
* @param {Function} [options.error] error callback
* @param options
*/
async destroy(options) {
if (!_.isNumber(this.id)) {
// the storage hasn't even been created => success
if (_.isFunction(options.success)) {
options.success()
}
return
}
try {
await axios.request({
method: 'DELETE',
url: OC.generateUrl(this._url + '/{id}', { id: this.id }),
confirmPassword: PwdConfirmationMode.Strict,
})
options.success()
} catch (e) {
options.error(e)
}
},
/**
* Validate this model
*
* @return {boolean} false if errors exist, true otherwise
*/
validate() {
if (this.mountPoint === '') {
return false
}
if (!this.backend) {
return false
}
if (this.errors) {
return false
}
return true
},
}
/**
* @param id
* @class OCA.Files_External.Settings.GlobalStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc Global external storage config
*/
const GlobalStorageConfig = function(id) {
this.id = id
this.applicableUsers = []
this.applicableGroups = []
}
/**
* @memberOf OCA.Files_External.Settings
*/
GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ {
_url: 'apps/files_external/globalstorages',
/**
* Applicable users
*
* @type Array.<string>
*/
applicableUsers: null,
/**
* Applicable groups
*
* @type Array.<string>
*/
applicableGroups: null,
/**
* Storage priority
*
* @type int
*/
priority: null,
/**
* Returns the data from this object
*
* @return {Array} JSON array of the data
*/
getData() {
const data = StorageConfig.prototype.getData.apply(this, arguments)
return _.extend(data, {
applicableUsers: this.applicableUsers,
applicableGroups: this.applicableGroups,
priority: this.priority,
})
},
})
/**
* @param id
* @class OCA.Files_External.Settings.UserStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
const UserStorageConfig = function(id) {
this.id = id
}
UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userstorages',
})
/**
* @param id
* @class OCA.Files_External.Settings.UserGlobalStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
const UserGlobalStorageConfig = function(id) {
this.id = id
}
UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userglobalstorages',
})
/**
* @class OCA.Files_External.Settings.MountOptionsDropdown
*
* @classdesc Dropdown for mount options
*
* @param {object} $container container DOM object
*/
const MountOptionsDropdown = function() {
}
/**
* @memberof OCA.Files_External.Settings
*/
MountOptionsDropdown.prototype = {
/**
* Dropdown element
*
* @member Object
*/
$el: null,
/**
* Show dropdown
*
* @param {object} $container container
* @param {object} mountOptions mount options
* @param {Array} visibleOptions enabled mount options
*/
show($container, mountOptions, visibleOptions) {
if (MountOptionsDropdown._last) {
MountOptionsDropdown._last.hide()
}
const $el = $(OCA.Files_External.Templates.mountOptionsDropDown({
mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'),
mountOptionsEncryptLabel: t('files_external', 'Enable encryption'),
mountOptionsPreviewsLabel: t('files_external', 'Enable previews'),
mountOptionsSharingLabel: t('files_external', 'Enable sharing'),
mountOptionsFilesystemCheckLabel: t('files_external', 'Check for changes'),
mountOptionsFilesystemCheckOnce: t('files_external', 'Never'),
mountOptionsFilesystemCheckDA: t('files_external', 'Once every direct access'),
mountOptionsReadOnlyLabel: t('files_external', 'Read only'),
deleteLabel: t('files_external', 'Disconnect'),
}))
this.$el = $el
const storage = $container[0].parentNode.className
this.setOptions(mountOptions, visibleOptions, storage)
this.$el.appendTo($container)
MountOptionsDropdown._last = this
this.$el.trigger('show')
},
hide() {
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() {
const options = {}
this.$el.find('input, select').each(function() {
const $this = $(this)
const key = $this.attr('name')
let 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
* @param {Array} visibleOptions enabled mount options
* @param storage
*/
setOptions(options, visibleOptions, storage) {
if (storage === 'owncloud') {
const ind = visibleOptions.indexOf('encrypt')
if (ind > 0) {
visibleOptions.splice(ind, 1)
}
}
const $el = this.$el
_.each(options, function(value, key) {
const $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)
}
})
$el.find('.optionRow').each(function(i, row) {
const $row = $(row)
const optionId = $row.find('input, select').attr('name')
if (visibleOptions.indexOf(optionId) === -1 && !$row.hasClass('persistent')) {
$row.hide()
2015-04-02 12:31:26 -04:00
} else {
$row.show()
}
})
},
}
/**
* @class OCA.Files_External.Settings.MountConfigListView
*
* @classdesc Mount configuration list view
*
* @param {object} $el DOM object containing the list
* @param {object} [options]
* @param {number} [options.userListLimit] page size in applicable users dropdown
*/
const MountConfigListView = function($el, options) {
this.initialize($el, options)
}
MountConfigListView.ParameterFlags = {
OPTIONAL: 1,
USER_PROVIDED: 2,
HIDDEN: 4,
}
MountConfigListView.ParameterTypes = {
TEXT: 0,
BOOLEAN: 1,
PASSWORD: 2,
}
/**
* @memberOf OCA.Files_External.Settings
*/
MountConfigListView.prototype = _.extend({
/**
* jQuery element containing the config list
*
* @type Object
*/
$el: null,
/**
* Storage config class
*
* @type Class
*/
_storageConfigClass: null,
/**
* Flag whether the list is about user storage configs (true)
* or global storage configs (false)
*
* @type bool
*/
_isPersonal: false,
/**
* Page size in applicable users dropdown
*
* @type int
*/
_userListLimit: 30,
/**
* List of supported backends
*
* @type Object.<string,Object>
*/
_allBackends: null,
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
/**
* List of all supported authentication mechanisms
*
* @type Object.<string,Object>
*/
_allAuthMechanisms: null,
_encryptionEnabled: false,
/**
* @param {object} $el DOM object containing the list
* @param {object} [options]
* @param {number} [options.userListLimit] page size in applicable users dropdown
*/
initialize($el, options) {
this.$el = $el
this._isPersonal = ($el.data('admin') !== true)
if (this._isPersonal) {
this._storageConfigClass = OCA.Files_External.Settings.UserStorageConfig
} else {
this._storageConfigClass = OCA.Files_External.Settings.GlobalStorageConfig
}
if (options && !_.isUndefined(options.userListLimit)) {
this._userListLimit = options.userListLimit
}
this._encryptionEnabled = options.encryptionEnabled
this._canCreateLocal = options.canCreateLocal
// read the backend config that was carefully crammed
// into the data-configurations attribute of the select
this._allBackends = this.$el.find('.selectBackend').data('configurations')
this._allAuthMechanisms = this.$el.find('#addMountPoint .authentication').data('mechanisms')
this._initEvents()
},
2015-03-20 05:48:14 -04:00
/**
* Custom JS event handlers
* Trigger callback for all existing configurations
* @param callback
*/
whenSelectBackend(callback) {
this.$el.find('tbody tr:not(#addMountPoint):not(.externalStorageLoading)').each(function(i, tr) {
const backend = $(tr).find('.backend').data('identifier')
callback($(tr), backend)
})
this.on('selectBackend', callback)
},
whenSelectAuthMechanism(callback) {
const self = this
this.$el.find('tbody tr:not(#addMountPoint):not(.externalStorageLoading)').each(function(i, tr) {
const authMechanism = $(tr).find('.selectAuthMechanism').val()
callback($(tr), authMechanism, self._allAuthMechanisms[authMechanism].scheme)
})
this.on('selectAuthMechanism', callback)
},
/**
* Initialize DOM event handlers
*/
_initEvents() {
const self = this
const onChangeHandler = _.bind(this._onChange, this)
// this.$el.on('input', 'td input', onChangeHandler);
this.$el.on('keyup', 'td input', onChangeHandler)
this.$el.on('paste', 'td input', onChangeHandler)
this.$el.on('change', 'td input:checkbox', onChangeHandler)
this.$el.on('change', '.applicable', onChangeHandler)
this.$el.on('click', '.status>span', function() {
self.recheckStorageConfig($(this).closest('tr'))
})
this.$el.on('click', 'td.mountOptionsToggle .icon-delete', function() {
self.deleteStorageConfig($(this).closest('tr'))
})
this.$el.on('click', 'td.save>.icon-checkmark', function() {
self.saveStorageConfig($(this).closest('tr'))
})
this.$el.on('click', 'td.mountOptionsToggle>.icon-more', function() {
$(this).attr('aria-expanded', 'true')
self._showMountOptionsDropdown($(this).closest('tr'))
})
this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this))
this.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this))
this.$el.on('change', '.applicableToAllUsers', _.bind(this._onChangeApplicableToAllUsers, this))
},
_onChange(event) {
const $target = $(event.target)
if ($target.closest('.dropdown').length) {
// ignore dropdown events
return
}
highlightInput($target)
const $tr = $target.closest('tr')
this.updateStatus($tr, null)
},
_onSelectBackend(event) {
const $target = $(event.target)
let $tr = $target.closest('tr')
const storageConfig = new this._storageConfigClass()
storageConfig.mountPoint = $tr.find('.mountPoint input').val()
storageConfig.backend = $target.val()
$tr.find('.mountPoint input').val('')
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
$tr.find('.selectBackend').prop('selectedIndex', 0)
const onCompletion = jQuery.Deferred()
$tr = this.newStorage(storageConfig, onCompletion)
$tr.find('.applicableToAllUsers').prop('checked', false).trigger('change')
onCompletion.resolve()
$tr.find('td.configuration').children().not('[type=hidden]').first().focus()
this.saveStorageConfig($tr)
},
_onSelectAuthMechanism(event) {
const $target = $(event.target)
const $tr = $target.closest('tr')
const authMechanism = $target.val()
const onCompletion = jQuery.Deferred()
this.configureAuthMechanism($tr, authMechanism, onCompletion)
onCompletion.resolve()
this.saveStorageConfig($tr)
},
_onChangeApplicableToAllUsers(event) {
const $target = $(event.target)
const $tr = $target.closest('tr')
const checked = $target.is(':checked')
$tr.find('.applicableUsersContainer').toggleClass('hidden', checked)
if (!checked) {
$tr.find('.applicableUsers').select2('val', '', true)
}
this.saveStorageConfig($tr)
},
/**
* Configure the storage config with a new authentication mechanism
*
* @param {jQuery} $tr config row
* @param {string} authMechanism
* @param {jQuery.Deferred} onCompletion
*/
configureAuthMechanism($tr, authMechanism, onCompletion) {
const authMechanismConfiguration = this._allAuthMechanisms[authMechanism]
const $td = $tr.find('td.configuration')
$td.find('.auth-param').remove()
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
$.each(authMechanismConfiguration.configuration, _.partial(
this.writeParameterInput, $td, _, _, ['auth-param'],
).bind(this))
this.trigger('selectAuthMechanism',
$tr, authMechanism, authMechanismConfiguration.scheme, onCompletion,
)
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
},
2015-09-13 18:23:42 -04:00
/**
* Create a config row for a new storage
*
* @param {StorageConfig} storageConfig storage config to pull values from
* @param {jQuery.Deferred} onCompletion
* @param {boolean} deferAppend
2015-09-13 18:23:42 -04:00
* @return {jQuery} created row
*/
newStorage(storageConfig, onCompletion, deferAppend) {
let mountPoint = storageConfig.mountPoint
let backend = this._allBackends[storageConfig.backend]
2015-09-13 18:23:42 -04:00
if (!backend) {
backend = {
name: 'Unknown: ' + storageConfig.backend,
invalid: true,
}
}
2015-09-13 18:23:42 -04:00
// FIXME: Replace with a proper Handlebar template
const $template = this.$el.find('tr#addMountPoint')
const $tr = $template.clone()
if (!deferAppend) {
$tr.insertBefore($template)
}
2015-09-13 18:23:42 -04:00
$tr.data('storageConfig', storageConfig)
$tr.show()
$tr.find('td.mountOptionsToggle, td.save, td.remove').removeClass('hidden')
$tr.find('td').last().removeAttr('style')
$tr.removeAttr('id')
$tr.find('select#selectBackend')
if (!deferAppend) {
initApplicableUsersMultiselect($tr.find('.applicableUsers'), this._userListLimit)
}
2015-09-13 18:23:42 -04:00
if (storageConfig.id) {
$tr.data('id', storageConfig.id)
2015-09-13 18:23:42 -04:00
}
$tr.find('.backend').text(backend.name)
2015-09-13 18:23:42 -04:00
if (mountPoint === '') {
mountPoint = this._suggestMountPoint(backend.name)
2015-09-13 18:23:42 -04:00
}
$tr.find('.mountPoint input').val(mountPoint)
$tr.addClass(backend.identifier)
$tr.find('.backend').data('identifier', backend.identifier)
2015-09-13 18:23:42 -04:00
if (backend.invalid || (backend.identifier === 'local' && !this._canCreateLocal)) {
$tr.find('[name=mountPoint]').prop('disabled', true)
$tr.find('.applicable,.mountOptionsToggle').empty()
$tr.find('.save').empty()
if (backend.invalid) {
this.updateStatus($tr, false, t('files_external', 'Unknown backend: {backendName}', { backendName: backend.name }))
}
return $tr
}
const selectAuthMechanism = $('<select class="selectAuthMechanism"></select>')
const neededVisibility = (this._isPersonal) ? StorageConfig.Visibility.PERSONAL : StorageConfig.Visibility.ADMIN
2015-09-13 18:23:42 -04:00
$.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {
if (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {
2015-09-13 18:23:42 -04:00
selectAuthMechanism.append(
$('<option value="' + authMechanism.identifier + '" data-scheme="' + authMechanism.scheme + '">' + authMechanism.name + '</option>'),
)
2015-09-13 18:23:42 -04:00
}
})
2015-09-13 18:23:42 -04:00
if (storageConfig.authMechanism) {
selectAuthMechanism.val(storageConfig.authMechanism)
} else {
storageConfig.authMechanism = selectAuthMechanism.val()
2015-09-13 18:23:42 -04:00
}
$tr.find('td.authentication').append(selectAuthMechanism)
2015-09-13 18:23:42 -04:00
const $td = $tr.find('td.configuration')
$.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this))
2015-09-13 18:23:42 -04:00
this.trigger('selectBackend', $tr, backend.identifier, onCompletion)
this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion)
2015-09-13 18:23:42 -04:00
if (storageConfig.backendOptions) {
$td.find('input, select').each(function() {
const input = $(this)
const val = storageConfig.backendOptions[input.data('parameter')]
if (val !== undefined) {
if (input.is('input:checkbox')) {
input.prop('checked', val)
}
input.val(storageConfig.backendOptions[input.data('parameter')])
highlightInput(input)
}
})
2015-09-13 18:23:42 -04:00
}
let applicable = []
2015-09-14 15:21:16 -04:00
if (storageConfig.applicableUsers) {
applicable = applicable.concat(storageConfig.applicableUsers)
2015-09-14 15:21:16 -04:00
}
if (storageConfig.applicableGroups) {
applicable = applicable.concat(
_.map(storageConfig.applicableGroups, function(group) {
return group + '(group)'
}),
)
2015-09-14 15:21:16 -04:00
}
if (applicable.length) {
$tr.find('.applicableUsers').val(applicable).trigger('change')
$tr.find('.applicableUsersContainer').removeClass('hidden')
} else {
// applicable to all
$tr.find('.applicableUsersContainer').addClass('hidden')
}
$tr.find('.applicableToAllUsers').prop('checked', !applicable.length)
2015-09-14 15:21:16 -04:00
const priorityEl = $('<input type="hidden" class="priority" value="' + backend.priority + '" />')
$tr.append(priorityEl)
2015-09-13 18:23:42 -04:00
if (storageConfig.mountOptions) {
$tr.find('input.mountOptions').val(JSON.stringify(storageConfig.mountOptions))
2015-09-13 18:23:42 -04:00
} else {
// FIXME default backend mount options
$tr.find('input.mountOptions').val(JSON.stringify({
encrypt: true,
previews: true,
enable_sharing: false,
filesystem_check_changes: 1,
encoding_compatibility: false,
readonly: false,
}))
2015-09-13 18:23:42 -04:00
}
return $tr
2015-09-13 18:23:42 -04:00
},
/**
* Load storages into config rows
*/
loadStorages() {
const self = this
2015-09-13 18:23:42 -04:00
const onLoaded1 = $.Deferred()
const onLoaded2 = $.Deferred()
this.$el.find('.externalStorageLoading').removeClass('hidden')
$.when(onLoaded1, onLoaded2).always(() => {
self.$el.find('.externalStorageLoading').addClass('hidden')
})
if (this._isPersonal) {
// load userglobal storages
$.ajax({
type: 'GET',
url: OC.generateUrl('apps/files_external/userglobalstorages'),
data: { testOnly: true },
contentType: 'application/json',
success(result) {
result = Object.values(result)
const onCompletion = jQuery.Deferred()
let $rows = $()
result.forEach(function(storageParams) {
let storageConfig
const isUserGlobal = storageParams.type === 'system' && self._isPersonal
storageParams.mountPoint = storageParams.mountPoint.substr(1) // trim leading slash
if (isUserGlobal) {
storageConfig = new UserGlobalStorageConfig()
} else {
storageConfig = new self._storageConfigClass()
}
_.extend(storageConfig, storageParams)
const $tr = self.newStorage(storageConfig, onCompletion, true)
// userglobal storages must be at the top of the list
$tr.detach()
self.$el.prepend($tr)
const $authentication = $tr.find('.authentication')
$authentication.text($authentication.find('select option:selected').text())
// disable any other inputs
$tr.find('.mountOptionsToggle, .remove').empty()
$tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled')
if (isUserGlobal) {
$tr.find('.configuration').find(':not(.user_provided)').remove()
} else {
// userglobal storages do not expose configuration data
$tr.find('.configuration').text(t('files_external', 'Admin defined'))
}
// don't recheck config automatically when there are a large number of storages
if (result.length < 20) {
self.recheckStorageConfig($tr)
} else {
self.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'))
}
$rows = $rows.add($tr)
})
initApplicableUsersMultiselect(self.$el.find('.applicableUsers'), this._userListLimit)
self.$el.find('tr#addMountPoint').before($rows)
const mainForm = $('#files_external')
if (result.length === 0 && mainForm.attr('data-can-create') === 'false') {
mainForm.hide()
$('a[href="#external-storage"]').parent().hide()
$('.emptycontent').show()
}
onCompletion.resolve()
onLoaded1.resolve()
},
})
} else {
onLoaded1.resolve()
}
const url = this._storageConfigClass.prototype._url
2015-09-13 18:23:42 -04:00
$.ajax({
type: 'GET',
url: OC.generateUrl(url),
contentType: 'application/json',
success(result) {
result = Object.values(result)
const onCompletion = jQuery.Deferred()
let $rows = $()
result.forEach(function(storageParams) {
storageParams.mountPoint = (storageParams.mountPoint === '/') ? '/' : storageParams.mountPoint.substr(1) // trim leading slash
const storageConfig = new self._storageConfigClass()
_.extend(storageConfig, storageParams)
const $tr = self.newStorage(storageConfig, onCompletion, true)
// don't recheck config automatically when there are a large number of storages
if (result.length < 20) {
self.recheckStorageConfig($tr)
} else {
self.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'))
}
$rows = $rows.add($tr)
})
initApplicableUsersMultiselect($rows.find('.applicableUsers'), this._userListLimit)
self.$el.find('tr#addMountPoint').before($rows)
onCompletion.resolve()
onLoaded2.resolve()
},
})
2015-09-13 18:23:42 -04:00
},
/**
* @param {jQuery} $td
* @param {string} parameter
* @param {string} placeholder
* @param {Array} classes
* @return {jQuery} newly created input
*/
writeParameterInput($td, parameter, placeholder, classes) {
const hasFlag = function(flag) {
return (placeholder.flags & flag) === flag
}
classes = $.isArray(classes) ? classes : []
classes.push('added')
if (hasFlag(MountConfigListView.ParameterFlags.OPTIONAL)) {
classes.push('optional')
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
}
if (hasFlag(MountConfigListView.ParameterFlags.USER_PROVIDED)) {
if (this._isPersonal) {
classes.push('user_provided')
} else {
return
}
}
let newElement
const trimmedPlaceholder = placeholder.value
if (hasFlag(MountConfigListView.ParameterFlags.HIDDEN)) {
newElement = $('<input type="hidden" class="' + classes.join(' ') + '" data-parameter="' + parameter + '" />')
} else if (placeholder.type === MountConfigListView.ParameterTypes.PASSWORD) {
newElement = $('<input type="password" class="' + classes.join(' ') + '" data-parameter="' + parameter + '" placeholder="' + trimmedPlaceholder + '" />')
} else if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
const checkboxId = _.uniqueId('checkbox_')
newElement = $('<div><label><input type="checkbox" id="' + checkboxId + '" class="' + classes.join(' ') + '" data-parameter="' + parameter + '" />' + trimmedPlaceholder + '</label></div>')
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
} else {
newElement = $('<input type="text" class="' + classes.join(' ') + '" data-parameter="' + parameter + '" placeholder="' + trimmedPlaceholder + '" />')
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
}
if (placeholder.defaultValue) {
if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
newElement.find('input').prop('checked', placeholder.defaultValue)
} else {
newElement.val(placeholder.defaultValue)
}
}
if (placeholder.tooltip) {
newElement.attr('title', placeholder.tooltip)
}
highlightInput(newElement)
$td.append(newElement)
return newElement
Authentication mechanisms for external storage backends A backend can now specify generic authentication schemes that it supports, instead of specifying the parameters for its authentication method directly. This allows multiple authentication mechanisms to be implemented for a single scheme, providing altered functionality. This commit introduces the backend framework for this feature, and so at this point the UI will be broken as the frontend does not specify the required information. Terminology: - authentication scheme Parameter interface for the authentication method. A backend supporting the 'password' scheme accepts two parameters, 'user' and 'password'. - authentication mechanism Specific mechanism implementing a scheme. Basic mechanisms may forward configuration options directly to the backend, more advanced ones may lookup parameters or retrieve them from the session New dropdown selector for external storage configurations to select the authentication mechanism to be used. Authentication mechanisms can have visibilities, just like backends. The API was extended too to make it easier to add/remove visibilities. In addition, the concept of 'allowed visibility' has been introduced, so a backend/auth mechanism can force a maximum visibility level (e.g. Local storage type) that cannot be overridden by configuration in the web UI. An authentication mechanism is a fully instantiated implementation. This allows an implementation to have dependencies injected into it, e.g. an \OCP\IDB for database operations. When a StorageConfig is being prepared for mounting, the authentication mechanism implementation has manipulateStorage() called, which inserts the relevant authentication method options into the storage ready for mounting.
2015-08-12 05:54:03 -04:00
},
/**
* Gets the storage model from the given row
*
* @param $tr row element
* @return {OCA.Files_External.StorageConfig} storage model instance
*/
getStorageConfig($tr) {
let storageId = $tr.data('id')
if (!storageId) {
// new entry
storageId = null
}
let storage = $tr.data('storageConfig')
if (!storage) {
storage = new this._storageConfigClass(storageId)
}
storage.errors = null
storage.mountPoint = $tr.find('.mountPoint input').val()
storage.backend = $tr.find('.backend').data('identifier')
storage.authMechanism = $tr.find('.selectAuthMechanism').val()
const classOptions = {}
const configuration = $tr.find('.configuration input')
const missingOptions = []
$.each(configuration, function(index, input) {
const $input = $(input)
const parameter = $input.data('parameter')
if ($input.attr('type') === 'button') {
return
}
if (!isInputValid($input) && !$input.hasClass('optional')) {
missingOptions.push(parameter)
return
}
if ($(input).is(':checkbox')) {
if ($(input).is(':checked')) {
classOptions[parameter] = true
} else {
classOptions[parameter] = false
}
} else {
classOptions[parameter] = $(input).val()
}
})
storage.backendOptions = classOptions
if (missingOptions.length) {
storage.errors = {
backendOptions: missingOptions,
}
}
// gather selected users and groups
if (!this._isPersonal) {
const multiselect = getSelectedApplicable($tr)
const users = multiselect.users || []
const groups = multiselect.groups || []
const isApplicableToAllUsers = $tr.find('.applicableToAllUsers').is(':checked')
if (isApplicableToAllUsers) {
storage.applicableUsers = []
storage.applicableGroups = []
} else {
storage.applicableUsers = users
storage.applicableGroups = groups
if (!storage.applicableUsers.length && !storage.applicableGroups.length) {
if (!storage.errors) {
storage.errors = {}
}
storage.errors.requiredApplicable = true
}
}
storage.priority = parseInt($tr.find('input.priority').val() || '100', 10)
}
const mountOptions = $tr.find('input.mountOptions').val()
if (mountOptions) {
storage.mountOptions = JSON.parse(mountOptions)
}
return storage
},
/**
* Deletes the storage from the given tr
*
* @param $tr storage row
* @param Function callback callback to call after save
*/
deleteStorageConfig($tr) {
const self = this
const configId = $tr.data('id')
if (!_.isNumber(configId)) {
// deleting unsaved storage
$tr.remove()
return
}
const storage = new this._storageConfigClass(configId)
OC.dialogs.confirm(
t('files_external', 'Are you sure you want to disconnect this external storage?')
+ ' '
+ t('files_external', 'It will make the storage unavailable in {instanceName} and will lead to a deletion of these files and folders on any sync client that is currently connected but will not delete any files and folders on the external storage itself.',
{
storage: this.mountPoint,
instanceName: window.OC.theme.name,
},
),
t('files_external', 'Delete storage?'),
function(confirm) {
if (confirm) {
self.updateStatus($tr, StorageConfig.Status.IN_PROGRESS)
storage.destroy({
success() {
$tr.remove()
},
error(result) {
const statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined
self.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage)
},
})
}
},
)
},
/**
* Saves the storage from the given tr
*
* @param $tr storage row
* @param Function callback callback to call after save
* @param callback
* @param concurrentTimer only update if the timer matches this
*/
saveStorageConfig($tr, callback, concurrentTimer) {
const self = this
const storage = this.getStorageConfig($tr)
if (!storage || !storage.validate()) {
return false
}
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS)
storage.save({
success(result) {
if (concurrentTimer === undefined
|| $tr.data('save-timer') === concurrentTimer
) {
self.updateStatus($tr, result.status, result.statusMessage)
$tr.data('id', result.id)
if (_.isFunction(callback)) {
callback(storage)
}
}
},
error(result) {
if (concurrentTimer === undefined
|| $tr.data('save-timer') === concurrentTimer
) {
const statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined
self.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage)
}
},
})
},
/**
* Recheck storage availability
*
* @param {jQuery} $tr storage row
* @return {boolean} success
*/
recheckStorageConfig($tr) {
const self = this
const storage = this.getStorageConfig($tr)
if (!storage.validate()) {
return false
}
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS)
storage.recheck({
success(result) {
self.updateStatus($tr, result.status, result.statusMessage)
},
error(result) {
const statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined
self.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage)
},
})
},
/**
* Update status display
*
* @param {jQuery} $tr
* @param {number} status
2015-09-16 11:58:26 -04:00
* @param {string} message
*/
updateStatus($tr, status, message) {
const $statusSpan = $tr.find('.status span')
switch (status) {
case null:
// remove status
$statusSpan.hide()
break
case StorageConfig.Status.IN_PROGRESS:
$statusSpan.attr('class', 'icon-loading-small')
break
case StorageConfig.Status.SUCCESS:
$statusSpan.attr('class', 'success icon-checkmark-white')
break
case StorageConfig.Status.INDETERMINATE:
$statusSpan.attr('class', 'indeterminate icon-info-white')
break
default:
$statusSpan.attr('class', 'error icon-error-white')
}
if (status !== null) {
$statusSpan.show()
}
if (typeof message !== 'string') {
message = t('files_external', 'Click to recheck the configuration')
}
$statusSpan.attr('title', message)
},
/**
* Suggest mount point name that doesn't conflict with the existing names in the list
*
* @param {string} defaultMountPoint default name
*/
_suggestMountPoint(defaultMountPoint) {
const $el = this.$el
const pos = defaultMountPoint.indexOf('/')
if (pos !== -1) {
defaultMountPoint = defaultMountPoint.substring(0, pos)
}
defaultMountPoint = defaultMountPoint.replace(/\s+/g, '')
let i = 1
let append = ''
let match = true
while (match && i < 20) {
match = false
$el.find('tbody td.mountPoint input').each(function(index, mountPoint) {
if ($(mountPoint).val() === defaultMountPoint + append) {
match = true
return false
}
})
if (match) {
append = i
i++
} else {
break
}
}
return defaultMountPoint + append
},
2015-09-13 18:23:42 -04:00
/**
* Toggles the mount options dropdown
*
* @param {object} $tr configuration row
2015-09-13 18:23:42 -04:00
*/
_showMountOptionsDropdown($tr) {
const self = this
const storage = this.getStorageConfig($tr)
const $toggle = $tr.find('.mountOptionsToggle')
const dropDown = new MountOptionsDropdown()
const visibleOptions = [
'previews',
'filesystem_check_changes',
'enable_sharing',
'encoding_compatibility',
'readonly',
'delete',
]
if (this._encryptionEnabled) {
visibleOptions.push('encrypt')
}
dropDown.show($toggle, storage.mountOptions || [], visibleOptions)
$('body').on('mouseup.mountOptionsDropdown', function(event) {
const $target = $(event.target)
if ($target.closest('.popovermenu').length) {
return
}
dropDown.hide()
})
dropDown.$el.on('hide', function() {
const mountOptions = dropDown.getOptions()
$('body').off('mouseup.mountOptionsDropdown')
$tr.find('input.mountOptions').val(JSON.stringify(mountOptions))
$tr.find('td.mountOptionsToggle>.icon-more').attr('aria-expanded', 'false')
self.saveStorageConfig($tr)
})
},
}, OC.Backbone.Events)
window.addEventListener('DOMContentLoaded', function() {
const enabled = $('#files_external').attr('data-encryption-enabled')
const canCreateLocal = $('#files_external').attr('data-can-create-local')
const encryptionEnabled = (enabled === 'true')
const mountConfigListView = new MountConfigListView($('#externalStorage'), {
encryptionEnabled,
canCreateLocal: (canCreateLocal === 'true'),
})
mountConfigListView.loadStorages()
2012-12-28 17:38:24 -05:00
// TODO: move this into its own View class
const $allowUserMounting = $('#allowUserMounting')
$allowUserMounting.bind('change', function() {
OC.msg.startSaving('#userMountingMsg')
if (this.checked) {
OCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'yes')
$('input[name="allowUserMountingBackends\\[\\]"]').prop('checked', true)
$('#userMountingBackends').removeClass('hidden')
$('input[name="allowUserMountingBackends\\[\\]"]').eq(0).trigger('change')
} else {
OCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'no')
$('#userMountingBackends').addClass('hidden')
}
OC.msg.finishedSaving('#userMountingMsg', { status: 'success', data: { message: t('files_external', 'Saved') } })
})
$('input[name="allowUserMountingBackends\\[\\]"]').bind('change', function() {
OC.msg.startSaving('#userMountingMsg')
let userMountingBackends = $('input[name="allowUserMountingBackends\\[\\]"]:checked').map(function() {
return $(this).val()
}).get()
const deprecatedBackends = $('input[name="allowUserMountingBackends\\[\\]"][data-deprecate-to]').map(function() {
if ($.inArray($(this).data('deprecate-to'), userMountingBackends) !== -1) {
return $(this).val()
}
return null
}).get()
userMountingBackends = userMountingBackends.concat(deprecatedBackends)
OCP.AppConfig.setValue('files_external', 'user_mounting_backends', userMountingBackends.join())
OC.msg.finishedSaving('#userMountingMsg', { status: 'success', data: { message: t('files_external', 'Saved') } })
// disable allowUserMounting
if (userMountingBackends.length === 0) {
$allowUserMounting.prop('checked', false)
$allowUserMounting.trigger('change')
}
})
$('#global_credentials').on('submit', async function(event) {
event.preventDefault()
const $form = $(this)
const $submit = $form.find('[type=submit]')
$submit.val(t('files_external', 'Saving …'))
const uid = $form.find('[name=uid]').val()
const user = $form.find('[name=username]').val()
const password = $form.find('[name=password]').val()
try {
await axios.request({
method: 'POST',
data: {
uid,
user,
password,
},
url: generateUrl('apps/files_external/globalcredentials'),
confirmPassword: PwdConfirmationMode.Strict,
})
$submit.val(t('files_external', 'Saved'))
setTimeout(function() {
$submit.val(t('files_external', 'Save'))
}, 2500)
} catch (error) {
$submit.val(t('files_external', 'Save'))
if (isAxiosError(error)) {
const message = error.response?.data?.message || t('files_external', 'Failed to save global credentials')
showError(t('files_external', 'Failed to save global credentials: {message}', { message }))
}
}
return false
})
// global instance
OCA.Files_External.Settings.mountConfig = mountConfigListView
/**
* Legacy
*
* @namespace
* @deprecated use OCA.Files_External.Settings.mountConfig instead
*/
OC.MountConfig = {
saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView),
}
})
// export
OCA.Files_External = OCA.Files_External || {}
/**
* @namespace
*/
OCA.Files_External.Settings = OCA.Files_External.Settings || {}
OCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig
OCA.Files_External.Settings.UserStorageConfig = UserStorageConfig
OCA.Files_External.Settings.MountConfigListView = MountConfigListView