mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
1588 lines
43 KiB
JavaScript
1588 lines
43 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
function getSelection($row) {
|
|
let values = $row.find('.applicableUsers').select2('val')
|
|
if (!values || values.length === 0) {
|
|
values = []
|
|
}
|
|
return values
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @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('&')
|
|
.split('<').join('<')
|
|
.split('>').join('>')
|
|
.split('"').join('"')
|
|
.split('\'').join(''')
|
|
}
|
|
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,
|
|
}
|
|
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 mechanism
|
|
*
|
|
* @type string
|
|
*/
|
|
authMechanism: null,
|
|
|
|
/**
|
|
* 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()
|
|
} 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,
|
|
|
|
/**
|
|
* 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()
|
|
},
|
|
|
|
/**
|
|
* 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('')
|
|
|
|
$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()
|
|
|
|
$.each(authMechanismConfiguration.configuration, _.partial(
|
|
this.writeParameterInput, $td, _, _, ['auth-param'],
|
|
).bind(this))
|
|
|
|
this.trigger('selectAuthMechanism',
|
|
$tr, authMechanism, authMechanismConfiguration.scheme, onCompletion,
|
|
)
|
|
},
|
|
|
|
/**
|
|
* Create a config row for a new storage
|
|
*
|
|
* @param {StorageConfig} storageConfig storage config to pull values from
|
|
* @param {jQuery.Deferred} onCompletion
|
|
* @param {boolean} deferAppend
|
|
* @return {jQuery} created row
|
|
*/
|
|
newStorage(storageConfig, onCompletion, deferAppend) {
|
|
let mountPoint = storageConfig.mountPoint
|
|
let backend = this._allBackends[storageConfig.backend]
|
|
|
|
if (!backend) {
|
|
backend = {
|
|
name: 'Unknown: ' + storageConfig.backend,
|
|
invalid: true,
|
|
}
|
|
}
|
|
|
|
// FIXME: Replace with a proper Handlebar template
|
|
const $template = this.$el.find('tr#addMountPoint')
|
|
const $tr = $template.clone()
|
|
if (!deferAppend) {
|
|
$tr.insertBefore($template)
|
|
}
|
|
|
|
$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)
|
|
}
|
|
|
|
if (storageConfig.id) {
|
|
$tr.data('id', storageConfig.id)
|
|
}
|
|
|
|
$tr.find('.backend').text(backend.name)
|
|
if (mountPoint === '') {
|
|
mountPoint = this._suggestMountPoint(backend.name)
|
|
}
|
|
$tr.find('.mountPoint input').val(mountPoint)
|
|
$tr.addClass(backend.identifier)
|
|
$tr.find('.backend').data('identifier', backend.identifier)
|
|
|
|
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
|
|
$.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {
|
|
if (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {
|
|
selectAuthMechanism.append(
|
|
$('<option value="' + authMechanism.identifier + '" data-scheme="' + authMechanism.scheme + '">' + authMechanism.name + '</option>'),
|
|
)
|
|
}
|
|
})
|
|
if (storageConfig.authMechanism) {
|
|
selectAuthMechanism.val(storageConfig.authMechanism)
|
|
} else {
|
|
storageConfig.authMechanism = selectAuthMechanism.val()
|
|
}
|
|
$tr.find('td.authentication').append(selectAuthMechanism)
|
|
|
|
const $td = $tr.find('td.configuration')
|
|
$.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this))
|
|
|
|
this.trigger('selectBackend', $tr, backend.identifier, onCompletion)
|
|
this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion)
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
|
|
let applicable = []
|
|
if (storageConfig.applicableUsers) {
|
|
applicable = applicable.concat(storageConfig.applicableUsers)
|
|
}
|
|
if (storageConfig.applicableGroups) {
|
|
applicable = applicable.concat(
|
|
_.map(storageConfig.applicableGroups, function(group) {
|
|
return group + '(group)'
|
|
}),
|
|
)
|
|
}
|
|
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)
|
|
|
|
const priorityEl = $('<input type="hidden" class="priority" value="' + backend.priority + '" />')
|
|
$tr.append(priorityEl)
|
|
|
|
if (storageConfig.mountOptions) {
|
|
$tr.find('input.mountOptions').val(JSON.stringify(storageConfig.mountOptions))
|
|
} 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,
|
|
}))
|
|
}
|
|
|
|
return $tr
|
|
},
|
|
|
|
/**
|
|
* Load storages into config rows
|
|
*/
|
|
loadStorages() {
|
|
const self = this
|
|
|
|
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
|
|
|
|
$.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()
|
|
},
|
|
})
|
|
},
|
|
|
|
/**
|
|
* @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')
|
|
}
|
|
|
|
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>')
|
|
} else {
|
|
newElement = $('<input type="text" class="' + classes.join(' ') + '" data-parameter="' + parameter + '" placeholder="' + trimmedPlaceholder + '" />')
|
|
}
|
|
|
|
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
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
* @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
|
|
},
|
|
|
|
/**
|
|
* Toggles the mount options dropdown
|
|
*
|
|
* @param {object} $tr configuration row
|
|
*/
|
|
_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()
|
|
|
|
// 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
|