diff options
author | Georg Ehrke <developer@georgehrke.com> | 2019-11-27 09:47:47 +0300 |
---|---|---|
committer | Georg Ehrke <developer@georgehrke.com> | 2020-04-17 11:18:18 +0300 |
commit | c0615e3a55885e341bb82e2f8fdb3b2f8e6f49c9 (patch) | |
tree | d1c142a6c99b3ca5409862a2c95769dc72aa0dfb /src | |
parent | 3f3f4af7cbb677abd151e2912cbb99fe81bebcd2 (diff) |
Move to new nextcloud npm packages
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/Admins.vue | 105 | ||||
-rw-r--r-- | src/Encryption.vue | 93 | ||||
-rw-r--r-- | src/Location.vue | 122 | ||||
-rw-r--r-- | src/Shares.vue | 66 | ||||
-rw-r--r-- | src/components/Admins.vue | 165 | ||||
-rw-r--r-- | src/components/Encryption.vue | 137 | ||||
-rw-r--r-- | src/components/Location.vue | 147 | ||||
-rw-r--r-- | src/components/Map.vue (renamed from src/Map.vue) | 0 | ||||
-rw-r--r-- | src/components/Shares.vue | 101 | ||||
-rw-r--r-- | src/main.js | 42 | ||||
-rw-r--r-- | src/nameProvider.js | 2 |
11 files changed, 571 insertions, 409 deletions
diff --git a/src/Admins.vue b/src/Admins.vue deleted file mode 100644 index b3c57fb..0000000 --- a/src/Admins.vue +++ /dev/null @@ -1,105 +0,0 @@ -<template> - <div class="who-has-access"> - <span :class="{ hidden: !isLoading }" class="icon icon-loading" /> - <div v-for="admin in admins" :key="admin.id" class="admin-avatar-container"> - <Avatar :user="admin.internal ? admin.id : null" - :display-name="admin.displayname" :size="64" :is-no-user="!admin.internal" /> - <Actions v-if="!admin.internal"> - <ActionButton icon="icon-close" @click="deleteAdditionalAdmin(admin)"> - {{ t('privacy', 'Remove external admin') }} - </ActionButton> - </Actions> - </div> - - <div v-if="isAdmin"> - <Actions v-if="!isAdding" class="addAdditionalAdmin"> - <ActionButton icon="icon-add" @click="openNewAdmin"> - {{ t('privacy', 'Add external admin') }} - </ActionButton> - </Actions> - <form v-if="isAdding" - v-click-outside="closeNewAdmin" - class="addAdditionalAdminFormContainer" - @submit.prevent="addAdditionalAdmin"> - <input v-model="newAdditionalAdminInputField" type="text" maxlength="64" - autocomplete="new-password" autocorrect="off" autocapitalize="off" - spellcheck="false" :placeholder="additionalAdminPlaceholderLabel"> - <input type="submit" value="" class="icon-confirm"> - <!-- add icon-loading --> - </form> - </div> - </div> -</template> - -<script> -import ClickOutside from 'vue-click-outside' -import HttpClient from 'nextcloud-axios' -import Vue from 'vue' - -import Actions from 'nextcloud-vue/dist/Components/Actions' -import ActionButton from 'nextcloud-vue/dist/Components/ActionButton' -import Avatar from 'nextcloud-vue/dist/Components/Avatar' -import { generateUrl } from 'nextcloud-server/dist/router' - -export default { - name: 'Admins', - components: { - Actions, - ActionButton, - Avatar - }, - directives: { - ClickOutside - }, - data() { - return { - admins: [], - newAdditionalAdminInputField: '', - isAdmin: false, - isLoading: true, - isAdding: false, - isSavingChanges: false - } - }, - mounted() { - this.isAdmin = OC.isUserAdmin() - - const url = generateUrl('/apps/privacy/api/admins') - HttpClient.get(url).then(resp => { - Vue.set(this, 'admins', resp.data) - this.isLoading = false - }) - }, - methods: { - openNewAdmin() { - setTimeout(() => { - this.isAdding = true - }, 0) - }, - closeNewAdmin() { - this.isAdding = false - this.newAdditionalAdminInputField = '' - }, - addAdditionalAdmin() { - console.warn(this.newAdditionalAdminInputField) - const url = generateUrl('/apps/privacy/api/admins') - this.isSavingChanges = true - - HttpClient.post(url, { name: this.newAdditionalAdminInputField }).then(resp => { - this.admins.push(resp.data) - - this.isSavingChanges = false - this.isAdding = false - this.newAdditionalAdminInputField = '' - }) - }, - deleteAdditionalAdmin(admin) { - const url = generateUrl('/apps/privacy/api/admins/{id}', { id: admin.id }) - HttpClient.delete(url).then(resp => { - const index = this.admins.indexOf(admin) - this.admins.splice(index, 1) - }) - } - } -} -</script> diff --git a/src/Encryption.vue b/src/Encryption.vue deleted file mode 100644 index 69b330a..0000000 --- a/src/Encryption.vue +++ /dev/null @@ -1,93 +0,0 @@ -<template> - <div class="who-has-access"> - <!-- eslint-disable-next-line vue/no-v-html --> - <p v-show="!isEditing" v-html="label" /> - <Actions v-if="isAdmin && !isEditing"> - <ActionButton icon="icon-rename" @click="openEditFullDiskEncryptionForm" /> - </Actions> - <div v-if="isEditing" v-click-outside="cancelEditFullDiskEncryptionForm"> - <form> - <input id="fullDiskEncryptionEnabledCheckbox" v-model="fullDiskEncryptionEnabled" - :disabled="isSavingChanges" type="checkbox" name="fullDiskEncryptionEnabledCheckbox" - class="checkbox" @change="saveFullDiskEncryptionForm"> - <label for="fullDiskEncryptionEnabledCheckbox"> - {{ checkboxLabel }} - </label> - </form> - </div> - </div> -</template> - -<script> -import HttpClient from 'nextcloud-axios' -import ClickOutside from 'vue-click-outside' - -import { generateUrl } from 'nextcloud-server/dist/router' - -import Actions from 'nextcloud-vue/dist/Components/Actions' -import ActionButton from 'nextcloud-vue/dist/Components/ActionButton' - -export default { - name: 'Encryption', - components: { - Actions, - ActionButton - }, - directives: { - ClickOutside - }, - data() { - return { - fullDiskEncryptionEnabled: false, - serverSideEncryptionEnabled: false, - isAdmin: true, - isEditing: false, - isSavingChanges: false - } - }, - computed: { - label() { - if (!this.serverSideEncryptionEnabled && !this.fullDiskEncryptionEnabled) { - return t('privacy', 'Your files are not protected by encryption.') - } else if (this.serverSideEncryptionEnabled && !this.fullDiskEncryptionEnabled) { - return t('privacy', 'Your files are encrypted with {linkopen}server-side-encryption ↗{linkclose}.') - .replace('{linkopen}', '<a href="https://nextcloud.com/blog/encryption-in-nextcloud/" target="_blank" title="" rel="noreferrer noopener">') - .replace('{linkclose}', '</a>') - } else if (!this.serverSideEncryptionEnabled && this.fullDiskEncryptionEnabled) { - return t('privacy', 'This server is protected with full-disk-encryption.') - } else { - return t('privacy', 'Your files are encrypted with {linkopen}server-side-encryption ↗{linkclose}. Additionally, this server is protected with full-disk-encryption.') - .replace('{linkopen}', '<a href="https://nextcloud.com/blog/encryption-in-nextcloud/" target="_blank" title="" rel="noreferrer noopener">') - .replace('{linkclose}', '</a>') - } - }, - checkboxLabel() { - return t('privacy', 'This server is using full-disk-encryption.') - } - }, - created() { - this.fullDiskEncryptionEnabled = this.$parent.fullDiskEncryptionEnabled - this.serverSideEncryptionEnabled = this.$parent.serverSideEncryptionEnabled - this.isAdmin = OC.isUserAdmin() - }, - methods: { - openEditFullDiskEncryptionForm() { - setTimeout(() => { - this.isEditing = true - }, 0) - }, - cancelEditFullDiskEncryptionForm() { - this.isEditing = false - }, - saveFullDiskEncryptionForm() { - const url = generateUrl('/apps/privacy/api/fullDiskEncryption') - this.isSavingChanges = true - - HttpClient.post(url, { enabled: this.fullDiskEncryptionEnabled ? '1' : '0' }).then(resp => { - this.isSavingChanges = false - this.isEditing = false - }) - } - } -} -</script> diff --git a/src/Location.vue b/src/Location.vue deleted file mode 100644 index 47c5b54..0000000 --- a/src/Location.vue +++ /dev/null @@ -1,122 +0,0 @@ -<template> - <div class="where-is-my-data"> - <span v-show="isLoading" class="icon icon-loading" /> - <p v-show="!isEditingLocation && !isLoading"> - <span v-show="country">{{ label }}<strong>{{ country }}.</strong></span> - <span v-show="!country">{{ labelForNoCountry }}</span> - <Actions v-if="isAdmin"> - <ActionButton icon="icon-rename" @click="editLocation"> - {{ t('privacy', 'Change data location') }} - </ActionButton> - </Actions> - </p> - <div v-show="isEditingLocation && !isLoading" class="multiselect-container"> - <Multiselect - :disabled="isSavingChanges" - :options="options" - :searchable="true" - track-by="code" - label="label" - :placeholder="placeholderLabel" - @input="onChange" /> - <span v-show="isSavingChanges" class="icon icon-loading" /> - </div> - <Map v-show="!isLoading" /> - </div> -</template> - -<script> -import Map from './Map.vue' -import HttpClient from 'nextcloud-axios' -import { generateUrl } from 'nextcloud-server/dist/router' - -import Multiselect from 'nextcloud-vue/dist/Components/Multiselect' -import Actions from 'nextcloud-vue/dist/Components/Actions' -import ActionButton from 'nextcloud-vue/dist/Components/ActionButton' -import { - getCountryList, - getNameForCountryCode -} from './nameProvider.js' - -export default { - name: 'Location', - components: { - ActionButton, - Actions, - Map, - Multiselect - }, - data() { - return { - selectedCountry: 'de', - isAdmin: false, - isEditingLocation: false, - isLoading: true, - isSavingChanges: false - } - }, - computed: { - label() { - return t('privacy', 'Your data is located in: ') - }, - labelForNoCountry() { - return t('privacy', 'The admin hasn\'t selected the location of the server yet.') - }, - country() { - return getNameForCountryCode(this.$data.selectedCountry) - }, - options() { - return getCountryList() - }, - placeholderLabel() { - return t('privacy', 'Please select a country') - } - }, - watch: { - selectedCountry: (newCountry, oldCountry) => { - const oldElm = document.querySelector('.where-is-my-data #' + oldCountry) - const newElm = document.querySelector('.where-is-my-data #' + newCountry) - - if (oldElm) { - oldElm.style.fill = null - } - if (newElm) { - newElm.style.fill = 'var(--color-primary)' - } - } - }, - mounted() { - this.isAdmin = OC.isUserAdmin() - const url = generateUrl('/apps/privacy/api/location') - - HttpClient.get(url).then(resp => { - this.selectedCountry = resp.data.code - - if (this.selectedCountry !== '') { - const elm = document.querySelector('.where-is-my-data #' + this.selectedCountry) - if (elm) { - elm.style.fill = '#e6605c' - } - } - - this.isLoading = false - }) - }, - methods: { - editLocation() { - this.isEditingLocation = true - }, - onChange(value) { - const url = generateUrl('/apps/privacy/api/location') - this.isSavingChanges = true - - HttpClient.post(url, { code: value.code }).then(resp => { - this.selectedCountry = value.code - - this.isEditingLocation = false - this.isSavingChanges = false - }) - } - } -} -</script> diff --git a/src/Shares.vue b/src/Shares.vue deleted file mode 100644 index 5693a28..0000000 --- a/src/Shares.vue +++ /dev/null @@ -1,66 +0,0 @@ -<template> - <div class="who-has-access"> - <span :class="{hidden: !isLoading}" class="icon icon-loading" /> - <span :class="{hidden: !isEmptyList}"> - {{ emptyLabel }} - </span> - <Avatar v-for="uid in uniqueShareUIDs" :key="uid" :user="uid" - :display-name="uidDisplaynameMap[uid]" :size="64" /> - </div> -</template> - -<script> -import HttpClient from 'nextcloud-axios' -import Vue from 'vue' - -import { generateOcsUrl } from 'nextcloud-server/dist/router' -import Avatar from 'nextcloud-vue/dist/Components/Avatar' - -export default { - name: 'Shares', - components: { - Avatar - }, - data() { - return { - uniqueShareUIDs: [], - uidDisplaynameMap: {}, - isLoading: true - } - }, - computed: { - isEmptyList() { - return this.isLoading === false && this.uniqueShareUIDs.length === 0 - }, - emptyLabel() { - return t('privacy', 'You don\'t have any shares with individual users.') - } - }, - mounted: function() { - const url = generateOcsUrl('/apps/files_sharing/api/v1/shares?format=json&shared_with_me=false') - const currentUserId = OC.getCurrentUser() - - HttpClient.get(url).then(resp => { - resp.data.ocs.data.forEach((d) => { - if (d.share_with === currentUserId) { - return - } - - switch (d.share_type) { - case 0: - if (this.uniqueShareUIDs.indexOf(d.share_with) === -1) { - this.uniqueShareUIDs.push(d.share_with) - Vue.set(this.uidDisplaynameMap, d.share_with, d.share_with_displayname) - } - break - - default: - break - } - }) - - this.isLoading = false - }) - } -} -</script> diff --git a/src/components/Admins.vue b/src/components/Admins.vue new file mode 100644 index 0000000..0765dcb --- /dev/null +++ b/src/components/Admins.vue @@ -0,0 +1,165 @@ +<!-- + - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - @author Georg Ehrke <oc.list@georgehrke.com> + - + - @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="who-has-access"> + <span :class="{ hidden: !isLoading }" class="icon icon-loading" /> + <div v-for="admin in admins" :key="admin.id" class="admin-avatar-container"> + <Avatar :user="admin.internal ? admin.id : null" + :display-name="admin.displayname" + :size="64" + :is-no-user="!admin.internal" /> + <Actions v-if="!admin.internal"> + <ActionButton icon="icon-close" @click="deleteAdditionalAdmin(admin)"> + {{ $t('privacy', 'Remove external admin') }} + </ActionButton> + </Actions> + </div> + + <div v-if="$is_admin"> + <Actions v-if="!isAdding" class="addAdditionalAdmin"> + <ActionButton icon="icon-add" @click.stop.prevent="openNewAdmin"> + {{ $t('privacy', 'Add external admin') }} + </ActionButton> + </Actions> + <form v-if="isAdding" + v-click-outside="closeNewAdmin" + class="addAdditionalAdminFormContainer" + @submit.prevent="addAdditionalAdmin"> + <input v-model="newAdditionalAdminInputField" + type="text" + maxlength="64" + autocomplete="new-password" + autocorrect="off" + autocapitalize="off" + spellcheck="false" + :placeholder="$t('privacy', 'Name of external admin')"> + <input type="submit" value="" class="icon-confirm"> + <!-- add icon-loading --> + </form> + </div> + </div> +</template> + +<script> +import Vue from 'vue' +import ClickOutside from 'vue-click-outside' +import HttpClient from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import Avatar from '@nextcloud/vue/dist/Components/Avatar' + +export default { + name: 'Admins', + components: { + Actions, + ActionButton, + Avatar, + }, + directives: { + ClickOutside, + }, + data() { + return { + admins: [], + newAdditionalAdminInputField: '', + isLoading: true, + isAdding: false, + isSavingChanges: false, + } + }, + /** + * This function is called on mount of the Vue component + * It sets the isAdmin property and + * loads additional admins from the server + */ + async mounted() { + const url = generateUrl('/apps/privacy/api/admins') + try { + const resp = await HttpClient.get(url) + Vue.set(this, 'admins', resp.data) + } catch (error) { + console.error(error) + this.$toast('Error loading additional administrator.') + } finally { + this.isLoading = false + } + }, + methods: { + /** + * Opens the new Admin dialog + */ + openNewAdmin() { + this.isAdding = true + }, + /** + * Closes the new Admin dialog and resets the input field + */ + closeNewAdmin() { + this.isAdding = false + this.newAdditionalAdminInputField = '' + }, + /** + * Creates an additional (virtual) admin on the server + * + * @returns {Promise<void>} + */ + async addAdditionalAdmin() { + const url = generateUrl('/apps/privacy/api/admins') + this.isSavingChanges = true + + try { + const response = await HttpClient.post(url, { name: this.newAdditionalAdminInputField }) + this.admins.push(response.data) + } catch (error) { + console.error(error) + this.$toast('Error adding new administrator.') + } finally { + this.isSavingChanges = false + this.isAdding = false + this.newAdditionalAdminInputField = '' + } + }, + /** + * Deletes an additional (virtual) admin from the server + * + * @param {Object} admin The admin object from this.admins + * @returns {Promise<void>} + */ + async deleteAdditionalAdmin(admin) { + const url = generateUrl('/apps/privacy/api/admins/{id}', { id: admin.id }) + + try { + await HttpClient.delete(url) + + const index = this.admins.indexOf(admin) + if (index !== -1) { + this.admins.splice(index, 1) + } + } catch (error) { + console.error(error) + this.$toast('Error deleting new administrator.') + } + }, + }, +} +</script> diff --git a/src/components/Encryption.vue b/src/components/Encryption.vue new file mode 100644 index 0000000..ec6d8f5 --- /dev/null +++ b/src/components/Encryption.vue @@ -0,0 +1,137 @@ +<!-- + - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - @author Georg Ehrke <oc.list@georgehrke.com> + - + - @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="who-has-access"> + <!-- eslint-disable-next-line vue/no-v-html --> + <p v-show="!isEditing" v-html="label" /> + <Actions v-if="$is_admin && !isEditing"> + <ActionButton icon="icon-rename" @click.stop.prevent="openEditFullDiskEncryptionForm" /> + </Actions> + <div v-if="isEditing" v-click-outside="cancelEditFullDiskEncryptionForm"> + <form> + <input id="fullDiskEncryptionEnabledCheckbox" + v-model="fullDiskEncryptionEnabled" + :disabled="isSavingChanges" + type="checkbox" + name="fullDiskEncryptionEnabledCheckbox" + class="checkbox" + @change="saveFullDiskEncryptionForm"> + <label for="fullDiskEncryptionEnabledCheckbox"> + {{ $t('privacy', 'This server is using full-disk-encryption.') }} + </label> + </form> + </div> + </div> +</template> + +<script> +import ClickOutside from 'vue-click-outside' + +import HttpClient from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import { loadState } from '@nextcloud/initial-state' + +export default { + name: 'Encryption', + components: { + Actions, + ActionButton, + }, + directives: { + ClickOutside, + }, + data() { + return { + fullDiskEncryptionEnabled: false, + serverSideEncryptionEnabled: false, + isEditing: false, + isSavingChanges: false, + } + }, + computed: { + label() { + if (!this.serverSideEncryptionEnabled && !this.fullDiskEncryptionEnabled) { + return this.$t('privacy', 'Your files are not protected by encryption.') + } else if (this.serverSideEncryptionEnabled && !this.fullDiskEncryptionEnabled) { + return this.$t('privacy', 'Your files are encrypted with {linkopen}server-side-encryption ↗{linkclose}.') + .replace('{linkopen}', '<a href="https://nextcloud.com/blog/encryption-in-nextcloud/" target="_blank" title="" rel="noreferrer noopener">') + .replace('{linkclose}', '</a>') + } else if (!this.serverSideEncryptionEnabled && this.fullDiskEncryptionEnabled) { + return this.$t('privacy', 'This server is protected with full-disk-encryption.') + } else { + return this.$t('privacy', 'Your files are encrypted with {linkopen}server-side-encryption ↗{linkclose}. Additionally, this server is protected with full-disk-encryption.') + .replace('{linkopen}', '<a href="https://nextcloud.com/blog/encryption-in-nextcloud/" target="_blank" title="" rel="noreferrer noopener">') + .replace('{linkclose}', '</a>') + } + }, + }, + /** + * This function is called on mount of the Vue component + * It checks if full-disk-encryption or server-side-encryption + * are enabled + */ + mounted() { + console.debug(loadState('privacy', 'fullDiskEncryptionEnabled')) + console.debug(loadState('privacy', 'serverSideEncryptionEnabled')) + this.fullDiskEncryptionEnabled = loadState('privacy', 'fullDiskEncryptionEnabled') === 1 + this.serverSideEncryptionEnabled = loadState('privacy', 'serverSideEncryptionEnabled') === 1 + }, + methods: { + /** + * Opens the form to edit the full-disk-encryption-state + */ + openEditFullDiskEncryptionForm() { + this.isEditing = true + }, + /** + * Closes the form to edit the full-disk-encryption-state + */ + cancelEditFullDiskEncryptionForm() { + this.isEditing = false + }, + /** + * Saves the new full-disk-encryption-state + * + * @returns {Promise<void>} + */ + async saveFullDiskEncryptionForm() { + const url = generateUrl('/apps/privacy/api/fullDiskEncryption') + this.isSavingChanges = true + + try { + await HttpClient.post(url, { enabled: this.fullDiskEncryptionEnabled ? '1' : '0' }) + } catch (error) { + console.error(error) + this.$toast('Error saving new state of full-disk-encryption') + + // Reset state + this.fullDiskEncryptionEnabled = !this.fullDiskEncryptionEnabled + } finally { + this.isSavingChanges = false + this.isEditing = false + } + }, + }, +} +</script> diff --git a/src/components/Location.vue b/src/components/Location.vue new file mode 100644 index 0000000..17fb131 --- /dev/null +++ b/src/components/Location.vue @@ -0,0 +1,147 @@ +<!-- + - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - @author Georg Ehrke <oc.list@georgehrke.com> + - + - @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="where-is-my-data"> + <p v-show="!isEditingLocation"> + <span v-show="country">{{ $t('privacy', 'Your data is located in: ') }}<strong>{{ country }}.</strong></span> + <span v-show="!country">{{ $t('privacy', 'The admin hasn\'t selected the location of the server yet.') }}</span> + <Actions v-if="$is_admin"> + <ActionButton icon="icon-rename" @click="editLocation"> + {{ t('privacy', 'Change data location') }} + </ActionButton> + </Actions> + </p> + <div v-show="isEditingLocation" class="multiselect-container"> + <Multiselect + :disabled="isSavingChanges" + :options="options" + :searchable="true" + track-by="code" + label="label" + :placeholder="$t('privacy', 'Please select a country')" + @input="onChange" /> + <span v-show="isSavingChanges" class="icon icon-loading" /> + </div> + <Map /> + </div> +</template> + +<script> +import HttpClient from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import { loadState } from '@nextcloud/initial-state' + +import { + getCountryList, + getNameForCountryCode, +} from '../nameProvider.js' +import Map from './Map.vue' + +export default { + name: 'Location', + components: { + ActionButton, + Actions, + Map, + Multiselect, + }, + data() { + return { + selectedCountry: 'de', + isEditingLocation: false, + isSavingChanges: false, + } + }, + computed: { + country() { + return getNameForCountryCode(this.$data.selectedCountry) + }, + options() { + return getCountryList() + }, + }, + watch: { + selectedCountry: (newCountry, oldCountry) => { + if (oldCountry !== '') { + const oldElm = document.querySelector('.where-is-my-data #' + oldCountry) + + if (oldElm) { + oldElm.style.fill = null + } + } + + if (newCountry !== '') { + const newElm = document.querySelector('.where-is-my-data #' + newCountry) + + if (newElm) { + newElm.style.fill = 'var(--color-primary)' + } + } + }, + }, + /** + * This function is called on mount of the Vue component + * It loads the location of the server. + */ + mounted() { + this.selectedCountry = loadState('privacy', 'location') + if (this.selectedCountry !== '') { + const elm = document.querySelector('.where-is-my-data #' + this.selectedCountry) + if (elm) { + elm.style.fill = '#e6605c' + } + } + }, + methods: { + /** + * Opens the edit location form + */ + editLocation() { + this.isEditingLocation = true + }, + /** + * Saves changes to the location form + * + * @param {Object} value The new country object + * @returns {Promise<void>} + */ + async onChange(value) { + const url = generateUrl('/apps/privacy/api/location') + this.isSavingChanges = true + + try { + await HttpClient.post(url, { code: value.code }) + this.selectedCountry = value.code + } catch (error) { + console.error(error) + this.$toast('Error saving new location of the server') + } finally { + this.isEditingLocation = false + this.isSavingChanges = false + } + }, + }, +} +</script> diff --git a/src/Map.vue b/src/components/Map.vue index f70b380..f70b380 100644 --- a/src/Map.vue +++ b/src/components/Map.vue diff --git a/src/components/Shares.vue b/src/components/Shares.vue new file mode 100644 index 0000000..b0079c6 --- /dev/null +++ b/src/components/Shares.vue @@ -0,0 +1,101 @@ +<!-- + - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - @author Georg Ehrke <oc.list@georgehrke.com> + - + - @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="who-has-access"> + <span :class="{hidden: !isLoading}" class="icon icon-loading" /> + <span :class="{hidden: !isEmptyList}"> + {{ $t('privacy', 'You don\'t have any shares with individual users.') }} + </span> + <Avatar v-for="uid in uniqueShareUIDs" + :key="uid" + :user="uid" + :display-name="uidDisplaynameMap[uid]" + :size="64" /> + </div> +</template> + +<script> +import Vue from 'vue' + +import HttpClient from '@nextcloud/axios' +import { generateOcsUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' +import Avatar from '@nextcloud/vue/dist/Components/Avatar' + +export default { + name: 'Shares', + components: { + Avatar, + }, + data() { + return { + uniqueShareUIDs: [], + uidDisplaynameMap: {}, + isLoading: true, + } + }, + computed: { + /** + * Checks if the list of shares is empty + * + * @returns {boolean} + */ + isEmptyList() { + return this.isLoading === false && this.uniqueShareUIDs.length === 0 + }, + }, + /** + * This function is called on mount of the Vue component + * It loads the people shared with + */ + async mounted() { + const url = generateOcsUrl('/apps/files_sharing/api/v1/shares?format=json&shared_with_me=false') + const currentUserId = getCurrentUser().uid + + try { + const resp = await HttpClient.get(url) + resp.data.ocs.data.forEach((d) => { + if (d.share_with === currentUserId) { + return + } + + switch (d.share_type) { + case 0: + if (this.uniqueShareUIDs.indexOf(d.share_with) === -1) { + this.uniqueShareUIDs.push(d.share_with) + Vue.set(this.uidDisplaynameMap, d.share_with, d.share_with_displayname) + } + break + + default: + break + } + }) + } catch (error) { + console.error(error) + this.$toast('Error loading information about shares.') + } finally { + this.isLoading = false + } + }, +} +</script> diff --git a/src/main.js b/src/main.js index a1c4b1a..3602517 100644 --- a/src/main.js +++ b/src/main.js @@ -18,13 +18,15 @@ * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ -import '@babel/polyfill' +import 'core-js/stable' import Vue from 'vue' +import { linkTo } from '@nextcloud/router' +import { translate, translatePlural } from '@nextcloud/l10n' -const Admins = () => import('./Admins.vue') -const Location = () => import('./Location.vue') -const Encryption = () => import('./Encryption.vue') -const Shares = () => import('./Shares.vue') +const Admins = () => import('./components/Admins.vue') +const Location = () => import('./components/Location.vue') +const Encryption = () => import('./components/Encryption.vue') +const Shares = () => import('./components/Shares.vue') // CSP config for webpack dynamic chunk loading // eslint-disable-next-line @@ -35,36 +37,32 @@ __webpack_nonce__ = btoa(OC.requestToken) // OC.generateUrl ensure the index.php (or not) // We do not want the index.php since we're loading files // eslint-disable-next-line -__webpack_public_path__ = OC.linkTo('privacy', 'js/') +__webpack_public_path__ = linkTo('privacy', 'js/') -Vue.prototype.t = t -Vue.prototype.OC = OC +Vue.prototype.$t = translate +Vue.prototype.$n = translatePlural +Vue.prototype.$is_admin = OC.isUserAdmin() +Vue.prototype.$toast = OCP.Toast // eslint-disable-line no-undef + +// The nextcloud-vue package does currently rely on t and n +Vue.prototype.t = translate +Vue.prototype.n = translatePlural const location = new Vue({ el: '#privacy_where_location', - render: h => h(Location) + render: h => h(Location), }) const admins = new Vue({ el: '#privacy_access_admins', - render: h => h(Admins) + render: h => h(Admins), }) const shares = new Vue({ el: '#privacy_access_shares', - render: h => h(Shares) + render: h => h(Shares), }) - -const privacyAccesEncryption = document.getElementById('privacy_access_encryption') -const fullDiskEncryptionEnabled = privacyAccesEncryption.dataset.fullDiskEncryption === '1' -const serverSideEncryptionEnabled = privacyAccesEncryption.dataset.serverSideEncryption === '1' const encryption = new Vue({ el: '#privacy_access_encryption', - data() { - return { - fullDiskEncryptionEnabled, - serverSideEncryptionEnabled - } - }, - render: h => h(Encryption) + render: h => h(Encryption), }) export default { location, admins, shares, encryption } diff --git a/src/nameProvider.js b/src/nameProvider.js index e811207..8a2ee46 100644 --- a/src/nameProvider.js +++ b/src/nameProvider.js @@ -172,7 +172,7 @@ const list = [ { code: 'ye', label: t('privacy', 'Yemen') }, { code: 'za', label: t('privacy', 'South Africa') }, { code: 'zm', label: t('privacy', 'Zambia') }, - { code: 'zw', label: t('privacy', 'Zimbabwe') } + { code: 'zw', label: t('privacy', 'Zimbabwe') }, ] export function getCountryList() { |