Browse Source

feat: restrict data/meta operations in gui

pull/8708/head
Pranav C 5 months ago
parent
commit
5d8b1fc55a
  1. 3
      packages/nc-gui/components/cell/MultiSelect.vue
  2. 3
      packages/nc-gui/components/cell/SingleSelect.vue
  3. 3
      packages/nc-gui/components/cell/attachment/Modal.vue
  4. 3
      packages/nc-gui/components/cell/attachment/index.vue
  5. 4
      packages/nc-gui/components/dashboard/Sidebar/TopSection.vue
  6. 3
      packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue
  7. 6
      packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue
  8. 2
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  9. 19
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  10. 9
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  11. 8
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  12. 4
      packages/nc-gui/components/project/AllTables.vue
  13. 3
      packages/nc-gui/components/smartsheet/Details.vue
  14. 3
      packages/nc-gui/components/smartsheet/Form.vue
  15. 3
      packages/nc-gui/components/smartsheet/Gallery.vue
  16. 3
      packages/nc-gui/components/smartsheet/Kanban.vue
  17. 3
      packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue
  18. 3
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  19. 3
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  20. 3
      packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
  21. 3
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
  22. 2
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
  23. 3
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  24. 3
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  25. 2
      packages/nc-gui/components/smartsheet/grid/Table.vue
  26. 3
      packages/nc-gui/components/smartsheet/header/Cell.vue
  27. 3
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  28. 3
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  29. 4
      packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue
  30. 3
      packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue
  31. 3
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  32. 3
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  33. 3
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  34. 4
      packages/nc-gui/components/smartsheet/topbar/SelectMode.vue
  35. 14
      packages/nc-gui/components/tabs/Smartsheet.vue
  36. 3
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  37. 3
      packages/nc-gui/components/virtual-cell/HasMany.vue
  38. 3
      packages/nc-gui/components/virtual-cell/Links.vue
  39. 3
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  40. 3
      packages/nc-gui/components/virtual-cell/OneToOne.vue
  41. 3
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  42. 3
      packages/nc-gui/composables/useCalendarViewStore.ts
  43. 3
      packages/nc-gui/composables/useExpandedFormStore.ts
  44. 3
      packages/nc-gui/composables/useKanbanViewStore.ts
  45. 3
      packages/nc-gui/composables/useMapViewDataStore.ts
  46. 39
      packages/nc-gui/composables/useRoles/index.ts
  47. 3
      packages/nc-gui/composables/useViewColumns.ts
  48. 3
      packages/nc-gui/composables/useViewData.ts
  49. 3
      packages/nc-gui/composables/useViewFilters.ts
  50. 3
      packages/nc-gui/composables/useViewGroupBy.ts
  51. 2
      packages/nc-gui/composables/useViewSorts.ts
  52. 9
      packages/nc-gui/context/index.ts
  53. 8
      packages/nocodb-sdk/src/lib/enums.ts
  54. 7
      packages/nocodb/src/services/public-datas.service.ts
  55. 16
      packages/nocodb/src/utils/acl.ts

3
packages/nc-gui/components/cell/MultiSelect.vue

@ -5,6 +5,7 @@ import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk' import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk'
import type { FormFieldsLimitOptionsType } from '~/lib/types' import type { FormFieldsLimitOptionsType } from '~/lib/types'
import MdiCloseCircle from '~icons/mdi/close-circle' import MdiCloseCircle from '~icons/mdi/close-circle'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
modelValue?: string | string[] modelValue?: string | string[]
@ -57,7 +58,7 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas() const { getMeta } = useMetas()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { isPg, isMysql } = useBase() const { isPg, isMysql } = useBase()

3
packages/nc-gui/components/cell/SingleSelect.vue

@ -4,6 +4,7 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import type { SelectOptionType } from 'nocodb-sdk' import type { SelectOptionType } from 'nocodb-sdk'
import type { FormFieldsLimitOptionsType } from '~/lib/types' import type { FormFieldsLimitOptionsType } from '~/lib/types'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
modelValue?: string | undefined modelValue?: string | undefined
@ -49,7 +50,7 @@ const searchVal = ref()
const { getMeta } = useMetas() const { getMeta } = useMetas()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { isPg, isMysql } = useBase() const { isPg, isMysql } = useBase()

3
packages/nc-gui/components/cell/attachment/Modal.vue

