Browse Source

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

pull/8836/head
Ramesh Mane 5 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> <script lang="ts" setup>
const props = defineProps<{ const props = defineProps<{
workspaceId?: string
baseId: string baseId: string
sourceId: string sourceId: string
modelValue: boolean modelValue: boolean
@ -9,7 +10,7 @@ const emit = defineEmits(['update:modelValue'])
const isOpen = useVModel(props, 'modelValue', emit) const isOpen = useVModel(props, 'modelValue', emit)
const activeSourceId = computed(() => props.sourceId) const { workspaceId, sourceId } = toRefs(props)
const { openedProject: base } = storeToRefs(useBases()) const { openedProject: base } = storeToRefs(useBases())
@ -47,7 +48,7 @@ onMounted(async () => {
<template> <template>
<GeneralModal v-model:visible="isOpen" size="xl" class="!w-[70rem] !top-[5vh]"> <GeneralModal v-model:visible="isOpen" size="xl" class="!w-[70rem] !top-[5vh]">
<div class="p-6 h-full"> <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> </div>
</GeneralModal> </GeneralModal>
</template> </template>

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

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

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

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

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

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

Loading…
Cancel
Save