Browse Source

fix: UI Acl & data sources

Signed-off-by: mertmit <mertmit99@gmail.com>
pull/9045/head
mertmit 4 months ago
parent
commit
2773260b8c
  1. 8
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  2. 238
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  3. 2
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  4. 2
      packages/nocodb/src/models/Source.ts

8
packages/nc-gui/components/dashboard/settings/DataSources.vue

@ -57,6 +57,13 @@ async function updateIfSourceOrderIsNullOrDuplicate() {
if (!hasNullOrDuplicates) return if (!hasNullOrDuplicates) return
// make sure default source is always first
sources.value = sources.value.sort((a, b) => {
if (a.is_local || a.is_meta) return -1
if (b.is_local || b.is_meta) return 1
return (a.order ?? 0) - (b.order ?? 0)
})
// update the local state // update the local state
sources.value = sources.value.map((source, i) => { sources.value = sources.value.map((source, i) => {
return { return {
@ -75,6 +82,7 @@ async function updateIfSourceOrderIsNullOrDuplicate() {
}) })
}), }),
) )
await loadProject(base.value.id as string, true)
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }

238
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -31,12 +31,6 @@ const tables = ref<any[]>([])
const searchInput = ref('') const searchInput = ref('')
const selectAll = ref({
editor: false,
commenter: false,
viewer: false,
})
const filteredTables = computed(() => const filteredTables = computed(() =>
tables.value.filter( tables.value.filter(
(el) => (el) =>
@ -46,6 +40,24 @@ const filteredTables = computed(() =>
), ),
) )
const allSelected = computed(() => {
return roles.value.reduce((acc, role) => {
return {
...acc,
[role]: tables.value.filter((t) => t.disabled[role]).length === 0,
}
}, {} as Record<Role, boolean>)
})
const toggleSelectAll = (role: Role) => {
const newValue = !allSelected.value[role]
tables.value.forEach((t) => {
t.disabled[role] = newValue
t.edited = true
})
}
async function loadTableList() { async function loadTableList() {
try { try {
if (!baseId.value) return if (!baseId.value) return
@ -81,62 +93,44 @@ async function saveUIAcl() {
const onRoleCheck = (record: any, role: Role) => { const onRoleCheck = (record: any, role: Role) => {
record.disabled[role] = !record.disabled[role] record.disabled[role] = !record.disabled[role]
record.edited = true record.edited = true
selectAll.value[role as Role] = filteredTables.value.every((t) => !t.disabled[role])
} }
onMounted(async () => { onMounted(async () => {
if (tables.value.length === 0) { if (tables.value.length === 0) {
await loadTableList() await loadTableList()
} }
for (const role of roles.value) {
selectAll.value[role as Role] = filteredTables.value.every((t) => !t.disabled[role])
}
}) })
const tableHeaderRenderer = (label: string) => () => h('div', { class: 'text-gray-500' }, label)
const columns = [ const columns = [
{ {
title: tableHeaderRenderer(t('labels.tableName')), title: t('labels.tableName'),
name: 'Table Name', name: 'Table Name',
}, },
{ {
title: tableHeaderRenderer(t('labels.viewName')), title: t('labels.viewName'),
name: 'View Name', name: 'View Name',
}, },
{ {
title: tableHeaderRenderer(t('objects.roleType.editor')), title: t('objects.roleType.editor'),
name: 'editor', name: 'editor',
width: 120, width: 120,
}, },
{ {
title: tableHeaderRenderer(t('objects.roleType.commenter')), title: t('objects.roleType.commenter'),
name: 'commenter', name: 'commenter',
width: 120, width: 120,
}, },
{ {
title: tableHeaderRenderer(t('objects.roleType.viewer')), title: t('objects.roleType.viewer'),
name: 'viewer', name: 'viewer',
width: 120, width: 120,
}, },
] ]
const toggleSelectAll = (role: Role) => {
selectAll.value[role] = !selectAll.value[role]
const enabled = selectAll.value[role]
filteredTables.value.forEach((t) => {
t.disabled[role] = !enabled
t.edited = true
})
}
</script> </script>
<template> <template>
<div class="h-full flex flex-row w-full items-center justify-center"> <div class="h-full flex flex-row w-full items-center justify-center">
<div class="h-full flex flex-col"> <div class="w-full h-full flex flex-col">
<NcTooltip class="mb-4 first-letter:capital font-bold max-w-100 truncate" show-on-truncate-only> <NcTooltip class="mb-4 first-letter:capital font-bold max-w-100 truncate" show-on-truncate-only>
<template #title>{{ base.title }}</template> <template #title>{{ base.title }}</template>
<span> UI ACL : {{ base.title }} </span> <span> UI ACL : {{ base.title }} </span>
@ -165,91 +159,121 @@ const toggleSelectAll = (role: Role) => {
</div> </div>
<div class="h-auto max-h-[calc(100%_-_102px)] overflow-y-auto nc-scrollbar-thin"> <div class="h-auto max-h-[calc(100%_-_102px)] overflow-y-auto nc-scrollbar-thin">
<a-table <div class="w-full" size="small">
class="w-full" <div class="table-header">
size="small" <template v-for="column in columns" :key="column.name">
:data-source="filteredTables" <template v-if="['editor', 'commenter', 'viewer'].includes(column.name)">
:columns="columns" <div class="table-header-col" :style="`width: ${column.width}px`">
:pagination="false" <div class="flex flex-row gap-x-1">
:loading="isLoading" <NcCheckbox
sticky v-model:checked="allSelected[column.name as Role]"
bordered @change="toggleSelectAll(column.name as Role)"
:custom-row=" />
(record) => ({ <div class="flex capitalize">
class: `nc-acl-table-row nc-acl-table-row-${record.title}`, {{ column.name }}
}) </div>
" </div>
> </div>
<template #headerCell="{ column }"> </template>
<template v-if="['editor', 'commenter', 'viewer'].includes(column.name)"> <template v-else>
<div class="flex flex-row gap-x-1"> <div class="table-header-col flex-1">
<NcCheckbox :checked="selectAll[column.name as Role]" @change="() => toggleSelectAll(column.name)" /> <div class="flex capitalize">{{ column.title }}</div>
<div class="flex capitalize">
{{ column.name }}
</div> </div>
</div> </template>
</template> </template>
<template v-else>{{ column.name }}</template> </div>
</template>
<template #emptyText> <template v-if="filteredTables.length === 0">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" /> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template> </template>
<template #bodyCell="{ record, column }"> <template v-else>
<div v-if="column.name === 'Table Name'"> <div
<div class="flex items-center gap-1"> v-for="record in filteredTables"
<div class="min-w-5 flex items-center justify-center"> :key="record.id"
<GeneralTableIcon :meta="{ meta: record.table_meta, type: record.ptype }" class="text-gray-500" /> :class="`table-body-row nc-acl-table-row nc-acl-table-row-${record.title}`"
</div> >
<NcTooltip class="overflow-ellipsis min-w-0 shrink-1 truncate" show-on-truncate-only> <template v-for="column in columns" :key="column.name">
<template #title>{{ record._ptn }}</template> <template v-if="column.name === 'Table Name'">
<span>{{ record._ptn }}</span> <div class="table-body-row-col flex-1">
</NcTooltip> <div class="min-w-5 flex items-center justify-center">
</div> <GeneralTableIcon :meta="{ meta: record.table_meta, type: record.ptype }" class="text-gray-500" />
</div> </div>
<NcTooltip class="overflow-ellipsis min-w-0 shrink-1 truncate" show-on-truncate-only>
<div v-if="column.name === 'View Name'"> <template #title>{{ record._ptn }}</template>
<div class="flex items-center gap-1"> <span>{{ record._ptn }}</span>
<div class="min-w-5 flex items-center justify-center"> </NcTooltip>
<GeneralTableIcon </div>
v-if="record?.meta?.icon" </template>
:meta="{ meta: record.meta, type: 'view' }" <template v-else-if="column.name === 'View Name'">
class="text-gray-500 !text-sm children:(!w-5 !h-5)" <div class="table-body-row-col flex-1">
/> <div class="min-w-5 flex items-center justify-center">
<GeneralViewIcon v-else :meta="record" class="text-gray-500"></GeneralViewIcon> <GeneralTableIcon
</div> v-if="record?.meta?.icon"
<NcTooltip class="overflow-ellipsis min-w-0 shrink-1 truncate" show-on-truncate-only> :meta="{ meta: record.meta, type: 'view' }"
<template #title>{{ record.is_default ? $t('title.defaultView') : record.title }}</template> class="text-gray-500 !text-sm children:(!w-5 !h-5)"
<span>{{ record.is_default ? $t('title.defaultView') : record.title }}</span> />
</NcTooltip> <GeneralViewIcon v-else :meta="record" class="text-gray-500"></GeneralViewIcon>
</div> </div>
</div> <NcTooltip class="overflow-ellipsis min-w-0 shrink-1 truncate" show-on-truncate-only>
<template #title>{{ record.is_default ? $t('title.defaultView') : record.title }}</template>
<div v-for="role in roles" :key="role"> <span>{{ record.is_default ? $t('title.defaultView') : record.title }}</span>
<div v-if="column.name === role"> </NcTooltip>
<NcTooltip> </div>
<template #title> </template>
<span v-if="record.disabled[role]"> <template v-else>
{{ $t('labels.clickToMake') }} '{{ record.title }}' {{ $t('labels.visibleForRole') }} {{ role }} <div class="table-body-row-col" :style="`width: ${column.width}px`">
{{ $t('labels.inUI') }} dashboard</span <NcTooltip>
> <template #title>
<span v-else <span v-if="record.disabled[column.name]">
>{{ $t('labels.clickToHide') }} '{{ record.title }}' {{ $t('labels.forRole') }}:{{ role }} {{ $t('labels.clickToMake') }} '{{ record.title }}' {{ $t('labels.visibleForRole') }} {{ column.name }}
{{ $t('labels.inUI') }}</span {{ $t('labels.inUI') }} dashboard</span
> >
</template> <span v-else
>{{ $t('labels.clickToHide') }} '{{ record.title }}' {{ $t('labels.forRole') }}:{{ column.name }}
<NcCheckbox {{ $t('labels.inUI') }}</span
:checked="!record.disabled[role]" >
:class="`nc-acl-${record.title}-${role}-chkbox !ml-0.25`" </template>
@change="onRoleCheck(record, role as Role)"
/> <NcCheckbox
</NcTooltip> :checked="!record.disabled[column.name]"
</div> :class="`nc-acl-${record.title}-${column.name}-chkbox !ml-0.25`"
@change="onRoleCheck(record, column.name as Role)"
/>
</NcTooltip>
</div>
</template>
</template>
</div> </div>
</template> </template>
</a-table> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss">
.table-header {
@apply flex items-center bg-gray-100 border-1 border-gray-200;
}
.table-header-col {
@apply flex items-center p-2 border-r-1 border-gray-200;
}
.table-header-col:last-child {
@apply border-r-0;
}
.table-body-row {
@apply flex items-center bg-white border-r-1 border-l-1 border-b-1 border-gray-200;
}
.table-body-row-col {
@apply flex items-center p-2 border-r-1 border-gray-200;
}
.table-body-row-col:last-child {
@apply border-r-0;
}
</style>

2
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -1061,7 +1061,7 @@ export const formulas: Record<string, FormulaMeta> = {
}, },
description: description:
'Verify and convert to a hyperlink if the input is a valid URL.', 'Verify and convert to a hyperlink if the input is a valid URL.',
syntax: 'URL(str, optionalLabel)', syntax: 'URL(string, [label])',
examples: [ examples: [
'URL("https://github.com/nocodb/nocodb")', 'URL("https://github.com/nocodb/nocodb")',
'URL({column1})', 'URL({column1})',

2
packages/nocodb/src/models/Source.ts

@ -117,7 +117,7 @@ export default class Source implements SourceType {
}, },
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
) { ) {
const oldSource = await Source.get(context, sourceId, false, ncMeta); const oldSource = await this.get(context, sourceId, false, ncMeta);
if (!oldSource) NcError.sourceNotFound(sourceId); if (!oldSource) NcError.sourceNotFound(sourceId);

Loading…
Cancel
Save