From bb71c44aa72bdd48640808997afcc1b59fe3dc37 Mon Sep 17 00:00:00 2001 From: jedi Date: Tue, 18 Jun 2024 20:10:10 +0200 Subject: [PATCH] migrate to vue 3 --- web/package.json | 34 ++- web/src/App.vue | 82 +----- web/src/components/AddItemModal.vue | 9 +- web/src/components/AddTicketModal.vue | 6 +- web/src/components/CollapsableCards.vue | 45 +--- web/src/components/Navbar.vue | 8 +- web/src/components/Toast.vue | 48 ---- web/src/main.js | 19 +- web/src/router.js | 20 +- web/src/{store/index.js => store.js} | 335 ++++++++++-------------- web/src/utils.js | 78 +++++- web/src/views/Items.vue | 7 +- web/src/views/Login.vue | 2 +- web/src/views/admin/Admin.vue | 2 +- web/src/views/admin/Debug.vue | 24 +- web/vue.config.js | 31 +++ 16 files changed, 318 insertions(+), 432 deletions(-) delete mode 100644 web/src/components/Toast.vue rename web/src/{store/index.js => store.js} (51%) create mode 100644 web/vue.config.js 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 {