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):
|
||||
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()))
|
||||
|
|
|
@ -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/')
|
||||
|
|
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,
|
||||
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'
|
||||
|
|
|
@ -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,7 +189,8 @@ const store = new Vuex.Store({
|
|||
localStorage.removeItem('permissions');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('token_expiry');
|
||||
router.push('/login');
|
||||
if (router.currentRoute.name !== 'login')
|
||||
router.push('/login');
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
|
@ -216,23 +220,24 @@ const store = new Vuex.Store({
|
|||
},
|
||||
async reloadToken({commit, state}) {
|
||||
try {
|
||||
const data = await fetch('/api/2/login/', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: state.user, password: state.password}),
|
||||
credentials: 'omit'
|
||||
}).then(r => r.json())
|
||||
if (data.token) {
|
||||
commit('setToken', data);
|
||||
axios.defaults.headers.common['Authorization'] = `Token ${data.token}`;
|
||||
return true;
|
||||
if (data.password) {
|
||||
const data = await fetch('/api/2/login/', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: state.user, password: state.password}),
|
||||
credentials: 'omit'
|
||||
}).then(r => r.json())
|
||||
if (data.token) {
|
||||
commit('setToken', data);
|
||||
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');
|
||||
|
|
|
@ -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>
|
||||
</template>
|
||||
</Table>
|
||||
</ul>
|
||||
<Table :items="users" :columns="['username']" :key-name="'id'">
|
||||
<template v-slot:default="{item}">
|
||||
<span>
|
||||
{{item.groups.join(', ')}}
|
||||
</span>
|
||||
</template>
|
||||
</Table>
|
||||
<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>
|
||||
<Table :items="groups" :columns="['name']" :key-name="'id'">
|
||||
<template v-slot:default="{item}">
|
||||
<span>
|
||||
{{item.members.join(', ')}}
|
||||
</span>
|
||||
</template>
|
||||
</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() {
|
||||
|
|
Loading…
Reference in a new issue