summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJannik Schönartz2019-02-25 07:52:15 +0100
committerJannik Schönartz2019-02-25 07:52:15 +0100
commitf42e850ad0778c147bead82a91d3805c81b66150 (patch)
tree2b5189a7f8a96ca4a15777a06a71581cd1b93ce3
parent[webapp/datatable] small design fixes (diff)
downloadbas-f42e850ad0778c147bead82a91d3805c81b66150.tar.gz
bas-f42e850ad0778c147bead82a91d3805c81b66150.tar.xz
bas-f42e850ad0778c147bead82a91d3805c81b66150.zip
[webapp/user] Add user management module for creating / deleting user accounts
-rw-r--r--server/api/users.js22
-rw-r--r--server/lib/authentication.js58
-rw-r--r--webapp/src/components/IpxeBuilderModule.vue2
-rw-r--r--webapp/src/components/IpxeBuilderModuleConfig.vue2
-rw-r--r--webapp/src/components/UserCreateForm.vue188
-rw-r--r--webapp/src/components/UserModule.vue128
-rw-r--r--webapp/src/components/UserModuleDelete.vue73
-rw-r--r--webapp/src/components/UserModuleEdit.vue95
-rw-r--r--webapp/src/config/dashboard.js6
-rw-r--r--webapp/src/config/i18n.js6
-rw-r--r--webapp/src/config/store.js4
-rw-r--r--webapp/src/store/users.js29
12 files changed, 578 insertions, 35 deletions
diff --git a/server/api/users.js b/server/api/users.js
index 1a724ac..dc77932 100644
--- a/server/api/users.js
+++ b/server/api/users.js
@@ -14,6 +14,12 @@ var authentication = require(path.join(__appdir, 'lib', 'authentication'))
*/
router.getAsync('', async (req, res) => {
const users = await db.user.findAll({ include: ['roles'], order: [['name', 'ASC']] })
+
+ // Remove passwords
+ await users.forEach(x => {
+ x = x.dataValues
+ delete x.password
+ })
res.status(200).send(users)
})
@@ -52,8 +58,19 @@ router.postAsync('/:id/roles', async (req, res) => {
})
// Post request for creating new user accounts.
-router.post('/', (req, res) => {
- authentication.signup(req, res)
+router.postAsync(['/', '/:id'], async (req, res) => {
+ if (req.query.delete !== undefined && req.query.delete !== 'false') {
+ const count = await db.user.destroy({ where: { id: req.body.ids } })
+ res.status(200).send({ count })
+ } else {
+ if (req.params.id === undefined) return authentication.signup(req, res)
+ else {
+ let user
+ user = await db.user.findOne({ where: { id: req.params.id } })
+ if (user) await user.update(req.body)
+ res.status(200).end()
+ }
+ }
})
// Post request for changing the password.
@@ -81,6 +98,7 @@ router.post('/:id', (req, res) => {
})
})
+// Function for deleting a single user
router.delete('/:id/', (req, res) => {
// Check if the user has the permission for chaning those userdata. Else return.
if (req.params.id !== 'current') {
diff --git a/server/lib/authentication.js b/server/lib/authentication.js
index f412e31..9c1062c 100644
--- a/server/lib/authentication.js
+++ b/server/lib/authentication.js
@@ -34,37 +34,37 @@ function loginToken (req, res) {
}
// Method for creating a new user.
-function signup (req, res) {
+async function signup (req, res) {
// TODO: Implement some security stuff. Not every user who call this request should be able to sign up.
var params = req.body
- if (!params.username) return res.status(500).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
- if (!params.password) return res.status(500).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
+ if (!params.username) return res.status(400).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
+ if (validateUsername(params.username)) return res.status(400).send({ auth: false, status: 'INVALID_USERNAME', error_message: 'Username does not fullfill the requirements. (No whitespaces)' })
+ if (!params.password) return res.status(400).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
// if (!params.email) return res.status(500).send({ auth: false, status: 'EMAIL_MISSING', error_message: 'This services requires an email.' })
// Database and user validation.
- db.user.findOne({ where: { username: params.username } }).then(userDb => {
- // User exists validation.
- if (userDb) return res.status(500).send({ auth: false, status: 'USER_ALREADY_EXISTS', error_message: 'The provided username already exists.' })
- // Password requirements validation.
- if (!validatePassword(params.password)) return res.status(500).send({ auth: false, status: 'PASSWORD_REQUIREMENTS', error_message: 'The password requirements are not fullfilled.' })
- // Email validation.
- if (!validateEmail(params.email)) return res.status(500).send({ auth: false, status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' })
- var userPassword = Buffer.from(params.password)
-
- // Register user
- pwd.hash(userPassword, function (err, hash) {
- if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' })
- // Saving the non improved hash and creating the user in the db.
- db.user.create({ username: params.username, password: hash, email: params.email, name: params.name }).then(userDb => {
- // TODO: Username could also be used because those are unique as well.
- var userId = userDb.id
- // Verify & improving the hash.
- verifyHash(res, userPassword, hash, userId, function () {
- return res.status(200).send({ auth: true, status: 'VALID' })
- })
- })
- })
- })
+ let userDb = await db.user.findOne({ where: { username: params.username } })
+
+ // User exists validation.
+ if (userDb) return res.status(500).send({ auth: false, status: 'USER_ALREADY_EXISTS', error_message: 'The provided username already exists.' })
+ // Password requirements validation.
+ if (!validatePassword(params.password)) return res.status(400).send({ auth: false, status: 'PASSWORD_REQUIREMENTS', error_message: 'The password requirements are not fullfilled.' })
+ // Email validation.
+ // if (!validateEmail(params.email)) return res.status(500).send({ auth: false, status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' })
+ var userPassword = Buffer.from(params.password)
+
+ // Register user
+ const hash = await pwd.hash(userPassword)
+ // if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' })
+
+ // Saving the non improved hash and creating the user in the db.
+ const newUser = await db.user.create({ username: params.username, password: hash, email: params.email, name: params.name })
+ // TODO: Username could also be used because those are unique as well.
+ var userId = newUser.id
+
+ // Verify & improving the hash.
+ await verifyHash(res, userPassword, hash, userId, () => {})
+ return res.status(200).send({ auth: true, status: 'VALID' })
}
// Logout method for the frontend. Deleting the cookies by overwriting them.
@@ -222,3 +222,9 @@ function validatePassword (password) {
if (password.length < 8) return false
return true
}
+
+// Function for validating the username. Username requirements are implemented here.
+function validateUsername (username) {
+ // Disallow whitespaces
+ return !/\s/.test(username)
+}
diff --git a/webapp/src/components/IpxeBuilderModule.vue b/webapp/src/components/IpxeBuilderModule.vue
index fde333f..56fd4ab 100644
--- a/webapp/src/components/IpxeBuilderModule.vue
+++ b/webapp/src/components/IpxeBuilderModule.vue
@@ -41,7 +41,7 @@ import { mapGetters } from 'vuex'
import IpxeBuilderModuleConfig from '@/components/IpxeBuilderModuleConfig'
export default {
- name: 'IpxeBuilder',
+ name: 'IpxeBuilderModule',
components: {
IpxeBuilderModuleConfig
},
diff --git a/webapp/src/components/IpxeBuilderModuleConfig.vue b/webapp/src/components/IpxeBuilderModuleConfig.vue
index 874c3ee..284a6cf 100644
--- a/webapp/src/components/IpxeBuilderModuleConfig.vue
+++ b/webapp/src/components/IpxeBuilderModuleConfig.vue
@@ -136,7 +136,7 @@ import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
- name: 'IpxeBuilder',
+ name: 'IpxeBuilderModuleConfig',
props: ['ipxeVersion'],
components: {
},
diff --git a/webapp/src/components/UserCreateForm.vue b/webapp/src/components/UserCreateForm.vue
new file mode 100644
index 0000000..616ca3c
--- /dev/null
+++ b/webapp/src/components/UserCreateForm.vue
@@ -0,0 +1,188 @@
+<i18n>
+{
+ "en": {
+ "confirmPassword": "Confirm Password",
+ "email": "E-Mail",
+ "emailError": "E-mail must be valid.",
+ "name": "Name",
+ "password": "Password",
+ "passwordEmptyError": "Password can not be empty.",
+ "passwordLengthError": "Minimum 8 characters required.",
+ "passwordMatchError": "Passwords do not match.",
+ "username": "Username",
+ "usernameEmptyError": "Username can not be empty.",
+ "usernameExistsError": "Username already taken.",
+ "usernameHasWhitespaces": "No whitespaces allowed in the username."
+ },
+ "de": {
+ "confirmPassword": "Passwort bestätigen",
+ "email": "E-Mail",
+ "emailError": "Keine gültige E-Mail.",
+ "name": "Name",
+ "password": "Passwort",
+ "passwordEmptyError": "Passwort kann nicht leer sein.",
+ "passwordLengthError": "Es sind mindestens 8 Zeichen erforderlich.",
+ "passwordMatchError": "Passwörter stimmen nicht überein.",
+ "username": "Benutzername",
+ "usernameEmptyError": "Benutzername kann nicht leer sein.",
+ "usernameExistsError": "Benutzername existiert bereits.",
+ "usernameHasWhitespaces": "Leerzeichen sind im Benutzername nicht erlaubt."
+ }
+}
+</i18n>
+
+<template>
+ <v-form class="signup-form" ref="form" v-model="valid">
+ <v-text-field
+ validate-on-blur
+ :label="$t('username')"
+ v-model="user.username"
+ :rules="usernameRules"
+ autocomplete="off"
+ ></v-text-field>
+ <v-text-field
+ validate-on-blur
+ type="password"
+ :label="$t('password')"
+ v-model="user.password"
+ :rules="passwordRules"
+ autocomplete="off"
+ ></v-text-field>
+ <v-text-field
+ validate-on-blur
+ type="password"
+ :label="$t('confirmPassword')"
+ v-model="user.confirmPassword"
+ :rules="confirmPasswordRules"
+ autocomplete="off"
+ ></v-text-field>
+ <v-text-field
+ :label="$t('name')"
+ autocomplete="off"
+ v-model="user.name"
+ ></v-text-field>
+ <v-text-field
+ validate-on-blur
+ :label="$t('email')"
+ :rules="emailRules"
+ autocomplete="off"
+ v-model="user.email"
+ ></v-text-field>
+ </v-form>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+export default {
+ name: 'UserCreateForm',
+ props: ['id', 'value'],
+ data () {
+ return {
+ valid: true,
+ usernameError: false,
+ usernameExistsError: false,
+ usernameRules: [
+ v => !!v || this.$t('usernameEmptyError'),
+ v => !this.usernameError || this.$t('usernameError'),
+ v => !/\s/.test(v) || this.$t('usernameHasWhitespaces'),
+ v => this.usernameTakenValidation(v) || this.$t('usernameExistsError')
+ ],
+ passwordRules: [
+ v => this.pwRequired(v) || this.$t('passwordEmptyError'),
+ v => this.pwLength(v) || this.$t('passwordLengthError')
+ ],
+ confirmPasswordRules: [
+ v => this.pwRequired(v) || this.$t('passwordEmptyError'),
+ v => v === this.user.password || this.$t('passwordMatchError')
+ ],
+ confirmPasswordError: false,
+ emailRules: [
+ v => this.$validateEmail(v) || this.$t('emailError')
+ ],
+ user: {
+ username: '',
+ password: '',
+ confirmPassword: '',
+ name: '',
+ email: ''
+ }
+ }
+ },
+ computed: {
+ ...mapState('users', ['dialog']),
+ show () { return this.dialog.show }
+ },
+ watch: {
+ show (value) {
+ if (value) {
+ this.$refs.form.resetValidation()
+ this.loadForm()
+ }
+ },
+ value: {
+ deep: true,
+ handler () {
+ this.user = this.value
+ }
+ },
+ user: {
+ deep: true,
+ handler () {
+ this.$emit('input', this.user)
+ }
+ }
+ },
+ methods: {
+ validate () {
+ return this.$refs.form.validate()
+ },
+ setUsernameTakenError () {
+ this.usernameExistsError = true
+ this.$refs.form.validate()
+ },
+ usernameTakenValidation (v) {
+ if (this.id) return true
+ else return !this.usernameExistsError
+ },
+ pwRequired (v) {
+ if (!!v || this.id) return true
+ else return false
+ },
+ pwLength (v) {
+ if (this.id && !v) return true
+ else return v.length >= 8
+ },
+ loadForm () {
+ if (this.id) this.loadUser(this.id)
+ else this.clearForm()
+ },
+ loadUser (id) {
+ this.$http('/api/users/' + id).then(response => {
+ let user = response.data
+ this.user.username = user.username
+ this.user.email = user.email
+ this.user.name = user.name
+ this.user.password = ''
+ this.user.confirmPassword = ''
+ })
+ },
+ clearForm () {
+ this.user.username = ''
+ this.user.email = ''
+ this.user.name = ''
+ this.user.password = ''
+ this.user.confirmPassword = ''
+ }
+ },
+ created () {
+ this.loadForm()
+ }
+}
+</script>
+
+<style scoped>
+.signup-form {
+ width: 300px;
+}
+</style>
diff --git a/webapp/src/components/UserModule.vue b/webapp/src/components/UserModule.vue
new file mode 100644
index 0000000..bee7607
--- /dev/null
+++ b/webapp/src/components/UserModule.vue
@@ -0,0 +1,128 @@
+<i18n>
+{
+ "en": {
+ "createUser": "Create user",
+ "deleteUsers": "Delete one user | Delete {0} user",
+ "email": "E-Mail",
+ "id": "ID",
+ "name": "Name",
+ "username": "Username",
+ "users": "Users"
+ },
+ "de": {
+ "createUser": "Benutzer erstellen",
+ "deleteUsers": "Lösche einen Benutzer | Lösche {0} Benutzer",
+ "email": "E-Mail",
+ "id": "ID",
+ "name": "Name",
+ "username": "Benutzername",
+ "users": "Benutzer"
+ }
+}
+</i18n>
+
+<template>
+ <v-container fill-height>
+ <v-layout>
+ <v-flex class="tabs-wrapper" xl10 offset-xl1 lg12>
+ <v-card>
+ <v-tabs :dark="tabsDark" :color="tabsColor" :slider-color="tabsSliderColor"
+ centered
+ v-model="tab"
+ >
+ <v-tab>{{ $t('users') }}</v-tab>
+ </v-tabs>
+ </v-card>
+ <v-tabs-items v-model="tab">
+ <v-tab-item>
+ <v-subheader>{{ $t('users') }}</v-subheader>
+
+ <v-card>
+ <data-table v-model="selectedUsers" :headers="headers" :items="users" @dblclick="editUser($event.id)">
+ <div slot="action" slot-scope="row" style="text-align: right">
+ <v-btn flat icon color="primary" @click.stop="editUser(row.item.id)"><v-icon>edit</v-icon></v-btn>
+ </div>
+ </data-table>
+ </v-card>
+ <div class="text-xs-right">
+ <v-btn color="error" flat @click="deleteUsers" :disabled="selectedUsers.length === 0">
+ <v-icon left>delete</v-icon>{{ $tc('deleteUsers', selectedUsers.length, [selectedUsers.length]) }}
+ </v-btn>
+ <v-btn color="success" flat @click="editUser(0)">
+ <v-icon left>edit</v-icon>{{ $t('createUser') }}
+ </v-btn>
+ </div>
+
+ </v-tab-item>
+ </v-tabs-items>
+ </v-flex>
+ </v-layout>
+
+ <v-dialog
+ :value="dialog.show"
+ @input="setDialog({ show: $event })"
+ :max-width="dialog.type === 'delete' ? '500px' : '500px'"
+ scrollable
+ :persistent="dialog.type !== 'delete'"
+ :fullscreen="$vuetify.breakpoint.smAndDown"
+ >
+ <user-module-delete v-if="dialog.type === 'delete'" />
+ <user-module-edit v-else-if="dialog.type === 'edit'"/>
+ </v-dialog>
+
+ </v-container>
+</template>
+
+<script>
+import { mapGetters, mapState, mapMutations } from 'vuex'
+import DataTable from '@/components/DataTable'
+import UserModuleDelete from '@/components/UserModuleDelete'
+import UserModuleEdit from '@/components/UserModuleEdit'
+
+export default {
+ name: 'UserModule',
+ components: {
+ UserModuleDelete,
+ UserModuleEdit,
+ DataTable
+ },
+ data () {
+ return {
+ tab: 0,
+ headers: [
+ { text: this.$t('id'), key: 'id' },
+ { text: this.$t('username'), key: 'username' },
+ { text: this.$t('name'), key: 'name' },
+ { text: this.$t('email'), key: 'email' },
+ { key: 'action' }
+ ],
+ selectedUsers: [],
+ user: ''
+ }
+ },
+ methods: {
+ ...mapMutations('users', ['setDialog']),
+ deleteUsers () {
+ this.setDialog({ show: true, type: 'delete', info: { id: this.user.id, selected: this.selectedUsers } })
+ },
+ editUser (id) {
+ this.setDialog({ show: true, type: 'edit', info: { id: id } })
+ }
+ },
+ computed: {
+ ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor']),
+ ...mapState('users', ['users', 'dialog'])
+ },
+ created () {
+ this.$store.dispatch('users/loadData')
+ this.$http('/api/users/current').then(response => {
+ this.user = response.data
+ })
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+</style>
diff --git a/webapp/src/components/UserModuleDelete.vue b/webapp/src/components/UserModuleDelete.vue
new file mode 100644
index 0000000..d35b755
--- /dev/null
+++ b/webapp/src/components/UserModuleDelete.vue
@@ -0,0 +1,73 @@
+<i18n>
+{
+ "en": {
+ "title": "Delete this user? | Delete these {0} users?",
+ "deleteYourself": "You are about to delete yourself!"
+ },
+ "de": {
+ "title": "Diesen Benutzer löschen? | Diese {0} Benutzer löschen?",
+ "deleteYourself": "Du bist dabei dich selber zu löschen!"
+ }
+}
+</i18n>
+
+<template>
+ <v-card :color="doIDeleteMyself ? 'error' : ''">
+ <v-card-title primary-title class="dialog-title elevation-3" >
+ <div class="headline">{{ $tc('title', dialog.info.selected.length, [dialog.info.selected.length]) }}</div>
+ </v-card-title>
+ <v-card-text style="height: 100%">
+ <div v-for="item in dialog.info.selected" class="grey--text" :key="item.id">
+ <div v-if="item.id === dialog.info.id" class="black--text">[{{ item.id }}] {{ item.name }}</div>
+ <div v-else :class="doIDeleteMyself ? 'white--text' : ''">[{{ item.id }}] {{ item.name }}</div>
+ </div>
+ <div v-if="doIDeleteMyself" class="white--text" style="margin-top: 20px;">{{ $t('deleteYourself') }}</div>
+ </v-card-text>
+ <v-divider></v-divider>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn flat="flat" @click="setDialog({ show: false })">{{ $t('cancel') }}</v-btn>
+ <v-btn :color="doIDeleteMyself ? 'white' : 'error'" @click="deleteItems"><div :class="doIDeleteMyself ? 'red--text' : 'white--text'">{{ $t('delete') }}</div></v-btn>
+ </v-card-actions>
+ </v-card>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+export default {
+ name: 'UserModuleDelete',
+ data () {
+ return {
+ }
+ },
+ computed: {
+ ...mapState('users', ['dialog']),
+ doIDeleteMyself () {
+ return this.dialog.info.selected.map(x => x.id).includes(this.dialog.info.id)
+ }
+ },
+ methods: {
+ setDialog (data) {
+ this.$store.commit('users/setDialog', data)
+ },
+ async deleteItems () {
+ await this.$http.post('/api/users/?delete', {
+ ids: this.dialog.info.selected.map(x => x.id)
+ })
+ this.$store.dispatch('users/loadData')
+ this.setDialog({ show: false })
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.dialog-title {
+ z-index: 1;
+}
+.selected-list {
+ padding: 30px;
+}
+</style>
diff --git a/webapp/src/components/UserModuleEdit.vue b/webapp/src/components/UserModuleEdit.vue
new file mode 100644
index 0000000..77be879
--- /dev/null
+++ b/webapp/src/components/UserModuleEdit.vue
@@ -0,0 +1,95 @@
+<i18n>
+{
+ "en": {
+ "titleExisting": "Edit user",
+ "titleNew": "Create user",
+ "userCreated": "User was successfully created."
+ },
+ "de": {
+ "titleExisting": "Benutzer bearbeiten",
+ "titleNew": "Benutzer erstellen",
+ "userCreated": "Benutzer wurde erfolgreich erstellt."
+ }
+}
+</i18n>
+
+<template>
+ <v-card>
+ <v-card-title primary-title class="dialog-title elevation-3">
+ <div class="headline">{{ dialog.info.id ? $t('titleExisting') : $t('titleNew') }}</div>
+ </v-card-title>
+ <v-card-text style="height: 100%;">
+ <div class="edit-user">
+ <signup v-model="user" ref="editComponent" :id="dialog.info.id"></signup>
+ </div>
+ </v-card-text>
+ <v-divider></v-divider>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn flat="flat" @click="setDialog({ show: false })">{{ $t('cancel') }}</v-btn>
+ <v-btn :color="dialog.info.id ? 'primary' : 'success'" @click="saveUser">{{ dialog.info.id ? $t('save') : $t('create') }}</v-btn>
+ </v-card-actions>
+ </v-card>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+import signup from '@/components/UserCreateForm'
+
+export default {
+ name: 'UserModuleEdit',
+ components: {
+ signup
+ },
+ data () {
+ return {
+ user: { }
+ }
+ },
+ computed: {
+ ...mapState('users', ['dialog'])
+ },
+ methods: {
+ setDialog (data) {
+ this.$store.commit('users/setDialog', data)
+ },
+ async saveUser () {
+ if (this.$refs.editComponent.validate()) {
+ var url = '/api/users'
+ if (this.dialog.info.id) {
+ url += '/' + this.dialog.info.id
+ }
+ this.$http.post(url, {
+ username: this.user.username,
+ password: this.user.password,
+ name: this.user.name,
+ email: this.user.email
+ }).then(response => {
+ this.$store.dispatch('users/loadData')
+ this.setDialog({ show: false })
+ this.$snackbar({ color: 'success', text: this.$t('userCreated') })
+ }).catch(error => {
+ if (error.response.data.status === 'USER_ALREADY_EXISTS') {
+ this.$refs.editComponent.setUsernameTakenError()
+ }
+ })
+ }
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.dialog-title {
+ z-index: 1;
+}
+.selected-list {
+ padding: 30px;
+}
+.edit-user {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+</style>
diff --git a/webapp/src/config/dashboard.js b/webapp/src/config/dashboard.js
index ac8a7b1..a5f6b4e 100644
--- a/webapp/src/config/dashboard.js
+++ b/webapp/src/config/dashboard.js
@@ -3,7 +3,8 @@ import ConfiguratorModule from '@/components/ConfiguratorModule'
import RegistrationModule from '@/components/RegistrationModule'
import BackendModule from '@/components/BackendModule'
import PermissionModule from '@/components/PermissionModule'
-import IpxeBuilder from '@/components/IpxeBuilderModule'
+import IpxeBuilderModule from '@/components/IpxeBuilderModule'
+import UserModule from '@/components/UserModule'
export default [
{ path: 'groups', component: GroupModule, icon: 'category' },
@@ -11,5 +12,6 @@ export default [
{ path: 'registration', component: RegistrationModule, icon: 'assignment' },
{ path: 'backends', component: BackendModule, icon: 'cloud' },
{ path: 'permissions', component: PermissionModule, icon: 'lock_open' },
- { path: 'ipxe', component: IpxeBuilder, icon: 'merge_type' }
+ { path: 'ipxe', component: IpxeBuilderModule, icon: 'merge_type' },
+ { path: 'users', component: UserModule, icon: 'contacts' }
]
diff --git a/webapp/src/config/i18n.js b/webapp/src/config/i18n.js
index 3f71729..8e0b48c 100644
--- a/webapp/src/config/i18n.js
+++ b/webapp/src/config/i18n.js
@@ -28,7 +28,8 @@ export default {
'RegistrationModule': 'Client Registration',
'BackendModule': 'External Backends',
'PermissionModule': 'Permission Manager',
- 'IpxeBuilder': 'iPXE Builder'
+ 'IpxeBuilderModule': 'iPXE Builder',
+ 'UserModule': 'User Management'
}
},
'de': {
@@ -60,7 +61,8 @@ export default {
'RegistrationModule': 'Client Registrierung',
'BackendModule': 'Externe Backends',
'PermissionModule': 'Rechteverwaltung',
- 'IpxeBuilder': 'iPXE Builder'
+ 'IpxeBuilderModule': 'iPXE Builder',
+ 'UserModule': 'Benutzerverwaltung'
}
}
}
diff --git a/webapp/src/config/store.js b/webapp/src/config/store.js
index f83328b..ac65696 100644
--- a/webapp/src/config/store.js
+++ b/webapp/src/config/store.js
@@ -4,6 +4,7 @@ import configurator from '@/store/configurator'
import registration from '@/store/registration'
import backends from '@/store/backends'
import permissions from '@/store/permissions'
+import users from '@/store/users'
export default {
notifications,
@@ -11,5 +12,6 @@ export default {
configurator,
registration,
backends,
- permissions
+ permissions,
+ users
}
diff --git a/webapp/src/store/users.js b/webapp/src/store/users.js
new file mode 100644
index 0000000..9e0f13c
--- /dev/null
+++ b/webapp/src/store/users.js
@@ -0,0 +1,29 @@
+
+import axios from 'axios'
+
+export default {
+ namespaced: true,
+ state: {
+ users: [],
+ dialog: {
+ show: false,
+ type: null,
+ info: {}
+ }
+ },
+ mutations: {
+ setUsers (state, users) { state.users = users },
+ setDialog (state, { show, type, info }) {
+ if (info !== undefined) state.dialog.info = info
+ if (type !== undefined) state.dialog.type = type
+ if (show !== undefined) state.dialog.show = show
+ }
+ },
+ actions: {
+ loadData (context) {
+ axios.get('/api/users').then(response => {
+ context.commit('setUsers', response.data)
+ })
+ }
+ }
+}