migrate to vue 3
This commit is contained in:
parent
bb07a6b641
commit
bb71c44aa7
16 changed files with 318 additions and 432 deletions
|
@ -1,43 +1,39 @@
|
||||||
{
|
{
|
||||||
"name": "c3cloc",
|
"name": "c3lf",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve --modern",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build --modern",
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.11.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||||
"@fortawesome/vue-fontawesome": "^0.1.8",
|
"@fortawesome/vue-fontawesome": "^3.0.6",
|
||||||
"axios": "^1.6.2",
|
"base-64": "^1.0.0",
|
||||||
"base-64": "^0.1.0",
|
|
||||||
"bootstrap": "^4.3.1",
|
"bootstrap": "^4.3.1",
|
||||||
"core-js": "^3.3.2",
|
"core-js": "^3.35.1",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"lodash": "^4.17.15",
|
|
||||||
"luxon": "^1.21.3",
|
"luxon": "^1.21.3",
|
||||||
"popper.js": "^1.16.0",
|
"popper.js": "^1.16.1",
|
||||||
"ramda": "^0.26.1",
|
"ramda": "^0.26.1",
|
||||||
"sass": "^1.19.0",
|
"sass": "^1.19.0",
|
||||||
"sass-loader": "^10.4.1",
|
"sass-loader": "^10.4.1",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^3.2.47",
|
||||||
"vue-debounce": "^2.2.0",
|
"vue-router": "^4.1.6",
|
||||||
"vue-qrcode-component": "^2.1.1",
|
"vuex": "^4.1.0",
|
||||||
"vue-router": "^3.1.3",
|
|
||||||
"vuex": "^3.1.2",
|
|
||||||
"vuex-router-sync": "^5.0.0",
|
|
||||||
"vuex-shared-mutations": "^1.0.2",
|
|
||||||
"yarn": "^1.22.21"
|
"yarn": "^1.22.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "^5.0.8",
|
"@vue/cli-plugin-babel": "^5.0.8",
|
||||||
"@vue/cli-service": "^5.0.8",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
"vue-template-compiler": "^2.6.10",
|
"vue-template-compiler": "^2.6.10",
|
||||||
"webpack": "^5"
|
"webpack": "^5",
|
||||||
|
"webpack-dev-server": "^4.15.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|
|
@ -5,40 +5,30 @@
|
||||||
<AddBoxModal v-if="showAddBoxModal && isLoggedIn" @close="closeAddBoxModal()" isModal="true"/>
|
<AddBoxModal v-if="showAddBoxModal && isLoggedIn" @close="closeAddBoxModal()" isModal="true"/>
|
||||||
<Navbar v-if="isLoggedIn" @addItemClicked="openAddItemModal()" @addTicketClicked="openAddTicketModal()"/>
|
<Navbar v-if="isLoggedIn" @addItemClicked="openAddItemModal()" @addTicketClicked="openAddTicketModal()"/>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
<div aria-live="polite" aria-atomic="true" v-if="isLoggedIn"
|
|
||||||
class="d-flex justify-content-end align-items-start fixed-top mx-1 my-5 py-3"
|
|
||||||
style="min-height: 200px; z-index: 100000; pointer-events: none">
|
|
||||||
<Toast v-for="(toast , index) in toasts" :key="index" :title="toast.title" :message="toast.message"
|
|
||||||
:color="toast.color"
|
|
||||||
@close="removeToast(toast.key)" style="pointer-events: auto"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import AddItemModal from '@/components/AddItemModal';
|
import AddItemModal from '@/components/AddItemModal';
|
||||||
import Toast from './components/Toast';
|
|
||||||
import {mapState, mapMutations, mapActions, mapGetters} from 'vuex';
|
import {mapState, mapMutations, mapActions, mapGetters} from 'vuex';
|
||||||
import AddTicketModal from "@/components/AddTicketModal.vue";
|
import AddTicketModal from "@/components/AddTicketModal.vue";
|
||||||
import AddBoxModal from "@/components/AddBoxModal.vue";
|
import AddBoxModal from "@/components/AddBoxModal.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {AddBoxModal, Toast, Navbar, AddItemModal, AddTicketModal},
|
components: {AddBoxModal, Navbar, AddItemModal, AddTicketModal},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['loadedItems', 'layout', 'toasts', 'showAddBoxModal']),
|
...mapState(['loadedItems', 'layout', 'toasts', 'showAddBoxModal']),
|
||||||
...mapGetters(['isLoggedIn']),
|
...mapGetters(['isLoggedIn']),
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
addItemModalOpen: false,
|
addItemModalOpen: false,
|
||||||
addTicketModalOpen: false,
|
addTicketModalOpen: false
|
||||||
notify_socket: null,
|
|
||||||
socket_toast: null,
|
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['removeToast', 'createToast', 'closeAddBoxModal', 'openAddBoxModal']),
|
...mapMutations(['removeToast', 'createToast', 'closeAddBoxModal', 'openAddBoxModal']),
|
||||||
...mapActions(['loadEventItems', 'loadTickets']),
|
...mapActions(['loadEvents']),
|
||||||
openAddItemModal() {
|
openAddItemModal() {
|
||||||
this.addItemModalOpen = true;
|
this.addItemModalOpen = true;
|
||||||
},
|
},
|
||||||
|
@ -50,72 +40,10 @@ export default {
|
||||||
},
|
},
|
||||||
closeAddTicketModal() {
|
closeAddTicketModal() {
|
||||||
this.addTicketModalOpen = false;
|
this.addTicketModalOpen = false;
|
||||||
},
|
}
|
||||||
tryConnect() {
|
|
||||||
if (!this.notify_socket || this.notify_socket.readyState !== WebSocket.OPEN) {
|
|
||||||
//if (this.socket_toast) {
|
|
||||||
// this.removeToast(this.socket_toast.key);
|
|
||||||
// this.socket_toast = null;
|
|
||||||
//}
|
|
||||||
//this.socket_toast = this.createToast({
|
|
||||||
// title: "Connecting...",
|
|
||||||
// message: "Connecting to websocket...",
|
|
||||||
// color: "warning"
|
|
||||||
//});
|
|
||||||
const scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
|
||||||
this.notify_socket = new WebSocket(scheme + '://' + window.location.host + '/ws/2/notify/');
|
|
||||||
this.notify_socket.onopen = (e) => {
|
|
||||||
//if (this.socket_toast) {
|
|
||||||
// this.removeToast(this.socket_toast.key);
|
|
||||||
// this.socket_toast = null;
|
|
||||||
//}
|
|
||||||
//this.socket_toast = this.createToast({
|
|
||||||
// title: "Connection established",
|
|
||||||
// message: JSON.stringify(e),
|
|
||||||
// color: "success"
|
|
||||||
//});
|
|
||||||
//console.log(e);
|
|
||||||
};
|
|
||||||
this.notify_socket.onclose = (e) => {
|
|
||||||
//if (this.socket_toast) {
|
|
||||||
// this.removeToast(this.socket_toast.key);
|
|
||||||
// this.socket_toast = null;
|
|
||||||
//}
|
|
||||||
//this.socket_toast = this.createToast({
|
|
||||||
// title: "Connection closed",
|
|
||||||
// message: JSON.stringify(e),
|
|
||||||
// color: "danger"
|
|
||||||
//});
|
|
||||||
//console.log(e);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.tryConnect();
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
this.notify_socket.onerror = (e) => {
|
|
||||||
//if (this.socket_toast) {
|
|
||||||
// this.removeToast(this.socket_toast.key);
|
|
||||||
// this.socket_toast = null;
|
|
||||||
//}
|
|
||||||
//this.socket_toast = this.createToast({
|
|
||||||
// title: "Connection error",
|
|
||||||
// message: JSON.stringify(e),
|
|
||||||
// color: "danger"
|
|
||||||
//});
|
|
||||||
//console.log(e);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.tryConnect();
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
this.notify_socket.onmessage = (e) => {
|
|
||||||
let data = JSON.parse(e.data);
|
|
||||||
this.loadEventItems()
|
|
||||||
this.loadTickets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
this.tryConnect();
|
document.title = document.location.hostname;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import EditItem from '@/components/EditItem';
|
import EditItem from '@/components/EditItem';
|
||||||
|
import {mapActions, mapState} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddItemModal',
|
name: 'AddItemModal',
|
||||||
|
@ -23,12 +24,16 @@ export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
item: {}
|
item: {}
|
||||||
}),
|
}),
|
||||||
|
computed: {
|
||||||
|
...mapState(['lastUsed'])
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.item = {box: this.$store.state.lastUsed.box || '', cid: this.$store.state.lastUsed.cid || ''};
|
this.item = {box: this.lastUsed.box || '', cid: this.lastUsed.cid || ''};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(['postItem']),
|
||||||
saveNewItem() {
|
saveNewItem() {
|
||||||
this.$store.dispatch('postItem', this.item).then(() => {
|
this.postItem(this.item).then(() => {
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {mapActions} from 'vuex';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import EditItem from '@/components/EditItem';
|
import EditItem from '@/components/EditItem';
|
||||||
|
|
||||||
|
@ -32,11 +33,12 @@ export default {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
created() {
|
created() {
|
||||||
this.ticket = {box: this.$store.state.lastUsed.box || '', cid: this.$store.state.lastUsed.cid || ''};
|
this.ticket = {};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(['postManualTicket']),
|
||||||
saveNewTicket() {
|
saveNewTicket() {
|
||||||
this.$store.dispatch('postManualTicket', this.ticket).then(() => {
|
this.postManualTicket(this.ticket).then(() => {
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-xl-2">
|
<div class="col-lg-3 col-xl-2">
|
||||||
<!--div class="card bg-dark text-light mb-2" id="filters">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title text-info">Sort & Filter</h5>
|
|
||||||
<div class="form-group" v-for="(column, index) in columns" :key="index">
|
|
||||||
<label>{{ column }}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<button
|
|
||||||
:class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
|
|
||||||
type="button"
|
|
||||||
@click="toggleSort(column)">
|
|
||||||
<font-awesome-icon :icon="getSortIcon(column)"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="filter"
|
|
||||||
:value="filters[column]"
|
|
||||||
@input="changeFilter(column, $event.target.value)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div-->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-9 col-xl-8">
|
<div class="col-lg-9 col-xl-8">
|
||||||
<div class="w-100"
|
<div class="w-100"
|
||||||
|
@ -48,6 +23,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import {mapGetters} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CollapsableCards',
|
name: 'CollapsableCards',
|
||||||
props: {
|
props: {
|
||||||
|
@ -75,25 +52,18 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const query = this.$router.currentRoute.query.collapsed;
|
const query = this.$router.currentRoute ? (this.$router.currentRoute.query ? this.$router.currentRoute.query.collapsed : null) : null;
|
||||||
if (query !== null && query !== undefined) {
|
if (query !== null && query !== undefined) {
|
||||||
this.collapsed = this.unpackInt(parseInt(query), this.sections.length);
|
this.collapsed = this.unpackInt(parseInt(query), this.sections.length);
|
||||||
} else {
|
} else {
|
||||||
this.collapsed = this.sections.map(() => true);
|
this.collapsed = this.sections.map(() => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.$router.push({...this.$router.currentRoute, query: {...this.$router.currentRoute.query, layout}});
|
|
||||||
//this.collapsed = this.sections.map(() => true);
|
|
||||||
/*this.columns.map(e => ({
|
|
||||||
k: e,
|
|
||||||
v: this.$store.getters.getFilters[e]
|
|
||||||
})).filter(e => e.v).forEach(e => this.setFilter(e.k, e.v));*/
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
grouped_items() {
|
grouped_items() {
|
||||||
return this.sections.map(section => this.items.filter(item => item[this.keyName] === section.slug));
|
return this.sections.map(section => this.items.filter(item => item[this.keyName] === section.slug));
|
||||||
},
|
},
|
||||||
|
...mapGetters(['route']),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
packInt(arr) {
|
packInt(arr) {
|
||||||
|
@ -112,8 +82,11 @@ export default {
|
||||||
collapsed: {
|
collapsed: {
|
||||||
handler() {
|
handler() {
|
||||||
const encoded = this.packInt(this.collapsed).toString()
|
const encoded = this.packInt(this.collapsed).toString()
|
||||||
if (this.$router.currentRoute.query.collapsed !== encoded)
|
if (this.route.query.collapsed !== encoded)
|
||||||
this.$router.push({...this.$router.currentRoute, query: {...this.$router.currentRoute.query, collapsed: encoded}});
|
this.$router.push({
|
||||||
|
...this.$router.currentRoute,
|
||||||
|
query: {...this.$router.currentRoute.query, collapsed: encoded}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -119,13 +119,13 @@ export default {
|
||||||
emits: ['addItemClicked', 'addTicketClicked'],
|
emits: ['addItemClicked', 'addTicketClicked'],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['events']),
|
...mapState(['events']),
|
||||||
...mapGetters(['getEventSlug', 'getActiveView', "checkPermission", "hasPermissions", "layout"]),
|
...mapGetters(['getEventSlug', 'getActiveView', "checkPermission", "hasPermissions", "layout", "route"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['changeEvent', 'changeView', 'searchEventItems']),
|
...mapActions(['changeEvent', 'changeView', 'searchEventItems']),
|
||||||
...mapMutations(['logout']),
|
...mapMutations(['logout']),
|
||||||
navigateTo(link) {
|
navigateTo(link) {
|
||||||
if (this.$router.currentRoute.path !== link)
|
if (this.route.path !== link)
|
||||||
this.$router.push(link);
|
this.$router.push(link);
|
||||||
},
|
},
|
||||||
isItemView() {
|
isItemView() {
|
||||||
|
@ -135,9 +135,9 @@ export default {
|
||||||
return this.getActiveView === 'tickets' || this.getActiveView === 'ticket';
|
return this.getActiveView === 'tickets' || this.getActiveView === 'ticket';
|
||||||
},
|
},
|
||||||
setLayout(layout) {
|
setLayout(layout) {
|
||||||
if (this.$router.currentRoute.query.layout === layout)
|
if (this.route.query.layout === layout)
|
||||||
return;
|
return;
|
||||||
this.$router.push({...this.$router.currentRoute, query: {...this.$router.currentRoute.query, layout}});
|
this.$router.push({...this.route, query: {...this.route.query, layout}});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="toast" :class="color && ('border-' + color)" role="alert" ref="toast" data-autohide="false">
|
|
||||||
<div class="toast-header" :class="[color && ('bg-' + color), color && 'text-light']">
|
|
||||||
<strong class="mr-auto pr-3">{{ title }}</strong>
|
|
||||||
<small>{{ displayTime }}</small>
|
|
||||||
<button type="button" class="ml-2 mb-1 close" @click="close()">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body" v-html="message">{{ message }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import $ from 'jquery';
|
|
||||||
import 'bootstrap/js/dist/toast';
|
|
||||||
import {DateTime} from 'luxon';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Toast',
|
|
||||||
props: ['title', 'message', 'color'],
|
|
||||||
data: () => ({
|
|
||||||
creationTime: DateTime.local(),
|
|
||||||
displayTime: 'just now',
|
|
||||||
timer: undefined
|
|
||||||
}),
|
|
||||||
mounted() {
|
|
||||||
const {toast} = this.$refs;
|
|
||||||
$(toast).toast('show');
|
|
||||||
this.timer = setInterval(this.updateDisplayTime, 1000);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
close() {
|
|
||||||
const {toast} = this.$refs;
|
|
||||||
$(toast).toast('hide');
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.$emit('close');
|
|
||||||
}, 500);
|
|
||||||
},
|
|
||||||
updateDisplayTime() {
|
|
||||||
this.displayTime = this.creationTime.toRelative();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,11 +1,8 @@
|
||||||
import Vue from 'vue';
|
import {createApp} from 'vue'
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import {sync} from 'vuex-router-sync';
|
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
// bootstrap
|
|
||||||
import 'jquery/dist/jquery.min.js';
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import 'bootstrap/dist/js/bootstrap.min.js';
|
import 'bootstrap/dist/js/bootstrap.min.js';
|
||||||
|
|
||||||
|
@ -46,20 +43,12 @@ import {
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
|
||||||
|
|
||||||
import vueDebounce from 'vue-debounce';
|
|
||||||
|
|
||||||
library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList,
|
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,
|
faWindowClose, faCamera, faStop, faPen, faCheck, faTimes, faSave, faEye, faComment, faUser, faComments, faEnvelope,
|
||||||
faArchive, faMinus, faExclamation, faHourglass, faClipboard, faTasks, faAngleDown, faAngleRight);
|
faArchive, faMinus, faExclamation, faHourglass, faClipboard, faTasks, faAngleDown, faAngleRight);
|
||||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
|
||||||
|
|
||||||
sync(store, router);
|
|
||||||
|
|
||||||
new Vue({
|
const app = createApp(App).use(store).use(router);
|
||||||
el: '#app',
|
|
||||||
store,
|
|
||||||
router,
|
|
||||||
render: h => h(App),
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.use(vueDebounce);
|
app.component('font-awesome-icon', FontAwesomeIcon);
|
||||||
|
app.mount('#app')
|
|
@ -1,24 +1,21 @@
|
||||||
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
|
import store from '@/store';
|
||||||
|
|
||||||
import Items from './views/Items';
|
import Items from './views/Items';
|
||||||
import Boxes from './views/Boxes';
|
import Boxes from './views/Boxes';
|
||||||
import Files from './views/Files';
|
import Files from './views/Files';
|
||||||
import Error from './views/Error';
|
|
||||||
import HowTo from './views/HowTo';
|
import HowTo from './views/HowTo';
|
||||||
import VueRouter from 'vue-router';
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Login from '@/views/Login.vue';
|
import Login from '@/views/Login.vue';
|
||||||
import Register from '@/views/Register.vue';
|
import Register from '@/views/Register.vue';
|
||||||
import Debug from "@/views/admin/Debug.vue";
|
import Debug from "@/views/admin/Debug.vue";
|
||||||
import Tickets from "@/views/Tickets.vue";
|
import Tickets from "@/views/Tickets.vue";
|
||||||
import Ticket from "@/views/Ticket.vue";
|
import Ticket from "@/views/Ticket.vue";
|
||||||
import Admin from "@/views/admin/Admin.vue";
|
import Admin from "@/views/admin/Admin.vue";
|
||||||
import store from "@/store";
|
|
||||||
import Empty from "@/views/Empty.vue";
|
import Empty from "@/views/Empty.vue";
|
||||||
import Events from "@/views/admin/Events.vue";
|
import Events from "@/views/admin/Events.vue";
|
||||||
import AccessControl from "@/views/admin/AccessControl.vue";
|
import AccessControl from "@/views/admin/AccessControl.vue";
|
||||||
import {default as BoxesAdmin} from "@/views/admin/Boxes.vue"
|
import {default as BoxesAdmin} from "@/views/admin/Boxes.vue"
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', redirect: '/37C3/items', meta: {requiresAuth: false}},
|
{path: '/', redirect: '/37C3/items', meta: {requiresAuth: false}},
|
||||||
{path: '/login/', name: 'login', component: Login, 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: '/user', name: 'user', component: Empty, meta: {requiresAuth: true}},
|
||||||
{path: '*', component: Error},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = createRouter({
|
||||||
mode: 'history',
|
history: createWebHistory(),
|
||||||
|
linkActiveClass: "active",
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,13 +98,10 @@ router.beforeEach((to, from, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach((to, from) => {
|
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);
|
//console.log('update last event', to.params.event);
|
||||||
store.commit('updateLastEvent', 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;
|
export default router;
|
||||||
|
|
|
@ -1,63 +1,11 @@
|
||||||
import Vue from 'vue';
|
import {createStore} from 'vuex';
|
||||||
import Vuex from 'vuex';
|
import router from './router';
|
||||||
import AxiosBootstrap from 'axios';
|
|
||||||
import * as _ from 'lodash/fp';
|
|
||||||
import router from '../router';
|
|
||||||
|
|
||||||
import * as base64 from 'base-64';
|
import * as base64 from 'base-64';
|
||||||
import * as utf8 from 'utf8';
|
import * as utf8 from 'utf8';
|
||||||
import {ticketStateColorLookup, ticketStateIconLookup} from "@/utils";
|
import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils";
|
||||||
import createMutationsSharer from "vuex-shared-mutations";
|
|
||||||
|
|
||||||
Vue.use(Vuex);
|
const store = createStore({
|
||||||
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 = `
|
|
||||||
<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.error('error interceptor fired', error.message);
|
|
||||||
|
|
||||||
if (error.isAxiosError) {
|
|
||||||
const message = `
|
|
||||||
<h3>A HTTP ${error.config.method} request failed.</h3>
|
|
||||||
<p>
|
|
||||||
url: ${error.config.url}
|
|
||||||
<br>
|
|
||||||
timeout: ${!!error.request.timeout}
|
|
||||||
<br>
|
|
||||||
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'});
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
|
||||||
state: {
|
state: {
|
||||||
keyIncrement: 0,
|
keyIncrement: 0,
|
||||||
events: [],
|
events: [],
|
||||||
|
@ -68,26 +16,30 @@ const store = new Vuex.Store({
|
||||||
tickets: [],
|
tickets: [],
|
||||||
users: [],
|
users: [],
|
||||||
groups: [],
|
groups: [],
|
||||||
|
state_options: [],
|
||||||
lastEvent: localStorage.getItem('lf_lastEvent') || '37C3',
|
lastEvent: localStorage.getItem('lf_lastEvent') || '37C3',
|
||||||
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
|
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
|
||||||
remember: false,
|
remember: false,
|
||||||
user: null,
|
|
||||||
password: null,
|
user: {
|
||||||
userPermissions: [],
|
username: null,
|
||||||
token: null,
|
password: null,
|
||||||
state_options: [],
|
permissions: [],
|
||||||
token_expiry: null,
|
token: null,
|
||||||
|
expiry: null,
|
||||||
|
},
|
||||||
|
|
||||||
local_loaded: false,
|
local_loaded: false,
|
||||||
showAddBoxModal: false,
|
showAddBoxModal: false,
|
||||||
toggle: false,
|
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getEventSlug: state => state.route && state.route.params.event ? state.route.params.event : state.lastEvent,
|
route: state => router.currentRoute.value,
|
||||||
getActiveView: state => state.route.name || 'items',
|
getEventSlug: state => router.currentRoute.value.params.event ? router.currentRoute.value.params.event : state.lastEvent,
|
||||||
getFilters: state => state.route.query,
|
getActiveView: state => router.currentRoute.value.name || 'items',
|
||||||
|
getFilters: state => router.currentRoute.value.query,
|
||||||
getBoxes: state => state.loadedBoxes,
|
getBoxes: state => state.loadedBoxes,
|
||||||
checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`),
|
checkPermission: state => (event, perm) => state.user.permissions.includes(`${event}:${perm}`) || state.user.permissions.includes(`*:${perm}`),
|
||||||
hasPermissions: state => state.userPermissions.length > 0,
|
hasPermissions: state => state.user.permissions.length > 0,
|
||||||
stateInfo: state => (slug) => {
|
stateInfo: state => (slug) => {
|
||||||
const obj = state.state_options.filter((s) => s.value === slug)[0];
|
const obj = state.state_options.filter((s) => s.value === slug)[0];
|
||||||
if (obj) {
|
if (obj) {
|
||||||
|
@ -107,9 +59,8 @@ const store = new Vuex.Store({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layout: (state, getters) => {
|
layout: (state, getters) => {
|
||||||
state.toggle = !state.toggle;
|
if (router.currentRoute.value.query.layout)
|
||||||
if (router.currentRoute.query.layout)
|
return router.currentRoute.value.query.layout;
|
||||||
return router.currentRoute.query.layout;
|
|
||||||
if (getters.getActiveView === 'items')
|
if (getters.getActiveView === 'items')
|
||||||
return 'cards';
|
return 'cards';
|
||||||
if (getters.getActiveView === 'tickets')
|
if (getters.getActiveView === 'tickets')
|
||||||
|
@ -118,21 +69,20 @@ const store = new Vuex.Store({
|
||||||
isLoggedIn(state) {
|
isLoggedIn(state) {
|
||||||
if (!state.local_loaded) {
|
if (!state.local_loaded) {
|
||||||
state.remember = localStorage.getItem('remember') === 'true';
|
state.remember = localStorage.getItem('remember') === 'true';
|
||||||
state.user = localStorage.getItem('user');
|
state.user.username = localStorage.getItem('user');
|
||||||
//state.password = localStorage.getItem('password');
|
//state.password = localStorage.getItem('password');
|
||||||
state.userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]');
|
state.user.permissions = JSON.parse(localStorage.getItem('permissions') || '[]');
|
||||||
state.token = localStorage.getItem('token');
|
state.user.token = localStorage.getItem('token');
|
||||||
state.token_expiry = localStorage.getItem('token_expiry');
|
state.user.expiry = localStorage.getItem('token_expiry');
|
||||||
state.local_loaded = true;
|
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: {
|
mutations: {
|
||||||
updateLastUsed(state, diff) {
|
updateLastUsed(state, diff) {
|
||||||
state.lastUsed = _.extend(state.lastUsed, diff);
|
state.lastUsed = {...state.lastUsed, ...diff};
|
||||||
localStorage.setItem('lf_lastUsed', JSON.stringify(state.lastUsed));
|
localStorage.setItem('lf_lastUsed', JSON.stringify(state.lastUsed));
|
||||||
},
|
},
|
||||||
updateLastEvent(state, slug) {
|
updateLastEvent(state, slug) {
|
||||||
|
@ -200,38 +150,39 @@ const store = new Vuex.Store({
|
||||||
localStorage.setItem('remember', remember);
|
localStorage.setItem('remember', remember);
|
||||||
},
|
},
|
||||||
setUser(state, user) {
|
setUser(state, user) {
|
||||||
state.user = user;
|
state.user.username = user;
|
||||||
if (user)
|
if (user)
|
||||||
localStorage.setItem('user', user);
|
localStorage.setItem('user', user);
|
||||||
},
|
},
|
||||||
setPassword(state, password) {
|
setPassword(state, password) {
|
||||||
state.password = password;
|
state.user.password = password;
|
||||||
},
|
},
|
||||||
setPermissions(state, permissions) {
|
setPermissions(state, permissions) {
|
||||||
state.userPermissions = permissions;
|
state.user.permissions = permissions;
|
||||||
if (permissions)
|
if (permissions)
|
||||||
localStorage.setItem('permissions', JSON.stringify(permissions));
|
localStorage.setItem('permissions', JSON.stringify(permissions));
|
||||||
},
|
},
|
||||||
setToken(state, {token, expiry}) {
|
setToken(state, {token, expiry}) {
|
||||||
state.token = token;
|
const user = {...state.user};
|
||||||
state.token_expiry = expiry;
|
user.token = token;
|
||||||
|
user.expiry = expiry;
|
||||||
|
state.user = user;
|
||||||
if (token)
|
if (token)
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem('token', token);
|
||||||
localStorage.setItem('token_expiry', expiry);
|
localStorage.setItem('token_expiry', expiry);
|
||||||
},
|
},
|
||||||
logout(state) {
|
setUserInfo(state, user) {
|
||||||
state.user = null;
|
state.user = user;
|
||||||
state.token = null;
|
},
|
||||||
localStorage.removeItem('user');
|
logout(state) {
|
||||||
localStorage.removeItem('permissions');
|
const user = {...state.user};
|
||||||
localStorage.removeItem('token');
|
user.user = null;
|
||||||
localStorage.removeItem('token_expiry');
|
user.password = null;
|
||||||
if (router.currentRoute.name !== 'login')
|
user.token = null;
|
||||||
router.push('/login');
|
user.expiry = null;
|
||||||
|
user.permissions = null;
|
||||||
|
state.user = user;
|
||||||
},
|
},
|
||||||
triggerLayoutChange(state) {
|
|
||||||
state.toggle = !state.toggle;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async login({commit, dispatch, state}, {username, password, remember}) {
|
async login({commit, dispatch, state}, {username, password, remember}) {
|
||||||
|
@ -247,7 +198,6 @@ const store = new Vuex.Store({
|
||||||
commit('setToken', data);
|
commit('setToken', data);
|
||||||
commit('setUser', username);
|
commit('setUser', username);
|
||||||
commit('setPassword', password);
|
commit('setPassword', password);
|
||||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
|
||||||
dispatch('afterLogin');
|
dispatch('afterLogin');
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -258,18 +208,17 @@ const store = new Vuex.Store({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async reloadToken({commit, state}) {
|
async reloadToken({commit, state, getters}) {
|
||||||
try {
|
try {
|
||||||
if (state.password) {
|
if (state.user.username && state.user.password) {
|
||||||
const data = await fetch('/api/2/login/', {
|
const data = await fetch('/api/2/login/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
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'
|
credentials: 'omit'
|
||||||
}).then(r => r.json()).catch(e => console.error(e))
|
}).then(r => r.json()).catch(e => console.error(e))
|
||||||
if (data && data.token) {
|
if (data && data.token) {
|
||||||
commit('setToken', data);
|
commit('setToken', data);
|
||||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,29 +229,33 @@ const store = new Vuex.Store({
|
||||||
store.commit('logout');
|
store.commit('logout');
|
||||||
},
|
},
|
||||||
//async verifyToken({commit, state}) {
|
//async verifyToken({commit, state}) {
|
||||||
async afterLogin({dispatch}) {
|
async afterLogin({dispatch, state}) {
|
||||||
const boxes = dispatch('loadBoxes');
|
let promises = [];
|
||||||
const states = dispatch('fetchTicketStates');
|
promises.push(dispatch('loadBoxes'));
|
||||||
const items = dispatch('loadEventItems');
|
promises.push(dispatch('fetchTicketStates'));
|
||||||
const tickets = dispatch('loadTickets');
|
promises.push(dispatch('loadEventItems'));
|
||||||
const user = dispatch('loadUserInfo');
|
promises.push(dispatch('loadTickets'));
|
||||||
await Promise.all([boxes, items, tickets, user, states]);
|
if (!state.user.permissions) {
|
||||||
|
promises.push(dispatch('loadUserInfo'));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
},
|
},
|
||||||
async fetchImage({state}, url) {
|
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}) {
|
async loadUserInfo({commit, state}) {
|
||||||
const {data} = await axios.get('/2/self/');
|
const {data, success} = await http.get('/2/self/', state.user.token);
|
||||||
commit('setUser', data.username);
|
|
||||||
commit('setPermissions', data.permissions);
|
commit('setPermissions', data.permissions);
|
||||||
},
|
},
|
||||||
async loadEvents({commit}) {
|
async loadEvents({commit, state}) {
|
||||||
const {data} = await axios.get('/2/events/');
|
const {data, success} = await http.get('/2/events/', state.user.token);
|
||||||
commit('replaceEvents', data);
|
if (data && success)
|
||||||
|
commit('replaceEvents', data);
|
||||||
},
|
},
|
||||||
async fetchTicketStates({commit}) {
|
async fetchTicketStates({commit, state}) {
|
||||||
const {data} = await axios.get('/2/tickets/states/');
|
const {data, success} = await http.get('/2/tickets/states/', state.user.token);
|
||||||
commit('replaceTicketStates', data);
|
if (data && success)
|
||||||
|
commit('replaceTicketStates', data);
|
||||||
},
|
},
|
||||||
changeEvent({dispatch, getters, commit}, eventName) {
|
changeEvent({dispatch, getters, commit}, eventName) {
|
||||||
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
||||||
|
@ -321,121 +274,121 @@ const store = new Vuex.Store({
|
||||||
if (slug in state.itemCache) {
|
if (slug in state.itemCache) {
|
||||||
commit('replaceLoadedItems', state.itemCache[slug]);
|
commit('replaceLoadedItems', state.itemCache[slug]);
|
||||||
}
|
}
|
||||||
const {data} = await axios.get(`/2/${slug}/items/`);
|
const {data, success} = await http.get(`/2/${slug}/items/`, state.user.token);
|
||||||
commit('replaceLoadedItems', data);
|
if (data && success) {
|
||||||
commit('setItemCache', {slug, items: data});
|
commit('replaceLoadedItems', data);
|
||||||
|
commit('setItemCache', {slug, items: data});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error loading items");
|
console.error("Error loading items");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async searchEventItems({commit, getters}, query) {
|
async searchEventItems({commit, getters, state}, query) {
|
||||||
const foo = utf8.encode(query);
|
const foo = utf8.encode(query);
|
||||||
const bar = base64.encode(foo);
|
const bar = base64.encode(foo);
|
||||||
|
|
||||||
const {data} = await axios.get(`/2/${getters.getEventSlug}/items/${bar}/`);
|
const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${bar}/`, state.user.token);
|
||||||
commit('replaceLoadedItems', data);
|
if (data && success)
|
||||||
|
commit('replaceLoadedItems', data);
|
||||||
},
|
},
|
||||||
async loadBoxes({commit}) {
|
async loadBoxes({commit, state}) {
|
||||||
const {data} = await axios.get('/2/boxes/');
|
const {data, success} = await http.get('/2/boxes/', state.user.token);
|
||||||
commit('replaceBoxes', data);
|
if (data && success)
|
||||||
|
commit('replaceBoxes', data);
|
||||||
},
|
},
|
||||||
async createBox({commit, dispatch}, box) {
|
async createBox({commit, dispatch, state}, box) {
|
||||||
const {data} = await axios.post('/2/boxes/', box);
|
const {data, success} = await http.post('/2/boxes/', box, state.user.token);
|
||||||
commit('replaceBoxes', data);
|
commit('replaceBoxes', data);
|
||||||
dispatch('loadBoxes').then(() => {
|
dispatch('loadBoxes').then(() => {
|
||||||
commit('closeAddBoxModal');
|
commit('closeAddBoxModal');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async deleteBox({commit, dispatch}, box_id) {
|
async deleteBox({commit, dispatch, state}, box_id) {
|
||||||
await axios.delete(`/2/boxes/${box_id}/`);
|
await http.delete(`/2/boxes/${box_id}/`, state.user.token);
|
||||||
dispatch('loadBoxes');
|
dispatch('loadBoxes');
|
||||||
},
|
},
|
||||||
async updateItem({commit, getters}, item) {
|
async updateItem({commit, getters, state}, item) {
|
||||||
const {data} = await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
|
const {
|
||||||
|
data,
|
||||||
|
success
|
||||||
|
} = await http.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
|
||||||
commit('updateItem', data);
|
commit('updateItem', data);
|
||||||
},
|
},
|
||||||
async markItemReturned({commit, getters}, item) {
|
async markItemReturned({commit, getters, state}, item) {
|
||||||
await axios.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true});
|
await http.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}, state.user.token);
|
||||||
commit('removeItem', item);
|
commit('removeItem', item);
|
||||||
},
|
},
|
||||||
async deleteItem({commit, getters}, item) {
|
async deleteItem({commit, getters, state}, item) {
|
||||||
await axios.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
|
await http.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
|
||||||
commit('removeItem', item);
|
commit('removeItem', item);
|
||||||
},
|
},
|
||||||
async postItem({commit, getters}, item) {
|
async postItem({commit, getters, state}, item) {
|
||||||
commit('updateLastUsed', {box: item.box, cid: item.cid});
|
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);
|
commit('appendItem', data);
|
||||||
},
|
},
|
||||||
async loadTickets({commit}) {
|
async loadTickets({commit, state}) {
|
||||||
const {data} = await axios.get('/2/tickets/');
|
const {data, success} = await http.get('/2/tickets/', state.user.token);
|
||||||
commit('replaceTickets', data);
|
if (data && success)
|
||||||
|
commit('replaceTickets', data);
|
||||||
},
|
},
|
||||||
async sendMail({commit, dispatch}, {id, message}) {
|
async sendMail({commit, dispatch, state}, {id, message}) {
|
||||||
const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message});
|
const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token);
|
||||||
await dispatch('loadTickets');
|
if (data && success) {
|
||||||
|
await dispatch('loadTickets');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async postManualTicket({commit, dispatch}, {sender, message, title,}) {
|
async postManualTicket({commit, dispatch, state}, {sender, message, title,}) {
|
||||||
const {data} = await axios.post(`/2/tickets/manual/`, {
|
const {data, success} = await http.post(`/2/tickets/manual/`, {
|
||||||
name: title,
|
name: title,
|
||||||
sender,
|
sender,
|
||||||
body: message,
|
body: message,
|
||||||
recipient: 'mail@c3lf.de'
|
recipient: 'mail@c3lf.de'
|
||||||
});
|
}, state.user.token);
|
||||||
await dispatch('loadTickets');
|
await dispatch('loadTickets');
|
||||||
},
|
},
|
||||||
async postComment({commit, dispatch}, {id, message}) {
|
async postComment({commit, dispatch, state}, {id, message}) {
|
||||||
const {data} = await axios.post(`/2/tickets/${id}/comment/`, {comment: message});
|
const {data, success} = await http.post(`/2/tickets/${id}/comment/`, {comment: message}, state.user.token);
|
||||||
await dispatch('loadTickets');
|
if (data && success) {
|
||||||
|
await dispatch('loadTickets');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async loadUsers({commit}) {
|
async loadUsers({commit, state}) {
|
||||||
const {data} = await axios.get('/2/users/');
|
const {data, success} = await http.get('/2/users/', state.user.token);
|
||||||
commit('replaceUsers', data);
|
if (data && success)
|
||||||
|
commit('replaceUsers', data);
|
||||||
},
|
},
|
||||||
async loadGroups({commit}) {
|
async loadGroups({commit, state}) {
|
||||||
const {data} = await axios.get('/2/groups/');
|
const {data, success} = await http.get('/2/groups/', state.user.token);
|
||||||
commit('replaceGroups', data);
|
if (data && success)
|
||||||
|
commit('replaceGroups', data);
|
||||||
},
|
},
|
||||||
async updateTicket({commit}, ticket) {
|
async updateTicket({commit, state}, ticket) {
|
||||||
const {data} = await axios.put(`/2/tickets/${ticket.id}/`, ticket);
|
const {data, success} = await http.put(`/2/tickets/${ticket.id}/`, ticket, state.user.token);
|
||||||
commit('updateTicket', data);
|
commit('updateTicket', data);
|
||||||
},
|
},
|
||||||
async updateTicketPartial({commit}, {id, ...ticket}) {
|
async updateTicketPartial({commit, state}, {id, ...ticket}) {
|
||||||
const {data} = await axios.patch(`/2/tickets/${id}/`, ticket);
|
const {data, success} = await http.patch(`/2/tickets/${id}/`, ticket, state.user.token);
|
||||||
commit('updateTicket', data);
|
commit('updateTicket', data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [createMutationsSharer({
|
});
|
||||||
predicate: [
|
|
||||||
'replaceLoadedItems',
|
store.watch((state) => state.user, (user) => {
|
||||||
'setItemCache',
|
console.log('user changed', user);
|
||||||
'setLayout',
|
if (store.getters.isLoggedIn) {
|
||||||
'replaceBoxes',
|
if (router.currentRoute.value.name === 'login' && router.currentRoute.value.query.redirect)
|
||||||
'updateItem',
|
router.push(router.currentRoute.value.query.redirect);
|
||||||
'removeItem',
|
else if (router.currentRoute.value.name === 'login')
|
||||||
'appendItem',
|
router.push('/');
|
||||||
'replaceTickets',
|
} else {
|
||||||
'replaceUsers',
|
if (router.currentRoute.value.name !== 'login') {
|
||||||
'replaceGroups',
|
router.push({
|
||||||
'updateTicket',
|
name: 'login',
|
||||||
'openAddBoxModal',
|
query: {redirect: router.currentRoute.value.fullPath},
|
||||||
'closeAddBoxModal',
|
});
|
||||||
'createToast',
|
}
|
||||||
'removeToast',
|
}
|
||||||
'setRemember',
|
|
||||||
'setUser',
|
|
||||||
'setPermissions',
|
|
||||||
'setToken',
|
|
||||||
'logout',
|
|
||||||
]
|
|
||||||
})],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
|
||||||
store.dispatch('loadEvents').then(() => {
|
|
||||||
if (store.getters.isLoggedIn) {
|
|
||||||
axios.defaults.headers.common['Authorization'] = `Token ${store.state.token}`;
|
|
||||||
store.dispatch('afterLogin');
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -24,4 +24,80 @@ function ticketStateIconLookup(ticket) {
|
||||||
return 'exclamation';
|
return 'exclamation';
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ticketStateColorLookup, ticketStateIconLookup};
|
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};
|
|
@ -93,7 +93,7 @@ export default {
|
||||||
...mapGetters(['layout']),
|
...mapGetters(['layout']),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['deleteItem', 'markItemReturned']),
|
...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem']),
|
||||||
openLightboxModalWith(item) {
|
openLightboxModalWith(item) {
|
||||||
this.lightboxHash = item.file;
|
this.lightboxHash = item.file;
|
||||||
},
|
},
|
||||||
|
@ -107,12 +107,15 @@ export default {
|
||||||
this.editingItem = null;
|
this.editingItem = null;
|
||||||
},
|
},
|
||||||
saveEditingItem() { // Saves the edited copy of the item.
|
saveEditingItem() { // Saves the edited copy of the item.
|
||||||
this.$store.dispatch('updateItem', this.editingItem);
|
this.updateItem(this.editingItem);
|
||||||
this.closeEditingModal();
|
this.closeEditingModal();
|
||||||
},
|
},
|
||||||
confirm(message) {
|
confirm(message) {
|
||||||
return window.confirm(message);
|
return window.confirm(message);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadEventItems();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -100,7 +100,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
input{
|
input {
|
||||||
background-color: var(--dark);
|
background-color: var(--dark);
|
||||||
border: var(--gray) 1px solid;;
|
border: var(--gray) 1px solid;;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<ul class="nav nav-tabs card-header-tabs">
|
<ul class="nav nav-tabs card-header-tabs">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<router-link class="nav-link" :to="{name: 'admin'}" active-class="active" exact>Dashboard</router-link>
|
<router-link class="nav-link" :to="{name: 'admin'}" active-class="dummy" exact-active-class="active">Dashboard</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<router-link class="nav-link" :to="{name: 'events'}" active-class="active">Events</router-link>
|
<router-link class="nav-link" :to="{name: 'events'}" active-class="active">Events</router-link>
|
||||||
|
|
|
@ -22,27 +22,13 @@
|
||||||
{{ box.name }}
|
{{ box.name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3 class="text-center">Mails</h3>
|
|
||||||
<!--p>{{ mails }}</p-->
|
|
||||||
<ul>
|
|
||||||
<li v-for="mail in mails" :key="mail.id">
|
|
||||||
{{ mail.id }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3 class="text-center">Issues</h3>
|
<h3 class="text-center">Issues</h3>
|
||||||
<!--p>{{ issues }}</p-->
|
<!--p>{{ issues }}</p-->
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="issue in issues" :key="issue.id">
|
<li v-for="issue in tickets" :key="issue.id">
|
||||||
{{ issue.id }}
|
{{ issue.id }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3 class="text-center">System Events</h3>
|
|
||||||
<!--p>{{ systemEvents }}</p-->
|
|
||||||
<ul>
|
|
||||||
<li v-for="systemEvent in systemEvents" :key="systemEvent.id">
|
|
||||||
{{ systemEvent.id }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -54,19 +40,17 @@ export default {
|
||||||
name: 'Debug',
|
name: 'Debug',
|
||||||
components: {Table},
|
components: {Table},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['events', 'loadedItems', 'loadedBoxes', 'mails', 'issues', 'systemEvents']),
|
...mapState(['events', 'loadedItems', 'loadedBoxes', 'tickets']),
|
||||||
qr_url() {
|
qr_url() {
|
||||||
return window.location.href;
|
return window.location.href;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['changeEvent', 'loadMails', 'loadIssues', 'loadSystemEvents']),
|
...mapActions(['changeEvent', 'loadTickets']),
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadMails();
|
this.loadTickets();
|
||||||
this.loadIssues();
|
|
||||||
this.loadSystemEvents();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
31
web/vue.config.js
Normal file
31
web/vue.config.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// vue.config.js
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
headers: {
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
"Access-Control-Allow-Headers": "*",
|
||||||
|
"Access-Control-Allow-Methods": "*"
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
'^/media/2': {
|
||||||
|
target: 'https://staging.c3lf.de/',
|
||||||
|
changeOrigin: true
|
||||||
|
},
|
||||||
|
'^/api/2': {
|
||||||
|
target: 'https://staging.c3lf.de/',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'^/api/1': {
|
||||||
|
target: 'https://staging.c3lf.de/',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'^/ws/2': {
|
||||||
|
target: 'http://127.0.0.1:8082/',
|
||||||
|
//changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
logLevel: 'debug',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue