Merge branch 'transfer/next' into next

This commit is contained in:
Tim Gröger 2020-10-16 09:38:14 +02:00
commit 644f225428
13 changed files with 362 additions and 96 deletions

View File

@ -1,7 +1,7 @@
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import { StateInterface } from '../store'; import { StateInterface } from '../store';
import config from '../config' import config from '../config';
declare module 'vue/types/vue' { declare module 'vue/types/vue' {
interface Vue { interface Vue {
@ -14,12 +14,13 @@ export default boot(({ Vue, store }) => {
Vue.prototype.$axios = axios; Vue.prototype.$axios = axios;
axios.defaults.baseURL = config.baseURL; axios.defaults.baseURL = config.baseURL;
axios.interceptors.request.use( axios.interceptors.request.use(config => {
config => { const token = (<StateInterface>store.state).user.token;
const token = (<StateInterface>store.state).user.token; if (token) {
if (token) { config.headers['Authorization'] = 'Token ' + token.token;
config.headers['Authorization'] = 'Token ' + token.token; }
} return config;
return config; });
});
}); });
export { axios };

View File

@ -2,24 +2,28 @@ import { boot } from 'quasar/wrappers';
import { StateInterface } from '../store'; import { StateInterface } from '../store';
export default boot(({ Vue, router, store }) => { export default boot(({ Vue, router, store }) => {
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
let user = (<StateInterface>store.state).user; let user = (<StateInterface>store.state).user;
console.log('login_boot', user);
if (to.matched.some(record => { if (
// permissions is set AND has NO matching permission to.matched.some(record => {
return record.meta.permissions !== undefined && // permissions is set AND has NO matching permission
!(record.meta.permissions.filter( return (
(value: string) => record.meta.permissions !== undefined &&
user.permissions.includes(value) !(
).length > 0); record.meta.permissions.filter((value: string) =>
}) user.permissions.includes(value)
) { ).length > 0
next({ )
path: '/login', );
query: { redirect: to.fullPath } })
}); ) {
} else { next({
next(); path: '/login',
} query: { redirect: to.fullPath }
}) });
} else {
next();
}
});
}); });

View File