@ -2,8 +2,9 @@
import { onKeyDown, useEventListener } from '@vueuse/core' import { onKeyDown, useEventListener } from '@vueuse/core'
import { useAttachmentCell } from './utils' import { useAttachmentCell } from './utils'
import { useSortable } from './sort' import { useSortable } from './sort'
import {useRolesWrapper} from "~/composables/useRoles";
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { const {
open, open,

3
packages/nc-gui/components/cell/attachment/index.vue

@ -2,6 +2,7 @@
import { onKeyDown } from '@vueuse/core' import { onKeyDown } from '@vueuse/core'
import { useProvideAttachmentCell } from './utils' import { useProvideAttachmentCell } from './utils'
import { useSortable } from './sort' import { useSortable } from './sort'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
modelValue?: string | Record<string, any>[] | null modelValue?: string | Record<string, any>[] | null
@ -175,7 +176,7 @@ const keydownSpace = (e: KeyboardEvent) => {
} }
} }
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const isConfirmModalOpen = ref(false) const isConfirmModalOpen = ref(false)
const filetoDelete = reactive({ const filetoDelete = reactive({
title: '', title: '',

4
packages/nc-gui/components/dashboard/Sidebar/TopSection.vue

@ -1,8 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import {useRolesWrapper} from "~/composables/useRoles";
const workspaceStore = useWorkspace() const workspaceStore = useWorkspace()
const baseStore = useBase() const baseStore = useBase()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { appInfo } = useGlobal() const { appInfo } = useGlobal()

3
packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue

@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia'
import { toRef } from '@vue/reactivity' import { toRef } from '@vue/reactivity'
import { resolveComponent } from '@vue/runtime-core' import { resolveComponent } from '@vue/runtime-core'
import { ref } from 'vue' import { ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -19,7 +20,7 @@ const emit = defineEmits<{
openTableCreateDialog: () => void openTableCreateDialog: () => void
}>() }>()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const base = toRef(props, 'base') const base = toRef(props, 'base')

6
packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue

@ -68,7 +68,7 @@ function openQuickImportDialog(type: string) {
<template #expandIcon></template> <template #expandIcon></template>
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('airtableImport', { roles: baseRole })" v-if="isUIAllowed('airtableImport', { roles: baseRole, source })"
key="quick-import-airtable" key="quick-import-airtable"
@click="openAirtableImportDialog(source.base_id, source.id)" @click="openAirtableImportDialog(source.base_id, source.id)"
> >
@ -86,7 +86,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('jsonImport', { roles: baseRole })" v-if="isUIAllowed('jsonImport', { roles: baseRole, source })"
key="quick-import-json" key="quick-import-json"
@click="openQuickImportDialog('json')" @click="openQuickImportDialog('json')"
> >
@ -97,7 +97,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('excelImport', { roles: baseRole })" v-if="isUIAllowed('excelImport', { roles: baseRole, source })"
key="quick-import-excel" key="quick-import-excel"
@click="openQuickImportDialog('excel')" @click="openQuickImportDialog('excel')"
> >

2
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -823,7 +823,7 @@ const onTableIdCopy = async () => {
</NcDropdown> </NcDropdown>
<NcButton <NcButton
v-if="isUIAllowed('tableCreate', { roles: baseRole })" v-if="isUIAllowed('tableCreate', { roles: baseRole, source })"
v-e="['c:source:add-table']" v-e="['c:source:add-table']"
type="text" type="text"
size="xxsmall" size="xxsmall"

19
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -213,6 +213,10 @@ const refreshViews = async () => {
await nextTick() await nextTick()
isExpanded.value = true isExpanded.value = true
} }
const source = computed(() => {
return base.value?.sources?.[sourceIndex.value]
})
</script> </script>
<template> <template>
@ -331,15 +335,16 @@ const refreshViews = async () => {
<template <template
v-if=" v-if="
!isSharedBase && !isSharedBase &&
(isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole })) (isUIAllowed('tableRename', { roles: baseRole, source }) ||
isUIAllowed('tableDelete', { roles: baseRole, source }))
" "
> >
<NcDivider /> <NcDivider />
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('tableRename', { roles: baseRole })" v-if="isUIAllowed('tableRename', { roles: baseRole, source })"
:data-testid="`sidebar-table-rename-${table.title}`" :data-testid="`sidebar-table-rename-${table.title}`"
class="nc-table-rename" class="nc-table-rename"
@click="openRenameTableDialog(table, base.sources[sourceIndex].id)" @click="openRenameTableDialog(table, source.id)"
> >
<div v-e="['c:table:rename']" class="flex gap-2 items-center"> <div v-e="['c:table:rename']" class="flex gap-2 items-center">
<GeneralIcon icon="rename" class="text-gray-700" /> <GeneralIcon icon="rename" class="text-gray-700" />
@ -349,9 +354,11 @@ const refreshViews = async () => {
<NcMenuItem <NcMenuItem
v-if=" v-if="
isUIAllowed('tableDuplicate') && isUIAllowed('tableDuplicate', {
source,
}) &&
base.sources?.[sourceIndex] && base.sources?.[sourceIndex] &&
(base.sources[sourceIndex].is_meta || base.sources[sourceIndex].is_local) (source.is_meta || source.is_local)
" "
:data-testid="`sidebar-table-duplicate-${table.title}`" :data-testid="`sidebar-table-duplicate-${table.title}`"
@click="duplicateTable(table)" @click="duplicateTable(table)"
@ -364,7 +371,7 @@ const refreshViews = async () => {
<NcDivider /> <NcDivider />
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('tableDelete', { roles: baseRole })" v-if="isUIAllowed('tableDelete', { roles: baseRole, source })"
:data-testid="`sidebar-table-delete-${table.title}`" :data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50 nc-table-delete" class="!text-red-500 !hover:bg-red-50 nc-table-delete"
@click="deleteTable" @click="deleteTable"

9
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -10,6 +10,7 @@ import {
SSLUsage, SSLUsage,
clientTypes as _clientTypes, clientTypes as _clientTypes,
} from '#imports' } from '#imports'
import {SourceRestriction} from "nocodb-sdk";
const props = defineProps<{ open: boolean; connectionType?: ClientType }>() const props = defineProps<{ open: boolean; connectionType?: ClientType }>()
@ -56,8 +57,8 @@ const formState = ref<ProjectCreateForm>({
sslUse: SSLUsage.No, sslUse: SSLUsage.No,
extraParameters: [], extraParameters: [],
meta: { meta: {
readOnlySchema: true, [SourceRestriction.META_READONLY]: true,
readOnlyData: false, [SourceRestriction.DATA_READONLY]: false,
}, },
}) })
@ -515,10 +516,10 @@ const toggleModal = (val: boolean) => {
</a-form-item> </a-form-item>
<a-form-item label="Readonly Schema"> <a-form-item label="Readonly Schema">
<a-switch v-model:checked="formState.meta.readOnlySchema" size="small"></a-switch> <a-switch v-model:checked="formState.meta[SourceRestriction.META_READONLY]" size="small"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Readonly Data"> <a-form-item label="Readonly Data">
<a-switch v-model:checked="formState.meta.readOnlyData" size="small"></a-switch> <a-switch v-model:checked="formState.meta[SourceRestriction.DATA_READONLY]" size="small"></a-switch>
</a-form-item> </a-form-item>
<div class="flex items-right justify-end gap-2"> <div class="flex items-right justify-end gap-2">

8
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -72,8 +72,8 @@ const customFormState = ref<ProjectCreateForm>({
sslUse: SSLUsage.No, sslUse: SSLUsage.No,
extraParameters: [], extraParameters: [],
meta: { meta: {
readOnlySchema: true, [SourceRestriction.META_READONLY]: true,
readOnlyData: false, [SourceRestriction.DATA_READONLY]: false,
}, },
}) })
@ -530,10 +530,10 @@ watch(
</a-form-item> </a-form-item>
<a-form-item label="Readonly Schema"> <a-form-item label="Readonly Schema">
<a-switch v-model:checked="formState.meta.readOnlySchema" size="small"></a-switch> <a-switch v-model:checked="formState.meta[SourceRestriction.META_READONLY]" size="small"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Readonly Data"> <a-form-item label="Readonly Data">
<a-switch v-model:checked="formState.meta.readOnlyData" size="small"></a-switch> <a-switch v-model:checked="formState.meta[SourceRestriction.DATA_READONLY]" size="small"></a-switch>
</a-form-item> </a-form-item>
<!-- Use Connection URL --> <!-- Use Connection URL -->

4
packages/nc-gui/components/project/AllTables.vue

@ -76,7 +76,7 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
}" }"
> >
<div <div
v-if="isUIAllowed('tableCreate')" v-if="isUIAllowed('tableCreate', {source: openedProject?.sources?.[0]})"
role="button" role="button"
class="nc-base-view-all-table-btn" class="nc-base-view-all-table-btn"
data-testid="proj-view-btn__add-new-table" data-testid="proj-view-btn__add-new-table"
@ -86,7 +86,7 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
<div class="label">{{ $t('general.new') }} {{ $t('objects.table') }}</div> <div class="label">{{ $t('general.new') }} {{ $t('objects.table') }}</div>
</div> </div>
<div <div
v-if="isUIAllowed('tableCreate')" v-if="isUIAllowed('tableCreate', {source: openedProject?.sources?.[0]})"
v-e="['c:table:import']" v-e="['c:table:import']"
role="button" role="button"
class="nc-base-view-all-table-btn" class="nc-base-view-all-table-btn"

3
packages/nc-gui/components/smartsheet/Details.vue

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue'
import {useRolesWrapper} from "~/composables/useRoles";
const { openedViewsTab } = storeToRefs(useViewsStore()) const { openedViewsTab } = storeToRefs(useViewsStore())
const { onViewsTabChange } = useViewsStore() const { onViewsTabChange } = useViewsStore()
@ -8,7 +9,7 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { base } = storeToRefs(useBase()) const { base } = storeToRefs(useBase())
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/Form.vue

@ -15,6 +15,7 @@ import {
isVirtualCol, isVirtualCol,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import type { ImageCropperConfig } from '~/lib/types' import type { ImageCropperConfig } from '~/lib/types'
import {useRolesWrapper} from "~/composables/useRoles";
provide(IsFormInj, ref(true)) provide(IsFormInj, ref(true))
provide(IsGalleryInj, ref(false)) provide(IsGalleryInj, ref(false))
@ -52,7 +53,7 @@ const { isMobileMode, user } = useGlobal()
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { base } = storeToRefs(useBase()) const { base } = storeToRefs(useBase())

3
packages/nc-gui/components/smartsheet/Gallery.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ViewTypes, isVirtualCol } from 'nocodb-sdk' import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Row as RowType } from '#imports' import type { Row as RowType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
interface Attachment { interface Attachment {
url: string url: string
@ -64,7 +65,7 @@ const coverImageObjectFitClass = computed(() => {
if (fk_cover_image_object_fit === CoverImageObjectFit.COVER) return '!object-cover' if (fk_cover_image_object_fit === CoverImageObjectFit.COVER) return '!object-cover'
}) })
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const hasEditPermission = computed(() => isUIAllowed('dataEdit')) const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
// TODO: extract this code (which is duplicated in grid and gallery) into a separate component // TODO: extract this code (which is duplicated in grid and gallery) into a separate component
const _contextMenu = ref(false) const _contextMenu = ref(false)

3
packages/nc-gui/components/smartsheet/Kanban.vue

@ -4,6 +4,7 @@ import Draggable from 'vuedraggable'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import { ViewTypes, isVirtualCol } from 'nocodb-sdk' import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Row as RowType } from '#imports' import type { Row as RowType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
interface Attachment { interface Attachment {
url: string url: string
@ -73,7 +74,7 @@ const {
const { isViewDataLoading } = storeToRefs(useViewsStore()) const { isViewDataLoading } = storeToRefs(useViewsStore())
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { appInfo, isMobileMode } = useGlobal() const { appInfo, isMobileMode } = useGlobal()

3
packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const emit = defineEmits(['expandRecord', 'newRecord']) const emit = defineEmits(['expandRecord', 'newRecord'])
@ -8,7 +9,7 @@ const meta = inject(MetaInj, ref())
const container = ref() const container = ref()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { selectedDate, formattedData, formattedSideBarData, calendarRange, updateRowProperty } = useCalendarViewStoreOrThrow() const { selectedDate, formattedData, formattedSideBarData, calendarRange, updateRowProperty } = useCalendarViewStoreOrThrow()

3
packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const emit = defineEmits(['expandRecord', 'newRecord']) const emit = defineEmits(['expandRecord', 'newRecord'])
@ -20,7 +21,7 @@ const { $e } = useNuxtApp()
const container = ref<null | HTMLElement>(null) const container = ref<null | HTMLElement>(null)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/calendar/MonthView.vue

@ -2,6 +2,7 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const emit = defineEmits(['newRecord', 'expandRecord']) const emit = defineEmits(['newRecord', 'expandRecord'])
@ -23,7 +24,7 @@ const { $e } = useNuxtApp()
const isMondayFirst = ref(true) const isMondayFirst = ref(true)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/calendar/SideMenu.vue

@ -2,6 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {useRolesWrapper} from "~/composables/useRoles";
const props = defineProps<{ const props = defineProps<{
visible: boolean visible: boolean
@ -15,7 +16,7 @@ interface Attachment {
const INFINITY_SCROLL_THRESHOLD = 100 const INFINITY_SCROLL_THRESHOLD = 100
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()

3
packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue

@ -2,6 +2,7 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import type { Row } from '~/lib/types' import type { Row } from '~/lib/types'
import {useRolesWrapper} from "~/composables/useRoles";
const emits = defineEmits(['expandRecord', 'newRecord']) const emits = defineEmits(['expandRecord', 'newRecord'])
@ -24,7 +25,7 @@ const container = ref<null | HTMLElement>(null)
const { width: containerWidth } = useElementSize(container) const { width: containerWidth } = useElementSize(container)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

2
packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue

@ -28,7 +28,7 @@ const { width: containerWidth } = useElementSize(container)
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { type CommentType, ProjectRoles } from 'nocodb-sdk' import { type CommentType, ProjectRoles } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const props = defineProps<{ const props = defineProps<{
loading: boolean loading: boolean
@ -45,7 +46,7 @@ const isExpandedFormLoading = computed(() => props.loading)
const tab = ref<'comments' | 'audits'>('comments') const tab = ref<'comments' | 'audits'>('comments')
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const router = useRouter() const router = useRouter()

3
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -12,6 +12,7 @@ import type { Ref } from 'vue'
import { Drawer } from 'ant-design-vue' import { Drawer } from 'ant-design-vue'
import NcModal from '../../nc/Modal.vue' import NcModal from '../../nc/Modal.vue'
import MdiChevronDown from '~icons/mdi/chevron-down' import MdiChevronDown from '~icons/mdi/chevron-down'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
modelValue?: boolean modelValue?: boolean
@ -73,7 +74,7 @@ const isUnsavedDuplicatedRecordExist = ref(false)
const isRecordLinkCopied = ref(false) const isRecordLinkCopied = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value) const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value)

2
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -166,7 +166,7 @@ const isViewColumnsLoading = computed(() => _isViewColumnsLoading.value || !meta
const resizingColumn = ref(false) const resizingColumn = ref(false)
// #Permissions // #Permissions
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const hasEditPermission = computed(() => isUIAllowed('dataEdit')) const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value) const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)

3
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnReqType, ColumnType } from 'nocodb-sdk' import type { ColumnReqType, ColumnType } from 'nocodb-sdk'
import { UITypes, UITypesName } from 'nocodb-sdk' import { UITypes, UITypesName } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
column: ColumnType column: ColumnType
@ -32,7 +33,7 @@ const isDropDownOpen = ref(false)
const column = toRef(props, 'column') const column = toRef(props, 'column')
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
provide(ColumnInj, column) provide(ColumnInj, column)

3
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -9,6 +9,7 @@ import {
isLinksOrLTAR, isLinksOrLTAR,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import { RelationTypes, UITypes, UITypesName, substituteColumnIdWithAliasInFormula } from 'nocodb-sdk' import { RelationTypes, UITypes, UITypesName, substituteColumnIdWithAliasInFormula } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const props = defineProps<{ const props = defineProps<{
column: ColumnType column: ColumnType
@ -36,7 +37,7 @@ provide(ColumnInj, column)
const { metas } = useMetas() const { metas } = useMetas()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -2,6 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk' import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk'
import type { WritableComputedRef } from '@vue/reactivity' import type { WritableComputedRef } from '@vue/reactivity'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
view: ViewType view: ViewType
@ -30,7 +31,7 @@ const vModel = useVModel(props, 'view', emits) as WritableComputedRef<ViewType &
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const activeView = inject(ActiveViewInj, ref()) const activeView = inject(ActiveViewInj, ref())

4
packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const { isUIAllowed } = useRoles() import {useRolesWrapper} from "~/composables/useRoles";
const { isUIAllowed } = useRolesWrapper()
const { groupingFieldColumn } = useKanbanViewStoreOrThrow() const { groupingFieldColumn } = useKanbanViewStoreOrThrow()

3
packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue

@ -3,6 +3,7 @@ import type { RequestParams } from 'nocodb-sdk'
import { ExportTypes } from 'nocodb-sdk' import { ExportTypes } from 'nocodb-sdk'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import * as XLSX from 'xlsx' import * as XLSX from 'xlsx'
import {useRolesWrapper} from "~/composables/useRoles";
const { t } = useI18n() const { t } = useI18n()
@ -32,7 +33,7 @@ const showWebhookDrawer = ref(false)
const quickImportDialog = ref(false) const quickImportDialog = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const exportFile = async (exportType: ExportTypes) => { const exportFile = async (exportType: ExportTypes) => {
let offset = 0 let offset = 0

3
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -2,6 +2,7 @@
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import {useRolesWrapper} from "~/composables/useRoles";
const { t } = useI18n() const { t } = useI18n()
@ -13,7 +14,7 @@ const { $e } = useNuxtApp()
const { dashboardUrl } = useDashboard() const { dashboardUrl } = useDashboard()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { isSharedBase } = storeToRefs(useBase()) const { isSharedBase } = storeToRefs(useBase())

3
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -2,6 +2,7 @@
import type { TableType, ViewType } from 'nocodb-sdk' import type { TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { LockType } from '#imports' import { LockType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -16,7 +17,7 @@ const props = withDefaults(
const emits = defineEmits(['rename', 'closeModal', 'delete']) const emits = defineEmits(['rename', 'closeModal', 'delete'])
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const isPublicView = inject(IsPublicInj, ref(false)) const isPublicView = inject(IsPublicInj, ref(false))

3
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Ref } from '@vue/reactivity' import type { Ref } from '@vue/reactivity'
import { LockType } from '#imports' import { LockType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
const { t } = useI18n() const { t } = useI18n()
@ -37,7 +38,7 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
{}, {},
) as Record<QuickImportDialogType, Ref<boolean>> ) as Record<QuickImportDialogType, Ref<boolean>>
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
useBase() useBase()

4
packages/nc-gui/components/smartsheet/topbar/SelectMode.vue

@ -1,7 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import {useRolesWrapper} from "~/composables/useRoles";
const { openedViewsTab, activeView } = storeToRefs(useViewsStore()) const { openedViewsTab, activeView } = storeToRefs(useViewsStore())
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { onViewsTabChange } = useViewsStore() const { onViewsTabChange } = useViewsStore()

14
packages/nc-gui/components/tabs/Smartsheet.vue

@ -39,6 +39,12 @@ const reloadViewMetaEventHook = createEventHook<void | boolean>()
const openNewRecordFormHook = createEventHook<void>() const openNewRecordFormHook = createEventHook<void>()
const { base } = storeToRefs(useBase())
const activeSource = computed(() => {
return meta.value?.source_id && base.value && base.value.sources?.find((source) => source.id === meta.value?.source_id)
})
useProvideKanbanViewStore(meta, activeView) useProvideKanbanViewStore(meta, activeView)
useProvideMapViewStore(meta, activeView) useProvideMapViewStore(meta, activeView)
useProvideCalendarViewStore(meta, activeView) useProvideCalendarViewStore(meta, activeView)
@ -52,9 +58,15 @@ provide(ReloadViewMetaHookInj, reloadViewMetaEventHook)
provide(OpenNewRecordFormHookInj, openNewRecordFormHook) provide(OpenNewRecordFormHookInj, openNewRecordFormHook)
provide(IsFormInj, isForm) provide(IsFormInj, isForm)
provide(TabMetaInj, activeTab) provide(TabMetaInj, activeTab)
provide(ActiveSourceInj, activeSource)
provide( provide(
ReadonlyInj, ReadonlyInj,
computed(() => !isUIAllowed('dataEdit')), computed(
() =>
!isUIAllowed('dataEdit', {
source: activeSource.value,
}),
),
) )
useExpandedFormDetachedProvider() useExpandedFormDetachedProvider()

3
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
@ -18,7 +19,7 @@ const isForm = inject(IsFormInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false)) const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const listItemsDlg = ref(false) const listItemsDlg = ref(false)

3
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -2,6 +2,7 @@
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { isSystemColumn } from 'nocodb-sdk' import { isSystemColumn } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
@ -25,7 +26,7 @@ const isOpen = ref(false)
const hideBackBtn = ref(false) const hideBackBtn = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow() const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()

3
packages/nc-gui/components/virtual-cell/Links.vue

@ -3,6 +3,7 @@ import { computed } from '@vue/reactivity'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { ref } from 'vue' import { ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const value = inject(CellValueInj, ref(0)) const value = inject(CellValueInj, ref(0))
@ -28,7 +29,7 @@ const isOpen = ref(false)
const hideBackBtn = ref(false) const hideBackBtn = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { t } = useI18n() const { t } = useI18n()

3
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
@ -24,7 +25,7 @@ const isOpen = ref(false)
const hideBackBtn = ref(false) const hideBackBtn = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow() const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()

3
packages/nc-gui/components/virtual-cell/OneToOne.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
@ -18,7 +19,7 @@ const isForm = inject(IsFormInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false)) const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const listItemsDlg = ref(false) const listItemsDlg = ref(false)

3
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props { interface Props {
value: string | number | boolean value: string | number | boolean
@ -17,7 +18,7 @@ const emit = defineEmits(['unlink'])
const { relatedTableMeta } = useLTARStoreOrThrow()! const { relatedTableMeta } = useLTARStoreOrThrow()!
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))

3
packages/nc-gui/composables/useCalendarViewStore.ts

@ -2,6 +2,7 @@ import type { ComputedRef, Ref } from 'vue'
import type { Api, CalendarRangeType, CalendarType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk' import type { Api, CalendarRangeType, CalendarType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {useRolesWrapper} from "~/composables/useRoles";
const formatData = (list: Record<string, any>[]) => const formatData = (list: Record<string, any>[]) =>
list.map( list.map(
@ -33,7 +34,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const pageDate = ref<dayjs.Dayjs>(dayjs()) const pageDate = ref<dayjs.Dayjs>(dayjs())
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()

3
packages/nc-gui/composables/useExpandedFormStore.ts

@ -2,6 +2,7 @@ import type { AuditType, ColumnType, CommentType, TableType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {useRolesWrapper} from "~/composables/useRoles";
const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, _row: Ref<Row>) => { const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, _row: Ref<Row>) => {
const { $e, $state, $api } = useNuxtApp() const { $e, $state, $api } = useNuxtApp()
@ -61,7 +62,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
// getters // getters
const displayValue = computed(() => { const displayValue = computed(() => {

3
packages/nc-gui/composables/useKanbanViewStore.ts

@ -1,5 +1,6 @@
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { Api, ColumnType, KanbanType, SelectOptionType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk' import type { Api, ColumnType, KanbanType, SelectOptionType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
type GroupingFieldColOptionsType = SelectOptionType & { collapsed: boolean } type GroupingFieldColOptionsType = SelectOptionType & { collapsed: boolean }
@ -27,7 +28,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const { sharedView, fetchSharedViewData, fetchSharedViewGroupedData } = useSharedView() const { sharedView, fetchSharedViewData, fetchSharedViewGroupedData } = useSharedView()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false)) const isPublic = ref(shared) || inject(IsPublicInj, ref(false))

3
packages/nc-gui/composables/useMapViewDataStore.ts

@ -1,5 +1,6 @@
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { ColumnType, MapType, PaginatedType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, MapType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const formatData = (list: Record<string, any>[]) => const formatData = (list: Record<string, any>[]) =>
list.map( list.map(
@ -32,7 +33,7 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false)) const isPublic = ref(shared) || inject(IsPublicInj, ref(false))

39
packages/nc-gui/composables/useRoles/index.ts

@ -1,5 +1,5 @@
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
import type { Roles, RolesObj, WorkspaceUserRoles } from 'nocodb-sdk' import { type Roles, type RolesObj, SourceRestriction, type SourceType, type WorkspaceUserRoles } from 'nocodb-sdk'
import { extractRolesObj } from 'nocodb-sdk' import { extractRolesObj } from 'nocodb-sdk'
const hasPermission = (role: Exclude<Roles, WorkspaceUserRoles>, hasRole: boolean, permission: Permission | string) => { const hasPermission = (role: Exclude<Roles, WorkspaceUserRoles>, hasRole: boolean, permission: Permission | string) => {
@ -129,7 +129,10 @@ export const useRoles = createSharedComposable(() => {
const isUIAllowed = ( const isUIAllowed = (
permission: Permission | string, permission: Permission | string,
args: { roles?: string | Record<string, boolean> | string[] | null } = {}, args: {
roles?: string | Record<string, boolean> | string[] | null
source?: SourceType & { meta?: Record<string, any> }
} = {},
) => { ) => {
const { roles } = args const { roles } = args
@ -141,6 +144,26 @@ export const useRoles = createSharedComposable(() => {
checkRoles = extractRolesObj(roles) checkRoles = extractRolesObj(roles)
} }
// check source level restrictions
if (
sourceRestrictions[SourceRestriction.DATA_READONLY][permission] ||
sourceRestrictions[SourceRestriction.META_READONLY][permission]
) {
const source = unref(args.source || null)
if (!source) {
console.warn('Source not found', permission, new Error().stack)
return false
}
if (source?.meta?.[SourceRestriction.DATA_READONLY] && sourceRestrictions[SourceRestriction.DATA_READONLY][permission]) {
return false
}
if (source?.meta?.[SourceRestriction.META_READONLY] && sourceRestrictions[SourceRestriction.META_READONLY][permission]) {
return false
}
}
return Object.entries(checkRoles).some(([role, hasRole]) => return Object.entries(checkRoles).some(([role, hasRole]) =>
hasPermission(role as Exclude<Roles, WorkspaceUserRoles>, hasRole, permission), hasPermission(role as Exclude<Roles, WorkspaceUserRoles>, hasRole, permission),
) )
@ -148,3 +171,15 @@ export const useRoles = createSharedComposable(() => {
return { allRoles, orgRoles, workspaceRoles, baseRoles, loadRoles, isUIAllowed } return { allRoles, orgRoles, workspaceRoles, baseRoles, loadRoles, isUIAllowed }
}) })
export const useRolesWrapper = () => {
const currentSource = inject(ActiveSourceInj, ref())
const useRolesRes = useRoles()
return {
...useRolesRes,
isUIAllowed: (...args: Parameters<ReturnType<typeof useRoles>['isUIAllowed']>) => {
return useRolesRes.isUIAllowed(args[0], { source: currentSource, ...(args[1] || {}) })
},
}
}

3
packages/nc-gui/composables/useViewColumns.ts

@ -1,6 +1,7 @@
import type { ColumnType, GridColumnReqType, GridColumnType, MapType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, GridColumnReqType, GridColumnType, MapType, TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes, isHiddenCol, isSystemColumn } from 'nocodb-sdk' import { ViewTypes, isHiddenCol, isSystemColumn } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const [useProvideViewColumns, useViewColumns] = useInjectionState( const [useProvideViewColumns, useViewColumns] = useInjectionState(
( (
@ -17,7 +18,7 @@ const [useProvideViewColumns, useViewColumns] = useInjectionState(
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { isSharedBase } = storeToRefs(useBase()) const { isSharedBase } = storeToRefs(useBase())

3
packages/nc-gui/composables/useViewData.ts

@ -3,6 +3,7 @@ import axios from 'axios'
import type { Api, ColumnType, FormColumnType, FormType, GalleryType, PaginatedType, TableType, ViewType } from 'nocodb-sdk' import type { Api, ColumnType, FormColumnType, FormType, GalleryType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { NavigateDir } from '#imports' import { NavigateDir } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
const formatData = (list: Record<string, any>[]) => const formatData = (list: Record<string, any>[]) =>
list.map((row) => ({ list.map((row) => ({
@ -59,7 +60,7 @@ export function useViewData(
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const routeQuery = computed(() => route.value.query as Record<string, string>) const routeQuery = computed(() => route.value.query as Record<string, string>)

3
packages/nc-gui/composables/useViewFilters.ts

@ -9,6 +9,7 @@ import {
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
import { UITypes, isSystemColumn } from 'nocodb-sdk' import { UITypes, isSystemColumn } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
type ColumnFilterType = FilterType & { status?: string; id?: string; children?: ColumnFilterType[]; is_group?: boolean } type ColumnFilterType = FilterType & { status?: string; id?: string; children?: ColumnFilterType[]; is_group?: boolean }
@ -41,7 +42,7 @@ export function useViewFilters(
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()

3
packages/nc-gui/composables/useViewGroupBy.ts

@ -2,6 +2,7 @@ import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import {useRolesWrapper} from "~/composables/useRoles";
const excludedGroupingUidt = [UITypes.Attachment, UITypes.QrCode, UITypes.Barcode] const excludedGroupingUidt = [UITypes.Attachment, UITypes.QrCode, UITypes.Barcode]
@ -48,7 +49,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
const isGroupBy = computed(() => !!groupBy.value.length) const isGroupBy = computed(() => !!groupBy.value.length)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()

2
packages/nc-gui/composables/useViewSorts.ts

@ -8,7 +8,7 @@ export function useViewSorts(view: Ref<ViewType | undefined>, reloadData?: () =>
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRolesWrapper()
const { isSharedBase } = storeToRefs(useBase()) const { isSharedBase } = storeToRefs(useBase())

9
packages/nc-gui/context/index.ts

@ -1,4 +1,4 @@
import type { ColumnType, FilterType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, FilterType, SourceType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, InjectionKey, Ref } from 'vue' import type { ComputedRef, InjectionKey, Ref } from 'vue'
import type { EventHook } from '@vueuse/core' import type { EventHook } from '@vueuse/core'
import type { PageSidebarNode } from '#imports' import type { PageSidebarNode } from '#imports'
@ -60,3 +60,10 @@ export const JsonExpandInj: InjectionKey<Ref<boolean>> = Symbol('json-expand-inj
export const AllFiltersInj: InjectionKey<Ref<Record<string, FilterType[]>>> = Symbol('all-filters-injection') export const AllFiltersInj: InjectionKey<Ref<Record<string, FilterType[]>>> = Symbol('all-filters-injection')
export const IsAdminPanelInj: InjectionKey<Ref<boolean>> = Symbol('is-admin-panel-injection') export const IsAdminPanelInj: InjectionKey<Ref<boolean>> = Symbol('is-admin-panel-injection')
export const ActiveSourceInj: InjectionKey<
ComputedRef<
SourceType & {
meta?: Record<string, any>
}
>
> = Symbol('active-source-injection')

8
packages/nocodb-sdk/src/lib/enums.ts

@ -326,3 +326,11 @@ export enum APIContext {
FILTERS = 'filters', FILTERS = 'filters',
SORTS = 'sorts', SORTS = 'sorts',
} }
export enum SourceRestriction {
META_READONLY = 'metaReadOnly',
DATA_READONLY = 'dataReadOnly',
}

7
packages/nocodb/src/services/public-datas.service.ts

@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { populateUniqueFileName, UITypes, ViewTypes } from 'nocodb-sdk'; import {populateUniqueFileName, SourceRestriction, UITypes, ViewTypes} from 'nocodb-sdk';
import slash from 'slash'; import slash from 'slash';
import { nocoExecute } from 'nc-help'; import { nocoExecute } from 'nc-help';
@ -293,6 +293,11 @@ export class PublicDatasService {
}); });
const source = await Source.get(context, model.source_id); const source = await Source.get(context, model.source_id);
if (source?.meta?.[SourceRestriction.DATA_READONLY]) {
NcError.forbidden('Data insert is restricted');
}
const base = await source.getProject(context); const base = await source.getProject(context);
const baseModel = await Model.getBaseModelSQL(context, { const baseModel = await Model.getBaseModelSQL(context, {

16
packages/nocodb/src/utils/acl.ts

@ -450,4 +450,20 @@ Object.values(rolePermissions).forEach((role) => {
} }
}); });
// excluded/restricted permissions at source level based on source restriction
// `true` means permission is restricted and `false`/missing means permission is allowed
export const sourceRestrictions = {
[SourceRestriction.DATA_READONLY]: {
dataInsert: true,
dataEdit: true,
dataDelete: true,
},
[SourceRestriction.META_READONLY]: {
tableCreate: true,
tableRename: true,
tableDelete: true,
tableDuplicate: true,
},
}
export default rolePermissions; export default rolePermissions;

Loading…
Cancel
Save