Browse Source

feat(nc-gui): add new audit logs ui for oss

pull/8836/head
Ramesh Mane 3 months ago
parent
commit
dfe16c5b34
  1. 45
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  2. 36
      packages/nc-gui/components/workspace/AuditLogs.vue
  3. 1
      packages/nc-gui/lib/acl.ts
  4. 14
      packages/nc-gui/pages/account/index.vue
  5. 3
      packages/nc-gui/pages/account/index/[page].vue
  6. 67
      packages/nc-gui/store/workspace.ts
  7. 2
      packages/nocodb/src/schema/swagger.json

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

@ -29,6 +29,8 @@ const { isMobileMode } = useGlobal()
const { api } = useApi()
const { auditLogsQuery } = storeToRefs(useWorkspace())
const { createProject: _createProject, updateProject, getProjectMetaInfo, loadProject } = basesStore
const { bases } = storeToRefs(basesStore)
@ -448,6 +450,38 @@ const onTableIdCopy = async () => {
const getSource = (sourceId: string) => {
return base.value.sources?.find((s) => s.id === sourceId)
}
async function openAudit(source: SourceType) {
$e('c:project:audit')
auditLogsQuery.value = {
...auditLogsQuery.value,
user: undefined,
dateRange: undefined,
dateRangeLabel: undefined,
startDate: undefined,
endData: undefined,
orderBy: {
created_at: 'desc',
user: undefined,
},
}
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgProjectAudit'), {
'modelValue': isOpen,
'sourceId': source!.id,
'onUpdate:modelValue': () => closeDialog(),
'baseId': base.value!.id,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
</script>
<template>
@ -581,6 +615,17 @@ const getSource = (sourceId: string) => {
</div>
</NcMenuItem>
<!-- Audit -->
<NcMenuItem
v-if="isUIAllowed('baseAuditList') && base?.sources?.[0]?.enabled"
key="audit"
data-testid="nc-sidebar-base-audit"
@click="openAudit(base?.sources?.[0])"
>
<GeneralIcon icon="audit" class="group-hover:text-black" />
{{ $t('title.audit') }}
</NcMenuItem>
<!-- Swagger: Rest APIs -->
<NcMenuItem
v-if="isUIAllowed('apiDocs')"

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

@ -1,7 +1,7 @@
<script setup lang="ts">
import { Empty } from 'ant-design-vue'
import type { VNodeRef } from '@vue/runtime-core'
import type { AuditType, WorkspaceUserType } from 'nocodb-sdk'
import type { AuditType, UserType, WorkspaceUserType } from 'nocodb-sdk'
import { AuditOperationTypes, auditOperationSubTypeLabels, auditOperationTypeLabels, timeAgo } from 'nocodb-sdk'
import dayjs from 'dayjs'
import { AuditLogsDateRange } from '~/lib/enums'
@ -18,6 +18,8 @@ const allowedAuditOperationTypes = [AuditOperationTypes.DATA, AuditOperationType
const { isUIAllowed } = useRoles()
const { $api } = useNuxtApp()
const workspaceStore = useWorkspace()
const { loadAudits: _loadAudits } = workspaceStore
@ -33,17 +35,17 @@ const {
const basesStore = useBases()
const { getBaseUsers } = basesStore
const { getBaseUsers, loadProjects } = basesStore
const { bases, basesList } = storeToRefs(basesStore)
const baseCollaborators = ref<User[]>([])
const localCollaborators = ref<User[] | UserType[]>([])
const auditCollaborators = computed(() => {
return (auditLogsQuery.value.baseId ? baseCollaborators.value : collaborators.value) || []
return (auditLogsQuery.value.baseId || !isEeUI ? localCollaborators.value : collaborators.value) || []
})
const collaboratorsMap = computed<Map<string, (WorkspaceUserType & { id: string }) | User>>(() => {
const collaboratorsMap = computed<Map<string, (WorkspaceUserType & { id: string }) | User | UserType>>(() => {
const map = new Map<string, WorkspaceUserType & { id: string }>()
auditCollaborators.value?.forEach((coll) => {
@ -118,7 +120,10 @@ const dateRangeOptions = computed(() => {
async function loadAudits(page = currentPage.value, limit = currentLimit.value, updateCurrentPage = true) {
try {
if ((isUIAllowed('workspaceAuditList') && !props.workspaceId) || (!isUIAllowed('workspaceAuditList') && !props.baseId)) {
if (
(isEeUI && isUIAllowed('workspaceAuditList') && !props.workspaceId) ||
(!isUIAllowed('workspaceAuditList') && !props.baseId)
) {
return
}
@ -142,7 +147,19 @@ const loadCollaborators = async () => {
baseId: auditLogsQuery.value.baseId,
})
baseCollaborators.value = users
localCollaborators.value = users
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const loadOrgUsers = async () => {
try {
const response: any = await $api.orgUsers.list()
if (!response?.list) return
localCollaborators.value = response.list as UserType[]
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -285,6 +302,9 @@ watch(
onMounted(async () => {
if (props.baseId) {
auditLogsQuery.value.baseId = props.baseId
} else {
await loadProjects()
await loadOrgUsers()
}
if (props.sourceId) {
@ -298,7 +318,7 @@ onMounted(async () => {
</script>
<template>
<div class="h-full flex flex-col w-full" :class="{ 'pt-6 gap-6': !baseId, 'gap-4': baseId }">
<div class="h-full flex flex-col" :class="{ 'gap-6': !baseId, 'pt-6': !baseId && isEeUI, '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" :class="{ 'gap-6': !baseId, 'gap-4': baseId }">

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

@ -39,6 +39,7 @@ const rolePermissions = {
viewCreateOrEdit: true,
baseReorder: true,
orgAdminPanel: true,
workspaceAuditList: true,
},
},
[OrgUserRoles.VIEWER]: {

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

@ -84,6 +84,20 @@ const logout = async () => {
<div class="select-none">API {{ $t('title.tokens') }}</div>
</div>
</NcMenuItem>
<NcMenuItem
key="audit"
class="item"
:class="{
active: $route.params.page === 'audit',
}"
@click="navigateTo('/account/audit')"
>
<div class="flex items-center space-x-2">
<component :is="iconMap.audit" class="opacity-80"/>
<div class="select-none">Audit Logs</div>
</div>
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('superAdminAppStore') && !isEeUI"
key="apps"

3
packages/nc-gui/pages/account/index/[page].vue

@ -5,6 +5,9 @@ const { appInfo } = useGlobal()
<template>
<div>
<AccountToken v-if="$route.params.page === 'tokens'" />
<div v-else-if="$route.params.page === 'audit'" class="h-[calc(100vh_-_4rem)] w-full">
<WorkspaceAuditLogs />
</div>
<AccountProfile v-else-if="$route.params.page === 'profile'" />
<AccountAppStore v-else-if="$route.params.page === 'apps' && !appInfo.isCloud" />
<span v-else></span>

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

@ -1,4 +1,4 @@
import type { BaseType } from 'nocodb-sdk'
import type { AuditType, BaseType } from 'nocodb-sdk'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { message } from 'ant-design-vue'
import { isString } from '@vue/shared'
@ -6,11 +6,9 @@ import type { AuditLogsQuery } from '~/lib/types'
const defaultAuditLogsQuery = {
type: undefined,
subType: undefined,
base: undefined,
user: undefined,
search: undefined,
baseId: undefined,
sourceId: undefined,
user: undefined,
startDate: undefined,
endData: undefined,
dateRangeLabel: undefined,
@ -23,6 +21,8 @@ const defaultAuditLogsQuery = {
export const useWorkspace = defineStore('workspaceStore', () => {
const basesStore = useBases()
const { isUIAllowed } = useRoles()
const collaborators = ref<any[] | null>()
const router = useRouter()
@ -51,15 +51,6 @@ export const useWorkspace = defineStore('workspaceStore', () => {
const isInvitingCollaborators = ref(false)
const workspaceUserCount = ref<number | undefined>(undefined)
const auditLogsQuery = ref<{
type?: string
subType?: string
base?: string
user?: string
search?: string
sourceId?: string
}>(defaultAuditLogsQuery)
const activePage = computed<'workspace' | 'recent' | 'shared' | 'starred'>(
() => (route.value.query.page as 'workspace' | 'recent' | 'shared' | 'starred') ?? 'recent',
)
@ -237,6 +228,49 @@ export const useWorkspace = defineStore('workspaceStore', () => {
}
}
const auditLogsQuery = ref<AuditLogsQuery>(defaultAuditLogsQuery)
const audits = ref<null | Array<AuditType>>(null)
const auditTotalRows = ref(0)
const auditCurrentPage = ref(1)
const auditCurrentLimit = ref(25)
const loadAudits = async (
workspaceId?: string,
page: number = auditCurrentPage.value,
limit: number = auditCurrentLimit.value,
) => {
try {
if (limit * (page - 1) > auditTotalRows.value) {
auditCurrentPage.value = 1
page = 1
}
const { list, pageInfo } = isUIAllowed('workspaceAuditList')
? await $api.utils.projectAuditList({
offset: limit * (page - 1),
limit,
...auditLogsQuery.value,
})
: await $api.base.auditList(auditLogsQuery.value.baseId, {
offset: limit * (page - 1),
limit,
...auditLogsQuery.value,
})
audits.value = list
auditTotalRows.value = pageInfo.totalRows ?? 0
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
audits.value = []
auditTotalRows.value = 0
auditCurrentPage.value = 1
}
}
function setLoadingState(isLoading = false) {
isWorkspaceLoading.value = isLoading
}
@ -283,6 +317,11 @@ export const useWorkspace = defineStore('workspaceStore', () => {
workspaceRole,
moveToOrg,
auditLogsQuery,
audits,
auditTotalRows,
auditCurrentPage,
auditCurrentLimit,
loadAudits,
}
})

2
packages/nocodb/src/schema/swagger.json

@ -14217,7 +14217,7 @@
"Utils"
]
}
}
},
"/api/v1/db/meta/projects/{baseId}/audits": {
"parameters": [
{

Loading…
Cancel
Save