@ -1,12 +1,12 @@
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import { RouteConfig } from 'vue-router'; import { RouteConfig } from 'vue-router';
import { Module } from 'vuex'; import { Module, Store } from 'vuex';
const config = { const config = {
// Do not change required Modules !! // Do not change required Modules !!
requiredModules: ['user'], requiredModules: ['user'],
// here you can import plugins. // here you can import plugins.
loadModules: ['plugin1', 'user-plugin'] loadModules: []
}; };
// do not change anything here !! // do not change anything here !!
@ -19,7 +19,7 @@ interface ShortCutLink {
interface Plugin { interface Plugin {
name: string; name: string;
routes: RouteConfig[]; routes: RouteConfig[];
store?: Module<never, never>[]; store?: Map<string, Module<any, any>>;
mainLink: PluginMainLink; mainLink: PluginMainLink;
requiredModules: string[]; requiredModules: string[];
shortcuts: ShortCutLink[]; shortcuts: ShortCutLink[];
@ -65,7 +65,7 @@ function combineRoutes(
target: RouteConfig[], target: RouteConfig[],
source: RouteConfig[] source: RouteConfig[]
): RouteConfig[] { ): RouteConfig[] {
// iterate first layer e.g. /main, /login etc. // iterate first layer e.g. /main, / etc.
source.forEach((sourceRouteConfig: RouteConfig) => { source.forEach((sourceRouteConfig: RouteConfig) => {
const targetRouteConfig: RouteConfig | undefined = target.find( const targetRouteConfig: RouteConfig | undefined = target.find(
(routeConfig: RouteConfig) => { (routeConfig: RouteConfig) => {
@ -138,7 +138,7 @@ function loadPlugin(
loadedPlugins: LoadedPlugins, loadedPlugins: LoadedPlugins,
modules: string[], modules: string[],
plugins: Plugin[], plugins: Plugin[],
store: any store: Store<any>
): LoadedPlugins { ): LoadedPlugins {
modules.forEach(requiredModule => { modules.forEach(requiredModule => {
const plugin = plugins.find(plugin => { const plugin = plugins.find(plugin => {
@ -147,10 +147,15 @@ function loadPlugin(
if (plugin) { if (plugin) {
loadedPlugins.routes = combineRoutes(loadedPlugins.routes, plugin.routes); loadedPlugins.routes = combineRoutes(loadedPlugins.routes, plugin.routes);
if (plugin.store) { if (plugin.store) {
plugin.store.forEach(store_module => { console.log(plugin.store);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access console.log(plugin.store.keys());
store.registerModule(store_module); plugin.store.forEach((store_plugin, store_namespace) => {
store.registerModule(store_namespace, store_plugin);
}); });
//.forEach(store_key => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
//store.registerModule(store_key, plugin.store.get(store_key));
//});
} }
loadedPlugins.mainLinks = combineMainLinks( loadedPlugins.mainLinks = combineMainLinks(
loadedPlugins.mainLinks, loadedPlugins.mainLinks,

View File

@ -1,9 +1,8 @@
<template> <template>
<q-page <q-page padding class="fit row justify-center items-center content-center">
padding <q-card
class="fit row justify-center items-center content-center" class="col-xs-11 col-sm-8 col-md-6 col-lg-4 justify-center items-center content-center"
> >
<q-card class="col-xs-11 col-sm-8 col-md-6 col-lg-4 justify-center items-center content-center">
<q-toolbar class="bg-primary text-white"> <q-toolbar class="bg-primary text-white">
<q-toolbar-title> <q-toolbar-title>
Login Login
@ -11,11 +10,7 @@
</q-toolbar> </q-toolbar>
<q-card-section> <q-card-section>
<q-form <q-form ref="LoginForm" @submit="doLogin" class="q-gutter-md">
ref="LoginForm"
@submit="doLogin"
class="q-gutter-md"
>
<q-input <q-input
filled filled
v-model="userid" v-model="userid"
@ -30,11 +25,7 @@
:rules="rules" :rules="rules"
/> />
<div class="row justify-end"> <div class="row justify-end">
<q-btn <q-btn label="Login" type="submit" color="primary" />
label="Login"
type="submit"
color="primary"
/>
</div> </div>
</q-form> </q-form>
</q-card-section> </q-card-section>
@ -51,19 +42,19 @@ export default defineComponent({
const userid = ref(''); const userid = ref('');
const password = ref(''); const password = ref('');
const rules = [ const rules = [
(val: string) => (val && val.length > 0) || 'Feld darf nicht leer sein!', (val: string) => (val && val.length > 0) || 'Feld darf nicht leer sein!'
]; ];
function doLogin() { function doLogin() {
console.log(userid.value, password.value);
ctx.root.$store.dispatch('user/login', { ctx.root.$store.dispatch('user/login', {
userid: userid.value, userid: userid.value,
password: password.value, password: password.value
}); });
//ctx.root.$router.push({ name: 'main' });
void ctx.root.$router.push({ name: 'user' });
} }
return { userid, password, doLogin, rules }; return { userid, password, doLogin, rules };
}, }
}); });
</script> </script>

View File

@ -0,0 +1,4 @@
export interface LoginData {
userid: string;
password: string;
}

View File

@ -0,0 +1,42 @@
<template>
<div>
<q-page padding class="fit row justify-center content-center items-center">
<div class="text-h5 row">
Deine Sessions:
</div>
<!--<div class="fit row justify-center content-center items-center">
<q-card
class="col-4"
height=""
v-for="(session, index) in sessions"
:key="'session' + index"
>
<q-card-section>
{{ session }}
</q-card-section>
</q-card>
</div> -->
<div class="row">
<q-btn label="show sessions" @click="showRootGetters" />
</div>
</q-page>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref } from '@vue/composition-api';
import { mainLink } from '../plugin';
export default defineComponent({
// name: 'PageName'
setup(_, ctx) {
const sessions = computed(
() => ctx.root.$store.getters['sessions/sessions']
);
function showRootGetters() {
//ctx.root.$store.dispatch('sessions/getSessions');
console.log(sessions.value);
}
return { showRootGetters, sessions };
}
});
</script>

View File

@ -3,22 +3,18 @@
<q-page <q-page
padding padding
class="fit row justify-center content-center items-center" class="fit row justify-center content-center items-center"
v-if="true" v-if="checkMain"
> >
<q-card <q-card class="col-4" height="">
class="col-4"
height=""
>
<q-card-section> <q-card-section>
Name: {{ userState.user.firstname }} {{ userState.user.lastname }}<br /> Name: {{ userState.firstname }} {{ userState.lastname }}<br />
E-Mail: {{ userState.user.mail }}<br /> E-Mail: {{ userState.mail }}<br />
Roles: <ul Roles:
v-for="role in userState.user.roles" <ul v-for="role in userState.roles" v-bind:key="role">
v-bind:key="role"
>
<li>{{ role }}</li> <li>{{ role }}</li>
</ul><br /> </ul>
Token expires: {{ userState.token.expires }} <br />
Token expires: {{ userToken.expires }}
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-page> </q-page>
@ -28,16 +24,25 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from '@vue/composition-api'; import { defineComponent, computed } from '@vue/composition-api';
import { UserStateInterface } from '../../../store/module-user/state'; import { UserStateInterface } from '../store/user';
import { mainLink } from '../plugin';
export default defineComponent({ export default defineComponent({
// name: 'PageName' // name: 'PageName'
setup(_, { root }) { setup(_, { root }) {
const userState = computed( const userState = computed(
() => <UserStateInterface>root.$store.state.user () => <UserStateInterface>root.$store.getters['user/user']
); );
const a = ctx.root.$flaschengeistPlugins.mainLinks; const userPermissions = computed(
return { a, userState }; () => <UserStateInterface>root.$store.getters['user/permissions']
}, );
const userToken = computed(
() => <UserStateInterface>root.$store.getters['user/token']
);
const checkMain = computed(() => {
return mainLink.name == root.$route.name;
});
return { userState, userPermissions, userToken, checkMain };
}
}); });
</script> </script>

View File

@ -1,4 +1,7 @@
import { Plugin, PluginMainLink } from 'boot/plugins'; import { Plugin, PluginMainLink } from 'boot/plugins';
import { Module } from 'vuex';
import userStore from './store/user';
import sessionsStore from './store/session';
import routes from './routes'; import routes from './routes';
const mainLink: PluginMainLink = { const mainLink: PluginMainLink = {
@ -6,7 +9,14 @@ const mainLink: PluginMainLink = {
title: 'User', title: 'User',
link: 'user', link: 'user',
icon: 'mdi-account', icon: 'mdi-account',
children: [] children: [
{
name: 'user',
title: 'Einstellungen',
link: 'user-settings',
icon: 'mdi-cog'
}
]
}; };
const plugin: Plugin = { const plugin: Plugin = {
@ -16,7 +26,11 @@ const plugin: Plugin = {
requiredModules: [], requiredModules: [],
shortcutsOut: [], shortcutsOut: [],
shortcuts: [], shortcuts: [],
version: '0.0.1' version: '0.0.1',
store: new Map<string, Module<any, any>>([
['user', userStore],
['sessions', sessionsStore]
])
}; };
export { mainLink }; export { mainLink };

View File

@ -8,7 +8,16 @@ const routes: RouteConfig[] = [
{ {
path: 'user', path: 'user',
name: 'user', name: 'user',
component: () => import('../pages/User.vue') component: () => import('../pages/User.vue'),
meta: { permissions: ['user'] },
children: [
{
path: 'settings',
name: 'user-settings',
meta: { permissions: ['user'] },
component: () => import('../pages/Settings.vue')
}
]
} }
] ]
} }

View File

@ -0,0 +1,68 @@
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
import { StateInterface } from 'src/store';
import { axios } from 'boot/axios';
import { LoginData } from 'src/plugins/user/models';
import { AxiosResponse } from 'axios';
import { LocalStorage } from 'quasar';
import { Router } from 'src/router';
export interface SessionInterface {
sessions: Session[];
loading: boolean;
}
export interface Session {
browser: string;
expires: string;
lifetime: number;
platform: string;
token: string;
}
const state: SessionInterface = {
sessions: [],
loading: false
};
const mutations: MutationTree<SessionInterface> = {
setSessions(state, sessions: Session[]) {
state.sessions = sessions;
},
setLoading(state, value) {
state.loading = value;
}
};
const actions: ActionTree<SessionInterface, StateInterface> = {
getSessions({ commit, rootGetters }) {
console.log(rootGetters);
commit('setLoading', true);
axios
.get('http://localhost:5000/auth', {
headers: { Token: rootGetters['user/token'].token }
})
.then(response => {
commit('setSessions', response.data);
})
.catch(error => {
console.exception(error);
})
.finally(() => {
commit('setLoading', false);
});
}
};
const getters: GetterTree<SessionInterface, StateInterface> = {
sessions(state) {
return state.sessions;
}
};
const sessions: Module<SessionInterface, StateInterface> = {
namespaced: true,
state,
mutations,
actions,
getters
};
export default sessions;

View File

@ -0,0 +1,126 @@
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
import { StateInterface } from 'src/store';
import { axios } from 'boot/axios';
import { LoginData } from 'src/plugins/user/models';
import { AxiosResponse } from 'axios';
import { LocalStorage } from 'quasar';
import { Router } from 'src/router';
export interface Token {
browser: string;
expires: string;
lifetime: number;
platform: string;
token: string;
}
export interface User {
display_name: string | null;
firstname: string;
lastname: string;
mail: string;
roles: string[];
}
export interface UserStateInterface extends LoginResponse {
loginLoading: boolean;
}
export interface LoginResponse {
permissions: string[];
token: Token;
user: User;
userid: string;
}
const state: UserStateInterface = {
permissions: [],
token: { browser: '', expires: '', lifetime: -1, platform: '', token: '' },
user: {
display_name: '',
firstname: '',
lastname: '',
mail: '',
roles: []
},
userid: '',
loginLoading: false
};
const mutations: MutationTree<UserStateInterface> = {
setPermissions(state, data: []) {
state.permissions = data;
},
setToken(state, data: Token) {
state.token = data;
},
setUser(state, data: User) {
state.user = data;
},
setUserId(state, data: string) {
state.userid = data;
},
setLoginLoading(state, data: boolean) {
state.loginLoading = data;
},
showState(state) {
console.log(state);
}
};
const actions: ActionTree<UserStateInterface, StateInterface> = {
login({ commit }, data: LoginData) {
console.log('bla');
commit('setLoginLoading', true);
void axios
.post('http://localhost:5000/auth', data)
.then((response: AxiosResponse<LoginResponse>) => {
commit('setPermissions', response.data.permissions);
console.log('saved permisisons');
commit('setToken', response.data.token);
commit('setUser', response.data.user);
commit('setUserId', response.data.userid);
commit('showState');
//LocalStorage.set('permissions', response.data.permissions);
//LocalStorage.set('token', response.data.token);
//LocalStorage.set('user', response.data.user);
//LocalStorage.set('userid', response.data.userid);
void Router.push({ name: 'user' });
})
.catch(error => {
console.exception(error);
})
.finally(() => {
commit('setLoginLoading', false);
});
}
};
const getters: GetterTree<UserStateInterface, StateInterface> = {
permissions({ permissions }) {
return permissions;
},
token({ token }) {
return token;
},
user({ user }) {
return user;
},
userid({ userid }) {
return userid;
},
loginLoading({ loginLoading }) {
return loginLoading;
}
};
const userStore: Module<UserStateInterface, StateInterface> = {
namespaced: true,
actions,
getters,
mutations,
state
};
export default userStore;

View File

@ -8,20 +8,19 @@ import routes from './routes';
* If not building with SSR mode, you can * If not building with SSR mode, you can
* directly export the Router instantiation * directly export the Router instantiation
*/ */
export const Router: VueRouter = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,
export default route<Store<StateInterface>>(function ({ Vue }) { // Leave these as is and change from quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
});
export default route<Store<StateInterface>>(function({ Vue }) {
Vue.use(VueRouter); Vue.use(VueRouter);
const Router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,
// Leave these as is and change from quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
});
return Router; return Router;
}) });

View File

@ -11,16 +11,14 @@ import { UserStateInterface } from './module-user/state';
export interface StateInterface { export interface StateInterface {
// Define your own store structure, using submodules if needed // Define your own store structure, using submodules if needed
user: UserStateInterface; example: unknown;
} }
export default store(function ({ Vue }) { export default store(function({ Vue }) {
Vue.use(Vuex); Vue.use(Vuex);
const Store = new Vuex.Store<StateInterface>({ const Store = new Vuex.Store<StateInterface>({
modules: { modules: {},
user
},
// enable strict mode (adds overhead!) // enable strict mode (adds overhead!)
// for dev mode only // for dev mode only