Browse Source

fix(nc-gui): display audit logs in modal for user how have only base creator access

pull/8836/head
Ramesh Mane 4 months ago
parent
commit
2a0b44f12a
  1. 5
      packages/nc-gui/components/dlg/ProjectAudit.vue
  2. 466
      packages/nc-gui/components/workspace/AuditLogs.vue
  3. 4
      packages/nc-gui/components/workspace/View.vue
  4. 1
      packages/nc-gui/lib/acl.ts

5
packages/nc-gui/components/dlg/ProjectAudit.vue

@ -1,5 +1,6 @@
<script lang="ts" setup>
const props = defineProps<{
workspaceId?: string
baseId: string
sourceId: string
modelValue: boolean
@ -9,7 +10,7 @@ const emit = defineEmits(['update:modelValue'])
const isOpen = useVModel(props, 'modelValue', emit)
const activeSourceId = computed(() => props.sourceId)
const { workspaceId, sourceId } = toRefs(props)
const { openedProject: base } = storeToRefs(useBases())
@ -47,7 +48,7 @@ onMounted(async () => {
<template>
<GeneralModal v-model:visible="isOpen" size="xl" class="!w-[70rem] !top-[5vh]">
<div class="p-6 h-full">
<DashboardSettingsBaseAudit v-if="!isLoading" :source-id="activeSourceId" :base-id="baseId" :show-all-columns="false" />
<WorkspaceAuditLogs v-if="!isLoading" :workspace-id="workspaceId" :source-id="sourceId" :base-id="baseId" />
</div>
</GeneralModal>
</template>

466
packages/nc-gui/components/workspace/AuditLogs.vue

@ -8,12 +8,16 @@ import { AuditLogsDateRange } from '~/lib/enums'
interface Props {
workspaceId?: string
baseId?: string
sourceId?: string
}
const props = defineProps<Props>()
const allowedAuditOperationTypes = [AuditOperationTypes.DATA, AuditOperationTypes.TABLE, AuditOperationTypes.TABLE_COLUMN]
const { isUIAllowed } = useRoles()
const workspaceStore = useWorkspace()
const { loadAudits: _loadAudits } = workspaceStore
@ -30,7 +34,8 @@ const {
const basesStore = useBases()
const { getBaseUsers } = basesStore
const { bases, basesList, basesUser } = storeToRefs(basesStore)
const { bases, basesList } = storeToRefs(basesStore)
const baseCollaborators = ref<User[]>([])
@ -111,12 +116,18 @@ const dateRangeOptions = computed(() => {
]
})
async function loadAudits(page = currentPage.value, limit = currentLimit.value) {
async function loadAudits(page = currentPage.value, limit = currentLimit.value, updateCurrentPage = true) {
try {
if (!props.workspaceId) return
if ((isUIAllowed('workspaceAuditList') && !props.workspaceId) || (!isUIAllowed('workspaceAuditList') && !props.baseId)) {
return
}
if (updateCurrentPage) {
currentPage.value = 1
}
isLoading.value = true
await _loadAudits(props.workspaceId, page, limit)
await _loadAudits(props.workspaceId, updateCurrentPage ? 1 : page, limit)
} catch {
} finally {
isLoading.value = false
@ -272,31 +283,43 @@ watch(
)
onMounted(async () => {
if (audits.value === null) {
await loadAudits(currentPage.value, currentLimit.value)
if (props.baseId) {
auditLogsQuery.value.base = props.baseId
}
if (props.sourceId) {
auditLogsQuery.value.sourceId = props.sourceId
}
if (audits.value === null && appInfo.value.auditEnabled) {
await loadAudits(currentPage.value, currentLimit.value, false)
}
})
</script>
<template>
<div class="h-full flex flex-col gap-4 w-full h-[calc(100vh-120px)] pt-6">
<div class="h-full flex flex-col w-full" :class="{ 'pt-6 gap-6': !baseId, 'gap-4': baseId }">
<div v-if="!appInfo.auditEnabled" class="text-red-500">Audit logs are currently disabled by administrators.</div>
<div class="flex flex-col gap-y-6">
<div class="flex flex-col" :class="{ 'gap-6': !baseId, 'gap-4': baseId }">
<div class="flex flex-col gap-3">
<div class="flex flex-row items-center gap-3">
<h6 class="text-xl font-semibold text-gray-900 !my-0">Audit Logs</h6>
<NcButton class="!px-1" type="text" size="xs" :disabled="isLoading" @click="loadAudits()">
<h6 class="text-xl font-semibold text-gray-900 !my-0">
Audit Logs
<span v-if="baseId"> : {{ bases.get(baseId)?.title }} </span>
</h6>
<NcButton v-if="appInfo.auditEnabled" class="!px-1" type="text" size="xs" :disabled="isLoading" @click="loadAudits()">
<!-- Reload -->
<div class="flex items-center text-gray-600 font-light">
<component :is="iconMap.refresh" :class="{ 'animate-infinite animate-spin': isLoading }" />
</div>
</NcButton>
</div>
<div class="text-sm text-gray-600">Track and monitor any changes made to any base in your workspace.</div>
<div v-if="!baseId" class="text-sm text-gray-600">Track and monitor any changes made to any base in your workspace.</div>
</div>
<div class="px-1 flex items-center gap-3">
<div v-if="appInfo.auditEnabled" class="px-1 flex items-center gap-3">
<NcDropdown
v-if="collaborators?.length"
v-if="auditCollaborators?.length"
v-model:visible="auditDropdowns.user"
@update:visible="handleClearDropdownSearch($event, 'user')"
overlay-class-name="overflow-hidden"
@ -403,7 +426,7 @@ onMounted(async () => {
</NcDropdown>
<NcDropdown
v-if="basesList?.length"
v-if="!baseId && basesList?.length"
v-model:visible="auditDropdowns.base"
@update:visible="handleClearDropdownSearch($event, 'base')"
overlay-class-name="overflow-hidden"
@ -583,7 +606,7 @@ onMounted(async () => {
<div class="!w-[127px] flex items-center justify-between gap-2">
<div class="max-w-full truncate text-sm !leading-5">
Range:
<span :class="{ 'text-brand-500': auditLogsQuery.type }">
<span :class="{ 'text-brand-500': auditLogsQuery.dateRange }">
{{ auditLogsQuery.dateRange ? auditLogsQuery.dateRangeLabel : 'All Time' }}
</span>
</div>
@ -673,254 +696,261 @@ onMounted(async () => {
</NcDropdown>
</div>
</div>
<div class="h-[calc(100%_-_134px)] relative">
<div class="table-wrapper max-h-[calc(100%_-_40px)] overflow-auto nc-scrollbar-thin">
<div class="nc-audit-logs-table table h-full relative">
<div class="thead sticky top-0">
<div class="tr">
<div class="th cell-user !hover:bg-gray-100" @click="updateOrderBy('user')">
<div class="flex items-center gap-3">
<div class="flex-1">User</div>
<NcButton type="text" size="xs" class="!p-0">
<template v-if="appInfo.auditEnabled">
<div
class="relative"
:class="{
'h-[calc(100%_-_92px)] ': baseId,
'h-[calc(100%_-_134px)]': !baseId,
}"
>
<div class="table-wrapper max-h-[calc(100%_-_40px)] overflow-auto nc-scrollbar-thin relative">
<div class="nc-audit-logs-table table h-full relative">
<div class="thead sticky top-0">
<div class="tr">
<div class="th cell-user !hover:bg-gray-100" @click="updateOrderBy('user')">
<div class="flex items-center gap-3">
<div>User</div>
<GeneralIcon
icon="chevronUpDown"
v-if="auditLogsQuery.orderBy?.user"
icon="chevronDown"
class="flex-none"
:class="{
'sort-asc': auditLogsQuery.orderBy?.user === 'asc',
'sort-desc': auditLogsQuery.orderBy?.user === 'desc',
'transform rotate-180': auditLogsQuery.orderBy?.user === 'asc',
}"
/>
</NcButton>
</div>
</div>
</div>
<div class="th cell-timestamp !hover:bg-gray-100" @click="updateOrderBy('created_at')">
<div class="flex items-center gap-3">
<div class="flex-1">Time stamp</div>
<NcButton type="text" size="xs" class="!p-0">
<div class="th cell-timestamp !hover:bg-gray-100" @click="updateOrderBy('created_at')">
<div class="flex items-center gap-3">
<div>Time stamp</div>
<GeneralIcon
icon="chevronUpDown"
v-if="auditLogsQuery.orderBy?.created_at"
icon="chevronDown"
class="flex-none"
:class="{
'sort-asc': auditLogsQuery.orderBy?.created_at === 'asc',
'sort-desc': auditLogsQuery.orderBy?.created_at === 'desc',
'transform rotate-180': auditLogsQuery.orderBy?.created_at === 'asc',
}"
/>
</NcButton>
</div>
</div>
</div>
<div class="th cell-base">Base</div>
<div class="th cell-type">Type</div>
<div class="th cell-sub-type">Sub-type</div>
<div class="th cell-description">Description</div>
<div class="th cell-ip">IP</div>
</div>
</div>
<div class="tbody">
<div
v-show="isLoading"
class="flex items-center justify-center absolute l-0 t-0 w-full h-full z-10 pb-10 pointer-events-none"
>
<div class="flex flex-col justify-center items-center gap-2">
<GeneralLoader size="xlarge" />
<span class="text-center">Loading...</span>
<div class="th cell-base">Base</div>
<div class="th cell-type">Type</div>
<div class="th cell-sub-type">Sub-type</div>
<div class="th cell-description">Description</div>
<div class="th cell-ip">IP</div>
</div>
</div>
<template v-if="audits?.length">
<template v-for="(audit, i) of audits" :key="i">
<div
class="tr"
:class="{
selected: selectedAudit?.id === audit.id && isRowExpanded,
}"
@click="handleRowClick(audit)"
>
<div class="td cell-user">
<div v-if="audit.user && collaboratorsMap.get(audit.user)?.email" class="w-full flex gap-3 items-center">
<GeneralUserIcon :email="collaboratorsMap.get(audit.user)?.email" size="base" class="flex-none" />
<div class="flex-1 flex flex-col max-w-[calc(100%_-_44px)]">
<div class="w-full flex gap-3">
<span class="text-sm text-gray-800 capitalize font-semibold truncate">
{{
collaboratorsMap.get(audit.user)?.display_name ||
collaboratorsMap
.get(audit.user)
?.email?.slice(0, collaboratorsMap.get(audit.user)?.email.indexOf('@'))
}}
<div class="tbody">
<template v-if="audits?.length">
<template v-for="(audit, i) of audits" :key="i">
<div
class="tr"
:class="{
selected: selectedAudit?.id === audit.id && isRowExpanded,
}"
@click="handleRowClick(audit)"
>
<div class="td cell-user">
<div v-if="audit.user && collaboratorsMap.get(audit.user)?.email" class="w-full flex gap-3 items-center">
<GeneralUserIcon :email="collaboratorsMap.get(audit.user)?.email" size="base" class="flex-none" />
<div class="flex-1 flex flex-col max-w-[calc(100%_-_44px)]">
<div class="w-full flex gap-3">
<span class="text-sm text-gray-800 capitalize font-semibold truncate">
{{
collaboratorsMap.get(audit.user)?.display_name ||
collaboratorsMap
.get(audit.user)
?.email?.slice(0, collaboratorsMap.get(audit.user)?.email.indexOf('@'))
}}
</span>
</div>
<span class="text-xs text-gray-600 truncate">
{{ collaboratorsMap.get(audit.user)?.email }}
</span>
</div>
<span class="text-xs text-gray-600 truncate">
{{ collaboratorsMap.get(audit.user)?.email }}
</span>
</div>
<template v-else>{{ audit.user }} </template>
</div>
<template v-else>{{ audit.user }} </template>
</div>
<div class="td cell-timestamp">
<NcTooltip placement="bottom">
<template #title> {{ parseStringDateTime(audit.created_at, 'D MMMM YYYY HH:mm') }}</template>
<div class="td cell-timestamp">
<NcTooltip placement="bottom">
<template #title> {{ parseStringDateTime(audit.created_at, 'D MMMM YYYY HH:mm') }}</template>
{{ timeAgo(audit.created_at) }}
</NcTooltip>
</div>
<div class="td cell-base">
<div v-if="audit.base_id" class="w-full">
<div class="truncate text-sm text-gray-800 font-semibold">
{{ bases.get(audit.base_id)?.title }}
{{ timeAgo(audit.created_at) }}
</NcTooltip>
</div>
<div class="td cell-base">
<div v-if="audit.base_id" class="w-full">
<div class="truncate text-sm text-gray-800 font-semibold">
{{ bases.get(audit.base_id)?.title }}
</div>
<div class="text-gray-600 text-xs">ID: {{ audit.base_id }}</div>
</div>
<div class="text-gray-600 text-xs">ID: {{ audit.base_id }}</div>
<template v-else>
{{ audit.base_id }}
</template>
</div>
<template v-else>
{{ audit.base_id }}
</template>
</div>
<div class="td cell-type">
<div class="truncate bg-gray-200 px-2 py-1 rounded-lg">
<NcTooltip class="truncate" placement="bottom" show-on-truncate-only>
<template #title> {{ auditOperationTypeLabels[audit.op_type] }}</template>
<div class="td cell-type">
<div class="truncate bg-gray-200 px-2 py-1 rounded-lg">
<NcTooltip class="truncate" placement="bottom" show-on-truncate-only>
<template #title> {{ auditOperationTypeLabels[audit.op_type] }}</template>
<span class="truncate"> {{ auditOperationTypeLabels[audit.op_type] }} </span>
</NcTooltip>
<span class="truncate"> {{ auditOperationTypeLabels[audit.op_type] }} </span>
</NcTooltip>
</div>
</div>
</div>
<div class="td cell-sub-type">
<div class="truncate">
<NcTooltip class="truncate" placement="bottom" show-on-truncate-only>
<template #title> {{ auditOperationSubTypeLabels[audit.op_sub_type] }}</template>
<div class="td cell-sub-type">
<div class="truncate">
<NcTooltip class="truncate" placement="bottom" show-on-truncate-only>
<template #title> {{ auditOperationSubTypeLabels[audit.op_sub_type] }}</template>
<span class="truncate"> {{ auditOperationSubTypeLabels[audit.op_sub_type] }} </span>
</NcTooltip>
<span class="truncate"> {{ auditOperationSubTypeLabels[audit.op_sub_type] }} </span>
</NcTooltip>
</div>
</div>
</div>
<div class="td cell-description">
<div class="truncate">
{{ audit.description }}
<div class="td cell-description">
<div class="truncate">
{{ audit.description }}
</div>
</div>
</div>
<div class="td cell-ip">
<div class="truncate">
{{ audit.ip }}
<div class="td cell-ip">
<div class="truncate">
{{ audit.ip }}
</div>
</div>
</div>
</div>
</template>
</template>
</template>
<div v-else-if="!isLoading" class="flex items-center justify-center text-gray-500">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
<div v-else-if="!isLoading" class="flex items-center justify-center text-gray-500">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</div>
</div>
</div>
</div>
</div>
<div
v-if="totalRows"
class="flex flex-row justify-center items-center bg-gray-50 min-h-10"
:class="{
'pointer-events-none': isLoading,
}"
>
<div class="flex justify-between items-center w-full px-6">
<div>&nbsp;</div>
<template v-if="+totalRows > currentLimit">
<NcPagination
v-model:current="currentPage"
v-model:page-size="currentLimit"
:total="+totalRows"
show-size-changer
:use-stored-page-size="false"
@update:current="loadAudits()"
@update:page-size="loadAudits(currentPage, $event)"
/>
</template>
<div class="text-gray-500 text-xs">{{ totalRows }} {{ totalRows === 1 ? 'record' : 'records' }}</div>
<div
v-show="isLoading"
class="flex items-center justify-center absolute left-0 top-0 w-full h-full z-10 pb-10 pointer-events-none"
>
<div class="flex flex-col justify-center items-center gap-2">
<GeneralLoader size="xlarge" />
<span class="text-center">Loading...</span>
</div>
</div>
</div>
</div>
<NcModal v-model:visible="isRowExpanded" size="medium" :show-separator="false" @keydown.esc="isRowExpanded = false">
<template #header>
<div class="flex items-center justify-between gap-x-2 w-full">
<div class="flex-1 text-base font-weight-700 text-gray-900">Audit Details</div>
<div class="flex items-center gap-2">
<span class="cell-header"> Time stamp </span>
<NcTooltip placement="bottom" class="text-gray-600 text-small leading-[18px]">
<template #title> {{ parseStringDateTime(selectedAudit.created_at, 'D MMMM YYYY HH:mm') }}</template>
{{ timeAgo(selectedAudit.created_at) }}
</NcTooltip>
<div
v-if="totalRows"
class="flex flex-row justify-center items-center bg-gray-50 min-h-10"
:class="{
'pointer-events-none': isLoading,
}"
>
<div class="flex justify-between items-center w-full px-6">
<div>&nbsp;</div>
<template v-if="+totalRows > currentLimit">
<NcPagination
v-model:current="currentPage"
v-model:page-size="currentLimit"
:total="+totalRows"
show-size-changer
:use-stored-page-size="false"
@update:current="loadAudits(undefined, undefined, false)"
@update:page-size="loadAudits(currentPage, $event, false)"
/>
</template>
<div class="text-gray-500 text-xs">{{ totalRows }} {{ totalRows === 1 ? 'record' : 'records' }}</div>
</div>
</div>
</template>
<div v-if="selectedAudit" class="flex flex-col gap-4">
<div class="bg-gray-50 rounded-lg border-1 border-gray-200 flex">
<div class="w-1/2 border-r border-gray-200 flex flex-col gap-2 px-4 py-3">
<div class="cell-header">Performed by</div>
<div
v-if="selectedAudit?.user && collaboratorsMap.get(selectedAudit.user)?.email"
class="w-full flex gap-3 items-center"
>
<GeneralUserIcon :email="collaboratorsMap.get(selectedAudit.user)?.email" size="base" class="flex-none" />
<div class="flex-1 flex flex-col">
<div class="w-full flex gap-3">
<span class="text-sm text-gray-800 capitalize font-semibold">
{{
collaboratorsMap.get(selectedAudit.user)?.display_name ||
collaboratorsMap
.get(selectedAudit.user)
?.email?.slice(0, collaboratorsMap.get(selectedAudit.user)?.email.indexOf('@'))
}}
</div>
<NcModal v-model:visible="isRowExpanded" size="medium" :show-separator="false" @keydown.esc="isRowExpanded = false">
<template #header>
<div class="flex items-center justify-between gap-x-2 w-full">
<div class="flex-1 text-base font-weight-700 text-gray-900">Audit Details</div>
<div class="flex items-center gap-2">
<span class="cell-header"> Time stamp </span>
<NcTooltip placement="bottom" class="text-gray-600 text-small leading-[18px]">
<template #title> {{ parseStringDateTime(selectedAudit.created_at, 'D MMMM YYYY HH:mm') }}</template>
{{ timeAgo(selectedAudit.created_at) }}
</NcTooltip>
</div>
</div>
</template>
<div v-if="selectedAudit" class="flex flex-col gap-4">
<div class="bg-gray-50 rounded-lg border-1 border-gray-200 flex">
<div class="w-1/2 border-r border-gray-200 flex flex-col gap-2 px-4 py-3">
<div class="cell-header">Performed by</div>
<div
v-if="selectedAudit?.user && collaboratorsMap.get(selectedAudit.user)?.email"
class="w-full flex gap-3 items-center"
>
<GeneralUserIcon :email="collaboratorsMap.get(selectedAudit.user)?.email" size="base" class="flex-none" />
<div class="flex-1 flex flex-col">
<div class="w-full flex gap-3">
<span class="text-sm text-gray-800 capitalize font-semibold">
{{
collaboratorsMap.get(selectedAudit.user)?.display_name ||
collaboratorsMap
.get(selectedAudit.user)
?.email?.slice(0, collaboratorsMap.get(selectedAudit.user)?.email.indexOf('@'))
}}
</span>
</div>
<span class="text-xs text-gray-600">
{{ collaboratorsMap.get(selectedAudit.user)?.email }}
</span>
</div>
<span class="text-xs text-gray-600">
{{ collaboratorsMap.get(selectedAudit.user)?.email }}
</span>
</div>
</div>
<div v-else class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.user }}</div>
</div>
<div class="w-1/2 flex flex-col gap-2 px-4 py-3">
<div class="cell-header">IP Address</div>
<div class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.ip }}</div>
</div>
</div>
<div class="bg-gray-50 rounded-lg border-1 border-gray-200 flex">
<div class="w-1/2 border-r border-gray-200 flex flex-col gap-2 px-4 py-3">
<div class="cell-header">Base</div>
<div v-if="selectedAudit?.base_id && bases.get(selectedAudit?.base_id)" class="flex items-stretch gap-3">
<div class="flex items-center">
<GeneralProjectIcon
:color="bases.get(selectedAudit?.base_id)?.meta?.iconColor"
:type="bases.get(selectedAudit?.base_id)?.type || 'database'"
class="nc-view-icon w-5 h-5"
/>
</div>
<div>
<div class="text-sm font-weight-500 text-gray-900">{{ bases.get(selectedAudit?.base_id)?.title }}</div>
<div class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.base_id }}</div>
</div>
<div v-else class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.user }}</div>
</div>
<div class="w-1/2 flex flex-col gap-2 px-4 py-3">
<div class="cell-header">IP Address</div>
<div class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.ip }}</div>
</div>
<template v-else>
{{ selectedAudit.base_id }}
</template>
</div>
<div class="w-1/2">
<div class="h-1/2 border-b border-gray-200 flex items-center gap-2 px-4 py-3">
<div class="cell-header">Type</div>
<div class="text-small leading-[18px] text-gray-600 bg-gray-200 px-1 rounded-md">
{{ auditOperationTypeLabels[selectedAudit?.op_type] }}
<div class="bg-gray-50 rounded-lg border-1 border-gray-200 flex">
<div class="w-1/2 border-r border-gray-200 flex flex-col gap-2 px-4 py-3">
<div class="cell-header">Base</div>
<div v-if="selectedAudit?.base_id && bases.get(selectedAudit?.base_id)" class="flex items-stretch gap-3">
<div class="flex items-center">
<GeneralProjectIcon
:color="bases.get(selectedAudit?.base_id)?.meta?.iconColor"
:type="bases.get(selectedAudit?.base_id)?.type || 'database'"
class="nc-view-icon w-5 h-5"
/>
</div>
<div>
<div class="text-sm font-weight-500 text-gray-900">{{ bases.get(selectedAudit?.base_id)?.title }}</div>
<div class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.base_id }}</div>
</div>
</div>
<template v-else>
{{ selectedAudit.base_id }}
</template>
</div>
<div class="h-1/2 flex items-center gap-2 px-4 py-3">
<div class="cell-header">Sub-type</div>
<div class="text-small leading-[18px] text-gray-600">
{{ auditOperationSubTypeLabels[selectedAudit?.op_sub_type] }}
<div class="w-1/2">
<div class="h-1/2 border-b border-gray-200 flex items-center gap-2 px-4 py-3">
<div class="cell-header">Type</div>
<div class="text-small leading-[18px] text-gray-600 bg-gray-200 px-1 rounded-md">
{{ auditOperationTypeLabels[selectedAudit?.op_type] }}
</div>
</div>
<div class="h-1/2 flex items-center gap-2 px-4 py-3">
<div class="cell-header">Sub-type</div>
<div class="text-small leading-[18px] text-gray-600">
{{ auditOperationSubTypeLabels[selectedAudit?.op_sub_type] }}
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="cell-header">{{ $t('labels.description') }}</div>
<div class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.description }}</div>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="cell-header">{{ $t('labels.description') }}</div>
<div class="text-small leading-[18px] text-gray-600">{{ selectedAudit?.description }}</div>
</div>
</div>
</NcModal>
</NcModal>
</template>
</div>
</template>

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

@ -128,7 +128,9 @@ onMounted(() => {
Audit Logs
</div>
</template>
<WorkspaceAuditLogs :workspace-id="currentWorkspace.id" />
<div class="h-[calc(100vh-120px)]">
<WorkspaceAuditLogs :workspace-id="currentWorkspace.id" />
</div>
</a-tab-pane>
</NcTabs>
</div>

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

@ -84,6 +84,7 @@ const rolePermissions = {
baseRename: true,
baseDuplicate: true,
sourceCreate: true,
baseAuditList: true,
},
},
[ProjectRoles.EDITOR]: {

Loading…
Cancel
Save