aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/src/components')
-rw-r--r--apps/settings/src/components/AdminAI.vue5
-rw-r--r--apps/settings/src/components/AdminSettingsSharingForm.vue10
-rw-r--r--apps/settings/src/components/AppList.vue10
-rw-r--r--apps/settings/src/components/AppNavigationGroupList.vue34
-rw-r--r--apps/settings/src/components/DeclarativeSettings/DeclarativeSection.vue15
-rw-r--r--apps/settings/src/components/GroupListItem.vue6
-rw-r--r--apps/settings/src/components/PersonalInfo/FediverseSection.vue9
-rw-r--r--apps/settings/src/components/PersonalInfo/PronounsSection.vue24
-rw-r--r--apps/settings/src/components/PersonalInfo/TwitterSection.vue4
-rw-r--r--apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue3
-rw-r--r--apps/settings/src/components/UserList.vue6
-rw-r--r--apps/settings/src/components/Users/NewUserDialog.vue28
-rw-r--r--apps/settings/src/components/Users/UserRow.vue68
13 files changed, 162 insertions, 60 deletions
diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue
index afa08cd676e..044ebd9183e 100644
--- a/apps/settings/src/components/AdminAI.vue
+++ b/apps/settings/src/components/AdminAI.vue
@@ -6,6 +6,11 @@
<div class="ai-settings">
<NcSettingsSection :name="t('settings', 'Unified task processing')"
:description="t('settings', 'AI tasks can be implemented by different apps. Here you can set which app should be used for which task.')">
+ <NcCheckboxRadioSwitch v-model="settings['ai.taskprocessing_guests']"
+ type="switch"
+ @update:modelValue="saveChanges">
+ {{ t('settings', 'Allow AI usage for guest users') }}
+ </NcCheckboxRadioSwitch>
<template v-for="type in taskProcessingTaskTypes">
<div :key="type">
<h3>{{ t('settings', 'Task:') }} {{ type.name }}</h3>
diff --git a/apps/settings/src/components/AdminSettingsSharingForm.vue b/apps/settings/src/components/AdminSettingsSharingForm.vue
index 25dcbb7972d..1973781edee 100644
--- a/apps/settings/src/components/AdminSettingsSharingForm.vue
+++ b/apps/settings/src/components/AdminSettingsSharingForm.vue
@@ -27,6 +27,15 @@
:label="t('settings', 'Ignore the following groups when checking group membership')"
style="width: 100%" />
</div>
+ <NcCheckboxRadioSwitch :checked.sync="settings.allowViewWithoutDownload">
+ {{ t('settings', 'Allow users to preview files even if download is disabled') }}
+ </NcCheckboxRadioSwitch>
+ <NcNoteCard v-show="settings.allowViewWithoutDownload"
+ id="settings-sharing-api-view-without-download-hint"
+ class="sharing__note"
+ type="warning">
+ {{ t('settings', 'Users will still be able to screenshot or record the screen. This does not provide any definitive protection.') }}
+ </NcNoteCard>
</div>
<div v-show="settings.enabled" id="settings-sharing-api" class="sharing__section">
@@ -258,6 +267,7 @@ interface IShareSettings {
remoteExpireAfterNDays: string
enforceRemoteExpireDate: boolean
allowCustomTokens: boolean
+ allowViewWithoutDownload: boolean
}
export default defineComponent({
diff --git a/apps/settings/src/components/AppList.vue b/apps/settings/src/components/AppList.vue
index cfc778fe409..3e40e08b257 100644
--- a/apps/settings/src/components/AppList.vue
+++ b/apps/settings/src/components/AppList.vue
@@ -200,9 +200,13 @@ export default {
const apps = [...this.$store.getters.getAllApps, ...exApps]
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
.sort(function(a, b) {
- const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name
- const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1) + b.name
- return OC.Util.naturalSortCompare(sortStringA, sortStringB)
+ const natSortDiff = OC.Util.naturalSortCompare(a, b)
+ if (natSortDiff === 0) {
+ const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1)
+ const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1)
+ return Number(sortStringA) - Number(sortStringB)
+ }
+ return natSortDiff
})
if (this.category === 'installed') {
diff --git a/apps/settings/src/components/AppNavigationGroupList.vue b/apps/settings/src/components/AppNavigationGroupList.vue
index b32a07bc9b8..2be7cce2f8b 100644
--- a/apps/settings/src/components/AppNavigationGroupList.vue
+++ b/apps/settings/src/components/AppNavigationGroupList.vue
@@ -42,7 +42,7 @@
<NcAppNavigationList class="account-management__group-list"
aria-describedby="group-list-desc"
data-cy-users-settings-navigation-groups="custom">
- <GroupListItem v-for="group in userGroups"
+ <GroupListItem v-for="group in filteredGroups"
:id="group.id"
ref="groupListItems"
:key="group.id"
@@ -57,12 +57,16 @@
</template>
<script setup lang="ts">
+import type CancelablePromise from 'cancelable-promise'
+import type { IGroup } from '../views/user-types.d.ts'
+
+import { mdiAccountGroup, mdiPlus } from '@mdi/js'
+import { showError } from '@nextcloud/dialogs'
+import { t } from '@nextcloud/l10n'
+import { useElementVisibility } from '@vueuse/core'
import { computed, ref, watch, onBeforeMount } from 'vue'
import { Fragment } from 'vue-frag'
import { useRoute, useRouter } from 'vue-router/composables'
-import { useElementVisibility } from '@vueuse/core'
-import { showError } from '@nextcloud/dialogs'
-import { mdiAccountGroup, mdiPlus } from '@mdi/js'
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
import NcActionText from '@nextcloud/vue/components/NcActionText'
@@ -92,7 +96,11 @@ const selectedGroup = computed(() => route.params?.selectedGroup)
/** Current active group - URL decoded */
const selectedGroupDecoded = computed(() => selectedGroup.value ? decodeURIComponent(selectedGroup.value) : null)
/** All available groups */
-const groups = computed(() => store.getters.getSortedGroups)
+const groups = computed(() => {
+ return isAdminOrDelegatedAdmin.value
+ ? store.getters.getSortedGroups
+ : store.getters.getSubAdminGroups
+})
/** User groups */
const { userGroups } = useFormatGroups(groups)
/** Server settings for current user */
@@ -115,6 +123,14 @@ const loadingGroups = ref(false)
const offset = ref(0)
/** Search query for groups */
const groupsSearchQuery = ref('')
+const filteredGroups = computed(() => {
+ if (isAdminOrDelegatedAdmin.value) {
+ return userGroups.value
+ }
+
+ const substring = groupsSearchQuery.value.toLowerCase()
+ return userGroups.value.filter(group => group.id.toLowerCase().search(substring) !== -1 || group.title.toLowerCase().search(substring) !== -1)
+})
const groupListItems = ref([])
const lastGroupListItem = computed(() => {
@@ -137,12 +153,16 @@ watch(groupsSearchQuery, async () => {
})
/** Cancelable promise for search groups request */
-const promise = ref(null)
+const promise = ref<CancelablePromise<IGroup[]>>()
/**
* Load groups
*/
async function loadGroups() {
+ if (!isAdminOrDelegatedAdmin.value) {
+ return
+ }
+
if (promise.value) {
promise.value.cancel()
}
@@ -163,7 +183,7 @@ async function loadGroups() {
} catch (error) {
logger.error(t('settings', 'Failed to load groups'), { error })
}
- promise.value = null
+ promise.value = undefined
loadingGroups.value = false
}
diff --git a/apps/settings/src/components/DeclarativeSettings/DeclarativeSection.vue b/apps/settings/src/components/DeclarativeSettings/DeclarativeSection.vue
index 334739337e1..9ee1680516e 100644
--- a/apps/settings/src/components/DeclarativeSettings/DeclarativeSection.vue
+++ b/apps/settings/src/components/DeclarativeSettings/DeclarativeSection.vue
@@ -119,6 +119,7 @@ import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
import NcInputField from '@nextcloud/vue/components/NcInputField'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
+import { confirmPassword } from '@nextcloud/password-confirmation'
export default {
name: 'DeclarativeSection',
@@ -202,9 +203,19 @@ export default {
}
},
- updateDeclarativeSettingsValue(formField, value = null) {
+ async updateDeclarativeSettingsValue(formField, value = null) {
try {
- return axios.post(generateOcsUrl('settings/api/declarative/value'), {
+ let url = generateOcsUrl('settings/api/declarative/value')
+ if (formField?.sensitive === true) {
+ url = generateOcsUrl('settings/api/declarative/value-sensitive')
+ try {
+ await confirmPassword()
+ } catch (err) {
+ showError(t('settings', 'Password confirmation is required'))
+ return
+ }
+ }
+ return axios.post(url, {
app: this.formApp,
formId: this.form.id.replace(this.formApp + '_', ''), // Remove app prefix to send clean form id
fieldId: formField.id,
diff --git a/apps/settings/src/components/GroupListItem.vue b/apps/settings/src/components/GroupListItem.vue
index 65d46136ec1..76088fa74db 100644
--- a/apps/settings/src/components/GroupListItem.vue
+++ b/apps/settings/src/components/GroupListItem.vue
@@ -13,7 +13,7 @@
</h2>
<NcNoteCard type="warning"
show-alert>
- {{ t('settings', 'You are about to remove the group "{group}". The accounts will NOT be deleted.', { group: name }) }}
+ {{ t('settings', 'You are about to delete the group "{group}". The accounts will NOT be deleted.', { group: name }) }}
</NcNoteCard>
<div class="modal__button-row">
<NcButton type="secondary"
@@ -62,7 +62,7 @@
<template #icon>
<Delete :size="20" />
</template>
- {{ t('settings', 'Remove group') }}
+ {{ t('settings', 'Delete group') }}
</NcActionButton>
</template>
</NcAppNavigationItem>
@@ -179,7 +179,7 @@ export default {
await this.$store.dispatch('removeGroup', this.id)
this.showRemoveGroupModal = false
} catch (error) {
- showError(t('settings', 'Failed to remove group "{group}"', { group: this.name }))
+ showError(t('settings', 'Failed to delete group "{group}"', { group: this.name }))
}
},
},
diff --git a/apps/settings/src/components/PersonalInfo/FediverseSection.vue b/apps/settings/src/components/PersonalInfo/FediverseSection.vue
index 65400ce0e45..043fa6e64b9 100644
--- a/apps/settings/src/components/PersonalInfo/FediverseSection.vue
+++ b/apps/settings/src/components/PersonalInfo/FediverseSection.vue
@@ -19,7 +19,7 @@ import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
import AccountPropertySection from './shared/AccountPropertySection.vue'
-const { fediverse } = loadState<AccountProperties>('settings', 'personalInfoParameters', {})
+const { fediverse } = loadState<AccountProperties>('settings', 'personalInfoParameters')
const value = ref({ ...fediverse })
const readable = NAME_READABLE_ENUM[fediverse.name]
@@ -29,11 +29,18 @@ const readable = NAME_READABLE_ENUM[fediverse.name]
* @param text The potential fediverse handle
*/
function onValidate(text: string): boolean {
+ // allow to clear the value
+ if (text === '') {
+ return true
+ }
+
+ // check its in valid format
const result = text.match(/^@?([^@/]+)@([^@/]+)$/)
if (result === null) {
return false
}
+ // check its a valid URL
try {
return URL.parse(`https://${result[2]}/`) !== null
} catch {
diff --git a/apps/settings/src/components/PersonalInfo/PronounsSection.vue b/apps/settings/src/components/PersonalInfo/PronounsSection.vue
index fb35b1800c5..e345cb8e225 100644
--- a/apps/settings/src/components/PersonalInfo/PronounsSection.vue
+++ b/apps/settings/src/components/PersonalInfo/PronounsSection.vue
@@ -8,16 +8,18 @@
:placeholder="randomPronounsPlaceholder" />
</template>
-<script>
-import { loadState } from '@nextcloud/initial-state'
+<script lang="ts">
+import type { IAccountProperty } from '../../constants/AccountPropertyConstants.ts'
+import { loadState } from '@nextcloud/initial-state'
+import { t } from '@nextcloud/l10n'
+import { defineComponent } from 'vue'
import AccountPropertySection from './shared/AccountPropertySection.vue'
+import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.ts'
-import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
-
-const { pronouns } = loadState('settings', 'personalInfoParameters', {})
+const { pronouns } = loadState<{ pronouns: IAccountProperty }>('settings', 'personalInfoParameters')
-export default {
+export default defineComponent({
name: 'PronounsSection',
components: {
@@ -33,13 +35,13 @@ export default {
computed: {
randomPronounsPlaceholder() {
const pronouns = [
- this.t('settings', 'she/her'),
- this.t('settings', 'he/him'),
- this.t('settings', 'they/them'),
+ t('settings', 'she/her'),
+ t('settings', 'he/him'),
+ t('settings', 'they/them'),
]
const pronounsExample = pronouns[Math.floor(Math.random() * pronouns.length)]
- return this.t('settings', `Your pronouns. E.g. ${pronounsExample}`, { pronounsExample })
+ return t('settings', 'Your pronouns. E.g. {pronounsExample}', { pronounsExample })
},
},
-}
+})
</script>
diff --git a/apps/settings/src/components/PersonalInfo/TwitterSection.vue b/apps/settings/src/components/PersonalInfo/TwitterSection.vue
index 802f96087c2..43d08f81e3f 100644
--- a/apps/settings/src/components/PersonalInfo/TwitterSection.vue
+++ b/apps/settings/src/components/PersonalInfo/TwitterSection.vue
@@ -19,7 +19,7 @@ import { ref } from 'vue'
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.ts'
import AccountPropertySection from './shared/AccountPropertySection.vue'
-const { twitter } = loadState<AccountProperties>('settings', 'personalInfoParameters', {})
+const { twitter } = loadState<AccountProperties>('settings', 'personalInfoParameters')
const value = ref({ ...twitter })
const readable = NAME_READABLE_ENUM[twitter.name]
@@ -29,6 +29,6 @@ const readable = NAME_READABLE_ENUM[twitter.name]
* @param text The potential twitter handle
*/
function onValidate(text: string): boolean {
- return text.match(/^@?([a-zA-Z0-9_]{2,15})$/) !== null
+ return text === '' || text.match(/^@?([a-zA-Z0-9_]{2,15})$/) !== null
}
</script>
diff --git a/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
index 8cdde6b7d9a..d039641ec72 100644
--- a/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
+++ b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
@@ -155,6 +155,7 @@ export default {
methods: {
async updateProperty(value) {
try {
+ this.hasError = false
const responseData = await savePrimaryAccountProperty(
this.name,
value,
@@ -180,10 +181,8 @@ export default {
this.isSuccess = true
setTimeout(() => { this.isSuccess = false }, 2000)
} else {
- this.$emit('update:value', this.initialValue)
handleError(error, errorMessage)
this.hasError = true
- setTimeout(() => { this.hasError = false }, 2000)
}
},
},
diff --git a/apps/settings/src/components/UserList.vue b/apps/settings/src/components/UserList.vue
index 5d6bd5f04ee..84c204805cc 100644
--- a/apps/settings/src/components/UserList.vue
+++ b/apps/settings/src/components/UserList.vue
@@ -350,11 +350,13 @@ export default {
setNewUserDefaultGroup(value) {
// Is no value set, but user is a line manager we set their group as this is a requirement for line manager
if (!value && !this.settings.isAdmin && !this.settings.isDelegatedAdmin) {
+ const groups = this.$store.getters.getSubAdminGroups
// if there are multiple groups we do not know which to add,
// so we cannot make the managers life easier by preselecting it.
- if (this.groups.length === 1) {
- value = this.groups[0].id
+ if (groups.length === 1) {
+ this.newUser.groups = [...groups]
}
+ return
}
if (value) {
diff --git a/apps/settings/src/components/Users/NewUserDialog.vue b/apps/settings/src/components/Users/NewUserDialog.vue
index 3e50efc2072..ef401b565fa 100644
--- a/apps/settings/src/components/Users/NewUserDialog.vue
+++ b/apps/settings/src/components/Users/NewUserDialog.vue
@@ -61,6 +61,7 @@
:required="newUser.password === '' || settings.newUserRequireEmail" />
<div class="dialog__item">
<NcSelect class="dialog__select"
+ data-test="groups"
:input-label="!settings.isAdmin && !settings.isDelegatedAdmin ? t('settings', 'Member of the following groups (required)') : t('settings', 'Member of the following groups')"
:placeholder="t('settings', 'Set account groups')"
:disabled="loading.groups || loading.all"
@@ -69,7 +70,7 @@
label="name"
:close-on-select="false"
:multiple="true"
- :taggable="true"
+ :taggable="settings.isAdmin || settings.isDelegatedAdmin"
:required="!settings.isAdmin && !settings.isDelegatedAdmin"
:create-option="(value) => ({ id: value, name: value, isCreating: true })"
@search="searchGroups"
@@ -85,7 +86,7 @@
:input-label="t('settings', 'Admin of the following groups')"
:placeholder="t('settings', 'Set account as admin for …')"
:disabled="loading.groups || loading.all"
- :options="subAdminsGroups"
+ :options="availableGroups"
:close-on-select="false"
:multiple="true"
label="name"
@@ -178,7 +179,6 @@ export default {
data() {
return {
- availableGroups: this.$store.getters.getSortedGroups.filter(group => group.id !== '__nc_internal_recent' && group.id !== 'disabled'),
possibleManagers: [],
// TRANSLATORS This string describes a manager in the context of an organization
managerInputLabel: t('settings', 'Manager'),
@@ -209,9 +209,12 @@ export default {
return this.$store.getters.getPasswordPolicyMinLength
},
- subAdminsGroups() {
- // data provided php side
- return this.availableGroups.filter(group => group.id !== 'admin' && group.id !== '__nc_internal_recent' && group.id !== 'disabled')
+ availableGroups() {
+ const groups = (this.settings.isAdmin || this.settings.isDelegatedAdmin)
+ ? this.$store.getters.getSortedGroups
+ : this.$store.getters.getSubAdminGroups
+
+ return groups.filter(group => group.id !== '__nc_internal_recent' && group.id !== 'disabled')
},
languages() {
@@ -273,6 +276,11 @@ export default {
},
async searchGroups(query, toggleLoading) {
+ if (!this.settings.isAdmin && !this.settings.isDelegatedAdmin) {
+ // managers cannot search for groups
+ return
+ }
+
if (this.promise) {
this.promise.cancel()
}
@@ -284,7 +292,10 @@ export default {
limit: 25,
})
const groups = await this.promise
- this.availableGroups = groups
+ // Populate store from server request
+ for (const group of groups) {
+ this.$store.commit('addGroup', group)
+ }
} catch (error) {
logger.error(t('settings', 'Failed to search groups'), { error })
}
@@ -302,7 +313,6 @@ export default {
this.loading.groups = true
try {
await this.$store.dispatch('addGroup', gid)
- this.availableGroups.push({ id: gid, name: gid })
this.newUser.groups.push({ id: gid, name: gid })
} catch (error) {
logger.error(t('settings', 'Failed to create group'), { error })
@@ -336,7 +346,7 @@ export default {
const validQuota = OC.Util.computerFileSize(quota)
if (validQuota !== null && validQuota >= 0) {
// unify format output
- quota = formatFileSize(parseFileSize(quota))
+ quota = formatFileSize(parseFileSize(quota, true))
this.newUser.quota = { id: quota, label: quota }
return this.newUser.quota
}
diff --git a/apps/settings/src/components/Users/UserRow.vue b/apps/settings/src/components/Users/UserRow.vue
index 987cf84492a..43668725972 100644
--- a/apps/settings/src/components/Users/UserRow.vue
+++ b/apps/settings/src/components/Users/UserRow.vue
@@ -255,16 +255,17 @@
data-cy-user-list-input-manager
:data-loading="loading.manager || undefined"
:input-id="'manager' + uniqueId"
- :close-on-select="true"
:disabled="isLoadingField"
- :append-to-body="false"
:loading="loadingPossibleManagers || loading.manager"
- label="displayname"
:options="possibleManagers"
:placeholder="managerLabel"
+ label="displayname"
+ :filterable="false"
+ :internal-search="false"
+ :clearable="true"
@open="searchInitialUserManager"
@search="searchUserManager"
- @option:selected="updateUserManager" />
+ @update:model-value="updateUserManager" />
</template>
<span v-else-if="!isObfuscated">
{{ user.manager }}
@@ -410,15 +411,35 @@ export default {
return encodeURIComponent(this.user.id + this.rand)
},
+ availableGroups() {
+ const groups = (this.settings.isAdmin || this.settings.isDelegatedAdmin)
+ ? this.$store.getters.getSortedGroups
+ : this.$store.getters.getSubAdminGroups
+
+ return groups.filter(group => group.id !== '__nc_internal_recent' && group.id !== 'disabled')
+ },
+
+ availableSubAdminGroups() {
+ return this.availableGroups.filter(group => group.id !== 'admin')
+ },
+
userGroupsLabels() {
return this.userGroups
- .map(group => group.name ?? group.id)
+ .map(group => {
+ // Try to match with more extensive group data
+ const availableGroup = this.availableGroups.find(g => g.id === group.id)
+ return availableGroup?.name ?? group.name ?? group.id
+ })
.join(', ')
},
userSubAdminGroupsLabels() {
return this.userSubAdminGroups
- .map(group => group.name ?? group.id)
+ .map(group => {
+ // Try to match with more extensive group data
+ const availableGroup = this.availableSubAdminGroups.find(g => g.id === group.id)
+ return availableGroup?.name ?? group.name ?? group.id
+ })
.join(', ')
},
@@ -502,7 +523,6 @@ export default {
return this.languages[0].languages.concat(this.languages[1].languages)
},
},
-
async beforeMount() {
if (this.user.manager) {
await this.initManager(this.user.manager)
@@ -559,7 +579,11 @@ export default {
this.loading.groupsDetails = true
try {
const groups = await loadUserGroups({ userId: this.user.id })
- this.availableGroups = this.availableGroups.map(availableGroup => groups.find(group => group.id === availableGroup.id) ?? availableGroup)
+ // Populate store from server request
+ for (const group of groups) {
+ this.$store.commit('addGroup', group)
+ }
+ this.selectedGroups = this.selectedGroups.map(selectedGroup => groups.find(group => group.id === selectedGroup.id) ?? selectedGroup)
} catch (error) {
logger.error(t('settings', 'Failed to load groups with details'), { error })
}
@@ -572,7 +596,11 @@ export default {
this.loading.subAdminGroupsDetails = true
try {
const groups = await loadUserSubAdminGroups({ userId: this.user.id })
- this.availableSubAdminGroups = this.availableSubAdminGroups.map(availableGroup => groups.find(group => group.id === availableGroup.id) ?? availableGroup)
+ // Populate store from server request
+ for (const group of groups) {
+ this.$store.commit('addGroup', group)
+ }
+ this.selectedSubAdminGroups = this.selectedSubAdminGroups.map(selectedGroup => groups.find(group => group.id === selectedGroup.id) ?? selectedGroup)
} catch (error) {
logger.error(t('settings', 'Failed to load sub admin groups with details'), { error })
}
@@ -595,8 +623,10 @@ export default {
limit: 25,
})
const groups = await this.promise
- this.availableGroups = groups
- this.availableSubAdminGroups = groups.filter(group => group.id !== 'admin')
+ // Populate store from server request
+ for (const group of groups) {
+ this.$store.commit('addGroup', group)
+ }
} catch (error) {
logger.error(t('settings', 'Failed to search groups'), { error })
}
@@ -613,11 +643,12 @@ export default {
})
},
- async updateUserManager(manager) {
- if (manager === null) {
- this.currentManager = ''
- }
+ async updateUserManager() {
this.loading.manager = true
+
+ // Store the current manager before making changes
+ const previousManager = this.user.manager
+
try {
await this.$store.dispatch('setUserData', {
userid: this.user.id,
@@ -627,7 +658,10 @@ export default {
} catch (error) {
// TRANSLATORS This string describes a line manager in the context of an organization
showError(t('settings', 'Failed to update line manager'))
- console.error(error)
+ logger.error('Failed to update manager:', { error })
+
+ // Revert to the previous manager in the UI on error
+ this.currentManager = previousManager
} finally {
this.loading.manager = false
}
@@ -753,8 +787,6 @@ export default {
this.loading.groups = true
try {
await this.$store.dispatch('addGroup', gid)
- this.availableGroups.push({ id: gid, name: gid })
- this.availableSubAdminGroups.push({ id: gid, name: gid })
const userid = this.user.id
await this.$store.dispatch('addUserGroup', { userid, gid })
this.userGroups.push({ id: gid, name: gid })