Commit 6285e654 authored by jackie / Andrea Ida Malkah Klaura's avatar jackie / Andrea Ida Malkah Klaura
Browse files

Merge branch 'refactor-to-vuex' into develop

fixes #36
parents b7ef6f21 18ce2b7c
......@@ -12201,6 +12201,11 @@
"es6-object-assign": "1.1.0"
}
},
"vuex": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz",
"integrity": "sha512-ha3jNLJqNhhrAemDXcmMJMKf1Zu4sybMPr9KxJIuOpVcsDQlTBYLLladav2U+g1AvdYDG5Gs0xBTb0M5pXXYFQ=="
},
"watchpack": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
......
......@@ -18,7 +18,8 @@
"vue": "^2.6.10",
"vue-full-calendar": "^2.7.0",
"vue-router": "^3.1.3",
"vuejs-logger": "^1.5.3"
"vuejs-logger": "^1.5.3",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.11.0",
......
......@@ -3,6 +3,7 @@
# API URLs for the other AuRa modules
# ===================================
VUE_APP_BASEURI_STEERING = http://127.0.0.1:8000
VUE_APP_API_STEERING = http://127.0.0.1:8000/api/v1/
VUE_APP_API_STEERING_SHOWS = http://127.0.0.1:8000/api/v1/shows/
VUE_APP_API_TANK = http://127.0.0.1:8040/api/v1/
......
......@@ -2,6 +2,7 @@
# ===============================
# These are the REST API endpoints of your aura/steering module
VUE_APP_BASEURI_STEERING = https://YOUR.STEERING.DOMAIN
VUE_APP_API_STEERING = http://YOUR.STEERING.DOMAIN/api/v1/
VUE_APP_API_STEERING_SHOWS = http://YOUR.STEERING.DOMAIN/api/v1/shows/
......
......@@ -12,10 +12,8 @@
<script>
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import oidc from 'oidc-client'
import header from './components/Header.vue'
import footer from './components/Footer.vue'
import axios from 'axios'
export default {
name: 'App',
......@@ -45,32 +43,11 @@ export default {
{ slug: 'credits', title: 'Credits' },
{ slug: 'debug', title: 'Debug' }
],
user: {
name: '',
email: '',
access_token: '',
expires_at: 0,
logged_in: false,
steeringUser: null
},
userOIDC: null,
oidcmgr: new oidc.UserManager({
userStore: new oidc.WebStorageStateStore(),
authority: process.env.VUE_APP_API_STEERING_OIDC_URI,
// the client id has to be a string, therefore we add the + ''
client_id: process.env.VUE_APP_OIDC_CLIENT_ID,
redirect_uri: process.env.VUE_APP_API_STEERING_OIDC_REDIRECT_URI,
silent_redirect_uri: 'http://localhost:8080/oidc_callback_silentRenew.html',
popup_redirect_uri: 'http://localhost:8080/oidc_callback_popupRenew.html',
accessTokenExpiringNotificationTime: process.env.VUE_APP_API_STEERING_OIDC_EXPIRE_NOTIFICATION,
response_type: 'id_token token',
scope: 'openid profile email',
post_logout_redirect_uri: process.env.VUE_APP_API_STEERING_OIDC_REDIRECT_URI_POSTLOGOUT,
loadUserInfo: true
})
}
},
computed: {
userOIDC () { return this.$store.state.auth.userOIDC },
user () { return this.$store.state.auth.user },
modules: function () {
if (this.user.logged_in === true) {
if (this.user.steeringUser && this.user.steeringUser.is_superuser) {
......@@ -84,103 +61,15 @@ export default {
}
},
mounted () {
// TODO: remove oidc logging after thorough testing
oidc.Log.logger = console
let self = this
this.oidcmgr.events.addAccessTokenExpiring(function () {
console.log('Debug info: starting silent access_token renewal')
self.oidcmgr.signinSilent().then(function (user) {
self.user.access_token = user.access_token
console.log(self.user.access_token)
}).catch(function (err) {
console.log(err)
alert('Your OpenID access token could not be renewed automatically.\n' +
'You will be logged out in ' + process.env.VUE_APP_API_STEERING_OIDC_EXPIRE_NOTIFICATION + ' seconds.')
})
})
this.oidcmgr.events.addAccessTokenExpired(function () {
// at this point where the app is just mounted, we cannot use the
// vue logger, because this.$log does not yet exist. therefore we log
// to the console, but only if the environment is set tu debug log level
if (process.env.VUE_APP_LOGLEVEL === 'debug') {
console.log("Debug info: OIDC token has expired. Logging out.")
}
self.signOut()
})
this.getOIDCUser()
this.$store.dispatch('auth/oidcInit')
},
methods: {
signIn () {
this.oidcmgr.signinRedirect().catch(err => {
alert('Error: something went wrong when signing in. See console for details.')
this.$log.error(err)
})
this.$store.dispatch('auth/signinRedirect')
},
signOut () {
this.oidcmgr.signoutRedirect().then(resp => {
this.user.logged_in = false
this.$log.debug('signed out', resp)
}).catch(err => {
this.$log.error(err)
alert('Error: something went wrong when logging out. See console for details.')
})
console.log('3')
},
getSteeringUser () {
axios.get(process.env.VUE_APP_API_STEERING + 'users/', {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.user.access_token }
}).then(response => {
if (response.data.length === 0) {
alert('No user profile data provided by steering backend!')
} else if (response.data.length === 1) {
this.user.steeringUser = response.data[0]
} else {
// in case we are a superuser, we get all users returned
// so we have to iterate through the user list to find out own profile
for (var u in response.data) {
if (response.data[u].username === this.user.name) {
this.user.steeringUser = response.data[u]
break
}
}
}
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not fetch user object from the steering backend. See console for details.')
})
this.$store.dispatch('auth/signoutRedirect')
},
getOIDCUser () {
let self = this
this.oidcmgr.getUser().then(function (user) {
if (user == null) {
self.user.logged_in = false
self.user.name = ''
self.user.email = ''
self.user.access_token = ''
} else {
// TODO: check user.expires_at
// if token already expired try to get a new one or mark the user as logged out
self.setUserProperties(user)
self.getSteeringUser()
}
}).catch(function (err) {
this.$log.error(err)
alert('Error: could not fetch OIDC user object. See console for details.')
})
},
setUserProperties (user) {
this.userOIDC = user
this.user.logged_in = true
this.user.name = user.profile.nickname
this.user.email = user.profile.email
this.user.access_token = user.access_token
// TODO: remove debug info after thorough testing
this.$log.debug(new Date(user.expires_at * 1000).toString())
this.$log.debug(new Date(user.expires_at * 1000).toUTCString())
this.$log.debug(user.access_token)
}
}
}
</script>
......
<template>
<b-container>
<b-row v-if="loaded.shows">
<b-col>
<b-alert
:show="showChangedAlert.countdown"
@dismiss-count-down="showChangedAlertCountdown"
>
Selected new show: <b>{{ shows[currentShow].name }}</b>
<b-progress
variant="info"
animated
:max="showChangedAlert.seconds"
:value="showChangedAlert.countdown"
height="3px"
/>
</b-alert>
<b-alert
:show="!showChangedAlert.countdown"
variant="light"
>
Currently selected show: <b>{{ shows[currentShow].name }}</b>
</b-alert>
</b-col>
<b-col align="right">
<b-dropdown
id="ddshows"
text="Sendereihe auswählen"
variant="outline-info"
>
<b-dropdown-item
v-for="(show, index) in shows"
:key="show.id"
@click="switchShow(index)"
>
{{ show.name }}
</b-dropdown-item>
</b-dropdown>
</b-col>
</b-row>
<b-row v-else>
<b-col cols="12">
<div align="center">
... loading show data ...
</div>
</b-col>
</b-row>
<show-selector
title="Emissions"
:callback="showHasSwitched"
/>
<hr>
<b-alert
......@@ -136,17 +94,19 @@
</template>
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
import { FullCalendar } from 'vue-full-calendar'
import 'fullcalendar/dist/fullcalendar.css'
import modalEmissionManagerCreate from './EmissionManagerModalCreate.vue'
import modalEmissionManagerResolve from './EmissionManagerModalResolve.vue'
import modalEmissionManagerEdit from './EmissionManagerModalEdit.vue'
import showSelector from './ShowSelector.vue'
import modalEmissionManagerCreate from './emissions/ModalCreate.vue'
import modalEmissionManagerResolve from './emissions/ModalResolve.vue'
import modalEmissionManagerEdit from './emissions/ModalEdit.vue'
import rrules from '../mixins/rrules'
export default {
components: {
FullCalendar,
'show-selector': showSelector,
'app-modalEmissionManagerCreate': modalEmissionManagerCreate,
'app-modalEmissionManagerResolve': modalEmissionManagerResolve,
'app-modalEmissionManagerEdit': modalEmissionManagerEdit,
......@@ -156,22 +116,9 @@ export default {
data () {
return {
currentShow: 0,
shows: [],
timeslots: [],
calendarSlots: [],
showChangedAlert: {
countdown: 0,
seconds: 3
},
// flags for loading data
loaded: {
shows: false,
timeslots: false,
calendarSlots: false,
},
// flag for when submitting resolve data
submitting: false,
// this flag signifies if we are in conflict resolution mode
......@@ -210,24 +157,44 @@ export default {
}
},
computed: {
loaded () {
return {
shows: this.$store.state.shows.loaded['shows'],
timeslots: this.$store.state.shows.loaded['timeslots'],
}
},
...mapGetters({
shows: 'shows/shows',
selectedShow: 'shows/selectedShow',
timeslots: 'shows/timeslots',
getPlaylistById: 'playlists/getPlaylistById',
files: 'files/files',
getFileById: 'files/getFileById',
})
},
created () {
if (this.$route.query.show) {
this.currentShow = this.$route.query.show
} else {
this.currentShow = 0
}
this.loadShows()
this.$store.dispatch('shows/fetchShows', {
callback: () => {
this.showHasSwitched()
}
})
},
methods: {
switchShow (index) {
this.currentShow = index
switchShow () {
this.loadCalendarSlots()
this.showChangedAlert.countdown = this.showChangedAlert.seconds
},
showChangedAlertCountdown(countdown) {
this.showChangedAlert.countdown = countdown
// This is the callback function that is activated by the show-selector
// component, whenever the user switches to a different show
showHasSwitched () {
this.$log.debug('show has switched to', this.selectedShow.name)
let start = this.$refs.calendar.fireMethod('getView').start.format()
let end = this.$refs.calendar.fireMethod('getView').end.format()
this.loadTimeslots(start, end)
},
getShowTitleById (id) {
......@@ -268,10 +235,10 @@ export default {
// currently selected show.
else {
let timeslot = this.timeslots.find(slot => slot.id === event.id)
if (timeslot.show !== this.shows[this.currentShow].id) {
if (timeslot.show !== this.selectedShow.id) {
this.switchShow(this.getShowIndexById(timeslot.show))
} else {
this.$refs.appModalEmissionManagerEdit.open(timeslot, this.shows[this.currentShow])
this.$refs.appModalEmissionManagerEdit.open(timeslot)
}
}
},
......@@ -509,38 +476,30 @@ export default {
solutions: this.resolveData.solutions,
}
this.$log.debug('resolveSubmit: schedule:', resolvedSchedule)
// now generate the URL and POST it to steering
let uri = process.env.VUE_APP_API_STEERING_SHOWS + this.shows[this.currentShow].id + '/schedules/'
this.submitting = true
axios.post(uri, resolvedSchedule, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.$log.debug('resolveSubmit: response:', response)
this.submitting = false
// if for some reason a new conflict arose, e.g. because in the meantime
// someone else inserted a conflicting schedule, we have to resolve.
if (response.data.projected === undefined) {
this.conflictMode = false
this.renderView(null)
} else {
this.resolve(response.data)
this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: resolvedSchedule,
callback: (response) => {
this.submitting = false
if (response.data.projected === undefined) {
this.conflictMode = false
this.renderView(null)
} else {
this.resolve(response.data)
}
},
callbackCancel: () => {
this.submitting = false
}
}).catch(error => {
this.submitting = false
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not submit final schedule. See console for details.')
// and we leave the modal open, so no call to its .hide function here
})
},
loadCalendarSlots () {
this.loaded.calendarSlots = false
this.calendarSlots = []
for (let i in this.timeslots) {
let highlighting = 'otherShow'
if (this.timeslots[i].show === this.shows[this.currentShow].id) {
if (this.timeslots[i].show === this.selectedShow.id) {
highlighting = 'currentShow'
}
this.calendarSlots.push({
......@@ -551,42 +510,15 @@ export default {
className: highlighting
})
}
this.loaded.calendarSlots = true
},
loadTimeslots (start, end) {
this.loaded.timeslots = false
let uri = process.env.VUE_APP_API_STEERING + 'timeslots?start=' + start + '&end=' + end
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.timeslots = response.data
this.loaded.timeslots = true
this.loadCalendarSlots()
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not load timeslots. See console for details.')
})
},
loadShows () {
this.loaded.shows = false
let uri = process.env.VUE_APP_API_STEERING + 'shows'
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.shows = response.data
this.loaded.shows = true
let start = this.$refs.calendar.fireMethod('getView').start.format()
let end = this.$refs.calendar.fireMethod('getView').end.format()
this.loadTimeslots(start, end)
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not load shows. See console for details.')
this.$store.dispatch('shows/fetchTimeslots', {
start: start,
end: end,
callback: () => {
this.loadCalendarSlots()
}
})
},
......
This diff is collapsed.
<template>
<div>
<b-modal
id="modal-file-manager-log"
title="File import log"
size="xl"
ok-only
>
<div v-if="loaded">
<div v-if="error">
<b-alert
variant="danger"
show
>
{{ error }}
</b-alert>
</div>
<div v-else>
{{ log }}
<hr>
<div v-if="results.fetch">
<h2>Fetch log</h2>
<b-table :items="results.fetch" />
</div>
<div v-if="results.normalize">
<h2>Normalize log</h2>
<b-table :items="results.normalize" />
</div>
</div>
</div>
<div v-else>
<div align="center">
<img
src="../assets/radio.gif"
width="96"
alt="loading data"
><br>
Loading file import log...
</div>
</div>
</b-modal>
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
loaded: false, // flag to indicate if log is loaded
slug: null, // the show's slug of the log's file
id: null, // the id of the log's file
log: '', // the log output
error: '', // potential error message
results: null, // the results object for the logs
}
},
methods: {
loadLog: function () {
let uri = process.env.VUE_APP_API_TANK + 'shows/' + this.slug + '/files/' + this.id + '/logs'
this.log = ''
this.error = ''
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
this.results = response.data.results
this.log = 'Available logs: ' + Object.keys(response.data.results).join(', ')
this.loaded = true
}).catch(error => {
this.error = 'Error: could not fetch import log from tank. See console for details.'
this.loaded = true
if (error.response) {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
} else if (error.request) {
this.$log.error(error.request)
} else {
this.$log.error(error.message)
}
})
},
show: function (slug, id) {
this.loaded = false
this.slug = slug
this.id = id
this.$bvModal.show('modal-file-manager-log')
this.loadLog()
}
}
}
</script>
<style scoped>
</style>
This diff is collapsed.
This diff is collapsed.
<template>
<b-row>
<b-col>
<h3>{{ title }}</h3>
</b-col>
<b-col align="right">
<b-dropdown
id="ddshows"
text="Sendereihe auswählen"
variant="info"
>
<b-dropdown-item
v-for="(show, index) in shows"
:key="show.id"
@click="switchShow(index)"
>
{{ show.name }}
</b-dropdown-item>
</b-dropdown>
</b-col>
</b-row>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: {
title: { type: String, default: 'Unlabeled component title' },
callback: { type: Function, default: null }
},
computed: {