import Vue from 'vue';
import Vuex from 'vuex';
import AxiosBootstrap from 'axios';
import * as _ from 'lodash/fp';
import router from '../router';
import * as base64 from 'base-64';
import * as utf8 from 'utf8';
import {ticketStateColorLookup, ticketStateIconLookup} from "@/utils";
import createMutationsSharer from "vuex-shared-mutations";
Vue.use(Vuex);
const axios = AxiosBootstrap.create({
baseURL: '/api',
});
axios.interceptors.response.use(response => response, error => {
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 = `
Access denied.
url: ${error.config.url}
method: ${error.config.method}
response-body: ${error.response && error.response.body}
`;
store.commit('createToast', {title: 'Error: Access denied', message, color: 'danger'});
return Promise.reject(error)
} else {
console.error('error interceptor fired', error.message);
if (error.isAxiosError) {
const message = `
A HTTP ${error.config.method} request failed.
url: ${error.config.url}
timeout: ${!!error.request.timeout}
response-body: ${error.response && error.response.body}
`;
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);
}
});
const store = new Vuex.Store({
state: {
keyIncrement: 0,
events: [],
loadedItems: [],
itemCache: {},
loadedBoxes: [],
toasts: [],
tickets: [],
users: [],
groups: [],
lastEvent: localStorage.getItem('lf_lastEvent') || '37C3',
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
remember: false,
user: null,
password: null,
userPermissions: [],
token: null,
state_options: [],
token_expiry: null,
local_loaded: false,
showAddBoxModal: false,
toggle: false,
},
getters: {
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,
checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`),
hasPermissions: state => state.userPermissions.length > 0,
stateInfo: state => (slug) => {
const obj = state.state_options.filter((s) => s.value === slug)[0];
if (obj) {
return {
color: ticketStateColorLookup(obj.value),
icon: ticketStateIconLookup(obj.value),
slug: obj.value,
text: obj.text,
}
} else {
return {
color: 'danger',
icon: 'exclamation',
slug: slug,
text: 'Unknown'
}
}
},
layout: (state, getters) => {
state.toggle = !state.toggle;
if (router.currentRoute.query.layout)
return router.currentRoute.query.layout;
if (getters.getActiveView === 'items')
return 'cards';
if (getters.getActiveView === 'tickets')
return 'tasks';
},
isLoggedIn(state) {
if (!state.local_loaded) {
state.remember = localStorage.getItem('remember') === 'true';
state.user = localStorage.getItem('user');
//state.password = localStorage.getItem('password');
state.userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]');
state.token = localStorage.getItem('token');
state.token_expiry = localStorage.getItem('token_expiry');
state.local_loaded = true;
axios.defaults.headers.common['Authorization'] = `Token ${state.token}`;
}
return state.user !== null && state.token !== null;
},
},
mutations: {
updateLastUsed(state, diff) {
state.lastUsed = _.extend(state.lastUsed, diff);
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;
},
replaceTicketStates(state, states) {
state.state_options = states;
},
changeView(state, {view, slug}) {
router.push({path: `/${slug}/${view}`});
},
replaceLoadedItems(state, newItems) {
state.loadedItems = newItems;
},
setItemCache(state, {slug, items}) {
state.itemCache[slug] = items;
},
replaceBoxes(state, loadedBoxes) {
state.loadedBoxes = loadedBoxes;
},
updateItem(state, updatedItem) {
const item = state.loadedItems.filter(({uid}) => uid === updatedItem.uid)[0];
Object.assign(item, updatedItem);
},
removeItem(state, item) {
state.loadedItems = state.loadedItems.filter(it => it !== item);
},
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);
},
openAddBoxModal(state) {
state.showAddBoxModal = true;
},
closeAddBoxModal(state) {
state.showAddBoxModal = false;
},
createToast(state, {title, message, color}) {
var toast = {title, message, color, key: state.keyIncrement}
state.toasts.push(toast);
state.keyIncrement += 1;
return toast;
},
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');
},
triggerLayoutChange(state) {
state.toggle = !state.toggle;
}
},
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 && 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 (state.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()).catch(e => console.error(e))
if (data && 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 verifyToken({commit, state}) {
async afterLogin({dispatch}) {
const boxes = dispatch('loadBoxes');
const states = dispatch('fetchTicketStates');
const items = dispatch('loadEventItems');
const tickets = dispatch('loadTickets');
const user = dispatch('loadUserInfo');
await Promise.all([boxes, items, tickets, user, states]);
},
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('/2/events/');
commit('replaceEvents', data);
},
async fetchTicketStates({commit}) {
const {data} = await axios.get('/2/tickets/states/');
commit('replaceTicketStates', data);
},
changeEvent({dispatch, getters, commit}, eventName) {
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
dispatch('loadEventItems');
},
changeView({getters}, link) {
router.push({path: `/${getters.getEventSlug}/${link.path}/`});
},
showBoxContent({getters}, box) {
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
},
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(`/2/${getters.getEventSlug}/items/${bar}/`);
commit('replaceLoadedItems', data);
},
async loadBoxes({commit}) {
const {data} = await axios.get('/2/boxes/');
commit('replaceBoxes', data);
},
async createBox({commit, dispatch}, box) {
const {data} = await axios.post('/2/boxes/', box);
commit('replaceBoxes', data);
dispatch('loadBoxes').then(() => {
commit('closeAddBoxModal');
});
},
async deleteBox({commit, dispatch}, box_id) {
await axios.delete(`/2/boxes/${box_id}/`);
dispatch('loadBoxes');
},
async updateItem({commit, getters}, item) {
const {data} = await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
commit('updateItem', data);
},
async markItemReturned({commit, getters}, item) {
await axios.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true});
commit('removeItem', item);
},
async deleteItem({commit, getters}, 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(`/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 postComment({commit, dispatch}, {id, message}) {
const {data} = await axios.post(`/2/tickets/${id}/comment/`, {comment: message});
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);
},
async updateTicket({commit}, ticket) {
const {data} = await axios.put(`/2/tickets/${ticket.id}/`, ticket);
commit('updateTicket', data);
},
async updateTicketPartial({commit}, {id, ...ticket}) {
const {data} = await axios.patch(`/2/tickets/${id}/`, ticket);
commit('updateTicket', data);
}
},
plugins: [createMutationsSharer({
predicate: [
'replaceLoadedItems',
'setItemCache',
'setLayout',
'replaceBoxes',
'updateItem',
'removeItem',
'appendItem',
'replaceTickets',
'replaceUsers',
'replaceGroups',
'updateTicket',
'openAddBoxModal',
'closeAddBoxModal',
'createToast',
'removeToast',
'setRemember',
'setUser',
'setPermissions',
'setToken',
'logout',
]
})],
});
export default store;
store.dispatch('loadEvents').then(() => {
if (store.getters.isLoggedIn) {
axios.defaults.headers.common['Authorization'] = `Token ${store.state.token}`;
store.dispatch('afterLogin');
}
});