stash
This commit is contained in:
parent
674106a8a5
commit
b8c3bcfa3b
8 changed files with 170 additions and 107 deletions
|
@ -16,11 +16,12 @@ from authentication.models import ExtendedUser
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
permissions = serializers.SerializerMethodField()
|
permissions = serializers.SerializerMethodField()
|
||||||
|
groups = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExtendedUser
|
model = ExtendedUser
|
||||||
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')
|
read_only_fields = ('id', 'username', 'email', 'first_name', 'last_name', 'permissions', 'groups')
|
||||||
|
|
||||||
def get_permissions(self, obj):
|
def get_permissions(self, obj):
|
||||||
return list(set(obj.get_permissions()))
|
return list(set(obj.get_permissions()))
|
||||||
|
|
|
@ -38,10 +38,13 @@ class UserApiTest(TestCase):
|
||||||
self.assertEqual(response.json()[0]['first_name'], '')
|
self.assertEqual(response.json()[0]['first_name'], '')
|
||||||
self.assertEqual(response.json()[0]['last_name'], '')
|
self.assertEqual(response.json()[0]['last_name'], '')
|
||||||
self.assertEqual(response.json()[0]['id'], 1)
|
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]['username'], 'testuser')
|
||||||
self.assertEqual(response.json()[1]['email'], 'test')
|
self.assertEqual(response.json()[1]['email'], 'test')
|
||||||
self.assertEqual(response.json()[1]['first_name'], '')
|
self.assertEqual(response.json()[1]['first_name'], '')
|
||||||
self.assertEqual(response.json()[1]['last_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):
|
def test_self_user(self):
|
||||||
response = self.client.get('/api/2/self/')
|
response = self.client.get('/api/2/self/')
|
||||||
|
|
45
web/src/components/Matrix2D.vue
Normal file
45
web/src/components/Matrix2D.vue
Normal 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>
|
61
web/src/components/Matrix3D.vue
Normal file
61
web/src/components/Matrix3D.vue
Normal 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>
|
|
@ -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>
|
|
|
@ -36,6 +36,7 @@ import {
|
||||||
faUser,
|
faUser,
|
||||||
faComments,
|
faComments,
|
||||||
faArchive,
|
faArchive,
|
||||||
|
faMinus,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
|
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,
|
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);
|
faArchive, faMinus);
|
||||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||||
|
|
||||||
//import VueQRCodeComponent from 'vue-qrcode-component'
|
//import VueQRCodeComponent from 'vue-qrcode-component'
|
||||||
|
|
|
@ -167,6 +167,9 @@ const store = new Vuex.Store({
|
||||||
if (user)
|
if (user)
|
||||||
localStorage.setItem('user', user);
|
localStorage.setItem('user', user);
|
||||||
},
|
},
|
||||||
|
setPassword(state, password) {
|
||||||
|
state.password = password;
|
||||||
|
},
|
||||||
setPermissions(state, permissions) {
|
setPermissions(state, permissions) {
|
||||||
state.userPermissions = permissions;
|
state.userPermissions = permissions;
|
||||||
if (permissions)
|
if (permissions)
|
||||||
|
@ -186,7 +189,8 @@ const store = new Vuex.Store({
|
||||||
localStorage.removeItem('permissions');
|
localStorage.removeItem('permissions');
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
localStorage.removeItem('token_expiry');
|
localStorage.removeItem('token_expiry');
|
||||||
router.push('/login');
|
if (router.currentRoute.name !== 'login')
|
||||||
|
router.push('/login');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -216,23 +220,24 @@ const store = new Vuex.Store({
|
||||||
},
|
},
|
||||||
async reloadToken({commit, state}) {
|
async reloadToken({commit, state}) {
|
||||||
try {
|
try {
|
||||||
const data = await fetch('/api/2/login/', {
|
if (data.password) {
|
||||||
method: 'POST',
|
const data = await fetch('/api/2/login/', {
|
||||||
headers: {'Content-Type': 'application/json'},
|
method: 'POST',
|
||||||
body: JSON.stringify({username: state.user, password: state.password}),
|
headers: {'Content-Type': 'application/json'},
|
||||||
credentials: 'omit'
|
body: JSON.stringify({username: state.user, password: state.password}),
|
||||||
}).then(r => r.json())
|
credentials: 'omit'
|
||||||
if (data.token) {
|
}).then(r => r.json())
|
||||||
commit('setToken', data);
|
if (data.token) {
|
||||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
commit('setToken', data);
|
||||||
return true;
|
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
//credentials failed, logout
|
//credentials failed, logout
|
||||||
store.commit('logout');
|
store.commit('logout');
|
||||||
router.push('/login');
|
|
||||||
},
|
},
|
||||||
async afterLogin({dispatch}) {
|
async afterLogin({dispatch}) {
|
||||||
const boxes = dispatch('loadBoxes');
|
const boxes = dispatch('loadBoxes');
|
||||||
|
|
|
@ -1,40 +1,59 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-center">Users</h3>
|
<h3 class="text-center">Users</h3>
|
||||||
<ul>
|
<Table :items="users" :columns="['username']" :key-name="'id'">
|
||||||
<Table :items="users" :columns="['username']" :key-name="'id'">
|
<template v-slot:default="{item}">
|
||||||
<template v-slot:default="{item}">
|
<span>
|
||||||
<span>_</span>
|
{{item.groups.join(', ')}}
|
||||||
</template>
|
</span>
|
||||||
</Table>
|
</template>
|
||||||
</ul>
|
</Table>
|
||||||
<h3 class="text-center">Groups</h3>
|
<h3 class="text-center">Groups</h3>
|
||||||
<ul>
|
<Table :items="groups" :columns="['name']" :key-name="'id'">
|
||||||
<Table3D :items="groupPermissions" :columns="groups" :rows="permissions" :folded-dimension="events">
|
<template v-slot:default="{item}">
|
||||||
<template v-slot:default="{item}">
|
<span>
|
||||||
<input type="checkbox" v-model="item" @click="console.log(item)"/>
|
{{item.members.join(', ')}}
|
||||||
</template>
|
</span>
|
||||||
</Table3D>
|
</template>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapActions, mapState} from 'vuex';
|
import {mapActions, mapState} from 'vuex';
|
||||||
import Table from "@/components/Table.vue";
|
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 {
|
export default {
|
||||||
name: 'AccessControl',
|
name: 'AccessControl',
|
||||||
components: {Table3D, Table},
|
components: {Matrix3D, Matrix2D, Table},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['users', 'groups', 'events']),
|
...mapState(['users', 'groups', 'events']),
|
||||||
permissions() {
|
permissionNames() {
|
||||||
return ['test']
|
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() {
|
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']),
|
methods: mapActions(['loadUsers', 'loadGroups']),
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
Loading…
Reference in a new issue