integrate properly with audit log search api

This commit is contained in:
Peter Clement 2023-02-21 10:48:55 +00:00
parent 6cc96f39b1
commit dc691bcaf7
6 changed files with 80 additions and 41 deletions

View File

@ -145,7 +145,8 @@
{#if autocomplete} {#if autocomplete}
<Search <Search
value={fetchTerm ? fetchTerm : searchTerm} value={fetchTerm ? fetchTerm : searchTerm}
on:change={event => (fetchTerm = event.detail)} on:change={event =>
fetchTerm ? (fetchTerm = event.detail) : (searchTerm = event.detail)}
{disabled} {disabled}
placeholder="Search" placeholder="Search"
/> />
@ -265,7 +266,6 @@
.popover-content :global(.spectrum-Search) { .popover-content :global(.spectrum-Search) {
margin-top: -1px; margin-top: -1px;
margin-left: -1px; margin-left: -1px;
width: calc(100% + 2px);
} }
.popover-content :global(.spectrum-Search input) { .popover-content :global(.spectrum-Search input) {
height: auto; height: auto;

View File

@ -0,0 +1,5 @@
<script>
export let value
</script>
<div>{value.name}</div>

View File

@ -1,12 +1,11 @@
<script> <script>
import dayjs from "dayjs" import dayjs from "dayjs"
export let row
import relativeTime from "dayjs/plugin/relativeTime" import relativeTime from "dayjs/plugin/relativeTime"
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
export let row
</script> </script>
<div> <div>
{dayjs(row.date).fromNow()} {dayjs(row.timestamp).fromNow()}
</div> </div>

View File

@ -1,6 +1,14 @@
<script> <script>
import { Avatar } from "@budibase/bbui" import { Avatar } from "@budibase/bbui"
export let row export let row
const getInitials = user => {
let initials = ""
initials += user.firstName ? user.firstName[0] : ""
initials += user.lastName ? user.lastName[0] : ""
return initials === "" ? user.email[0] : initials
}
</script> </script>
<Avatar size="M" initials={row.user[0]} /> <Avatar size="M" initials={getInitials(row.user)} />

View File

@ -13,17 +13,17 @@
import { licensing, users, apps, auditLogs } from "stores/portal" import { licensing, users, apps, auditLogs } from "stores/portal"
import LockedFeature from "../../_components/LockedFeature.svelte" import LockedFeature from "../../_components/LockedFeature.svelte"
import { createPaginationStore } from "helpers/pagination" import { createPaginationStore } from "helpers/pagination"
import { setContext } from "svelte" import { onMount, setContext } from "svelte"
import ViewDetailsRenderer from "./_components/ViewDetailsRenderer.svelte" import ViewDetailsRenderer from "./_components/ViewDetailsRenderer.svelte"
import UserRenderer from "./_components/UserRenderer.svelte" import UserRenderer from "./_components/UserRenderer.svelte"
import TimeRenderer from "./_components/TimeRenderer.svelte" import TimeRenderer from "./_components/TimeRenderer.svelte"
import AppColumnRenderer from "./_components/AppColumnRenderer.svelte"
const schema = { const schema = {
name: { width: "1fr" }, date: { width: "0.8fr" },
date: { width: "1.5fr" },
user: { width: "0.5fr" }, user: { width: "0.5fr" },
app: { width: "1fr" }, app: { width: "1fr", fieldName: "name" },
event: { width: "1fr" }, name: { width: "1fr" },
view: { width: "auto", borderLeft: true, displayName: "" }, view: { width: "auto", borderLeft: true, displayName: "" },
} }
@ -40,6 +40,10 @@
column: "date", column: "date",
component: TimeRenderer, component: TimeRenderer,
}, },
{
column: "app",
component: AppColumnRenderer,
},
] ]
let userSearchTerm = "" let userSearchTerm = ""
@ -55,22 +59,15 @@
let sidePanelVisible = false let sidePanelVisible = false
let startDate, endDate let startDate, endDate
let data = [
{
name: "User created",
date: "2023-02-14T10:19:52.021Z",
user: "Peter Clement",
app: "School Admin Panel",
event: "User added",
metadata: {
name: "Peter Clement",
email: "",
},
},
]
$: fetchUsers(userPage, userSearchTerm) $: fetchUsers(userPage, userSearchTerm)
$: fetchLogs(logsPage, logSearchTerm) $: fetchLogs(
logsPage,
logSearchTerm,
startDate,
endDate,
selectedUsers,
selectedApps
)
$: userPage = $userPageInfo.page $: userPage = $userPageInfo.page
$: logsPage = $logsPageInfo.page $: logsPage = $logsPageInfo.page
@ -97,7 +94,14 @@
} }
} }
const fetchLogs = async (logsPage, search) => { const fetchLogs = async (
logsPage,
search,
startDate,
endDate,
selectedUsers,
selectedApps
) => {
if ($logsPageInfo.loading) { if ($logsPageInfo.loading) {
return return
} }
@ -117,7 +121,10 @@
userIds: selectedUsers, userIds: selectedUsers,
appIds: selectedApps, appIds: selectedApps,
}) })
logsPageInfo.fetched($auditLogs.hasNextPage, $auditLogs.nextPage) logsPageInfo.fetched(
$auditLogs.logs.hasNextPage,
$auditLogs.logs.nextPage
)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
notifications.error("Error getting audit logs") notifications.error("Error getting audit logs")
@ -170,6 +177,10 @@
setContext("auditLogs", { setContext("auditLogs", {
viewDetails, viewDetails,
}) })
onMount(async () => {
await auditLogs.getEventDefinitions()
})
</script> </script>
<LockedFeature <LockedFeature
@ -211,16 +222,24 @@
</div> </div>
<div class="select"> <div class="select">
<Multiselect <Multiselect
autocomplete
placeholder="All apps" placeholder="All apps"
label="App" label="App"
getOptionValue={app => app.appId} getOptionValue={app => "app_dev_" + app.appId}
getOptionLabel={app => app.name} getOptionLabel={app => app.name}
options={$apps} options={$apps}
bind:value={selectedApps} bind:value={selectedApps}
/> />
</div> </div>
<div class="select"> <div class="select">
<Multiselect placeholder="All events" label="Event" /> <Multiselect
autocomplete
getOptionValue={event => event[0]}
getOptionLabel={event => event[1]}
options={Object.entries($auditLogs.events)}
placeholder="All events"
label="Event"
/>
</div> </div>
</div> </div>
<div style="padding-bottom: var(--spacing-s)"> <div style="padding-bottom: var(--spacing-s)">
@ -228,14 +247,14 @@
</div> </div>
<div style="max-width: 150px; "> <div style="max-width: 150px; ">
<Search placeholder="Search" value={""} /> <Search placeholder="Search" bind:value={logSearchTerm} />
</div> </div>
</div> </div>
<Layout noPadding> <Layout noPadding>
<Table <Table
{customRenderers} {customRenderers}
on:click={viewDetails} on:click={viewDetails}
{data} data={$auditLogs.logs.data}
allowEditColumns={false} allowEditColumns={false}
allowEditRows={false} allowEditRows={false}
allowSelectRows={false} allowSelectRows={false}
@ -255,7 +274,8 @@
<div class="side-panel-header"> <div class="side-panel-header">
Audit Logs Audit Logs
<Icon <Icon
icon="Close" hoverable
name="Close"
on:click={() => { on:click={() => {
sidePanelVisible = false sidePanelVisible = false
}} }}
@ -263,7 +283,6 @@
</div> </div>
<div style="padding-top: 10px; height: 95%"> <div style="padding-top: 10px; height: 95%">
<CoreTextArea <CoreTextArea
disabled={true}
minHeight={"300px"} minHeight={"300px"}
height={"100%"} height={"100%"}
value={JSON.stringify(selectedLog.metadata, null, 2)} value={JSON.stringify(selectedLog.metadata, null, 2)}

View File

@ -3,23 +3,31 @@ import { API } from "api"
import { licensing } from "stores/portal" import { licensing } from "stores/portal"
export function createAuditLogsStore() { export function createAuditLogsStore() {
const { subscribe, set } = writable({ const { subscribe, update } = writable({
logs: [], events: {},
logs: {},
}) })
async function search(opts = {}) { async function search(opts = {}) {
if (get(licensing).auditLogsEnabled) { if (get(licensing).auditLogsEnabled) {
const paged = await API.searchAuditLogs(opts) const paged = await API.searchAuditLogs(opts)
set({
...paged, update(state => {
...opts, return { ...state, logs: { ...paged, opts } }
}) })
return paged return paged
} }
} }
async function getEventDefinitions() { async function getEventDefinitions() {
return await API.getEventDefinitions() const events = await API.getEventDefinitions()
update(state => {
return { ...state, ...events }
})
console.log(events)
} }
async function downloadLogs(opts = {}) { async function downloadLogs(opts = {}) {