Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/richdocuments.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJulius Härtl <jus@bitgrid.net>2019-07-19 20:12:00 +0300
committerJulius Härtl <jus@bitgrid.net>2019-08-30 17:52:19 +0300
commitb848201e582672a0f3f03a1aab560233ca6cad40 (patch)
tree204b30dcb3ee8d3937c80415b797657c46c2464a /src
parent48c627ef0b3c8e7c76890ad2587828d693370c7e (diff)
Start with vue admin panel
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'src')
-rw-r--r--src/admin.js25
-rw-r--r--src/components/AdminSettings.vue161
-rw-r--r--src/components/SettingsCheckbox.vue78
-rw-r--r--src/components/SettingsInputText.vue98
-rw-r--r--src/components/SettingsSelectGroup.vue125
-rw-r--r--src/components/SettingsSelectTag.vue200
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>