Browse Source

fix: revise frontend acl

pull/6399/head
mertmit 1 year ago
parent
commit
a930f7d242
  1. 6
      packages/nc-gui/components/account/UserList.vue
  2. 6
      packages/nc-gui/components/account/UsersModal.vue
  3. 26
      packages/nc-gui/components/cell/MultiSelect.vue
  4. 25
      packages/nc-gui/components/cell/SingleSelect.vue
  5. 6
      packages/nc-gui/components/cell/attachment/Modal.vue
  6. 2
      packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue
  7. 10
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  8. 14
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  9. 2
      packages/nc-gui/components/dashboard/TreeView/index.vue
  10. 2
      packages/nc-gui/components/general/AddBaseButton.vue
  11. 9
      packages/nc-gui/components/general/PreviewAs.vue
  12. 10
      packages/nc-gui/components/project/InviteProjectCollabSection.vue
  13. 4
      packages/nc-gui/components/project/View.vue
  14. 4
      packages/nc-gui/components/smartsheet/Form.vue
  15. 2
      packages/nc-gui/components/smartsheet/Gallery.vue
  16. 2
      packages/nc-gui/components/smartsheet/Kanban.vue
  17. 2
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  18. 8
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  19. 16
      packages/nc-gui/components/smartsheet/grid/Table.vue
  20. 8
      packages/nc-gui/components/smartsheet/header/Cell.vue
  21. 2
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  22. 4
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  23. 2
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  24. 2
      packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue
  25. 2
      packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue
  26. 2
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  27. 2
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  28. 2
      packages/nc-gui/components/tabs/Smartsheet.vue
  29. 11
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  30. 19
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  31. 2
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  32. 2
      packages/nc-gui/components/virtual-cell/HasMany.vue
  33. 2
      packages/nc-gui/components/virtual-cell/Links.vue
  34. 2
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  35. 2
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  36. 16
      packages/nc-gui/components/workspace/ProjectList.vue
  37. 5
      packages/nc-gui/composables/useGlobal/types.ts
  38. 2
      packages/nc-gui/composables/useGridViewColumn.ts
  39. 2
      packages/nc-gui/composables/useKanbanViewStore.ts
  40. 2
      packages/nc-gui/composables/useMapViewDataStore.ts
  41. 21
      packages/nc-gui/composables/useRoles/index.ts
  42. 26
      packages/nc-gui/composables/useUIPermission/index.ts
  43. 4
      packages/nc-gui/composables/useViewColumns.ts
  44. 2
      packages/nc-gui/composables/useViewData.ts
  45. 2
      packages/nc-gui/layouts/base.vue
  46. 2
      packages/nc-gui/layouts/new.vue
  47. 155
      packages/nc-gui/lib/constants.ts
  48. 21
      packages/nc-gui/lib/enums.ts
  49. 31
      packages/nc-gui/lib/types.ts
  50. 2
      packages/nc-gui/pages/account/index.vue
  51. 2
      packages/nc-gui/store/projectsShortcuts.ts
  52. 5
      packages/nc-gui/store/share.ts
  53. 1
      packages/nc-gui/utils/index.ts
  54. 15
      packages/nc-gui/utils/userUtils.ts
  55. 10
      packages/nocodb-sdk/src/lib/globals.ts
  56. 9
      packages/nocodb-sdk/src/lib/helperFunctions.ts

