|
|
@ -8,11 +8,10 @@ |
|
|
Layout, |
|
|
Layout, |
|
|
Modal, |
|
|
Modal, |
|
|
ModalContent, |
|
|
ModalContent, |
|
|
Icon, |
|
|
Search, |
|
|
notifications, |
|
|
notifications, |
|
|
Pagination, |
|
|
Pagination, |
|
|
Search, |
|
|
Divider, |
|
|
Label, |
|
|
|
|
|
} from "@budibase/bbui" |
|
|
} from "@budibase/bbui" |
|
|
import AddUserModal from "./_components/AddUserModal.svelte" |
|
|
import AddUserModal from "./_components/AddUserModal.svelte" |
|
|
import { users, groups, auth } from "stores/portal" |
|
|
import { users, groups, auth } from "stores/portal" |
|
|
@ -20,68 +19,42 @@ |
|
|
import DeleteRowsButton from "components/backend/DataTable/buttons/DeleteRowsButton.svelte" |
|
|
import DeleteRowsButton from "components/backend/DataTable/buttons/DeleteRowsButton.svelte" |
|
|
import GroupsTableRenderer from "./_components/GroupsTableRenderer.svelte" |
|
|
import GroupsTableRenderer from "./_components/GroupsTableRenderer.svelte" |
|
|
import AppsTableRenderer from "./_components/AppsTableRenderer.svelte" |
|
|
import AppsTableRenderer from "./_components/AppsTableRenderer.svelte" |
|
|
import NameTableRenderer from "./_components/NameTableRenderer.svelte" |
|
|
|
|
|
import RoleTableRenderer from "./_components/RoleTableRenderer.svelte" |
|
|
import RoleTableRenderer from "./_components/RoleTableRenderer.svelte" |
|
|
import { goto } from "@roxi/routify" |
|
|
import { goto } from "@roxi/routify" |
|
|
import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte" |
|
|
import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte" |
|
|
import PasswordModal from "./_components/PasswordModal.svelte" |
|
|
import PasswordModal from "./_components/PasswordModal.svelte" |
|
|
import ImportUsersModal from "./_components/ImportUsersModal.svelte" |
|
|
import ImportUsersModal from "./_components/ImportUsersModal.svelte" |
|
|
import { createPaginationStore } from "helpers/pagination" |
|
|
import { createPaginationStore } from "helpers/pagination" |
|
|
|
|
|
import { get } from "svelte/store" |
|
|
import { Constants } from "@budibase/frontend-core" |
|
|
import { Constants } from "@budibase/frontend-core" |
|
|
|
|
|
|
|
|
const accessTypes = [ |
|
|
|
|
|
{ |
|
|
|
|
|
icon: "User", |
|
|
|
|
|
description: "App user - Only has access to published apps", |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
icon: "Hammer", |
|
|
|
|
|
description: "Developer - Access to the app builder", |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
icon: "Draw", |
|
|
|
|
|
description: "Admin - Full access", |
|
|
|
|
|
}, |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
//let email |
|
|
|
|
|
let enrichedUsers = [] |
|
|
let enrichedUsers = [] |
|
|
let createUserModal, |
|
|
let createUserModal, |
|
|
inviteConfirmationModal, |
|
|
inviteConfirmationModal, |
|
|
onboardingTypeModal, |
|
|
onboardingTypeModal, |
|
|
passwordModal, |
|
|
passwordModal, |
|
|
importUsersModal |
|
|
importUsersModal |
|
|
|
|
|
|
|
|
let pageInfo = createPaginationStore() |
|
|
let pageInfo = createPaginationStore() |
|
|
let prevEmail = undefined, |
|
|
let prevEmail = undefined, |
|
|
searchEmail = undefined |
|
|
searchEmail = undefined |
|
|
|
|
|
|
|
|
let selectedRows = [] |
|
|
let selectedRows = [] |
|
|
let customRenderers = [ |
|
|
let customRenderers = [ |
|
|
{ column: "userGroups", component: GroupsTableRenderer }, |
|
|
{ column: "userGroups", component: GroupsTableRenderer }, |
|
|
{ column: "apps", component: AppsTableRenderer }, |
|
|
{ column: "apps", component: AppsTableRenderer }, |
|
|
{ column: "name", component: NameTableRenderer }, |
|
|
|
|
|
{ column: "role", component: RoleTableRenderer }, |
|
|
{ column: "role", component: RoleTableRenderer }, |
|
|
] |
|
|
] |
|
|
|
|
|
|
|
|
$: hasGroupsLicense = $auth.user?.license.features.includes( |
|
|
|
|
|
Constants.Features.USER_GROUPS |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
$: schema = { |
|
|
$: schema = { |
|
|
name: {}, |
|
|
|
|
|
email: {}, |
|
|
email: {}, |
|
|
role: { |
|
|
role: { |
|
|
sortable: false, |
|
|
sortable: false, |
|
|
}, |
|
|
}, |
|
|
...(hasGroupsLicense && { |
|
|
...($auth.groupsEnabled && { |
|
|
userGroups: { sortable: false, displayName: "User groups" }, |
|
|
userGroups: { sortable: false, displayName: "Groups" }, |
|
|
}), |
|
|
}), |
|
|
apps: {}, |
|
|
apps: {}, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
$: userData = [] |
|
|
$: userData = [] |
|
|
|
|
|
|
|
|
$: page = $pageInfo.page |
|
|
$: page = $pageInfo.page |
|
|
$: fetchUsers(page, searchEmail) |
|
|
$: fetchUsers(page, searchEmail) |
|
|
$: { |
|
|
$: { |
|
|
@ -104,6 +77,7 @@ |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const showOnboardingTypeModal = async addUsersData => { |
|
|
const showOnboardingTypeModal = async addUsersData => { |
|
|
userData = await removingDuplicities(addUsersData) |
|
|
userData = await removingDuplicities(addUsersData) |
|
|
if (!userData?.users?.length) return |
|
|
if (!userData?.users?.length) return |
|
|
@ -112,13 +86,13 @@ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function createUserFlow() { |
|
|
async function createUserFlow() { |
|
|
let emails = userData?.users?.map(x => x.email) || [] |
|
|
const payload = userData?.users?.map(user => ({ |
|
|
|
|
|
email: user.email, |
|
|
|
|
|
builder: user.role === Constants.BudibaseRoles.Developer, |
|
|
|
|
|
admin: user.role === Constants.BudibaseRoles.Admin, |
|
|
|
|
|
})) |
|
|
try { |
|
|
try { |
|
|
const res = await users.invite({ |
|
|
const res = await users.invite(payload) |
|
|
emails: emails, |
|
|
|
|
|
builder: false, |
|
|
|
|
|
admin: false, |
|
|
|
|
|
}) |
|
|
|
|
|
notifications.success(res.message) |
|
|
notifications.success(res.message) |
|
|
inviteConfirmationModal.show() |
|
|
inviteConfirmationModal.show() |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
@ -198,6 +172,10 @@ |
|
|
const deleteRows = async () => { |
|
|
const deleteRows = async () => { |
|
|
try { |
|
|
try { |
|
|
let ids = selectedRows.map(user => user._id) |
|
|
let ids = selectedRows.map(user => user._id) |
|
|
|
|
|
if (ids.includes(get(auth).user._id)) { |
|
|
|
|
|
notifications.error("You cannot delete yourself") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
await users.bulkDelete(ids) |
|
|
await users.bulkDelete(ids) |
|
|
notifications.success(`Successfully deleted ${selectedRows.length} rows`) |
|
|
notifications.success(`Successfully deleted ${selectedRows.length} rows`) |
|
|
selectedRows = [] |
|
|
selectedRows = [] |
|
|
@ -227,23 +205,13 @@ |
|
|
} |
|
|
} |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<Layout noPadding> |
|
|
<Layout noPadding gap="M"> |
|
|
<Layout gap="XS" noPadding> |
|
|
<Layout gap="XS" noPadding> |
|
|
<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> |
|
|
|
|
|
|
|
|
<div> |
|
|
|
|
|
{#each accessTypes as type} |
|
|
|
|
|
<div class="access-description"> |
|
|
|
|
|
<Icon name={type.icon} /> |
|
|
|
|
|
<div class="access-text"> |
|
|
|
|
|
<Body size="S">{type.description}</Body> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
{/each} |
|
|
|
|
|
</div> |
|
|
|
|
|
</Layout> |
|
|
</Layout> |
|
|
<Layout gap="S" noPadding> |
|
|
<Divider size="S" /> |
|
|
|
|
|
<div class="controls"> |
|
|
<ButtonGroup> |
|
|
<ButtonGroup> |
|
|
<Button |
|
|
<Button |
|
|
dataCy="add-user" |
|
|
dataCy="add-user" |
|
|
@ -251,39 +219,47 @@ |
|
|
icon="UserAdd" |
|
|
icon="UserAdd" |
|
|
cta>Add users</Button |
|
|
cta>Add users</Button |
|
|
> |
|
|
> |
|
|
<Button on:click={importUsersModal.show} icon="Import" primary |
|
|
<Button |
|
|
>Import users</Button |
|
|
on:click={importUsersModal.show} |
|
|
|
|
|
icon="Import" |
|
|
|
|
|
secondary |
|
|
|
|
|
newStyles |
|
|
> |
|
|
> |
|
|
|
|
|
Import users |
|
|
<div class="field"> |
|
|
</Button> |
|
|
<Label size="L">Search email</Label> |
|
|
</ButtonGroup> |
|
|
<Search bind:value={searchEmail} placeholder="" /> |
|
|
<div class="controls-right"> |
|
|
</div> |
|
|
<Search bind:value={searchEmail} placeholder="Search email" /> |
|
|
{#if selectedRows.length > 0} |
|
|
{#if selectedRows.length > 0} |
|
|
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} /> |
|
|
<DeleteRowsButton |
|
|
|
|
|
item="user" |
|
|
|
|
|
on:updaterows |
|
|
|
|
|
{selectedRows} |
|
|
|
|
|
{deleteRows} |
|
|
|
|
|
/> |
|
|
{/if} |
|
|
{/if} |
|
|
</ButtonGroup> |
|
|
|
|
|
<Table |
|
|
|
|
|
on:click={({ detail }) => $goto(`./${detail._id}`)} |
|
|
|
|
|
{schema} |
|
|
|
|
|
bind:selectedRows |
|
|
|
|
|
data={enrichedUsers} |
|
|
|
|
|
allowEditColumns={false} |
|
|
|
|
|
allowEditRows={false} |
|
|
|
|
|
allowSelectRows={true} |
|
|
|
|
|
showHeaderBorder={false} |
|
|
|
|
|
{customRenderers} |
|
|
|
|
|
/> |
|
|
|
|
|
<div class="pagination"> |
|
|
|
|
|
<Pagination |
|
|
|
|
|
page={$pageInfo.pageNumber} |
|
|
|
|
|
hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage} |
|
|
|
|
|
hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage} |
|
|
|
|
|
goToPrevPage={pageInfo.prevPage} |
|
|
|
|
|
goToNextPage={pageInfo.nextPage} |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</Layout> |
|
|
</div> |
|
|
|
|
|
<Table |
|
|
|
|
|
on:click={({ detail }) => $goto(`./${detail._id}`)} |
|
|
|
|
|
{schema} |
|
|
|
|
|
bind:selectedRows |
|
|
|
|
|
data={enrichedUsers} |
|
|
|
|
|
allowEditColumns={false} |
|
|
|
|
|
allowEditRows={false} |
|
|
|
|
|
allowSelectRows={true} |
|
|
|
|
|
showHeaderBorder={false} |
|
|
|
|
|
{customRenderers} |
|
|
|
|
|
/> |
|
|
|
|
|
<div class="pagination"> |
|
|
|
|
|
<Pagination |
|
|
|
|
|
page={$pageInfo.pageNumber} |
|
|
|
|
|
hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage} |
|
|
|
|
|
hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage} |
|
|
|
|
|
goToPrevPage={pageInfo.prevPage} |
|
|
|
|
|
goToNextPage={pageInfo.nextPage} |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
</Layout> |
|
|
</Layout> |
|
|
|
|
|
|
|
|
<Modal bind:this={createUserModal}> |
|
|
<Modal bind:this={createUserModal}> |
|
|
@ -320,28 +296,22 @@ |
|
|
display: flex; |
|
|
display: flex; |
|
|
flex-direction: row; |
|
|
flex-direction: row; |
|
|
justify-content: flex-end; |
|
|
justify-content: flex-end; |
|
|
margin-top: var(--spacing-xl); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.field { |
|
|
.controls { |
|
|
display: flex; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
|
|
|
flex-direction: row; |
|
|
flex-direction: row; |
|
|
grid-gap: var(--spacing-m); |
|
|
justify-content: space-between; |
|
|
margin-left: auto; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.field > :global(*) + :global(*) { |
|
|
|
|
|
margin-left: var(--spacing-m); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
.controls-right { |
|
|
.access-description { |
|
|
|
|
|
display: flex; |
|
|
display: flex; |
|
|
margin-top: var(--spacing-xl); |
|
|
flex-direction: row; |
|
|
opacity: 0.8; |
|
|
justify-content: flex-end; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
gap: var(--spacing-xl); |
|
|
} |
|
|
} |
|
|
|
|
|
.controls-right :global(.spectrum-Search) { |
|
|
.access-text { |
|
|
width: 200px; |
|
|
margin-left: var(--spacing-m); |
|
|
|
|
|
} |
|
|
} |
|
|
</style> |
|
|
</style> |
|
|
|