mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
refactor: rework async GroupList
This commit is contained in:
parent
3e44d959eb
commit
fe380005a5
1 changed files with 110 additions and 134 deletions
|
@ -1,165 +1,151 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import TagsSelectizeItem from '@/components/globals/formItems/TagsSelectizeItem.vue'
|
import TagsSelectizeItem from '@/components/globals/formItems/TagsSelectizeItem.vue'
|
||||||
import { useAutoModal } from '@/composables/useAutoModal'
|
import { useAutoModal } from '@/composables/useAutoModal'
|
||||||
import { useInitialQueries } from '@/composables/useInitialQueries'
|
|
||||||
import { useSearch } from '@/composables/useSearch'
|
import { useSearch } from '@/composables/useSearch'
|
||||||
import { isEmptyValue } from '@/helpers/commons'
|
import { toEntries } from '@/helpers/commons'
|
||||||
import type { Obj } from '@/types/commons'
|
import type { Obj } from '@/types/commons'
|
||||||
|
import type { Group, Permission, UserItem } from '@/types/core/data'
|
||||||
|
import type { TagUpdateArgs } from '@/types/form'
|
||||||
|
|
||||||
// TODO add global search with type (search by: group, user, permission)
|
// TODO add global search with type (search by: group, user, permission)
|
||||||
// TODO add vuex store update on inputs ?
|
// TODO add vuex store update on inputs ?
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const modalConfirm = useAutoModal()
|
const modalConfirm = useAutoModal()
|
||||||
const { loading } = useInitialQueries(
|
|
||||||
[
|
const {
|
||||||
|
primaryGroups,
|
||||||
|
userGroups,
|
||||||
|
permissionOptions,
|
||||||
|
userOptions,
|
||||||
|
activeUserGroups,
|
||||||
|
} = await api
|
||||||
|
.fetchAll<[Obj<UserItem>, Obj<Group>, Obj<Permission>]>([
|
||||||
{ uri: 'users', cachePath: 'users' },
|
{ uri: 'users', cachePath: 'users' },
|
||||||
{
|
{
|
||||||
uri: 'users/groups?full&include_primary_groups',
|
uri: 'users/groups?full&include_primary_groups',
|
||||||
cachePath: 'groups',
|
cachePath: 'groups',
|
||||||
},
|
},
|
||||||
{ uri: 'users/permissions?full', cachePath: 'permissions' },
|
{ uri: 'users/permissions?full', cachePath: 'permissions' },
|
||||||
],
|
])
|
||||||
{ onQueriesResponse },
|
.then(([users, groups, permsDict]) => {
|
||||||
)
|
type DGroup = Group & {
|
||||||
|
members: string[]
|
||||||
|
name: string
|
||||||
|
isSpecial?: boolean
|
||||||
|
disabledItems?: string[]
|
||||||
|
}
|
||||||
|
const permIds = Object.keys(permsDict)
|
||||||
|
const userNames = users ? Object.keys(users) : []
|
||||||
|
const specialGroupsFilters = {
|
||||||
|
visitors: (id: string) => permsDict[id].protected,
|
||||||
|
all_users: (id: string) => ['ssh.main', 'sftp.main'].includes(id),
|
||||||
|
admins: (id: string) => ['ssh.main', 'sftp.main'].includes(id),
|
||||||
|
}
|
||||||
|
|
||||||
const permissions = ref()
|
function isSpecialGroup(
|
||||||
const permissionsOptions = ref()
|
name: string,
|
||||||
const primaryGroups = ref<Obj[] | undefined>()
|
): name is 'visitors' | 'all_users' | 'admins' {
|
||||||
const userGroups = ref()
|
return ['visitors', 'all_users', 'admins'].includes(name)
|
||||||
const usersOptions = ref()
|
}
|
||||||
const activeUserGroups = ref()
|
|
||||||
|
const { primaryGroups, userGroups } = toEntries(groups).reduce(
|
||||||
|
(g, [name, data]) => {
|
||||||
|
const group: DGroup = {
|
||||||
|
name,
|
||||||
|
// Clone data to avoid mutating the cache
|
||||||
|
members: [...data.members],
|
||||||
|
permissions: [...data.permissions],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userNames.includes(name)) {
|
||||||
|
g.userGroups[name] = group
|
||||||
|
} else {
|
||||||
|
if (isSpecialGroup(name)) {
|
||||||
|
group.isSpecial = true
|
||||||
|
// Forbid to add or remove a protected permission on group `visitors`
|
||||||
|
// Forbid to add ssh and sftp permission on group `all_users` and `admins`
|
||||||
|
group.disabledItems = permIds.filter(specialGroupsFilters[name])
|
||||||
|
}
|
||||||
|
g.primaryGroups.push(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g
|
||||||
|
},
|
||||||
|
{ primaryGroups: [] as DGroup[], userGroups: {} as Obj<DGroup> },
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
primaryGroups: ref(primaryGroups),
|
||||||
|
userGroups: reactive(userGroups),
|
||||||
|
permissionOptions: permIds.map((id) => ({
|
||||||
|
value: id,
|
||||||
|
text: permsDict[id].label,
|
||||||
|
})),
|
||||||
|
userOptions: userNames,
|
||||||
|
activeUserGroups: ref(
|
||||||
|
Object.values(userGroups)
|
||||||
|
.filter((group) => group.permissions.length > 0)
|
||||||
|
.map((group) => group.name),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const [search, filteredGroups] = useSearch(primaryGroups, (s, group) => {
|
const [search, filteredGroups] = useSearch(primaryGroups, (s, group) => {
|
||||||
return group.name.toLowerCase().includes(s)
|
return group.name.toLowerCase().includes(s)
|
||||||
})
|
})
|
||||||
|
|
||||||
function onQueriesResponse(users: any, allGroups: any, permsDict: any) {
|
async function onPermissionChanged(
|
||||||
// Do not use computed properties to get values from the store here to avoid auto
|
{ tag: perm, action, applyFn }: TagUpdateArgs,
|
||||||
// updates while modifying values.
|
name: string,
|
||||||
const permissions_ = Object.entries(permsDict).map(([id, value]) => ({
|
) {
|
||||||
id,
|
if (action === 'add' && ['sftp.main', 'ssh.main'].includes(perm)) {
|
||||||
...value,
|
|
||||||
}))
|
|
||||||
const userNames = users ? Object.keys(users) : []
|
|
||||||
const primaryGroups_ = []
|
|
||||||
const userGroups_ = {}
|
|
||||||
|
|
||||||
for (const groupName in allGroups) {
|
|
||||||
// copy the group to unlink it from the store
|
|
||||||
const group_ = { ...allGroups[groupName], name: groupName }
|
|
||||||
group_.permissions = group_.permissions.map((perm) => {
|
|
||||||
return permsDict[perm].label
|
|
||||||
})
|
|
||||||
|
|
||||||
if (userNames.includes(groupName)) {
|
|
||||||
userGroups_[groupName] = group_
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
group_.isSpecial = ['visitors', 'all_users', 'admins'].includes(groupName)
|
|
||||||
|
|
||||||
if (groupName === 'visitors') {
|
|
||||||
// Forbid to add or remove a protected permission on group `visitors`
|
|
||||||
group_.disabledItems = permissions_
|
|
||||||
.filter(({ id }) => {
|
|
||||||
return (
|
|
||||||
['mail.main', 'xmpp.main'].includes(id) || permsDict[id].protected
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(({ id }) => permsDict[id].label)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupName === 'all_users') {
|
|
||||||
// Forbid to add ssh and sftp permission on group `all_users`
|
|
||||||
group_.disabledItems = permissions_
|
|
||||||
.filter(({ id }) => {
|
|
||||||
return ['ssh.main', 'sftp.main'].includes(id)
|
|
||||||
})
|
|
||||||
.map(({ id }) => permsDict[id].label)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupName === 'admins') {
|
|
||||||
// Forbid to add ssh and sftp permission on group `admins`
|
|
||||||
group_.disabledItems = permissions_
|
|
||||||
.filter(({ id }) => {
|
|
||||||
return ['ssh.main', 'sftp.main'].includes(id)
|
|
||||||
})
|
|
||||||
.map(({ id }) => permsDict[id].label)
|
|
||||||
}
|
|
||||||
|
|
||||||
primaryGroups_.push(group_)
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeUserGroups_ = Object.entries(userGroups_)
|
|
||||||
.filter(([_, group]) => {
|
|
||||||
return group.permissions.length > 0
|
|
||||||
})
|
|
||||||
.map(([name]) => name)
|
|
||||||
|
|
||||||
permissions.value = permissions_
|
|
||||||
permissionsOptions.value = permissions_.map((perm) => perm.label)
|
|
||||||
primaryGroups.value = primaryGroups_
|
|
||||||
userGroups.value = isEmptyValue(userGroups_) ? null : userGroups_
|
|
||||||
usersOptions.value = userNames
|
|
||||||
activeUserGroups.value = activeUserGroups_
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onPermissionChanged({ option, groupName, action, applyMethod }) {
|
|
||||||
const permId = permissions.value.find((perm) => perm.label === option).id
|
|
||||||
if (action === 'add' && ['sftp.main', 'ssh.main'].includes(permId)) {
|
|
||||||
const confirmed = await modalConfirm(
|
const confirmed = await modalConfirm(
|
||||||
t('confirm_group_add_access_permission', {
|
t('confirm_group_add_access_permission', { name, perm }),
|
||||||
name: groupName,
|
|
||||||
perm: option,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
}
|
}
|
||||||
|
|
||||||
api
|
api
|
||||||
.put({
|
.put({
|
||||||
uri: `users/permissions/${permId}/${action}/${groupName}`,
|
uri: `users/permissions/${perm}/${action}/${name}`,
|
||||||
cachePath: `permissions.${permId}`,
|
cachePath: `permissions.${perm}`,
|
||||||
humanKey: { key: 'permissions.' + action, perm: option, name: groupName },
|
humanKey: { key: `permissions.${action}`, perm, name },
|
||||||
})
|
})
|
||||||
.then(() => applyMethod(option))
|
.then(() => applyFn(perm))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUserChanged({ option, groupName, action, applyMethod }) {
|
function onUserChanged(
|
||||||
|
{ tag: user, action, applyFn }: TagUpdateArgs,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
api
|
api
|
||||||
.put({
|
.put({
|
||||||
uri: `users/groups/${groupName}/${action}/${option}`,
|
uri: `users/groups/${name}/${action}/${user}`,
|
||||||
cachePath: `groups.${groupName}`,
|
cachePath: `groups.${name}`,
|
||||||
humanKey: { key: 'groups.' + action, user: option, name: groupName },
|
humanKey: { key: `groups.${action}`, user, name },
|
||||||
})
|
})
|
||||||
.then(() => applyMethod(option))
|
.then(() => applyFn(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSpecificUserAdded({ option: userName, action, applyMethod }) {
|
async function deleteGroup(name: string) {
|
||||||
if (action === 'add') {
|
const confirmed = await modalConfirm(t('confirm_delete', { name }))
|
||||||
userGroups.value[userName].permissions = []
|
|
||||||
applyMethod(userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteGroup(groupName) {
|
|
||||||
const confirmed = await modalConfirm(t('confirm_delete', { name: groupName }))
|
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
|
|
||||||
api
|
api
|
||||||
.delete({
|
.delete({
|
||||||
uri: `users/groups/${groupName}`,
|
uri: `users/groups/${name}`,
|
||||||
cachePath: `groups.${groupName}`,
|
cachePath: `groups.${name}`,
|
||||||
humanKey: { key: 'groups.delete', name: groupName },
|
humanKey: { key: 'groups.delete', name },
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
primaryGroups.value = primaryGroups.value?.filter(
|
// FIXME primaryGroups as ref to override it ?
|
||||||
(group) => group.name !== groupName,
|
primaryGroups.value = primaryGroups.value.filter(
|
||||||
|
(group) => group.name !== name,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -170,7 +156,6 @@ async function deleteGroup(groupName) {
|
||||||
v-model="search"
|
v-model="search"
|
||||||
:items="filteredGroups"
|
:items="filteredGroups"
|
||||||
items-name="groups"
|
items-name="groups"
|
||||||
:loading="loading"
|
|
||||||
skeleton="CardFormSkeleton"
|
skeleton="CardFormSkeleton"
|
||||||
>
|
>
|
||||||
<template #top-bar-buttons>
|
<template #top-bar-buttons>
|
||||||
|
@ -222,12 +207,12 @@ async function deleteGroup(groupName) {
|
||||||
<template v-if="group.name == 'admins' || !group.isSpecial">
|
<template v-if="group.name == 'admins' || !group.isSpecial">
|
||||||
<TagsSelectizeItem
|
<TagsSelectizeItem
|
||||||
v-model="group.members"
|
v-model="group.members"
|
||||||
:options="usersOptions"
|
:options="userOptions"
|
||||||
:id="group.name + '-users'"
|
:id="group.name + '-users'"
|
||||||
:label="$t('group_add_member')"
|
:label="$t('group_add_member')"
|
||||||
tag-icon="user"
|
tag-icon="user"
|
||||||
items-name="users"
|
items-name="users"
|
||||||
@tag-update="onUserChanged({ ...$event, groupName: group.name })"
|
@tag-update="onUserChanged($event, group.name)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</BCol>
|
</BCol>
|
||||||
|
@ -241,27 +226,20 @@ async function deleteGroup(groupName) {
|
||||||
<BCol>
|
<BCol>
|
||||||
<TagsSelectizeItem
|
<TagsSelectizeItem
|
||||||
v-model="group.permissions"
|
v-model="group.permissions"
|
||||||
:options="permissionsOptions"
|
:options="permissionOptions"
|
||||||
:id="group.name + '-perms'"
|
:id="group.name + '-perms'"
|
||||||
:label="$t('group_add_permission')"
|
:label="$t('group_add_permission')"
|
||||||
tag-icon="key-modern"
|
tag-icon="key-modern"
|
||||||
items-name="permissions"
|
items-name="permissions"
|
||||||
@tag-update="
|
|
||||||
onPermissionChanged({ ...$event, groupName: group.name })
|
|
||||||
"
|
|
||||||
:disabled-items="group.disabledItems"
|
:disabled-items="group.disabledItems"
|
||||||
|
@tag-update="onPermissionChanged($event, group.name)"
|
||||||
/>
|
/>
|
||||||
</BCol>
|
</BCol>
|
||||||
</BRow>
|
</BRow>
|
||||||
</YCard>
|
</YCard>
|
||||||
|
|
||||||
<!-- USER GROUPS CARD -->
|
<!-- USER GROUPS CARD -->
|
||||||
<YCard
|
<YCard collapsable :title="$t('group_specific_permissions')" icon="group">
|
||||||
v-if="userGroups"
|
|
||||||
collapsable
|
|
||||||
:title="$t('group_specific_permissions')"
|
|
||||||
icon="group"
|
|
||||||
>
|
|
||||||
<template v-for="userName in activeUserGroups" :key="userName">
|
<template v-for="userName in activeUserGroups" :key="userName">
|
||||||
<BRow>
|
<BRow>
|
||||||
<BCol md="3" lg="2">
|
<BCol md="3" lg="2">
|
||||||
|
@ -271,14 +249,12 @@ async function deleteGroup(groupName) {
|
||||||
<BCol>
|
<BCol>
|
||||||
<TagsSelectizeItem
|
<TagsSelectizeItem
|
||||||
v-model="userGroups[userName].permissions"
|
v-model="userGroups[userName].permissions"
|
||||||
:options="permissionsOptions"
|
:options="permissionOptions"
|
||||||
:id="userName + '-perms'"
|
:id="userName + '-perms'"
|
||||||
:label="$t('group_add_permission')"
|
:label="$t('group_add_permission')"
|
||||||
tag-icon="key-modern"
|
tag-icon="key-modern"
|
||||||
items-name="permissions"
|
items-name="permissions"
|
||||||
@tag-update="
|
@tag-update="onPermissionChanged($event, userName)"
|
||||||
onPermissionChanged({ ...$event, groupName: userName })
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</BCol>
|
</BCol>
|
||||||
</BRow>
|
</BRow>
|
||||||
|
@ -287,12 +263,12 @@ async function deleteGroup(groupName) {
|
||||||
|
|
||||||
<TagsSelectizeItem
|
<TagsSelectizeItem
|
||||||
v-model="activeUserGroups"
|
v-model="activeUserGroups"
|
||||||
:options="usersOptions"
|
auto
|
||||||
|
:options="userOptions"
|
||||||
id="user-groups"
|
id="user-groups"
|
||||||
:label="$t('group_add_member')"
|
:label="$t('group_add_member')"
|
||||||
no-tags
|
no-tags
|
||||||
items-name="users"
|
items-name="users"
|
||||||
@tag-update="onSpecificUserAdded"
|
|
||||||
/>
|
/>
|
||||||
</YCard>
|
</YCard>
|
||||||
</ViewSearch>
|
</ViewSearch>
|
||||||
|
|
Loading…
Reference in a new issue