Reworked user and session store, added Admin function for user.
* Sync Login with backend * Split Main into MainUserSettins and Settings * Added AdminSetting to change other users, added UserSelector Component for selecting users (can be reused for other stuff ;-) ). * Split hasPermission into helper file for code reuse
This commit is contained in:
parent
5c11e02b2c
commit
8689e84d47
|
@ -5,16 +5,8 @@ import { Store } from 'vuex';
|
||||||
|
|
||||||
export default boot<Store<StateInterface>>(({ router, store }) => {
|
export default boot<Store<StateInterface>>(({ router, store }) => {
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const user = store.state.user.currentUser;
|
|
||||||
const session = store.state.session.currentSession;
|
const session = store.state.session.currentSession;
|
||||||
|
|
||||||
let permissions: string[] = [];
|
|
||||||
if (user) {
|
|
||||||
user.roles.forEach(role => {
|
|
||||||
permissions = permissions.concat(role.permissions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to.name != 'login') {
|
if (to.name != 'login') {
|
||||||
if (!session || session.expires <= new Date()) {
|
if (!session || session.expires <= new Date()) {
|
||||||
store.dispatch('session/logout').catch(error => {
|
store.dispatch('session/logout').catch(error => {
|
||||||
|
@ -32,7 +24,7 @@ export default boot<Store<StateInterface>>(({ router, store }) => {
|
||||||
return (<{ permissions: FG.Permission[] }>(
|
return (<{ permissions: FG.Permission[] }>(
|
||||||
record.meta
|
record.meta
|
||||||
)).permissions.every((permission: string) => {
|
)).permissions.every((permission: string) => {
|
||||||
return permissions.includes(permission);
|
return store.state.user.currentPermissions.includes(permission);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
import { computed, defineComponent } from '@vue/composition-api';
|
import { computed, defineComponent } from '@vue/composition-api';
|
||||||
import { Store } from 'vuex';
|
import { Store } from 'vuex';
|
||||||
import { StateInterface } from 'src/store';
|
import { StateInterface } from 'src/store';
|
||||||
|
import { hasPermissions } from 'src/components/permission';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'EssentialLink',
|
name: 'EssentialLink',
|
||||||
|
@ -65,17 +66,9 @@ export default defineComponent({
|
||||||
return props.title;
|
return props.title;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasPermissions = computed(() => {
|
const isGranted = computed(() =>
|
||||||
let permissions = props.permissions;
|
hasPermissions(props.permissions || [], root.$store)
|
||||||
if (permissions) {
|
);
|
||||||
return (<string[]>permissions).every(permission => {
|
|
||||||
return (<{ 'user/permissions': string[] }>(
|
|
||||||
(<Store<StateInterface>>root.$store).getters
|
|
||||||
))['user/permissions'].includes(permission);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { realTitle: title, hasPermissions };
|
return { realTitle: title, hasPermissions };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<q-btn flat dense :icon="icon" :to="{ name: link }" v-if="hasPermissions" />
|
<q-btn flat dense :icon="icon" :to="{ name: link }" v-if="isGranted" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent } from '@vue/composition-api';
|
import { computed, defineComponent } from '@vue/composition-api';
|
||||||
import { Store } from 'vuex';
|
import { Store } from 'vuex';
|
||||||
import { StateInterface } from 'src/store';
|
import { StateInterface } from 'src/store';
|
||||||
|
import { hasPermissions } from 'src/components/permission';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ShortCutLink',
|
name: 'ShortCutLink',
|
||||||
|
@ -23,18 +24,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props, { root }) {
|
setup(props, { root }) {
|
||||||
const hasPermissions = computed(() => {
|
const isGranted = computed(() =>
|
||||||
let permissions = props.permissions;
|
hasPermissions(props.permissions || [], root.$store)
|
||||||
if (permissions) {
|
);
|
||||||
return (<string[]>permissions).every(permission => {
|
return { isGranted };
|
||||||
return (<{ 'user/permissions': string[] }>(
|
|
||||||
(<Store<StateInterface>>root.$store).getters
|
|
||||||
))['user/permissions'].includes(permission);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return { hasPermissions };
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
import { StateInterface } from 'src/store';
|
||||||
|
|
||||||
|
export function hasPermission(
|
||||||
|
permission: string,
|
||||||
|
store: Store<StateInterface>
|
||||||
|
) {
|
||||||
|
return store.state.user.currentPermissions.includes(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasPermissions(needed: string[], store: Store<StateInterface>) {
|
||||||
|
const permissions = store.state.user.currentPermissions;
|
||||||
|
return needed.every(value => permissions.includes(value));
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
label="Benutzer"
|
||||||
|
@input="updated"
|
||||||
|
v-model="user"
|
||||||
|
:options="users"
|
||||||
|
option-label="display_name"
|
||||||
|
option-value="userid"
|
||||||
|
map-options
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref } from '@vue/composition-api';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
import { StateInterface } from 'src/store';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
user: FG.User;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UserSelector',
|
||||||
|
props: ['user'],
|
||||||
|
setup(props: Props, { root, emit }) {
|
||||||
|
const store = <Store<StateInterface>>root.$store;
|
||||||
|
const users = computed(() => store.state.user.users);
|
||||||
|
const user = ref(props.user);
|
||||||
|
const updated = (value: FG.User) => {
|
||||||
|
emit('update:user', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
updated,
|
||||||
|
users
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,191 +0,0 @@
|
||||||
<template>
|
|
||||||
<q-card class="col-12">
|
|
||||||
<q-linear-progress indeterminate rounded color="primary" v-if="loading" />
|
|
||||||
<q-form @submit="save" @reset="reset">
|
|
||||||
<q-card-section class="fit row justify-start content-center items-center">
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
||||||
label="Vorname"
|
|
||||||
:rules="[notEmpty]"
|
|
||||||
v-model="firstname"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
||||||
label="Nachname"
|
|
||||||
:rules="[notEmpty]"
|
|
||||||
v-model="lastname"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
||||||
label="Benutzername"
|
|
||||||
readonly
|
|
||||||
:value="user.userid"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
||||||
label="E-Mail"
|
|
||||||
:rules="[isEmail, notEmpty]"
|
|
||||||
v-model="mail"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
||||||
label="Display Name"
|
|
||||||
:rules="[notEmpty]"
|
|
||||||
v-model="display_name"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-select
|
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
||||||
label="Rollen"
|
|
||||||
readonly
|
|
||||||
v-model="user.roles"
|
|
||||||
:options="user.roles"
|
|
||||||
filled
|
|
||||||
>
|
|
||||||
<template v-slot:selected-item="scope">
|
|
||||||
<q-chip v-for="(item, index) in scope.opt" :key="'item' + index">
|
|
||||||
{{ item.name }}
|
|
||||||
</q-chip>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</q-card-section>
|
|
||||||
<q-separator />
|
|
||||||
<q-card-section class="fit row justify-start content-center items-center">
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
hint="Password muss immer eingetragen werden"
|
|
||||||
:rules="[notEmpty]"
|
|
||||||
v-model="password"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
|
||||||
label="Neues Password"
|
|
||||||
type="password"
|
|
||||||
v-model="new_password"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
|
||||||
label="Wiederhole neues Password"
|
|
||||||
type="password"
|
|
||||||
:disable="new_password.length == 0"
|
|
||||||
:rules="[samePassword]"
|
|
||||||
v-model="new_password2"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn label="test" @click="$store.dispatch('user/getUser')" />
|
|
||||||
<q-btn label="Reset" type="reset" />
|
|
||||||
<q-btn color="primary" type="submit" label="Speichern" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, ref } from '@vue/composition-api';
|
|
||||||
import { Store } from 'vuex';
|
|
||||||
import { StateInterface } from 'src/store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Main',
|
|
||||||
setup(_, { root }) {
|
|
||||||
const store = <Store<StateInterface>>root.$store;
|
|
||||||
|
|
||||||
const user = computed(() => <FG.User>store.state.user.currentUser);
|
|
||||||
|
|
||||||
const firstname = ref(user.value?.firstname);
|
|
||||||
const lastname = ref(user.value?.lastname);
|
|
||||||
const mail = ref(user.value?.mail);
|
|
||||||
const display_name = ref(user.value?.display_name);
|
|
||||||
|
|
||||||
const password = ref('');
|
|
||||||
const new_password = ref('');
|
|
||||||
const new_password2 = ref('');
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
let change_values: { [index: string]: string } = {
|
|
||||||
firstname: firstname.value,
|
|
||||||
lastname: lastname.value,
|
|
||||||
mail: mail.value,
|
|
||||||
display_name: display_name.value
|
|
||||||
};
|
|
||||||
Object.keys(change_values).forEach(key => {
|
|
||||||
if (
|
|
||||||
change_values[key] === (<{ [index: string]: any }>user.value)[key]
|
|
||||||
) {
|
|
||||||
delete change_values[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
change_values = Object.assign(change_values, {
|
|
||||||
password: password.value
|
|
||||||
});
|
|
||||||
if (new_password.value != '') {
|
|
||||||
change_values = Object.assign(change_values, {
|
|
||||||
new_password: new_password.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
store.dispatch('user/updateUser', change_values).catch(error => {
|
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
firstname.value = user.value.firstname;
|
|
||||||
lastname.value = user.value.lastname;
|
|
||||||
mail.value = user.value.mail;
|
|
||||||
display_name.value = user.value.display_name;
|
|
||||||
password.value = '';
|
|
||||||
new_password.value = '';
|
|
||||||
new_password2.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function samePassword(val: string) {
|
|
||||||
return val == new_password.value || 'Passwörter sind nicht identisch!';
|
|
||||||
}
|
|
||||||
|
|
||||||
function notEmpty(val: string) {
|
|
||||||
return !!val || 'Feld darf nicht leer sein!';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmail(val: string | null) {
|
|
||||||
return (
|
|
||||||
!val ||
|
|
||||||
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w\w+)+$/.test(val) ||
|
|
||||||
'E-Mail ist nicht valide.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const loading = computed(() => {
|
|
||||||
return (
|
|
||||||
store.state.user.getUserLoading || store.state.user.updateUserLoading
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
firstname,
|
|
||||||
lastname,
|
|
||||||
mail,
|
|
||||||
display_name,
|
|
||||||
password,
|
|
||||||
new_password,
|
|
||||||
new_password2,
|
|
||||||
samePassword,
|
|
||||||
isEmail,
|
|
||||||
notEmpty,
|
|
||||||
save,
|
|
||||||
reset,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
<template>
|
||||||
|
<q-form @submit="save" @reset="reset">
|
||||||
|
<q-linear-progress indeterminate rounded color="primary" v-if="loading" />
|
||||||
|
<q-card-section class="fit row justify-start content-center items-center">
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
label="Vorname"
|
||||||
|
:rules="[notEmpty]"
|
||||||
|
v-model="props.user.firstname"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
label="Nachname"
|
||||||
|
:rules="[notEmpty]"
|
||||||
|
v-model="props.user.lastname"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
label="Benutzername"
|
||||||
|
readonly
|
||||||
|
:value="props.user.userid"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
label="E-Mail"
|
||||||
|
:rules="[isEmail, notEmpty]"
|
||||||
|
v-model="props.user.mail"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
label="Display Name"
|
||||||
|
:rules="[notEmpty]"
|
||||||
|
v-model="props.user.display_name"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-select
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
label="Rollen"
|
||||||
|
filled
|
||||||
|
multiple
|
||||||
|
use-chips
|
||||||
|
v-model="props.user.roles"
|
||||||
|
:readonly="() => canSetRoles()"
|
||||||
|
:options="allRoles"
|
||||||
|
option-label="name"
|
||||||
|
option-value="name"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-section class="fit row justify-start content-center items-center">
|
||||||
|
<q-input
|
||||||
|
v-if="isCurrentUser"
|
||||||
|
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
hint="Password muss immer eingetragen werden"
|
||||||
|
:rules="[notEmpty]"
|
||||||
|
v-model="password"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
||||||
|
label="Neues Password"
|
||||||
|
type="password"
|
||||||
|
v-model="new_password"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
||||||
|
label="Wiederhole neues Password"
|
||||||
|
type="password"
|
||||||
|
:disable="new_password.length == 0"
|
||||||
|
:rules="[samePassword]"
|
||||||
|
v-model="new_password2"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn label="Reset" type="reset" />
|
||||||
|
<q-btn color="primary" type="submit" label="Speichern" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
ref,
|
||||||
|
onBeforeMount
|
||||||
|
} from '@vue/composition-api';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
import { StateInterface } from 'src/store';
|
||||||
|
import { hasPermission } from 'src/components/permission';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
user?: FG.User;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MainUserSettings',
|
||||||
|
props: ['user'],
|
||||||
|
setup(props: Props, { root }) {
|
||||||
|
const store = <Store<StateInterface>>root.$store;
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
store.dispatch('user/getRoles', false).catch(error => {
|
||||||
|
console.warn(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const isCurrentUser = computed(
|
||||||
|
() => props.user?.userid === store.state.user.currentUser?.userid
|
||||||
|
);
|
||||||
|
|
||||||
|
const canSetRoles = computed(() => hasPermission('users_set_roles', store));
|
||||||
|
|
||||||
|
const oldUser = computed(() => {
|
||||||
|
if (isCurrentUser.value) return <FG.User>store.state.user.currentUser;
|
||||||
|
else
|
||||||
|
return store.state.user.users.filter(user => {
|
||||||
|
user.userid === props.user?.userid;
|
||||||
|
})[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
const allRoles = computed(() =>
|
||||||
|
store.state.user.roles.map(role => role.name)
|
||||||
|
);
|
||||||
|
const password = ref('');
|
||||||
|
const new_password = ref('');
|
||||||
|
const new_password2 = ref('');
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
let changed = <FG.User>props.user;
|
||||||
|
changed = Object.assign(changed, {
|
||||||
|
password: password.value
|
||||||
|
});
|
||||||
|
if (new_password.value != '') {
|
||||||
|
changed = Object.assign(changed, {
|
||||||
|
new_password: new_password.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
store.dispatch('user/updateUser', changed).catch(error => {
|
||||||
|
console.warn(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
props.user = oldUser.value;
|
||||||
|
password.value = '';
|
||||||
|
new_password.value = '';
|
||||||
|
new_password2.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function samePassword(val: string) {
|
||||||
|
return val == new_password.value || 'Passwörter sind nicht identisch!';
|
||||||
|
}
|
||||||
|
|
||||||
|
function notEmpty(val: string) {
|
||||||
|
return !!val || 'Feld darf nicht leer sein!';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmail(val: string | null) {
|
||||||
|
return (
|
||||||
|
!val ||
|
||||||
|
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w\w+)+$/.test(val) ||
|
||||||
|
'E-Mail ist nicht valide.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = computed(() => store.state.user.loading > 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
props,
|
||||||
|
allRoles,
|
||||||
|
canSetRoles,
|
||||||
|
password,
|
||||||
|
new_password,
|
||||||
|
new_password2,
|
||||||
|
samePassword,
|
||||||
|
isCurrentUser,
|
||||||
|
isEmail,
|
||||||
|
notEmpty,
|
||||||
|
save,
|
||||||
|
reset,
|
||||||
|
loading
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<q-page
|
||||||
|
padding
|
||||||
|
class="fit row justify-center content-center items-center q-gutter-sm"
|
||||||
|
>
|
||||||
|
<q-card class="col-12">
|
||||||
|
<q-card-section
|
||||||
|
class="fit row justify-start content-center items-center"
|
||||||
|
>
|
||||||
|
<div class="col-xs-12 col-sm-6 text-center text-h6">
|
||||||
|
Benutzereinstellungen
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-6 q-pa-sm">
|
||||||
|
<UserSelector :user="user" @update:user="userUpdated" />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<MainUserSettings :user="user" />
|
||||||
|
</q-card>
|
||||||
|
</q-page>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onBeforeMount, ref } from '@vue/composition-api';
|
||||||
|
import UserSelector from '../components/UserSelector.vue';
|
||||||
|
import MainUserSettings from '../components/settings/MainUserSettings.vue';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
import { StateInterface } from 'src/store';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'AdminSettings',
|
||||||
|
components: { UserSelector, MainUserSettings },
|
||||||
|
setup(_, { root }) {
|
||||||
|
const store = <Store<StateInterface>>root.$store;
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
store.dispatch('user/getUsers').catch(error => console.warn(error));
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = ref(<FG.User>store.state.user.currentUser);
|
||||||
|
|
||||||
|
// can be dropped with VUE3
|
||||||
|
const userUpdated = (value: FG.User) => {
|
||||||
|
user.value = value;
|
||||||
|
console.log(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
userUpdated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -3,42 +3,42 @@
|
||||||
<q-page
|
<q-page
|
||||||
padding
|
padding
|
||||||
class="fit row justify-center content-center items-center q-gutter-sm"
|
class="fit row justify-center content-center items-center q-gutter-sm"
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="fit row justify-center content-center items-center q-gutter-sm"
|
|
||||||
>
|
>
|
||||||
<circular-progress v-if="sessionsLoading" />
|
<circular-progress v-if="sessionsLoading" />
|
||||||
<div class="col-12 text-left text-h6">
|
<q-card class="col-12">
|
||||||
Allgemeine Einstellungen:
|
<q-card-section
|
||||||
</div>
|
class="fit row justify-start content-center items-center"
|
||||||
<Main />
|
>
|
||||||
<div class="col-12 text-left text-h6">
|
<div class="col-12 text-center text-h6">Benutzereinstellungen</div>
|
||||||
Aktive Sessions:
|
</q-card-section>
|
||||||
</div>
|
<MainUserSettings :user="currentUser" />
|
||||||
|
</q-card>
|
||||||
|
<div class="col-12 text-left text-h6">Aktive Sessions:</div>
|
||||||
<sessions
|
<sessions
|
||||||
v-for="(session, index) in sessions"
|
v-for="(session, index) in sessions"
|
||||||
:key="'session' + index"
|
:key="'session' + index"
|
||||||
:session="session"
|
:session="session"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<q-btn label="show sessions" @click="showRootGetters" />
|
|
||||||
</div>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onBeforeMount } from '@vue/composition-api';
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
onBeforeMount,
|
||||||
|
ref
|
||||||
|
} from '@vue/composition-api';
|
||||||
import CircularProgress from 'components/loading/CircularProgress.vue';
|
import CircularProgress from 'components/loading/CircularProgress.vue';
|
||||||
import Sessions from '../components/settings/Sessions.vue';
|
import Sessions from '../components/settings/Sessions.vue';
|
||||||
import Main from '../components/settings/Main.vue';
|
import MainUserSettings from '../components/settings/MainUserSettings.vue';
|
||||||
import { Store } from 'vuex';
|
import { Store } from 'vuex';
|
||||||
import { StateInterface } from 'src/store';
|
import { StateInterface } from 'src/store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// name: 'PageName'
|
// name: 'PageName'
|
||||||
components: { CircularProgress, Sessions, Main },
|
components: { CircularProgress, Sessions, MainUserSettings },
|
||||||
setup(_, { root }) {
|
setup(_, { root }) {
|
||||||
const store = <Store<StateInterface>>root.$store;
|
const store = <Store<StateInterface>>root.$store;
|
||||||
|
|
||||||
|
@ -47,21 +47,13 @@ export default defineComponent({
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentUser = ref(<FG.User>store.state.user.currentUser);
|
||||||
const sessions = computed(() => store.state.session.sessions);
|
const sessions = computed(() => store.state.session.sessions);
|
||||||
|
const sessionsLoading = computed(() => store.state.session.loading);
|
||||||
function showRootGetters() {
|
|
||||||
console.log(sessions.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionsLoading = computed(
|
|
||||||
() =>
|
|
||||||
store.state.session.loading ||
|
|
||||||
store.state.user.getUserLoading ||
|
|
||||||
store.state.user.updateUserLoading
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showRootGetters,
|
currentUser,
|
||||||
sessionsLoading,
|
sessionsLoading,
|
||||||
sessions
|
sessions
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,15 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
|
||||||
shortcut: true,
|
shortcut: true,
|
||||||
meta: { permissions: ['user'] },
|
meta: { permissions: ['user'] },
|
||||||
component: () => import('../pages/Settings.vue')
|
component: () => import('../pages/Settings.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Admin',
|
||||||
|
icon: 'mdi-cog',
|
||||||
|
path: 'admin',
|
||||||
|
name: 'admin-settings',
|
||||||
|
shortcut: false,
|
||||||
|
meta: { permissions: ['users_edit_other'] },
|
||||||
|
component: () => import('../pages/AdminSettings.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
|
||||||
import { LoginData, LoginResponse } from 'src/plugins/user/models';
|
import { LoginData, LoginResponse } from 'src/plugins/user/models';
|
||||||
import { StateInterface } from 'src/store';
|
import { StateInterface } from 'src/store';
|
||||||
import { axios } from 'src/boot/axios';
|
import { axios } from 'src/boot/axios';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import { Router } from 'src/router';
|
import { Router } from 'src/router';
|
||||||
import { LocalStorage, Loading } from 'quasar';
|
import { LocalStorage, Loading } from 'quasar';
|
||||||
|
|
||||||
|
@ -12,10 +12,15 @@ export interface SessionInterface {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadFromLocal() {
|
||||||
|
const session = LocalStorage.getItem<FG.Session>('currentSession');
|
||||||
|
if (session) session.expires = new Date(session.expires);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
const state: SessionInterface = {
|
const state: SessionInterface = {
|
||||||
sessions: [],
|
sessions: [],
|
||||||
currentSession:
|
currentSession: loadFromLocal() || undefined,
|
||||||
LocalStorage.getItem<FG.Session>('currentSession') || undefined,
|
|
||||||
loading: false
|
loading: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,12 +52,13 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
|
||||||
response.data.session.expires = new Date(response.data.session.expires);
|
response.data.session.expires = new Date(response.data.session.expires);
|
||||||
commit('setCurrentSession', response.data.session);
|
commit('setCurrentSession', response.data.session);
|
||||||
commit('user/setCurrentUser', response.data.user, { root: true });
|
commit('user/setCurrentUser', response.data.user, { root: true });
|
||||||
void Router.push({ name: 'user-main' });
|
commit('user/setCurrentPermissions', response.data.permissions, {
|
||||||
})
|
root: true
|
||||||
.catch(error => {
|
});
|
||||||
console.exception(error);
|
|
||||||
})
|
})
|
||||||
|
.catch(error => console.warn(error))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
void Router.push({ name: 'user-main' });
|
||||||
Loading.hide();
|
Loading.hide();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -70,7 +76,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
|
||||||
/**
|
/**
|
||||||
* Delete a given session
|
* Delete a given session
|
||||||
*/
|
*/
|
||||||
deleteSession({ commit, rootState }, token: string | null) {
|
deleteSession({ commit, dispatch, rootState }, token: string | null) {
|
||||||
if (token === null) return;
|
if (token === null) return;
|
||||||
|
|
||||||
commit('setLoading', true);
|
commit('setLoading', true);
|
||||||
|
@ -78,17 +84,23 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
|
||||||
.delete(`/auth/${token}`)
|
.delete(`/auth/${token}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (token === rootState.session.currentSession?.token) {
|
if (token === rootState.session.currentSession?.token) {
|
||||||
commit('clearCurrentSession');
|
void dispatch('clearup');
|
||||||
commit('user/clearCurrentUser', null, { root: true });
|
|
||||||
void Router.push({ name: 'login' });
|
|
||||||
} else {
|
} else {
|
||||||
commit('getSessions');
|
commit('getSessions');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch((error: AxiosError) => {
|
||||||
|
if (!error.response || error.response.status != 401) throw error;
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
commit('setLoading', false);
|
commit('setLoading', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
clearup({ commit }) {
|
||||||
|
commit('clearCurrentSession');
|
||||||
|
commit('user/clearCurrentUser', null, { root: true });
|
||||||
|
void Router.push({ name: 'login' });
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Get all sessions from current User
|
* Get all sessions from current User
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,19 +3,25 @@ import { StateInterface } from 'src/store';
|
||||||
import { axios } from 'boot/axios';
|
import { axios } from 'boot/axios';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { SessionStorage } from 'quasar';
|
import { SessionStorage } from 'quasar';
|
||||||
|
import { CurrentUserResponse } from 'src/plugins/user/models';
|
||||||
|
|
||||||
export interface UserStateInterface {
|
export interface UserStateInterface {
|
||||||
updateUserLoading: boolean;
|
|
||||||
getUserLoading: boolean;
|
|
||||||
currentUser?: FG.User;
|
currentUser?: FG.User;
|
||||||
|
currentPermissions: FG.Permission[];
|
||||||
users: FG.User[];
|
users: FG.User[];
|
||||||
|
roles: FG.Role[];
|
||||||
|
permissions: FG.Permission[];
|
||||||
|
loading: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: UserStateInterface = {
|
const state: UserStateInterface = {
|
||||||
users: [],
|
users: [],
|
||||||
|
roles: [],
|
||||||
|
permissions: [],
|
||||||
currentUser: SessionStorage.getItem<FG.User>('currentUser') || undefined,
|
currentUser: SessionStorage.getItem<FG.User>('currentUser') || undefined,
|
||||||
updateUserLoading: false,
|
currentPermissions:
|
||||||
getUserLoading: false
|
SessionStorage.getItem<FG.Permission[]>('currentPermissions') || [],
|
||||||
|
loading: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations: MutationTree<UserStateInterface> = {
|
const mutations: MutationTree<UserStateInterface> = {
|
||||||
|
@ -23,35 +29,46 @@ const mutations: MutationTree<UserStateInterface> = {
|
||||||
SessionStorage.set('currentUser', data);
|
SessionStorage.set('currentUser', data);
|
||||||
state.currentUser = data;
|
state.currentUser = data;
|
||||||
},
|
},
|
||||||
|
setCurrentPermissions(state, data: FG.Permission[]) {
|
||||||
|
SessionStorage.set('currentPermissions', data);
|
||||||
|
state.currentPermissions = data;
|
||||||
|
},
|
||||||
clearCurrentUser(state) {
|
clearCurrentUser(state) {
|
||||||
SessionStorage.remove('currentUser');
|
SessionStorage.remove('currentUser');
|
||||||
|
SessionStorage.remove('currentPermissions');
|
||||||
state.currentUser = undefined;
|
state.currentUser = undefined;
|
||||||
|
state.currentPermissions = [];
|
||||||
},
|
},
|
||||||
setUsers(state, data: FG.User[]) {
|
setUsers(state, data: FG.User[]) {
|
||||||
state.users = data;
|
state.users = data;
|
||||||
},
|
},
|
||||||
setLoading(
|
setRoles(state, data: FG.Role[]) {
|
||||||
state,
|
state.roles = data;
|
||||||
data: { key: 'updateUserLoading' | 'getUserLoading'; data: boolean }
|
},
|
||||||
) {
|
setPermissions(state, data: FG.Permission[]) {
|
||||||
state[data.key] = data.data;
|
state.permissions = data;
|
||||||
|
},
|
||||||
|
setLoading(state, data = true) {
|
||||||
|
if (data) state.loading += 1;
|
||||||
|
else state.loading -= 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions: ActionTree<UserStateInterface, StateInterface> = {
|
const actions: ActionTree<UserStateInterface, StateInterface> = {
|
||||||
getCurrentUser({ commit, rootState }) {
|
getCurrentUser({ commit, rootState }) {
|
||||||
if (rootState.session.currentSession) {
|
if (rootState.session.currentSession) {
|
||||||
commit('setLoading', { key: 'getUserLoading', data: true });
|
commit('setLoading');
|
||||||
axios
|
axios
|
||||||
.get(`/users/${rootState.session.currentSession.userid}`)
|
.get(`/users/${rootState.session.currentSession.userid}`)
|
||||||
.then((response: AxiosResponse<FG.User>) => {
|
.then((response: AxiosResponse<CurrentUserResponse>) => {
|
||||||
commit('setCurrentUser', response.data);
|
commit('setCurrentUser', response.data);
|
||||||
|
commit('setCurrentPermissions', response.data.permissions);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
commit('setLoading', { key: 'getUserLoading', data: false });
|
commit('setLoading', false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.debug('User not logged in, can not get current_user.');
|
console.debug('User not logged in, can not get current_user.');
|
||||||
|
@ -60,7 +77,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
|
||||||
|
|
||||||
getUsers({ commit }) {
|
getUsers({ commit }) {
|
||||||
axios
|
axios
|
||||||
.get(`/users`)
|
.get('/users')
|
||||||
.then((response: AxiosResponse<FG.User[]>) => {
|
.then((response: AxiosResponse<FG.User[]>) => {
|
||||||
commit('setUsers', response.data);
|
commit('setUsers', response.data);
|
||||||
})
|
})
|
||||||
|
@ -69,20 +86,43 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateUser({ commit, state, dispatch }, data) {
|
updateUser({ commit, state, dispatch }, data: FG.User) {
|
||||||
commit('setLoading', { key: 'updateUserLoading', data: true });
|
commit('setLoading');
|
||||||
if (!state.currentUser) throw 'Not logged in';
|
|
||||||
axios
|
axios
|
||||||
.put(`/users/${state.currentUser.userid}`, data)
|
.put(`/users/${data.userid}`, data)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
if (state.currentUser && state.currentUser.userid === data.userid)
|
||||||
void dispatch('getCurrentUser');
|
void dispatch('getCurrentUser');
|
||||||
|
else void dispatch('getUsers');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
commit('setLoading', { key: 'updateUserLoading', data: false });
|
commit('setLoading', false);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getRoles({ commit, state }, force = true) {
|
||||||
|
if (!force && state.roles.length > 0) return;
|
||||||
|
commit('setLoading');
|
||||||
|
axios
|
||||||
|
.get('/roles')
|
||||||
|
.then((response: AxiosResponse<FG.Role[]>) => {
|
||||||
|
commit('setRoles', response.data);
|
||||||
|
})
|
||||||
|
.finally(() => commit('setLoading', false));
|
||||||
|
},
|
||||||
|
|
||||||
|
getPermissions({ commit, state }, force = true) {
|
||||||
|
if (!force && state.permissions.length > 0) return;
|
||||||
|
commit('setLoading');
|
||||||
|
axios
|
||||||
|
.get('/roles')
|
||||||
|
.then((response: AxiosResponse<FG.Permission[]>) => {
|
||||||
|
commit('setPermissions', response.data);
|
||||||
|
})
|
||||||
|
.finally(() => commit('setLoading', false));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,20 +133,11 @@ const getters: GetterTree<UserStateInterface, StateInterface> = {
|
||||||
users({ users }) {
|
users({ users }) {
|
||||||
return users;
|
return users;
|
||||||
},
|
},
|
||||||
loading({ updateUserLoading, getUserLoading }) {
|
loading({ loading }) {
|
||||||
return updateUserLoading || getUserLoading;
|
return loading > 0;
|
||||||
},
|
},
|
||||||
displayName({ currentUser }) {
|
displayName({ currentUser }) {
|
||||||
return currentUser?.display_name;
|
return currentUser?.display_name;
|
||||||
},
|
|
||||||
permissions({ currentUser }) {
|
|
||||||
let permissions: string[] = [];
|
|
||||||
if (currentUser) {
|
|
||||||
currentUser.roles.forEach(role => {
|
|
||||||
permissions = permissions.concat(role.permissions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return permissions;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue