This commit is contained in:
j3d1 2023-12-16 22:35:48 +01:00
parent 674106a8a5
commit b8c3bcfa3b
8 changed files with 170 additions and 107 deletions

View file

@ -16,11 +16,12 @@ from authentication.models import ExtendedUser
class UserSerializer(serializers.ModelSerializer):
permissions = serializers.SerializerMethodField()
groups = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name')
class Meta:
model = ExtendedUser
fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions')
read_only_fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions')
fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions', 'groups')
read_only_fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions', 'groups')
def get_permissions(self, obj):
return list(set(obj.get_permissions()))

View file

@ -38,10 +38,13 @@ class UserApiTest(TestCase):
self.assertEqual(response.json()[0]['first_name'], '')
self.assertEqual(response.json()[0]['last_name'], '')
self.assertEqual(response.json()[0]['id'], 1)
self.assertEqual(response.json()[0]['groups'], [])
self.assertEqual(response.json()[1]['username'], 'testuser')
self.assertEqual(response.json()[1]['email'], 'test')
self.assertEqual(response.json()[1]['first_name'], '')
self.assertEqual(response.json()[1]['last_name'], '')
self.assertEqual(response.json()[1]['id'], 2)
self.assertEqual(response.json()[1]['groups'], ['testgroup1', 'testgroup2'])
def test_self_user(self):
response = self.client.get('/api/2/self/')

View file

@ -0,0 +1,45 @@
<template>
<table class="table table-striped table-dark">
<thead>
<tr>
<th scope="col"></th>
<th scope="col" v-for="(column, index) in columns" :key="index">
{{ column }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in rows" :key="i">
<td>{{ row }}</td>
<td v-for="(column, j) in columns" :key="j">
<font-awesome-icon v-if="items[j][i]" icon="check" class="text-success"/>
<font-awesome-icon v-else icon="times" class="text-danger"/>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'Matrix2D',
props: {
items: {
type: Array,
required: true
},
columns: {
type: Array,
required: true
},
rows: {
type: Array,
required: true
}
},
};
</script>
<style>
</style>

View file

@ -0,0 +1,61 @@
<template>
<table class="table table-striped table-dark">
<thead>
<tr>
<th scope="col"></th>
<th scope="col" v-for="(column, index) in columns" :key="index">
{{ column }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in rows" :key="i">
<td>{{ row }}</td>
<td v-for="(column, j) in columns" :key="j">
<font-awesome-icon v-if="triState(items[j][i]) === 'on'" icon="check" class="text-success"/>
<font-awesome-icon v-else-if="triState(items[j][i]) === 'off'" icon="times" class="text-danger"/>
<font-awesome-icon v-else icon="minus" class="text-warning"/>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'Matrix3D',
props: {
items: {
type: Array,
required: true
},
columns: {
type: Array,
required: true
},
rows: {
type: Array,
required: true
},
foldedDimension: {
type: Array,
required: true
}
},
methods: {
triState(list) {
if(list.every(e => e === true)) {
return 'on';
} else if(list.every(e => e === false)) {
return 'off';
} else {
return 'partial';
}
}
},
};
</script>
<style>
</style>

View file

@ -1,72 +0,0 @@
<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>

View file

@ -36,6 +36,7 @@ import {
faUser,
faComments,
faArchive,
faMinus,
} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
@ -43,7 +44,7 @@ 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);
faArchive, faMinus);
Vue.component('font-awesome-icon', FontAwesomeIcon);
//import VueQRCodeComponent from 'vue-qrcode-component'

View file

@ -167,6 +167,9 @@ const store = new Vuex.Store({
if (user)
localStorage.setItem('user', user);
},
setPassword(state, password) {
state.password = password;
},
setPermissions(state, permissions) {
state.userPermissions = permissions;
if (permissions)
@ -186,6 +189,7 @@ const store = new Vuex.Store({
localStorage.removeItem('permissions');
localStorage.removeItem('token');
localStorage.removeItem('token_expiry');
if (router.currentRoute.name !== 'login')
router.push('/login');
},
},
@ -216,6 +220,7 @@ const store = new Vuex.Store({
},
async reloadToken({commit, state}) {
try {
if (data.password) {
const data = await fetch('/api/2/login/', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
@ -227,12 +232,12 @@ const store = new Vuex.Store({
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
return true;
}
}
} catch (e) {
console.error(e);
}
//credentials failed, logout
store.commit('logout');
router.push('/login');
},
async afterLogin({dispatch}) {
const boxes = dispatch('loadBoxes');

View file

@ -1,40 +1,59 @@
<template>
<div>
<h3 class="text-center">Users</h3>
<ul>
<Table :items="users" :columns="['username']" :key-name="'id'">
<template v-slot:default="{item}">
<span>_</span>
<span>
{{item.groups.join(', ')}}
</span>
</template>
</Table>
</ul>
<h3 class="text-center">Groups</h3>
<ul>
<Table3D :items="groupPermissions" :columns="groups" :rows="permissions" :folded-dimension="events">
<Table :items="groups" :columns="['name']" :key-name="'id'">
<template v-slot:default="{item}">
<input type="checkbox" v-model="item" @click="console.log(item)"/>
<span>
{{item.members.join(', ')}}
</span>
</template>
</Table3D>
</ul>
</Table>
<Matrix2D :items="groupPermissions" :columns="groupNames" :rows="permissionNames"/>
<h3 class="text-center">Permissions</h3>
<Matrix3D :items="userPermissions" :columns="userNames" :rows="permissionNames"
:folded-dimension="eventsNames"/>
</div>
</template>
<script>
import {mapActions, mapState} from 'vuex';
import Table from "@/components/Table.vue";
import Table3D from "@/components/Table3D.vue";
import Matrix3D from "@/components/Matrix3D.vue";
import Matrix2D from "@/components/Matrix2D.vue";
export default {
name: 'AccessControl',
components: {Table3D, Table},
components: {Matrix3D, Matrix2D, Table},
computed: {
...mapState(['users', 'groups', 'events']),
permissions() {
return ['test']
permissionNames() {
return this.groups.map(g => g.permissions).flat().map(p => p.split(":")[1])
},
groupNames() {
return this.groups.map(g => g.name)
},
eventsNames() {
return this.events.map(e => e.slug)
},
userNames() {
return this.users.map(u => u.username)
},
groupPermissions() {
return [[{name: 'test', active: true}], [{name: 'test', active: true}], [{name: 'test', active: true}]]
}
return this.groups.map(g => this.permissionNames.map(p => g.permissions.includes("*:" + p)))
},
userPermissions() {
return this.users.map(u => this.permissionNames.map(p => this.events.map(e =>
u.permissions.includes("*:" + p) || u.permissions.includes(e.slug + ":" + p)
)))
},
},
methods: mapActions(['loadUsers', 'loadGroups']),
mounted() {