diff --git a/vospace-ui-frontend/src/App.vue b/vospace-ui-frontend/src/App.vue index 566eb288a621bd7838658eed94a85ea37ddc0b6f..cc2e8ecfe566c4f3871d28a8eea9ed4c9d7f98ca 100644 --- a/vospace-ui-frontend/src/App.vue +++ b/vospace-ui-frontend/src/App.vue @@ -70,7 +70,7 @@ export default { right: 0; left: 0; background-color: rgba(255, 255, 255, 0.7); - z-index: 1000; + z-index: 100000; } .spinner-wrapper { diff --git a/vospace-ui-frontend/src/api/mock/data/sharing.json b/vospace-ui-frontend/src/api/mock/data/sharing.json new file mode 100644 index 0000000000000000000000000000000000000000..7a000cd100e3a32f8245053570a252adee981a0d --- /dev/null +++ b/vospace-ui-frontend/src/api/mock/data/sharing.json @@ -0,0 +1,4 @@ +{ + "people": ["mario.rossi", "bianca.verdi"], + "groups": ["group1", "group2", "group3"] +} diff --git a/vospace-ui-frontend/src/api/mock/index.js b/vospace-ui-frontend/src/api/mock/index.js index da818a1244c370383e099314882f89336d91deca..dbbddd775e3e1d81b398e8ff0774bf2fbaf1d608 100644 --- a/vospace-ui-frontend/src/api/mock/index.js +++ b/vospace-ui-frontend/src/api/mock/index.js @@ -4,6 +4,7 @@ import folder2 from './data/nodes/folder2'; import job from './data/job'; import jobs from './data/jobs'; import user from './data/user'; +import sharing from './data/sharing'; import store from '../../store'; @@ -59,6 +60,12 @@ export default { return fetch({}); }, keepalive() { + return fetch({}, false); + }, + getSharingInfo() { + return fetch(sharing); + }, + setNodeGroups() { return fetch({}); } } diff --git a/vospace-ui-frontend/src/api/server/index.js b/vospace-ui-frontend/src/api/server/index.js index 23878170ddaacd4370fc4d8efa7899a2addbeee7..48974f142350ec9cfd3ff11697b0f27b7f072981 100644 --- a/vospace-ui-frontend/src/api/server/index.js +++ b/vospace-ui-frontend/src/api/server/index.js @@ -152,5 +152,28 @@ export default { 'Cache-Control': 'no-cache' } }, false, false); + }, + getSharingInfo() { + let url = BASE_API_URL + 'sharing'; + return apiRequest({ + method: 'GET', + url: url, + withCredentials: true, + headers: { + 'Cache-Control': 'no-cache' + } + }, true, true); + }, + setNodeGroups(data) { + let url = BASE_API_URL + 'sharing'; + return apiRequest({ + method: 'POST', + url: url, + withCredentials: true, + headers: { + 'Cache-Control': 'no-cache' + }, + data + }, true, true); } } diff --git a/vospace-ui-frontend/src/components/modal/ConfirmDeleteModal.vue b/vospace-ui-frontend/src/components/modal/ConfirmDeleteModal.vue index 69c448edca0e003b87bdcdcb4ab0e2dfb02a756e..720ff679070abb017f32f7bf9fbcdce870641b1e 100644 --- a/vospace-ui-frontend/src/components/modal/ConfirmDeleteModal.vue +++ b/vospace-ui-frontend/src/components/modal/ConfirmDeleteModal.vue @@ -1,5 +1,5 @@ <template> -<b-modal id="confirm-delete-modal" title="Confirm delete" okTitle="Yes, delete" @ok="deleteNodes" @hidden="reset" ok-variant="danger"> +<b-modal id="confirm-delete-modal" title="Confirm delete" okTitle="Yes, delete" @ok.prevent="deleteNodes" @hidden="reset" ok-variant="danger"> <p>Do you really want to delete the following nodes?</p> <p> <ul> @@ -19,10 +19,7 @@ export default { reset() { this.$store.commit('setNodesToDelete', []); }, - deleteNodes(event) { - // Prevent modal from closing - event.preventDefault(); - + deleteNodes() { this.$store.dispatch('deleteNodes') .then(() => { this.$bvModal.hide('confirm-delete-modal'); diff --git a/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue b/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue index d671009b8210fe5ee68e78aa6e3ea967a3e08cc7..c68e8b5f63fe41c7a2c01eb3684e3e99cc9bec93 100644 --- a/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue +++ b/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue @@ -1,5 +1,5 @@ <template> -<b-modal id="create-folder-modal" title="Create folder" okTitle="Create" @show="reset" @shown="afterShow" @ok="createFolder"> +<b-modal id="create-folder-modal" title="Create folder" okTitle="Create" @show="reset" @shown="afterShow" @ok.prevent="createFolder"> <b-form inline> <label class="w-25" for="new-folder-name-input">Folder name</label> <b-form-input v-model.trim="newFolderName" id="new-folder-name-input" ref="newFolderNameInput" class="w-75" aria-describedby="new-folder-name-input-feedback" :state="newFolderNameState" v-on:input="resetError" @@ -37,10 +37,7 @@ export default { resetError() { this.newFolderNameError = null; }, - createFolder(event) { - // Prevent modal from closing - event.preventDefault(); - + createFolder() { if (!this.newFolderName) { this.newFolderNameError = "Folder name is required"; } else if (/[<>?":\\/`|'*]/.test(this.newFolderName)) { diff --git a/vospace-ui-frontend/src/components/modal/ShareInputGroup.vue b/vospace-ui-frontend/src/components/modal/ShareInputGroup.vue new file mode 100644 index 0000000000000000000000000000000000000000..b721bdbe04c2687ad1cf83db53d88ea271a95208 --- /dev/null +++ b/vospace-ui-frontend/src/components/modal/ShareInputGroup.vue @@ -0,0 +1,71 @@ +<template> +<div> + <div class="row"> + <div class="col-2"> + <label :for="id + '-input'" class="mt-2 float-right"><strong>{{label}}</strong></label> + </div> + <div class="col"> + <b-input-group> + <b-form-input :list="id + '-list'" :id="id + '-input'" :placeholder="placeholder" v-model="inputValue" @input="resetError()" @keydown.native.enter="addItem()"></b-form-input> + <datalist :id="id + '-list'"> + <option v-for="option in options" v-bind:key="option">{{option}}</option> + </datalist> + <b-input-group-append> + <b-button variant="success" @click="addItem()">{{buttonText}}</b-button> + </b-input-group-append> + </b-input-group> + <span class="text-danger" v-if="inputError">{{inputError}}</span> + </div> + </div> + <div class="row mb-3 mt-1"> + <div class="col offset-2"> + <b-badge variant="primary" v-for="value in values" size="lg" v-bind:key="value" class="mr-1"> + {{value}} + <span class="pointer" @click="removeItem(value)">×</span> + </b-badge> + </div> + </div> +</div> +</template> + +<script> +export default { + props: { + id: String, + values: Array, + options: Array, + label: String, + placeholder: String, + buttonText: String, + }, + data() { + return { + inputValue: null, + inputError: null, + } + }, + methods: { + addItem() { + if (this.options.indexOf(this.inputValue) === -1) { + this.inputError = "Please select one of the available values from the list"; + return; + } + const index = this.values.indexOf(this.inputValue); + if (index === -1) { + this.values.push(this.inputValue); + } + this.inputError = null; + this.inputValue = null; + }, + resetError() { + this.inputError = null; + }, + removeItem(value) { + const index = this.values.indexOf(value); + if (index > -1) { + this.values.splice(index, 1); + } + } + } +} +</script> diff --git a/vospace-ui-frontend/src/components/modal/ShareModal.vue b/vospace-ui-frontend/src/components/modal/ShareModal.vue index ae9c9dff8f9cae057c27ad6f51d3b213ca7a3849..c07803d8c47865f7f112f9c77bdd5dab94cb11ea 100644 --- a/vospace-ui-frontend/src/components/modal/ShareModal.vue +++ b/vospace-ui-frontend/src/components/modal/ShareModal.vue @@ -1,82 +1,51 @@ <template> -<b-modal id="share-modal" :title="'Share ' + node.path" okTitle="Share" @show="reset" @ok="share" size="lg"> +<b-modal id="share-modal" :title="'Share ' + node.path" okTitle="Save" @show="onShow" @ok.prevent="share" size="lg"> <h5>Read permissions</h5> - <div class="row"> - <div class="col-2"> - <label for="user-read-input" class="mt-2 float-right"><strong>Users</strong></label> - </div> - <div class="col"> - <b-input-group> - <b-form-input list="user-read-list" id="user-read-input" placeholder="Search user..."></b-form-input> - <datalist id="user-read-list"> - <option v-for="user in selectableUserRead" v-bind:key="user.id">{{user.label}}</option> - </datalist> - <b-input-group-append> - <b-button variant="success">Add user</b-button> - </b-input-group-append> - </b-input-group> - </div> - </div> - <div class="row"> - <div class="col offset-2"> - <b-badge variant="light" v-for="user in userRead" v-bind:key="user">{{user}}</b-badge> - </div> - </div> - <div class="row"> - <div class="col-2"> - <label for="group-read-input" class="mt-2 float-right"><strong>Groups</strong></label> - </div> - <div class="col"> - <b-input-group> - <b-form-input list="group-read-list" id="group-read-input" placeholder="Search group..."></b-form-input> - <datalist id="group-read-list"> - <option v-for="group in selectableGroupRead" v-bind:key="group">{{group}}</option> - </datalist> - <b-input-group-append> - <b-button variant="success">Add group</b-button> - </b-input-group-append> - </b-input-group> - </div> - </div> - <div class="row"> - <div class="col offset-2"> - <b-badge variant="primary" v-for="group in groupRead" size="lg" v-bind:key="group"> - {{group}} - <span class="pointer" @click="removeItem(groupRead, group)">×</span> - </b-badge> - </div> - </div> + <ShareInputGroup id="user-read" :values="userRead" :options="people" label="Users" placeholder="Search user..." buttonText="Add user" /> + <ShareInputGroup id="group-read" :values="groupRead" :options="groups" label="Groups" placeholder="Search group..." buttonText="Add group" /> <h5 class="mt-3">Write permissions</h5> - <b-form inline> - </b-form> + <ShareInputGroup id="user-write" :values="userWrite" :options="people" label="Users" placeholder="Search user..." buttonText="Add user" /> + <ShareInputGroup id="group-write" :values="groupWrite" :options="groups" label="Groups" placeholder="Search group..." buttonText="Add group" /> </b-modal> </template> <script> +import ShareInputGroup from './ShareInputGroup'; +import client from 'api-client'; + +function setArray(array, newValues) { + array.splice(0, array.length); + for (let value of newValues) { + array.push(value); + } +} + export default { + components: { + ShareInputGroup + }, computed: { node() { return this.$store.state.nodeToShare; } }, data() { return { - userReadInput: null, - groupReadInput: null, - selectableUserRead: [], - selectableGroupRead: [], groupRead: [], userRead: [], - userWriteInput: null, - groupWriteInput: null, - selectableUserWrite: [], - selectableGroupWrite: [], groupWrite: [], - userWrite: [] + userWrite: [], + people: [], + groups: [] } }, methods: { - reset() { + onShow() { this.setGroups(this.userRead, this.groupRead, this.node.groupRead); this.setGroups(this.userWrite, this.groupWrite, this.node.groupWrite); + client.getSharingInfo() + .then(res => { + setArray(this.people, res.people); + setArray(this.groups, res.groups); + }); }, setGroups(userArr, groupArr, nodeArr) { userArr.splice(0, userArr.length); @@ -84,25 +53,23 @@ export default { for (let group of nodeArr.split(' ')) { if (group.startsWith('people.')) { userArr.push(group.substring('people.'.length).replaceAll('\\\\', '\\')); - } else { + } else if (group.trim() !== '') { groupArr.push(group.replaceAll('\\\\', '\\')); } } }, - addItem(arr, value) { - const index = arr.indexOf(value); - if (index === -1) { - arr.push(value); - } - }, - removeItem(arr, value) { - const index = arr.indexOf(value); - if (index > -1) { - arr.splice(index, 1); - } - }, share() { - // TODO + client.setNodeGroups({ + path: this.$store.state.nodeToShare.path, + groupRead: this.groupRead, + groupWrite: this.groupWrite, + userRead: this.userRead, + userWrite: this.userWrite + }).then(() => { + // Reload current path + this.$bvModal.hide('share-modal'); + this.$store.dispatch('setPath', this.$store.state.path); + }); } } } diff --git a/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue b/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue index 849c38b33367e93c68eb73080fd1fb7e41ea696e..4f3e39efde5a5c0bb93abcf53cd9130d3afc745f 100644 --- a/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue +++ b/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue @@ -1,5 +1,5 @@ <template> -<b-modal id="upload-files-modal" title="Upload file" okTitle="Upload" @show="reset" @ok="uploadFiles"> +<b-modal id="upload-files-modal" title="Upload file" okTitle="Upload" @show="reset" @ok.prevent="uploadFiles"> <b-form-file v-model="files" :multiple="true" :state="fileState" placeholder="Choose your files or drop them here..." drop-placeholder="Drop files here..." @change="resetError"></b-form-file> <b-form-invalid-feedback id="upload-file-input-feedback" class="text-right">{{uploadFileError}}</b-form-invalid-feedback> <div class="mt-3">Selected files: {{ selectedFiles }}</div> @@ -39,10 +39,7 @@ export default { resetError() { this.uploadFileError = null; }, - uploadFiles(event) { - // Prevent modal from closing - event.preventDefault(); - + uploadFiles() { if (this.files.length === 0) { this.uploadFileError = "Select at least one file"; } else {