diff --git a/web/package.json b/web/package.json
index ab6e8b7..87f6d71 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,43 +1,39 @@
{
- "name": "c3cloc",
+ "name": "c3lf",
"version": "0.1.0",
"private": true,
"scripts": {
- "serve": "vue-cli-service serve",
- "build": "vue-cli-service build",
+ "serve": "vue-cli-service serve --modern",
+ "build": "vue-cli-service build --modern",
"lint": "vue-cli-service lint"
},
"dependencies": {
- "@fortawesome/fontawesome-svg-core": "^1.2.25",
- "@fortawesome/free-solid-svg-icons": "^5.11.2",
- "@fortawesome/vue-fontawesome": "^0.1.8",
- "axios": "^1.6.2",
- "base-64": "^0.1.0",
+ "@fortawesome/fontawesome-svg-core": "^6.5.1",
+ "@fortawesome/free-solid-svg-icons": "^6.5.1",
+ "@fortawesome/vue-fontawesome": "^3.0.6",
+ "base-64": "^1.0.0",
"bootstrap": "^4.3.1",
- "core-js": "^3.3.2",
+ "core-js": "^3.35.1",
"jquery": "^3.4.1",
- "lodash": "^4.17.15",
"luxon": "^1.21.3",
- "popper.js": "^1.16.0",
+ "popper.js": "^1.16.1",
"ramda": "^0.26.1",
"sass": "^1.19.0",
"sass-loader": "^10.4.1",
"utf8": "^3.0.0",
- "vue": "^2.6.10",
- "vue-debounce": "^2.2.0",
- "vue-qrcode-component": "^2.1.1",
- "vue-router": "^3.1.3",
- "vuex": "^3.1.2",
- "vuex-router-sync": "^5.0.0",
- "vuex-shared-mutations": "^1.0.2",
+ "vue": "^3.2.47",
+ "vue-router": "^4.1.6",
+ "vuex": "^4.1.0",
"yarn": "^1.22.21"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"express-basic-auth": "^1.2.1",
+ "http-proxy-middleware": "^2.0.6",
"vue-template-compiler": "^2.6.10",
- "webpack": "^5"
+ "webpack": "^5",
+ "webpack-dev-server": "^4.15.1"
},
"eslintConfig": {
"root": true,
diff --git a/web/src/App.vue b/web/src/App.vue
index f249fde..ede1d16 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -5,40 +5,30 @@
-
-
-
diff --git a/web/src/components/AddItemModal.vue b/web/src/components/AddItemModal.vue
index 294e8a7..c90829f 100644
--- a/web/src/components/AddItemModal.vue
+++ b/web/src/components/AddItemModal.vue
@@ -15,6 +15,7 @@
\ No newline at end of file
diff --git a/web/src/main.js b/web/src/main.js
index 4a76c46..865436d 100644
--- a/web/src/main.js
+++ b/web/src/main.js
@@ -1,11 +1,8 @@
-import Vue from 'vue';
+import {createApp} from 'vue'
import App from './App.vue';
-import {sync} from 'vuex-router-sync';
import store from './store';
import router from './router';
-// bootstrap
-import 'jquery/dist/jquery.min.js';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.min.js';
@@ -46,20 +43,12 @@ import {
} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
-import vueDebounce from 'vue-debounce';
-
library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList,
faWindowClose, faCamera, faStop, faPen, faCheck, faTimes, faSave, faEye, faComment, faUser, faComments, faEnvelope,
faArchive, faMinus, faExclamation, faHourglass, faClipboard, faTasks, faAngleDown, faAngleRight);
-Vue.component('font-awesome-icon', FontAwesomeIcon);
-sync(store, router);
-new Vue({
- el: '#app',
- store,
- router,
- render: h => h(App),
-});
+const app = createApp(App).use(store).use(router);
-Vue.use(vueDebounce);
\ No newline at end of file
+app.component('font-awesome-icon', FontAwesomeIcon);
+app.mount('#app')
\ No newline at end of file
diff --git a/web/src/router.js b/web/src/router.js
index 4169a91..3490bc3 100644
--- a/web/src/router.js
+++ b/web/src/router.js
@@ -1,24 +1,21 @@
+import {createRouter, createWebHistory} from 'vue-router'
+import store from '@/store';
+
import Items from './views/Items';
import Boxes from './views/Boxes';
import Files from './views/Files';
-import Error from './views/Error';
import HowTo from './views/HowTo';
-import VueRouter from 'vue-router';
-import Vue from 'vue';
import Login from '@/views/Login.vue';
import Register from '@/views/Register.vue';
import Debug from "@/views/admin/Debug.vue";
import Tickets from "@/views/Tickets.vue";
import Ticket from "@/views/Ticket.vue";
import Admin from "@/views/admin/Admin.vue";
-import store from "@/store";
import Empty from "@/views/Empty.vue";
import Events from "@/views/admin/Events.vue";
import AccessControl from "@/views/admin/AccessControl.vue";
import {default as BoxesAdmin} from "@/views/admin/Boxes.vue"
-Vue.use(VueRouter);
-
const routes = [
{path: '/', redirect: '/37C3/items', meta: {requiresAuth: false}},
{path: '/login/', name: 'login', component: Login, meta: {requiresAuth: false}},
@@ -75,11 +72,11 @@ const routes = [
]
},
{path: '/user', name: 'user', component: Empty, meta: {requiresAuth: true}},
- {path: '*', component: Error},
];
-const router = new VueRouter({
- mode: 'history',
+const router = createRouter({
+ history: createWebHistory(),
+ linkActiveClass: "active",
routes,
});
@@ -101,13 +98,10 @@ router.beforeEach((to, from, next) => {
});
router.afterEach((to, from) => {
- if (to.params.event) {
+ if (to.params.event && to.params.event !== store.state.lastEvent) {
//console.log('update last event', to.params.event);
store.commit('updateLastEvent', to.params.event);
}
- if (to.query.layout !== from.query.layout) {
- store.commit('triggerLayoutChange', to.query.layout);
- }
});
export default router;
diff --git a/web/src/store/index.js b/web/src/store.js
similarity index 51%
rename from web/src/store/index.js
rename to web/src/store.js
index 692ed29..a47239d 100644
--- a/web/src/store/index.js
+++ b/web/src/store.js
@@ -1,63 +1,11 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import AxiosBootstrap from 'axios';
-import * as _ from 'lodash/fp';
-import router from '../router';
+import {createStore} from 'vuex';
+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";
+import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils";
-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({
+const store = createStore({
state: {
keyIncrement: 0,
events: [],
@@ -68,26 +16,30 @@ const store = new Vuex.Store({
tickets: [],
users: [],
groups: [],
+ state_options: [],
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,
+
+ user: {
+ username: null,
+ password: null,
+ permissions: [],
+ token: null,
+ 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,
+ route: state => router.currentRoute.value,
+ getEventSlug: state => router.currentRoute.value.params.event ? router.currentRoute.value.params.event : state.lastEvent,
+ getActiveView: state => router.currentRoute.value.name || 'items',
+ getFilters: state => router.currentRoute.value.query,
getBoxes: state => state.loadedBoxes,
- checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`),
- hasPermissions: state => state.userPermissions.length > 0,
+ checkPermission: state => (event, perm) => state.user.permissions.includes(`${event}:${perm}`) || state.user.permissions.includes(`*:${perm}`),
+ hasPermissions: state => state.user.permissions.length > 0,
stateInfo: state => (slug) => {
const obj = state.state_options.filter((s) => s.value === slug)[0];
if (obj) {
@@ -107,9 +59,8 @@ const store = new Vuex.Store({
}
},
layout: (state, getters) => {
- state.toggle = !state.toggle;
- if (router.currentRoute.query.layout)
- return router.currentRoute.query.layout;
+ if (router.currentRoute.value.query.layout)
+ return router.currentRoute.value.query.layout;
if (getters.getActiveView === 'items')
return 'cards';
if (getters.getActiveView === 'tickets')
@@ -118,21 +69,20 @@ const store = new Vuex.Store({
isLoggedIn(state) {
if (!state.local_loaded) {
state.remember = localStorage.getItem('remember') === 'true';
- state.user = localStorage.getItem('user');
+ state.user.username = 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.user.permissions = JSON.parse(localStorage.getItem('permissions') || '[]');
+ state.user.token = localStorage.getItem('token');
+ state.user.expiry = localStorage.getItem('token_expiry');
state.local_loaded = true;
- axios.defaults.headers.common['Authorization'] = `Token ${state.token}`;
}
- return state.user !== null && state.token !== null;
+ return state.user && state.user.username !== null && state.user.token !== null;
},
},
mutations: {
updateLastUsed(state, diff) {
- state.lastUsed = _.extend(state.lastUsed, diff);
+ state.lastUsed = {...state.lastUsed, ...diff};
localStorage.setItem('lf_lastUsed', JSON.stringify(state.lastUsed));
},
updateLastEvent(state, slug) {
@@ -200,38 +150,39 @@ const store = new Vuex.Store({
localStorage.setItem('remember', remember);
},
setUser(state, user) {
- state.user = user;
+ state.user.username = user;
if (user)
localStorage.setItem('user', user);
},
setPassword(state, password) {
- state.password = password;
+ state.user.password = password;
},
setPermissions(state, permissions) {
- state.userPermissions = permissions;
+ state.user.permissions = permissions;
if (permissions)
localStorage.setItem('permissions', JSON.stringify(permissions));
},
setToken(state, {token, expiry}) {
- state.token = token;
- state.token_expiry = expiry;
+ const user = {...state.user};
+ user.token = token;
+ user.expiry = expiry;
+ state.user = user;
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');
+ setUserInfo(state, user) {
+ state.user = user;
+ },
+ logout(state) {
+ const user = {...state.user};
+ user.user = null;
+ user.password = null;
+ user.token = null;
+ user.expiry = null;
+ user.permissions = null;
+ state.user = user;
},
- triggerLayoutChange(state) {
- state.toggle = !state.toggle;
- }
},
actions: {
async login({commit, dispatch, state}, {username, password, remember}) {
@@ -247,7 +198,6 @@ const store = new Vuex.Store({
commit('setToken', data);
commit('setUser', username);
commit('setPassword', password);
- axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
dispatch('afterLogin');
return true;
} else {
@@ -258,18 +208,17 @@ const store = new Vuex.Store({
return false;
}
},
- async reloadToken({commit, state}) {
+ async reloadToken({commit, state, getters}) {
try {
- if (state.password) {
+ if (state.user.username && state.user.password) {
const data = await fetch('/api/2/login/', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({username: state.user, password: state.password}),
+ body: JSON.stringify({username: state.user.username, password: state.user.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;
}
}
@@ -280,29 +229,33 @@ const store = new Vuex.Store({
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 afterLogin({dispatch, state}) {
+ let promises = [];
+ promises.push(dispatch('loadBoxes'));
+ promises.push(dispatch('fetchTicketStates'));
+ promises.push(dispatch('loadEventItems'));
+ promises.push(dispatch('loadTickets'));
+ if (!state.user.permissions) {
+ promises.push(dispatch('loadUserInfo'));
+ }
+ await Promise.all(promises);
},
async fetchImage({state}, url) {
- return await fetch(url, {headers: {'Authorization': `Token ${state.token}`}});
+ return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}});
},
- async loadUserInfo({commit}) {
- const {data} = await axios.get('/2/self/');
- commit('setUser', data.username);
+ async loadUserInfo({commit, state}) {
+ const {data, success} = await http.get('/2/self/', state.user.token);
commit('setPermissions', data.permissions);
},
- async loadEvents({commit}) {
- const {data} = await axios.get('/2/events/');
- commit('replaceEvents', data);
+ async loadEvents({commit, state}) {
+ const {data, success} = await http.get('/2/events/', state.user.token);
+ if (data && success)
+ commit('replaceEvents', data);
},
- async fetchTicketStates({commit}) {
- const {data} = await axios.get('/2/tickets/states/');
- commit('replaceTicketStates', data);
+ async fetchTicketStates({commit, state}) {
+ const {data, success} = await http.get('/2/tickets/states/', state.user.token);
+ if (data && success)
+ commit('replaceTicketStates', data);
},
changeEvent({dispatch, getters, commit}, eventName) {
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
@@ -321,121 +274,121 @@ const store = new Vuex.Store({
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});
+ const {data, success} = await http.get(`/2/${slug}/items/`, state.user.token);
+ if (data && success) {
+ commit('replaceLoadedItems', data);
+ commit('setItemCache', {slug, items: data});
+ }
} catch (e) {
console.error("Error loading items");
}
},
- async searchEventItems({commit, getters}, query) {
+ async searchEventItems({commit, getters, state}, query) {
const foo = utf8.encode(query);
const bar = base64.encode(foo);
- const {data} = await axios.get(`/2/${getters.getEventSlug}/items/${bar}/`);
- commit('replaceLoadedItems', data);
+ const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${bar}/`, state.user.token);
+ if (data && success)
+ commit('replaceLoadedItems', data);
},
- async loadBoxes({commit}) {
- const {data} = await axios.get('/2/boxes/');
- commit('replaceBoxes', data);
+ async loadBoxes({commit, state}) {
+ const {data, success} = await http.get('/2/boxes/', state.user.token);
+ if (data && success)
+ commit('replaceBoxes', data);
},
- async createBox({commit, dispatch}, box) {
- const {data} = await axios.post('/2/boxes/', box);
+ async createBox({commit, dispatch, state}, box) {
+ const {data, success} = await http.post('/2/boxes/', box, state.user.token);
commit('replaceBoxes', data);
dispatch('loadBoxes').then(() => {
commit('closeAddBoxModal');
});
},
- async deleteBox({commit, dispatch}, box_id) {
- await axios.delete(`/2/boxes/${box_id}/`);
+ async deleteBox({commit, dispatch, state}, box_id) {
+ await http.delete(`/2/boxes/${box_id}/`, state.user.token);
dispatch('loadBoxes');
},
- async updateItem({commit, getters}, item) {
- const {data} = await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
+ async updateItem({commit, getters, state}, item) {
+ const {
+ data,
+ success
+ } = await http.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
commit('updateItem', data);
},
- async markItemReturned({commit, getters}, item) {
- await axios.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true});
+ async markItemReturned({commit, getters, state}, item) {
+ await http.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}, state.user.token);
commit('removeItem', item);
},
- async deleteItem({commit, getters}, item) {
- await axios.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
+ async deleteItem({commit, getters, state}, item) {
+ await http.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
commit('removeItem', item);
},
- async postItem({commit, getters}, item) {
+ async postItem({commit, getters, state}, item) {
commit('updateLastUsed', {box: item.box, cid: item.cid});
- const {data} = await axios.post(`/2/${getters.getEventSlug}/item/`, item);
+ const {data, success} = await http.post(`/2/${getters.getEventSlug}/item/`, item, state.user.token);
commit('appendItem', data);
},
- async loadTickets({commit}) {
- const {data} = await axios.get('/2/tickets/');
- commit('replaceTickets', data);
+ async loadTickets({commit, state}) {
+ const {data, success} = await http.get('/2/tickets/', state.user.token);
+ if (data && success)
+ commit('replaceTickets', data);
},
- async sendMail({commit, dispatch}, {id, message}) {
- const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message});
- await dispatch('loadTickets');
+ async sendMail({commit, dispatch, state}, {id, message}) {
+ const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token);
+ if (data && success) {
+ await dispatch('loadTickets');
+ }
},
- async postManualTicket({commit, dispatch}, {sender, message, title,}) {
- const {data} = await axios.post(`/2/tickets/manual/`, {
+ async postManualTicket({commit, dispatch, state}, {sender, message, title,}) {
+ const {data, success} = await http.post(`/2/tickets/manual/`, {
name: title,
sender,
body: message,
recipient: 'mail@c3lf.de'
- });
+ }, state.user.token);
await dispatch('loadTickets');
},
- async postComment({commit, dispatch}, {id, message}) {
- const {data} = await axios.post(`/2/tickets/${id}/comment/`, {comment: message});
- await dispatch('loadTickets');
+ async postComment({commit, dispatch, state}, {id, message}) {
+ const {data, success} = await http.post(`/2/tickets/${id}/comment/`, {comment: message}, state.user.token);
+ if (data && success) {
+ await dispatch('loadTickets');
+ }
},
- async loadUsers({commit}) {
- const {data} = await axios.get('/2/users/');
- commit('replaceUsers', data);
+ async loadUsers({commit, state}) {
+ const {data, success} = await http.get('/2/users/', state.user.token);
+ if (data && success)
+ commit('replaceUsers', data);
},
- async loadGroups({commit}) {
- const {data} = await axios.get('/2/groups/');
- commit('replaceGroups', data);
+ async loadGroups({commit, state}) {
+ const {data, success} = await http.get('/2/groups/', state.user.token);
+ if (data && success)
+ commit('replaceGroups', data);
},
- async updateTicket({commit}, ticket) {
- const {data} = await axios.put(`/2/tickets/${ticket.id}/`, ticket);
+ async updateTicket({commit, state}, ticket) {
+ const {data, success} = await http.put(`/2/tickets/${ticket.id}/`, ticket, state.user.token);
commit('updateTicket', data);
},
- async updateTicketPartial({commit}, {id, ...ticket}) {
- const {data} = await axios.patch(`/2/tickets/${id}/`, ticket);
+ async updateTicketPartial({commit, state}, {id, ...ticket}) {
+ const {data, success} = await http.patch(`/2/tickets/${id}/`, ticket, state.user.token);
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',
- ]
- })],
+});
+
+store.watch((state) => state.user, (user) => {
+ console.log('user changed', user);
+ if (store.getters.isLoggedIn) {
+ if (router.currentRoute.value.name === 'login' && router.currentRoute.value.query.redirect)
+ router.push(router.currentRoute.value.query.redirect);
+ else if (router.currentRoute.value.name === 'login')
+ router.push('/');
+ } else {
+ if (router.currentRoute.value.name !== 'login') {
+ router.push({
+ name: 'login',
+ query: {redirect: router.currentRoute.value.fullPath},
+ });
+ }
+ }
});
export default store;
-
-store.dispatch('loadEvents').then(() => {
- if (store.getters.isLoggedIn) {
- axios.defaults.headers.common['Authorization'] = `Token ${store.state.token}`;
- store.dispatch('afterLogin');
- }
-});
diff --git a/web/src/utils.js b/web/src/utils.js
index 3464224..6e9d8e4 100644
--- a/web/src/utils.js
+++ b/web/src/utils.js
@@ -24,4 +24,80 @@ function ticketStateIconLookup(ticket) {
return 'exclamation';
}
-export {ticketStateColorLookup, ticketStateIconLookup};
\ No newline at end of file
+const http = {
+ get: async (url, token) => {
+ if (!token) {
+ return null;
+ }
+ const response = await fetch('/api' + url, {
+ method: 'GET',
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Token ${token}`,
+ },
+ });
+ const success = response.status === 200 || response.status === 201;
+ return {data: await response.json() || {}, success};
+ },
+ post: async (url, data, token) => {
+ if (!token) {
+ return null;
+ }
+ const response = await fetch('/api' + url, {
+ method: 'POST',
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Token ${token}`,
+ },
+ body: JSON.stringify(data),
+ });
+ const success = response.status === 200 || response.status === 201;
+ return {data: await response.json() || {}, success};
+ },
+ put: async (url, data, token) => {
+ if (!token) {
+ return null;
+ }
+ const response = await fetch('/api' + url, {
+ method: 'PUT',
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Token ${token}`,
+ },
+ body: JSON.stringify(data),
+ });
+ const success = response.status === 200 || response.status === 201;
+ return {data: await response.json() || {}, success};
+ },
+ patch: async (url, data, token) => {
+ if (!token) {
+ return null;
+ }
+ const response = await fetch('/api' + url, {
+ method: 'PATCH',
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Token ${token}`,
+ },
+ body: JSON.stringify(data),
+ });
+ const success = response.status === 200 || response.status === 201;
+ return {data: await response.json() || {}, success};
+ },
+ delete: async (url, token) => {
+ if (!token) {
+ return null;
+ }
+ const response = await fetch('/api' + url, {
+ method: 'DELETE',
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Token ${token}`,
+ },
+ });
+ const success = response.status === 200 || response.status === 201;
+ return {data: await response.json() || {}, success};
+ }
+}
+
+export {ticketStateColorLookup, ticketStateIconLookup, http};
\ No newline at end of file
diff --git a/web/src/views/Items.vue b/web/src/views/Items.vue
index a8b0cf5..04d08fb 100644
--- a/web/src/views/Items.vue
+++ b/web/src/views/Items.vue
@@ -93,7 +93,7 @@ export default {
...mapGetters(['layout']),
},
methods: {
- ...mapActions(['deleteItem', 'markItemReturned']),
+ ...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem']),
openLightboxModalWith(item) {
this.lightboxHash = item.file;
},
@@ -107,12 +107,15 @@ export default {
this.editingItem = null;
},
saveEditingItem() { // Saves the edited copy of the item.
- this.$store.dispatch('updateItem', this.editingItem);
+ this.updateItem(this.editingItem);
this.closeEditingModal();
},
confirm(message) {
return window.confirm(message);
}
+ },
+ mounted() {
+ this.loadEventItems();
}
};
diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue
index abe3989..8cff6c2 100644
--- a/web/src/views/Login.vue
+++ b/web/src/views/Login.vue
@@ -100,7 +100,7 @@ export default {