stash
This commit is contained in:
parent
81f1f97a6b
commit
674106a8a5
16 changed files with 346 additions and 208 deletions
|
@ -7,6 +7,7 @@ from django.contrib.auth import login
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
from knox.models import AuthToken
|
from knox.models import AuthToken
|
||||||
from knox.views import LoginView as KnoxLoginView
|
from knox.views import LoginView as KnoxLoginView
|
||||||
|
|
||||||
|
@ -36,6 +37,26 @@ class UserViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSerializer(serializers.ModelSerializer):
|
||||||
|
permissions = serializers.SerializerMethodField()
|
||||||
|
members = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
fields = ('id', 'name', 'permissions', 'members')
|
||||||
|
|
||||||
|
def get_permissions(self, obj):
|
||||||
|
return ["*:" + p.codename for p in obj.permissions.all()]
|
||||||
|
|
||||||
|
def get_members(self, obj):
|
||||||
|
return [u.username for u in obj.user_set.all()]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Group.objects.all()
|
||||||
|
serializer_class = GroupSerializer
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def selfUser(request):
|
def selfUser(request):
|
||||||
|
@ -85,6 +106,7 @@ class LoginView(KnoxLoginView):
|
||||||
|
|
||||||
router = routers.SimpleRouter()
|
router = routers.SimpleRouter()
|
||||||
router.register(r'users', UserViewSet, basename='users')
|
router.register(r'users', UserViewSet, basename='users')
|
||||||
|
router.register(r'groups', GroupViewSet, basename='groups')
|
||||||
|
|
||||||
urlpatterns = router.urls + [
|
urlpatterns = router.urls + [
|
||||||
path('self/', selfUser),
|
path('self/', selfUser),
|
||||||
|
|
|
@ -132,3 +132,49 @@ class UserApiTest(TestCase):
|
||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertTrue('token' in response.json())
|
self.assertTrue('token' in response.json())
|
||||||
|
|
||||||
|
|
||||||
|
class GroupApiTest(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.event = Event.objects.create(name='testevent', slug='testevent')
|
||||||
|
# Admin, Orga, Team, User are created by default
|
||||||
|
self.group1 = Group.objects.create(name='testgroup1')
|
||||||
|
self.group2 = Group.objects.create(name='testgroup2')
|
||||||
|
self.group1.permissions.add(Permission.objects.get(codename='add_item'))
|
||||||
|
self.group1.permissions.add(Permission.objects.get(codename='view_item'))
|
||||||
|
self.group2.permissions.add(Permission.objects.get(codename='view_event'))
|
||||||
|
self.group2.permissions.add(Permission.objects.get(codename='view_item'))
|
||||||
|
self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test')
|
||||||
|
self.user.user_permissions.add(Permission.objects.get(codename='add_event'))
|
||||||
|
self.user.groups.add(self.group1)
|
||||||
|
self.user.groups.add(self.group2)
|
||||||
|
self.user.save()
|
||||||
|
EventPermission.objects.create(event=self.event, user=self.user,
|
||||||
|
permission=Permission.objects.get(codename='delete_item'))
|
||||||
|
self.user.save()
|
||||||
|
self.token = AuthToken.objects.create(user=self.user)
|
||||||
|
self.client = Client(headers={'Authorization': 'Token ' + self.token[1]})
|
||||||
|
|
||||||
|
def test_groups(self):
|
||||||
|
response = self.client.get('/api/2/groups/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 6)
|
||||||
|
self.assertEqual(response.json()[0]['name'], 'Admin')
|
||||||
|
self.assertEqual(response.json()[1]['name'], 'Orga')
|
||||||
|
self.assertEqual(response.json()[2]['name'], 'Team')
|
||||||
|
self.assertEqual(response.json()[3]['name'], 'User')
|
||||||
|
self.assertEqual(response.json()[4]['name'], 'testgroup1')
|
||||||
|
self.assertEqual(response.json()[5]['name'], 'testgroup2')
|
||||||
|
|
||||||
|
def test_group(self):
|
||||||
|
response = self.client.get('/api/2/groups/5/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json()['name'], 'testgroup1')
|
||||||
|
permissions = response.json()['permissions']
|
||||||
|
self.assertEqual(len(permissions), 2)
|
||||||
|
self.assertTrue('*:add_item' in permissions)
|
||||||
|
self.assertTrue('*:view_item' in permissions)
|
||||||
|
members = response.json()['members']
|
||||||
|
self.assertEqual(len(members), 1)
|
||||||
|
self.assertEqual(members[0], 'testuser')
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" v-if="checkPermission(getEventSlug, 'delete_event')">
|
<li class="nav-item" v-if="checkPermission(getEventSlug, 'delete_event')">
|
||||||
<router-link :to="{name: 'admin'}" :class="['nav-link', { active: getActiveView === 'admin' }]">
|
<router-link :to="{name: 'admin'}" class="nav-link" active-class="active">
|
||||||
Admin
|
Admin
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
|
72
web/src/components/Table3D.vue
Normal file
72
web/src/components/Table3D.vue
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<table class="table table-striped table-dark">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" v-for="(column, index) in columns" :key="index">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<button
|
||||||
|
:class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
|
||||||
|
@click="toggleSort(column)"
|
||||||
|
>
|
||||||
|
{{ column }}
|
||||||
|
<span :class="{ 'text-info': column === sortBy }">
|
||||||
|
<font-awesome-icon :icon="getSortIcon(column)"/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="filter"
|
||||||
|
:value="filters[column]"
|
||||||
|
@input="changeFilter(column, $event.target.value)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in internalItems" :key="item[keyName]" @click="$emit('itemActivated', item)">
|
||||||
|
<td v-for="(column, index) in columns" :key="index">{{ item[column] }}</td>
|
||||||
|
<td>
|
||||||
|
<slot v-bind:item="item"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DataContainer from '@/mixins/data-container';
|
||||||
|
import router from '../router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Table3D',
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
foldedDimension: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-body-move {
|
||||||
|
transition: transform 1s;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -25,8 +25,8 @@
|
||||||
</span>
|
</span>
|
||||||
<div class="new-comment">
|
<div class="new-comment">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" placeholder="Add a comment..." v-model="newMail">
|
<input type="text" placeholder="reply mail..." v-model="newMail">
|
||||||
<button class="btn" @click="sendMail">
|
<button class="btn btn-primary" @click="sendMail">
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p><span v-html="body"></span></p>
|
<p><span v-html="body"></span></p>
|
||||||
<button class="button">👏 2</button>
|
<!--button class="button">👏 2</button>
|
||||||
<button class="button | square">
|
<button class="button | square">
|
||||||
<font-awesome-icon icon="user"/>
|
<font-awesome-icon icon="user"/>
|
||||||
</button>
|
</button-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="show-replies">
|
<!--button class="show-replies">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-forward"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-forward"
|
||||||
width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
<font-awesome-icon icon="user"/>
|
<font-awesome-icon icon="user"/>
|
||||||
</i>
|
</i>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -34,14 +34,16 @@ import {
|
||||||
faComment,
|
faComment,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faUser,
|
faUser,
|
||||||
faComments
|
faComments,
|
||||||
|
faArchive,
|
||||||
} 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';
|
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);
|
||||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||||
|
|
||||||
//import VueQRCodeComponent from 'vue-qrcode-component'
|
//import VueQRCodeComponent from 'vue-qrcode-component'
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
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 Events from './views/Events';
|
|
||||||
import Error from './views/Error';
|
import Error from './views/Error';
|
||||||
import HowTo from './views/HowTo';
|
import HowTo from './views/HowTo';
|
||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
|
@ -14,36 +13,49 @@ 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 store from "@/store";
|
||||||
import Empty from "@/views/Empty.vue";
|
import Empty from "@/views/Empty.vue";
|
||||||
|
import Events from "@/views/admin/Events.vue";
|
||||||
|
import AccessControl from "@/views/admin/AccessControl.vue";
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', redirect: '/Camp23/items', meta: {requiresAuth: false}},
|
{path: '/', redirect: '/Camp23/items', meta: {requiresAuth: false}},
|
||||||
{path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}},
|
{path: '/login/', name: 'login', component: Login, meta: {requiresAuth: false}},
|
||||||
{path: '/register', name: 'register', component: Register, meta: {requiresAuth: false}},
|
{path: '/register/', name: 'register', component: Register, meta: {requiresAuth: false}},
|
||||||
{path: '/howto', name: 'howto', component: HowTo, meta: {requiresAuth: true}},
|
{path: '/howto/', name: 'howto', component: HowTo, meta: {requiresAuth: true}},
|
||||||
{path: '/:event/items', name: 'items', component: Items, meta:
|
{path: '/:event/items/', name: 'items', component: Items, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'view_item'}},
|
{requiresAuth: true, requiresPermission: 'view_item'}},
|
||||||
{path: '/:event/item/:uid', name: 'item', component: Items, meta:
|
{path: '/:event/item/:uid/', name: 'item', component: Items, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'view_item'}},
|
{requiresAuth: true, requiresPermission: 'view_item'}},
|
||||||
{path: '/:event/boxes', name: 'boxes', component: Boxes, meta:
|
{path: '/:event/boxes/', name: 'boxes', component: Boxes, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'view_container'}},
|
{requiresAuth: true, requiresPermission: 'view_container'}},
|
||||||
{path: '/:event/box/:uid', name: 'box', component: Boxes, meta:
|
{path: '/:event/box/:uid/', name: 'box', component: Boxes, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'view_container'}},
|
{requiresAuth: true, requiresPermission: 'view_container'}},
|
||||||
{path: '/:event/tickets', name: 'tickets', component: Tickets, meta:
|
{path: '/:event/tickets/', name: 'tickets', component: Tickets, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'view_issuethread'}},
|
{requiresAuth: true, requiresPermission: 'view_issuethread'}},
|
||||||
{path: '/:event/ticket/:id', name: 'ticket', component: Ticket, meta:
|
{path: '/:event/ticket/:id/', name: 'ticket', component: Ticket, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'view_issuethread'}},
|
{requiresAuth: true, requiresPermission: 'view_issuethread'}},
|
||||||
{path: '/admin', name: 'admin', component: Admin, meta:
|
{path: '/admin/', component: Admin, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'delete_event'}},
|
{requiresAuth: true, requiresPermission: 'delete_event'},
|
||||||
{path: '/admin/files', name: 'files', component: Files, meta:
|
children: [
|
||||||
{requiresAuth: true, requiresPermission: 'delete_event'}},
|
{
|
||||||
{path: '/admin/events', name: 'events', component: Events, meta:
|
path: 'files/', name: 'files', component: Files, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'delete_event'}},
|
{requiresAuth: true, requiresPermission: 'delete_event'}
|
||||||
{path: '/admin/debug', name: 'debug', component: Debug, meta:
|
},
|
||||||
{requiresAuth: true, requiresPermission: 'delete_event'}},
|
{
|
||||||
{path: '/admin/users', name: 'users', component: Events, meta:
|
path: 'events/', name: 'events', component: Events, meta:
|
||||||
{requiresAuth: true, requiresPermission: 'delete_event'}},
|
{requiresAuth: true, requiresPermission: 'delete_event'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '', name: 'admin', component: Debug, meta:
|
||||||
|
{requiresAuth: true, requiresPermission: 'delete_event'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users/', name: 'users', component: AccessControl, meta:
|
||||||
|
{requiresAuth: true, requiresPermission: 'delete_event'}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{path: '/user', name: 'user', component: Empty, meta: {requiresAuth: true}},
|
{path: '/user', name: 'user', component: Empty, meta: {requiresAuth: true}},
|
||||||
{path: '*', component: Error},
|
{path: '*', component: Error},
|
||||||
];
|
];
|
||||||
|
|
|
@ -67,10 +67,13 @@ const store = new Vuex.Store({
|
||||||
loadedBoxes: [],
|
loadedBoxes: [],
|
||||||
toasts: [],
|
toasts: [],
|
||||||
tickets: [],
|
tickets: [],
|
||||||
|
users: [],
|
||||||
|
groups: [],
|
||||||
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
|
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
|
||||||
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
|
lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'),
|
||||||
remember: false,
|
remember: false,
|
||||||
user: null,
|
user: null,
|
||||||
|
password: null,
|
||||||
userPermissions: [],
|
userPermissions: [],
|
||||||
token: null,
|
token: null,
|
||||||
token_expiry: null,
|
token_expiry: null,
|
||||||
|
@ -136,6 +139,12 @@ const store = new Vuex.Store({
|
||||||
replaceTickets(state, tickets) {
|
replaceTickets(state, tickets) {
|
||||||
state.tickets = tickets;
|
state.tickets = tickets;
|
||||||
},
|
},
|
||||||
|
replaceUsers(state, users) {
|
||||||
|
state.users = users;
|
||||||
|
},
|
||||||
|
replaceGroups(state, groups) {
|
||||||
|
state.groups = groups;
|
||||||
|
},
|
||||||
updateTicket(state, updatedTicket) {
|
updateTicket(state, updatedTicket) {
|
||||||
const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0];
|
const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0];
|
||||||
Object.assign(ticket, updatedTicket);
|
Object.assign(ticket, updatedTicket);
|
||||||
|
@ -193,6 +202,7 @@ const store = new Vuex.Store({
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
commit('setToken', data);
|
commit('setToken', data);
|
||||||
commit('setUser', username);
|
commit('setUser', username);
|
||||||
|
commit('setPassword', password);
|
||||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
||||||
dispatch('afterLogin');
|
dispatch('afterLogin');
|
||||||
return true;
|
return true;
|
||||||
|
@ -209,7 +219,7 @@ const store = new Vuex.Store({
|
||||||
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.token}),
|
body: JSON.stringify({username: state.user, password: state.password}),
|
||||||
credentials: 'omit'
|
credentials: 'omit'
|
||||||
}).then(r => r.json())
|
}).then(r => r.json())
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
|
@ -229,6 +239,7 @@ const store = new Vuex.Store({
|
||||||
const items = dispatch('loadEventItems');
|
const items = dispatch('loadEventItems');
|
||||||
const tickets = dispatch('loadTickets');
|
const tickets = dispatch('loadTickets');
|
||||||
const user = dispatch('loadUserInfo');
|
const user = dispatch('loadUserInfo');
|
||||||
|
await Promise.all([boxes, items, tickets, user]);
|
||||||
},
|
},
|
||||||
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.token}`}});
|
||||||
|
@ -301,7 +312,15 @@ const store = new Vuex.Store({
|
||||||
async sendMail({commit, dispatch}, {id, message}) {
|
async sendMail({commit, dispatch}, {id, message}) {
|
||||||
const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message});
|
const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message});
|
||||||
await dispatch('loadTickets');
|
await dispatch('loadTickets');
|
||||||
}
|
},
|
||||||
|
async loadUsers({commit}) {
|
||||||
|
const {data} = await axios.get('/2/users/');
|
||||||
|
commit('replaceUsers', data);
|
||||||
|
},
|
||||||
|
async loadGroups({commit}) {
|
||||||
|
const {data} = await axios.get('/2/groups/');
|
||||||
|
commit('replaceGroups', data);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-8 offset-xl-2">
|
|
||||||
<Table
|
|
||||||
:columns="['slug', 'name']"
|
|
||||||
:items="events"
|
|
||||||
:keyName="'slug'"
|
|
||||||
v-slot="{ item }"
|
|
||||||
>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn btn-secondary" @click.stop="changeEvent(item)">
|
|
||||||
<font-awesome-icon icon="archive"/>
|
|
||||||
use
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-danger" @click.stop="">
|
|
||||||
<font-awesome-icon icon="trash"/>
|
|
||||||
delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {mapActions, mapState} from 'vuex';
|
|
||||||
import Table from '@/components/Table';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Events',
|
|
||||||
components: {Table},
|
|
||||||
computed: mapState(['events']),
|
|
||||||
methods: mapActions(['changeEvent']),
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
49
web/src/views/admin/AccessControl.vue
Normal file
49
web/src/views/admin/AccessControl.vue
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-center">Users</h3>
|
||||||
|
<ul>
|
||||||
|
<Table :items="users" :columns="['username']" :key-name="'id'">
|
||||||
|
<template v-slot:default="{item}">
|
||||||
|
<span>_</span>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</ul>
|
||||||
|
<h3 class="text-center">Groups</h3>
|
||||||
|
<ul>
|
||||||
|
<Table3D :items="groupPermissions" :columns="groups" :rows="permissions" :folded-dimension="events">
|
||||||
|
<template v-slot:default="{item}">
|
||||||
|
<input type="checkbox" v-model="item" @click="console.log(item)"/>
|
||||||
|
</template>
|
||||||
|
</Table3D>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapActions, mapState} from 'vuex';
|
||||||
|
import Table from "@/components/Table.vue";
|
||||||
|
import Table3D from "@/components/Table3D.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AccessControl',
|
||||||
|
components: {Table3D, Table},
|
||||||
|
computed: {
|
||||||
|
...mapState(['users', 'groups', 'events']),
|
||||||
|
permissions() {
|
||||||
|
return ['test']
|
||||||
|
},
|
||||||
|
groupPermissions() {
|
||||||
|
return [[{name: 'test', active: true}], [{name: 'test', active: true}], [{name: 'test', active: true}]]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: mapActions(['loadUsers', 'loadGroups']),
|
||||||
|
mounted() {
|
||||||
|
this.loadUsers();
|
||||||
|
this.loadGroups();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -3,22 +3,23 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-8 offset-xl-2">
|
<div class="col-xl-8 offset-xl-2">
|
||||||
<div class="card bg-dark text-light mb-2" id="filters">
|
<div class="card bg-dark text-light mb-2" id="filters">
|
||||||
<h3 class="text-center">Admin</h3>
|
<div class="card-header">
|
||||||
<ul>
|
<ul class="nav nav-tabs card-header-tabs">
|
||||||
<li>
|
<li class="nav-item">
|
||||||
<router-link :to="{name: 'debug'}">Debug</router-link>
|
<router-link class="nav-link" :to="{name: 'admin'}" active-class="active" exact>Dashboard</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="nav-item">
|
||||||
<router-link :to="{name: 'boxes', params: {event: getEventSlug}}">Boxes</router-link>
|
<router-link class="nav-link" :to="{name: 'events'}" active-class="active">Events</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="nav-item">
|
||||||
<router-link :to="{name: 'events'}">Events</router-link>
|
<router-link class="nav-link" :to="{name: 'users'}" active-class="active">Access Control</router-link>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<router-link :to="{name: 'users'}">Users</router-link>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,9 +27,11 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters} from 'vuex';
|
import {mapGetters} from 'vuex';
|
||||||
|
import Cards from "@/components/Cards.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
|
components: {Cards},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['getEventSlug']),
|
...mapGetters(['getEventSlug']),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-8 offset-xl-2">
|
|
||||||
<Table
|
<Table
|
||||||
:columns="['cid', 'name','itemCount']"
|
:columns="['cid', 'name','itemCount']"
|
||||||
:items="loadedBoxes"
|
:items="loadedBoxes"
|
||||||
|
@ -17,9 +14,6 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
<div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-8 offset-xl-2">
|
|
||||||
<!--qr-code :text="qr_url" color="#000" bg-color="#fff" error-level="H" class="qr-code"></qr-code-->
|
<!--qr-code :text="qr_url" color="#000" bg-color="#fff" error-level="H" class="qr-code"></qr-code-->
|
||||||
<h3 class="text-center">Events</h3>
|
<h3 class="text-center">Events</h3>
|
||||||
<!--p>{{ events }}</p-->
|
<!--p>{{ events }}</p-->
|
||||||
|
@ -46,18 +44,15 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapActions, mapState} from 'vuex';
|
import {mapActions, mapState} from 'vuex';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Events from "@/views/Events.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Debug',
|
name: 'Debug',
|
||||||
components: {Events, Table},
|
components: {Table},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['events', 'loadedItems', 'loadedBoxes', 'mails', 'issues', 'systemEvents']),
|
...mapState(['events', 'loadedItems', 'loadedBoxes', 'mails', 'issues', 'systemEvents']),
|
||||||
qr_url() {
|
qr_url() {
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-8 offset-xl-2">
|
|
||||||
<Table
|
<Table
|
||||||
:columns="['slug', 'name']"
|
:columns="['slug', 'name']"
|
||||||
:items="events"
|
:items="events"
|
||||||
|
@ -19,9 +16,6 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-8 offset-xl-2">
|
|
||||||
<h3 class="text-center">Users</h3>
|
|
||||||
<p>{{ users }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {mapActions, mapState} from 'vuex';
|
|
||||||
import Table from '@/components/Table';
|
|
||||||
import Events from "@/views/Events.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Users',
|
|
||||||
computed: mapState(['users']),
|
|
||||||
methods: mapActions(['loadUsers']),
|
|
||||||
mounted() {
|
|
||||||
this.loadUsers();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
Loading…
Reference in a new issue