add base audit logs ui

This commit is contained in:
Peter Clement 2023-02-13 09:47:08 +00:00
parent 2bd6ff627a
commit 70ac7b81c3
13 changed files with 249 additions and 7 deletions

View File

@ -14,6 +14,7 @@
export let autocomplete = false export let autocomplete = false
export let sort = false export let sort = false
export let autoWidth = false export let autoWidth = false
export let fetchTerm = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -83,6 +84,7 @@
{options} {options}
isPlaceholder={!value?.length} isPlaceholder={!value?.length}
{autocomplete} {autocomplete}
bind:fetchTerm
{isOptionSelected} {isOptionSelected}
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}

View File

@ -31,7 +31,8 @@
export let autoWidth = false export let autoWidth = false
export let autocomplete = false export let autocomplete = false
export let sort = false export let sort = false
export let fetchTerm = null
$: console.log(fieldText)
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let searchTerm = null let searchTerm = null
@ -71,7 +72,7 @@
} }
const getFilteredOptions = (options, term, getLabel) => { const getFilteredOptions = (options, term, getLabel) => {
if (autocomplete && term) { if (autocomplete && term && !fetchTerm) {
const lowerCaseTerm = term.toLowerCase() const lowerCaseTerm = term.toLowerCase()
return options.filter(option => { return options.filter(option => {
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm) return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
@ -144,8 +145,8 @@
> >
{#if autocomplete} {#if autocomplete}
<Search <Search
value={searchTerm} value={fetchTerm ? fetchTerm : searchTerm}
on:change={event => (searchTerm = event.detail)} on:change={event => (fetchTerm = event.detail)}
{disabled} {disabled}
placeholder="Search" placeholder="Search"
/> />
@ -247,7 +248,7 @@
} }
.popover-content.auto-width .spectrum-Menu-itemLabel { .popover-content.auto-width .spectrum-Menu-itemLabel {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: none;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.popover-content:not(.auto-width) .spectrum-Menu-itemLabel { .popover-content:not(.auto-width) .spectrum-Menu-itemLabel {

View File

@ -15,6 +15,9 @@
export let getOptionValue = option => option export let getOptionValue = option => option
export let sort = false export let sort = false
export let autoWidth = false export let autoWidth = false
export let autocomplete = false
export let fetchTerm = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
value = e.detail value = e.detail
@ -34,6 +37,8 @@
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}
{autoWidth} {autoWidth}
{autocomplete}
bind:fetchTerm
on:change={onChange} on:change={onChange}
on:click on:click
/> />

View File

@ -20,6 +20,7 @@
export let autoWidth = false export let autoWidth = false
export let sort = false export let sort = false
export let tooltip = "" export let tooltip = ""
export let autocomplete = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -51,6 +52,7 @@
{getOptionIcon} {getOptionIcon}
{getOptionColour} {getOptionColour}
{isOptionEnabled} {isOptionEnabled}
{autocomplete}
on:change={onChange} on:change={onChange}
on:click on:click
/> />

View File

@ -4,12 +4,13 @@
import { Content, SideNav, SideNavItem } from "components/portal/page" import { Content, SideNav, SideNavItem } from "components/portal/page"
import { menu } from "stores/portal" import { menu } from "stores/portal"
$: wide = $isActive("./auditLogs")
$: pages = $menu.find(x => x.title === "Account")?.subPages || [] $: pages = $menu.find(x => x.title === "Account")?.subPages || []
$: !pages.length && $goto("../") $: !pages.length && $goto("../")
</script> </script>
<Page> <Page>
<Content narrow> <Content narrow={!wide}>
<div slot="side-nav"> <div slot="side-nav">
<SideNav> <SideNav>
{#each pages as { title, href }} {#each pages as { title, href }}

View File

@ -0,0 +1,6 @@
<script>
import { Avatar } from "@budibase/bbui"
export let row
</script>
<Avatar size="M" initials={"PC"} />

View File

@ -0,0 +1,13 @@
<script>
import { ActionButton } from "@budibase/bbui"
import { getContext } from "svelte"
export let row
const auditLogs = getContext("auditLogs")
const onClick = e => {
e.stopPropagation()
auditLogs.viewDetails(row)
}
</script>
<ActionButton size="S" on:click={onClick}>Edit</ActionButton>

View File

@ -0,0 +1,200 @@
<script>
import {
Layout,
Table,
Select,
Search,
Multiselect,
notifications,
} from "@budibase/bbui"
import { licensing, users, apps } from "stores/portal"
import LockedFeature from "../../_components/LockedFeature.svelte"
import { createPaginationStore } from "helpers/pagination"
import { getContext, setContext } from "svelte"
import Portal from "svelte-portal"
import ViewDetailsRenderer from "./_components/ViewDetailsRenderer.svelte"
import UserRenderer from "./_components/UserRenderer.svelte"
const sidePanel = getContext("side-panel")
const schema = {
name: {},
date: {},
user: { width: "auto" },
app: {},
event: {},
view: { width: "auto", borderLeft: true, displayName: "" },
}
const customRenderers = [
{
column: "view",
component: ViewDetailsRenderer,
},
{
column: "user",
component: UserRenderer,
},
]
let searchTerm = ""
let pageInfo = createPaginationStore()
let prevSearch = undefined
let selectedUsers = []
let selectedLog
let data = [
{
name: "User created",
date: "2021-03-01 12:00:00",
user: "Peter Clement",
app: "School Admin Panel",
event: "User added",
metadata: {
name: "Peter Clement",
email: "",
},
},
]
$: fetchUsers(page, searchTerm)
$: page = $pageInfo.page
$: enrichedList = enrich($users.data || [], selectedUsers)
$: sortedList = sort(enrichedList)
const fetchUsers = async (page, search) => {
if ($pageInfo.loading) {
return
}
// need to remove the page if they've started searching
if (search && !prevSearch) {
pageInfo.reset()
page = undefined
}
prevSearch = search
try {
pageInfo.loading()
await users.search({ page, email: search })
pageInfo.fetched($users.hasNextPage, $users.nextPage)
} catch (error) {
notifications.error("Error getting user list")
}
}
const enrich = (list, selected) => {
return list.map(item => {
return {
...item,
selected: selected.find(x => x === item._id) != null,
}
})
}
const sort = list => {
let sortedList = list.slice()
sortedList?.sort((a, b) => {
if (a.selected === b.selected) {
return a["email"] < b["email"] ? -1 : 1
} else if (a.selected) {
return -1
} else if (b.selected) {
return 1
}
return 0
})
return sortedList
}
const viewDetails = detail => {
selectedLog = detail
sidePanel.open()
}
setContext("auditLogs", {
viewDetails,
})
</script>
<LockedFeature
title={"Audit Logs"}
planType={"Business plan"}
description={"View all events that have occurred in your Budibase installation"}
enabled={$licensing.auditLogsEnabled}
upgradeButtonClick={async () => {
$licensing.goToUpgradePage()
}}
>
<div class="controls">
<div class="search">
<div class="select">
<Select placeholder="All" label="Activity" />
</div>
<div class="select">
<Multiselect
bind:fetchTerm={searchTerm}
placeholder="All users"
label="Users"
autocomplete
bind:value={selectedUsers}
getOptionValue={user => user._id}
getOptionLabel={user => user.email}
options={sortedList}
/>
</div>
<div class="select">
<Multiselect
placeholder="All apps"
label="App"
getOptionValue={app => app.appId}
getOptionLabel={app => app.name}
options={$apps}
/>
</div>
<div class="select">
<Multiselect placeholder="All events" label="Event" />
</div>
</div>
<div style="width: 200px;">
<Search placeholder="Search" value={""} />
</div>
</div>
<Layout noPadding>
<Table
{customRenderers}
on:click={viewDetails}
{data}
allowEditColumns={false}
allowEditRows={false}
allowSelectRows={false}
{schema}
/>
</Layout>
</LockedFeature>
{#if selectedLog}
<Portal target="#side-panel">
<div>hello</div>
</Portal>
{/if}
<style>
.controls {
display: flex;
flex-direction: row;
gap: var(--spacing-l);
align-items: flex-end;
flex-wrap: wrap;
}
.search {
display: flex;
gap: var(--spacing-m);
align-items: flex-start;
flex: 1 1 auto;
max-width: 100%;
}
.select {
flex: 1 1 0;
max-width: 200px;
min-width: 80px;
}
</style>

View File

@ -184,7 +184,7 @@
<Divider /> <Divider />
{#if !$licensing.backupsEnabled} {#if !$licensing.backupsEnabled}
{#if !$auth.accountPortalAccess && !$licensing.groupsEnabled && $admin.cloud} {#if !$auth.accountPortalAccess && $admin.cloud}
<Body>Contact your account holder to upgrade your plan.</Body> <Body>Contact your account holder to upgrade your plan.</Body>
{/if} {/if}
<div class="pro-buttons"> <div class="pro-buttons">

View File

@ -55,6 +55,7 @@
notifications.error(`Error saving variable: ${err.message}`) notifications.error(`Error saving variable: ${err.message}`)
} }
} }
$: console.log($environment.variables)
</script> </script>
<LockedFeature <LockedFeature

View File

@ -64,6 +64,10 @@ export const createLicensingStore = () => {
Constants.Features.ENVIRONMENT_VARIABLES Constants.Features.ENVIRONMENT_VARIABLES
) )
let auditLogsEnabled = license.features.includes(
Constants.Features.AUDIT_LOGS
)
auditLogsEnabled = true
store.update(state => { store.update(state => {
return { return {
...state, ...state,
@ -72,6 +76,7 @@ export const createLicensingStore = () => {
groupsEnabled, groupsEnabled,
backupsEnabled, backupsEnabled,
environmentVariablesEnabled, environmentVariablesEnabled,
auditLogsEnabled,
} }
}) })
}, },

View File

@ -75,6 +75,10 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => {
title: "Usage", title: "Usage",
href: "/builder/portal/account/usage", href: "/builder/portal/account/usage",
}, },
{
title: "Audit Logs",
href: "/builder/portal/account/auditLogs",
},
] ]
if ($admin.cloud && $auth?.user?.accountPortalAccess) { if ($admin.cloud && $auth?.user?.accountPortalAccess) {
accountSubPages.push({ accountSubPages.push({
@ -87,6 +91,7 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => {
href: "/builder/portal/account/upgrade", href: "/builder/portal/account/upgrade",
}) })
} }
// add license check here
if ( if (
$auth?.user?.accountPortalAccess && $auth?.user?.accountPortalAccess &&
$auth.user.account.stripeCustomerId $auth.user.account.stripeCustomerId

View File

@ -115,6 +115,7 @@ export const Features = {
USER_GROUPS: "userGroups", USER_GROUPS: "userGroups",
BACKUPS: "appBackups", BACKUPS: "appBackups",
ENVIRONMENT_VARIABLES: "environmentVariables", ENVIRONMENT_VARIABLES: "environmentVariables",
AUDIT_LOGS: "auditLogs",
} }
// Role IDs // Role IDs