diff options
author | Julius Härtl <jus@bitgrid.net> | 2019-07-19 20:12:00 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2019-08-30 17:52:19 +0300 |
commit | b848201e582672a0f3f03a1aab560233ca6cad40 (patch) | |
tree | 204b30dcb3ee8d3937c80415b797657c46c2464a /src | |
parent | 48c627ef0b3c8e7c76890ad2587828d693370c7e (diff) |
Start with vue admin panel
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'src')
-rw-r--r-- | src/admin.js | 25 | ||||
-rw-r--r-- | src/components/AdminSettings.vue | 161 | ||||
-rw-r--r-- | src/components/SettingsCheckbox.vue | 78 | ||||
-rw-r--r-- | src/components/SettingsInputText.vue | 98 | ||||
-rw-r--r-- | src/components/SettingsSelectGroup.vue | 125 | ||||
-rw-r--r-- | src/components/SettingsSelectTag.vue | 200 |
6 files changed, 686 insertions, 1 deletions
diff --git a/src/admin.js b/src/admin.js index 2d3344d4..150c2916 100644 --- a/src/admin.js +++ b/src/admin.js @@ -1,6 +1,29 @@ /* global OC, $ */ +import Vue from 'vue' +import AdminSettings from './components/AdminSettings' -var documentsSettings = { +// CSP config for webpack dynamic chunk loading +// eslint-disable-next-line +__webpack_nonce__ = btoa(OC.requestToken) + +// Correct the root of the app for chunk loading +// OC.linkTo matches the apps folders +// eslint-disable-next-line +__webpack_public_path__ = OC.linkTo('richdocuments', 'js/') + +Vue.prototype.t = t +Vue.prototype.n = n +Vue.prototype.OC = OC +Vue.prototype.OCA = OCA + +const element = document.getElementById('admin-vue') + +/* eslint-disable-next-line no-new */ +new Vue({ + render: h => h(AdminSettings, { props: { initial: JSON.parse(element.dataset.initial) } }) +}).$mount('#admin-vue') + +const documentsSettings = { _createExtApp: function() { var app1 = document.createElement('div') app1.setAttribute('class', 'external-app') diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue new file mode 100644 index 00000000..c7d1ba67 --- /dev/null +++ b/src/components/AdminSettings.vue @@ -0,0 +1,161 @@ +<!-- + - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <div class="section"> + <h2>Secure view settings</h2> + + <p>{{ t('richdocuments', 'Secure view enables you to secure documents by embedding a watermark') }}</p> + <settings-checkbox v-model="settings.watermark.enabled" label="Enable watermarking" hint="" + :disabled="updating" @input="update" /> + <settings-input-text v-if="settings.watermark.enabled" v-model="settings.watermark.text" label="Watermark text" + :hint="t('richdocuments', 'Supported placeholders: {userId}, {date}')" + :disabled="updating" @update="update" /> + <div v-if="settings.watermark.enabled"> + <settings-checkbox v-model="settings.watermark.allTags" label="Show watermark on tagged files" :disabled="updating" + @input="update" /> + <p v-if="settings.watermark.allTags" class="checkbox-details"> + <settings-select-tag v-model="settings.watermark.allTagsList" label="Select tags to enforce watermarking" @input="update" /> + </p> + <settings-checkbox v-model="settings.watermark.allGroups" label="Show watermark for users of groups" :disabled="updating" + @input="update" /> + <p v-if="settings.watermark.allGroups" class="checkbox-details"> + <settings-select-group v-model="settings.watermark.allGroupsList" label="Select tags to enforce watermarking" @input="update" /> + </p> + <settings-checkbox v-model="settings.watermark.shareAll" label="Show watermark for all shares" hint="" + :disabled="updating" @input="update" /> + <settings-checkbox v-if="!settings.watermark.shareAll" v-model="settings.watermark.shareRead" label="Show watermark for read only shares" + hint="" + :disabled="updating" @input="update" /> + + <h3>Link shares</h3> + <settings-checkbox v-model="settings.watermark.linkAll" label="Show watermark for all link shares" hint="" + :disabled="updating" @input="update" /> + <settings-checkbox v-if="!settings.watermark.linkAll" v-model="settings.watermark.linkSecure" label="Show watermark for download hidden shares" + hint="" + :disabled="updating" @input="update" /> + <settings-checkbox v-if="!settings.watermark.linkAll" v-model="settings.watermark.linkRead" label="Show watermark for read only link shares" + hint="" + :disabled="updating" @input="update" /> + <settings-checkbox v-if="!settings.watermark.linkAll" v-model="settings.watermark.linkTags" label="Show watermark on link shares with specific system tags" + :disabled="updating" @input="update" /> + <p v-if="!settings.watermark.linkAll && settings.watermark.linkTags" class="checkbox-details"> + <settings-select-tag v-model="settings.watermark.linkTagsList" label="Select tags to enforce watermarking" @input="update" /> + </p> + </div> + </div> +</template> + +<script> +import Vue from 'vue' +import axios from 'nextcloud-axios' +import SettingsCheckbox from './SettingsCheckbox' +import SettingsInputText from './SettingsInputText' +import SettingsSelectTag from './SettingsSelectTag' +import SettingsSelectGroup from './SettingsSelectGroup' +import { generateUrl } from 'nextcloud-router' + +export default { + name: 'AdminSettings', + components: { + SettingsCheckbox, + SettingsInputText, + SettingsSelectTag, + SettingsSelectGroup + }, + props: { + initial: { + type: Object, + required: true + } + }, + data() { + return { + updating: false, + groups: [], + tags: [], + settings: { + watermark: { + enabled: false, + shareAll: false, + shareRead: false, + linkSecure: false, + linkRead: false, + linkAll: false, + linkTags: false, + linkTagsList: [], + allGroups: false, + allGroupsList: [], + allTags: false, + allTagsList: [], + text: '' + } + } + } + }, + beforeMount() { + for (let key in this.initial) { + if (!this.initial.hasOwnProperty(key)) { + continue + } + + let [ parent, setting ] = key.split('_') + if (parent === 'watermark') { + Vue.set(this.settings[parent], setting, this.initial[key]) + } else { + Vue.set(this.settings, key, this.initial[key]) + } + + } + Vue.set(this.settings, 'data', this.initial) + }, + methods: { + update() { + this.updating = true + let settings = this.settings + axios.post(generateUrl('/apps/richdocuments/settings/watermark'), { settings }).then((response) => { + this.updating = false + }).catch((error) => { + this.updating = false + OC.Notification.showTemporary('Failed to save settings') + console.error(error) + }) + } + } +} +</script> + +<style scoped> + p { + margin-bottom: 15px; + } + p.checkbox-details { + margin-left: 25px; + margin-top: -10px; + margin-bottom: 20px; + } + input, + .multiselect { + width: 100%; + max-width: 400px; + } +</style> diff --git a/src/components/SettingsCheckbox.vue b/src/components/SettingsCheckbox.vue new file mode 100644 index 00000000..94a3c32a --- /dev/null +++ b/src/components/SettingsCheckbox.vue @@ -0,0 +1,78 @@ +<!-- + - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <p> + <input :id="id" type="checkbox" class="checkbox" + :checked="inputVal" :disabled="disabled" @change="$emit('input', $event.target.checked)"> + <label :for="id">{{ label }}</label><br> + <em v-if="hint !== ''">{{ hint }}</em> + </p> +</template> + +<script> +let uuid = 0 +export default { + name: 'SettingsCheckbox', + props: { + label: { + type: String, + required: true + }, + hint: { + type: String, + default: '' + }, + value: { + type: Boolean, + default: false + }, + disabled: { + type: Boolean, + default: false + } + }, + data() { + return { + inputVal: this.value + } + }, + computed: { + id() { + return 'settings-checkbox-' + this.uuid + } + }, + watch: { + value(newVal) { + this.inputVal = this.value + } + }, + beforeCreate: function() { + this.uuid = uuid.toString() + uuid += 1 + } +} +</script> + +<style scoped> + +</style> diff --git a/src/components/SettingsInputText.vue b/src/components/SettingsInputText.vue new file mode 100644 index 00000000..90a9336e --- /dev/null +++ b/src/components/SettingsInputText.vue @@ -0,0 +1,98 @@ +<!-- + - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <form @submit.prevent=""> + <div class="input-wrapper"> + <label :for="id">{{ label }}</label> + <input :id="id" type="text" :value="inputVal" + :disabled="disabled" @input="$emit('input', $event.target.value)"> + <input type="submit" class="icon-confirm" value="" + @click="$emit('update', inputVal)"> + </div> + <p v-if="hint !== ''" class="hint"> + {{ hint }} + </p> + </form> +</template> + +<script> +let uuid = 0 +export default { + name: 'SettingsInputText', + props: { + label: { + type: String, + required: true + }, + hint: { + type: String, + default: '' + }, + value: { + type: String, + default: '' + }, + disabled: { + type: Boolean, + default: false + } + }, + data() { + return { + inputVal: this.value + } + }, + computed: { + id() { + return 'settings-input-text-' + this.uuid + } + }, + watch: { + value(newVal) { + this.inputVal = this.value + } + }, + beforeCreate: function() { + this.uuid = uuid.toString() + uuid += 1 + } +} +</script> + +<style scoped> + .input-wrapper { + display: flex; + flex-wrap: wrap; + width: 100%; + max-width: 400px; + } + label { + width: 100%; + } + input[type=text] { + flex-grow: 1; + } + .hint { + color: var(--color-text-lighter); + } +</style> diff --git a/src/components/SettingsSelectGroup.vue b/src/components/SettingsSelectGroup.vue new file mode 100644 index 00000000..ccf945d6 --- /dev/null +++ b/src/components/SettingsSelectGroup.vue @@ -0,0 +1,125 @@ +<!-- + - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <multiselect v-model="inputValObjects" + :options="Object.values(groups)" :options-limit="5" + :placeholder="label" + track-by="id" + label="displayname" + class="multiselect-vue" :multiple="true" + :close-on-select="false" :tag-width="60" + :disabled="disabled" @input="update" + @search-change="asyncFindGroup"> + <span slot="noResult">{{ t('settings', 'No results') }}</span> + </multiselect> +</template> + +<script> +import axios from 'nextcloud-axios' +import Multiselect from 'nextcloud-vue/dist/Components/Multiselect' + +let uuid = 0 +export default { + name: 'SettingsSelectGroup', + components: { + Multiselect + }, + props: { + label: { + type: String, + required: true + }, + hint: { + type: String, + default: '' + }, + value: { + type: Array, + default: () => [] + }, + disabled: { + type: Boolean, + default: false + } + }, + data() { + return { + inputValObjects: [], + groups: [] + } + }, + computed: { + id() { + return 'settings-select-group-' + this.uuid + } + }, + watch: { + value(newVal) { + this.inputValObjects = this.getValueObject() + } + }, + created: function() { + this.uuid = uuid.toString() + uuid += 1 + this.asyncFindGroup('').then((result) => { + this.inputValObjects = this.getValueObject() + }) + }, + methods: { + getValueObject() { + return this.value.filter((group) => group !== '' && typeof group !== 'undefined').map( + (id) => { + if (typeof this.groups[id] === 'undefined') { + return { + id: id, + displayname: id + } + } + return this.groups[id] + } + ) + }, + update() { + this.$emit('input', this.inputValObjects.map((element) => element.id)) + }, + asyncFindGroup(query) { + query = typeof query === 'string' ? encodeURI(query) : '' + return axios.get(OC.linkToOCS(`cloud/groups/details?search=${query}&limit=1`, 2)) + .then((response) => { + if (Object.keys(response.data.ocs.data.groups).length > 0) { + response.data.ocs.data.groups.forEach((element) => { + if (typeof this.groups[element.id] === 'undefined') { + this.groups[element.id] = element + } + }) + + return true + } + return false + }).catch((error) => { + this.$emit('error', error) + }) + } + } +} +</script> diff --git a/src/components/SettingsSelectTag.vue b/src/components/SettingsSelectTag.vue new file mode 100644 index 00000000..352d5546 --- /dev/null +++ b/src/components/SettingsSelectTag.vue @@ -0,0 +1,200 @@ +<!-- + - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <multiselect v-model="inputValObjects" + :options="tags" :options-limit="5" + :placeholder="label" + track-by="id" + :custom-label="tagLabel" + class="multiselect-vue" :multiple="true" + :close-on-select="false" :tag-width="60" + :disabled="disabled" @input="update" + @search-change="asyncFind"> + <span slot="noResult">{{ t('settings', 'No results') }}</span> + <template #option="scope"> + {{ tagLabel(scope.option) }} + </template> + </multiselect> +</template> + +<script> +import axios from 'nextcloud-axios' + +import { generateRemoteUrl } from 'nextcloud-router' +import Multiselect from 'nextcloud-vue/dist/Components/Multiselect' + +const xmlToJson = (xml) => { + let obj = {} + + if (xml.nodeType === 1) { + if (xml.attributes.length > 0) { + obj['@attributes'] = {} + for (let j = 0; j < xml.attributes.length; j++) { + const attribute = xml.attributes.item(j) + obj['@attributes'][attribute.nodeName] = attribute.nodeValue + } + } + } else if (xml.nodeType === 3) { + obj = xml.nodeValue + } + + if (xml.hasChildNodes()) { + for (let i = 0; i < xml.childNodes.length; i++) { + const item = xml.childNodes.item(i) + const nodeName = item.nodeName + if (typeof (obj[nodeName]) === 'undefined') { + obj[nodeName] = xmlToJson(item) + } else { + if (typeof obj[nodeName].push === 'undefined') { + var old = obj[nodeName] + obj[nodeName] = [] + obj[nodeName].push(old) + } + obj[nodeName].push(xmlToJson(item)) + } + } + } + return obj +} + +const parseXml = (xml) => { + let dom = null + try { + dom = (new DOMParser()).parseFromString(xml, 'text/xml') + } catch (e) { + console.error('Failed to parse xml document', e) + } + return dom +} + +const xmlToTagList = (xml) => { + let json = xmlToJson(parseXml(xml)) + let list = json['d:multistatus']['d:response'] + let result = [] + for (let index in list) { + let tag = list[index]['d:propstat'] + + if (tag['d:status']['#text'] !== 'HTTP/1.1 200 OK') { + continue + } + result.push({ + id: tag['d:prop']['oc:id']['#text'], + displayName: tag['d:prop']['oc:display-name']['#text'], + canAssign: tag['d:prop']['oc:can-assign']['#text'] === 'true', + userAssignable: tag['d:prop']['oc:user-assignable']['#text'] === 'true', + userVisible: tag['d:prop']['oc:user-visible']['#text'] === 'true' + }) + } + return result +} + +const searchTags = function() { + return axios({ + method: 'PROPFIND', + url: generateRemoteUrl('dav') + '/systemtags/', + data: `<?xml version="1.0"?> + <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"> + <d:prop> + <oc:id /> + <oc:display-name /> + <oc:user-visible /> + <oc:user-assignable /> + <oc:can-assign /> + </d:prop> + </d:propfind>` + }).then((response) => { + return xmlToTagList(response.data) + }) +} + +let uuid = 0 +export default { + name: 'SettingsSelectTag', + components: { + Multiselect + }, + props: { + label: { + type: String, + required: true + }, + hint: { + type: String, + default: '' + }, + value: { + type: Array, + default: () => [] + }, + disabled: { + type: Boolean, + default: false + } + }, + data() { + return { + inputValObjects: [], + tags: [] + } + }, + computed: { + id() { + return 'settings-input-text-' + this.uuid + } + }, + watch: { + value(newVal) { + this.inputValObjects = this.getValueObject() + } + }, + beforeCreate: function() { + this.uuid = uuid.toString() + uuid += 1 + searchTags().then((result) => { + this.tags = result + this.inputValObjects = this.getValueObject() + }) + }, + methods: { + asyncFind(query) { + }, + getValueObject() { + return this.value.filter((tag) => tag !== '').map( + (id) => this.tags.find((tag) => tag.id === id) + ) + }, + update() { + this.$emit('input', this.inputValObjects.map((element) => element.id)) + }, + tagLabel({ displayName, userVisible, userAssignable }) { + if (userVisible === false) { + return `${displayName} (invisible)` + } + if (userAssignable === false) { + return `${displayName} (restricted)` + } + return displayName + } + } +} +</script> |