update vuex store to use API v2
This commit is contained in:
parent
0ebfe3adfb
commit
21ec29caa8
2 changed files with 316 additions and 67 deletions
|
@ -10,28 +10,26 @@
|
|||
:class="{ active: event.slug === getEventSlug }" @click="changeEvent(event)">{{ event.slug }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="custom-control-inline mr-1">
|
||||
<button type="button" class="btn mx-1 text-nowrap btn-success" @click="$emit('addClicked')">
|
||||
<font-awesome-icon icon="plus"/>
|
||||
<span class="d-none d-md-inline"> Add</span>
|
||||
</button>
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')">
|
||||
<font-awesome-icon icon="th"/>
|
||||
</button>
|
||||
<button :class="['btn', 'btn-info', { active: layout === 'table' }]" @click="setLayout('table')">
|
||||
<font-awesome-icon icon="list"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<form class="form-inline mt-1 my-lg-auto my-xl-auto w-100 d-inline">
|
||||
<ul class="nav nav-tabs flex-nowrap">
|
||||
<li class="nav-item" v-if="checkPermission(getEventSlug, 'view_item')">
|
||||
<router-link :to="{name: 'items', params: {event: getEventSlug}}"
|
||||
:class="['nav-link', { active: isItemView() }]">
|
||||
Items
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-item" v-if="checkPermission(getEventSlug, 'view_issuethread')">
|
||||
<router-link :to="{name: 'tickets', params: {event: getEventSlug}}"
|
||||
:class="['nav-link', { active: isTicketView() }]">
|
||||
Tickets
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-item" v-if="checkPermission(getEventSlug, 'delete_event')">
|
||||
<router-link :to="{name: 'admin'}" class="nav-link" active-class="active">
|
||||
Admin
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="form-inline mt-1 my-lg-auto my-xl-auto w-100 d-inline mr-1" v-if="hasPermissions">
|
||||
<input
|
||||
class="form-control w-100"
|
||||
type="search"
|
||||
|
@ -41,10 +39,33 @@
|
|||
disabled
|
||||
>
|
||||
</form>
|
||||
|
||||
<div class="custom-control-inline mr-1" v-if="hasPermissions">
|
||||
<div class="btn-group btn-group-toggle mr-1" v-if="isItemView()">
|
||||
<button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')">
|
||||
<font-awesome-icon icon="th"/>
|
||||
</button>
|
||||
<button :class="['btn', 'btn-info', { active: layout === 'table' }]" @click="setLayout('table')">
|
||||
<font-awesome-icon icon="list"/>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn text-nowrap btn-success mr-1" @click="$emit('addItemClicked')"
|
||||
v-if="isItemView()">
|
||||
<font-awesome-icon icon="plus"/>
|
||||
<span class="d-none d-md-inline"> Add Item</span>
|
||||
</button>
|
||||
<button type="button" class="btn text-nowrap btn-success mr-1" @click="$emit('addTicketClicked')"
|
||||
v-if="isTicketView()">
|
||||
<font-awesome-icon icon="plus"/>
|
||||
<span class="d-none d-md-inline"> Add Ticket</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<!--li class="nav-item dropdown">
|
||||
<button class="btn nav-link dropdown-toggle" type="button" id="dropdownMenuButton2"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ getActiveView }}
|
||||
|
@ -55,16 +76,20 @@
|
|||
<a class="nav-link text-nowrap" href="#" @click="changeView(link)">{{ link.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</li-->
|
||||
|
||||
<li class="nav-item" v-for="(link, index) in links" v-bind:key="index">
|
||||
<a class="nav-link text-nowrap" :href="link.path" @click.prevent="navigateTo(link.path)">
|
||||
{{ link.title }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-nowrap" href="/logout" @click.prevent="logout()">
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
|
@ -80,23 +105,55 @@ export default {
|
|||
//{'title':'mass-edit','path':'massedit'},
|
||||
],
|
||||
links: [
|
||||
{'title': 'howto engel', 'path': '/howto/'}
|
||||
{'title': 'howto engel', 'path': '/howto/'},
|
||||
]
|
||||
}),
|
||||
computed: {
|
||||
...mapState(['events', 'activeEvent', 'layout']),
|
||||
...mapGetters(['getEventSlug', 'getActiveView']),
|
||||
...mapState(['events', 'layout']),
|
||||
...mapGetters(['getEventSlug', 'getActiveView', "checkPermission", "hasPermissions"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['changeEvent', 'changeView', 'searchEventItems']),
|
||||
...mapMutations(['setLayout']),
|
||||
...mapMutations(['setLayout', 'logout']),
|
||||
navigateTo(link) {
|
||||
this.$router.push(link);
|
||||
if (this.$router.currentRoute.path !== link)
|
||||
this.$router.push(link);
|
||||
},
|
||||
isItemView() {
|
||||
return this.getActiveView === 'items' || this.getActiveView === 'item';
|
||||
},
|
||||
isTicketView() {
|
||||
return this.getActiveView === 'tickets' || this.getActiveView === 'ticket';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import "../scss/navbar.scss";
|
||||
|
||||
.nav-tabs {
|
||||
margin-bottom: -0.5rem !important;
|
||||
border-bottom: var(--gray) solid 1px !important;
|
||||
|
||||
& .nav-item {
|
||||
margin-right: 0.5em;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
& .nav-link {
|
||||
padding-bottom: 1rem !important;
|
||||
border: var(--gray) solid 1px !important;
|
||||
border-bottom: none !important;
|
||||
|
||||
&.active {
|
||||
background: black !important;
|
||||
border-bottom: none;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import AxiosBootstrap from 'axios';
|
||||
import config from '../config';
|
||||
import * as _ from 'lodash/fp';
|
||||
import router from '../router';
|
||||
|
||||
|
@ -10,17 +9,37 @@ import * as utf8 from 'utf8';
|
|||
|
||||
Vue.use(Vuex);
|
||||
const axios = AxiosBootstrap.create({
|
||||
baseURL: config.service.url,
|
||||
auth: config.service.auth
|
||||
baseURL: '/api',
|
||||
});
|
||||
|
||||
axios.interceptors.response.use(response => response, error => {
|
||||
console.log('error interceptor fired');
|
||||
console.error(error); // todo: toast error
|
||||
console.log(Object.entries(error));
|
||||
|
||||
if (error.isAxiosError) {
|
||||
if (error.response.status === 401) {
|
||||
console.log('401 interceptor fired');
|
||||
store.dispatch('reloadToken').then((ok) => {
|
||||
if (ok) {
|
||||
error.config.headers['Authorization'] = `Token ${store.state.token}`;
|
||||
return axios.request(error.config);
|
||||
}
|
||||
});
|
||||
} else if (error.response.status === 403) {
|
||||
const message = `
|
||||
<h3>Access denied.</h3>
|
||||
<p>
|
||||
url: ${error.config.url}
|
||||
<br>
|
||||
method: ${error.config.method}
|
||||
<br>
|
||||
response-body: ${error.response && error.response.body}
|
||||
</p>
|
||||
`;
|
||||
store.commit('createToast', {title: 'Error: Access denied', message, color: 'danger'});
|
||||
return Promise.reject(error)
|
||||
} else {
|
||||
console.log('error interceptor fired');
|
||||
console.error(error); // todo: toast error
|
||||
console.log(Object.entries(error));
|
||||
|
||||
if (error.isAxiosError) {
|
||||
const message = `
|
||||
<h3>A HTTP ${error.config.method} request failed.</h3>
|
||||
<p>
|
||||
url: ${error.config.url}
|
||||
|
@ -30,11 +49,12 @@ axios.interceptors.response.use(response => response, error => {
|
|||
response-body: ${error.response && error.response.body}
|
||||
</p>
|
||||
`;
|
||||
store.commit('createToast', {title: 'Error: HTTP', message, color: 'danger'});
|
||||
} else {
|
||||
store.commit('createToast', {title: 'Error: Unknown', message: error.toString(), color: 'danger'});
|
||||
store.commit('createToast', {title: 'Error: HTTP', message, color: 'danger'});
|
||||
} else {
|
||||
store.commit('createToast', {title: 'Error: Unknown', message: error.toString(), color: 'danger'});
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
const store = new Vuex.Store({
|
||||
|
@ -43,20 +63,50 @@ const store = new Vuex.Store({
|
|||
events: [],
|
||||
layout: 'cards',
|
||||
loadedItems: [],
|
||||
itemCache: {},
|
||||
loadedBoxes: [],
|
||||
toasts: [],
|
||||
lastUsed: localStorage.getItem('lf_lastUsed') || {},
|
||||
tickets: [],
|
||||
users: [],
|
||||
groups: [],
|
||||
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
|
||||
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
|
||||
remember: false,
|
||||
user: null,
|
||||
password: null,
|
||||
userPermissions: [],
|
||||
token: null,
|
||||
token_expiry: null,
|
||||
local_loaded: false,
|
||||
},
|
||||
getters: {
|
||||
getEventSlug: state => state.route && state.route.params.event ? state.route.params.event : state.events.length ? state.events[0].slug : '36C3',
|
||||
getEventSlug: state => state.route && state.route.params.event ? state.route.params.event : state.lastEvent,
|
||||
getActiveView: state => state.route.name || 'items',
|
||||
getFilters: state => state.route.query,
|
||||
getBoxes: state => state.loadedBoxes
|
||||
getBoxes: state => state.loadedBoxes,
|
||||
checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`),
|
||||
hasPermissions: state => state.userPermissions.length > 0,
|
||||
isLoggedIn(state) {
|
||||
if (!state.local_loaded) {
|
||||
state.remember = localStorage.getItem('remember') === 'true'
|
||||
state.user = localStorage.getItem('user')
|
||||
state.userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]')
|
||||
state.token = localStorage.getItem('token')
|
||||
state.token_expiry = localStorage.getItem('token_expiry')
|
||||
state.local_loaded = true
|
||||
}
|
||||
|
||||
return state.user !== null && state.token !== null;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
updateLastUsed(state, diff) {
|
||||
state.lastUsed = _.extend(state.lastUsed, diff);
|
||||
localStorage.setItem('lf_lastUsed', state.lastUsed);
|
||||
localStorage.setItem('lf_lastUsed', JSON.stringify(state.lastUsed));
|
||||
},
|
||||
updateLastEvent(state, slug) {
|
||||
state.lastEvent = slug;
|
||||
localStorage.setItem('lf_lastEvent', slug);
|
||||
},
|
||||
replaceEvents(state, events) {
|
||||
state.events = events;
|
||||
|
@ -67,6 +117,9 @@ const store = new Vuex.Store({
|
|||
replaceLoadedItems(state, newItems) {
|
||||
state.loadedItems = newItems;
|
||||
},
|
||||
setItemCache(state, {slug, items}) {
|
||||
state.itemCache[slug] = items;
|
||||
},
|
||||
setLayout(state, layout) {
|
||||
state.layout = layout;
|
||||
},
|
||||
|
@ -83,6 +136,19 @@ const store = new Vuex.Store({
|
|||
appendItem(state, item) {
|
||||
state.loadedItems.push(item);
|
||||
},
|
||||
replaceTickets(state, tickets) {
|
||||
state.tickets = tickets;
|
||||
},
|
||||
replaceUsers(state, users) {
|
||||
state.users = users;
|
||||
},
|
||||
replaceGroups(state, groups) {
|
||||
state.groups = groups;
|
||||
},
|
||||
updateTicket(state, updatedTicket) {
|
||||
const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0];
|
||||
Object.assign(ticket, updatedTicket);
|
||||
},
|
||||
createToast(state, {title, message, color}) {
|
||||
var toast = {title, message, color, key: state.keyIncrement}
|
||||
state.toasts.push(toast);
|
||||
|
@ -91,61 +157,187 @@ const store = new Vuex.Store({
|
|||
},
|
||||
removeToast(state, key) {
|
||||
state.toasts = state.toasts.filter(toast => toast.key !== key);
|
||||
}
|
||||
},
|
||||
setRemember(state, remember) {
|
||||
state.remember = remember;
|
||||
localStorage.setItem('remember', remember);
|
||||
},
|
||||
setUser(state, user) {
|
||||
state.user = user;
|
||||
if (user)
|
||||
localStorage.setItem('user', user);
|
||||
},
|
||||
setPassword(state, password) {
|
||||
state.password = password;
|
||||
},
|
||||
setPermissions(state, permissions) {
|
||||
state.userPermissions = permissions;
|
||||
if (permissions)
|
||||
localStorage.setItem('permissions', JSON.stringify(permissions));
|
||||
},
|
||||
setToken(state, {token, expiry}) {
|
||||
state.token = token;
|
||||
state.token_expiry = expiry;
|
||||
if (token)
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('token_expiry', expiry);
|
||||
},
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('permissions');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('token_expiry');
|
||||
if (router.currentRoute.name !== 'login')
|
||||
router.push('/login');
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async login({commit, dispatch, state}, {username, password, remember}) {
|
||||
commit('setRemember', remember);
|
||||
try {
|
||||
const data = await fetch('/api/2/login/', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: username, password: password}),
|
||||
credentials: 'omit'
|
||||
}).then(r => r.json())
|
||||
if (data.token) {
|
||||
commit('setToken', data);
|
||||
commit('setUser', username);
|
||||
commit('setPassword', password);
|
||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
||||
dispatch('afterLogin');
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
async reloadToken({commit, state}) {
|
||||
try {
|
||||
if (data.password) {
|
||||
const data = await fetch('/api/2/login/', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: state.user, password: state.password}),
|
||||
credentials: 'omit'
|
||||
}).then(r => r.json())
|
||||
if (data.token) {
|
||||
commit('setToken', data);
|
||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
//credentials failed, logout
|
||||
store.commit('logout');
|
||||
},
|
||||
async afterLogin({dispatch}) {
|
||||
const boxes = dispatch('loadBoxes');
|
||||
const items = dispatch('loadEventItems');
|
||||
const tickets = dispatch('loadTickets');
|
||||
const user = dispatch('loadUserInfo');
|
||||
await Promise.all([boxes, items, tickets, user]);
|
||||
},
|
||||
async fetchImage({state}, url) {
|
||||
return await fetch(url, {headers: {'Authorization': `Token ${state.token}`}});
|
||||
},
|
||||
async loadUserInfo({commit}) {
|
||||
const {data} = await axios.get('/2/self/');
|
||||
commit('setUser', data.username);
|
||||
commit('setPermissions', data.permissions);
|
||||
},
|
||||
async loadEvents({commit}) {
|
||||
const {data} = await axios.get('/1/events');
|
||||
const {data} = await axios.get('/2/events/');
|
||||
commit('replaceEvents', data);
|
||||
},
|
||||
changeEvent({dispatch, getters}, eventName) {
|
||||
router.push({path: `/${eventName.slug}/${getters.getActiveView}`});
|
||||
changeEvent({dispatch, getters, commit}, eventName) {
|
||||
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
||||
dispatch('loadEventItems');
|
||||
},
|
||||
changeView({getters}, link) {
|
||||
router.push({path: `/${getters.getEventSlug}/${link.path}`});
|
||||
router.push({path: `/${getters.getEventSlug}/${link.path}/`});
|
||||
},
|
||||
showBoxContent({getters}, box) {
|
||||
router.push({path: `/${getters.getEventSlug}/items`, query: {box}});
|
||||
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
|
||||
},
|
||||
async loadEventItems({commit, getters}) {
|
||||
const {data} = await axios.get(`/1/${getters.getEventSlug}/items`);
|
||||
commit('replaceLoadedItems', data);
|
||||
async loadEventItems({commit, getters, state}) {
|
||||
try {
|
||||
commit('replaceLoadedItems', []);
|
||||
const slug = getters.getEventSlug;
|
||||
if (slug in state.itemCache) {
|
||||
commit('replaceLoadedItems', state.itemCache[slug]);
|
||||
}
|
||||
const {data} = await axios.get(`/2/${slug}/items/`);
|
||||
commit('replaceLoadedItems', data);
|
||||
commit('setItemCache', {slug, items: data});
|
||||
} catch (e) {
|
||||
console.error("Error loading items");
|
||||
}
|
||||
},
|
||||
async searchEventItems({commit, getters}, query) {
|
||||
const foo = utf8.encode(query);
|
||||
const bar = base64.encode(foo);
|
||||
|
||||
const {data} = await axios.get(`/1/${getters.getEventSlug}/items/${bar}`);
|
||||
const {data} = await axios.get(`/2/${getters.getEventSlug}/items/${bar}/`);
|
||||
commit('replaceLoadedItems', data);
|
||||
},
|
||||
async loadBoxes({commit}) {
|
||||
const {data} = await axios.get('/1/boxes');
|
||||
const {data} = await axios.get('/2/boxes/');
|
||||
commit('replaceBoxes', data);
|
||||
},
|
||||
async updateItem({commit, getters}, item) {
|
||||
const {data} = await axios.put(`/1/${getters.getEventSlug}/item/${item.uid}`, item);
|
||||
const {data} = await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
|
||||
commit('updateItem', data);
|
||||
},
|
||||
async markItemReturned({commit, getters}, item) {
|
||||
await axios.put(`/1/${getters.getEventSlug}/item/${item.uid}`, {returned: true});
|
||||
await axios.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true});
|
||||
commit('removeItem', item);
|
||||
},
|
||||
async deleteItem({commit, getters}, item) {
|
||||
await axios.delete(`/1/${getters.getEventSlug}/item/${item.uid}`, item);
|
||||
await axios.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
|
||||
commit('removeItem', item);
|
||||
},
|
||||
async postItem({commit, getters}, item) {
|
||||
commit('updateLastUsed', {box: item.box, cid: item.cid});
|
||||
const {data} = await axios.post(`/1/${getters.getEventSlug}/item`, item);
|
||||
const {data} = await axios.post(`/2/${getters.getEventSlug}/item/`, item);
|
||||
commit('appendItem', data);
|
||||
}
|
||||
},
|
||||
async loadTickets({commit}) {
|
||||
const {data} = await axios.get('/2/tickets/');
|
||||
commit('replaceTickets', data);
|
||||
},
|
||||
async sendMail({commit, dispatch}, {id, message}) {
|
||||
const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message});
|
||||
await dispatch('loadTickets');
|
||||
},
|
||||
async postManualTicket({commit, dispatch}, {sender, message, title,}) {
|
||||
const {data} = await axios.post(`/2/tickets/manual/`, {name: title, sender, body: message, recipient: 'mail@c3lf.de'});
|
||||
await dispatch('loadTickets');
|
||||
},
|
||||
async loadUsers({commit}) {
|
||||
const {data} = await axios.get('/2/users/');
|
||||
commit('replaceUsers', data);
|
||||
},
|
||||
async loadGroups({commit}) {
|
||||
const {data} = await axios.get('/2/groups/');
|
||||
commit('replaceGroups', data);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export default store;
|
||||
|
||||
store.dispatch('loadEvents').then(() => {
|
||||
store.dispatch('loadEventItems');
|
||||
store.dispatch('loadBoxes');
|
||||
if (store.getters.isLoggedIn) {
|
||||
axios.defaults.headers.common['Authorization'] = `Token ${store.state.token}`;
|
||||
store.dispatch('afterLogin');
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue