moved to bootstrap-vue, there is a bug in vee-validate and bootstrap-vue >rc15

This commit is contained in:
2019-04-23 16:13:18 +02:00
parent c374ae1796
commit 4a97efce6f
26 changed files with 4310 additions and 743 deletions

View File

@@ -1,77 +1,124 @@
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/login">Login</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 id="app">
<sync-loader :loading="isLoading"></sync-loader>
<div id="nav">
<router-link to="/">Home</router-link>
|
<router-link to="/about">About</router-link>
|
<router-link to="/login">Login</router-link>
|
<router-link to="/profile">Profile</router-link>
|
<router-link :to="{ name: 'rooms' }">{{ $t('rooms') }}</router-link>
|
<router-link :to="{ name: 'recorders' }">{{ $t('recorders') }}</router-link>
|
<router-link :to="{ name: 'commands' }">{{ $t('commands') }}</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>
</div> |
<span>({{tokenValidity}})</span> |
<span>({{refreshTokenValidity}})</span>
|
<span>({{tokenValidity}})</span> |
<span>({{refreshTokenValidity}})</span>
</div>
<router-view/>
</div>
<router-view/>
</div>
</template>
<script>
import {getRemainingJwtValiditySeconds} from "@/utils";
import {getRemainingJwtValiditySeconds} from '@/utils';
import SyncLoader from 'vue-spinner/src/SyncLoader.vue';
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);
});
export default {
components: {
SyncLoader,
},
data() {
return {
isLoading: false,
tokenValidity: -1,
refreshTokenValidity: -1,
};
},
methods: {
// todo ...
},
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">
@import '../node_modules/bootstrap/scss/bootstrap.scss';
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
@import '../node_modules/bootstrap/scss/bootstrap.scss';
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
#nav {
padding: 30px;
text-align: center;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
/* Absolute Center Spinner */
.v-spinner {
position: fixed;
z-index: 999;
// height: 2em;
// width: 2em;
overflow: visible;
margin: auto;
top: 50%;
left: 50%;
bottom: 0;
right: 0;
}
/* Transparent Overlay */
.v-spinner:before {
content: '';
display: block;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.2);
}
}
}
</style>

View File

@@ -1,13 +0,0 @@
// RepositoryFactory.js
import GroupRepository from "./groupRepository";
import UserRepository from "./userRepository";
const repositories = {
group: GroupRepository,
user: UserRepository,
};
export const RepositoryFactory = {
get: name => repositories[name],
};

View File

@@ -0,0 +1,28 @@
// RepositoryFactory.js
import GroupRepository from './groupRepository';
import UserRepository from './userRepository';
import RoomRepository from './roomRepository';
import RecorderRepository from './recorderRepository';
export default function get(name: string) {
switch (name) {
case 'group': {
return GroupRepository;
}
case 'recorder': {
return RecorderRepository;
}
case 'room': {
return RoomRepository;
}
case 'user': {
return UserRepository;
}
default: {
// statements;
break;
}
}
}

View File

