diff --git a/src/components/Filters.vue b/src/components/Filters.vue index c9e4c2d040dce593d55c60afe92e523706f576f4..07d21b79452501672718b164afd2349f262ef871 100644 --- a/src/components/Filters.vue +++ b/src/components/Filters.vue @@ -1,21 +1,6 @@ <template> <b-menu> <div> - <b-menu-list label="minimum performance"> - <b-slider - v-model="perfValue" - :min="0" :max="3" - :tooltip="false" - ticks - lazy - @change="changeTag" - > - <b-slider-tick :value="0">10</b-slider-tick> - <b-slider-tick :value="1">100</b-slider-tick> - <b-slider-tick :value="2">1000</b-slider-tick> - <b-slider-tick :value="3">10000</b-slider-tick> - </b-slider> - </b-menu-list> <b-menu-list v-for="category in tags" v-bind:key="category.name" :label="category.name"> <div v-for="tag in category.tags" v-bind:key="tag" class="field"> <b-checkbox diff --git a/src/components/ModelTable.vue b/src/components/ModelTable.vue index 817fec6a537a2b2557da157dee4a970db9da11e1..f080a5eb9bde06b222c3f4c5be549738f48f1480 100644 --- a/src/components/ModelTable.vue +++ b/src/components/ModelTable.vue @@ -6,7 +6,7 @@ </b-table-column> <b-table-column field="vote" label="Votes" sortable> - {{ props.row.vote }} + {{ props.row.votes }} </b-table-column> <b-table-column field="date" label="Last modification" centered sortable> @@ -20,7 +20,7 @@ <b-table-column label="" centered> <b-button class="actionButton" icon-left="eye" type="is-info" tag="router-link" :to="{ name: 'Model', query: { id: props.row.id } }" outlined/> <b-button class="actionButton" icon-left="pencil" type="is-warning" tag="router-link" :to="{ name: 'ModelEdit', params: {model: props.row} }" outlined/> - <b-button class="actionButton" icon-left="delete" type="is-danger" @click="removePrompt(props.row.id)" outlined/> + <b-button class="actionButton" icon-left="delete" type="is-danger" @click="removePrompt(props.row.id, props.row.name)" outlined/> </b-table-column> </template> </b-table> @@ -53,31 +53,30 @@ export default { edit (id) { }, - removePrompt (id) { - const name = this.models[id].name + removePrompt (id, name) { this.$buefy.dialog.confirm({ message: 'Delete ' + name + ' ?', cancelText: 'Abort', confirmText: 'Delete', type: 'is-danger', hasIcon: true, - onConfirm: () => this.remove(id) + onConfirm: () => this.remove(id, name) }) }, - async remove (id) { - const url = this.$$serverurl + 'models' + '?id=' + id - const name = this.models[id].name + async remove (id, name) { + const token = await localStorage.getItem('token') + const url = this.$serverurl + 'models' + '?id=' + id try { await fetch(url, { method: 'DELETE', headers: { - Authorization: 'Bearer ' + this.token + Authorization: 'Bearer ' + token } }) this.$buefy.toast.open(name + ' deleted') - this.getData() + this.getModels() } catch (error) { - this.$buefy.toast.open('Delete error') + this.$buefy.toast.open('Delete error ' + error) } } } diff --git a/src/components/ModelUpload.vue b/src/components/ModelUpload.vue index e535dc5dd8b15b8c7104c188b2fd1663048ab2b7..b67ffa0c328a07cfee2fbead41436947849060b3 100644 --- a/src/components/ModelUpload.vue +++ b/src/components/ModelUpload.vue @@ -3,7 +3,7 @@ <b-progress :value="progress" size="is-large" show-value :type="status"> {{statusMessage}} </b-progress> - <b-progress v-if="subProgress" size="is-medium"> + <b-progress v-if="subProgress" size="is-large"> {{subMessage}} </b-progress> <b-button @@ -116,8 +116,7 @@ export default { shortDescription: this.model.shortDescription, longDescription: this.model.longDescription, performance: this.model.performance, - tags: this.model.tags, - customLayers: this.model.customLayers + tags: this.model.tags }) }) return await response.json() @@ -127,10 +126,11 @@ export default { }, async uploadModelFile () { const data = new FormData() - const url = this.$serverurl + 'models?id=' + this.modelId + '/upload' + const url = this.$serverurl + 'models/upload' const token = await localStorage.getItem('token') - data.append('model', this.model.file) + data.append('file', this.model.file) + data.append('id', this.modelId) try { const response = await fetch(url, { @@ -152,14 +152,17 @@ export default { for (var i = 0; i < this.model.customLayers.length; i++) { const layer = this.model.customLayers[i] const data = new FormData() - const url = this.$serverurl + 'models?id=' + this.modelId + '/layer?id=' + i + const url = this.$serverurl + 'models/uploadLayer' this.subMessage = 'Uploading ' + layer.name - console.log('Upload layers ' + i + ' | ' + layer.file) + console.log('Uploading layer ' + i + ' | ' + layer.file) if (layer.file === undefined) continue + if (layer.name === undefined) continue - data.append('layer', layer.file) + data.append('file', layer.file) + data.append('name', layer.name) + data.append('id', this.modelId) try { const response = await fetch(url, { diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 11b96f9680e71a1e9ecf778c41124f4f23a915e4..39801c581ec8953f8298571b061abff5444bea31 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -106,6 +106,8 @@ export default { logout () { this.isLogged = false localStorage.removeItem('token') + localStorage.removeItem('user_role') + localStorage.removeItem('user_id') this.$router.push({ name: 'Home' }) } } diff --git a/src/router/index.js b/src/router/index.js index 0e97d71d61db92392316d137351afac962c9353f..25e57a8fef78b0c158ab7043d2973d089be7c588 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -59,6 +59,14 @@ const routes = [ meta: { requiresAuth: true } + }, + { + path: '/admin', + name: 'Admin', + component: () => import('../views/Admin.vue'), + meta: { + requiresAuth: true + } } ] diff --git a/src/views/Account.vue b/src/views/Account.vue index dd88602c61d38ad968de53bb7dd1a4881d198d01..67c5b826ccb88c30919469facc563106a9e421e5 100644 --- a/src/views/Account.vue +++ b/src/views/Account.vue @@ -19,6 +19,16 @@ > Change account details </b-button> + + <b-button + v-if="account.role == 'ROLE_ADMIN'" + icon-left="cctv" + type="is-info" + tag="router-link" + :to="{ name: 'Admin' }" + > + Admin panel + </b-button> </div> </div> <div class="box"> @@ -43,6 +53,8 @@ export default { }, async mounted () { this.account = await this.getAccount() + await localStorage.setItem('user_role', this.account.role) + await localStorage.setItem('user_id', this.account.id) }, methods: { async getAccount () { diff --git a/src/views/Admin.vue b/src/views/Admin.vue new file mode 100644 index 0000000000000000000000000000000000000000..317f7238a4c35c33864829fbe05f93c20544e09f --- /dev/null +++ b/src/views/Admin.vue @@ -0,0 +1,95 @@ +<template> + <div class="adminDiv container"> + <div class="box"> + <h1 class="title">Admin panel</h1> + <b-tabs> + <b-tab-item label="Models"> + <b-table :data="modelList" :loading="isLoading" striped hoverable> + <template slot-scope="props"> + <b-table-column field="name" label="Model name" searchable sortable> + {{ props.row.name }} + </b-table-column> + + <b-table-column field="vote" label="Votes" sortable> + {{ props.row.votes }} + </b-table-column> + + <b-table-column field="date" label="Last modification" centered sortable> + {{ new Date(props.row.modificationDate).toLocaleDateString() }} + </b-table-column> + + <b-table-column field="date" label="Added" centered sortable> + {{ new Date(props.row.addedDate).toLocaleDateString() }} + </b-table-column> + + <b-table-column label="" centered> + <b-button class="actionButton" icon-left="delete" type="is-danger" @click="removeUserPrompt(props.row.id, props.row.name)" outlined/> + </b-table-column> + </template> + </b-table> + </b-tab-item> + + <b-tab-item label="Users"> + <b-table :data="userList" :loading="isLoading" striped hoverable> + <template slot-scope="props"> + <b-table-column field="username" label="Username" searchable sortable> + {{ props.row.name }} + </b-table-column> + + <b-table-column field="email" label="Email" searchable sortable> + {{ props.row.name }} + </b-table-column> + + <b-table-column label="" centered> + <b-button class="actionButton" icon-left="delete" type="is-danger" @click="removeModelPrompt(props.row.id, props.row.name)" outlined/> + </b-table-column> + </template> + </b-table> + </b-tab-item> + </b-tabs> + </div> + </div> +</template> + +<script> +export default { + name: 'Admin', + data () { + return { + isLoading: true, + userList: null, + modelList: null + } + }, + async mounted () { + this.modelList = await this.getModelList() + this.userList = await this.getUserList() + }, + methods: { + async getModelList () { + const token = await localStorage.getItem('token') + const url = this.$serverurl + 'user/list' + try { + const response = await fetch(url, { headers: { Authorization: 'Bearer ' + token } }) + return await response.json() + } catch (error) { + return null + } + }, + async getUserList () { + const token = await localStorage.getItem('token') + const url = this.$serverurl + 'model/list' + try { + const response = await fetch(url, { headers: { Authorization: 'Bearer ' + token } }) + return await response.json() + } catch (error) { + return null + } + } + } +} +</script> + +<style> + +</style> diff --git a/src/views/Model.vue b/src/views/Model.vue index 5687df5c0371c15d8acd9fc44fc2126d002414a4..caa5690a3be524994429570c813bc31dd9f6e409 100644 --- a/src/views/Model.vue +++ b/src/views/Model.vue @@ -14,7 +14,12 @@ </b-taglist> </div> <div class="column infoColumn"> - <h1 class="title is-4">{{model.vote}} votes <b-button size="is-small" icon-left="heart-outline" style="top:-2px"/></h1> + <div v-if="isAuthor"> + <b-button icon-left="pencil" type="is-warning" tag="router-link" :to="{ name: 'ModelEdit', params: {model: model} }">Edit model</b-button> + <br> + <br> + </div> + <h1 class="title is-4">{{model.votes}} votes <b-button size="is-small" icon-left="heart-outline" style="top:-2px"/></h1> <small>Author: </small><strong>{{model.author.username}}</strong> <br> <small>Added: </small><strong>{{new Date(model.addedDate).toLocaleDateString()}}</strong> @@ -66,7 +71,8 @@ export default { return { model: '', isError: false, - isLoading: true + isLoading: true, + isAuthor: false } }, computed: { @@ -79,6 +85,12 @@ export default { this.model = await this.getModel(id) if (this.model == null) this.isError = true this.isLoading = false + + const userId = await localStorage.getItem('user_id') + if (parseInt(userId) === this.model.author.id) this.isAuthor = true + + const userRole = await localStorage.getItem('user_role') + if (userRole === 'ROLE_ADMIN') this.isAuthor = true }, methods: { async getModel (id) { diff --git a/src/views/ModelEdit.vue b/src/views/ModelEdit.vue index d088c265c0990f1e798826cda221bf8821e6af29..f15565d94009b36cbbede21e70447d1d6a1705b1 100644 --- a/src/views/ModelEdit.vue +++ b/src/views/ModelEdit.vue @@ -13,12 +13,12 @@ </b-field> <b-field label="Long description"> - <markdownEditor v-bind:input="model.longDescription"/> + <markdownEditor v-bind:input="model.longDescription" v-on:update-description="model.longDescription = $event"/> </b-field> <hr> <h1 class="title">Tags</h1> - <tagEditor v-bind:tags="model.tags"/> + <tagEditor v-bind:modelTags="model.tags" v-on:update-tags="model.tags = $event"/> <hr> <h1 class="title">Files</h1> @@ -35,16 +35,22 @@ </div> </section> </b-upload> + <span v-if="model.file" class="file-name">{{ model.file.name }}</span> </b-field> </div> <div class="column rightColumn"> - <layersEditor v-bind:layers="model.customLayers"/> + <layersEditor v-bind:layers="model.customLayers" v-on:update-layers="model.customLayers = $event"/> </div> </div> </div> + <div class="box"> + <b-button type="is-danger" @click="removePrompt" expanded>Delete model</b-button> + </div> + + <div class="box buttons"> <b-button tag="router-link" :to="{ name: 'Account' }">Cancel</b-button> <b-button type="is-success" @click="openUploadModal">Save</b-button> </div> @@ -82,9 +88,45 @@ export default { isUploadModalActive: false } }, + mounted () { + this.model.tags = this.convertTag() + }, methods: { + convertTag () { + var list = [] + this.model.tags.forEach(tag => { + list.push(tag.name) + }) + return list + }, openUploadModal () { this.isUploadModalActive = true + }, + removePrompt () { + this.$buefy.dialog.confirm({ + message: 'Delete ' + this.model.name + ' ?', + cancelText: 'Abort', + confirmText: 'Delete', + type: 'is-danger', + hasIcon: true, + onConfirm: () => this.remove() + }) + }, + async remove () { + const token = await localStorage.getItem('token') + const url = this.$serverurl + 'models' + '?id=' + this.model.id + try { + await fetch(url, { + method: 'DELETE', + headers: { + Authorization: 'Bearer ' + token + } + }) + this.$buefy.toast.open(this.model.name + ' deleted') + this.getModels() + } catch (error) { + this.$buefy.toast.open('Delete error ' + error) + } } } } diff --git a/src/views/Search.vue b/src/views/Search.vue index e547c4f73ac6d1c5c476f3c379ef7ee5fc6decb0..c4fca1d459a76827475eecee2b6e159378bc4689 100644 --- a/src/views/Search.vue +++ b/src/views/Search.vue @@ -16,15 +16,15 @@ expanded /> <b-select placeholder="Order by" icon="filter" @input="setOrder"> - <option value="vote">Most vote</option> - <option value="date">Most recent</option> + <option value="votes">Most vote</option> + <option value="lastModified">Most recent</option> </b-select> </b-field> - <h1 class="title is-6">{{result.total}} results - page {{result.page}}</h1> + <h1 class="title is-6">{{result.totalResult}} results - page {{result.page}} of {{result.totalPage}}</h1> <div v-for="model in result.models" v-bind:key="model.name"> <ModelCard v-bind:model="model"/> </div> - <b-pagination :total="result.total" :current.sync="result.page" :per-page="resultSize" @change="changePage"/> + <b-pagination :total="result.totalResult" :current.sync="result.page" :per-page="resultSize" @change="changePage"/> </div> <div v-else class="column is-four-fifths"> <h1 class="title">no modelo</h1> @@ -95,9 +95,9 @@ export default { const params = new URLSearchParams() if (this.paramSearch != null) params.append('name', this.paramSearch) - if (this.paramTags != null) params.append('tag', this.paramTags) + if (this.paramTags != null) params.append('tags', this.paramTags) if (this.paramPerf != null) params.append('param', this.paramPerf) - if (this.paramOrder != null) params.append('order', this.paramOrder) + if (this.paramOrder != null) params.append('sort', this.paramOrder) params.append('size', this.resultSize) params.append('page', this.page)