Browse Source

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

pull/8836/head
Ramesh Mane 3 months ago
parent
commit
2a0b44f12a
  1. 5
      packages/nc-gui/components/dlg/ProjectAudit.vue
  2. 110
      packages/nc-gui/components/workspace/AuditLogs.vue
  3. 2
      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>

110
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,37 +696,43 @@ 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
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="nc-audit-logs-table table h-full relative">
<div class="thead sticky top-0"> <div class="thead sticky top-0">
<div class="tr"> <div class="tr">
<div class="th cell-user !hover:bg-gray-100" @click="updateOrderBy('user')"> <div class="th cell-user !hover:bg-gray-100" @click="updateOrderBy('user')">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex-1">User</div> <div>User</div>
<NcButton type="text" size="xs" class="!p-0">
<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 class="flex-1">Time stamp</div> <div>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>
@ -714,15 +743,6 @@ onMounted(async () => {
</div> </div>
</div> </div>
<div class="tbody"> <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>
<template v-if="audits?.length"> <template v-if="audits?.length">
<template v-for="(audit, i) of audits" :key="i"> <template v-for="(audit, i) of audits" :key="i">
<div <div
@ -808,6 +828,15 @@ onMounted(async () => {
</div> </div>
</div> </div>
</div> </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
v-if="totalRows" v-if="totalRows"
class="flex flex-row justify-center items-center bg-gray-50 min-h-10" class="flex flex-row justify-center items-center bg-gray-50 min-h-10"
@ -824,8 +853,8 @@ onMounted(async () => {
:total="+totalRows" :total="+totalRows"
show-size-changer show-size-changer
:use-stored-page-size="false" :use-stored-page-size="false"
@update:current="loadAudits()" @update:current="loadAudits(undefined, undefined, false)"
@update:page-size="loadAudits(currentPage, $event)" @update:page-size="loadAudits(currentPage, $event, false)"
/> />
</template> </template>
<div class="text-gray-500 text-xs">{{ totalRows }} {{ totalRows === 1 ? 'record' : 'records' }}</div> <div class="text-gray-500 text-xs">{{ totalRows }} {{ totalRows === 1 ? 'record' : 'records' }}</div>
@ -921,6 +950,7 @@ onMounted(async () => {
</div> </div>
</div> </div>
</NcModal> </NcModal>
</template>
</div> </div>
</template> </template>

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

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