@@ -1,19 +1,20 @@
// groupRepository.js
import Repository from "./Repository";
// @ts-ignore
import Repository from './Repository';
const resource = "/group";
const resource = '/group';
export default {
getGroups() {
return Repository.get(`${resource}`);
},
getGroup(groupId) {
getGroup(groupId: number) {
return Repository.get(`${resource}/${groupId}`);
},
createGroup(groupData) {
createGroup(groupData: any) {
return Repository.post(`${resource}`, groupData);
},
};

View File

@@ -58,6 +58,6 @@ export function fetchUserGroup(jwt: any, groupId: 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}` } });
}

View File

@@ -0,0 +1,24 @@
// groupRepository.ts
// @ts-ignore
import Repository from './Repository';
const resource = '/recorder';
export default {
getRecorders() {
return Repository.get(`${resource}`);
},
getRecorder(recorderId: number) {
return Repository.get(`${resource}/${recorderId}`);
},
createRecorder(recorderData: any) {
return Repository.post(`${resource}`, recorderData);
},
updateRecorder(recorderId: number, recorderData: any) {
return Repository.put(`${resource}/${recorderId}`, recorderData);
},
};

24
src/api/roomRepository.ts Normal file
View File

@@ -0,0 +1,24 @@
// groupRepository.ts
// @ts-ignore
import Repository from './Repository';
const resource = '/room';
export default {
getRooms() {
return Repository.get(`${resource}`);
},
getRoom(roomId: number) {
return Repository.get(`${resource}/${roomId}`);
},
createRoom(roomData: any) {
return Repository.post(`${resource}`, roomData);
},
updateRoom(roomId: number, roomData: any) {
return Repository.put(`${resource}/${roomId}`, roomData);
},
};

View File

@@ -1,19 +1,20 @@
// groupRepository.js
// groupRepository.ts
import Repository from "./Repository";
// @ts-ignore
import Repository from './Repository';
const resource = "/user";
const resource = '/user';
export default {
getUsers() {
return Repository.get(`${resource}`);
},
getUser(userId) {
getUser(userId: number) {
return Repository.get(`${resource}/${userId}`);
},
createUser(userData) {
createUser(userData: any) {
return Repository.post(`${resource}`, userData);
},
};

View File

@@ -22,29 +22,31 @@
</template>
<script>
import { EventBus } from '@/utils'
import {EventBus} from '@/utils';
export default {
data () {
data() {
return {
email: '',
errorMsg: '',
};
},
methods: {
authenticate () {
this.$store.dispatch('login', { email: this.email, password: this.password })
authenticate() {
this.$store.dispatch('login', {email: this.email, password: this.password})
.then(() => this.$router.push('/'));
},
},
mounted () {
this.$log.debug("Admin: mounting...");
mounted() {
this.$log.debug('Admin: mounting...');
},
beforeDestroy () {
beforeDestroy() {
// todo ...
},
computed: {
// todo ...
},
}
};
</script>
<style lang="scss">

View File

@@ -12,7 +12,8 @@
<thead>
<tr>
<th v-for="col in group_columns" v-on:click="sortTable(col)">{{col}}
<div class="arrow" v-if="col == sortColumn" v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
<div class="arrow" v-if="col == sortColumn"
v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
</th>
</tr>
</thead>
@@ -26,7 +27,8 @@
<div class="number"
v-for="i in num_group_pages()"
v-bind:class="[i == currentPage ? 'active' : '']"
v-on:click="change_page(i)">{{i}}</div>
v-on:click="change_page(i)">{{i}}
</div>
</div>
</div>
@@ -50,17 +52,18 @@
</template>
<script>
import { EventBus } from '@/utils'
import { RepositoryFactory} from "@/api/RepositoryFactory";
const GroupRepository = RepositoryFactory.get('group');
import {EventBus} from '@/utils';
import getRepository from '@/api/RepositoryFactory';
const GroupRepository = getRepository('group');
export default {
data () {
data() {
return {
isLoading: false,
groups: [],
errorMsg: '',
group: {name:'', description:''},
group: {name: '', description: ''},
sortColumn: '',
currentPage: 1,
elementsPerPage: 5,
@@ -71,7 +74,7 @@
this.isLoading = true;
// const { data } = await GroupRepository.get();
GroupRepository.getGroups()
.then(response => {
.then((response) => {
this.groups = response.data;
this.isLoading = false;
this.$log.debug(response);
@@ -79,8 +82,8 @@
},
get_group_rows() {
var start = (this.currentPage-1) * this.elementsPerPage;
var end = start + this.elementsPerPage;
const start = (this.currentPage - 1) * this.elementsPerPage;
const end = start + this.elementsPerPage;
return this.groups.slice(start, end);
},
num_group_pages() {
@@ -97,19 +100,19 @@
this.sortColumn = col;
}
var ascending = this.ascending;
const ascending = this.ascending;
this.groups.sort(function(a, b) {
this.groups.sort((a, b) => {
if (a[col] > b[col]) {
return ascending ? 1 : -1
return ascending ? 1 : -1;
} else if (a[col] < b[col]) {
return ascending ? -1 : 1
return ascending ? -1 : 1;
}
return 0;
})
});
},
createGroup(){
this.$log.info("Creating new group...");
createGroup() {
this.$log.info('Creating new group...');
GroupRepository.createGroup(this.group);
this.fetch();
},
@@ -118,6 +121,7 @@
this.fetch();
},
beforeDestroy() {
// todo....or not
},
computed: {
group_columns() {
@@ -127,7 +131,7 @@
return Object.keys(this.groups[0]);
},
},
}
};
</script>
<style lang="scss">
@@ -135,6 +139,7 @@
color: red;
font-weight: bold;
}
table {
font-family: 'Open Sans', sans-serif;
width: 750px;
@@ -152,17 +157,21 @@
padding: 8px;
min-width: 30px;
}
table th:hover {
background: #717699;
}
table td {
text-align: left;
padding: 8px;
border-right: 2px solid #7D82A8;
}
table td:last-child {
border-right: none;
}
table tbody tr:nth-child(2n) td {
background: #D4D8F9;
}
@@ -184,17 +193,21 @@
padding: 8px;
min-width: 30px;
}
table th:hover {
background: #717699;
}
table td {
text-align: left;
padding: 8px;
border-right: 2px solid #7D82A8;
}
table td:last-child {
border-right: none;
}
table tbody tr:nth-child(2n) td {
background: #D4D8F9;
}
@@ -209,9 +222,11 @@
.arrow_down {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAaCAYAAABPY4eKAAAAAXNSR0IArs4c6QAAAvlJREFUSA29Vk1PGlEUHQaiiewslpUJiyYs2yb9AyRuJGm7c0VJoFXSX9A0sSZN04ULF12YEBQDhMCuSZOm1FhTiLY2Rky0QPlQBLRUsICoIN/0PCsGyox26NC3eTNn3r3n3TvnvvsE1PkwGo3yUqkkEQqFgw2Mz7lWqwng7ztN06mxsTEv8U0Aam5u7r5EInkplUol/f391wAJCc7nEAgE9Uwmkzo4OPiJMa1Wq6cFs7Ozt0H6RqlUDmJXfPIx+qrX69Ti4mIyHA5r6Wq1egND+j+IyW6QAUoul18XiUTDNHaSyGazKcZtdgk8wqhUKh9o/OMvsVgsfHJy0iWqVrcQNRUMBnd6enqc9MjISAmRP3e73T9al3XnbWNjIw2+KY1Gc3imsNHR0YV4PP5+d3e32h3K316TySQFoX2WyWR2glzIO5fLTSD6IElLNwbqnFpbWyO/96lCoai0cZjN5kfYQAYi5H34fL6cxWIZbya9iJyAhULBHAqFVlMpfsV/fHxMeb3er+Vy+VUzeduzwWC45XA4dlD/vEXvdDrj8DvURsYEWK3WF4FA4JQP9mg0WrHZbEYmnpa0NxYgPVObm5teiLABdTQT8a6vrwdRWhOcHMzMzCiXlpb2/yV6qDttMpkeshEzRk4Wo/bfoe4X9vb2amzGl+HoXNT29vZqsVi0sK1jJScG+Xx+HGkL4Tew2TPi5zUdQQt9otPpuBk3e0TaHmMDh1zS7/f780S0zX6Yni+NnBj09fUZUfvudDrNZN+GkQbl8Xi8RLRtHzsB9Hr9nfn5+SjSeWUCXC7XPq5kw53wsNogjZNohYXL2EljstvtrAL70/mVaW8Y4OidRO1/gwgbUMvcqGmcDc9aPvD1gnTeQ+0nmaInokRj0nHh+uvIiVOtVvt2a2vLv7Ky0tL3cRTXIcpPAwMDpq6R4/JXE4vFQ5FI5CN+QTaRSFCYc8vLy1l0rge4ARe5kJ/d27kYkLXoy2Jo4C7K8CZOsEBvb+9rlUp1xNXPL7v3IDwxvPD6AAAAAElFTkSuQmCC')
}
.arrow_up {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAaCAYAAACgoey0AAAAAXNSR0IArs4c6QAAAwpJREFUSA21Vt1PUmEYP4dvkQ8JFMwtBRocWAkDbiqXrUWXzU1rrTt0bdVqXbb1tbW16C9IBUSmm27cODdneoXjputa6069qwuW6IIBIdLvdaF4OAcOiGeDc87zPs/vd57P96WpFq7p6enbGo1mjKZpeTabjU1MTCRagGnOZHFxcXxtbe1XKpUq7+zslJeXl//Mz8+Hy+Uy3RxSE9qTk5M3otFooVQqgef4Wl9f343FYoEmoISrxuNxFX5f9vb2jhn/PxUKhfLS0tIPfFifUESRUMV8Pv/M6XReRm5rTGQyGeXxeGxYe1ezeBpBOBx2rKysbO7v79d4Wy3Y2Nj4GQqFbgnhaugxwiuGJx99Pp9FLBbXxYTXvTqd7v3MzIy6riIWGxJnMpl7AwMD14xGYyMsSq1WUyQdUqn0eSPlusQIsbGrq+vl4OCgvhFQZd1utyv1en0gEolcqsi47nWJlUrlG5fLZVcoFFy2nDKSDpIWlUoVTCQSEk4lCHmJMZ2GTCbTiMVikfIZ88l7enoos9l8dXt7+z6fDicxSJUokqDX6xXcl2wCROoc0vQCWL3sNfLOSdzR0fHY4XC4tVotl40gmVwup9xuN4OQv+UyqCFGH9rg7SOGYVRcBs3IEG4J0nVnamrqOtvuBDGGgQg9+wHFcVEi4a0LNkbdd6TrPKo8ODc311mteIIYjT/a398/jK+s1jnVM0kXoufCFvq0GuiIGEVgQIhfoygM1QrteEa9dAL7ITiYCt4RMabOK5AyKKzKWtvupLcRciu8D5J0EuDDPyT/Snd39yh6VtY2NhYQSR9G79Ds7OxdskRjEyAufvb7/cPoO5Z6e1+xtVKrq6vfcFzyi/A3ZrPZ3GdNSlwgo5ekE4X2RIQGf2C1WlufFE0GBeGWYQ8YERWLxQtnUVB830MKLZfL9RHir8lkssCn2G751tZWEWe03zTKm15YWPiEiXXTYDB0Ig/t7yd8PRws4EicwWHxO4jHD8/C5HiTTqd1BwcHFozKU89origB+y/kmzgYpgOBQP4fGmUiZmJ+WNgAAAAASUVORK5CYII=')
}
.arrow {
float: right;
width: 12px;
@@ -230,6 +245,7 @@
margin: 0px 5px;
cursor: pointer;
}
.number:hover, .number.active {
background: #717699;
}

View File

@@ -9,14 +9,14 @@
</div>
</div>
<ul>
<li v-for="(provider, index) in loginProviders" v-bind:id="index">
<a :href="provider.url">{{ index }} ({{provider.type}})</a>
</li>
<li v-for="(provider, index) in loginProviders" v-bind:id="index">
<a :href="provider.url">{{ index }} ({{provider.type}})</a>
</li>
</ul>
<h3>Users</h3>
<ul>
<li v-for="(user) in users" >
<li v-for="(user) in users">
<a href="#">({{user.id}}) {{user.first_name}}</a>
</li>
</ul>
@@ -50,10 +50,11 @@
</template>
<script>
import { EventBus } from '@/utils'
import {EventBus} from '@/utils';
export default {
data () {
data() {
return {
email: '',
password: '',
@@ -62,19 +63,20 @@
};
},
methods: {
authenticate () {
this.$store.dispatch('login', { email: this.email, password: this.password })
authenticate() {
this.$store.dispatch('login', {email: this.email, password: this.password})
.then(() => this.$router.push('/'));
},
register () {
this.$store.dispatch('register', { email: this.email, password: this.password })
register() {
this.$store.dispatch('register', {email: this.email, password: this.password})
.then(() => this.$router.push('/'));
},
oidc_login () {
this.$store.dispatch('oidc_login', "\\oidc_login_redirection");
oidc_login() {
this.$store.dispatch('oidc_login', '\\oidc_login_redirection');
},
},
mounted () {
mounted() {
// this.$parent.$data.isLoading = true;
EventBus.$on('failedRegistering', (msg) => {
this.errorMsg = msg;
});
@@ -91,22 +93,22 @@
this.$store.dispatch('loadLoginProviders');
// get tokens
if(this.$cookies.isKey("tokens")){
this.$store.dispatch('storeTokens', JSON.parse(atob(this.$cookies.get("tokens"))));
this.$cookies.remove("tokens");
if (this.$cookies.isKey('tokens')) {
this.$store.dispatch('storeTokens', JSON.parse(atob(this.$cookies.get('tokens'))));
this.$cookies.remove('tokens');
}
console.log(this.$cookies.keys());
this.$log.debug(this.$cookies.keys());
},
beforeDestroy () {
beforeDestroy() {
EventBus.$off('failedRegistering');
EventBus.$off('failedAuthentication');
},
computed: {
users () {
users() {
return this.$store.state.users;
},
},
}
};
</script>
<style lang="scss">

View File

@@ -1,75 +0,0 @@
<template>
<div>
<div class="field">
<label class="label is-large">Question</label>
<div class="control">
<input type="text" class="input is-large" v-model="question">
</div>
</div>
<div class="field">
<div class="control">
<a class="button is-large is-info" @click="addChoice">
<span class="icon is-small">
<i class="fa fa-plus-square-o fa-align-left" aria-hidden="true"></i>
</span>
<span>Add choice</span>
</a>
<a class="button is-large is-primary" @click="saveQuestion">
<span class="icon is-small">
<i class="fa fa-check"></i>
</span>
<span>Save</span>
</a>
</div>
</div>
<h2 class="label is-large" v-show="choices.length > 0">Question Choices</h2>
<div class="field has-addons" v-for="(choice, idx) in choices" v-bind:key="idx">
<div class="control choice">
<input type="text" class="input is-large" v-model="choices[idx]">
</div>
<div class="control">
<a class="button is-large">
<span class="icon is-small" @click.stop="removeChoice(choice)">
<i class="fa fa-times" aria-hidden="true"></i>
</span>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
question: '',
choices: []
}
},
methods: {
removeChoice (choice) {
const idx = this.choices.findIndex(c => c === choice)
this.choices.splice(idx, 1)
},
saveQuestion () {
this.$emit('questionComplete', {
question: this.question,
choices: this.choices.filter(c => !!c)
})
this.question = ''
this.choices = ['']
},
addChoice () {
this.choices.push('')
}
}
}
</script>
<style>
.choice {
width: 90%;
}
</style>

View File

@@ -1,122 +0,0 @@
<template>
<div>
<section class="hero is-primary">
<div class="hero-body">
<div class="container has-text-centered">
<h2 class="title">{{ name }}</h2>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="tabs is-centered is-fullwidth is-large">
<ul>
<li :class="{'is-active': step == 'name'}" @click="step = 'name'">
<a>Name</a>
</li>
<li :class="{'is-active': step == 'questions'}" @click="step = 'questions'">
<a>Questions</a>
</li>
<li :class="{'is-active': step == 'review'}" @click="step = 'review'">
<a>Review</a>
</li>
</ul>
</div>
<div class="columns">
<div class="column is-half is-offset-one-quarter">
<div class="name" v-show="step === 'name'">
<div class="field">
<label class="label is-large" for="name">Survey name:</label>
<div class="control">
<input type="text" class="input is-large" id="name" v-model="name">
</div>
</div>
</div>
<div class="questions" v-show="step === 'questions'">
<new-question v-on:questionComplete="appendQuestion"/>
</div>
<div class="review" v-show="step === 'review'">
<ul>
<li class="question" v-for="(question, qIdx) in questions" :key="`question-${qIdx}`">
<div class="title">
{{ question.question }}
<span class="icon is-medium is-pulled-right delete-question"
@click.stop="removeQuestion(question)">
<i class="fa fa-times" aria-hidden="true"></i>
</span>
</div>
<ul>
<li v-for="(choice , cIdx) in question.choices" :key="`choice-${cIdx}`">
{{ cIdx + 1 }}. {{ choice }}
</li>
</ul>
</li>
</ul>
<div class="control">
<a class="button is-large is-primary" @click="submitSurvey">Submit</a>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script>
import NewQuestion from '@/components/NewQuestion'
export default {
components: { NewQuestion },
data () {
return {
step: 'name',
name: '',
questions: []
}
},
methods: {
appendQuestion (newQuestion) {
this.questions.push(newQuestion)
},
removeQuestion (question) {
const idx = this.questions.findIndex(q => q.question === question.question)
this.questions.splice(idx, 1)
},
submitSurvey () {
this.$store.dispatch('submitNewSurvey', {
name: this.name,
questions: this.questions
})
.then(() => this.$router.push('/'))
.catch((error) => {
console.log('Error creating survey', error)
this.$router.push('/')
})
}
}
}
</script>
<style>
.question {
margin: 10px 20px 25px 10px;
}
.delete-question {
cursor: pointer;
padding: 10px;
}
.delete-question:hover {
background-color: lightgray;
border-radius: 50%;
}
</style>

View File

@@ -17,11 +17,11 @@
</template>
<script>
import { EventBus } from '@/utils'
import {getRemainingJwtValiditySeconds} from "../utils";
import {EventBus} from '@/utils';
import {getRemainingJwtValiditySeconds} from '../utils';
export default {
data () {
data() {
return {
email: '',
errorMsg: '',
@@ -30,19 +30,19 @@
};
},
methods: {
authenticate () {
this.$store.dispatch('login', { email: this.email, password: this.password })
authenticate() {
this.$store.dispatch('login', {email: this.email, password: this.password})
.then(() => this.$router.push('/'));
},
},
mounted () {
this.$log.debug("Profile: mounting...");
mounted() {
this.$log.debug('Profile: mounting...');
EventBus.$on('failedLoadingProfile', (msg) => {
this.errorMsg = msg;
});
this.$store.dispatch('loadProfile');
this.$nextTick(() =>{
this.$nextTick( () => {
window.setInterval(() => {
this.$log.debug(getRemainingJwtValiditySeconds(this.$store.state.access_token));
this.tokenValidity = getRemainingJwtValiditySeconds(this.$store.state.access_token);
@@ -52,15 +52,15 @@
},
beforeDestroy () {
beforeDestroy() {
EventBus.$off('failedLoadingProfile');
},
computed: {
profile () {
profile() {
return this.$store.state.profile;
},
},
}
};
</script>
<style lang="scss">

View File

@@ -0,0 +1,271 @@
<template>
<div>
<section class="section">
<h1 class="title">{{ $t('Manage recorders and recorder models') }}</h1>
<p class="subtitle">
{{ $t('List, create, update and delete') }} <strong>{{ $t('recorders') }}</strong> and <strong>recorder models</strong>!
</p>
<hr>
<h2 class="">{{ $t('Create recorder') }}</h2>
<!-- form starts here -->
<form v-on:submit.prevent="$log.debug(form)">
<section class="form">
<div class="field">
<label class="label">{{ $t('name') }}</label>
<div class="control">
<input name="name"
v-model="form.name"
v-validate="'required|min:3'"
v-bind:class="{'is-danger': errors.has('name')}"
class="input" type="text" placeholder="Full name">
</div>
<p class="help is-danger" v-show="errors.has('name')">
{{ errors.first('name') }}
</p>
</div>
<div class="field">
<label class="label">{{ $t('description') }}</label>
<div class="control">
<textarea class="textarea" placeholder="description of recorder"
v-model="form.description"></textarea>
</div>
</div>
<div class="field">
<label class="label">{{ $t('room') }}</label>
<div class="control">
<div class="select">
<select v-model="form.room">
<option disabled value="">No room selected</option>
<option v-for="option in options.room" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="label">{{ $t('model') }}</label>
<div class="control">
<div class="select">
<select v-model="form.model">
<option disabled value="">No model selected</option>
<option v-for="option in options.model" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="label">LogRocket Usecases</label>
<div class="control">
<div class="select is-multiple">
<select multiple v-model="form.logrocket_usecases">
<option>Debugging</option>
<option>Fixing Errors</option>
<option>User support</option>
</select>
</div>
</div>
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox" v-model="form.terms">
I agree to the <a href="#">terms and conditions</a>
</label>
</div>
</div>
<div class="field">
<label>
<strong>What dev concepts are you interested in?</strong>
</label>
<div class="control">
<label class="checkbox">
<input type="checkbox" v-model="form.concepts"
value="promises">
Promises
</label>
<label class="checkbox">
<input type="checkbox" v-model="form.concepts"
value="testing">
Testing
</label>
</div>
</div>
<div class="field">
<label><strong>Is JavaScript awesome?</strong></label>
<div class="control">
<label class="radio">
<input v-model="form.js_awesome" type="radio" value="Yes">
Yes
</label>
<label class="radio">
<input v-model="form.js_awesome" type="radio" value="Yeap!">
Yeap!
</label>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button
v-bind:disabled="errors.any()"
class="button is-primary">
Submit
</button>
</div>
</div>
</section>
</form>
<hr>
<h2 class="">{{ $t('Create recorder model') }}</h2>
<!-- form starts here -->
<form v-on:submit.prevent="$log.debug(form)">
<section class="form">
<div class="field">
<label class="label">{{ $t('model name') }}</label>
<div class="control">
<input name="name"
v-model="form.name"
v-validate="'required|min:3'"
v-bind:class="{'is-danger': errors.has('name')}"
class="input" type="text" placeholder="Full name">
</div>
<p class="help is-danger" v-show="errors.has('name')">
{{ errors.first('name') }}
</p>
</div>
<div class="field">
<label class="label">{{ $t('notes') }}</label>
<div class="control">
<textarea class="textarea" placeholder="Notes"
v-model="form.notes"></textarea>
</div>
</div>
<div class="field">
<label class="label">Recorder commands</label>
<div class="control">
<div class="select is-multiple">
<select multiple v-model="form.commands">
<option v-for="option in recorderCommands.options">
{{ option }}
</option>
</select>
</div>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button
v-bind:disabled="errors.any()"
class="button is-primary">
Create
</button>
</div>
</div>
</section>
</form>
</section>
<div class="column">
<section class="section" id="results">
<div class="box">
<ul>
<!-- loop through all the `form` properties and show their values -->
<li v-for="(item, k) in form">
<strong>{{ k }}:</strong> {{ item }}
</li>
</ul>
</div>
</section>
</div>
</div>
</template>
<script>
export default {
data() {
return {
step: 'name',
name: '',
questions: [],
form: {
name: '',
message: '',
inquiry_type: '',
logrocket_usecases: [],
terms: false,
concepts: [],
js_awesome: '',
},
};
},
methods: {
appendQuestion(newQuestion) {
this.questions.push(newQuestion);
},
removeQuestion(question) {
const idx = this.questions.findIndex((q) => q.question === question.question);
this.questions.splice(idx, 1);
},
submitSurvey() {
this.$store.dispatch('submitNewSurvey', {
name: this.name,
questions: this.questions,
})
.then(() => this.$router.push('/'))
.catch((error) => {
this.$log.error('Error creating survey', error);
this.$router.push('/');
});
},
},
computed: {
options() {
return {
room: [
{value: 'audimax', text: 'AudiMax'},
{value: 'hmu', text: 'Unterer Hörsaal'},
{value: 'hmo', text: 'Oberer Hörsaal'},
],
model: [
{value: 'extron', text: 'extron'},
{value: 'smp', text: 'SMP'},
{value: 'smp2', text: 'SMP2'},
],
};
},
recorderCommands() {
return {
options: [
'reboot',
'reset',
'setStreamingProfile',
],
};
},
},
};
</script>
<style>
</style>

353
src/components/Rooms.vue Normal file
View File

@@ -0,0 +1,353 @@
<template>
<div class="container">
<section class="section">
<h1 class="title">{{ $t('Manage rooms') }}</h1>
<p class="lead">
{{ $t('List, create and delete') }} <strong>{{ $t('rooms') }}</strong>
</p>
<br/>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="room-list-tab" data-toggle="tab" href="#room-list" role="tab"
aria-controls="room-list" aria-selected="true">Room list</a>
</li>
<li class="nav-item">
<a class="nav-link" id="room-create-tab" data-toggle="tab" href="#room-create" role="tab"
aria-controls="room-create" aria-selected="false">Create room</a>
</li>
</ul>
<b-tabs card>
<b-tab title="Room list" active>
<p>{{ $t('There are')}}&nbsp;{{rooms.length}}&nbsp;{{ $t('rooms defined')}}:</p>
<b-card-group deck>
<b-card v-for="(room) in rooms" :title="room.name">
<b-card-text>
<h5 class="card-title">{{ $t('name') }}:&nbsp;{{room.name}}&nbsp;
<a class="float-right badge badge-pill badge-primary">
<font-awesome-icon icon="pencil-alt"/>
</a>
</h5>
<p class="card-text"><strong>{{ $t('alternate_name') }}:</strong>&nbsp;{{room.alternate_name}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a>
</p>
<p class="card-text"><strong>{{ $t('room_number') }}:</strong>&nbsp;{{room.number}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a></p>
<p class="card-text">
<small><strong>{{ $t('comment') }}:</strong>&nbsp;{{room.comment}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a></small>
</p>
<hr/>
<div v-if="room.recorder">
<p class="card-text"><strong>{{ $t('Recorder') }}:</strong>&nbsp;{{room.recorder.name}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a></p>
</div>
<div v-else>
<p class="card-text"><strong>{{ $t('Recorder') }}:</strong>&nbsp;{{
$t('no_recorder_defined')}}</p>
<div class="form-group row">
<div class="col-sm-8">
<select class="form-control" v-model="form.recorder">
<option disabled value="">No recorder selected</option>
<option v-for="option in options.recorders" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
<label class="label col-sm-4 col-form-label">{{ $t('recorder') }}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
v-model="show_assigned_recorders" id="defaultCheck1">
<label class="form-check-label" for="defaultCheck1">
Show already assigned recorders
</label>
</div>
</div>
</b-card-text>
<div slot="footer">
<small class="text-muted">Last updated 3 mins ago</small>
</div>
</b-card>
<b-card title="Title">
<b-card-text>
This card has supporting text below as a natural lead-in to additional content.
</b-card-text>
<div slot="footer">
<small class="text-muted">Last updated 3 mins ago</small>
</div>
</b-card>
<b-card title="Title">
<b-card-text>
This is a wider card with supporting text below as a natural lead-in to additional
content.
This card has even longer content than the first to show that equal height action.
</b-card-text>
<div slot="footer">
<small class="text-muted">Last updated 3 mins ago</small>
</div>
</b-card>
</b-card-group>
</b-tab>
<b-tab title="Create room">
<b-card-text>
<p>{{ $t('Create a new room')}}:</p>
</b-card-text>
</b-tab>
</b-tabs>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="room-list" role="tabpanel" aria-labelledby="home-tab">
<p>{{ $t('There are')}}&nbsp;{{rooms.length}}&nbsp;{{ $t('rooms defined')}}:</p>
<div class="card-deck">
<div class="card" v-for="(room) in rooms">
<h5 class="card-header">{{room.name}} - {{room.number}}</h5>
<div class="card-body">
<h5 class="card-title">{{ $t('name') }}:&nbsp;{{room.name}}&nbsp;
<a class="float-right badge badge-pill badge-primary">
<font-awesome-icon icon="pencil-alt"/>
</a>
</h5>
<p class="card-text"><strong>{{ $t('alternate_name') }}:</strong>&nbsp;{{room.alternate_name}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a>
</p>
<p class="card-text"><strong>{{ $t('room_number') }}:</strong>&nbsp;{{room.number}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a></p>
<p class="card-text">
<small><strong>{{ $t('comment') }}:</strong>&nbsp;{{room.comment}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a></small>
</p>
<hr/>
<div v-if="room.recorder">
<p class="card-text"><strong>{{ $t('Recorder') }}:</strong>&nbsp;{{room.recorder.name}}&nbsp;
<a class="float-right badge badge-pill badge-info">
<font-awesome-icon icon="pencil-alt"/>
</a></p>
</div>
<div v-else>
<p class="card-text"><strong>{{ $t('Recorder') }}:</strong>&nbsp;{{
$t('no_recorder_defined')}}</p>
<div class="form-group row">
<div class="col-sm-8">
<select class="form-control" v-model="form.recorder">
<option disabled value="">No recorder selected</option>
<option v-for="option in options.recorders" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
<label class="label col-sm-4 col-form-label">{{ $t('recorder') }}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
v-model="show_assigned_recorders" id="defaultCheck1">
<label class="form-check-label" for="defaultCheck1">
Show already assigned recorders
</label>
</div>
</div>
</div>
<div class="card-footer text-muted">
<button type="button" class="btn btn-danger">{{ $t('delete') }}&nbsp;<font-awesome-icon
icon="trash"/>
</button>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="room-create" role="tabpanel" aria-labelledby="profile-tab">
<p>{{ $t('Create a new room')}}:</p>
<!-- form starts here -->
<form v-on:submit.prevent="saveRoom()">
<section class="form">
<div class="form-group row">
<label class="label required col-sm-2 col-form-label">{{ $t('name') }}</label>
<div class="col-sm-6">
<input name="name"
v-model="form.name"
v-validate="'required|min:3'"
v-bind:class="{'is-danger': errors.has('name'), 'is-invalid': errors.has('name')}"
class="form-control" type="text" placeholder="Room name" required>
</div>
<p class="col-sm-4" v-show="errors.has('name')">
{{ errors.first('name') }}
</p>
</div>
<div class="form-group row">
<label class="label col-sm-2 col-form-label">{{ $t('alternate_name') }}</label>
<div class="col-sm-6">
<input name="alternate_name"
v-model="form.alternate_name"
class="form-control" type="text" placeholder="Alternate name">
</div>
</div>
<div class="form-group row">
<label class="label required col-sm-2 col-form-label">{{ $t('number') }}</label>
<div class="col-sm-6">
<input name="number"
v-model="form.number"
v-validate="'required|min:3'"
v-bind:class="{'is-danger': errors.has('number'), 'is-invalid': errors.has('number')}"
class="form-control" type="text" placeholder="Room number" required>
</div>
<p class="col-sm-4" v-show="errors.has('number')">
{{ errors.first('number') }}
</p>
</div>
<div class="form-group row">
<label class="label col-sm-2 col-form-label">{{ $t('comment') }}</label>
<div class="col-sm-6">
<textarea class="textarea form-control" placeholder="Comments, remarks, notes, etc."
v-model="form.comment"></textarea>
</div>
</div>
<div class="form-group row">
<label class="label col-sm-2 col-form-label">{{ $t('recorder') }}</label>
<div class="col-sm-6">
<select class="form-control" v-model="form.recorder">
<option disabled value="">No recorder selected</option>
<option v-for="option in options.recorders" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button
v-bind:disabled="errors.any()"
type="submit"
class="btn btn-primary">
Create room
</button>
</div>
</div>
</section>
</form>
</div>
</div>
<hr>
</section>
<div class="column">
<section class="section" id="results">
<div class="box">
<ul>
<!-- loop through all the `form` properties and show their values -->
<li v-for="(item, k) in form">
<strong>{{ k }}:</strong> {{ item }}
</li>
</ul>
</div>
</section>
</div>
</div>
</template>
<script>
import PulseLoader from 'vue-spinner/src/PulseLoader.vue';
import getRepository from '@/api/RepositoryFactory';
const RoomRepository = getRepository('room');
export default {
components: {
PulseLoader,
},
props: [],
data() {
return {
show_assigned_recorders: false,
form: {
name: '',
alternate_name: '',
number: '',
comment: '',
recorder: null,
},
};
},
methods: {
saveRoom() {
this.$parent.$data.isLoading = true;
RoomRepository.createRoom(this.form)
.then(() => {
this.$store.dispatch('loadRooms')
.then(() => {
this.$parent.$data.isLoading = false;
});
});
},
},
mounted() {
this.$parent.$data.isLoading = true;
this.$store.dispatch('loadRooms')
.then(() => {
this.$parent.$data.isLoading = false;
});
},
computed: {
options() {
return {
recorders: [
{value: 8, text: 'SMP 351 AudiMax'},
{value: 13, text: 'SMP 351 HMU'},
],
};
},
rooms() {
return this.$store.state.rooms;
},
},
};
</script>
<style>
.comment {
white-space: pre-wrap;
}
.card:hover {
background-color: whitesmoke;
}
.required:after {
content: " *";
}
</style>

View File

@@ -1,115 +0,0 @@
<template>
<div>
<section class="hero is-primary">
<div class="hero-body">
<div class="container has-text-centered">
<h2 class="title">{{ survey.name }}</h2>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-10 is-offset-1">
<div
v-for="(question, idx) in survey.questions"
v-bind:key="question.id"
v-show="currentQuestion === idx">
<div class="column is-offset-3 is-6">
<h4 class='title has-text-centered'>{{ question.text }}</h4>
</div>
<div class="column is-offset-4 is-4">
<div class="control">
<div v-for="choice in question.choices" v-bind:key="choice.id">
<label class="radio">
<input type="radio" v-model="question.choice" name="choice" :value="choice.id">
{{ choice.text }}
</label>
</div>
</div>
</div>
</div>
<div class="column is-offset-one-quarter is-half">
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
<a class="pagination-previous" @click.stop="goToPreviousQuestion"><i class="fa fa-chevron-left" aria-hidden="true"></i> &nbsp;&nbsp; Back</a>
<a class="pagination-next" @click.stop="goToNextQuestion">Next &nbsp;&nbsp; <i class="fa fa-chevron-right" aria-hidden="true"></i></a>
</nav>
</div>
<div class="has-text-centered">
<a v-show="surveyComplete" class='button is-large is-focused is-primary' @click="handleSubmit">Submit</a>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script>
export default {
data () {
return {
currentQuestion: 0
}
},
beforeMount () {
this.$store.dispatch('loadSurvey', { id: parseInt(this.$route.params.id) })
},
methods: {
goToNextQuestion () {
if (this.currentQuestion === this.survey.questions.length - 1) {
this.currentQuestion = 0
} else {
this.currentQuestion++
}
},
goToPreviousQuestion () {
if (this.currentQuestion === 0) {
this.currentQuestion = this.survey.questions.lenth - 1
} else {
this.currentQuestion--
}
},
handleSubmit () {
this.$store.dispatch('addSurveyResponse')
.then(() => this.$router.push('/'))
}
},
computed: {
surveyComplete () {
if (this.survey.questions) {
const numQuestions = this.survey.questions.length
const numCompleted = this.survey.questions.filter(q => q.choice).length
return numQuestions === numCompleted
}
return false
},
survey () {
return this.$store.state.currentSurvey
},
selectedChoice: {
get () {
const question = this.survey.questions[this.currentQuestion]
return question.choice
},
set (value) {
const question = this.survey.questions[this.currentQuestion]
this.$store.commit('setChoice', { questionId: question.id, choice: value })
}
}
}
}
</script>
<style>
</style>

View File

@@ -5,14 +5,15 @@
<div class="hero-body">
<div class="container has-text-centered">
<h3 class="title">Manage users</h3>
<div>{{users}}</div>
<div>{{users}}</div>
<div id="userTable">
<table>
<thead>
<tr>
<th v-for="col in columns" v-on:click="sortTable(col)">{{col}}
<div class="arrow" v-if="col == sortColumn" v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
<div class="arrow" v-if="col == sortColumn"
v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
</th>
</tr>
</thead>
@@ -26,7 +27,8 @@
<div class="number"
v-for="i in num_pages()"
v-bind:class="[i === currentPage ? 'active' : '']"
v-on:click="change_page(i)">{{i}}</div>
v-on:click="change_page(i)">{{i}}
</div>
</div>
</div>
@@ -47,14 +49,16 @@
</template>
<script>
import { EventBus } from '@/utils';
import {EventBus} from '@/utils';
import {getRemainingJwtValiditySeconds} from '../utils';
import { RepositoryFactory} from '@/api/RepositoryFactory';
const UserRepository = RepositoryFactory.get('user');
import getRepository from '@/api/RepositoryFactory';
const UserRepository = getRepository('user');
export default {
data() {
return {
isLoading: false,
user: {},
users: [],
errorMsg: '',
@@ -68,7 +72,7 @@
this.isLoading = true;
// const { data } = await GroupRepository.get();
UserRepository.getUsers()
.then( (response) => {
.then((response) => {
this.users = response.data;
this.isLoading = false;
this.$log.debug(response);
@@ -76,8 +80,8 @@
},
get_rows() {
let start = (this.currentPage-1) * this.elementsPerPage;
let end = start + this.elementsPerPage;
const start = (this.currentPage - 1) * this.elementsPerPage;
const end = start + this.elementsPerPage;
return this.users.slice(start, end);
},
num_pages() {
@@ -94,19 +98,19 @@
this.sortColumn = col;
}
let ascending = this.ascending;
const ascending = this.ascending;
this.users.sort(function(a, b) {
this.users.sort((a, b) => {
if (a[col] > b[col]) {
return ascending ? 1 : -1
return ascending ? 1 : -1;
} else if (a[col] < b[col]) {
return ascending ? -1 : 1
return ascending ? -1 : 1;
}
return 0;
})
});
},
createUser(){
this.$log.info("Creating new user...");
createUser() {
this.$log.info('Creating new user...');
UserRepository.createUser(this.user);
this.fetch();
},
@@ -115,6 +119,7 @@
this.fetch();
},
beforeDestroy() {
// todo
},
computed: {
columns() {
@@ -124,7 +129,7 @@
return Object.keys(this.users[0]);
},
},
}
};
</script>
<style lang="scss">

View File

@@ -1,6 +1,7 @@
import Vue from 'vue';
import axios from 'axios';
import VueAxios from 'vue-axios';
import BootstrapVue from 'bootstrap-vue';
import App from './App.vue';
import router from './router';
import store from './store';
@@ -8,16 +9,29 @@ import VueSweetalert2 from 'vue-sweetalert2';
import VueCookies from 'vue-cookies';
import VueLogger from 'vuejs-logger';
import i18n from '@/plugins/i18n';
import VeeValidate from 'vee-validate';
// @ts-ignore
import FlagIcon from 'vue-flag-icon';
// following is to avoid missing type definitions
// const FlagIcon = require('vue-flag-icon');
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCoffee, faTrash, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
//import 'bootstrap';
//import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
const isProduction = process.env.NODE_ENV === 'production';
library.add(faCoffee, faTrash, faPencilAlt);
Vue.component('font-awesome-icon', FontAwesomeIcon);
const options = {
isEnabled: true,
// logLevel : isProduction ? 'error' : 'debug',
@@ -31,9 +45,11 @@ const options = {
Vue.use(VueLogger, options);
Vue.use(VueAxios, axios);
Vue.use(BootstrapVue);
Vue.use(FlagIcon);
Vue.use(VueCookies);
Vue.use(VueSweetalert2);
Vue.use(VeeValidate);
// setup fake backend
// import { configureFakeBackend } from './helpers';

View File

@@ -3,97 +3,110 @@ import Router from 'vue-router';
import Home from './views/Home.vue';
import NotFound from './views/NotFound.vue';
import Survey from '@/components/Survey.vue';
import NewSurvey from '@/components/NewSurvey.vue';
import NewSurvey from '@/components/Rooms.vue';
import Login from '@/components/Login.vue';
import Admin from '@/components/Admin.vue';
import Profile from '@/components/Profile.vue';
import User from '@/components/User.vue';
import Group from '@/components/Group.vue';
import Rooms from '@/components/Rooms.vue';
import Recorders from '@/components/Recorders.vue';
import store from '@/store';
Vue.use(Router);
export const router = new Router({
// export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/admin',
name: 'admin',
component: Admin,
children: [
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
name: 'admin.user',
path: 'user',
component: User,
path: '/',
name: 'home',
component: Home,
},
{
name: 'admin.group',
path: 'group',
component: Group,
path: '/login',
name: 'login',
component: Login,
},
],
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
}, {
path: '/surveys/:id',
name: 'Survey',
component: Survey,
}, {
path: '/surveys',
name: 'NewSurvey',
component: NewSurvey,
beforeEnter(to, from, next) {
if (!store.getters.isAuthenticated) {
next('/login');
} else {
next();
}
},
}, {
path: '/profile',
name: 'Profile',
component: Profile,
beforeEnter(to, from, next) {
if (!store.getters.isAuthenticated) {
Vue.$log.debug('not authenticated!');
if (store.getters.isRefreshTokenValid) {
Vue.$log.debug('refresh token is still valid :)');
store.dispatch('refreshToken')
.then(() => next())
.catch(() => next('/login'));
} else {
next('/login');
}
} else {
next();
}
},
},
{
path: '*',
name: 'notFound',
component: NotFound,
},
],
{
path: '/admin',
name: 'admin',
component: Admin,
children: [
{
name: 'admin.user',
path: 'user',
component: User,
},
{
name: 'admin.group',
path: 'group',
component: Group,
},
],
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
}, {
path: '/surveys/:id',
name: 'Survey',
component: Rooms,
}, {
path: '/rooms',
name: 'rooms',
component: Rooms,
}, {
path: '/recorders',
name: 'recorders',
component: Recorders,
}, {
path: '/commands',
name: 'commands',
component: Rooms,
}, {
path: '/surveys',
name: 'NewSurvey',
component: NewSurvey,
beforeEnter(to, from, next) {
if (!store.getters.isAuthenticated) {
next('/login');
} else {
next();
}
},
}, {
path: '/profile',
name: 'Profile',
component: Profile,
beforeEnter(to, from, next) {
if (!store.getters.isAuthenticated) {
Vue.$log.debug('not authenticated!');
if (store.getters.isRefreshTokenValid) {
Vue.$log.debug('refresh token is still valid :)');
store.dispatch('refreshToken')
.then(() => next())
.catch(() => next('/login'));
} else {
next('/login');
}
} else {
next();
}
},
},
{
path: '*',
name: 'notFound',
component: NotFound,
},
],
});
export default router;

View File

@@ -1,6 +1,8 @@
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
import getRepository from '@/api/RepositoryFactory';
import RoomRepository from '@/api/roomRepository';
// imports of AJAX functions will go here
import {
@@ -14,13 +16,13 @@ import {
oidc_login, fetchUsers, getFreshToken, fetchProfile, fetchUserGroups,
} from '@/api';
import {isValidJwt, EventBus} from '@/utils';
import {response} from "express";
Vue.use(Vuex);
const state = {
// single source of data
surveys: [],
rooms: [],
loginProviders: [],
currentSurvey: {},
profile: {},
@@ -30,43 +32,44 @@ const state = {
refresh_token: '',
};
// const RoomRepository = getRepository('room');
const actions = {
// asynchronous operations
loadSurveys(context: any) {
return fetchSurveys()
.then((response) => {
context.commit('setSurveys', { surveys: response.data });
});
},
// @ts-ignore
loadSurvey(context: any, { id }) {
return fetchSurvey(id)
.then((response) => {
context.commit('setSurvey', { survey: response.data });
});
},
addSurveyResponse(context: any) {
return saveSurveyResponse(context.state.currentSurvey);
},
loadUsers(context: any) {
return fetchUsers(context.state.access_token)
.then((response) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setUsers', { users: response.data });
EventBus.$emit('usersLoaded', response.data);
})
.catch((error) => {
Vue.$log.warn('Error loading users!', error);
EventBus.$emit('failedLoadingUsers', error);
});
},
// asynchronous operations
loadRooms(context: any) {
return RoomRepository.getRooms()
.then((response: any) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setRooms', {rooms: response.data});
EventBus.$emit('roomsLoaded', response.data);
})
.catch((error: any) => {
Vue.$log.warn('Error loading users!', error);
EventBus.$emit('failedLoadingUsers', error);
});
},
loadUsers(context: any) {
return fetchUsers(context.state.access_token)
.then((response) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setUsers', {users: response.data});
EventBus.$emit('usersLoaded', response.data);
})
.catch((error) => {
Vue.$log.warn('Error loading users!', error);
EventBus.$emit('failedLoadingUsers', error);
});
},
loadUserGroups(context: any) {
return fetchUserGroups(context.state.access_token)
.then((response) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setUserGroups', { groups: response.data });
context.commit('setUserGroups', {groups: response.data});
EventBus.$emit('groupsLoaded', response.data);
})
.catch((error) => {
@@ -74,148 +77,152 @@ const actions = {
EventBus.$emit('failedLoadingUserGroups', error);
});
},
loadProfileAuthCheck(context:any){
if (!getters.isAuthenticated) {
EventBus.$emit('accessTokenInvalid');
if (!getters.isRefreshTokenValid) {
Vue.$log.warn('Access and refresh token invalid! User must login again!');
EventBus.$emit('refreshTokenInvalid');
EventBus.$emit('accessAndRefreshTokenInvalid');
} else {
return context.dispatch('refreshToken').then( () => {
context.commit('loadProfile');
});
}
} else {
context.commit('loadProfile');
}
},
loadProfile(context: any) {
return fetchProfile(context.state.access_token)
.then((response) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setProfile', { profile: response.data });
EventBus.$emit('profileLoaded', response.data);
})
.catch((error) => {
Vue.$log.warn('Error loading profile!', error);
EventBus.$emit('failedLoadingProfile', error);
});
},
loadLoginProviders(context: any) {
return getProviders()
.then((response) => {
context.commit('setLoginProviderData', {providers: response.data});
EventBus.$emit('loginProvidersLoaded', response.data);
});
},
login(context: any, userData: any) {
context.commit('setUserData', { userData });
return authenticate(userData)
.then((response) => context.commit('setJwtToken', { tokens: response.data }))
.catch((error) => {
Vue.$log.warn('Error Authenticating: ', error);
EventBus.$emit('failedAuthentication', error);
});
},
oidc_login(context: any, redirectionUrl: any) {
// context.commit('setUserData', { userData });
return oidc_login(redirectionUrl)
.then((response) => context.commit('setJwtToken', { tokens: response.data }))
.catch((error) => {
Vue.$log.warn('Error Authenticating: ', error);
EventBus.$emit('failedAuthentication', error);
});
},
refreshToken(context: any) {
EventBus.$emit('refreshingToken');
Vue.$log.debug('Refreshing tokens!');
return getFreshToken(context.state.refresh_token)
.then((response) => {
context.commit('setTokens', { tokens: response.data });
Vue.$log.debug('Tokens refreshed!'); })
.catch((error) => {
Vue.$log.warn('Error Refreshing token: ', error);
EventBus.$emit('failedRefreshingToken', error);
});
},
storeTokens(context: any, tokens: any) {
context.commit('setTokens', {tokens});
EventBus.$emit('storedTokens');
},
register(context: any, userData: any) {
context.commit('setUserData', { userData });
return register(userData)
.then(context.dispatch('login', userData))
.catch((error) => {
Vue.$log.warn('Error Registering: ', error);
EventBus.$emit('failedRegistering: ', error);
});
},
submitNewSurvey(context: any, survey: any) {
return postNewSurvey(survey, context.state.access_token);
},
loadProfileAuthCheck(context: any) {
if (!getters.isAuthenticated) {
EventBus.$emit('accessTokenInvalid');
if (!getters.isRefreshTokenValid) {
Vue.$log.warn('Access and refresh token invalid! User must login again!');
EventBus.$emit('refreshTokenInvalid');
EventBus.$emit('accessAndRefreshTokenInvalid');
} else {
return context.dispatch('refreshToken').then(() => {
context.commit('loadProfile');
});
}
} else {
context.commit('loadProfile');
}
},
loadProfile(context: any) {
return fetchProfile(context.state.access_token)
.then((response) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setProfile', {profile: response.data});
EventBus.$emit('profileLoaded', response.data);
})
.catch((error) => {
Vue.$log.warn('Error loading profile!', error);
EventBus.$emit('failedLoadingProfile', error);
});
},
loadLoginProviders(context: any) {
return getProviders()
.then((response) => {
context.commit('setLoginProviderData', {providers: response.data});
EventBus.$emit('loginProvidersLoaded', response.data);
});
},
login(context: any, userData: any) {
context.commit('setUserData', {userData});
return authenticate(userData)
.then((response) => context.commit('setJwtToken', {tokens: response.data}))
.catch((error) => {
Vue.$log.warn('Error Authenticating: ', error);
EventBus.$emit('failedAuthentication', error);
});
},
oidc_login(context: any, redirectionUrl: any) {
// context.commit('setUserData', { userData });
return oidc_login(redirectionUrl)
.then((response) => context.commit('setJwtToken', {tokens: response.data}))
.catch((error) => {
Vue.$log.warn('Error Authenticating: ', error);
EventBus.$emit('failedAuthentication', error);
});
},
refreshToken(context: any) {
EventBus.$emit('refreshingToken');
Vue.$log.debug('Refreshing tokens!');
return getFreshToken(context.state.refresh_token)
.then((response) => {
context.commit('setTokens', {tokens: response.data});
Vue.$log.debug('Tokens refreshed!');
})
.catch((error) => {
Vue.$log.warn('Error Refreshing token: ', error);
EventBus.$emit('failedRefreshingToken', error);
});
},
storeTokens(context: any, tokens: any) {
context.commit('setTokens', {tokens});
EventBus.$emit('storedTokens');
},
register(context: any, userData: any) {
context.commit('setUserData', {userData});
return register(userData)
.then(context.dispatch('login', userData))
.catch((error) => {
Vue.$log.warn('Error Registering: ', error);
EventBus.$emit('failedRegistering: ', error);
});
},
submitNewSurvey(context: any, survey: any) {
return postNewSurvey(survey, context.state.access_token);
},
};
const mutations = {
// isolated data mutations
setSurveys(sState: any, payload: any) {
sState.surveys = payload.surveys;
},
setSurvey(sState: any, payload: any) {
const nQuestions = payload.survey.questions.length;
for (let i = 0; i < nQuestions; i++) {
payload.survey.questions[i].choice = null;
}
sState.currentSurvey = payload.survey;
},
setUsers(sState: any, payload: any) {
sState.users = payload.users;
},
setUserGroups(sState: any, payload: any) {
sState.userGroups = payload.groups;
},
setChoice(sState: any, payload: any) {
const { questionId, choice } = payload;
const nQuestions = sState.currentSurvey.questions.length;
for (let i = 0; i < nQuestions; i++) {
if (sState.currentSurvey.questions[i].id === questionId) {
sState.currentSurvey.questions[i].choice = choice;
break;
}
}
},
setLoginProviderData(sState: any, payload: any) {
Vue.$log.debug('got loginProviders = ', payload);
sState.loginProviders = payload.providers;
},
// probably old ...
setUserData(sState: any, payload: any) {
Vue.$log.debug('setUserData payload = ', payload);
sState.userData = payload.userData;
},
setProfile(sState: any, payload: any) {
Vue.$log.debug('setProfile payload = ', payload);
sState.profile = payload.profile;
},
setJwtToken(sState: any, payload: any) {
Vue.$log.debug('setJwtToken payload = ', payload);
localStorage.tokens = payload.tokens;
sState.access_token = payload.tokens.access_token;
sState.refresh_token = payload.tokens.refresh_token;
},
setTokens(sState: any, payload: any) {
Vue.$log.debug('setTokens payload = ', payload);
if(payload.tokens.access_token){
// isolated data mutations
setSurveys(sState: any, payload: any) {
sState.surveys = payload.surveys;
},
setSurvey(sState: any, payload: any) {
const nQuestions = payload.survey.questions.length;
for (let i = 0; i < nQuestions; i++) {
payload.survey.questions[i].choice = null;
}
sState.currentSurvey = payload.survey;
},
setRooms(sState: any, payload: any) {
sState.rooms = payload.rooms;
},
setUsers(sState: any, payload: any) {
sState.users = payload.users;
},
setUserGroups(sState: any, payload: any) {
sState.userGroups = payload.groups;
},
setChoice(sState: any, payload: any) {
const {questionId, choice} = payload;
const nQuestions = sState.currentSurvey.questions.length;
for (let i = 0; i < nQuestions; i++) {
if (sState.currentSurvey.questions[i].id === questionId) {
sState.currentSurvey.questions[i].choice = choice;
break;
}
}
},
setLoginProviderData(sState: any, payload: any) {
Vue.$log.debug('got loginProviders = ', payload);
sState.loginProviders = payload.providers;
},
// probably old ...
setUserData(sState: any, payload: any) {
Vue.$log.debug('setUserData payload = ', payload);
sState.userData = payload.userData;
},
setProfile(sState: any, payload: any) {
Vue.$log.debug('setProfile payload = ', payload);
sState.profile = payload.profile;
},
setJwtToken(sState: any, payload: any) {
Vue.$log.debug('setJwtToken payload = ', payload);
localStorage.tokens = payload.tokens;
sState.access_token = payload.tokens.access_token;
}
if(payload.tokens.refresh_token){
sState.refresh_token = payload.tokens.refresh_token;
}
Vue.$log.debug('access_token: ' + sState.access_token);
Vue.$log.debug('refresh_token: ' + sState.refresh_token);
},
sState.refresh_token = payload.tokens.refresh_token;
},
setTokens(sState: any, payload: any) {
Vue.$log.debug('setTokens payload = ', payload);
if (payload.tokens.access_token) {
sState.access_token = payload.tokens.access_token;
}
if (payload.tokens.refresh_token) {
sState.refresh_token = payload.tokens.refresh_token;
}
Vue.$log.debug('access_token: ' + sState.access_token);
Vue.$log.debug('refresh_token: ' + sState.refresh_token);
},
};
const getters = {
@@ -238,11 +245,11 @@ const getters = {
};
const store = new Vuex.Store({
state,
actions,
mutations,
getters,
plugins: [createPersistedState()],
state,
actions,
mutations,
getters,
plugins: [createPersistedState()],
});
export default store;

View File

@@ -1,5 +1,6 @@
<template>
<div class="home">
<b-alert show>Default Alert</b-alert>
<div>
<button v-for="entry in languages" :key="entry.title" @click="changeLocale(entry.language)">
<flag :iso="entry.flag" v-bind:squared="false"/>