Browse Source

Merge pull request #6399 from nocodb/nc-fix/ui-acl

fix: revise frontend acl
pull/6427/head
mertmit 1 year ago committed by GitHub
parent
commit
5c3ed74fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      packages/nc-gui/components/account/UserList.vue
  2. 10
      packages/nc-gui/components/account/UsersModal.vue
  3. 24
      packages/nc-gui/components/cell/MultiSelect.vue
  4. 23
      packages/nc-gui/components/cell/SingleSelect.vue
  5. 4
      packages/nc-gui/components/cell/TextArea.vue
  6. 13
      packages/nc-gui/components/cell/attachment/Modal.vue
  7. 16
      packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue
  8. 14
      packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue
  9. 32
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  10. 18
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  11. 6
      packages/nc-gui/components/dashboard/TreeView/index.vue
  12. 4
      packages/nc-gui/components/general/AddBaseButton.vue
  13. 9
      packages/nc-gui/components/general/PreviewAs.vue
  14. 4
      packages/nc-gui/components/general/ShareBaseButton.vue
  15. 6
      packages/nc-gui/components/general/ShareProject.vue
  16. 7
      packages/nc-gui/components/project/AllTables.vue
  17. 10
      packages/nc-gui/components/project/InviteProjectCollabSection.vue
  18. 6
      packages/nc-gui/components/project/View.vue
  19. 6
      packages/nc-gui/components/smartsheet/Cell.vue
  20. 2
      packages/nc-gui/components/smartsheet/Details.vue
  21. 8
      packages/nc-gui/components/smartsheet/Form.vue
  22. 4
      packages/nc-gui/components/smartsheet/Gallery.vue
  23. 4
      packages/nc-gui/components/smartsheet/Kanban.vue
  24. 4
      packages/nc-gui/components/smartsheet/Toolbar.vue
  25. 17
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  26. 12
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  27. 3
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  28. 18
      packages/nc-gui/components/smartsheet/grid/Table.vue
  29. 12
      packages/nc-gui/components/smartsheet/header/Cell.vue
  30. 6
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  31. 8
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  32. 6
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  33. 2
      packages/nc-gui/components/smartsheet/sidebar/toolbar/Developer.vue
  34. 6
      packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue
  35. 6
      packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue
  36. 6
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  37. 6
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  38. 4
      packages/nc-gui/components/tabs/Auth.vue
  39. 6
      packages/nc-gui/components/tabs/Smartsheet.vue
  40. 15
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  41. 19
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  42. 6
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  43. 6
      packages/nc-gui/components/virtual-cell/HasMany.vue
  44. 4
      packages/nc-gui/components/virtual-cell/Links.vue
  45. 6
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  46. 4
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  47. 4
      packages/nc-gui/components/workspace/CreateProjectBtn.vue
  48. 22
      packages/nc-gui/components/workspace/ProjectList.vue
  49. 2
      packages/nc-gui/composables/useExpandedFormStore.ts
  50. 5
      packages/nc-gui/composables/useGlobal/types.ts
  51. 17
      packages/nc-gui/composables/useGridViewColumn.ts
  52. 6
      packages/nc-gui/composables/useKanbanViewStore.ts
  53. 4
      packages/nc-gui/composables/useMapViewDataStore.ts
  54. 58
      packages/nc-gui/composables/useRoles/index.ts
  55. 61
      packages/nc-gui/composables/useUIPermission/index.ts
  56. 0
      packages/nc-gui/composables/useUIPermission/rolePermissions.ts
  57. 19
      packages/nc-gui/composables/useViewColumns.ts
  58. 6
      packages/nc-gui/composables/useViewData.ts
  59. 4
      packages/nc-gui/composables/useViewFilters.ts
  60. 2
      packages/nc-gui/composables/useViewGroupBy.ts
  61. 4
      packages/nc-gui/composables/useViewSorts.ts
  62. 2
      packages/nc-gui/layouts/base.vue
  63. 2
      packages/nc-gui/layouts/new.vue
  64. 148
      packages/nc-gui/lib/acl.ts
  65. 89
      packages/nc-gui/lib/constants.ts
  66. 21
      packages/nc-gui/lib/enums.ts
  67. 1
      packages/nc-gui/lib/index.ts
  68. 31
      packages/nc-gui/lib/types.ts
  69. 6
      packages/nc-gui/pages/account/index.vue
  70. 4
      packages/nc-gui/pages/account/index/users/[[nestedPage]].vue
  71. 4
      packages/nc-gui/store/projectsShortcuts.ts
  72. 5
      packages/nc-gui/store/share.ts
  73. 7
      packages/nc-gui/store/workspace.ts
  74. 1
      packages/nc-gui/utils/index.ts
  75. 15
      packages/nc-gui/utils/userUtils.ts
  76. 10
      packages/nocodb-sdk/src/lib/globals.ts
  77. 12
      packages/nocodb-sdk/src/lib/helperFunctions.ts

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

@ -1,7 +1,8 @@
<script lang="ts" setup>
import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk'
import { OrgUserRoles } 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 +59,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,
@ -195,7 +196,7 @@ const copyPasswordResetUrl = async (user: User) => {
>
<a-select-option
class="nc-users-list-role-option"
:value="Role.OrgLevelCreator"
:value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
@ -206,7 +207,7 @@ const copyPasswordResetUrl = async (user: User) => {
<a-select-option
class="nc-users-list-role-option"
:value="Role.OrgLevelViewer"
:value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>

10
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 = ''
}
@ -196,7 +196,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-select v-model:value="usersData.role" class="nc-user-roles" dropdown-class-name="nc-dropdown-user-role">
<a-select-option
class="nc-role-option"
:value="Role.OrgLevelCreator"
:value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
@ -207,7 +207,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-select-option
class="nc-role-option"
:value="Role.OrgLevelViewer"
:value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>

24
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,
@ -80,7 +79,7 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { hasRole } = useRoles()
const { isUIAllowed } = useRoles()
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"
>

23
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,
@ -70,7 +69,7 @@ const searchVal = ref()
const { getMeta } = useMetas()
const { hasRole } = useRoles()
const { isUIAllowed } = useRoles()
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)