6
packages/nc-gui/components/account/UserList.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk'
import type { OrgUserReqType, RequestParams, Roles, UserType } from 'nocodb-sdk'
import type { User } from '#imports'
import { Role, extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useNuxtApp } from '#imports'
import { extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useNuxtApp } from '#imports'
const { api, isLoading } = useApi()
@ -58,7 +58,7 @@ const loadUsers = async (page = currentPage.value, limit = currentLimit.value) =
loadUsers()
const updateRole = async (userId: string, roles: Role) => {
const updateRole = async (userId: string, roles: Roles) => {
try {
await api.orgUsers.update(userId, {
roles,

6
packages/nc-gui/components/account/UsersModal.vue

@ -1,10 +1,10 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import type { OrgUserReqType } from 'nocodb-sdk'
import { OrgUserRoles } from 'nocodb-sdk'
import type { User, Users } from '#imports'
import {
Form,
Role,
computed,
emailValidator,
extractSdkResponseErrorMsg,
@ -34,7 +34,7 @@ const { copy } = useCopy()
const { dashboardUrl } = useDashboard()
const usersData = ref<Users>({ emails: '', role: Role.OrgLevelViewer, invitationToken: undefined })
const usersData = ref<Users>({ emails: '', role: OrgUserRoles.VIEWER, invitationToken: undefined })
const formRef = ref()
@ -90,7 +90,7 @@ const copyUrl = async () => {
const clickInviteMore = () => {
$e('c:user:invite-more')
usersData.value.invitationToken = undefined
usersData.value.role = Role.OrgLevelViewer
usersData.value.role = OrgUserRoles.VIEWER
usersData.value.emails = ''
}

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

@ -4,7 +4,6 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk'
import { WorkspaceUserRoles } from 'nocodb-sdk'
import {
ActiveCellInj,
CellClickHookInj,
@ -27,8 +26,8 @@ import {
useEventListener,
useMetas,
useProject,
useRoles,
useSelectedCellKeyupListener,
useUIPermission,
watch,
} from '#imports'
import MdiCloseCircle from '~icons/mdi/close-circle'
@ -80,7 +79,7 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { hasRole } = useRoles()
const { isUIAllowed } = useUIPermission()
const { isPg, isMysql } = useProject()
@ -105,15 +104,7 @@ const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const hasEditRoles = computed(
() =>
hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole('editor', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true) ||
hasRole(WorkspaceUserRoles.EDITOR, true),
)
const hasEditRoles = computed(() => isUIAllowed('dataEdit'))
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
@ -423,16 +414,7 @@ const selectedOpts = computed(() => {
</a-select-option>
<a-select-option
v-if="
searchVal &&
isOptionMissing &&
!isPublic &&
!disableOptionCreation &&
(hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true))
"
v-if="searchVal && isOptionMissing && !isPublic && !disableOptionCreation && isUIAllowed('fieldEdit')"
:key="searchVal"
:value="searchVal"
>

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

@ -4,7 +4,6 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType } from 'nocodb-sdk'
import { WorkspaceUserRoles } from 'nocodb-sdk'
import {
ActiveCellInj,
CellClickHookInj,
@ -23,8 +22,8 @@ import {
ref,
useEventListener,
useProject,
useRoles,
useSelectedCellKeyupListener,
useUIPermission,
watch,
} from '#imports'
@ -70,7 +69,7 @@ const searchVal = ref()
const { getMeta } = useMetas()
const { hasRole } = useRoles()
const { isUIAllowed } = useUIPermission()
const { isPg, isMysql } = useProject()
@ -78,15 +77,7 @@ const { isPg, isMysql } = useProject()
// temporary until it's add the option to column meta
const tempSelectedOptState = ref<string>()
const isNewOptionCreateEnabled = computed(
() =>
!isPublic.value &&
!disableOptionCreation &&
(hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true)),
)
const isNewOptionCreateEnabled = computed(() => !isPublic.value && !disableOptionCreation && isUIAllowed('fieldEdit'))
const options = computed<(SelectOptionType & { value: string })[]>(() => {
if (column?.value.colOptions) {
@ -106,15 +97,7 @@ const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const hasEditRoles = computed(
() =>
hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole('editor', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true) ||
hasRole(WorkspaceUserRoles.EDITOR, true),
)
const hasEditRoles = computed(() => isUIAllowed('dataEdit'))
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)

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

@ -96,7 +96,7 @@ const handleFileDelete = (i: number) => {
<template #title>
<div class="flex gap-4">
<div
v-if="isSharedForm || (!readOnly && isUIAllowed('tableAttachment') && !isPublic && !isLocked)"
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)"
class="nc-attach-file group"
data-testid="attachment-expand-file-picker-button"
@click="open"
@ -141,7 +141,7 @@ const handleFileDelete = (i: number) => {
<template #title> Remove File </template>
<component
:is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('tableAttachment') && !isPublic && !isLocked)"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)"
class="nc-attachment-remove"
@click.stop="onRemoveFileClick(item.title, i)"
/>
@ -156,7 +156,7 @@ const handleFileDelete = (i: number) => {
</a-tooltip>
<a-tooltip
v-if="isSharedForm || (!readOnly && isUIAllowed('tableAttachment') && !isPublic && !isLocked)"
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)"
placement="bottom"
>
<template #title> Rename File </template>

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

@ -116,7 +116,7 @@ function openTableCreateMagicDialog(baseId?: string) {
<template>
<div
v-if="isUIAllowed('table-create', false, projectRole)"
v-if="isUIAllowed('tableCreate', false, projectRole)"
class="group flex items-center gap-2 pl-2 pr-4.75 py-1 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="emit('openTableCreateDialog')"
>

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

@ -469,7 +469,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
<GeneralIcon icon="copy" class="group-hover:text-black" />
{{ $t('activity.account.projInfo') }}
</NcMenuItem>
<NcMenuItem v-if="isUIAllowed('duplicateProject', true, projectRole)" @click="duplicateProject(project)">
<NcMenuItem v-if="isUIAllowed('projectDuplicate', true, projectRole)" @click="duplicateProject(project)">
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }}
</NcMenuItem>
@ -493,7 +493,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</template>
<!-- Team & Settings -->
<NcMenuItem
v-if="isUIAllowed('settings')"
v-if="isUIAllowed('settingsPage')"
key="teamAndSettings"
v-e="['c:navdraw:project-settings']"
class="nc-sidebar-project-project-settings"
@ -676,7 +676,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
<template v-else-if="contextMenuTarget.type === 'base'"></template>
<template v-else-if="contextMenuTarget.type === 'table'">
<NcMenuItem v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(contextMenuTarget.value, true)">
<NcMenuItem v-if="isUIAllowed('tableRename')" @click="openRenameTableDialog(contextMenuTarget.value, true)">
<div class="nc-project-option-item">
<GeneralIcon icon="edit" class="text-gray-700" />
{{ $t('general.rename') }}
@ -684,7 +684,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('table-duplicate') && (contextMenuBase?.is_meta || contextMenuBase?.is_local)"
v-if="isUIAllowed('tableDuplicate') && (contextMenuBase?.is_meta || contextMenuBase?.is_local)"
@click="duplicateTable(contextMenuTarget.value)"
>
<div class="nc-project-option-item">
@ -693,7 +693,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</div>
</NcMenuItem>
<NcMenuItem v-if="isUIAllowed('table-delete')" @click="isTableDeleteDialogVisible = true">
<NcMenuItem v-if="isUIAllowed('tableDelete')" @click="isTableDeleteDialogVisible = true">
<div class="nc-project-option-item text-red-600">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}

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

@ -71,7 +71,7 @@ const { isSharedBase } = useProject()
// const isMultiBase = computed(() => project.bases && project.bases.length > 1)
const canUserEditEmote = computed(() => {
return isUIAllowed('tableIconCustomisation', false, projectRole?.value)
return isUIAllowed('tableIconEdit', false, projectRole?.value)
})
</script>
@ -125,7 +125,7 @@ const canUserEditEmote = computed(() => {
v-if="table.type === 'table'"
class="flex w-5 !text-gray-500 text-sm"
:class="{
'group-hover:text-gray-500': isUIAllowed('treeview-drag-n-drop', false, projectRole),
'group-hover:text-gray-500': isUIAllowed('tableSort', false, projectRole),
'!text-black': openedTableId === table.id,
}"
/>
@ -133,7 +133,7 @@ const canUserEditEmote = computed(() => {
v-else
class="flex w-5 !text-gray-500 text-sm"
:class="{
'group-hover:text-gray-500': isUIAllowed('treeview-drag-n-drop', false, projectRole),
'group-hover:text-gray-500': isUIAllowed('tableSort', false, projectRole),
'!text-black': openedTableId === table.id,
}"
/>
@ -157,7 +157,7 @@ const canUserEditEmote = computed(() => {
<NcDropdown
v-if="
!isSharedBase && (isUIAllowed('table-rename', false, projectRole) || isUIAllowed('table-delete', false, projectRole))
!isSharedBase && (isUIAllowed('tableRename', false, projectRole) || isUIAllowed('tableDelete', false, projectRole))
"
:trigger="['click']"
@click.stop
@ -173,7 +173,7 @@ const canUserEditEmote = computed(() => {
<template #overlay>
<NcMenu>
<NcMenuItem
v-if="isUIAllowed('table-rename', false, projectRole)"
v-if="isUIAllowed('tableRename', false, projectRole)"
:data-testid="`sidebar-table-rename-${table.title}`"
@click="openRenameTableDialog(table, project.bases[baseIndex].id)"
>
@ -183,7 +183,7 @@ const canUserEditEmote = computed(() => {
<NcMenuItem
v-if="
isUIAllowed('table-duplicate') &&
isUIAllowed('tableDuplicate') &&
project.bases?.[baseIndex] &&
(project.bases[baseIndex].is_meta || project.bases[baseIndex].is_local)
"
@ -195,7 +195,7 @@ const canUserEditEmote = computed(() => {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('table-delete', false, projectRole)"
v-if="isUIAllowed('tableDelete', false, projectRole)"
:data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50"
@click="isTableDeleteDialogVisible = true"

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

@ -145,7 +145,7 @@ const duplicateTable = async (table: TableType) => {
const isCreateTableAllowed = computed(
() =>
isUIAllowed('table-create') &&
isUIAllowed('tableCreate') &&
route.value.name !== 'index' &&
route.value.name !== 'index-index' &&
route.value.name !== 'index-index-create' &&

2
packages/nc-gui/components/general/AddBaseButton.vue

@ -10,7 +10,7 @@ const toggleDialog = inject(ToggleDialogInj, () => {})
<template>
<div
v-if="isUIAllowed('settings')"
v-if="isUIAllowed('settingsPage')"
class="flex items-center w-full pl-3 hover:(text-primary bg-primary bg-opacity-5)"
@click="toggleDialog(true, undefined, undefined, projectId)"
>

9
packages/nc-gui/components/general/PreviewAs.vue

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { ProjectRole, iconMap, onUnmounted, ref, useEventListener, useGlobal, useI18n, useNuxtApp, watch } from '#imports'
import { ProjectRoles } from 'nocodb-sdk'
import { iconMap, onUnmounted, ref, useEventListener, useGlobal, useI18n, useNuxtApp, watch } from '#imports'
import MdiAccountStar from '~icons/mdi/account-star'
import MdiAccountHardHat from '~icons/mdi/account-hard-hat'
import PhPencilCircleThin from '~icons/ph/pencil-circle-thin'
@ -14,9 +15,9 @@ const { t } = useI18n()
const { previewAs } = useGlobal()
const roleList = [
{ value: ProjectRole.Editor, label: t('objects.roleType.editor') },
{ value: ProjectRole.Commenter, label: t('objects.roleType.commenter') },
{ value: ProjectRole.Viewer, label: t('objects.roleType.viewer') },
{ value: ProjectRoles.EDITOR, label: t('objects.roleType.editor') },
{ value: ProjectRoles.COMMENTER, label: t('objects.roleType.commenter') },
{ value: ProjectRoles.VIEWER, label: t('objects.roleType.viewer') },
]
const roleIcon = {

10
packages/nc-gui/components/project/InviteProjectCollabSection.vue

@ -130,19 +130,19 @@ const copyUrl = async () => {
{{ role }}
</div>
</div> -->
<NcBadge v-if="role === ProjectRole.Owner" color="purple">
<NcBadge v-if="role === ProjectRoles.OWNER" color="purple">
<p class="badge-text">{{ role }}</p>
</NcBadge>
<NcBadge v-if="role === ProjectRole.Creator" color="blue">
<NcBadge v-if="role === ProjectRoles.CREATOR" color="blue">
<p class="badge-text">{{ role }}</p>
</NcBadge>
<NcBadge v-if="role === ProjectRole.Editor" color="green">
<NcBadge v-if="role === ProjectRoles.EDITOR" color="green">
<p class="badge-text">{{ role }}</p>
</NcBadge>
<NcBadge v-if="role === ProjectRole.Commenter" color="orange">
<NcBadge v-if="role === ProjectRoles.COMMENTER" color="orange">
<p class="badge-text">{{ role }}</p>
</NcBadge>
<NcBadge v-if="role === ProjectRole.Viewer" color="yellow">
<NcBadge v-if="role === ProjectRoles.VIEWER" color="yellow">
<p class="badge-text">{{ role }}</p>
</NcBadge>
</a-select-option>

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

@ -97,7 +97,7 @@ watch(
<!-- <a-tab-pane v-if="defaultBase" key="erd" tab="Project ERD" force-render class="pt-4 pb-12">
<ErdView :base-id="defaultBase!.id" class="!h-full" />
</a-tab-pane> -->
<a-tab-pane v-if="isUIAllowed('shareProject')" key="collaborator">
<a-tab-pane v-if="isUIAllowed('newUser')" key="collaborator">
<template #tab>
<div class="tab-title" data-testid="proj-view-tab__access-settings">
<GeneralIcon icon="users" class="!h-3.5 !w-3.5" />
@ -106,7 +106,7 @@ watch(
</template>
<ProjectAccessSettings />
</a-tab-pane>
<a-tab-pane v-if="isUIAllowed('createBase')" key="data-source">
<a-tab-pane v-if="isUIAllowed('baseCreate')" key="data-source">
<template #tab>
<div class="tab-title" data-testid="proj-view-tab__data-sources">
<GeneralIcon icon="database" />

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

@ -48,7 +48,7 @@ const formState = reactive({})
const secondsRemain = ref(0)
const isEditable = isUIAllowed('editFormView' as Permission)
const isEditable = isUIAllowed('viewFieldEdit' as Permission)
const meta = inject(MetaInj, ref())
@ -592,7 +592,7 @@ watch(view, (nextView) => {
@click="activeRow = element.title"
>
<div
v-if="isUIAllowed('editFormView') && !isRequired(element, element.required)"
v-if="isUIAllowed('viewFieldEdit') && !isRequired(element, element.required)"
class="absolute flex top-2 right-2"
>
<component

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

@ -85,7 +85,7 @@ const isRowEmpty = (record: any, col: any) => {
}
const { isUIAllowed } = useUIPermission()
const hasEditPermission = computed(() => isUIAllowed('xcDatatableEditable'))
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
// TODO: extract this code (which is duplicated in grid and gallery) into a separate component
const _contextMenu = ref(false)
const contextMenu = computed({

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

@ -103,7 +103,7 @@ provide(IsGridInj, ref(false))
provide(IsKanbanInj, ref(true))
const hasEditPermission = computed(() => isUIAllowed('xcDatatableEditable'))
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const fields = inject(FieldsInj, ref([]))

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

@ -20,7 +20,7 @@ const { user } = useGlobal()
const { isUIAllowed } = useUIPermission()
const hasEditPermission = computed(() => isUIAllowed('commentEditable'))
const hasEditPermission = computed(() => isUIAllowed('commentEdit'))
// currently, edit option is disable on purpose
// since the current update wouldn't keep track of the previous values

8
packages/nc-gui/components/smartsheet/expanded-form/Header.vue

@ -62,7 +62,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
if (cmdOrCtrl) {
switch (e.key) {
case 'Enter': {
if (isUIAllowed('tableRowUpdate')) {
if (isUIAllowed('dataEdit')) {
await save()
}
}
@ -129,7 +129,7 @@ const onConfirmDeleteRowClick = async () => {
class="nc-expand-form-save-btn !w-[60px]"
type="primary"
size="small"
:disabled="!isUIAllowed('tableRowUpdate')"
:disabled="!isUIAllowed('dataEdit')"
@click="save"
>
{{ $t('general.save') }}
@ -148,7 +148,7 @@ const onConfirmDeleteRowClick = async () => {
{{ $t('general.reload') }}
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && emit('duplicateRow')">
<a-menu-item v-if="isUIAllowed('dataEdit') && !isNew" @click="!isNew && emit('duplicateRow')">
<div v-e="['c:row-expand:duplicate']" class="py-2 flex gap-2 a">
<component
:is="iconMap.copy"
@ -157,7 +157,7 @@ const onConfirmDeleteRowClick = async () => {
{{ $t('activity.duplicateRow') }}
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && onDeleteRowClick()">
<a-menu-item v-if="isUIAllowed('dataEdit') && !isNew" @click="!isNew && onDeleteRowClick()">
<div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center">
<component
:is="iconMap.delete"

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

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { nextTick } from '@vue/runtime-core'
import type { ColumnReqType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes, ViewTypes, WorkspaceUserRoles, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { UITypes, ViewTypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import {
ActiveViewInj,
CellUrlDisableOverlayInj,
@ -30,7 +30,6 @@ import {
useI18n,
useMultiSelect,
useNuxtApp,
useRoles,
useRoute,
useSmartsheetStoreOrThrow,
useUIPermission,
@ -172,10 +171,9 @@ const cellRefs = ref<{ el: HTMLElement }[]>([])
const gridRect = useElementBounding(gridWrapper)
// #Permissions
const { hasRole } = useRoles()
const { isUIAllowed } = useUIPermission()
const hasEditPermission = computed(() => isUIAllowed('xcDatatableEditable'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('add-column') && !isSqlView.value)
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)
// #Variables
const addColumnDropdown = ref(false)
@ -1350,13 +1348,7 @@ const expandAndLooseFocus = (row: Row, col: Record<string, any>) => {
<span class="flex-1" />
<div
v-if="
!readOnly ||
hasRole('commenter', true) ||
hasRole('viewer', true) ||
hasRole(WorkspaceUserRoles.COMMENTER, true) ||
hasRole(WorkspaceUserRoles.VIEWER, true)
"
v-if="isUIAllowed('expandedForm')"
class="nc-expand"
:data-testid="`nc-expand-${rowIndex}`"
:class="{ 'nc-comment': row.rowMeta?.commentCount }"

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

@ -42,13 +42,13 @@ const closeAddColumnDropdown = () => {
}
const openHeaderMenu = () => {
if (!isForm.value && !isExpandedForm.value && isUIAllowed('edit-column')) {
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit')) {
editColumnDropdown.value = true
}
}
const openDropDown = () => {
if (isForm.value || isExpandedForm.value || !isUIAllowed('edit-column')) return
if (isForm.value || isExpandedForm.value || !isUIAllowed('fieldEdit')) return
isDropDownOpen.value = !isDropDownOpen.value
}
</script>
@ -65,7 +65,7 @@ const openDropDown = () => {
<div
v-if="column"
class="name pl-1 !truncate"
:class="{ 'cursor-pointer pt-0.25': !isForm && isUIAllowed('edit-column') && !hideMenu }"
:class="{ 'cursor-pointer pt-0.25': !isForm && isUIAllowed('fieldEdit') && !hideMenu }"
style="white-space: pre-line"
:title="column.title"
>
@ -77,7 +77,7 @@ const openDropDown = () => {
<template v-if="!hideMenu">
<div class="flex-1" />
<LazySmartsheetHeaderMenu
v-if="!isForm && !isExpandedForm && isUIAllowed('edit-column')"
v-if="!isForm && !isExpandedForm && isUIAllowed('fieldEdit')"
v-model:is-open="isDropDownOpen"
@add-column="addField"
@edit="openHeaderMenu"

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

@ -149,7 +149,7 @@ const closeAddColumnDropdown = () => {
<div class="flex-1" />
<LazySmartsheetHeaderMenu
v-if="!isForm && isUIAllowed('edit-column') && !isExpandedForm"
v-if="!isForm && isUIAllowed('fieldEdit') && !isExpandedForm"
v-model:is-open="isDropDownOpen"
:virtual="true"
@add-column="addField"

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

@ -59,7 +59,7 @@ const onClick = useDebounceFn(() => {
/** Enable editing view name on dbl click */
function onDblClick() {
if (!isUIAllowed('virtualViewsCreateOrEdit')) return
if (!isUIAllowed('viewCreateOrEdit')) return
if (!isEditing.value) {
isEditing.value = true
@ -222,7 +222,7 @@ watch(rightSidebarState, () => {
<div class="flex-1" />
<template v-if="!isEditing && !isLocked && isUIAllowed('virtualViewsCreateOrEdit')">
<template v-if="!isEditing && !isLocked && isUIAllowed('viewCreateOrEdit')">
<NcDropdown v-model:visible="isDropdownOpen" overlay-class-name="!rounded-lg">
<div
class="invisible !group-hover:visible"

2
packages/nc-gui/components/smartsheet/sidebar/index.vue

@ -203,7 +203,7 @@ function onOpenModal({
</div>
<LazySmartsheetSidebarMenuTop v-else :views="views" @open-modal="onOpenModal" @deleted="loadViews" />
</div>
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="flex flex-col">
<div v-if="isUIAllowed('viewCreateOrEdit')" class="flex flex-col">
<div class="!mb-3 w-full border-b-1 border-gray-200" />
<div v-if="!activeTable" class="flex flex-col pt-2 pb-5 px-6">

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

@ -33,7 +33,7 @@ provide(IsKanbanInj, ref(true))
<template>
<a-dropdown
v-if="!IsPublic && isUIAllowed('edit-column')"
v-if="!IsPublic && isUIAllowed('fieldEdit')"
v-model:visible="open"
:trigger="['click']"
overlay-class-name="nc-dropdown-kanban-add-edit-stack-menu"

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

@ -146,7 +146,7 @@ const exportFile = async (exportType: ExportTypes) => {
</div>
<div
v-if="isUIAllowed('sharedViewList') && !isView && !isPublicView"
v-if="isUIAllowed('shareView') && !isView && !isPublicView"
v-e="['a:actions:shared-view-list']"
class="nc-menu-item"
@click="sharedViewListDlg = true"

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

@ -249,7 +249,7 @@ watch(shared, () => {
<template>
<div>
<a-button
v-if="isUIAllowed('share-view') && !isSharedBase"
v-if="isUIAllowed('shareView') && !isSharedBase"
v-e="['c:view:share']"
outlined
class="nc-btn-share-view nc-toolbar-btn"

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

@ -161,7 +161,7 @@ useMenuCloseOnEsc(open)
</a-sub-menu>
<a-sub-menu
v-if="isUIAllowed('view-type')"
v-if="isUIAllowed('viewCreateOrEdit')"
key="lock-type"
class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0"
>

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

@ -76,7 +76,7 @@ provide(IsFormInj, isForm)
provide(TabMetaInj, activeTab)
provide(
ReadonlyInj,
computed(() => !isUIAllowed('xcDatatableEditable')),
computed(() => !isUIAllowed('dataEdit')),
)
const grid = ref()

11
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -1,12 +1,11 @@
<script setup lang="ts">
import { OrgUserRoles } from 'nocodb-sdk'
import { OrgUserRoles, RoleColors } from 'nocodb-sdk'
import type { ProjectUserReqType, RequestParams } from 'nocodb-sdk'
import {
extractSdkResponseErrorMsg,
iconMap,
message,
onBeforeMount,
projectRoleTagColors,
ref,
storeToRefs,
useApi,
@ -278,15 +277,11 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<div
v-if="isSuperAdmin(user)"
class="rounded-full px-3 py-1 nc-user-role"
:style="{ backgroundColor: projectRoleTagColors[OrgUserRoles.SUPER_ADMIN] }"
:style="{ backgroundColor: RoleColors[OrgUserRoles.SUPER_ADMIN] }"
>
Super Admin
</div>
<div
v-if="user.roles"
class="rounded-full px-3 py-1 nc-user-role"
:style="{ backgroundColor: projectRoleTagColors[user.roles] }"
>
<div v-if="user.roles" class="rounded-full px-3 py-1 nc-user-role" :style="{ backgroundColor: RoleColors[user.roles] }">
{{ $t(`objects.roleType.${user.roles}`) }}
</div>
</div>

19
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -1,17 +1,15 @@
<script setup lang="ts">
import type { Input } from 'ant-design-vue'
import { ProjectRoles, RoleColors } from 'nocodb-sdk'
import type { ProjectUserReqType } from 'nocodb-sdk'
import {
Form,
ProjectRole,
computed,
emailValidator,
extractSdkResponseErrorMsg,
iconMap,
message,
onMounted,
projectRoleTagColors,
projectRoles,
ref,
storeToRefs,
useActiveKeyupListener,
@ -44,7 +42,7 @@ const { copy } = useCopy()
const { dashboardUrl } = useDashboard()
const usersData = ref<Users>({ emails: undefined, role: ProjectRole.Viewer, invitationToken: undefined })
const usersData = ref<Users>({ emails: undefined, role: ProjectRoles.VIEWER, invitationToken: undefined })
const formRef = ref()
@ -68,7 +66,7 @@ onMounted(() => {
const close = () => {
emit('closed')
usersData.value = { role: ProjectRole.Viewer }
usersData.value = { role: ProjectRoles.VIEWER }
}
const saveUser = async () => {
@ -81,7 +79,7 @@ const saveUser = async () => {
try {
if (selectedUser?.id) {
await $api.auth.projectUserUpdate(project.value.id, selectedUser.id, {
roles: usersData.value.role as ProjectRole,
roles: usersData.value.role as ProjectRoles,
email: selectedUser.email,
project_id: project.value.id,
projectName: project.value.title,
@ -127,7 +125,7 @@ const copyUrl = async () => {
const clickInviteMore = () => {
$e('c:user:invite-more')
usersData.value.invitationToken = undefined
usersData.value.role = ProjectRole.Viewer
usersData.value.role = ProjectRoles.VIEWER
usersData.value.emails = undefined
}
@ -244,12 +242,9 @@ watch(
class="nc-user-roles !rounded-md"
dropdown-class-name="nc-dropdown-user-role"
>
<a-select-option v-for="(role, index) in projectRoles" :key="index" :value="role" class="nc-role-option">
<a-select-option v-for="(role, index) in ProjectRoles" :key="index" :value="role" class="nc-role-option">
<div class="flex flex-row h-full justify-start items-center">
<div
class="px-3 py-1 flex rounded-full text-xs"
:style="{ backgroundColor: projectRoleTagColors[role] }"
>
<div class="px-3 py-1 flex rounded-full text-xs" :style="{ backgroundColor: RoleColors[role] }">
{{ role }}
</div>
</div>

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

@ -103,7 +103,7 @@ const belongsToColumn = computed(
</div>
<div
v-if="!readOnly && !isLocked && (isUIAllowed('xcDatatableEditable') || isForm) && !isUnderLookup"
v-if="!readOnly && !isLocked && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup"
class="flex justify-end gap-1 min-h-[30px] items-center"
>
<GeneralIcon

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

@ -129,7 +129,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
/>
<GeneralIcon
v-if="(!readOnly && isUIAllowed('xcDatatableEditable')) || isForm"
v-if="(!readOnly && isUIAllowed('dataEdit')) || isForm"
icon="plus"
class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus"
@click.stop="listItemsDlg = true"

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

@ -108,7 +108,7 @@ const localCellValue = computed<any[]>(() => {
<div v-if="!isLocked && !isUnderLookup" class="flex justify-end hidden group-hover:flex items-center">
<MdiPlus
v-if="(!readOnly && isUIAllowed('xcDatatableEditable')) || isForm"
v-if="(!readOnly && isUIAllowed('dataEdit')) || isForm"
class="select-none !text-md text-gray-700 nc-action-icon nc-plus"
@click.stop="listItemsDlg = true"
/>

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

@ -131,7 +131,7 @@ const m2mColumn = computed(
/>
<GeneralIcon
v-if="!readOnly && isUIAllowed('xcDatatableEditable')"
v-if="!readOnly && isUIAllowed('dataEdit')"
icon="plus"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus"
@click.stop="listItemsDlg = true"

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

@ -97,7 +97,7 @@ export default {
<div
v-show="active || isForm"
v-if="showUnlinkButton && !readOnly && !isLocked && isUIAllowed('xcDatatableEditable')"
v-if="showUnlinkButton && !readOnly && !isLocked && isUIAllowed('dataEdit')"
class="flex items-center"
>
<component

16
packages/nc-gui/components/workspace/ProjectList.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Empty } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { ProjectStatus, WorkspaceUserRoles } from 'nocodb-sdk'
import { ProjectRoles, ProjectStatus, WorkspaceUserRoles } from 'nocodb-sdk'
import { nextTick } from '@vue/runtime-core'
import { NcProjectType, isEeUI, navigateTo, storeToRefs, timeAgo, useGlobal, useWorkspace } from '#imports'
import { useNuxtApp } from '#app'
@ -40,11 +40,11 @@ const roleAlias = {
[WorkspaceUserRoles.CREATOR]: 'Workspace Creator',
[WorkspaceUserRoles.EDITOR]: 'Workspace Editor',
[WorkspaceUserRoles.COMMENTER]: 'Workspace Commenter',
[ProjectRole.Creator]: 'Project Creator',
[ProjectRole.Editor]: 'Project Editor',
[ProjectRole.Viewer]: 'Project Viewer',
[ProjectRole.Commenter]: 'Project Commenter',
[ProjectRole.Owner]: 'Project Owner',
[ProjectRoles.CREATOR]: 'Project Creator',
[ProjectRoles.EDITOR]: 'Project Editor',
[ProjectRoles.VIEWER]: 'Project Viewer',
[ProjectRoles.COMMENTER]: 'Project Commenter',
[ProjectRoles.OWNER]: 'Project Owner',
}
const deleteProject = (project: ProjectType) => {
@ -346,7 +346,7 @@ const setIcon = async (icon: string, project: ProjectType) => {
<a-menu-item
v-if="
record.type === NcProjectType.DB &&
isUIAllowed('duplicateProject', true, [record.workspace_role, record.project_role].join())
isUIAllowed('projectDuplicate', true, [record.workspace_role, record.project_role].join())
"
@click="duplicateProject(record)"
>
@ -357,7 +357,7 @@ const setIcon = async (icon: string, project: ProjectType) => {
</a-menu-item>
<!--
<a-menu-item
v-if="false && isUIAllowed('moveProject', true, [record.workspace_role, record.project_role].join())"
v-if="false && isUIAllowed('projectMove', true, [record.workspace_role, record.project_role].join())"
@click="moveProject(record)"
>
<div class="nc-menu-item-wrapper">

5
packages/nc-gui/composables/useGlobal/types.ts

@ -1,7 +1,8 @@
import type { ComputedRef, Ref, ToRefs } from 'vue'
import type { WritableComputedRef } from '@vue/reactivity'
import type { JwtPayload } from 'jwt-decode'
import type { Language, NcProjectType, ProjectRole, User, useCounter } from '#imports'
import type { ProjectRoles } from 'nocodb-sdk'
import type { Language, NcProjectType, User, useCounter } from '#imports'
export interface AppInfo {
ncSiteUrl: string
@ -36,7 +37,7 @@ export interface StoredState {
lang: keyof typeof Language
darkMode: boolean
filterAutoSave: boolean
previewAs: ProjectRole | null
previewAs: ProjectRoles | null
includeM2M: boolean
showNull: boolean
currentVersion: string | null

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

@ -99,7 +99,7 @@ const [useProvideGridViewColumn, useGridViewColumn] = useInjectionState(
}
// sync with server if allowed
if (!isPublic.value && isUIAllowed('gridColUpdate') && gridViewCols.value[id]?.id) {
if (!isPublic.value && isUIAllowed('viewFieldEdit') && gridViewCols.value[id]?.id) {
await $api.dbView.gridColumnUpdate(gridViewCols.value[id].id as string, {
...props,
})

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

@ -313,7 +313,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
}
async function updateKanbanMeta(updateObj: Partial<KanbanType>) {
if (!viewMeta?.value?.id || !isUIAllowed('xcDatatableEditable')) return
if (!viewMeta?.value?.id || !isUIAllowed('dataEdit')) return
await $api.dbView.kanbanUpdate(viewMeta.value.id, updateObj)
}

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

@ -86,7 +86,7 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
}
async function updateMapMeta(updateObj: Partial<MapType>) {
if (!viewMeta?.value?.id || !isUIAllowed('xcDatatableEditable')) return
if (!viewMeta?.value?.id || !isUIAllowed('dataEdit')) return
await $api.dbView.mapUpdate(viewMeta.value.id, updateObj)
}

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

@ -1,6 +1,6 @@
import type { Roles, RolesObj } from 'nocodb-sdk'
import { extractRolesObj } from 'nocodb-sdk'
import { computed, createSharedComposable, useApi, useGlobal } from '#imports'
import type { ProjectRole, Role, Roles } from '#imports'
/**
* Provides the roles a user currently has
@ -8,7 +8,6 @@ import type { ProjectRole, Role, Roles } from '#imports'
* * `userRoles` - the roles a user has outside of projects
* * `projectRoles` - the roles a user has in the current project (if one was loaded)
* * `allRoles` - all roles a user has (userRoles + projectRoles)
* * `hasRole` - a function to check if a user has a specific role
* * `loadRoles` - a function to load reload user roles for scope
*/
export const useRoles = createSharedComposable(() => {
@ -16,7 +15,7 @@ export const useRoles = createSharedComposable(() => {
const { api } = useApi()
const allRoles = computed<Roles | null>(() => {
const allRoles = computed<RolesObj | null>(() => {
let orgRoles = user.value?.roles ?? {}
orgRoles = extractRolesObj(orgRoles)
@ -31,7 +30,7 @@ export const useRoles = createSharedComposable(() => {
}
})
const orgRoles = computed<Roles | null>(() => {
const orgRoles = computed<RolesObj | null>(() => {
let orgRoles = user.value?.roles ?? {}
orgRoles = extractRolesObj(orgRoles)
@ -39,7 +38,7 @@ export const useRoles = createSharedComposable(() => {
return orgRoles
})
const projectRoles = computed<Roles | null>(() => {
const projectRoles = computed<RolesObj | null>(() => {
let projectRoles = user.value?.project_roles ?? {}
if (Object.keys(projectRoles).length === 0) {
@ -51,7 +50,7 @@ export const useRoles = createSharedComposable(() => {
return projectRoles
})
const workspaceRoles = computed<Roles | null>(() => {
const workspaceRoles = computed<RolesObj | null>(() => {
return null
})
@ -104,13 +103,5 @@ export const useRoles = createSharedComposable(() => {
}
}
function hasRole(role: Role | ProjectRole | string, includePreviewRoles = false) {
if (previewAs.value && includePreviewRoles) {
return previewAs.value === role
}
return allRoles.value[role]
}
return { allRoles, orgRoles, workspaceRoles, projectRoles, loadRoles, hasRole }
return { allRoles, orgRoles, workspaceRoles, projectRoles, loadRoles }
})

26
packages/nc-gui/composables/useUIPermission/index.ts

@ -1,8 +1,10 @@
import { isString } from '@vue/shared'
import type { Roles } from 'nocodb-sdk'
import { extractRolesObj } from 'nocodb-sdk'
import { createSharedComposable, rolePermissions, useGlobal, useRoles } from '#imports'
import type { Permission, ProjectRole, Role } from '#imports'
import type { Permission } from '#imports'
const hasPermission = (role: Role | ProjectRole, hasRole: boolean, permission: Permission | string) => {
const hasPermission = (role: Roles, hasRole: boolean, permission: Permission | string) => {
const rolePermission = rolePermissions[role]
if (!hasRole || !rolePermission) return false
@ -13,10 +15,6 @@ const hasPermission = (role: Role | ProjectRole, hasRole: boolean, permission: P
return !!rolePermission.include[permission as keyof typeof rolePermission.include]
}
if ('exclude' in rolePermission && rolePermission.exclude) {
return !rolePermission.exclude[permission as keyof typeof rolePermission.exclude]
}
return rolePermission[permission as keyof typeof rolePermission]
}
@ -37,24 +35,16 @@ export const useUIPermission = createSharedComposable(() => {
let roles: Record<string, boolean> = {}
if (!userRoles) {
roles = allRoles.value
} else if (Array.isArray(userRoles) || typeof userRoles === 'string') {
roles = (Array.isArray(userRoles) ? userRoles : userRoles.split(','))
// filter out any empty-string/null/undefined values
.filter(Boolean)
.reduce<Record<string, boolean>>((acc, role) => {
acc[role] = true
return acc
}, {})
} else if (typeof userRoles === 'object') {
roles = userRoles
if (allRoles.value) roles = allRoles.value
} else {
roles = extractRolesObj(userRoles)
}
if (userRoles && combineWithStateRoles) {
roles = { ...roles, ...allRoles.value }
}
return Object.entries(roles).some(([role, hasRole]) => hasPermission(role as Role | ProjectRole, hasRole, permission))
return Object.entries(roles).some(([role, hasRole]) => hasPermission(role as Roles, hasRole, permission))
}
return { isUIAllowed }

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

@ -35,7 +35,7 @@ export function useViewColumns(
const { addUndo, defineViewScope } = useUndoRedo()
const isLocalMode = computed(
() => isPublic.value || !isUIAllowed('hideAllColumns') || !isUIAllowed('showAllColumns') || isSharedBase.value,
() => isPublic.value || !isUIAllowed('viewFieldEdit') || !isUIAllowed('viewFieldEdit') || isSharedBase.value,
)
const localChanges = ref<Field[]>([])
@ -169,7 +169,7 @@ export function useViewColumns(
localChanges.value.push(field)
}
if (isUIAllowed('fieldsSync')) {
if (isUIAllowed('viewFieldEdit')) {
if (field.id && view?.value?.id) {
await $api.dbViewColumn.update(view.value.id, field.id, field)
} else if (view.value?.id) {

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

@ -136,7 +136,7 @@ export function useViewData(
/** load row comments count */
async function loadAggCommentsCount() {
if (!isUIAllowed('commentsCount')) return
if (!isUIAllowed('commentCount')) return
if (isPublic.value || isSharedBase.value) return

2
packages/nc-gui/layouts/base.vue

@ -98,7 +98,7 @@ hooks.hook('page:finish', () => {
</a-menu-item>
<a-menu-divider class="!m-0" />
<!-- <a-menu-item v-if="isUIAllowed('appStore')" key="0" class="!rounded-t">
<!-- <a-menu-item v-if="isUIAllowed('superAdminAppStore')" key="0" class="!rounded-t">
<nuxt-link
v-e="['c:settings:appstore', { page: true }]"
class="nc-project-menu-item group !no-underline"

2
packages/nc-gui/layouts/new.vue

@ -99,7 +99,7 @@ export default {
</a-menu-item>
<a-menu-divider class="!m-0" />
<!-- <a-menu-item v-if="isUIAllowed('appStore')" key="0" class="!rounded-t">
<!-- <a-menu-item v-if="isUIAllowed('superAdminAppStore')" key="0" class="!rounded-t">
<nuxt-link
v-e="['c:settings:appstore', { page: true }]"
class="nc-project-menu-item group !no-underline"

155
packages/nc-gui/lib/constants.ts

@ -1,4 +1,4 @@
import { ProjectRole, Role } from './enums'
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk'
export const NOCO = 'noco'
@ -18,90 +18,139 @@ export const GROUP_BY_VARS = {
__nc_false__: 'Unchecked',
} as Record<string, string>,
}
const roleScopes = {
org: [OrgUserRoles.VIEWER, OrgUserRoles.CREATOR],
project: [ProjectRoles.VIEWER, ProjectRoles.COMMENTER, ProjectRoles.EDITOR, ProjectRoles.CREATOR, ProjectRoles.OWNER],
}
interface Perm {
include?: Record<string, boolean>
}
/**
* Each permission value means the following
* `*` - which is wildcard, means all permissions are allowed
* `include` - which is an object, means only the permissions listed in the object are allowed
* `exclude` - which is an object, means all permissions are allowed except the ones listed in the object
* `undefined` or `{}` - which is the default value, means no permissions are allowed
* */
const rolePermissions = {
// general role permissions
[Role.Super]: '*',
[Role.Admin]: {} as Record<string, boolean>,
[Role.Guest]: {} as Record<string, boolean>,
[Role.OrgLevelCreator]: {
// org level role permissions
[OrgUserRoles.SUPER_ADMIN]: '*',
[OrgUserRoles.CREATOR]: {
include: {
projectCreate: true,
projectActions: true,
projectSettings: true,
projectMove: true,
projectDelete: true,
projectDuplicate: true,
},
},
[OrgUserRoles.VIEWER]: {
include: {
importRequest: true,
},
},
// Project role permissions
[ProjectRole.Creator]: {
exclude: {
appStore: true,
superAdminUserManagement: true,
superAdminAppSettings: true,
appLicense: true,
moveProject: true,
projectDelete: true,
projectCreate: true,
},
[ProjectRoles.OWNER]: {
include: {},
},
[ProjectRole.Owner]: {
exclude: {
projectCreate: true,
appStore: true,
superAdminUserManagement: true,
superAdminAppSettings: true,
appLicense: true,
[ProjectRoles.CREATOR]: {
include: {
baseCreate: true,
fieldUpdate: true,
hookList: true,
tableCreate: true,
tableRename: true,
tableDelete: true,
tableDuplicate: true,
tableSort: true,
layoutRename: true,
layoutDelete: true,
airtableImport: true,
jsonImport: true,
excelImport: true,
settingsPage: true,
newUser: true,
webhook: true,
shareView: true,
fieldEdit: true,
fieldAdd: true,
tableIconEdit: true,
viewCreateOrEdit: true,
},
},
[ProjectRole.Editor]: {
[ProjectRoles.EDITOR]: {
include: {
smartSheet: true,
xcDatatableEditable: true,
column: true,
tableAttachment: true,
tableRowUpdate: true,
dataInsert: true,
rowComments: true,
gridViewOptions: true,
dataEdit: true,
sortSync: true,
fieldsSync: true,
gridColUpdate: true,
filterSync: true,
filterChildrenRead: true,
viewFieldEdit: true,
csvImport: true,
apiDocs: true,
projectSettings: true,
newUser: false,
commentEditable: true,
commentList: true,
commentsCount: true,
},
},
[ProjectRole.Commenter]: {
[ProjectRoles.COMMENTER]: {
include: {
smartSheet: true,
column: true,
rowComments: true,
projectSettings: true,
commentEditable: true,
commentEdit: true,
commentList: true,
commentsCount: true,
commentCount: true,
},
},
[ProjectRole.Viewer]: {
[ProjectRoles.VIEWER]: {
include: {
smartSheet: true,
column: true,
projectSettings: true,
expandedForm: true,
},
},
} as const
[ProjectRoles.NO_ACCESS]: {
include: {},
},
} as Record<OrgUserRoles | ProjectRoles, Perm | '*'>
// validate no duplicate permissions within same scope
/*
We inherit include permissions from previous roles in the same scope (role order)
We inherit exclude permissions from previous roles in the same scope (reverse role order)
To determine role order, we use `roleScopes` object
*/
Object.values(roleScopes).forEach((roles) => {
const scopePermissions: Record<string, boolean> = {}
const duplicates: string[] = []
roles.forEach((role) => {
const perms = (rolePermissions[role] as Perm).include || {}
Object.keys(perms).forEach((perm) => {
if (scopePermissions[perm]) {
duplicates.push(perm)
}
scopePermissions[perm] = true
})
})
if (duplicates.length) {
throw new Error(
`Duplicate permissions found in roles ${roles.join(', ')}. Please remove duplicate permissions: ${duplicates.join(', ')}`,
)
}
})
// inherit include permissions within scope (role order)
Object.values(roleScopes).forEach((roles) => {
let roleIndex = 0
for (const role of roles) {
if (roleIndex === 0) {
roleIndex++
continue
}
if (rolePermissions[role] === '*') continue
if ((rolePermissions[role] as Perm).include && (rolePermissions[roles[roleIndex - 1]] as Perm).include) {
Object.assign((rolePermissions[role] as Perm).include!, (rolePermissions[roles[roleIndex - 1]] as Perm).include)
}
roleIndex++
}
})
export { rolePermissions }

21
packages/nc-gui/lib/enums.ts

@ -1,24 +1,3 @@
export enum Role {
Super = 'super',
Admin = 'admin',
OrgLevelCreator = 'org-level-creator',
OrgLevelViewer = 'org-level-viewer',
WorkspaceLevelCreator = 'workspace-level-creator',
WorkspaceLevelViewer = 'workspace-level-viewer',
WorkspaceLevelOwner = 'workspace-level-owner',
Guest = 'guest',
}
export enum ProjectRole {
Owner = 'owner',
Creator = 'creator',
Editor = 'editor',
Commenter = 'commenter',
Viewer = 'viewer',
}
export enum ClientType {
MYSQL = 'mysql2',
MSSQL = 'mssql',

31
packages/nc-gui/lib/types.ts

@ -1,17 +1,8 @@
import type {
ColumnType,
FilterType,
MetaType,
OrgUserRoles,
PaginatedType,
ProjectType,
ViewTypes,
WorkspaceUserRoles,
} from 'nocodb-sdk'
import type { ColumnType, FilterType, MetaType, PaginatedType, ProjectType, Roles, RolesObj, ViewTypes } from 'nocodb-sdk'
import type { I18n } from 'vue-i18n'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import type { UploadFile } from 'ant-design-vue'
import type { ImportSource, ImportType, ProjectRole, Role, TabType } from './enums'
import type { ImportSource, ImportType, TabType } from './enums'
import type { rolePermissions } from './constants'
interface User {
@ -19,9 +10,9 @@ interface User {
email: string
firstname: string | null
lastname: string | null
roles: Roles | string
project_roles: Roles | string
workspace_roles: Roles | string
roles: RolesObj
project_roles: RolesObj
workspace_roles: RolesObj
invite_token?: string
project_id?: string
display_name?: string | null
@ -47,8 +38,6 @@ interface Field {
isViewEssentialField?: boolean
}
type Roles<T extends Role | ProjectRole = Role | ProjectRole> = Record<T | string, boolean>
type Filter = FilterType & {
field?: string
status?: 'update' | 'delete' | 'create'
@ -171,15 +160,9 @@ interface GroupNestedIn {
column_uidt: string
}
type AllRoles =
| (typeof ProjectRole)[keyof typeof ProjectRole]
| (typeof Role)[keyof typeof Role]
| (typeof WorkspaceUserRoles)[keyof typeof WorkspaceUserRoles]
| (typeof OrgUserRoles)[keyof typeof OrgUserRoles]
interface Users {
emails?: string
role: AllRoles
role: Roles
invitationToken?: string
}
@ -191,7 +174,6 @@ export {
User,
ProjectMetaInfo,
Field,
Roles,
Filter,
NocoI18n,
ThemeConfig,
@ -209,7 +191,6 @@ export {
ImportWorkerPayload,
Group,
GroupNestedIn,
AllRoles,
Users,
ViewPageType,
NcButtonSize,

2
packages/nc-gui/pages/account/index.vue

@ -86,7 +86,7 @@ const logout = async () => {
</div>
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('appStore') && !isEeUI"
v-if="isUIAllowed('superAdminAppStore') && !isEeUI"
key="apps"
class="item"
:class="{

2
packages/nc-gui/store/projectsShortcuts.ts

@ -38,7 +38,7 @@ export const useProjectsShortcuts = defineStore('projectsShortcutsStore', () =>
}
// 'ALT + ,'
case 188: {
if (isUIAllowed('settings') && !isDrawerOrModalExist()) {
if (isUIAllowed('settingsPage') && !isDrawerOrModalExist()) {
$e('c:shortcut', { key: 'ALT + ,' })
const projectsStore = useProjects()

5
packages/nc-gui/store/share.ts

@ -1,4 +1,5 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
import { ProjectRoles } from 'nocodb-sdk'
import type { Users } from '#imports'
export const useShare = defineStore('share', () => {
@ -28,7 +29,7 @@ export const useShare = defineStore('share', () => {
const showShareModal = ref(false)
const invitationUsersData = ref<Users>({ emails: undefined, role: ProjectRole.Viewer, invitationToken: undefined })
const invitationUsersData = ref<Users>({ emails: undefined, role: ProjectRoles.VIEWER, invitationToken: undefined })
watch(
[isProjectPublic],
@ -41,7 +42,7 @@ export const useShare = defineStore('share', () => {
const resetData = () => {
formStatus.value = 'project-collaborate'
invitationValid.value = false
invitationUsersData.value = { emails: undefined, role: ProjectRole.Viewer, invitationToken: undefined }
invitationUsersData.value = { emails: undefined, role: ProjectRoles.VIEWER, invitationToken: undefined }
isInvitationLinkCopied.value = false
}

1
packages/nc-gui/utils/index.ts

@ -16,7 +16,6 @@ export * from './validation'
export * from './viewUtils'
export * from './currencyUtils'
export * from './dataUtils'
export * from './userUtils'
export * from './stringUtils'
export * from './memStorage'
export * from './browserUtils'

15
packages/nc-gui/utils/userUtils.ts

@ -1,15 +0,0 @@
import { OrgUserRoles } from 'nocodb-sdk'
import { ProjectRole } from '../lib/enums'
export const projectRoleTagColors = {
[ProjectRole.Owner]: '#cfdffe',
[ProjectRole.Creator]: '#d0f1fd',
[ProjectRole.Editor]: '#c2f5e8',
[ProjectRole.Commenter]: '#ffdaf6',
[ProjectRole.Viewer]: '#ffdce5',
[OrgUserRoles.SUPER_ADMIN]: '#f5d7cb',
}
export const projectRoles = [ProjectRole.Creator, ProjectRole.Editor, ProjectRole.Commenter, ProjectRole.Viewer]
export const docsProjectRoles = [ProjectRole.Editor, ProjectRole.Viewer]

10
packages/nocodb-sdk/src/lib/globals.ts

@ -1,3 +1,5 @@
import { OrgUserRoles, ProjectRoles, WorkspaceUserRoles } from "./enums";
export enum ViewTypes {
FORM = 1,
GALLERY = 2,
@ -120,3 +122,11 @@ export enum TiptapMarksTypes {
code = 'code',
underline = 'underline',
}
type Roles = OrgUserRoles | ProjectRoles | WorkspaceUserRoles;
type RolesObj = Partial<Record<Roles, boolean>>;
type RolesType = RolesObj | string[] | string;
export { Roles, RolesObj, RolesType };

9
packages/nocodb-sdk/src/lib/helperFunctions.ts

@ -1,5 +1,5 @@
import UITypes from './UITypes';
import { OrgUserRoles, ProjectRoles, WorkspaceUserRoles } from './enums';
import { Roles, RolesObj, RolesType } from './globals';
// import {RelationTypes} from "./globals";
@ -22,12 +22,7 @@ const isSystemColumn = (col): boolean =>
(col.pk && col.meta && col.meta.ag) ||
col.system);
type Roles = Record<
OrgUserRoles | ProjectRoles | WorkspaceUserRoles | string,
boolean
>;
const extractRolesObj = (roles: Roles | string[] | string): Roles => {
const extractRolesObj = (roles: RolesType): RolesObj => {
if (!roles) return null;
if (typeof roles === 'object' && !Array.isArray(roles)) return roles;

Loading…
Cancel
Save