diff --git a/.DS_Store b/.DS_Store index 5727624753d0da5ebf79f56abb570840f935382b..0d65cd1f1e144dfed29031c2fecfac290cf4a67a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/frontend/assets/css/main.css b/frontend/assets/css/main.css index 2cfe1b80d47e80f904573138480734954d99e8b8..5a055fabad00d5b3d063b8d5f3d204e9c18a6da6 100644 --- a/frontend/assets/css/main.css +++ b/frontend/assets/css/main.css @@ -9,6 +9,8 @@ cursor: pointer; } + + @media only screen and (min-width: 768px) { .table-width { width: calc(80vw - 260px); @@ -37,6 +39,30 @@ /* Forms */ +.form-title, .form-content, .btn-content, .form-error { + font-size: 0.85rem; + color: #1E889B; + letter-spacing: 0.025rem; + + font-family: 'Open Sans Regular'; + } + + .form-error { + color: red; + font-size: 0.7rem; + font-family: 'Open Sans Bold'; + } + + .btn-content { + color: white; + margin-top: 5px; + margin-left: -5px; + } + + .form-title-margin { + margin-top: -5px; + } + .field-length { width: 16.5rem; height: 2.25rem; @@ -44,20 +70,46 @@ margin-bottom: 0.65rem; } -.form-border, .btn-border { +.form-border, .btn-border, .form-border-error { border-radius: 8px; border: 1px solid #1E889B; } +.form-border-error { + border: 1px solid red; +} + +/* Buttons */ + .btn-action { + cursor: pointer; background-color: #1E889B; height: 2.3rem; } +.btn-disabled { + background-color: gray; +} + .btn-action:hover { background-color: #23B1CB; } +/* Center */ +.center { + align-items: center; + display: flex; + } + + .center-inside { + margin-left: calc(50vw - 8.25rem); + } + + .center-inside-add-user { + margin-left: calc(50vw - 300px); + margin-top: -160px; + } + .page-item .page-link { color: #1E889B !important; diff --git a/frontend/components/root/Loading.vue b/frontend/components/root/Loading.vue new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/frontend/components/root/Navbar.vue b/frontend/components/root/Navbar.vue index 73702c4b74c1b558f36e3cb2566efd892fe62af0..1fd444f44a4b2dfec9a0dd73b8907364825a21cb 100644 --- a/frontend/components/root/Navbar.vue +++ b/frontend/components/root/Navbar.vue @@ -8,7 +8,7 @@ </div> <!-- Role --> <div id="role"> - Role: {{ role }} + Role: {{ role.charAt(0).toUpperCase() + role.slice(1) }} </div> </b-nav-item> @@ -26,64 +26,69 @@ </div> </b-nav-item> - <!-- Edit Dataset --> - <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('edit-dataset')"> - <div id="edit-dataset"> - Edit Dataset - </div> - </b-nav-item> - - <!-- User Management --> - <b-nav-item - v-b-toggle.collapse-user-management - link-classes="side-bar-color mt-3 ml-4"> - <div class="icon-text-wrapper"> - User Management - <i class="ml-3 mt-1 fas fa-caret-down" /> - </div> - </b-nav-item> - - <b-collapse id="collapse-user-management" visible role="tabpanel"> - <!-- Show All Users --> - <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('show-all-users')"> - <div class="icon-text-wrapper"> - <i class="mt-1 mr-3 fas fa-users" /> - <div id="show-all-users"> - Show All Users - </div> + <div v-if="role === 'editor' || role === 'admin'"> + <!-- Edit Dataset --> + <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('edit-dataset')"> + <div id="edit-dataset"> + Edit Dataset </div> </b-nav-item> - - <!-- Add User --> - <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('add-user')"> + </div> + + <div v-if="role === 'admin'"> + <!-- User Management --> + <b-nav-item + v-b-toggle.collapse-user-management + link-classes="side-bar-color mt-3 ml-4" + > <div class="icon-text-wrapper"> - <i class="mt-1 mr-3 fas fa-plus-circle" /> - <div id="add-user"> - Add User - </div> + User Management + <i class="ml-3 mt-1 fas fa-caret-down" /> </div> </b-nav-item> - <!-- Edit User --> - <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('edit-user')"> - <div class="icon-text-wrapper"> - <i class="mt-1 mr-3 fas fa-pen" /> - <div id="edit-user"> - Edit User + <b-collapse id="collapse-user-management" visible role="tabpanel"> + <!-- Show All Users --> + <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('show-all-users')"> + <div class="icon-text-wrapper"> + <i class="mt-1 mr-3 fas fa-users" /> + <div id="show-all-users"> + Show All Users + </div> </div> - </div> - </b-nav-item> - - <!-- Delete User --> - <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('delete-user')"> - <div class="icon-text-wrapper"> - <i class="mt-1 mr-3 fas fa-trash" /> - <div id="delete-user"> - Delete User + </b-nav-item> + + <!-- Add User --> + <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('add-user')"> + <div class="icon-text-wrapper"> + <i class="mt-1 mr-3 fas fa-plus-circle" /> + <div id="add-user"> + Add User + </div> </div> - </div> - </b-nav-item> - </b-collapse> + </b-nav-item> + + <!-- Edit User --> + <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('edit-user')"> + <div class="icon-text-wrapper"> + <i class="mt-1 mr-3 fas fa-pen" /> + <div id="edit-user"> + Edit User + </div> + </div> + </b-nav-item> + + <!-- Delete User --> + <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('delete-user')"> + <div class="icon-text-wrapper"> + <i class="mt-1 mr-3 fas fa-trash" /> + <div id="delete-user"> + Delete User + </div> + </div> + </b-nav-item> + </b-collapse> + </div> </b-nav> </template> @@ -106,13 +111,15 @@ export default { }, watch: { activeElmtID (newElmtID, oldElmtID) { + console.log("newElmtID: ", newElmtID) + console.log("oldElmtID: ", oldElmtID) this.setClass(oldElmtID, '') this.setClass(newElmtID, 'active') } }, mounted () { this.onPathChangeHandler(window.location.pathname) - console.log(/^\/main\/label(\/|(\?)|$)/.test(window.location.pathname)) + // console.log(/^\/main\/label(\/|(\?)|$)/.test(window.location.pathname)) }, methods: { setClass (elmtID, className) { @@ -124,6 +131,7 @@ export default { this.redirectRoute(elmtID) }, redirectRoute (elmtID) { + console.log("elmtID: ", elmtID) switch (elmtID) { case 'label-dataset': this.$nuxt.$router.replace({ path: '/main/label'}) @@ -149,7 +157,9 @@ export default { } }, onPathChangeHandler (browserURL) { + console.log(browserURL) if ((/^\/main\/label(\/|(\?)|$)/.test(browserURL))) { + console.log("dataset panggil") this.changeActiveElmtID('label-dataset') } else if ((/^\/main\/json(\/|(\?)|$)/.test(browserURL))) { this.changeActiveElmtID('json-outputs') diff --git a/frontend/components/user-management/DeleteableTable.vue b/frontend/components/user-management/DeleteableTable.vue index deb91e6509873472dc42036d30d58b60fe3d99e0..4234e34722ed414e725def0bfff39a13d957a19f 100644 --- a/frontend/components/user-management/DeleteableTable.vue +++ b/frontend/components/user-management/DeleteableTable.vue @@ -11,12 +11,14 @@ <!-- Delete row --> <template v-slot:cell(delete)="row"> <div class="icon-layout"> - <i - class="fas fa-trash trash-icon" - @mouseover="row.item['_rowVariant'] = 'danger'" - @mouseleave="row.item['_rowVariant'] = ''" - @click="handleOnDelete(row.item.id, row.item.username)" - /> + <div v-if="row.item['user_id'] !== 1"> + <i + class="margin-icon fas fa-trash trash-icon" + @mouseover="row.item['_rowVariant'] = 'danger'" + @mouseleave="row.item['_rowVariant'] = ''" + @click="handleOnDelete(row.item.user_id, row.item.username)" + /> + </div> </div> </template> </b-table> @@ -42,8 +44,7 @@ export default { var text = name + " will be deleted." this.showDeleteConfirmation("Are You Sure?", text).then((result) => { if (result.value) { - this.deleteRowContent(id) - this.showDeletedAlert() + this.sendDeleteToBackEnd(id) } }) } else { @@ -62,15 +63,21 @@ export default { }) }, + sendDeleteToBackEnd (id) { + var url = '/api/user/' + id + this.$axios.delete(url).then( + this.deleteRowContent(id), + this.showDeletedAlert() + ) + }, + deleteRowContent (id) { for (let idx = 0; idx < this.rows.length; idx++) { - if (this.rows[idx].id === id) { + if (this.rows[idx].user_id === id) { this.rows.splice(idx, 1) break } } - // TO DO: - // Send changes to backend! }, showDeletedAlert () { @@ -104,4 +111,9 @@ export default { display: flex; } + .margin-icon { + margin-right: -30px; + /* margin-lefet: 30px; */ + } + </style> \ No newline at end of file diff --git a/frontend/components/user-management/EditableTable.vue b/frontend/components/user-management/EditableTable.vue index 2536eb01279010d6459c111eacc18adda90ea0f5..18282b6f25d1f487eb902681d557d4b700e47c40 100644 --- a/frontend/components/user-management/EditableTable.vue +++ b/frontend/components/user-management/EditableTable.vue @@ -9,22 +9,24 @@ class="table table-width table-borderless" > <!-- Role in Table --> - <template v-slot:cell(role)="row"> - <div v-if="!row.item.isRoleEditMode" @click="handleRoleChange(row.item.id)"> - {{ row.item.role }} - <div class="icon-layout"> - <i class="fas fa-caret-down icon" /> + <template v-slot:cell(user_role)="row"> + <div v-if="row.item.user_id !== 1"> + <div v-if="!row.item.isRoleEditMode" @click="handleRoleChange(row.item.user_id)"> + {{ row.item.user_role }} + <div class="icon-layout"> + <i class="fas fa-caret-down icon" /> + </div> + </div> + <div v-else> + <b-form-select + class="form-height select-font" + :options="roles" + :value="row.item.user_role" + autofocus + @change.native="handleOnChange($event, row.item.user_id, 'user_role', row.item.user_role)" + @blur.native="row.item.isRoleEditMode = false" + /> </div> - </div> - <div v-else> - <b-form-select - class="form-height select-font" - :options="roles" - :value="row.item.role" - autofocus - @change.native="handleOnChange($event, row.item.id, 'role', row.item.role)" - @blur.native="row.item.isRoleEditMode = false" - /> </div> </template> </b-table> @@ -56,7 +58,7 @@ export default { methods: { handleRoleChange (id) { var index = this.rows.findIndex(function (val) { - return val.id === id + return val.user_id === id }) this.rows[index].isRoleEditMode = true @@ -71,14 +73,23 @@ export default { }) }, - approveConfirmation (value, id, type) { + approveConfirmation (role, id, type) { + var payloadUsername = '' + this.rows.forEach((row) => { - if (row.id === id) { - row[type] = value + if (row.user_id === id) { + row[type] = role + payloadUsername = row['username'] } }) - // TO DO: - // Send changes to backend! + + var url = 'api/user/' + id + this.$axios.put(url, { + username: payloadUsername, + user_role: role.toLowerCase() + }).then( + this.showApprovedAlert() + ).catch(error => console.error(error)) }, showApprovedAlert () { @@ -96,17 +107,17 @@ export default { }, // Type can be: name, email, or department handleOnChange (event, id, type, originalValue) { - var text = "You will change " + originalValue + " to " + event.target.value - this.showConfirmation("Are You Sure?", text).then((result) => { - console.log("Result: ", result) - // If user confirms the changes: - if (result.value) { - this.approveConfirmation(event.target.value, id, type) - this.showApprovedAlert() - } else { - this.showCancelledAlert() - } - }) + if (originalValue !== event.target.value) { + var text = "You will change " + originalValue + " to " + event.target.value + this.showConfirmation("Are You Sure?", text).then((result) => { + // If user confirms the changes: + if (result.value) { + this.approveConfirmation(event.target.value, id, type) + } else { + this.showCancelledAlert() + } + }) + } } } } diff --git a/frontend/config.js b/frontend/config.js index 50550ddac0b5289c3ba9f849981a85c1fa9dc8c9..6db88158609c24e6619aa2f9b073388a5ceed685 100644 --- a/frontend/config.js +++ b/frontend/config.js @@ -1,7 +1,7 @@ // Environment Variables // For development -export const backendURL = "http://localhost:8080" +export const backendURL = "http://localhost:8081" export const frontendURL = 'http://localhost:3000' // For deployment diff --git a/frontend/middleware/auth.js b/frontend/middleware/auth.js index ff2dbcee8273066af145973002fc8d713153be97..82fcaf1c36b4e59abdda90ac46c93b76de1db2ab 100644 --- a/frontend/middleware/auth.js +++ b/frontend/middleware/auth.js @@ -1,3 +1,48 @@ -export default { +// This is a nuxtjs' axios-module configuration +import cookieParser from 'cookieparser' +import cookies from 'js-cookie' + +// This is a javascript middleware which utilizes cookies and JSON web token (JWT) authentication. +export default async function ({ req, route, redirect, store, $axios }) { + + var userData = await getUserInfo({ req, $axios }) + var isUserLoggedIn = !!userData + console.log("userData: ", userData) + + // Then test the url + var urlRequiresNonAuth = route.fullPath === '/' + if (!isUserLoggedIn && !urlRequiresNonAuth) { + store.dispatch(('user/deleteUser')) + return redirect('/') + } + + if (isUserLoggedIn && urlRequiresNonAuth) { + return redirect('/main/label') + } + + store.dispatch('user/setUser', userData) + + return Promise.resolve() +} + +async function getUserInfo ({ req, $axios }) { + // Send accessToken to backend and check it. + let payload = { + cookie: '' + } + + if (process.server) { + payload.cookie = cookieParser.parse(req.headers.cookie || '')['Authorization'] + } else { + payload.cookie = cookies.get('Authorization') + } + + var url = '/api/user/validatesession' + try { + var response = await $axios.post(url, payload).catch(error => { console.error(error); throw error }) + return response.data.data + } catch (err) { + return false + } } \ No newline at end of file diff --git a/frontend/mixins/user-management/getAllUsers.js b/frontend/mixins/user-management/getAllUsers.js new file mode 100644 index 0000000000000000000000000000000000000000..760e5e7411bc1f7cbbad3becc67bfb6b1cc280ad --- /dev/null +++ b/frontend/mixins/user-management/getAllUsers.js @@ -0,0 +1,31 @@ +export default { + data () { + return { + rows: [] + } + }, + methods: { + async getAllUsers () { + var url = '/api/user' + var response = await this.$axios(url).catch(error => console.error(error)) + console.log("response: ", response) + if (response && response.status === 200) { + return response.data.data + } else { + return null + } + } + }, + async mounted () { + var usersData = await this.getAllUsers() + if (usersData) { + usersData.forEach(user => { + user['_rowVariant'] = '' + user['isRoleEditMode'] = false + var role = user['user_role'] + user['user_role'] = role.charAt(0).toUpperCase() + role.slice(1) + this.rows.push(user) + }) + } + } +} \ No newline at end of file diff --git a/frontend/nuxt.config.js b/frontend/nuxt.config.js index 71561acb5a66533ae4e12095a8303ac037ff8210..7d487f57364a1102d7376b7bade6536df102f4d6 100644 --- a/frontend/nuxt.config.js +++ b/frontend/nuxt.config.js @@ -49,7 +49,7 @@ export default { '@nuxtjs/axios' ], router: { - + middleware: ['auth'] }, axios: { // See https://github.com/nuxt-community/axios-module#options diff --git a/frontend/package.json b/frontend/package.json index 37a4b1d869f62495b24089cb2ce1dfa332b2eb69..06222c21610478a0b98a561fb6ed051462d59175 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,7 @@ "@nuxtjs/axios": "^5.9.5", "bootstrap": "^4.1.3", "bootstrap-vue": "^2.0.0", - "cookie-parser": "^1.4.4", + "cookieparser": "^0.1.0", "js-cookie": "^2.2.1", "nuxt": "^2.0.0", "vue-sweetalert2": "^3.0.1" diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 5c7d0aa9fbfcfcb729cb545b00a278c5799cb7c6..2996f6390cc917c120f97fd21e16f4891551b599 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -1,15 +1,166 @@ <template> - <div> + <div class="container-fluid"> + <div class="row bg-auth center"> + <div class="col center-inside"> + <!-- Username --> + <div class="row"> + <div class="col"> + <h5 class="form-title font-auth"> + Username + </h5> + </div> + </div> + <div class="row mt-1"> + <div class="col"> + <input + v-model="usernameForm.name" + type="text" + :class="['form-control', 'field-length', 'form-content', usernameForm.isDirty && isUsernameFormEmpty ? 'form-border-error': 'form-border']" + placeholder="Type username..." + name="username" + @focus="usernameForm.isDirty = true" + > + <div v-if="isUsernameFormEmpty && usernameForm.isDirty"> + <p class="form-error"> + Username cannot be empty. + </p> + </div> + </div> + </div> + <!-- Passcode --> + <div class="row mt-2"> + <div class="col"> + <h5 class="form-title font-auth"> + Passcode + </h5> + </div> + </div> + <div class="row mt-1"> + <div class="col"> + <input + v-model="passcodeForm.name" + type="password" + :class="['form-control', 'field-length', 'form-content', passcodeForm.isDirty && isPasscodeFormEmpty ? 'form-border-error': 'form-border']" + placeholder="Type passcode..." + name="passcode" + @focus="passcodeForm.isDirty = true" + > + <div v-if="isPasscodeFormEmpty && passcodeForm.isDirty"> + <p class="form-error"> + Passcode cannot be empty. + </p> + </div> + </div> + </div> + <!-- Button --> + <div class="row mt-2"> + <div class="col"> + <button + :class="['btn-border', 'btn-action', 'field-length', 'form-content']" @click="handleOnLogin()" + > + <p class="btn-content"> + Login + </p> + </button> + </div> + </div> + </div> + </div> </div> </template> <script> +import cookies from 'js-cookie' export default { data () { return { - + usernameForm: { + name: '', + isDirty: '' + }, + passcodeForm: { + name: '', + isDirty: '' + } + } + }, + computed: { + isUsernameFormEmpty () { + return this.usernameForm.name === '' + }, + isPasscodeFormEmpty () { + return this.passcodeForm.name === '' } + }, + methods: { + async handleOnLogin () { + if (!this.isUsernameFormEmpty && !this.isPasscodeFormEmpty) { + var payload = { + "username": this.usernameForm.name, + "passcode": this.passcodeForm.name + } + var url = '/api/user/login' + const response = await this.$axios.post(url, payload).catch(error => console.error(error)) + console.log("Response: ", response) + this.handleResponse(response) + } else { + this.usernameForm.isDirty = true + this.passcodeForm.isDirty = true + } + }, + handleResponse (response) { + if (response && response.status === 200) { + this.handleSuccessResponse(response) + } else { + this.handleIncorrectResponse() + } + }, + handleSuccessResponse (response) { + // Set cookie + cookies.remove('Authorization') + cookies.set('Authorization', response.data.data.cookie) + this.$router.push('/main/label') + }, + handleIncorrectResponse () { + this.$swal.fire({ + title: "Wrong Credentials", + icon: 'error', + text: 'Please fill your credentials again.' + }) + } } } -</script> \ No newline at end of file +</script> + +<style scoped> + .bg-auth { + background-color: #005362; + height: 100vh; + } + + .font-auth { + color: white; + } + + .form-error { + color: white; + font-family: "Open Sans Regular"; + } + + .form-border-error { + border: 1px solid white; + } + + .btn-border { + border: 1px solid white; + } + + .btn-action { + background-color: #005362; + } + + .btn-action:hover { + background-color: #11616F; + } +</style> \ No newline at end of file diff --git a/frontend/pages/main.vue b/frontend/pages/main.vue index 2ec61fcb03922e41f3018bd303f2bb27fab95ceb..c83baf15f85b00ee477a486da5695a0a2e3f5262 100644 --- a/frontend/pages/main.vue +++ b/frontend/pages/main.vue @@ -1,21 +1,24 @@ <template> <div class="container-fluid"> <div class="row"> - <Navbar username="Edward" role="Admin" /> + <Navbar :username="user.username" :role="user.role" /> <nuxt-child /> </div> </div> </template> - - <script> -// import { mapGetters } from 'vuex' import Navbar from '~/components/root/Navbar' +import { mapGetters } from 'vuex' export default { components: { Navbar - } + }, + computed: { + ...mapGetters({ + user: 'user/getUser' + }) + } } </script> \ No newline at end of file diff --git a/frontend/pages/main/add-user.vue b/frontend/pages/main/add-user.vue index 77b5f2c612fa07b692973275fbdd14e17bb6a5c3..3bdde7a842be3ff125574c61cd8b98b300bfa21c 100644 --- a/frontend/pages/main/add-user.vue +++ b/frontend/pages/main/add-user.vue @@ -1,5 +1,5 @@ <template> - <div class="col"> + <div class="col center center-inside-add-user"> <div class="ml-4"> <div class="row"> <div class="col"> @@ -21,16 +21,42 @@ id="username" v-model="username" type="text" - class="form-control form-border field-length form-content" + :class="['form-control', 'field-length', 'form-content', isFormDirty && isUsernameEmpty ? 'form-border-error': 'form-border']" placeholder="Type username..." name="username" + @focus="isFormDirty = true" > + <div v-if="isUsernameEmpty && isFormDirty"> + <p class="form-error"> + Username cannot be empty. + </p> + </div> + </div> + </div> + <div class="row"> + <div class="col"> + <p class="form-title"> + Role + </p> + </div> + </div> + <div class="row form-title-margin"> + <div class="col"> + <b-form-select + id="role" + v-model="role" + type="text" + class="form-control form-border field-length form-content" + :options="roles" + placeholder="Type role..." + name="role" + /> </div> </div> <div class="row mt-2"> <div class="col"> <button - class="btn-border btn-action field-length form-content" @click="handleOnSubmit()" + :class="['btn-border', isUsernameEmpty ? 'btn-disabled': 'btn-action', 'field-length', 'form-content']" @click="handleOnSubmit()" > <p class="btn-content"> Add User @@ -46,43 +72,50 @@ export default { data () { return { - username: '' + username: '', + role: 'Labeler', + roles: ['Admin', 'Labeler', 'Editor'], + isFormDirty: false + } + }, + computed: { + isUsernameEmpty () { + return this.username === '' } }, methods: { - handleOnSubmit () { - // Send to backend - this.showPassCode(this.username) + async handleOnSubmit () { + if (!this.isUsernameEmpty) { + var payload = { + 'username': this.username, + 'user_role': this.role.toLowerCase() + } + var url = '/api/user/register' + // Send to backend + var response = await this.$axios.post(url, payload).catch(error => console.log(error)) + if (response && response.status === 200) { + this.showInfo(this.username, response.data.data.passcode) + } else { + this.handleIncorrectResponse() + } + } else { + this.isFormDirty = true + } }, - showPassCode (username) { + showInfo (username, passcode) { this.$swal.fire({ title: username + ' is Added!', icon: 'success', - text: 'Passcode : ...' + text: 'Passcode : ' + passcode + }) + }, + handleIncorrectResponse () { + this.$swal.fire({ + title: "User already exists", + icon: 'error', + text: 'Please try with another user!' }) } } } -</script> - -<style scoped> - - .form-title, .form-content, .btn-content { - font-size: 0.85rem; - color: #1E889B; - letter-spacing: 0.025rem; - - font-family: 'Open Sans Regular'; - } - - .btn-action .btn-content { - color: white; - margin-top: 5px; - margin-left: -5px; - } - - .form-title-margin { - margin-top: -5px; - } - -</style> \ No newline at end of file +</script> \ No newline at end of file diff --git a/frontend/pages/main/delete-user.vue b/frontend/pages/main/delete-user.vue index f633e3d690c530391477018e824c75eae47e54bb..854450ded8fc8f4c3e669642e8723b0d8945836b 100644 --- a/frontend/pages/main/delete-user.vue +++ b/frontend/pages/main/delete-user.vue @@ -23,20 +23,14 @@ <script> import DeleteableTable from '~/components/user-management/DeleteableTable' +import getAllUsers from '~/mixins/user-management/getAllUsers' export default { components: { DeleteableTable }, + mixins: [getAllUsers], data () { return { - rows: [ - { id: 1, isRoleEditMode: false, username: 'Edward Alexander Jaya', role: 'Labeler', passcode: 'ABCD', _rowVariant: '' }, - { id: 2, isRoleEditMode: false, username: 'Rayza Mahendra', role: 'Labeler', passcode: 'DEFG', _rowVariant: ''}, - { id: 3, isRoleEditMode: false, username: 'Muhammad Nurdin Husen', role: 'Editor', passcode: 'HIJK', _rowVariant: '' }, - { id: 4, isRoleEditMode: false, username: 'Eka Sunandika', role: 'Editor', passcode: 'LMNO', _rowVariant: '' }, - { id: 5, isRoleEditMode: false, username: 'Ahmad Rizal Alifio', role: 'Labeler', passcode: 'PQRS', _rowVariant: '' }, - { id: 6, isRoleEditMode: false, username: 'Ardian Umam', role: 'Admin', passcode: 'TUVW', _rowVariant: ''} - ], columns: [ { key: 'username', @@ -44,7 +38,7 @@ export default { sortable: true }, { - key: 'role', + key: 'user_role', label: 'Role', sortable: true }, diff --git a/frontend/pages/main/edit-user.vue b/frontend/pages/main/edit-user.vue index 3770d71fae511454800234aadcd0a3668cce4546..f17358fde213dceb3b9af3740f6655fb5bcb6a5d 100644 --- a/frontend/pages/main/edit-user.vue +++ b/frontend/pages/main/edit-user.vue @@ -23,20 +23,15 @@ <script> import EditableTable from '~/components/user-management/EditableTable' +import getAllUsers from '~/mixins/user-management/getAllUsers' + export default { components: { EditableTable }, + mixins: [getAllUsers], data () { return { - rows: [ - { id: 1, isRoleEditMode: false, username: 'Edward Alexander Jaya', role: 'Labeler', passcode: 'ABCD', _rowVariant: '' }, - { id: 2, isRoleEditMode: false, username: 'Rayza Mahendra', role: 'Labeler', passcode: 'DEFG', _rowVariant: ''}, - { id: 3, isRoleEditMode: false, username: 'Muhammad Nurdin Husen', role: 'Editor', passcode: 'HIJK', _rowVariant: '' }, - { id: 4, isRoleEditMode: false, username: 'Eka Sunandika', role: 'Editor', passcode: 'LMNO', _rowVariant: '' }, - { id: 5, isRoleEditMode: false, username: 'Ahmad Rizal Alifio', role: 'Labeler', passcode: 'PQRS', _rowVariant: '' }, - { id: 6, isRoleEditMode: false, username: 'Ardian Umam', role: 'Admin', passcode: 'TUVW', _rowVariant: ''} - ], columns: [ { key: 'username', @@ -44,7 +39,7 @@ export default { sortable: true }, { - key: 'role', + key: 'user_role', label: 'Role', sortable: true } diff --git a/frontend/pages/main/show-user.vue b/frontend/pages/main/show-user.vue index a2d0d059909983427e2fcc0393a253b9bcc48ec4..09f033817df8c62e7a3b0ea2962a5d97effaffba 100644 --- a/frontend/pages/main/show-user.vue +++ b/frontend/pages/main/show-user.vue @@ -23,29 +23,24 @@ <script> import ReadOnlyTable from '~/components/user-management/ReadOnlyTable' +import getAllUsers from '~/mixins/user-management/getAllUsers' + export default { components: { ReadOnlyTable }, + mixins: [getAllUsers], data () { return { - rows: [ - { id: 1, isRoleEditMode: false, username: 'Edward Alexander Jaya', role: 'Labeler', passcode: 'ABCD', _rowVariant: '' }, - { id: 2, isRoleEditMode: false, username: 'Rayza Mahendra', role: 'Labeler', passcode: 'DEFG', _rowVariant: ''}, - { id: 3, isRoleEditMode: false, username: 'Muhammad Nurdin Husen', role: 'Editor', passcode: 'HIJK', _rowVariant: '' }, - { id: 4, isRoleEditMode: false, username: 'Eka Sunandika', role: 'Editor', passcode: 'LMNO', _rowVariant: '' }, - { id: 5, isRoleEditMode: false, username: 'Ahmad Rizal Alifio', role: 'Labeler', passcode: 'PQRS', _rowVariant: '' }, - { id: 6, isRoleEditMode: false, username: 'Ardian Umam', role: 'Admin', passcode: 'TUVW', _rowVariant: ''} - ], columns: [ { - key: 'role', - label: 'Role', + key: 'username', + label: 'Username', sortable: true }, { - key: 'username', - label: 'Username', + key: 'user_role', + label: 'Role', sortable: true }, { diff --git a/frontend/plugins/axios.js b/frontend/plugins/axios.js index f6fb09546ebf513ec97bcaf84e03f8242de4c392..a41d3e4ce28067f3d391b93fa06bf65d140455df 100644 --- a/frontend/plugins/axios.js +++ b/frontend/plugins/axios.js @@ -1,6 +1,5 @@ // This is a nuxtjs' axios-module configuration - -import cookieParser from 'cookie-parser' +import cookieParser from 'cookieparser' import cookies from 'js-cookie' import { frontendURL } from '~/config.js' @@ -9,38 +8,26 @@ export default function ({ $axios, req, redirect, route }) { // Set all headers with Authorization header on request $axios.onRequest((config) => { var token = '' - var user_id = '' if (process.server) { - token = cookieParser.parse(req.headers.cookie || '')['Authorization'] - user_id = cookieParser.parse(req.headers.cookie || '')['user_id'] + token = 'Basic ' + cookieParser.parse(req.headers.cookie || '')['Authorization'] } else { - token = cookies.get('Authorization') - user_id = cookies.get('user_id') + token = 'Basic ' + cookies.get('Authorization') } config.headers.common['Authorization'] = token - config.headers.common['user_id'] = user_id }) + // Handle on error $axios.onError((error) => { const code = parseInt(error.response && error.response.status) if (code === 401 || code === 429) { + // Remove all cookies if (process.server) { - if (route.fullPath !== '/auth') { - redirect('/auth') - } - } else { - if (window.location.pathname !== '/auth') { - window.location.href = frontendURL + '/auth' - } - } - } else if (code === 500) { - if (process.server) { - if (route.fullPath !== '/auth/activate/500') { - // redirect('/auth/activate/500') + if (route.fullPath !== '/') { + redirect('/') } } else { - if (window.location.pathname !== '/auth/activate/500') { - // window.location.href = frontendURL + '/auth/activate/500' + if (window.location.pathname !== '/') { + window.location.href = frontendURL + '' } } } diff --git a/frontend/store/README.md b/frontend/store/README.md deleted file mode 100644 index 1972d277a298cdda81f3ac94a5c8c27f09a005b9..0000000000000000000000000000000000000000 --- a/frontend/store/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# STORE - -**This directory is not required, you can delete it if you don't want to use it.** - -This directory contains your Vuex Store files. -Vuex Store option is implemented in the Nuxt.js framework. - -Creating a file in this directory automatically activates the option in the framework. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). diff --git a/frontend/store/user.js b/frontend/store/user.js new file mode 100644 index 0000000000000000000000000000000000000000..2754cf560b2ef176fb5fa8dadb8e7581e7a736a6 --- /dev/null +++ b/frontend/store/user.js @@ -0,0 +1,38 @@ +const state = () => ({ + user: { + username: '', + role: '' + } +}) + +const getters = { + getUser: state => state.user +} + +const actions = { + setUser: ({ commit }, payload) => { + commit('setUser', payload) + }, + deleteUser: ({ commit }) => { + commit('deleteUser') + } +} + +const mutations = { + setUser (state, payload) { + state.user.username = payload['username'] + state.user.role = payload['user_role'] + }, + deleteUser (state) { + state.user.username = '' + state.user.role = '' + } +} + +export default { + state, + getters, + actions, + mutations, + namespaced: true +} \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 198de1c3b12c8784c22cae54e852b0a2a69b0d29..e21c6bd058f2e2339a7998ffd6999b38cc1c189f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2707,29 +2707,26 @@ convert-source-map@^1.4.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -cookie-parser@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188" - integrity sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw== - dependencies: - cookie "0.3.1" - cookie-signature "1.0.6" - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1, cookie@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - cookie@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +cookieparser@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cookieparser/-/cookieparser-0.1.0.tgz#ea12cb1085c174f3167faeaf7985f79abe671d0e" + integrity sha1-6hLLEIXBdPMWf66veYX3mr5nHQ4= + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"