some user table fixes
This commit is contained in:
parent
6eb4f189ce
commit
4543b1213f
|
@ -27,6 +27,7 @@
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
import Logo from "assets/bb-space-man.svg"
|
import Logo from "assets/bb-space-man.svg"
|
||||||
import AccessFilter from "./_components/AcessFilter.svelte"
|
import AccessFilter from "./_components/AcessFilter.svelte"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
let sortBy = "name"
|
let sortBy = "name"
|
||||||
let template
|
let template
|
||||||
|
@ -68,6 +69,8 @@
|
||||||
$: unlocked = lockedApps?.length === 0
|
$: unlocked = lockedApps?.length === 0
|
||||||
$: automationErrors = getAutomationErrors(enrichedApps)
|
$: automationErrors = getAutomationErrors(enrichedApps)
|
||||||
|
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type !== Constants.PlanType.FREE
|
||||||
|
|
||||||
const enrichApps = (apps, user, sortBy) => {
|
const enrichApps = (apps, user, sortBy) => {
|
||||||
const enrichedApps = apps.map(app => ({
|
const enrichedApps = apps.map(app => ({
|
||||||
...app,
|
...app,
|
||||||
|
@ -355,7 +358,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
{#if $groups.length}
|
{#if isProPlan && $groups.length}
|
||||||
<AccessFilter on:change={accessFilterAction} />
|
<AccessFilter on:change={accessFilterAction} />
|
||||||
{/if}
|
{/if}
|
||||||
<Select
|
<Select
|
||||||
|
|
|
@ -9,12 +9,14 @@
|
||||||
Tags,
|
Tags,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { groups } from "stores/portal"
|
import { groups, auth } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
||||||
|
|
||||||
import UserGroupsRow from "./_components/UserGroupsRow.svelte"
|
import UserGroupsRow from "./_components/UserGroupsRow.svelte"
|
||||||
|
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type !== Constants.PlanType.FREE
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let group = {
|
let group = {
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -23,7 +25,6 @@
|
||||||
users: [],
|
users: [],
|
||||||
apps: [],
|
apps: [],
|
||||||
}
|
}
|
||||||
let proPlan = true
|
|
||||||
|
|
||||||
async function deleteGroup(group) {
|
async function deleteGroup(group) {
|
||||||
try {
|
try {
|
||||||
|
@ -54,7 +55,7 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<Heading size="M">User groups</Heading>
|
<Heading size="M">User groups</Heading>
|
||||||
{#if !proPlan}
|
{#if !isProPlan}
|
||||||
<Tags>
|
<Tags>
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<div class="tag">
|
<div class="tag">
|
||||||
|
@ -68,13 +69,15 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
<div class="align-buttons">
|
<div class="align-buttons">
|
||||||
<Button
|
<Button
|
||||||
icon={proPlan ? "UserGroup" : ""}
|
newStyles
|
||||||
cta={proPlan}
|
icon={isProPlan ? "UserGroup" : ""}
|
||||||
|
cta={isProPlan}
|
||||||
on:click={() => modal.show()}
|
on:click={() => modal.show()}
|
||||||
>{proPlan ? "Create user group" : "Upgrade Account"}</Button
|
>{isProPlan ? "Create user group" : "Upgrade Account"}</Button
|
||||||
>
|
>
|
||||||
{#if !proPlan}
|
{#if !isProPlan}
|
||||||
<Button
|
<Button
|
||||||
|
newStyles
|
||||||
secondary
|
secondary
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
window.open("https://budibase.com/pricing/", "_blank")
|
window.open("https://budibase.com/pricing/", "_blank")
|
||||||
|
@ -83,7 +86,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if proPlan}
|
{#if isProPlan}
|
||||||
<div class="groupTable">
|
<div class="groupTable">
|
||||||
{#each $groups as group}
|
{#each $groups as group}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
let selectedGroups = []
|
let selectedGroups = []
|
||||||
let allAppList = []
|
let allAppList = []
|
||||||
|
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type !== Constants.PlanType.FREE
|
||||||
|
|
||||||
$: allAppList = $apps
|
$: allAppList = $apps
|
||||||
.filter(x => {
|
.filter(x => {
|
||||||
if ($userFetch.data?.roles) {
|
if ($userFetch.data?.roles) {
|
||||||
|
@ -244,52 +246,53 @@
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<!-- User groups -->
|
{#if isProPlan}
|
||||||
<Layout gap="XS" noPadding>
|
<!-- User groups -->
|
||||||
<div class="tableTitle">
|
<Layout gap="XS" noPadding>
|
||||||
<div>
|
<div class="tableTitle">
|
||||||
<Heading size="XS">User groups</Heading>
|
<div>
|
||||||
<Body size="S">Add or remove this user from user groups</Body>
|
<Heading size="XS">User groups</Heading>
|
||||||
</div>
|
<Body size="S">Add or remove this user from user groups</Body>
|
||||||
<div bind:this={popoverAnchor}>
|
</div>
|
||||||
<Button on:click={popover.show()} icon="UserGroup" cta
|
<div bind:this={popoverAnchor}>
|
||||||
>Add User Group</Button
|
<Button on:click={popover.show()} icon="UserGroup" cta
|
||||||
>
|
>Add User Group</Button
|
||||||
</div>
|
|
||||||
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
|
||||||
<UserGroupPicker
|
|
||||||
key={"name"}
|
|
||||||
title={"Group"}
|
|
||||||
bind:searchTerm
|
|
||||||
bind:selected={selectedGroups}
|
|
||||||
bind:filtered={filteredGroups}
|
|
||||||
{addAll}
|
|
||||||
select={addGroup}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<List>
|
|
||||||
{#if userGroups.length}
|
|
||||||
{#each userGroups as group}
|
|
||||||
<ListItem
|
|
||||||
title={group.name}
|
|
||||||
icon={group.icon}
|
|
||||||
iconBackground={group.color}
|
|
||||||
><Icon
|
|
||||||
on:click={removeGroup(group._id)}
|
|
||||||
hoverable
|
|
||||||
size="L"
|
|
||||||
name="Close"
|
|
||||||
/></ListItem
|
|
||||||
>
|
>
|
||||||
{/each}
|
</div>
|
||||||
{:else}
|
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
||||||
<ListItem icon="UserGroup" title="No groups" />
|
<UserGroupPicker
|
||||||
{/if}
|
key={"name"}
|
||||||
</List>
|
title={"Group"}
|
||||||
</Layout>
|
bind:searchTerm
|
||||||
|
bind:selected={selectedGroups}
|
||||||
|
bind:filtered={filteredGroups}
|
||||||
|
{addAll}
|
||||||
|
select={addGroup}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<List>
|
||||||
|
{#if userGroups.length}
|
||||||
|
{#each userGroups as group}
|
||||||
|
<ListItem
|
||||||
|
title={group.name}
|
||||||
|
icon={group.icon}
|
||||||
|
iconBackground={group.color}
|
||||||
|
><Icon
|
||||||
|
on:click={removeGroup(group._id)}
|
||||||
|
hoverable
|
||||||
|
size="L"
|
||||||
|
name="Close"
|
||||||
|
/></ListItem
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<ListItem icon="UserGroup" title="No groups" />
|
||||||
|
{/if}
|
||||||
|
</List>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
<!-- User Apps -->
|
<!-- User Apps -->
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
<div class="appsTitle">
|
<div class="appsTitle">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
InputDropdown,
|
InputDropdown,
|
||||||
Layout,
|
Layout,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { groups } from "stores/portal"
|
import { groups, auth } from "stores/portal"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
let disabled
|
let disabled
|
||||||
let userGroups = []
|
let userGroups = []
|
||||||
|
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type !== Constants.PlanType.FREE
|
||||||
|
|
||||||
$: userData = [
|
$: userData = [
|
||||||
{
|
{
|
||||||
email: "",
|
email: "",
|
||||||
|
@ -67,14 +69,16 @@
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Multiselect
|
{#if isProPlan}
|
||||||
bind:value={userGroups}
|
<Multiselect
|
||||||
placeholder="Select User Groups"
|
bind:value={userGroups}
|
||||||
label="User Groups"
|
placeholder="Select User Groups"
|
||||||
options={$groups}
|
label="User Groups"
|
||||||
getOptionLabel={option => option.name}
|
options={$groups}
|
||||||
getOptionValue={option => option._id}
|
getOptionLabel={option => option.name}
|
||||||
/>
|
getOptionValue={option => option._id}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
Multiselect,
|
Multiselect,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { groups } from "stores/portal"
|
import { groups, auth } from "stores/portal"
|
||||||
import { emailValidator } from "../../../../../../helpers/validation"
|
import { emailValidator } from "../../../../../../helpers/validation"
|
||||||
|
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
const BYTES_IN_MB = 1000000
|
const BYTES_IN_MB = 1000000
|
||||||
|
@ -21,7 +20,9 @@
|
||||||
let userEmails = []
|
let userEmails = []
|
||||||
let userGroups = []
|
let userGroups = []
|
||||||
let usersRole = null
|
let usersRole = null
|
||||||
|
|
||||||
$: invalidEmails = []
|
$: invalidEmails = []
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type !== Constants.PlanType.FREE
|
||||||
|
|
||||||
const validEmails = userEmails => {
|
const validEmails = userEmails => {
|
||||||
for (const email of userEmails) {
|
for (const email of userEmails) {
|
||||||
|
@ -86,14 +87,16 @@
|
||||||
options={Constants.BuilderRoleDescriptions}
|
options={Constants.BuilderRoleDescriptions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Multiselect
|
{#if isProPlan}
|
||||||
bind:value={userGroups}
|
<Multiselect
|
||||||
placeholder="Select User Groups"
|
bind:value={userGroups}
|
||||||
label="User Groups"
|
placeholder="Select User Groups"
|
||||||
options={$groups}
|
label="User Groups"
|
||||||
getOptionLabel={option => option.name}
|
options={$groups}
|
||||||
getOptionValue={option => option._id}
|
getOptionLabel={option => option.name}
|
||||||
/>
|
getOptionValue={option => option._id}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
{value}
|
{value}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text">Invite pending...</div>
|
<div class="text">Not Available</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
Label,
|
Label,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AddUserModal from "./_components/AddUserModal.svelte"
|
import AddUserModal from "./_components/AddUserModal.svelte"
|
||||||
import { users, groups } from "stores/portal"
|
import { users, groups, auth } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "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 NameTableRenderer from "./_components/NameTableRenderer.svelte"
|
||||||
|
@ -27,25 +28,7 @@
|
||||||
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 { Constants } from "@budibase/frontend-core"
|
||||||
const schema = {
|
|
||||||
name: {},
|
|
||||||
email: {},
|
|
||||||
role: {
|
|
||||||
noPropagation: true,
|
|
||||||
sortable: false,
|
|
||||||
},
|
|
||||||
userGroups: { sortable: false, displayName: "User groups" },
|
|
||||||
apps: { width: "120px" },
|
|
||||||
settings: {
|
|
||||||
sortable: false,
|
|
||||||
width: "60px",
|
|
||||||
displayName: "",
|
|
||||||
align: "Right",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
$: userData = []
|
|
||||||
|
|
||||||
const accessTypes = [
|
const accessTypes = [
|
||||||
{
|
{
|
||||||
|
@ -74,6 +57,38 @@
|
||||||
let prevEmail = undefined,
|
let prevEmail = undefined,
|
||||||
searchEmail = undefined
|
searchEmail = undefined
|
||||||
|
|
||||||
|
let selectedRows = []
|
||||||
|
let customRenderers = [
|
||||||
|
{ column: "userGroups", component: GroupsTableRenderer },
|
||||||
|
{ column: "apps", component: AppsTableRenderer },
|
||||||
|
{ column: "name", component: NameTableRenderer },
|
||||||
|
{ column: "settings", component: SettingsTableRenderer },
|
||||||
|
{ column: "role", component: RoleTableRenderer },
|
||||||
|
]
|
||||||
|
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type !== Constants.PlanType.FREE
|
||||||
|
|
||||||
|
$: schema = {
|
||||||
|
name: {},
|
||||||
|
email: {},
|
||||||
|
role: {
|
||||||
|
noPropagation: true,
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
...(isProPlan && {
|
||||||
|
userGroups: { sortable: false, displayName: "User groups" },
|
||||||
|
}),
|
||||||
|
apps: { width: "120px" },
|
||||||
|
settings: {
|
||||||
|
sortable: false,
|
||||||
|
width: "60px",
|
||||||
|
displayName: "",
|
||||||
|
align: "Right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
$: userData = []
|
||||||
|
|
||||||
$: page = $pageInfo.page
|
$: page = $pageInfo.page
|
||||||
$: fetchUsers(page, searchEmail)
|
$: fetchUsers(page, searchEmail)
|
||||||
|
|
||||||
|
@ -164,6 +179,19 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deleteRows = async () => {
|
||||||
|
try {
|
||||||
|
let ids = selectedRows.map(user => user._id)
|
||||||
|
await users.bulkDelete(ids)
|
||||||
|
notifications.success(`Successfully deleted ${selectedRows.length} rows`)
|
||||||
|
selectedRows = []
|
||||||
|
await fetchUsers(page, searchEmail)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
notifications.error("Error deleting rows")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchUsers(page, email) {
|
async function fetchUsers(page, email) {
|
||||||
if ($pageInfo.loading) {
|
if ($pageInfo.loading) {
|
||||||
return
|
return
|
||||||
|
@ -211,26 +239,25 @@
|
||||||
<Button on:click={importUsersModal.show} icon="Import" primary
|
<Button on:click={importUsersModal.show} icon="Import" primary
|
||||||
>Import Users</Button
|
>Import Users</Button
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label size="L">Search email</Label>
|
<Label size="L">Search email</Label>
|
||||||
<Search bind:value={searchEmail} placeholder="" />
|
<Search bind:value={searchEmail} placeholder="" />
|
||||||
</div>
|
</div>
|
||||||
|
{#if selectedRows.length > 0}
|
||||||
|
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} />
|
||||||
|
{/if}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Table
|
<Table
|
||||||
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
||||||
{schema}
|
{schema}
|
||||||
|
bind:selectedRows
|
||||||
data={enrichedUsers}
|
data={enrichedUsers}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={true}
|
allowSelectRows={true}
|
||||||
showHeaderBorder={false}
|
showHeaderBorder={false}
|
||||||
customRenderers={[
|
{customRenderers}
|
||||||
{ column: "userGroups", component: GroupsTableRenderer },
|
|
||||||
{ column: "apps", component: AppsTableRenderer },
|
|
||||||
{ column: "name", component: NameTableRenderer },
|
|
||||||
{ column: "settings", component: SettingsTableRenderer },
|
|
||||||
{ column: "role", component: RoleTableRenderer },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|
|
@ -8,13 +8,15 @@
|
||||||
ListItem,
|
ListItem,
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
|
Pagination,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
import { users, groups, apps } from "stores/portal"
|
import { users, groups, apps, auth } from "stores/portal"
|
||||||
import AssignmentModal from "./AssignmentModal.svelte"
|
import AssignmentModal from "./AssignmentModal.svelte"
|
||||||
import { createPaginationStore } from "helpers/pagination"
|
import { createPaginationStore } from "helpers/pagination"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
|
|
||||||
|
@ -22,11 +24,11 @@
|
||||||
let appGroups = []
|
let appGroups = []
|
||||||
let appUsers = []
|
let appUsers = []
|
||||||
let pageInfo = createPaginationStore()
|
let pageInfo = createPaginationStore()
|
||||||
let prevSearch = undefined,
|
|
||||||
search = undefined
|
|
||||||
|
|
||||||
$: page = $pageInfo.page
|
$: page = $pageInfo.page
|
||||||
$: fetchUsers(page, search)
|
$: fetchUsers(page)
|
||||||
|
|
||||||
|
$: isProPlan = $auth.user?.license.plan.type === Constants.PlanType.FREE
|
||||||
|
|
||||||
$: appUsers =
|
$: appUsers =
|
||||||
$users.data?.filter(x => {
|
$users.data?.filter(x => {
|
||||||
|
@ -41,6 +43,19 @@
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: filteredUsers =
|
||||||
|
$users.data?.filter(x => {
|
||||||
|
return !Object.keys(x.roles).find(y => {
|
||||||
|
return extractAppId(y) === extractAppId(app.appId)
|
||||||
|
})
|
||||||
|
}) || []
|
||||||
|
|
||||||
|
$: filteredGroups = $groups.filter(element => {
|
||||||
|
return !element.apps.find(y => {
|
||||||
|
return y.appId === app.appId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
function extractAppId(id) {
|
function extractAppId(id) {
|
||||||
const split = id?.split("_") || []
|
const split = id?.split("_") || []
|
||||||
return split.length ? split[split.length - 1] : null
|
return split.length ? split[split.length - 1] : null
|
||||||
|
@ -70,26 +85,27 @@
|
||||||
await users.save(newUser)
|
await users.save(newUser)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await pageInfo.reset()
|
await groups.actions.init()
|
||||||
|
await users.search({ page, appId: app.appId })
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
async function updateRole(user) {
|
async function updateUserRole(role, user) {
|
||||||
console.log(user)
|
user.roles[app.appId] = role
|
||||||
|
users.save(user)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
async function fetchUsers(page, search) {
|
async function updateGroupRole(role, group) {
|
||||||
|
group.role = role
|
||||||
|
groups.actions.save(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUsers(page) {
|
||||||
if ($pageInfo.loading) {
|
if ($pageInfo.loading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// need to remove the page if they've started searching
|
|
||||||
if (search && !prevSearch) {
|
|
||||||
pageInfo.reset()
|
|
||||||
page = undefined
|
|
||||||
}
|
|
||||||
prevSearch = search
|
|
||||||
try {
|
try {
|
||||||
pageInfo.loading()
|
pageInfo.loading()
|
||||||
await users.search({ page, search })
|
await users.search({ page, appId: app.appId })
|
||||||
pageInfo.fetched($users.hasNextPage, $users.nextPage)
|
pageInfo.fetched($users.hasNextPage, $users.nextPage)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error getting user list")
|
notifications.error("Error getting user list")
|
||||||
|
@ -120,21 +136,29 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<List title="User Groups">
|
{#if isProPlan}
|
||||||
{#each appGroups as group}
|
<List title="User Groups">
|
||||||
<ListItem
|
{#each appGroups as group}
|
||||||
title={group.name}
|
<ListItem
|
||||||
icon={group.icon}
|
title={group.name}
|
||||||
iconBackground={group.color}
|
icon={group.icon}
|
||||||
>
|
iconBackground={group.color}
|
||||||
<RoleSelect autoWidth quiet value={group.role} />
|
>
|
||||||
</ListItem>
|
<RoleSelect
|
||||||
{/each}
|
on:change={e => updateGroupRole(e.detail, group)}
|
||||||
</List>
|
autoWidth
|
||||||
|
quiet
|
||||||
|
value={group.role}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
{/each}
|
||||||
|
</List>
|
||||||
|
{/if}
|
||||||
<List title="Users">
|
<List title="Users">
|
||||||
{#each appUsers as user}
|
{#each appUsers as user}
|
||||||
<ListItem title={user.email} avatar>
|
<ListItem title={user.email} avatar>
|
||||||
<RoleSelect
|
<RoleSelect
|
||||||
|
on:change={e => updateUserRole(e.detail, user)}
|
||||||
autoWidth
|
autoWidth
|
||||||
quiet
|
quiet
|
||||||
value={user.roles[
|
value={user.roles[
|
||||||
|
@ -146,6 +170,15 @@
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{/each}
|
{/each}
|
||||||
</List>
|
</List>
|
||||||
|
<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>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="align">
|
<div class="align">
|
||||||
<Layout gap="S">
|
<Layout gap="S">
|
||||||
|
@ -167,7 +200,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={assignmentModal}>
|
<Modal bind:this={assignmentModal}>
|
||||||
<AssignmentModal userData={$users.data} {addData} />
|
<AssignmentModal
|
||||||
|
userData={filteredUsers.length ? filteredUsers : $users.data}
|
||||||
|
groups={isProPlan ? filteredGroups : []}
|
||||||
|
{addData}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, PickerDropdown, ActionButton } from "@budibase/bbui"
|
import { ModalContent, PickerDropdown, ActionButton } from "@budibase/bbui"
|
||||||
import { groups } from "stores/portal"
|
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let addData
|
export let addData
|
||||||
export let userData = []
|
export let userData = []
|
||||||
|
export let groups = []
|
||||||
|
|
||||||
$: optionSections = {
|
$: optionSections = {
|
||||||
groups: {
|
...(groups.length && {
|
||||||
data: $groups,
|
groups: {
|
||||||
getLabel: group => group.name,
|
data: groups,
|
||||||
getValue: group => group._id,
|
getLabel: group => group.name,
|
||||||
getIcon: group => group.icon,
|
getValue: group => group._id,
|
||||||
getColour: group => group.color,
|
getIcon: group => group.icon,
|
||||||
},
|
getColour: group => group.color,
|
||||||
|
},
|
||||||
|
}),
|
||||||
users: {
|
users: {
|
||||||
data: userData,
|
data: userData,
|
||||||
getLabel: user => user.email,
|
getLabel: user => user.email,
|
||||||
|
|
|
@ -75,6 +75,10 @@ export function createUsersStore() {
|
||||||
update(users => users.filter(user => user._id !== id))
|
update(users => users.filter(user => user._id !== id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function bulkDelete(userIds) {
|
||||||
|
await API.deleteUsers(userIds)
|
||||||
|
}
|
||||||
|
|
||||||
async function save(user) {
|
async function save(user) {
|
||||||
return await API.saveUser(user)
|
return await API.saveUser(user)
|
||||||
}
|
}
|
||||||
|
@ -87,6 +91,7 @@ export function createUsersStore() {
|
||||||
acceptInvite,
|
acceptInvite,
|
||||||
create,
|
create,
|
||||||
save,
|
save,
|
||||||
|
bulkDelete,
|
||||||
delete: del,
|
delete: del,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,19 @@ export const buildUserEndpoints = API => ({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple users
|
||||||
|
* @param userId the ID of the user to delete
|
||||||
|
*/
|
||||||
|
deleteUsers: async userIds => {
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/global/users/bulkDelete`,
|
||||||
|
body: {
|
||||||
|
userIds,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites a user to the current tenant.
|
* Invites a user to the current tenant.
|
||||||
* @param email the email address to send the invitation to
|
* @param email the email address to send the invitation to
|
||||||
|
|
|
@ -84,6 +84,13 @@ export const BuilderRoleDescriptions = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const PlanType = {
|
||||||
|
FREE: "free",
|
||||||
|
TEAM: "team",
|
||||||
|
BUSINESS: "business",
|
||||||
|
ENTERPRISE: "enterprise",
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API version header attached to all requests.
|
* API version header attached to all requests.
|
||||||
* Version changelog:
|
* Version changelog:
|
||||||
|
|
|
@ -73,6 +73,7 @@ const checkAuthorizedResource = async (
|
||||||
|
|
||||||
export = (permType: any, permLevel: any = null, opts = { schema: false }) =>
|
export = (permType: any, permLevel: any = null, opts = { schema: false }) =>
|
||||||
async (ctx: any, next: any) => {
|
async (ctx: any, next: any) => {
|
||||||
|
console.log(ctx)
|
||||||
// webhooks don't need authentication, each webhook unique
|
// webhooks don't need authentication, each webhook unique
|
||||||
// also internal requests (between services) don't need authorized
|
// also internal requests (between services) don't need authorized
|
||||||
if (isWebhookEndpoint(ctx) || ctx.internal) {
|
if (isWebhookEndpoint(ctx) || ctx.internal) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const bulkSave = async (ctx: any) => {
|
||||||
newUsers.forEach((user: any) => {
|
newUsers.forEach((user: any) => {
|
||||||
usersToSave.push(
|
usersToSave.push(
|
||||||
users.save(user, {
|
users.save(user, {
|
||||||
hashPassword: false,
|
hashPassword: true,
|
||||||
requirePassword: user.requirePassword,
|
requirePassword: user.requirePassword,
|
||||||
bulkCreate: true,
|
bulkCreate: true,
|
||||||
})
|
})
|
||||||
|
@ -53,10 +53,11 @@ export const bulkSave = async (ctx: any) => {
|
||||||
delete user.password
|
delete user.password
|
||||||
})
|
})
|
||||||
|
|
||||||
groupsToSave.forEach(async group => {
|
if (groupsToSave.length)
|
||||||
group.users = [...group.users, ...allUsers]
|
groupsToSave.forEach(async group => {
|
||||||
await db.put(group)
|
group.users = [...group.users, ...allUsers]
|
||||||
})
|
await db.put(group)
|
||||||
|
})
|
||||||
|
|
||||||
ctx.body = response
|
ctx.body = response
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -130,6 +131,19 @@ export const destroy = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const bulkDelete = async (ctx: any) => {
|
||||||
|
const { userIds } = ctx.request.body
|
||||||
|
|
||||||
|
let deleted = 0
|
||||||
|
userIds.forEach(async (id: any) => {
|
||||||
|
await users.destroy(id, ctx.user)
|
||||||
|
deleted++
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
message: `${deleted} user(s) deleted`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const search = async (ctx: any) => {
|
export const search = async (ctx: any) => {
|
||||||
const paginated = await users.paginatedUsers(ctx.request.body)
|
const paginated = await users.paginatedUsers(ctx.request.body)
|
||||||
// user hashed password shouldn't ever be returned
|
// user hashed password shouldn't ever be returned
|
||||||
|
|
|
@ -14,6 +14,7 @@ function buildGroupSaveValidation() {
|
||||||
color: Joi.string().required(),
|
color: Joi.string().required(),
|
||||||
icon: Joi.string().required(),
|
icon: Joi.string().required(),
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
|
role: Joi.string().optional(),
|
||||||
users: Joi.array().optional(),
|
users: Joi.array().optional(),
|
||||||
apps: Joi.array().optional(),
|
apps: Joi.array().optional(),
|
||||||
createdAt: Joi.string().optional(),
|
createdAt: Joi.string().optional(),
|
||||||
|
|
|
@ -63,6 +63,7 @@ router
|
||||||
.get("/api/global/users", builderOrAdmin, controller.fetch)
|
.get("/api/global/users", builderOrAdmin, controller.fetch)
|
||||||
.post("/api/global/users/search", builderOrAdmin, controller.search)
|
.post("/api/global/users/search", builderOrAdmin, controller.search)
|
||||||
.delete("/api/global/users/:id", adminOnly, controller.destroy)
|
.delete("/api/global/users/:id", adminOnly, controller.destroy)
|
||||||
|
.post("/api/global/users/bulkDelete", adminOnly, controller.bulkDelete)
|
||||||
.get("/api/global/roles/:appId")
|
.get("/api/global/roles/:appId")
|
||||||
.post(
|
.post(
|
||||||
"/api/global/users/invite",
|
"/api/global/users/invite",
|
||||||
|
|
Loading…
Reference in New Issue