added admin (group, user) interface components
This commit is contained in:
17
package-lock.json
generated
17
package-lock.json
generated
@@ -2704,6 +2704,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
|
||||||
"integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
|
"integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
|
||||||
},
|
},
|
||||||
|
"bootstrap-sass": {
|
||||||
|
"version": "3.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-sass/-/bootstrap-sass-3.4.1.tgz",
|
||||||
|
"integrity": "sha512-p5rxsK/IyEDQm2CwiHxxUi0MZZtvVFbhWmyMOt4lLkA4bujDA1TGoKT0i1FKIWiugAdP+kK8T5KMDFIKQCLYIA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"bootstrap-vue": {
|
"bootstrap-vue": {
|
||||||
"version": "2.0.0-rc.13",
|
"version": "2.0.0-rc.13",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.13.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.13.tgz",
|
||||||
@@ -7545,6 +7551,11 @@
|
|||||||
"topo": "3.x.x"
|
"topo": "3.x.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jquery": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
|
||||||
|
},
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
|
||||||
@@ -9483,9 +9494,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"popper.js": {
|
"popper.js": {
|
||||||
"version": "1.14.7",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz",
|
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz",
|
||||||
"integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ=="
|
"integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA=="
|
||||||
},
|
},
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.20",
|
"version": "1.0.20",
|
||||||
|
|||||||
@@ -11,8 +11,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/cli": "^3.4.1",
|
"@vue/cli": "^3.4.1",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
|
"bootstrap": "^4.3.1",
|
||||||
"bootstrap-vue": "^2.0.0-rc.13",
|
"bootstrap-vue": "^2.0.0-rc.13",
|
||||||
|
"jquery": "^3.3.1",
|
||||||
"js-cookie": "^2.2.0",
|
"js-cookie": "^2.2.0",
|
||||||
|
"popper.js": "^1.15.0",
|
||||||
"vue": "^2.6.6",
|
"vue": "^2.6.6",
|
||||||
"vue-axios": "^2.1.4",
|
"vue-axios": "^2.1.4",
|
||||||
"vue-class-component": "^6.0.0",
|
"vue-class-component": "^6.0.0",
|
||||||
@@ -34,8 +37,9 @@
|
|||||||
"@vue/cli-plugin-unit-mocha": "^3.4.0",
|
"@vue/cli-plugin-unit-mocha": "^3.4.0",
|
||||||
"@vue/cli-service": "^3.4.0",
|
"@vue/cli-service": "^3.4.0",
|
||||||
"@vue/test-utils": "^1.0.0-beta.20",
|
"@vue/test-utils": "^1.0.0-beta.20",
|
||||||
|
"bootstrap-sass": "^3.4.1",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.11.0",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"typescript": "^3.0.0",
|
"typescript": "^3.0.0",
|
||||||
"vue-template-compiler": "^2.5.21"
|
"vue-template-compiler": "^2.5.21"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />
|
<!--<link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />-->
|
||||||
<style>
|
<style>
|
||||||
a { cursor: pointer; }
|
a { cursor: pointer; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
49
src/App.vue
49
src/App.vue
@@ -4,21 +4,66 @@
|
|||||||
<router-link to="/">Home</router-link> |
|
<router-link to="/">Home</router-link> |
|
||||||
<router-link to="/about">About</router-link> |
|
<router-link to="/about">About</router-link> |
|
||||||
<router-link to="/login">Login</router-link> |
|
<router-link to="/login">Login</router-link> |
|
||||||
<router-link to="/profile">Profile</router-link>
|
<router-link to="/profile">Profile</router-link> |
|
||||||
|
<!-- Example split danger button -->
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" onclick="location.href='/admin'" class="btn btn-danger">Admin</button>
|
||||||
|
<button type="button" class="btn btn-danger dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="sr-only">Toggle Dropdown</span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<router-link class="dropdown-item" :to="{ name: 'admin.user' }">{{ $t('user') }}</router-link>
|
||||||
|
<router-link class="dropdown-item" :to="{ name: 'admin.group' }">{{ $t('group') }}</router-link>
|
||||||
|
<a class="dropdown-item" href="#">Something else here</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="#">Separated link</a>
|
||||||
|
</div>
|
||||||
|
</div> |
|
||||||
|
<span>({{tokenValidity}})</span> |
|
||||||
|
<span>({{refreshTokenValidity}})</span>
|
||||||
</div>
|
</div>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {getRemainingJwtValiditySeconds} from "@/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
tokenValidity: -1,
|
||||||
|
refreshTokenValidity: -1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
|
||||||
|
this.$nextTick(() =>{
|
||||||
|
window.setInterval(() => {
|
||||||
|
this.$log.debug(getRemainingJwtValiditySeconds(this.$store.state.access_token));
|
||||||
|
this.tokenValidity = getRemainingJwtValiditySeconds(this.$store.state.access_token);
|
||||||
|
this.refreshTokenValidity = getRemainingJwtValiditySeconds(this.$store.state.refresh_token);
|
||||||
|
this.$log.debug(this.$store.state);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '../node_modules/bootstrap/scss/bootstrap.scss';
|
||||||
#app {
|
#app {
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}About
|
}
|
||||||
#nav {
|
#nav {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
a {
|
a {
|
||||||
|
|||||||
12
src/api/Repository.js
Normal file
12
src/api/Repository.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Repository.js
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const baseDomain = "http://localhost:5443";
|
||||||
|
const API_URL = `${baseDomain}/api/v1`;
|
||||||
|
|
||||||
|
export default axios.create({
|
||||||
|
API_URL,
|
||||||
|
headers: { headers: { Authorization: `Bearer ${jwt}` } },
|
||||||
|
});
|
||||||
@@ -45,6 +45,10 @@ export function fetchUsers(jwt: any) {
|
|||||||
return axios.get(`${API_URL}/v1/user`, { headers: { Authorization: `Bearer ${jwt}` } });
|
return axios.get(`${API_URL}/v1/user`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createUser(jwt: any, userData: any) {
|
||||||
|
return axios.post(`${API_URL}/v1/user`, userData, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchProfile(jwt: any) {
|
export function fetchProfile(jwt: any) {
|
||||||
Vue.$log.debug("JWT: "+ jwt);
|
Vue.$log.debug("JWT: "+ jwt);
|
||||||
return axios.get(`${API_URL}/v1/user/profile`, { headers: { Authorization: `Bearer ${jwt}` } });
|
return axios.get(`${API_URL}/v1/user/profile`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
|||||||
55
src/components/Admin.vue
Normal file
55
src/components/Admin.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<!-- components/Admin.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Admin Tools</h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<router-link :to="{ name: 'admin.group'}">{{ $t('group') }}</router-link>
|
||||||
|
<br>
|
||||||
|
<router-link :to="{ name: 'admin.user'}">{{ $t('user') }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<section>
|
||||||
|
<router-view></router-view>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { EventBus } from '@/utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate () {
|
||||||
|
this.$store.dispatch('login', { email: this.email, password: this.password })
|
||||||
|
.then(() => this.$router.push('/'));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$log.debug("Admin: mounting...");
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
59
src/components/Group.vue
Normal file
59
src/components/Group.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!-- components/Group.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Groups</h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<h3 class="title">Add Group</h3>
|
||||||
|
<input v-model="group.name" placeholder="Group name">
|
||||||
|
<p>Name is: {{ group.name }}</p>
|
||||||
|
<span>Multiline message is:</span>
|
||||||
|
<p style="white-space: pre-line;">{{ group.description }}</p>
|
||||||
|
<br>
|
||||||
|
<textarea v-model="group.message" placeholder="Description for group"></textarea>
|
||||||
|
<button v-on:click="create_group">{{$t('create_group')}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { EventBus } from '@/utils'
|
||||||
|
import {getRemainingJwtValiditySeconds} from "../utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
errorMsg: '',
|
||||||
|
group: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
create_group () {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
src/components/User.vue
Normal file
47
src/components/User.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- components/User.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h3 class="title">Manage users</h3>
|
||||||
|
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { EventBus } from '@/utils'
|
||||||
|
import {getRemainingJwtValiditySeconds} from "../utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
users: '',
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,6 +12,9 @@ import FlagIcon from 'vue-flag-icon';
|
|||||||
// following is to avoid missing type definitions
|
// following is to avoid missing type definitions
|
||||||
// const FlagIcon = require('vue-flag-icon');
|
// const FlagIcon = require('vue-flag-icon');
|
||||||
|
|
||||||
|
import 'bootstrap';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import NotFound from './views/NotFound.vue';
|
|||||||
import Survey from '@/components/Survey.vue';
|
import Survey from '@/components/Survey.vue';
|
||||||
import NewSurvey from '@/components/NewSurvey.vue';
|
import NewSurvey from '@/components/NewSurvey.vue';
|
||||||
import Login from '@/components/Login.vue';
|
import Login from '@/components/Login.vue';
|
||||||
|
import Admin from '@/components/Admin.vue';
|
||||||
import Profile from '@/components/Profile.vue';
|
import Profile from '@/components/Profile.vue';
|
||||||
|
import User from '@/components/User.vue';
|
||||||
|
import Group from '@/components/Group.vue';
|
||||||
import store from '@/store';
|
import store from '@/store';
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
@@ -23,9 +26,26 @@ export const router = new Router({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'Login',
|
name: 'login',
|
||||||
component: Login,
|
component: Login,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
name: 'admin',
|
||||||
|
component: Admin,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'admin.user',
|
||||||
|
path: 'user',
|
||||||
|
component: User,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin.group',
|
||||||
|
path: 'group',
|
||||||
|
component: Group,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'about',
|
name: 'about',
|
||||||
@@ -56,6 +76,7 @@ export const router = new Router({
|
|||||||
if (!store.getters.isAuthenticated) {
|
if (!store.getters.isAuthenticated) {
|
||||||
Vue.$log.debug('not authenticated!');
|
Vue.$log.debug('not authenticated!');
|
||||||
if (store.getters.isRefreshTokenValid) {
|
if (store.getters.isRefreshTokenValid) {
|
||||||
|
Vue.$log.debug('refresh token is still valid :)');
|
||||||
store.dispatch('refreshToken')
|
store.dispatch('refreshToken')
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(() => next('/login'));
|
.catch(() => next('/login'));
|
||||||
|
|||||||
7
src/scss/_base.scss
Normal file
7
src/scss/_base.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019. Tobias Kurze
|
||||||
|
*/
|
||||||
|
|
||||||
|
.router-link-exact-active {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
@@ -3,9 +3,10 @@ module.exports = {
|
|||||||
loaderOptions: {
|
loaderOptions: {
|
||||||
sass: {
|
sass: {
|
||||||
data: `
|
data: `
|
||||||
@import "@/scss/_variables.scss";
|
@import "@/scss/_variables.scss";
|
||||||
`
|
@import "@/scss/_base.scss";
|
||||||
}
|
`,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user