Update groups list and groups details pages to new designs. Multiple fixes and improvements. Standardise divider thickness
This commit is contained in:
parent
feee950c3d
commit
206af376fd
|
@ -80,7 +80,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</h1>
|
</h1>
|
||||||
{#if showDivider}
|
{#if showDivider}
|
||||||
<Divider size="M" />
|
<Divider />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,7 @@
|
||||||
</Body>
|
</Body>
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="query-header">
|
<div class="query-header">
|
||||||
<Heading size="S">Tables</Heading>
|
<Heading size="S">Tables</Heading>
|
||||||
<div class="table-buttons">
|
<div class="table-buttons">
|
||||||
|
@ -192,7 +192,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="S"><i>No tables found.</i></Body>
|
<Body size="S"><i>No tables found.</i></Body>
|
||||||
{/if}
|
{/if}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="query-header">
|
<div class="query-header">
|
||||||
<Heading size="S">Relationships</Heading>
|
<Heading size="S">Relationships</Heading>
|
||||||
<Button primary on:click={() => openRelationshipModal()}>
|
<Button primary on:click={() => openRelationshipModal()}>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<Heading size="S">Headers</Heading>
|
<Heading size="S">Headers</Heading>
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<Heading size="S">Authentication</Heading>
|
<Heading size="S">Authentication</Heading>
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
</Body>
|
</Body>
|
||||||
<RestAuthenticationBuilder bind:configs={datasource.config.authConfigs} />
|
<RestAuthenticationBuilder bind:configs={datasource.config.authConfigs} />
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<Heading size="S">Variables</Heading>
|
<Heading size="S">Variables</Heading>
|
||||||
|
|
|
@ -200,7 +200,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{#if views?.length}
|
{#if views?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">Views</Heading>
|
<Heading size="XS">Views</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -211,7 +211,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if queries?.length}
|
{#if queries?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">Queries</Heading>
|
<Heading size="XS">Queries</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -227,7 +227,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if links?.length}
|
{#if links?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">Relationships</Heading>
|
<Heading size="XS">Relationships</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -238,7 +238,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if fields?.length}
|
{#if fields?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">Fields</Heading>
|
<Heading size="XS">Fields</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -249,7 +249,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if jsonArrays?.length}
|
{#if jsonArrays?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">JSON Arrays</Heading>
|
<Heading size="XS">JSON Arrays</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -260,7 +260,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if showDataProviders && dataProviders?.length}
|
{#if showDataProviders && dataProviders?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">Data Providers</Heading>
|
<Heading size="XS">Data Providers</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,7 +276,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if otherSources?.length}
|
{#if otherSources?.length}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Heading size="XS">Other</Heading>
|
<Heading size="XS">Other</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
</header>
|
</header>
|
||||||
<Body size="M">{integration.description}</Body>
|
<Body size="M">{integration.description}</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="config-header">
|
<div class="config-header">
|
||||||
<Heading size="S">Configuration</Heading>
|
<Heading size="S">Configuration</Heading>
|
||||||
<Button disabled={!changed} cta on:click={saveDatasource}>Save</Button>
|
<Button disabled={!changed} cta on:click={saveDatasource}>Save</Button>
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
{#if datasource.plus}
|
{#if datasource.plus}
|
||||||
<PlusConfigForm bind:datasource save={saveDatasource} />
|
<PlusConfigForm bind:datasource save={saveDatasource} />
|
||||||
{/if}
|
{/if}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="query-header">
|
<div class="query-header">
|
||||||
<Heading size="S">Queries</Heading>
|
<Heading size="S">Queries</Heading>
|
||||||
<div class="query-buttons">
|
<div class="query-buttons">
|
||||||
|
|
|
@ -548,7 +548,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<Layout paddingY="S" gap="S">
|
<Layout paddingY="S" gap="S">
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
{#if !response && Object.keys(schema).length === 0}
|
{#if !response && Object.keys(schema).length === 0}
|
||||||
<Heading size="M">Response</Heading>
|
<Heading size="M">Response</Heading>
|
||||||
<div class="placeholder">
|
<div class="placeholder">
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
|
|
||||||
{#if loaded && $templates?.length}
|
{#if loaded && $templates?.length}
|
||||||
<TemplateDisplay templates={$templates} />
|
<TemplateDisplay templates={$templates} />
|
||||||
|
|
|
@ -10,9 +10,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: wide =
|
$: wide = $page.path.includes("email/:template")
|
||||||
$page.path.includes("email/:template") ||
|
|
||||||
($page.path.includes("groups") && !$page.path.includes(":groupId"))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $auth.isAdmin}
|
{#if $auth.isAdmin}
|
||||||
|
|
|
@ -295,7 +295,7 @@
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
{#if providers.google}
|
{#if providers.google}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">
|
<Heading size="S">
|
||||||
<div class="provider-title">
|
<div class="provider-title">
|
||||||
|
@ -334,7 +334,7 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
{/if}
|
{/if}
|
||||||
{#if providers.oidc}
|
{#if providers.oidc}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">
|
<Heading size="S">
|
||||||
<div class="provider-title">
|
<div class="provider-title">
|
||||||
|
|
|
@ -132,7 +132,7 @@
|
||||||
values below and click activate.
|
values below and click activate.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
{#if smtpConfig}
|
{#if smtpConfig}
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">SMTP</Heading>
|
<Heading size="S">SMTP</Heading>
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">Templates</Heading>
|
<Heading size="S">Templates</Heading>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
|
|
|
@ -5,13 +5,16 @@
|
||||||
Button,
|
Button,
|
||||||
Layout,
|
Layout,
|
||||||
Heading,
|
Heading,
|
||||||
Body,
|
|
||||||
Icon,
|
Icon,
|
||||||
Popover,
|
Popover,
|
||||||
notifications,
|
notifications,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
StatusLight,
|
StatusLight,
|
||||||
|
Divider,
|
||||||
|
ActionMenu,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import UserGroupPicker from "components/settings/UserGroupPicker.svelte"
|
import UserGroupPicker from "components/settings/UserGroupPicker.svelte"
|
||||||
import { createPaginationStore } from "helpers/pagination"
|
import { createPaginationStore } from "helpers/pagination"
|
||||||
|
@ -19,6 +22,9 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
||||||
|
import GroupIcon from "./_components/GroupIcon.svelte"
|
||||||
|
|
||||||
export let groupId
|
export let groupId
|
||||||
|
|
||||||
|
@ -29,10 +35,16 @@
|
||||||
let prevSearch = undefined
|
let prevSearch = undefined
|
||||||
let pageInfo = createPaginationStore()
|
let pageInfo = createPaginationStore()
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
let editModal
|
||||||
|
let deleteModal
|
||||||
|
|
||||||
$: page = $pageInfo.page
|
$: page = $pageInfo.page
|
||||||
$: fetchUsers(page, searchTerm)
|
$: fetchUsers(page, searchTerm)
|
||||||
$: group = $groups.find(x => x._id === groupId)
|
$: group = $groups.find(x => x._id === groupId)
|
||||||
|
$: filtered =
|
||||||
|
$users.data?.filter(x => !group?.users.map(y => y._id).includes(x._id)) ||
|
||||||
|
[]
|
||||||
|
$: groupApps = $apps.filter(x => group?.apps.includes(x.appId))
|
||||||
|
|
||||||
async function addAll() {
|
async function addAll() {
|
||||||
selectedUsers = [...selectedUsers, ...filtered.map(u => u._id)]
|
selectedUsers = [...selectedUsers, ...filtered.map(u => u._id)]
|
||||||
|
@ -88,11 +100,7 @@
|
||||||
userGroups,
|
userGroups,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$: filtered =
|
|
||||||
$users.data?.filter(x => !group?.users.map(y => y._id).includes(x._id)) ||
|
|
||||||
[]
|
|
||||||
|
|
||||||
$: groupApps = $apps.filter(x => group.apps.includes(x.appId))
|
|
||||||
async function removeUser(id) {
|
async function removeUser(id) {
|
||||||
let newUsers = group.users.filter(user => user._id !== id)
|
let newUsers = group.users.filter(user => user._id !== id)
|
||||||
group.users = newUsers
|
group.users = newUsers
|
||||||
|
@ -131,6 +139,25 @@
|
||||||
return role?.name || "Custom role"
|
return role?.name || "Custom role"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteGroup() {
|
||||||
|
try {
|
||||||
|
await groups.actions.delete(group)
|
||||||
|
notifications.success("User group deleted successfully")
|
||||||
|
$goto("./")
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
notifications.error(`Failed to delete user group`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveGroup(group) {
|
||||||
|
try {
|
||||||
|
await groups.actions.save(group)
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error(`Failed to save user group`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([groups.actions.init(), apps.load(), roles.fetch()])
|
await Promise.all([groups.actions.init(), apps.load(), roles.fetch()])
|
||||||
|
@ -142,119 +169,132 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<Layout noPadding>
|
<Layout noPadding gap="XL">
|
||||||
<div>
|
<div>
|
||||||
<ActionButton
|
<ActionButton on:click={() => $goto("../groups")} icon="ArrowLeft">
|
||||||
on:click={() => $goto("../groups")}
|
|
||||||
size="S"
|
|
||||||
icon="ArrowLeft"
|
|
||||||
>
|
|
||||||
Back
|
Back
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="header">
|
|
||||||
<div class="title">
|
<Layout noPadding gap="M">
|
||||||
<div style="background: {group?.color};" class="circle">
|
<div class="header">
|
||||||
<div>
|
<div class="title">
|
||||||
<Icon size="M" name={group?.icon} />
|
<GroupIcon {group} />
|
||||||
|
<div class="text-padding">
|
||||||
|
<Heading>{group?.name}</Heading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-padding">
|
<div>
|
||||||
<Heading>{group?.name}</Heading>
|
<ActionMenu align="right">
|
||||||
|
<span slot="control">
|
||||||
|
<Icon hoverable name="More" />
|
||||||
|
</span>
|
||||||
|
<MenuItem icon="Refresh" on:click={() => editModal.show()}>
|
||||||
|
Edit
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="Delete" on:click={() => deleteModal.show()}>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div bind:this={popoverAnchor}>
|
|
||||||
<Button on:click={popover.show()} icon="UserAdd" cta>Add user</Button>
|
|
||||||
</div>
|
|
||||||
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
|
||||||
<UserGroupPicker
|
|
||||||
key={"email"}
|
|
||||||
title={"User"}
|
|
||||||
bind:searchTerm
|
|
||||||
bind:selected={selectedUsers}
|
|
||||||
bind:filtered
|
|
||||||
{addAll}
|
|
||||||
select={selectUser}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<List>
|
<Divider />
|
||||||
{#if group?.users.length}
|
|
||||||
{#each group.users as user}
|
|
||||||
<ListItem title={user?.email} avatar
|
|
||||||
><Icon
|
|
||||||
on:click={() => removeUser(user?._id)}
|
|
||||||
hoverable
|
|
||||||
size="L"
|
|
||||||
name="Close"
|
|
||||||
/></ListItem
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<ListItem icon="UserGroup" title="You have no users in this team" />
|
|
||||||
{/if}
|
|
||||||
</List>
|
|
||||||
<div
|
|
||||||
style="flex-direction: column; margin-top: var(--spacing-m)"
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
<Heading weight="light" size="XS">Apps</Heading>
|
|
||||||
<div style="margin-top: var(--spacing-xs)">
|
|
||||||
<Body size="S"
|
|
||||||
>Manage apps that this User group has been assigned to</Body
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<List>
|
<Layout noPadding gap="S">
|
||||||
{#if groupApps.length}
|
<div class="header">
|
||||||
{#each groupApps as app}
|
<Heading size="S">Users</Heading>
|
||||||
<ListItem
|
<div bind:this={popoverAnchor}>
|
||||||
title={app.name}
|
<Button on:click={popover.show()} icon="UserAdd" cta>
|
||||||
icon={app?.icon?.name || "Apps"}
|
Add user
|
||||||
iconBackground={app?.icon?.color || ""}
|
</Button>
|
||||||
>
|
</div>
|
||||||
<div class="title ">
|
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
||||||
<StatusLight
|
<UserGroupPicker
|
||||||
square
|
key={"email"}
|
||||||
color={RoleUtils.getRoleColour(group.roles[`app_${app.appId}`])}
|
title={"User"}
|
||||||
>
|
bind:searchTerm
|
||||||
{getRoleLabel(app.appId)}
|
bind:selected={selectedUsers}
|
||||||
</StatusLight>
|
bind:filtered
|
||||||
</div>
|
{addAll}
|
||||||
</ListItem>
|
select={selectUser}
|
||||||
{/each}
|
/>
|
||||||
{:else}
|
</Popover>
|
||||||
<ListItem icon="UserGroup" title="No apps" />
|
</div>
|
||||||
{/if}
|
<List>
|
||||||
</List>
|
{#if group?.users.length}
|
||||||
|
{#each group.users as user}
|
||||||
|
<ListItem title={user?.email} avatar>
|
||||||
|
<Icon
|
||||||
|
on:click={() => removeUser(user?._id)}
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
name="Close"
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<ListItem
|
||||||
|
icon="UserGroup"
|
||||||
|
title="You have no users in this user group"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</List>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<Heading size="S">Apps</Heading>
|
||||||
|
<List>
|
||||||
|
{#if groupApps.length}
|
||||||
|
{#each groupApps as app}
|
||||||
|
<ListItem
|
||||||
|
title={app.name}
|
||||||
|
icon={app?.icon?.name || "Apps"}
|
||||||
|
iconBackground={app?.icon?.color || ""}
|
||||||
|
>
|
||||||
|
<div class="title ">
|
||||||
|
<StatusLight
|
||||||
|
square
|
||||||
|
color={RoleUtils.getRoleColour(
|
||||||
|
group.roles[`app_${app.appId}`]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getRoleLabel(app.appId)}
|
||||||
|
</StatusLight>
|
||||||
|
</div>
|
||||||
|
</ListItem>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<ListItem icon="UserGroup" title="This group has access to no apps" />
|
||||||
|
{/if}
|
||||||
|
</List>
|
||||||
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<Modal bind:this={editModal}>
|
||||||
.text-padding {
|
<CreateEditGroupModal {group} {saveGroup} />
|
||||||
margin-left: var(--spacing-l);
|
</Modal>
|
||||||
}
|
<ConfirmDialog
|
||||||
|
bind:this={deleteModal}
|
||||||
|
title="Delete user group"
|
||||||
|
okText="Delete user group"
|
||||||
|
onOk={deleteGroup}
|
||||||
|
>
|
||||||
|
Are you sure you wish to delete <b>{group?.name}?</b>
|
||||||
|
</ConfirmDialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
justify-content: flex-start;
|
||||||
.circle {
|
align-items: center;
|
||||||
border-radius: 50%;
|
gap: var(--spacing-m);
|
||||||
height: 30px;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 1.2em;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle > div {
|
|
||||||
padding: calc(1.5 * var(--spacing-xs)) var(--spacing-xs);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script>
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let group
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="background: {group?.color};" class="circle">
|
||||||
|
<Icon size="S" name={group?.icon} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
color: white;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,20 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { Avatar } from "@budibase/bbui"
|
import GroupIcon from "./GroupIcon.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
export let row
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="align">
|
<div class="align">
|
||||||
{#if value}
|
{#if value}
|
||||||
<div class="spacing">
|
<GroupIcon group={row} />
|
||||||
<Avatar
|
|
||||||
size="L"
|
|
||||||
initials={value
|
|
||||||
.split(" ")
|
|
||||||
.map(x => x[0])
|
|
||||||
.join("")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{value}
|
{value}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text">-</div>
|
<div class="text">-</div>
|
||||||
|
@ -26,12 +19,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacing {
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
|
@ -21,11 +21,6 @@
|
||||||
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="name" style="display: flex; margin-left: var(--spacing-xl)">
|
<div class="name" style="display: flex; margin-left: var(--spacing-xl)">
|
||||||
<div style="background: {group.color};" class="circle">
|
|
||||||
<div>
|
|
||||||
<Icon size="M" name={group.icon} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="name" data-cy="app-name-link">
|
<div class="name" data-cy="app-name-link">
|
||||||
<Body size="S">{group.name}</Body>
|
<Body size="S">{group.name}</Body>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,31 +45,12 @@
|
||||||
: "s"}
|
: "s"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div class="group-row-actions">
|
|
||||||
<div>
|
|
||||||
<Button on:click={() => $goto(`./${group._id}`)} size="S" cta
|
|
||||||
>Manage</Button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ActionMenu align="right">
|
|
||||||
<span slot="control">
|
|
||||||
<Icon hoverable name="More" />
|
|
||||||
</span>
|
|
||||||
<MenuItem on:click={() => deleteGroup(group)} icon="Delete"
|
|
||||||
>Delete</MenuItem
|
|
||||||
>
|
|
||||||
<MenuItem on:click={() => editGroup(group)} icon="Edit">Edit</MenuItem>
|
|
||||||
</ActionMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<!-- <MenuItem on:click={() => deleteGroup(group)} icon="Delete"-->
|
||||||
<CreateEditGroupModal {group} {saveGroup} />
|
<!-- >Delete</MenuItem-->
|
||||||
</Modal>
|
<!-- >-->
|
||||||
|
|
||||||
|
<!-- <MenuItem on:click={() => editGroup(group)} icon="Edit">Edit</MenuItem>-->
|
||||||
<style>
|
<style>
|
||||||
.group-row-actions {
|
.group-row-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -88,23 +64,7 @@
|
||||||
grid-template-columns: 75px 75px;
|
grid-template-columns: 75px 75px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.circle {
|
|
||||||
border-radius: 50%;
|
|
||||||
height: 30px;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 1.2em;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableElement {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle > div {
|
|
||||||
padding: calc(1.5 * var(--spacing-xs)) var(--spacing-xs);
|
|
||||||
}
|
|
||||||
.name {
|
.name {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script>
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="align">
|
||||||
|
<div class="spacing">
|
||||||
|
<Icon name="User" />
|
||||||
|
</div>
|
||||||
|
{parseInt(value?.length) || 0}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.align {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacing {
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,16 +4,23 @@
|
||||||
Heading,
|
Heading,
|
||||||
Body,
|
Body,
|
||||||
Button,
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
Modal,
|
Modal,
|
||||||
Tag,
|
Tag,
|
||||||
Tags,
|
Tags,
|
||||||
|
Table,
|
||||||
|
Divider,
|
||||||
|
Search,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { groups, auth } from "stores/portal"
|
import { groups, auth } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
||||||
import UserGroupsRow from "./_components/UserGroupsRow.svelte"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import AppsTableRenderer from "../users/_components/AppsTableRenderer.svelte"
|
||||||
|
import UsersTableRenderer from "./_components/UsersTableRenderer.svelte"
|
||||||
|
import GroupNameTableRenderer from "./_components/GroupNameTableRenderer.svelte"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
const DefaultGroup = {
|
const DefaultGroup = {
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -23,22 +30,39 @@
|
||||||
apps: [],
|
apps: [],
|
||||||
roles: {},
|
roles: {},
|
||||||
}
|
}
|
||||||
let modal
|
|
||||||
let group = cloneDeep(DefaultGroup)
|
|
||||||
|
|
||||||
async function deleteGroup(group) {
|
let modal
|
||||||
try {
|
let searchString
|
||||||
groups.actions.delete(group)
|
let group = cloneDeep(DefaultGroup)
|
||||||
} catch (error) {
|
let customRenderers = [
|
||||||
notifications.error(`Failed to delete group`)
|
{ column: "name", component: GroupNameTableRenderer },
|
||||||
|
{ column: "users", component: UsersTableRenderer },
|
||||||
|
{ column: "apps", component: AppsTableRenderer },
|
||||||
|
]
|
||||||
|
|
||||||
|
$: schema = {
|
||||||
|
name: {},
|
||||||
|
users: { sortable: false },
|
||||||
|
apps: { sortable: false },
|
||||||
|
}
|
||||||
|
$: filteredGroups = filterGroups($groups, searchString)
|
||||||
|
|
||||||
|
const filterGroups = (groups, searchString) => {
|
||||||
|
if (!searchString) {
|
||||||
|
return groups
|
||||||
}
|
}
|
||||||
|
searchString = searchString.toLocaleLowerCase()
|
||||||
|
return groups?.filter(group => {
|
||||||
|
return group.name?.toLowerCase().includes(searchString)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveGroup(group) {
|
async function saveGroup(group) {
|
||||||
try {
|
try {
|
||||||
await groups.actions.save(group)
|
await groups.actions.save(group)
|
||||||
|
notifications.success(`User group created successfully`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error(`Failed to save group`)
|
notifications.error(`Failed to save user group`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,58 +77,63 @@
|
||||||
await groups.actions.init()
|
await groups.actions.init()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error getting User groups")
|
notifications.error("Error getting user groups")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding gap="M">
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<div style="display: flex;">
|
<Heading size="M">User groups</Heading>
|
||||||
<Heading size="M">User groups</Heading>
|
{#if !$auth.groupsEnabled}
|
||||||
{#if !$auth.groupsEnabled}
|
<Tags>
|
||||||
<Tags>
|
<div class="tags">
|
||||||
<div class="tags">
|
<div class="tag">
|
||||||
<div class="tag">
|
<Tag icon="LockClosed">Pro plan</Tag>
|
||||||
<Tag icon="LockClosed">Pro plan</Tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Tags>
|
</div>
|
||||||
{/if}
|
</Tags>
|
||||||
</div>
|
{/if}
|
||||||
<Body>Easily assign and manage your users access with User Groups</Body>
|
<Body>Easily assign and manage your users access with User Groups</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<div class="align-buttons">
|
<Divider />
|
||||||
<Button
|
<div class="controls">
|
||||||
newStyles
|
<ButtonGroup>
|
||||||
icon={$auth.groupsEnabled ? "UserGroup" : ""}
|
|
||||||
cta={$auth.groupsEnabled}
|
|
||||||
on:click={$auth.groupsEnabled
|
|
||||||
? showCreateGroupModal
|
|
||||||
: window.open("https://budibase.com/pricing/", "_blank")}
|
|
||||||
>
|
|
||||||
{$auth.groupsEnabled ? "Create user group" : "Upgrade Account"}
|
|
||||||
</Button>
|
|
||||||
{#if !$auth.groupsEnabled}
|
|
||||||
<Button
|
<Button
|
||||||
newStyles
|
newStyles
|
||||||
secondary
|
icon={$auth.groupsEnabled ? "UserGroup" : ""}
|
||||||
on:click={() => {
|
cta={$auth.groupsEnabled}
|
||||||
window.open("https://budibase.com/pricing/", "_blank")
|
on:click={$auth.groupsEnabled
|
||||||
}}>View Plans</Button
|
? showCreateGroupModal
|
||||||
|
: window.open("https://budibase.com/pricing/", "_blank")}
|
||||||
>
|
>
|
||||||
{/if}
|
{$auth.groupsEnabled ? "Create user group" : "Upgrade Account"}
|
||||||
</div>
|
</Button>
|
||||||
|
{#if !$auth.groupsEnabled}
|
||||||
{#if $auth.groupsEnabled && $groups.length}
|
<Button
|
||||||
<div class="groupTable">
|
newStyles
|
||||||
{#each $groups as group}
|
secondary
|
||||||
<div>
|
on:click={() => {
|
||||||
<UserGroupsRow {saveGroup} {deleteGroup} {group} />
|
window.open("https://budibase.com/pricing/", "_blank")
|
||||||
</div>
|
}}
|
||||||
{/each}
|
>
|
||||||
|
View Plans
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</ButtonGroup>
|
||||||
|
<div class="controls-right">
|
||||||
|
<Search bind:value={searchString} placeholder="Search" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
<Table
|
||||||
|
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
||||||
|
{schema}
|
||||||
|
data={filteredGroups}
|
||||||
|
allowEditColumns={false}
|
||||||
|
allowEditRows={false}
|
||||||
|
showHeaderBorder={false}
|
||||||
|
{customRenderers}
|
||||||
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
@ -112,37 +141,24 @@
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.align-buttons {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: var(--spacing-xl);
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.controls-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.controls-right :global(.spectrum-Search) {
|
||||||
|
width: 200px;
|
||||||
}
|
}
|
||||||
.tag {
|
.tag {
|
||||||
margin-top: var(--spacing-xs);
|
margin-top: var(--spacing-xs);
|
||||||
margin-left: var(--spacing-m);
|
margin-left: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.groupTable {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid var(--spectrum-alias-border-color-mid);
|
|
||||||
border-left: 1px solid var(--spectrum-alias-border-color-mid);
|
|
||||||
background: var(--spectrum-global-color-gray-50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.groupTable :global(> div) {
|
|
||||||
background: var(--bg-color);
|
|
||||||
|
|
||||||
height: 55px;
|
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
grid-template-columns: 2fr 2fr 2fr auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
padding: 0 var(--spacing-s);
|
|
||||||
border-top: 1px solid var(--spectrum-alias-border-color-mid);
|
|
||||||
border-right: 1px solid var(--spectrum-alias-border-color-mid);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -247,7 +247,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<Heading size="S">Details</Heading>
|
<Heading size="S">Details</Heading>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacing {
|
.spacing {
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,9 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity {
|
.opacity {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacing {
|
.spacing {
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,7 @@
|
||||||
<Heading>Users</Heading>
|
<Heading>Users</Heading>
|
||||||
<Body>Add users and control who gets access to your published apps</Body>
|
<Body>Add users and control who gets access to your published apps</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
analytics.
|
analytics.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">Information</Heading>
|
<Heading size="S">Information</Heading>
|
||||||
<Body size="S">Here you can update your logo and organization name.</Body>
|
<Body size="S">Here you can update your logo and organization name.</Body>
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if !$admin.cloud}
|
{#if !$admin.cloud}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">Platform</Heading>
|
<Heading size="S">Platform</Heading>
|
||||||
<Body size="S">Here you can set up general platform settings.</Body>
|
<Body size="S">Here you can set up general platform settings.</Body>
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !$admin.cloud}
|
{#if !$admin.cloud}
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">Analytics</Heading>
|
<Heading size="S">Analytics</Heading>
|
||||||
<Body size="S">Choose whether to opt-in or opt-out of analytics.</Body>
|
<Body size="S">Choose whether to opt-in or opt-out of analytics.</Body>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<Heading size="M">Theming</Heading>
|
<Heading size="M">Theming</Heading>
|
||||||
<Body>Customize how Budibase looks and feels.</Body>
|
<Body>Customize how Budibase looks and feels.</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label size="L">Builder theme</Label>
|
<Label size="L">Builder theme</Label>
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
latest features, security updates and much more.
|
latest features, security updates and much more.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
{#if version}
|
{#if version}
|
||||||
<div>
|
<div>
|
||||||
<Label size="L">Current version</Label>
|
<Label size="L">Current version</Label>
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">Activate</Heading>
|
<Heading size="S">Activate</Heading>
|
||||||
<Body size="S">Enter your license key below to activate your plan</Body>
|
<Body size="S">Enter your license key below to activate your plan</Body>
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
<Layout gap="L" noPadding>
|
<Layout gap="L" noPadding>
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
<Heading size="S">Plan</Heading>
|
<Heading size="S">Plan</Heading>
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="S">
|
<Layout gap="S">
|
||||||
<Divider size="S" />
|
<Divider />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
<Layout gap="XS">
|
<Layout gap="XS">
|
||||||
|
|
Loading…
Reference in New Issue