diff --git a/src/app/components/user/UserForm.vue b/src/app/components/user/UserForm.vue
index d4a74ad4..110b99aa 100644
--- a/src/app/components/user/UserForm.vue
+++ b/src/app/components/user/UserForm.vue
@@ -14,7 +14,10 @@ const emit = defineEmits<{
'cancel': []
}>()
-const { table: { pageSize } } = useAppConfig()
+const {
+ table: { pageSize },
+ features: { repositories: { 'server-search': serverSearch } },
+} = useAppConfig()
const { currentUser } = useAuth()
const { schema } = useUserSchema(() => properties.mode)
const { preferredLanguageOptions, userRoleOptions } = useUserFormOptions()
@@ -52,6 +55,7 @@ const {
label: repository.serviceName,
value: repository.id,
}),
+ server: serverSearch,
})
setupRepoScroll(repositorySelect)
@@ -353,7 +357,8 @@ const isSelf = computed(() =>
v-model="state.repositoryRoles[index] as { label: string, value: string }"
v-model:search-term="repoSearchTerm" size="xl"
:placeholder="$t('user.placeholder.repository-name')"
- :items="repositoryNames" :loading="repoSearchStatus === 'pending'" ignore-filter
+ :items="repositoryNames" :loading="repoSearchStatus === 'pending'"
+ :ignore-filter="serverSearch"
class="flex-2"
@update:open="onRepoOpen"
/>
diff --git a/src/app/composables/forms.ts b/src/app/composables/forms.ts
index 4cc8aa34..cd144aa7 100644
--- a/src/app/composables/forms.ts
+++ b/src/app/composables/forms.ts
@@ -26,6 +26,7 @@ const useSelectMenuInfiniteScroll =
(
debounce = 300,
scrollDistance = 10,
query = {},
+ server = true,
} = options
const page = ref(1)
@@ -41,10 +42,11 @@ const useSelectMenuInfiniteScroll = (
...query,
q: searchTermDebounced.value || undefined,
p: page.value,
- l: limit,
+ l: server ? limit : -1,
})),
lazy: true,
immediate: false,
+ watch: false,
})
watch(data, (newData) => {
@@ -57,6 +59,7 @@ const useSelectMenuInfiniteScroll = (
})
watch(searchTermDebounced, () => {
+ if (!server) return
page.value = 1
hasMore.value = true
execute()
diff --git a/src/app/composables/groups.ts b/src/app/composables/groups.ts
index f49971c2..fbd79913 100644
--- a/src/app/composables/groups.ts
+++ b/src/app/composables/groups.ts
@@ -15,7 +15,11 @@ const useGroupsTable = () => {
const { t: $t } = useI18n()
const { copy } = useClipboard()
- const { table: { pageSize: pageSizeConfig } } = useAppConfig()
+ const {
+ table: { pageSize: pageSizeConfig },
+ features: { groups: { 'sort-columns': sortColumns },
+ repositories: { 'server-search': serverSearch } },
+ } = useAppConfig()
const query = computed(() => normalizeGroupsQuery(route.query))
const updateQuery = async (newQuery: Partial) => {
@@ -170,11 +174,15 @@ const useGroupsTable = () => {
},
{
accessorKey: 'id',
- header: () => sortableHeader('id'),
+ header: () => sortColumns
+ ? sortableHeader('id')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.id),
},
{
accessorKey: 'displayName',
- header: () => sortableHeader('displayName'),
+ header: () => sortColumns
+ ? sortableHeader('displayName')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.displayName),
cell: ({ row }) => {
const name: string = row.original.displayName
return h(ULink, {
@@ -188,14 +196,19 @@ const useGroupsTable = () => {
},
{
accessorKey: 'public',
- header: () => sortableHeader('public'),
+ header: () => sortColumns
+ ? sortableHeader('public')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.public),
cell: ({ row }) => (
publicStatus.value[`${row.original.public}`]
),
},
{
accessorKey: 'memberListVisibility',
- header: () => sortableHeader('memberListVisibility'),
+ header: () => sortColumns
+ ? sortableHeader('memberListVisibility')
+ : h('span', { class: 'text-xs text-default font-medium' },
+ columnNames.value.memberListVisibility),
cell: ({ row }) => (
visibilityStatus.value[row.original.memberListVisibility]
),
@@ -315,6 +328,7 @@ const useGroupsTable = () => {
} = useSelectMenuInfiniteScroll({
url: repositorySelect.url,
limit: pageSizeConfig.repositories[0],
+ server: serverSearch,
transform: repository => ({
label: repository.serviceName,
value: repository.id,
diff --git a/src/app/composables/repositories.ts b/src/app/composables/repositories.ts
index b736e821..b7bbd4ca 100644
--- a/src/app/composables/repositories.ts
+++ b/src/app/composables/repositories.ts
@@ -6,6 +6,8 @@ import { UButton, UDropdownMenu, UIcon, ULink } from '#components'
import type { ButtonProps, DropdownMenuItem, TableColumn, TableRow } from '@nuxt/ui'
+const { features: { repositories: { 'sort-columns': sortColumns } } } = useAppConfig()
+
/** Composable for managing repositories table */
const useRepositoriesTable = () => {
const route = useRoute()
@@ -68,12 +70,17 @@ const useRepositoriesTable = () => {
const columns = computed(() => [
{
accessorKey: 'id',
- header: () => sortableHeader('id'),
+ header: () => sortColumns
+ ? sortableHeader('id')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.id),
cell: ({ row }) => row.original.spConnectorId,
+ enableGlobalFilter: false,
},
{
accessorKey: 'serviceName',
- header: () => sortableHeader('serviceName'),
+ header: () => sortColumns
+ ? sortableHeader('serviceName')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.serviceName),
cell: ({ row }) => {
const name: string = row.original.serviceName
return h(ULink, {
@@ -87,7 +94,9 @@ const useRepositoriesTable = () => {
},
{
accessorKey: 'serviceUrl',
- header: () => sortableHeader('serviceUrl'),
+ header: () => sortColumns
+ ? sortableHeader('serviceUrl')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.serviceUrl),
cell: ({ row }) => {
const url: string = row.original.serviceUrl
return url
@@ -109,7 +118,10 @@ const useRepositoriesTable = () => {
},
{
accessorKey: 'entityIds',
- header: () => sortableHeader('entityIds'),
+ accessorFn: row => row.entityIds?.[0],
+ header: () => sortColumns
+ ? sortableHeader('entityIds')
+ : h('span', { class: 'text-xs text-default font-medium' }, columnNames.value.entityIds),
cell: ({ row }) => row.original.entityIds?.[0],
meta: {
class: {
diff --git a/src/app/composables/useBulk.ts b/src/app/composables/useBulk.ts
index 475dcbe5..76f5840e 100644
--- a/src/app/composables/useBulk.ts
+++ b/src/app/composables/useBulk.ts
@@ -3,12 +3,9 @@
*/
import { UBadge, UIcon } from '#components'
-import type { FetchError } from 'ofetch'
import type { BadgeProps, TableColumn } from '@nuxt/ui'
-const toast = useToast()
-
-const useBulk = () => {
+const useBulk = () => {
const currentStep = ref<'upload' | 'validate' | 'result'>('upload')
const { t: $t } = useI18n()
@@ -46,13 +43,11 @@ const useBulk = () => {
const pageSize = ref(query.value.l)
const pageNumber = ref(query.value.p)
const sortOrder = ref(query.value.d)
- const makePageInfo = (result: Ref) => {
- return computed(() => {
- const start = result.value?.offset ?? 1
- const total = result.value?.total ?? 0
- const end = Math.min(start + pageSize.value!, total)
- return `${start} - ${end} / ${total}`
- })
+ const makePageInfo = (result: Ref) => {
+ const start = result.value?.offset ?? 1
+ const total = result.value?.total ?? 0
+ const end = Math.min(start + pageSize.value!, total)
+ return `${start} - ${end} / ${total}`
}
const makeStatusFilters = () => {
@@ -149,7 +144,52 @@ const useBulk = () => {
},
},
])
+ type IndicatorColor = 'success' | 'info' | 'error' | 'warning'
+ interface Indicator {
+ title: string
+ icon: string
+ number: number
+ color: IndicatorColor
+ key: string
+ }
+ const makeIndicators = (summary: ValidationResults | ExecuteResults | undefined): Indicator[] => [
+ {
+ title: $t('bulk.status.create'),
+ icon: 'i-lucide-plus-circle',
+ number: summary?.summary.create ?? 0,
+ color: 'success',
+ key: 'create',
+ },
+ {
+ title: $t('bulk.status.update'),
+ icon: 'i-lucide-pencil',
+ number: summary?.summary.update ?? 0,
+ color: 'info',
+ key: 'update',
+ },
+ {
+ title: $t('bulk.status.delete'),
+ icon: 'i-lucide-trash-2',
+ number: summary?.summary.delete ?? 0,
+ color: 'error',
+ key: 'delete',
+ },
+ {
+ title: $t('bulk.status.skip'),
+ icon: 'i-lucide-minus-circle',
+ number: summary?.summary.skip ?? 0,
+ color: 'warning',
+ key: 'skip',
+ },
+ {
+ title: $t('bulk.status.error'),
+ icon: 'i-lucide-circle-x',
+ number: summary?.summary.error ?? 0,
+ color: 'error',
+ key: 'error',
+ },
+ ]
return {
query,
currentStep,
@@ -159,6 +199,7 @@ const useBulk = () => {
sortOrder,
columns,
makePageInfo,
+ makeIndicators,
updateQuery,
makeStatusFilters,
}
@@ -174,209 +215,27 @@ const useUserUpload = () => {
}
}
-const useValidation = ({ taskId, selectedRepository }: { taskId: Ref
+const useValidation = ({ taskId }: { taskId: Ref
selectedRepository: Ref }) => {
const { query } = useBulk()
- const validationResults = ref([])
- const missingUsers = ref([])
const selectedMissingUsers = useState>(
`selection-missing-users:${taskId}`, () => ({}),
)
- const fetchValidationResults = (url: string) => {
- return useFetch(url, {
- method: 'GET',
- query,
- lazy: true,
- server: false,
- onResponseError({ response }) {
- switch (response.status) {
- case 400: { {
- toast.add({
- title: $t('bulk.status.error'),
- description: $t('bulk.validation.fetch_failed'),
- color: 'error',
- icon: 'i-lucide-circle-x',
- }) }
- break
- }
- default:{
- handleFetchError({ response })
- break
- }
- }
- },
- })
- }
const selectedCount = computed(() => {
return Object.values(selectedMissingUsers.value).filter(value => value === true).length
})
const toggleSelection = (userId: string) => {
selectedMissingUsers.value[userId] = !selectedMissingUsers.value[userId]
}
- const temporaryFileId = ref(undefined)
- const summary = ref({
- create: 0, update: 0, delete: 0, skip: 0, error: 0,
- })
-
- const { handleFetchError } = useErrorHandling()
- const executeBulkUpdate = async (url: string) => {
- if (!taskId || !selectedRepository.value) {
- throw new Error('Missing required data')
- }
- try {
- const results = await $fetch(url, {
- method: 'POST',
- body: {
- tempFileId: temporaryFileId.value,
- repositoryId: selectedRepository.value,
- deleteUsers:
- Object.keys(selectedMissingUsers.value).filter(key => selectedMissingUsers.value[key]),
- } as ExcuteRequest,
- })
- return results
- }
- catch (error) {
- handleFetchError({ response: (error as FetchError).response! })
- return { taskId: undefined, historyId: undefined }
- }
- }
-
- const useBulkIndicators = computed(() => [
- {
- title: $t('bulk.status.create'),
- icon: 'i-lucide-plus-circle',
- number: summary.value.create ?? 0,
- color: 'success',
- key: 'create',
- },
- {
- title: $t('bulk.status.update'),
- icon: 'i-lucide-pencil',
- number: summary.value.update ?? 0,
- color: 'info',
- key: 'update',
- },
- {
- title: $t('bulk.status.delete'),
- icon: 'i-lucide-trash-2',
- number: summary.value.delete ?? 0,
- color: 'error',
- key: 'delete',
- },
- {
- title: $t('bulk.status.skip'),
- icon: 'i-lucide-minus-circle',
- number: summary.value.skip ?? 0,
- color: 'warning',
- key: 'skip',
- },
- {
- title: $t('bulk.status.error'),
- icon: 'i-lucide-circle-x',
- number: summary.value.error ?? 0,
- color: 'error',
- key: 'error',
- },
- ])
return {
- useBulkIndicators,
query,
- validationResults,
- missingUsers,
selectedMissingUsers,
selectedCount,
- summary,
taskId,
- temporaryFileId,
- fetchValidationResults,
- executeBulkUpdate,
toggleSelection,
}
}
-const useExecuteUpload = () => {
- const { query } = useBulk()
- const uploadResult = ref(undefined)
- const { handleFetchError } = useErrorHandling()
- const fetchUploadResult = async (url: string) => {
- const { data, execute } = await useFetch(url, {
- method: 'GET',
- query,
- lazy: true,
- server: false,
- onResponseError({ response }) {
- switch (response.status) {
- case 400: {
- toast.add({
- title: $t('bulk.status.error'),
- description: $t('bulk.execute.result_failed'),
- color: 'error',
- icon: 'i-lucide-circle-x',
- })
- break
- }
- default: {
- handleFetchError({ response })
- break
- }
- }
- },
- })
- return { uploadResult: data, execute }
- }
-
- const resultSummary = computed(() => ({
- create: uploadResult.value!.summary.create,
- update: uploadResult.value!.summary.update,
- delete: uploadResult.value!.summary.delete,
- skip: uploadResult.value!.summary.skip,
- error: uploadResult.value!.summary.error,
- }))
-
- const useBulkIndicators = computed(() => [
- {
- title: $t('bulk.status.create'),
- icon: 'i-lucide-plus-circle',
- number: resultSummary.value.create ?? 0,
- color: 'success',
- key: 'create',
- },
- {
- title: $t('bulk.status.update'),
- icon: 'i-lucide-pencil',
- number: resultSummary.value.update ?? 0,
- color: 'info',
- key: 'update',
- },
- {
- title: $t('bulk.status.delete'),
- icon: 'i-lucide-trash-2',
- number: resultSummary.value.delete ?? 0,
- color: 'error',
- key: 'delete',
- },
- {
- title: $t('bulk.status.skip'),
- icon: 'i-lucide-minus-circle',
- number: resultSummary.value.skip ?? 0,
- color: 'warning',
- key: 'skip',
- },
- {
- title: $t('bulk.status.error'),
- icon: 'i-lucide-circle-x',
- number: resultSummary.value.error ?? 0,
- color: 'error',
- key: 'error',
- },
- ])
- return {
- uploadResult,
- useBulkIndicators,
- fetchUploadResult,
- resultSummary,
- }
-}
-export { useBulk, useUserUpload, useValidation, useExecuteUpload }
+export { useBulk, useUserUpload, useValidation }
diff --git a/src/app/composables/useHistory.ts b/src/app/composables/useHistory.ts
index b5eb6d80..dccfdfeb 100644
--- a/src/app/composables/useHistory.ts
+++ b/src/app/composables/useHistory.ts
@@ -6,9 +6,8 @@ import type { Row } from '@tanstack/table-core'
import type { DropdownMenuItem, TableColumn } from '@nuxt/ui'
import type { DateRange } from 'reka-ui'
-const toast = useToast()
-
const useHistory = () => {
+ const toast = useToast()
const route = useRoute()
const { t: $t } = useI18n()
const { currentUser } = useAuth()
@@ -26,39 +25,10 @@ const useHistory = () => {
const sortOrder = computed(() => query.value.d)
const pageNumber = ref(query.value.p)
const pageSize = ref(query.value.l)
- const loading = ref(false)
- const downloadGroups = ref([])
- const uploadRows = ref([])
const totalItems = ref(0)
- const fileExistsCache = ref