add base audit logs ui
This commit is contained in:
parent
2bd6ff627a
commit
70ac7b81c3
|
@ -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}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script>
|
||||||
|
import { Avatar } from "@budibase/bbui"
|
||||||
|
export let row
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Avatar size="M" initials={"PC"} />
|
|
@ -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>
|
|
@ -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>
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue