mirror of https://github.com/nextcloud/server.git
feat(settings): Split account management into navigation and content
The should ease the maintenance of it due to reduced complexity. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/44092/head
parent
1100e908b7
commit
30d5b02811
@ -0,0 +1,52 @@
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { IGroup } from '../views/user-types'
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
||||
/**
|
||||
* Format a group to a menu entry
|
||||
*
|
||||
* @param group the group
|
||||
*/
|
||||
function formatGroupMenu(group?: IGroup) {
|
||||
if (typeof group === 'undefined') {
|
||||
return null
|
||||
}
|
||||
|
||||
const item = {
|
||||
id: group.id,
|
||||
title: group.name,
|
||||
usercount: group.usercount,
|
||||
count: Math.max(0, group.usercount - group.disabled),
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
export const useFormatGroups = (groups: Ref<IGroup[]>|ComputedRef<IGroup[]>) => {
|
||||
/**
|
||||
* All non-disabled non-admin groups
|
||||
*/
|
||||
const userGroups = computed(() => {
|
||||
const formatted = groups.value
|
||||
// filter out disabled and admin
|
||||
.filter(group => group.id !== 'disabled' && group.id !== 'admin')
|
||||
// format group
|
||||
.map(group => formatGroupMenu(group))
|
||||
// remove invalid
|
||||
.filter(group => group !== null)
|
||||
return formatted as NonNullable<ReturnType<typeof formatGroupMenu>>[]
|
||||
})
|
||||
|
||||
/**
|
||||
* The admin group if found otherwise null
|
||||
*/
|
||||
const adminGroup = computed(() => formatGroupMenu(groups.value.find(group => group.id === 'admin')))
|
||||
|
||||
/**
|
||||
* The group of disabled users
|
||||
*/
|
||||
const disabledGroup = computed(() => formatGroupMenu(groups.value.find(group => group.id === 'disabled')))
|
||||
|
||||
return { adminGroup, disabledGroup, userGroups }
|
||||
}
|
@ -1,5 +1,222 @@
|
||||
<template>
|
||||
<div>...</div>
|
||||
<NcAppNavigation :aria-label="t('settings', 'Account management')">
|
||||
<NcAppNavigationNew button-id="new-user-button"
|
||||
:text="t('settings','New account')"
|
||||
@click="showNewUserMenu"
|
||||
@keyup.enter="showNewUserMenu"
|
||||
@keyup.space="showNewUserMenu">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiPlus" />
|
||||
</template>
|
||||
</NcAppNavigationNew>
|
||||
|
||||
<NcAppNavigationList class="account-management__system-list"
|
||||
data-cy-users-settings-navigation-groups="system">
|
||||
<NcAppNavigationItem id="everyone"
|
||||
:exact="true"
|
||||
:name="t('settings', 'Active accounts')"
|
||||
:to="{ name: 'users' }">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiAccount" />
|
||||
</template>
|
||||
<template #counter>
|
||||
<NcCounterBubble v-if="userCount" :type="!selectedGroupDecoded ? 'highlighted' : undefined">
|
||||
{{ userCount }}
|
||||
</NcCounterBubble>
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<NcAppNavigationItem v-if="isAdmin"
|
||||
id="admin"
|
||||
:exact="true"
|
||||
:name="t('settings', 'Admins')"
|
||||
:to="{ name: 'group', params: { selectedGroup: 'admin' } }">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiShieldAccount" />
|
||||
</template>
|
||||
<template #counter>
|
||||
<NcCounterBubble v-if="adminGroup && adminGroup.count > 0"
|
||||
:type="selectedGroupDecoded === 'admin' ? 'highlighted' : undefined">
|
||||
{{ adminGroup.count }}
|
||||
</NcCounterBubble>
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<!-- Hide the disabled if none, if we don't have the data (-1) show it -->
|
||||
<NcAppNavigationItem v-if="disabledGroup && (disabledGroup.usercount > 0 || disabledGroup.usercount === -1)"
|
||||
id="disabled"
|
||||
:exact="true"
|
||||
:name="t('settings', 'Disabled accounts')"
|
||||
:to="{ name: 'group', params: { selectedGroup: 'disabled' } }">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiAccountOff" />
|
||||
</template>
|
||||
<template v-if="disabledGroup.usercount > 0" #counter>
|
||||
<NcCounterBubble :type="selectedGroupDecoded === 'disabled' ? 'highlighted' : undefined">
|
||||
{{ disabledGroup.usercount }}
|
||||
</NcCounterBubble>
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
</NcAppNavigationList>
|
||||
|
||||
<NcAppNavigationCaption :name="t('settings', 'Groups')"
|
||||
:disabled="loadingAddGroup"
|
||||
:aria-label="loadingAddGroup ? t('settings', 'Creating group…') : t('settings', 'Create group')"
|
||||
force-menu
|
||||
is-heading
|
||||
:open.sync="isAddGroupOpen">
|
||||
<template #actionsTriggerIcon>
|
||||
<NcLoadingIcon v-if="loadingAddGroup" />
|
||||
<NcIconSvgWrapper v-else :path="mdiPlus" />
|
||||
</template>
|
||||
<template #actions>
|
||||
<NcActionText>
|
||||
<template #icon>
|
||||
<AccountGroup :size="20" />
|
||||
</template>
|
||||
{{ t('settings', 'Create group') }}
|
||||
</NcActionText>
|
||||
<NcActionInput :label="t('settings', 'Group name')"
|
||||
data-cy-users-settings-new-group-name
|
||||
:label-outside="false"
|
||||
:disabled="loadingAddGroup"
|
||||
:value.sync="newGroupName"
|
||||
:error="hasAddGroupError"
|
||||
:helper-text="hasAddGroupError ? t('settings', 'Please enter a valid group name') : ''"
|
||||
@submit="createGroup" />
|
||||
</template>
|
||||
</NcAppNavigationCaption>
|
||||
|
||||
<NcAppNavigationList class="account-management__group-list" data-cy-users-settings-navigation-groups="custom">
|
||||
<GroupListItem v-for="group in userGroups"
|
||||
:id="group.id"
|
||||
:key="group.id"
|
||||
:active="selectedGroupDecoded === group.id"
|
||||
:name="group.title"
|
||||
:count="group.count" />
|
||||
</NcAppNavigationList>
|
||||
|
||||
<template #footer>
|
||||
<NcButton class="account-management__settings-toggle"
|
||||
type="tertiary"
|
||||
@click="isDialogOpen = true">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiCog" />
|
||||
</template>
|
||||
{{ t('settings', 'Account management settings') }}
|
||||
</NcButton>
|
||||
<UserSettingsDialog :open.sync="isDialogOpen" />
|
||||
</template>
|
||||
</NcAppNavigation>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mdiAccount, mdiAccountOff, mdiCog, mdiPlus, mdiShieldAccount } from '@mdi/js'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
|
||||
import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js'
|
||||
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
|
||||
import NcAppNavigationCaption from '@nextcloud/vue/dist/Components/NcAppNavigationCaption.js'
|
||||
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
|
||||
import NcAppNavigationList from '@nextcloud/vue/dist/Components/NcAppNavigationList.js'
|
||||
import NcAppNavigationNew from '@nextcloud/vue/dist/Components/NcAppNavigationNew.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
||||
|
||||
import GroupListItem from '../components/GroupListItem.vue'
|
||||
import UserSettingsDialog from '../components/Users/UserSettingsDialog.vue'
|
||||
import { useStore } from '../store'
|
||||
import { useRoute, useRouter } from 'vue-router/composables'
|
||||
import { useFormatGroups } from '../composables/useGroupsNavigation'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
/** State of the 'new-account' dialog */
|
||||
const isDialogOpen = ref(false)
|
||||
|
||||
/** Current active group in the view - this is URL encoded */
|
||||
const selectedGroup = computed(() => route.params?.selectedGroup)
|
||||
/** Current active group - URL decoded */
|
||||
const selectedGroupDecoded = computed(() => selectedGroup.value ? decodeURIComponent(selectedGroup.value) : null)
|
||||
|
||||
/** Overall user count */
|
||||
const userCount = computed(() => store.getters.getUserCount)
|
||||
/** All available groups */
|
||||
const groups = computed(() => store.getters.getGroups)
|
||||
const { adminGroup, disabledGroup, userGroups } = useFormatGroups(groups)
|
||||
|
||||
/** True if the current user is an administrator */
|
||||
const isAdmin = computed(() => store.getters.getServerData.isAdmin)
|
||||
|
||||
/** True if the 'add-group' dialog is open - needed to be able to close it when the group is created */
|
||||
const isAddGroupOpen = ref(false)
|
||||
/** True if the group creation is in progress to show loading spinner and disable adding another one */
|
||||
const loadingAddGroup = ref(false)
|
||||
/** Error state for creating a new group */
|
||||
const hasAddGroupError = ref(false)
|
||||
/** Name of the group to create (used in the group creation dialog) */
|
||||
const newGroupName = ref('')
|
||||
|
||||
/**
|
||||
* Create a new group
|
||||
*/
|
||||
async function createGroup() {
|
||||
hasAddGroupError.value = false
|
||||
const groupId = newGroupName.value.trim()
|
||||
if (groupId === '') {
|
||||
hasAddGroupError.value = true
|
||||
return
|
||||
}
|
||||
|
||||
isAddGroupOpen.value = false
|
||||
loadingAddGroup.value = true
|
||||
|
||||
try {
|
||||
await store.dispatch('addGroup', groupId)
|
||||
await router.push({
|
||||
name: 'group',
|
||||
params: {
|
||||
selectedGroup: encodeURIComponent(groupId),
|
||||
},
|
||||
})
|
||||
newGroupName.value = ''
|
||||
} catch {
|
||||
showError(t('settings', 'Failed to create group'))
|
||||
}
|
||||
loadingAddGroup.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the new-user form dialog
|
||||
*/
|
||||
function showNewUserMenu() {
|
||||
store.commit('setShowConfig', {
|
||||
key: 'showNewUserForm',
|
||||
value: true,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.account-management{
|
||||
&__system-list {
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
&__group-list {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
&__settings-toggle {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,14 @@
|
||||
export interface IGroup {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Overall user count
|
||||
*/
|
||||
usercount: number
|
||||
|
||||
/**
|
||||
* Number of disabled users
|
||||
*/
|
||||
disabled: number
|
||||
}
|
Loading…
Reference in New Issue