Initial work to move users view to vue
This commit is contained in:
@@ -14,6 +14,7 @@ import ServicesView from './views/ServicesView.vue';
|
||||
import SettingsView from './views/SettingsView.vue';
|
||||
import SupportView from './views/SupportView.vue';
|
||||
import UserDirectoryView from './views/UserDirectoryView.vue';
|
||||
import UsersView from './views/UsersView.vue';
|
||||
import VolumesView from './views/VolumesView.vue';
|
||||
|
||||
const VIEWS = {
|
||||
@@ -29,6 +30,7 @@ const VIEWS = {
|
||||
SETTINGS: 'settings',
|
||||
SUPPORT: 'support',
|
||||
USER_DIRECTORY: 'user-directory',
|
||||
USERS: 'users',
|
||||
VOLUMES: 'volumes',
|
||||
};
|
||||
|
||||
@@ -59,8 +61,10 @@ function onHashChange() {
|
||||
view.value = VIEWS.SETTINGS;
|
||||
} else if (v === VIEWS.SUPPORT) {
|
||||
view.value = VIEWS.SUPPORT;
|
||||
} else if (v === VIEWS.USER_DIRECTORY) {
|
||||
} else if (v === VIEWS.USER_DIRECTORY) {
|
||||
view.value = VIEWS.USER_DIRECTORY;
|
||||
} else if (v === VIEWS.USERS) {
|
||||
view.value = VIEWS.USERS;
|
||||
} else if (v === VIEWS.VOLUMES) {
|
||||
view.value = VIEWS.VOLUMES;
|
||||
} else {
|
||||
@@ -99,6 +103,7 @@ onMounted(async () => {
|
||||
<SettingsView v-else-if="view === VIEWS.SETTINGS" />
|
||||
<SupportView v-else-if="view === VIEWS.SUPPORT" />
|
||||
<UserDirectoryView v-else-if="view === VIEWS.USER_DIRECTORY" />
|
||||
<UsersView v-else-if="view === VIEWS.USERS" />
|
||||
<VolumesView v-else-if="view === VIEWS.VOLUMES" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
188
dashboard/src/views/UsersView.vue
Normal file
188
dashboard/src/views/UsersView.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script setup>
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Button, ButtonGroup, TextInput, Dropdown, TableView } from 'pankow';
|
||||
import { ROLES } from '../constants.js';
|
||||
import Section from '../components/Section.vue';
|
||||
import UsersModel from '../models/UsersModel.js';
|
||||
import GroupsModel from '../models/GroupsModel.js';
|
||||
import ProfileModel from '../models/ProfileModel.js';
|
||||
|
||||
const usersModel = UsersModel.create();
|
||||
const groupsModel = GroupsModel.create();
|
||||
const profileModel = ProfileModel.create();
|
||||
|
||||
|
||||
const usersColumns = {
|
||||
role: { width: '33.5px' },
|
||||
user: { label: t('users.users.user'), sort: true },
|
||||
groups: { label: t('users.users.groups'), sort: true },
|
||||
actions: {}
|
||||
};
|
||||
|
||||
const groupsColumns = {
|
||||
name: { label: t('users.groups.name'), sort: true },
|
||||
users: { label: t('users.groups.users'), sort: true },
|
||||
actions: {}
|
||||
};
|
||||
|
||||
// TODO maybe replace with app.provide and inject
|
||||
const profile = ref({});
|
||||
const filterOptions = ref([
|
||||
{ id: 'all', name: 'All Users' },
|
||||
{ id: 'active', name: 'Active Users' },
|
||||
{ id: 'inactive', name: 'Inactive Users' }
|
||||
]);
|
||||
const users = ref([]);
|
||||
const usersById = ref({});
|
||||
const search = ref('');
|
||||
const filter = ref(filterOptions.value[0].id);
|
||||
const groups = ref([]);
|
||||
const groupsById = ref({});
|
||||
const roles = ref([]);
|
||||
|
||||
async function refreshUsers() {
|
||||
const [error, result] = await usersModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
usersById.value = {};
|
||||
users.value = result;
|
||||
result.forEach(user => {
|
||||
usersById.value[user.id] = user;
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshGroups() {
|
||||
const [error, result] = await groupsModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
groupsById.value = {};
|
||||
groups.value = result;
|
||||
result.forEach(group => {
|
||||
groupsById.value[group.id] = group;
|
||||
});
|
||||
}
|
||||
|
||||
function canEdit(user) {
|
||||
const roleInt1 = roles.value.findIndex(function (role) { return role.id === profile.value.role; });
|
||||
const roleInt2 = roles.value.findIndex(function (role) { return role.id === user.role; });
|
||||
|
||||
return (roleInt1 - roleInt2) >= 0;
|
||||
}
|
||||
|
||||
function canImpersonate(user) {
|
||||
// only admins can impersonate
|
||||
if (!profile.value.isAtLeastAdmin) return false;
|
||||
|
||||
// only users with username can be impersonated
|
||||
if (!user.username) return false;
|
||||
|
||||
// normal admins cannot impersonate owners
|
||||
if (!profile.value.isAtLeastOwner && [ ROLES.OWNER ].indexOf(user.role) !== -1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMe(user) {
|
||||
return user.username === profile.value.username;
|
||||
}
|
||||
|
||||
function groupMembers(group) {
|
||||
return group.userIds.filter(function (uid) { return !!usersById.value[uid]; }).map(function (uid) { return usersById.value[uid].username || usersById.value[uid].email; }).join(' ');
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const [error, result] = await profileModel.get();
|
||||
if (error) return console.error(error);
|
||||
|
||||
profile.value = result;
|
||||
|
||||
await refreshUsers();
|
||||
await refreshGroups();
|
||||
|
||||
// Order matters for permissions used in canEdit
|
||||
roles.value = [
|
||||
{ id: 'user', name: t('users.role.user'), disabled: false },
|
||||
{ id: 'usermanager', name: t('users.role.usermanager'), disabled: false },
|
||||
{ id: 'mailmanager', name: t('users.role.mailmanager'), disabled: false },
|
||||
{ id: 'admin', name: t('users.role.admin'), disabled: !profile.value.isAtLeastAdmin },
|
||||
{ id: 'owner', name: t('users.role.owner'), disabled: !profile.value.isAtLeastOwner }
|
||||
];
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<Section :title="$t('main.navbar.users')">
|
||||
<template #header-buttons>
|
||||
<TextInput v-model="search" placeholder="Search ..." />
|
||||
<Dropdown outline tool :options="filterOptions" option-key="id" option-label="name" v-model="filter"></Dropdown>
|
||||
<Button icon="fa-solid fa-user-plus">{{ $t('users.newUserAction') }}</Button>
|
||||
</template>
|
||||
|
||||
<TableView :columns="usersColumns" :model="users" style="max-height: 500px;">
|
||||
<template #role="user">
|
||||
<i class="fas fa-crown arrow" v-if="user.active && user.role === 'owner'" v-tooltip="$t('users.users.superadminTooltip')"></i>
|
||||
<i class="fa fa-user-tie arrow" v-if="user.active && user.role === 'admin'" v-tooltip="$t('users.users.adminTooltip')"></i>
|
||||
<i class="fas fa-users-cog arrow" v-if="user.active && user.role === 'usermanager'" v-tooltip="$t('users.users.usermanagerTooltip')"></i>
|
||||
<i class="fas fa-mail-bulk arrow" v-if="user.active && user.role === 'mailmanager'" v-tooltip="$t('users.users.mailmanagerTooltip')"></i>
|
||||
<i class="fa fa-ban" v-if="!user.active" v-tooltip="$t('users.users.inactiveTooltip')"></i>
|
||||
</template>
|
||||
<template #user="user">
|
||||
{{ user.displayName }} <span class="text-muted">{{ user.username }}</span> <i v-show="user.source" class="far fa-address-book" v-tooltip="$t('users.users.externalLdapTooltip')"></i>
|
||||
</template>
|
||||
<template #groups="user">
|
||||
<span class="group-label" v-for="groupId in user.groupIds" :key="groupId">
|
||||
{{ groupsById[groupId] ? groupsById[groupId].name : groupId }}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions="user">
|
||||
<div class="table-actions">
|
||||
<ButtonGroup>
|
||||
<Button small tool secondary :disabled="!canEdit(user)" v-if="!user.inviteAccepted && !isMe(user) && !user.source" @click="invitation.show(user)" v-tooltip="$t('users.users.invitationTooltip')" icon="fa-solid fa-paper-plane" />
|
||||
<Button small tool secondary :disabled="!canEdit(user)" v-if="user.inviteAccepted && !user.source" @click="passwordReset.show(user)" v-tooltip="$t('users.users.resetPasswordTooltip')" icon="fa-solid fa-key" />
|
||||
<Button small tool secondary :disabled="!canImpersonate(user)" @click="setGhost.show(user)" v-tooltip="$t('users.users.setGhostTooltip')" icon="fa-solid fa-user-secret" />
|
||||
<Button small tool secondary :disabled="!canEdit(user)" @click="userEdit.show(user)" v-tooltip="$t('users.users.editUserTooltip')" icon="fa fa-pencil-alt" />
|
||||
<Button small tool danger :disabled="!canEdit(user) || isMe(user)" @click="userRemove.show(user)" v-tooltip="$t('users.users.removeUserTooltip')" icon="far fa-trash-alt" />
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
<div>{{ $t('users.users.count', { count: users.length }) }}</div>
|
||||
</Section>
|
||||
|
||||
<Section :title="$t('users.groups.title')">
|
||||
<template #header-buttons>
|
||||
<Button icon="fa-solid fa-plus">{{ $t('users.groups.newGroupAction') }}</Button>
|
||||
</template>
|
||||
|
||||
<TableView :columns="groupsColumns" :model="groups" style="max-height: 500px;">
|
||||
<template #name="group">
|
||||
{{ group.name }} <i ng-show="group.source" class="far fa-address-book" uib-tooltip="{{ 'users.groups.externalLdapTooltip' | tr }}"></i>
|
||||
</template>
|
||||
<template #users="group">
|
||||
{{ groupMembers(group) }}
|
||||
</template>
|
||||
<template #actions="group">
|
||||
<div class="table-actions">
|
||||
<Button tool small secondary @click="groupEdit.show(group)" v-tooltip="'Edit Group'" icon="fa fa-pencil-alt" />
|
||||
<Button tool small danger @click="groupRemove.show(group)" v-tooltip="'Remove Group'" icon="far fa-trash-alt" />
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.group-label {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user