4
packages/nc-gui/components/cell/TextArea.vue

@ -6,11 +6,11 @@ import {
EditColumnInj,
EditModeInj,
IsExpandedFormOpenInj,
ReadonlyInj,
RowHeightInj,
iconMap,
inject,
useVModel,
ReadonlyInj,
} from '#imports'
const props = defineProps<{
@ -139,9 +139,9 @@ onClickOutside(inputWrapperRef, (e) => {
class="p-1 !pt-1 !pr-3 !border-0 !border-r-0 !focus:outline-transparent nc-scrollbar-md !text-black"
:bordered="false"
:auto-size="{ minRows: 20, maxRows: 20 }"
:disabled="readOnly"
@keydown.stop
@keydown.escape="isVisible = false"
:disabled="readOnly"
/>
</div>
</template>

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

@ -2,9 +2,9 @@
import { onKeyDown, useEventListener } from '@vueuse/core'
import { useAttachmentCell } from './utils'
import { useSortable } from './sort'
import { iconMap, isImage, ref, useAttachment, useDropZone, useUIPermission, watch } from '#imports'
import { iconMap, isImage, ref, useAttachment, useDropZone, useRoles, watch } from '#imports'
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const {
open,
@ -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)"
/>
@ -155,10 +155,7 @@ const handleFileDelete = (i: number) => {
</div>
</a-tooltip>
<a-tooltip
v-if="isSharedForm || (!readOnly && isUIAllowed('tableAttachment') && !isPublic && !isLocked)"
placement="bottom"
>
<a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)" placement="bottom">
<template #title> Rename File </template>
<div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]">

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

@ -4,7 +4,7 @@ import { storeToRefs } from 'pinia'
import { toRef } from '@vue/reactivity'
import { resolveComponent } from '@vue/runtime-core'
import { ref } from 'vue'
import { ProjectRoleInj, useDialog, useUIPermission } from '#imports'
import { ProjectRoleInj, useDialog, useRoles } from '#imports'
const props = withDefaults(
defineProps<{
@ -20,7 +20,7 @@ const emit = defineEmits<{
openTableCreateDialog: () => void
}>()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const project = toRef(props, 'project')
@ -116,7 +116,7 @@ function openTableCreateMagicDialog(baseId?: string) {
<template>
<div
v-if="isUIAllowed('table-create', false, projectRole)"
v-if="isUIAllowed('tableCreate', { roles: 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')"
>
@ -158,7 +158,7 @@ function openTableCreateMagicDialog(baseId?: string) {
<!-- Quick Import From -->
<a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0">
<a-menu-item
v-if="isUIAllowed('airtableImport', false, projectRole)"
v-if="isUIAllowed('airtableImport', { roles: projectRole })"
key="quick-import-airtable"
@click="openAirtableImportDialog(project.bases[baseIndex].id)"
>
@ -169,7 +169,7 @@ function openTableCreateMagicDialog(baseId?: string) {
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('csvImport', false, projectRole)"
v-if="isUIAllowed('csvImport', { roles: projectRole })"
key="quick-import-csv"
@click="openQuickImportDialog('csv', project.bases[baseIndex].id)"
>
@ -180,7 +180,7 @@ function openTableCreateMagicDialog(baseId?: string) {
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport', false, projectRole)"
v-if="isUIAllowed('jsonImport', { roles: projectRole })"
key="quick-import-json"
@click="openQuickImportDialog('json', project.bases[baseIndex].id)"
>
@ -191,7 +191,7 @@ function openTableCreateMagicDialog(baseId?: string) {
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport', false, projectRole)"
v-if="isUIAllowed('excelImport', { roles: projectRole })"
key="quick-import-excel"
@click="openQuickImportDialog('excel', project.bases[baseIndex].id)"
>
@ -243,7 +243,7 @@ function openTableCreateMagicDialog(baseId?: string) {
<a-menu-divider class="my-0" /> -->
<a-menu-item v-if="isUIAllowed('importRequest', false, projectRole)" key="add-new-table" class="py-1 rounded-b">
<a-menu-item v-if="isUIAllowed('importRequest', { roles: projectRole })" key="add-new-table" class="py-1 rounded-b">
<a
v-e="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"

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

@ -8,7 +8,7 @@ const props = defineProps<{
const base = toRef(props, 'base')
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const projectRole = inject(ProjectRoleInj)
@ -68,7 +68,7 @@ function openQuickImportDialog(type: string) {
<template #expandIcon></template>
<NcMenuItem
v-if="isUIAllowed('airtableImport', false, projectRole)"
v-if="isUIAllowed('airtableImport', { roles: projectRole })"
key="quick-import-airtable"
@click="openAirtableImportDialog(base.id)"
>
@ -76,13 +76,17 @@ function openQuickImportDialog(type: string) {
<div class="ml-0.5">Airtable</div>
</NcMenuItem>
<NcMenuItem v-if="isUIAllowed('csvImport', false, projectRole)" key="quick-import-csv" @click="openQuickImportDialog('csv')">
<NcMenuItem
v-if="isUIAllowed('csvImport', { roles: projectRole })"
key="quick-import-csv"
@click="openQuickImportDialog('csv')"
>
<GeneralIcon icon="csv" class="w-4 group-hover:text-black" />
CSV file
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('jsonImport', false, projectRole)"
v-if="isUIAllowed('jsonImport', { roles: projectRole })"
key="quick-import-json"
@click="openQuickImportDialog('json')"
>
@ -91,7 +95,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('excelImport', false, projectRole)"
v-if="isUIAllowed('excelImport', { roles: projectRole })"
key="quick-import-excel"
@click="openQuickImportDialog('excel')"
>

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

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { nextTick } from '@vue/runtime-core'
import { message } from 'ant-design-vue'
import { stringifyRolesObj } from 'nocodb-sdk'
import type { BaseType, ProjectType, TableType } from 'nocodb-sdk'
import { LoadingOutlined } from '@ant-design/icons-vue'
import { useTitle } from '@vueuse/core'
@ -43,6 +44,8 @@ const { activeTable } = storeToRefs(useTablesStore())
const { appInfo, navigateToProject } = useGlobal()
const { orgRoles, isUIAllowed } = useRoles()
useTabs()
const editMode = ref(false)
@ -53,8 +56,6 @@ const { t } = useI18n()
const input = ref<HTMLInputElement>()
const { isUIAllowed } = useUIPermission()
const projectRole = inject(ProjectRoleInj)
const { activeProjectId } = storeToRefs(useProjects())
@ -438,7 +439,11 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</span>
<div :class="{ 'flex flex-grow h-full': !editMode }" @click="onProjectClick(project)"></div>
<NcDropdown v-if="isUIAllowed('tableCreate', false, projectRole)" v-model:visible="isOptionsOpen" :trigger="['click']">
<NcDropdown
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
v-model:visible="isOptionsOpen"
:trigger="['click']"
>
<NcButton
class="nc-sidebar-node-btn"
:class="{ '!text-black !opacity-100': isOptionsOpen }"
@ -469,7 +474,10 @@ 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', { roles: [stringifyRolesObj(orgRoles), projectRole].join() })"
@click="duplicateProject(project)"
>
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }}
</NcMenuItem>
@ -493,7 +501,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"
@ -510,7 +518,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</template>
<NcMenuItem
v-if="isUIAllowed('projectDelete', false, projectRole)"
v-if="isUIAllowed('projectDelete', { roles: [stringifyRolesObj(orgRoles), projectRole].join() })"
class="!text-red-500 !hover:bg-red-50"
@click="isProjectDeleteDialogVisible = true"
>
@ -522,7 +530,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</NcDropdown>
<NcButton
v-if="isUIAllowed('tableCreate', false, projectRole)"
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
class="nc-sidebar-node-btn"
size="xxsmall"
type="text"
@ -604,7 +612,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</a-tooltip>
</div>
<div
v-if="isUIAllowed('tableCreate', false, projectRole)"
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
class="flex flex-row items-center gap-x-0.25 w-12.25"
>
<NcDropdown
@ -642,7 +650,7 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
</NcDropdown>
<NcButton
v-if="isUIAllowed('tableCreate', false, projectRole)"
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
type="text"
size="xxsmall"
class="nc-sidebar-node-btn"
@ -676,7 +684,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 +692,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 +701,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') }}

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

@ -5,7 +5,7 @@ import { message } from 'ant-design-vue'
import { storeToRefs } from 'pinia'
import { useNuxtApp } from '#app'
import { ProjectRoleInj, TreeViewInj, useTabs, useUIPermission } from '#imports'
import { ProjectRoleInj, TreeViewInj, useRoles, useTabs } from '#imports'
const props = withDefaults(
defineProps<{
@ -22,7 +22,7 @@ const baseIndex = toRef(props, 'baseIndex')
const route = useRoute()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const tabStore = useTabs()
const { updateTab } = tabStore
@ -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', { roles: 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', { roles: 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', { roles: 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', { roles: projectRole }) || isUIAllowed('tableDelete', { roles: 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', { roles: 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', { roles: projectRole })"
:data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50"
@click="isTableDeleteDialogVisible = true"

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

@ -20,14 +20,14 @@ import {
useNuxtApp,
useProject,
useProjects,
useRoles,
useTablesStore,
useTabs,
useUIPermission,
} from '#imports'
import { useRouter } from '#app'
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { addTab } = useTabs()
@ -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' &&

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

@ -1,7 +1,7 @@
<script setup lang="ts">
import { iconMap } from '#imports'
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { t } = useI18n()
@ -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 = {

4
packages/nc-gui/components/general/ShareBaseButton.vue

@ -1,11 +1,11 @@
<script setup lang="ts">
import { isDrawerOrModalExist, isMac, useNuxtApp, useRoute, useUIPermission } from '#imports'
import { isDrawerOrModalExist, isMac, useNuxtApp, useRoles, useRoute } from '#imports'
const route = useRoute()
const showUserModal = ref(false)
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { $e } = useNuxtApp()

6
packages/nc-gui/components/general/ShareProject.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { isDrawerOrModalExist, isMac, useNuxtApp } from '#imports'
import { isDrawerOrModalExist, isMac, useNuxtApp, useRoles } from '#imports'
interface Props {
disabled?: boolean
@ -15,7 +15,7 @@ const { project } = storeToRefs(useProject())
const { $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
@ -36,7 +36,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<template>
<div
v-if="isUIAllowed('shareProject') && visibility !== 'hidden' && (activeTable || project)"
v-if="isUIAllowed('projectShare') && visibility !== 'hidden' && (activeTable || project)"
class="flex flex-col justify-center h-full"
data-testid="share-project-button"
:data-sharetype="visibility"

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

@ -1,5 +1,4 @@
<script lang="ts" setup>
import { stringifyRolesObj } from 'nocodb-sdk'
import type { BaseType, TableType } from 'nocodb-sdk'
import dayjs from 'dayjs'
@ -7,9 +6,7 @@ const { activeTables } = storeToRefs(useTablesStore())
const { openTable } = useTablesStore()
const { openedProject } = storeToRefs(useProjects())
const { isUIAllowed } = useUIPermission()
const { allRoles } = useRoles()
const { isUIAllowed } = useRoles()
const { $e } = useNuxtApp()
@ -74,7 +71,7 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
<template>
<div class="nc-all-tables-view">
<div v-if="isUIAllowed('tableCreate', false, stringifyRolesObj(allRoles))" class="flex flex-row gap-x-6 pb-3 pt-6">
<div v-if="isUIAllowed('tableCreate')" class="flex flex-row gap-x-6 pb-3 pt-6">
<div class="nc-project-view-all-table-btn" data-testid="proj-view-btn__add-new-table" @click="openTableCreateDialog()">
<GeneralIcon icon="addOutlineBox" />
<div class="label">{{ $t('general.new') }} {{ $t('objects.table') }}</div>

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>

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

@ -14,7 +14,7 @@ const route = router.currentRoute
return openedProject.value?.bases?.[0]
}) */
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { isMobileMode } = useGlobal()
@ -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" />

6
packages/nc-gui/components/smartsheet/Cell.vue

@ -252,7 +252,11 @@ onUnmounted(() => {
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="(isLocked || (isPublic && readOnly && !isForm) || isSystemColumn(column)) && !isAttachment(column) && !isTextArea(column)"
v-if="
(isLocked || (isPublic && readOnly && !isForm) || isSystemColumn(column)) &&
!isAttachment(column) &&
!isTextArea(column)
"
class="nc-locked-overlay"
/>
</template>

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

@ -2,7 +2,7 @@
const { openedViewsTab } = storeToRefs(useViewsStore())
const { onViewsTabChange } = useViewsStore()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const openedSubTab = computed({
get() {

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

@ -22,7 +22,7 @@ import {
useGlobal,
useI18n,
useNuxtApp,
useUIPermission,
useRoles,
useViewColumns,
useViewData,
watch,
@ -42,13 +42,13 @@ const formRef = ref()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
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

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

@ -84,8 +84,8 @@ const isRowEmpty = (record: any, col: any) => {
return Array.isArray(val) && val.length === 0
}
const { isUIAllowed } = useUIPermission()
const hasEditPermission = computed(() => isUIAllowed('xcDatatableEditable'))
const { isUIAllowed } = useRoles()
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({

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

@ -89,7 +89,7 @@ const {
const { isViewDataLoading } = storeToRefs(useViewsStore())
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { appInfo } = useGlobal()
@ -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([]))

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

@ -1,5 +1,5 @@
<script setup lang="ts">
import { IsPublicInj, inject, ref, useSharedView, useSmartsheetStoreOrThrow, useUIPermission, useViewsStore } from '#imports'
import { IsPublicInj, inject, ref, useRoles, useSharedView, useSmartsheetStoreOrThrow, useViewsStore } from '#imports'
const { isGrid, isGallery, isKanban, isMap } = useSmartsheetStoreOrThrow()
@ -9,7 +9,7 @@ const { isViewsLoading } = storeToRefs(useViewsStore())
const { isMobileMode } = useGlobal()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { allowCSVDownload } = useSharedView()

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

@ -1,7 +1,18 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import type { AuditType } from 'nocodb-sdk'
import { enumColor, iconMap, ref, timeAgo, useCopy, useExpandedFormStoreOrThrow, useGlobal, useI18n, watch } from '#imports'
import {
enumColor,
iconMap,
ref,
timeAgo,
useCopy,
useExpandedFormStoreOrThrow,
useGlobal,
useI18n,
useRoles,
watch,
} from '#imports'
const { loadCommentsAndLogs, commentsAndLogs, isCommentsLoading, commentsOnly, saveComment, isYou, comment, updateComment } =
useExpandedFormStoreOrThrow()
@ -18,9 +29,9 @@ const { t } = useI18n()
const { user } = useGlobal()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
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

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

@ -7,9 +7,9 @@ import {
isMac,
useCopy,
useExpandedFormStoreOrThrow,
useRoles,
useSmartsheetRowStoreOrThrow,
useSmartsheetStoreOrThrow,
useUIPermission,
} from '#imports'
const props = defineProps<{ view?: ViewType }>()
@ -24,7 +24,7 @@ const { commentsDrawer, displayValue, primaryKey, save: _save, loadRow, deleteRo
const { isNew, syncLTARRefs, state } = useSmartsheetRowStoreOrThrow()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
@ -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"

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

@ -21,6 +21,7 @@ import {
useActiveKeyupListener,
useProvideExpandedFormStore,
useProvideSmartsheetStore,
useRoles,
useRouter,
useVModel,
watch,
@ -57,7 +58,7 @@ const meta = toRef(props, 'meta')
const router = useRouter()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
// override cell click hook to avoid unexpected behavior at form fields
provide(CellClickHookInj, undefined)

18
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,
@ -33,7 +33,6 @@ import {
useRoles,
useRoute,
useSmartsheetStoreOrThrow,
useUIPermission,
useUndoRedo,
useViewsStore,
watch,
@ -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 { isUIAllowed } = useRoles()
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 }"

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

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ColumnReqType, ColumnType } from 'nocodb-sdk'
import { ColumnInj, IsExpandedFormOpenInj, IsFormInj, IsKanbanInj, inject, provide, ref, toRef, useUIPermission } from '#imports'
import { ColumnInj, IsExpandedFormOpenInj, IsFormInj, IsKanbanInj, inject, provide, ref, toRef, useRoles } from '#imports'
interface Props {
column: ColumnType
@ -23,7 +23,7 @@ const isKanban = inject(IsKanbanInj, ref(false))
const column = toRef(props, 'column')
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
provide(ColumnInj, column)
@ -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"

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

@ -19,7 +19,7 @@ import {
toRef,
useI18n,
useMetas,
useUIPermission,
useRoles,
} from '#imports'
const props = defineProps<{ column: ColumnType; hideMenu?: boolean; required?: boolean | number; hideIcon?: boolean }>()
@ -38,7 +38,7 @@ provide(ColumnInj, column)
const { metas } = useMetas()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const meta = inject(MetaInj, ref())
@ -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"

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

@ -2,7 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core'
import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk'
import type { WritableComputedRef } from '@vue/reactivity'
import { IsLockedInj, inject, message, onKeyStroke, useDebounceFn, useNuxtApp, useUIPermission, useVModel } from '#imports'
import { IsLockedInj, inject, message, onKeyStroke, useDebounceFn, useNuxtApp, useRoles, useVModel } from '#imports'
interface Props {
view: ViewType
@ -31,7 +31,7 @@ const vModel = useVModel(props, 'view', emits) as WritableComputedRef<ViewType &
const { $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const activeView = inject(ActiveViewInj, ref())
@ -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"

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

@ -10,9 +10,9 @@ import {
useCommandPalette,
useDialog,
useNuxtApp,
useRoles,
useRoute,
useRouter,
useUIPermission,
useViewsStore,
watch,
} from '#imports'
@ -39,7 +39,7 @@ const setLastOpenedViewId = (viewId?: string) => {
}
}
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const router = useRouter()
@ -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/sidebar/toolbar/Developer.vue

@ -1,7 +1,7 @@
<script setup lang="ts">
const { isMobileMode } = useGlobal()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const isPublicView = inject(IsPublicInj, ref(false))

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

@ -9,10 +9,10 @@ import {
ref,
useKanbanViewStoreOrThrow,
useMenuCloseOnEsc,
useUIPermission,
useRoles,
} from '#imports'
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { groupingFieldColumn } = useKanbanViewStoreOrThrow()
@ -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"

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

@ -15,9 +15,9 @@ import {
useI18n,
useNuxtApp,
useProject,
useRoles,
useSharedView,
useSmartsheetStoreOrThrow,
useUIPermission,
} from '#imports'
const { t } = useI18n()
@ -48,7 +48,7 @@ const showWebhookDrawer = ref(false)
const quickImportDialog = ref(false)
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const exportFile = async (exportType: ExportTypes) => {
let offset = 0
@ -146,7 +146,7 @@ const exportFile = async (exportType: ExportTypes) => {
</div>
<div
v-if="isUIAllowed('sharedViewList') && !isView && !isPublicView"
v-if="isUIAllowed('viewShare') && !isView && !isPublicView"
v-e="['a:actions:shared-view-list']"
class="nc-menu-item"
@click="sharedViewListDlg = true"

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

@ -16,8 +16,8 @@ import {
useI18n,
useNuxtApp,
useProject,
useRoles,
useSmartsheetStoreOrThrow,
useUIPermission,
watch,
} from '#imports'
import type { SharedView } from '#imports'
@ -32,7 +32,7 @@ const { $e } = useNuxtApp()
const { dashboardUrl } = useDashboard()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { isSharedBase } = storeToRefs(useProject())
@ -249,7 +249,7 @@ watch(shared, () => {
<template>
<div>
<a-button
v-if="isUIAllowed('share-view') && !isSharedBase"
v-if="isUIAllowed('viewShare') && !isSharedBase"
v-e="['c:view:share']"
outlined
class="nc-btn-share-view nc-toolbar-btn"

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

@ -17,8 +17,8 @@ import {
useMenuCloseOnEsc,
useNuxtApp,
useProject,
useRoles,
useSmartsheetStoreOrThrow,
useUIPermission,
} from '#imports'
const { t } = useI18n()
@ -56,7 +56,7 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
{},
) as Record<QuickImportDialogType, Ref<boolean>>
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
useProject()
@ -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"
>

4
packages/nc-gui/components/tabs/Auth.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ComputedRef } from 'vue'
import { computed, useI18n, useUIPermission } from '#imports'
import { computed, useI18n, useRoles } from '#imports'
interface Tab {
title: string
@ -10,7 +10,7 @@ interface Tab {
const { t } = useI18n()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const tabsInfo: Tab[] = [
{

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

@ -21,8 +21,8 @@ import {
useMetas,
useProvideKanbanViewStore,
useProvideSmartsheetStore,
useRoles,
useSqlEditor,
useUIPermission,
} from '#imports'
import type { TabItem } from '#imports'
@ -30,7 +30,7 @@ const props = defineProps<{
activeTab: TabItem
}>()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { metas, getMeta } = useMetas()
@ -76,7 +76,7 @@ provide(IsFormInj, isForm)
provide(TabMetaInj, activeTab)
provide(
ReadonlyInj,
computed(() => !isUIAllowed('xcDatatableEditable')),
computed(() => !isUIAllowed('dataEdit')),
)
const grid = ref()

15
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,
@ -15,7 +14,7 @@ import {
useI18n,
useNuxtApp,
useProject,
useUIPermission,
useRoles,
watchDebounced,
} from '#imports'
import type { User } from '#imports'
@ -30,7 +29,7 @@ const { project } = storeToRefs(useProject())
const { copy } = useCopy()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { dashboardUrl } = useDashboard()
@ -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>

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

@ -16,9 +16,9 @@ import {
inject,
ref,
useProvideLTARStore,
useRoles,
useSelectedCellKeyupListener,
useSmartsheetRowStoreOrThrow,
useUIPermission,
} from '#imports'
const column = inject(ColumnInj)!
@ -39,7 +39,7 @@ const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const listItemsDlg = ref(false)
@ -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

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

@ -14,9 +14,9 @@ import {
inject,
ref,
useProvideLTARStore,
useRoles,
useSelectedCellKeyupListener,
useSmartsheetRowStoreOrThrow,
useUIPermission,
} from '#imports'
const column = inject(ColumnInj)!
@ -39,7 +39,7 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
@ -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"

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

@ -27,7 +27,7 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { state, isNew } = useSmartsheetRowStoreOrThrow()
@ -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"
/>

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

@ -16,9 +16,9 @@ import {
inject,
ref,
useProvideLTARStore,
useRoles,
useSelectedCellKeyupListener,
useSmartsheetRowStoreOrThrow,
useUIPermission,
} from '#imports'
const column = inject(ColumnInj)!
@ -41,7 +41,7 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
@ -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"

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

@ -28,7 +28,7 @@ const emit = defineEmits(['unlink'])
const { relatedTableMeta } = useLTARStoreOrThrow()!
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const readOnly = inject(ReadonlyInj, ref(false))
@ -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

4
packages/nc-gui/components/workspace/CreateProjectBtn.vue

@ -10,7 +10,7 @@ const props = defineProps<{
centered?: boolean
}>()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { orgRoles, workspaceRoles } = useRoles()
@ -28,7 +28,7 @@ const centered = computed(() => props.centered ?? true)
<template>
<NcButton
v-if="isUIAllowed('projectCreate', false, workspaceRoles ?? orgRoles) && !isSharedBase"
v-if="isUIAllowed('projectCreate', { roles: workspaceRoles ?? orgRoles }) && !isSharedBase"
type="text"
:size="size"
:centered="centered"

22
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'
@ -20,7 +20,7 @@ const { navigateToProject } = useGlobal()
const { $e, $jobs } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { refreshCommandPalette } = useCommandPalette()
@ -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) => {
@ -326,7 +326,7 @@ const setIcon = async (icon: string, project: ProjectType) => {
<template v-if="column.dataIndex === 'id'">
<a-dropdown
v-if="isUIAllowed('projectActionMenu', true, [record.workspace_role, record.project_role].join())"
v-if="isUIAllowed('projectActionMenu', { roles: [record.workspace_role, record.project_role].join() })"
:trigger="['click']"
>
<div @click.stop>
@ -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', { roles: [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', { roles: [record.workspace_role, record.project_role].join() })"
@click="moveProject(record)"
>
<div class="nc-menu-item-wrapper">
@ -367,7 +367,7 @@ const setIcon = async (icon: string, project: ProjectType) => {
</a-menu-item>
-->
<a-menu-item
v-if="isUIAllowed('projectDelete', true, [record.workspace_role, record.project_role].join())"
v-if="isUIAllowed('projectDelete', { roles: [record.workspace_role, record.project_role].join() })"
@click="deleteProject(record)"
>
<div class="nc-menu-item-wrapper text-red-500">

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

@ -57,7 +57,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
// getters
const displayValue = computed(() => {

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

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

@ -1,23 +1,12 @@
import type { ColumnType, GridColumnReqType, GridColumnType, ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {
IsPublicInj,
computed,
inject,
ref,
useMetas,
useNuxtApp,
useStyleTag,
useUIPermission,
useUndoRedo,
watch,
} from '#imports'
import { IsPublicInj, computed, inject, ref, useMetas, useNuxtApp, useRoles, useStyleTag, useUndoRedo, watch } from '#imports'
const [useProvideGridViewColumn, useGridViewColumn] = useInjectionState(
(view: Ref<(ViewType & { columns?: GridColumnType[] }) | undefined>, statePublic = false) => {
const { css, load: loadCss, unload: unloadCss } = useStyleTag('')
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { $api } = useNuxtApp()
@ -99,7 +88,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,
})

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

@ -21,9 +21,9 @@ import {
useInjectionState,
useNuxtApp,
useProject,
useRoles,
useSharedView,
useSmartsheetStoreOrThrow,
useUIPermission,
useUndoRedo,
} from '#imports'
@ -51,7 +51,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const { sharedView, fetchSharedViewData, fetchSharedViewGroupedData } = useSharedView()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false))
@ -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)
}

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

@ -34,7 +34,7 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
const { $api } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false))
@ -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)
}

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

@ -1,6 +1,22 @@
import { isString } from '@vue/shared'
import type { Roles, RolesObj, WorkspaceUserRoles } from 'nocodb-sdk'
import { extractRolesObj } from 'nocodb-sdk'
import { computed, createSharedComposable, useApi, useGlobal } from '#imports'
import type { ProjectRole, Role, Roles } from '#imports'
import { computed, createSharedComposable, rolePermissions, useApi, useGlobal } from '#imports'
import type { Permission } from '#imports'
const hasPermission = (role: Exclude<Roles, WorkspaceUserRoles>, hasRole: boolean, permission: Permission | string) => {
const rolePermission = rolePermissions[role]
if (!hasRole || !rolePermission) return false
if (isString(rolePermission) && rolePermission === '*') return true
if ('include' in rolePermission && rolePermission.include) {
return !!rolePermission.include[permission as keyof typeof rolePermission.include]
}
return rolePermission[permission as keyof typeof rolePermission]
}
/**
* Provides the roles a user currently has
@ -8,15 +24,14 @@ 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(() => {
const { user, previewAs } = useGlobal()
const { user } = useGlobal()
const { api } = useApi()
const allRoles = computed<Roles | null>(() => {
const allRoles = computed<RolesObj | null>(() => {
let orgRoles = user.value?.roles ?? {}
orgRoles = extractRolesObj(orgRoles)
@ -31,7 +46,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 +54,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 +66,7 @@ export const useRoles = createSharedComposable(() => {
return projectRoles
})
const workspaceRoles = computed<Roles | null>(() => {
const workspaceRoles = computed<RolesObj | null>(() => {
return null
})
@ -75,7 +90,7 @@ export const useRoles = createSharedComposable(() => {
...user.value,
roles: res.roles,
project_roles: res.project_roles,
}
} as typeof User
} else if (options?.isSharedErd) {
const res = await api.auth.me(
{
@ -92,7 +107,7 @@ export const useRoles = createSharedComposable(() => {
...user.value,
roles: res.roles,
project_roles: res.project_roles,
}
} as typeof User
} else if (projectId) {
const res = await api.auth.me({ project_id: projectId })
@ -100,17 +115,28 @@ export const useRoles = createSharedComposable(() => {
...user.value,
roles: res.roles,
project_roles: res.project_roles,
}
} as typeof User
}
}
function hasRole(role: Role | ProjectRole | string, includePreviewRoles = false) {
if (previewAs.value && includePreviewRoles) {
return previewAs.value === role
const isUIAllowed = (
permission: Permission | string,
args: { roles?: string | Record<string, boolean> | string[] | null } = {},
) => {
const { roles } = args
let checkRoles: Record<string, boolean> = {}
if (!roles) {
if (allRoles.value) checkRoles = allRoles.value
} else {
checkRoles = extractRolesObj(roles)
}
return allRoles.value[role]
return Object.entries(checkRoles).some(([role, hasRole]) =>
hasPermission(role as Exclude<Roles, WorkspaceUserRoles>, hasRole, permission),
)
}
return { allRoles, orgRoles, workspaceRoles, projectRoles, loadRoles, hasRole }
return { allRoles, orgRoles, workspaceRoles, projectRoles, loadRoles, isUIAllowed }
})

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

@ -1,61 +0,0 @@
import { isString } from '@vue/shared'
import { createSharedComposable, rolePermissions, useGlobal, useRoles } from '#imports'
import type { Permission, ProjectRole, Role } from '#imports'
const hasPermission = (role: Role | ProjectRole, hasRole: boolean, permission: Permission | string) => {
const rolePermission = rolePermissions[role]
if (!hasRole || !rolePermission) return false
if (isString(rolePermission) && rolePermission === '*') return true
if ('include' in rolePermission && rolePermission.include) {
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]
}
export const useUIPermission = createSharedComposable(() => {
const { previewAs } = useGlobal()
const { allRoles } = useRoles()
const isUIAllowed = (
permission: Permission | string,
skipPreviewAs = false,
userRoles: string | Record<string, boolean> | string[] | null = null,
combineWithStateRoles = false,
) => {
if (previewAs.value && !skipPreviewAs) {
return hasPermission(previewAs.value, true, permission)
}
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 (userRoles && combineWithStateRoles) {
roles = { ...roles, ...allRoles.value }
}
return Object.entries(roles).some(([role, hasRole]) => hasPermission(role as Role | ProjectRole, hasRole, permission))
}
return { isUIAllowed }
})

0
packages/nc-gui/composables/useUIPermission/rolePermissions.ts

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

@ -1,18 +1,7 @@
import { ViewTypes, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, MapType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import {
IsPublicInj,
computed,
inject,
ref,
storeToRefs,
useNuxtApp,
useProject,
useUIPermission,
useUndoRedo,
watch,
} from '#imports'
import { IsPublicInj, computed, inject, ref, storeToRefs, useNuxtApp, useProject, useRoles, useUndoRedo, watch } from '#imports'
import type { Field } from '#imports'
export function useViewColumns(
@ -28,14 +17,14 @@ export function useViewColumns(
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { isSharedBase } = storeToRefs(useProject())
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 +158,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) {

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

@ -16,11 +16,11 @@ import {
useI18n,
useNuxtApp,
useProject,
useRoles,
useRouter,
useSharedView,
useSmartsheetStoreOrThrow,
useState,
useUIPermission,
} from '#imports'
import type { Row } from '#imports'
@ -76,7 +76,7 @@ export function useViewData(
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const routeQuery = computed(() => route.value.query as Record<string, string>)
@ -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

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

@ -16,7 +16,7 @@ import {
useMetas,
useNuxtApp,
useProject,
useUIPermission,
useRoles,
watch,
} from '#imports'
import type { Filter, UndoRedoAction } from '#imports'
@ -42,7 +42,7 @@ export function useViewFilters(
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { metas } = useMetas()

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

@ -34,7 +34,7 @@ export const useViewGroupBy = (view: Ref<ViewType | undefined>, where?: Computed
const isGroupBy = computed(() => !!groupBy.value.length)
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()

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

@ -12,9 +12,9 @@ import {
storeToRefs,
useNuxtApp,
useProject,
useRoles,
useSharedView,
useSmartsheetStoreOrThrow,
useUIPermission,
} from '#imports'
export function useViewSorts(view: Ref<ViewType | undefined>, reloadData?: () => void) {
@ -24,7 +24,7 @@ export function useViewSorts(view: Ref<ViewType | undefined>, reloadData?: () =>
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const { isSharedBase } = storeToRefs(useProject())

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"

148
packages/nc-gui/lib/acl.ts

@ -0,0 +1,148 @@
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk'
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
* `undefined` or `{}` - which is the default value, means no permissions are allowed
* */
const rolePermissions = {
// org level role permissions
[OrgUserRoles.SUPER_ADMIN]: '*',
[OrgUserRoles.CREATOR]: {
include: {
projectCreate: true,
projectMove: true,
projectDelete: true,
projectDuplicate: true,
newUser: true,
},
},
[OrgUserRoles.VIEWER]: {
include: {
importRequest: true,
},
},
// Project role permissions
[ProjectRoles.OWNER]: {
include: {
projectDelete: 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,
fieldEdit: true,
fieldAdd: true,
tableIconEdit: true,
viewCreateOrEdit: true,
viewShare: true,
projectShare: true,
},
},
[ProjectRoles.EDITOR]: {
include: {
dataInsert: true,
dataEdit: true,
sortSync: true,
filterSync: true,
filterChildrenRead: true,
viewFieldEdit: true,
csvImport: true,
apiDocs: true,
},
},
[ProjectRoles.COMMENTER]: {
include: {
commentEdit: true,
commentList: true,
commentCount: true,
},
},
[ProjectRoles.VIEWER]: {
include: {
projectSettings: true,
expandedForm: true,
},
},
[ProjectRoles.NO_ACCESS]: {
include: {},
},
} as Record<OrgUserRoles | ProjectRoles, Perm | '*'>
/*
We inherit include permissions from previous roles in the same scope (role order)
To determine role order, we use `roleScopes` object
So for example ProjectRoles.COMMENTER has `commentEdit` permission,
which means ProjectRoles.EDITOR, ProjectRoles.CREATOR, ProjectRoles.OWNER will also have `commentEdit` permission
where as ProjectRoles.VIEWER, ProjectRoles.NO_ACCESS will not have `commentEdit` permission.
This is why we are validating that there are no duplicate permissions within the same scope
even though it is not required for the code to work. It is to keep the code clean and easy to understand.
*/
// validate no duplicate permissions within same scope
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 }

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

@ -1,5 +1,3 @@
import { ProjectRole, Role } from './enums'
export const NOCO = 'noco'
export const SYSTEM_COLUMNS = ['id', 'title', 'created_at', 'updated_at']
@ -18,90 +16,3 @@ export const GROUP_BY_VARS = {
__nc_false__: 'Unchecked',
} as Record<string, string>,
}
/**
* 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]: {
include: {
projectCreate: true,
projectActions: true,
projectSettings: true,
},
},
// Project role permissions
[ProjectRole.Creator]: {
exclude: {
appStore: true,
superAdminUserManagement: true,
superAdminAppSettings: true,
appLicense: true,
moveProject: true,
projectDelete: true,
projectCreate: true,
},
},
[ProjectRole.Owner]: {
exclude: {
projectCreate: true,
appStore: true,
superAdminUserManagement: true,
superAdminAppSettings: true,
appLicense: true,
},
},
[ProjectRole.Editor]: {
include: {
smartSheet: true,
xcDatatableEditable: true,
column: true,
tableAttachment: true,
tableRowUpdate: true,
dataInsert: true,
rowComments: true,
gridViewOptions: true,
sortSync: true,
fieldsSync: true,
gridColUpdate: true,
filterSync: true,
filterChildrenRead: true,
csvImport: true,
apiDocs: true,
projectSettings: true,
newUser: false,
commentEditable: true,
commentList: true,
commentsCount: true,
},
},
[ProjectRole.Commenter]: {
include: {
smartSheet: true,
column: true,
rowComments: true,
projectSettings: true,
commentEditable: true,
commentList: true,
commentsCount: true,
},
},
[ProjectRole.Viewer]: {
include: {
smartSheet: true,
column: true,
projectSettings: true,
},
},
} as const
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',

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

@ -1,3 +1,4 @@
export * from './constants'
export * from './enums'
export * from './types'
export * from './acl'

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,

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

@ -1,11 +1,11 @@
<script lang="ts" setup>
import { iconMap, navigateTo, useUIPermission } from '#imports'
import { iconMap, navigateTo, useRoles } from '#imports'
definePageMeta({
hideHeader: true,
})
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const $route = useRoute()
@ -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="{

4
packages/nc-gui/pages/account/index/users/[[nestedPage]].vue

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useUIPermission } from '#imports'
import { useRoles } from '#imports'
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
</script>
<template>

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

@ -3,7 +3,7 @@ import { isDrawerOrModalExist, useEventListener } from '#imports'
export const useProjectsShortcuts = defineStore('projectsShortcutsStore', () => {
const { $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isUIAllowed } = useRoles()
const isMounted = ref(false)
@ -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
}

7
packages/nc-gui/store/workspace.ts

@ -1,3 +1,4 @@
import { OrgUserRoles } from 'nocodb-sdk'
import type { ProjectType } from 'nocodb-sdk'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { message } from 'ant-design-vue'
@ -66,17 +67,17 @@ export const useWorkspace = defineStore('workspaceStore', () => {
/** getters */
const isWorkspaceCreator = computed(() => {
// todo: type correction
return orgRoles.value?.[Role.OrgLevelCreator]
return orgRoles.value?.[OrgUserRoles.CREATOR]
})
const isWorkspaceOwner = computed(() => {
// todo: type correction
return orgRoles.value?.[Role.OrgLevelCreator]
return orgRoles.value?.[OrgUserRoles.CREATOR]
})
const isWorkspaceOwnerOrCreator = computed(() => {
// todo: type correction
return orgRoles.value?.[Role.OrgLevelCreator]
return orgRoles.value?.[OrgUserRoles.CREATOR]
})
/** actions */

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 };

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

@ -1,5 +1,5 @@
import UITypes from './UITypes';
import { OrgUserRoles, ProjectRoles, WorkspaceUserRoles } from './enums';
import { 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;
@ -44,7 +39,8 @@ const extractRolesObj = (roles: Roles | string[] | string): Roles => {
}, {});
};
const stringifyRolesObj = (roles: Roles): string => {
const stringifyRolesObj = (roles?: RolesObj | null): string => {
if (!roles) return '';
const rolesArr = Object.keys(roles).filter((r) => roles[r]);
return rolesArr.join(',');
};

Loading…
Cancel
Save