Rewrite app overview access tab
This commit is contained in:
parent
b14132cd9a
commit
5e0200544c
|
@ -88,8 +88,8 @@
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
>
|
>
|
||||||
{#if fieldIcon}
|
{#if fieldIcon}
|
||||||
<span class="option-extra">
|
<span class="option-extra icon">
|
||||||
<Icon name={fieldIcon} />
|
<Icon size="S" name={fieldIcon} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if fieldColour}
|
{#if fieldColour}
|
||||||
|
@ -168,8 +168,8 @@
|
||||||
class:is-disabled={!isOptionEnabled(option)}
|
class:is-disabled={!isOptionEnabled(option)}
|
||||||
>
|
>
|
||||||
{#if getOptionIcon(option, idx)}
|
{#if getOptionIcon(option, idx)}
|
||||||
<span class="option-extra">
|
<span class="option-extra icon">
|
||||||
<Icon name={getOptionIcon(option, idx)} />
|
<Icon size="S" name={getOptionIcon(option, idx)} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if getOptionColour(option, idx)}
|
{#if getOptionColour(option, idx)}
|
||||||
|
@ -241,6 +241,9 @@
|
||||||
.option-extra {
|
.option-extra {
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
|
.option-extra.icon {
|
||||||
|
margin: 0 -1px;
|
||||||
|
}
|
||||||
|
|
||||||
.spectrum-Popover :global(.spectrum-Search) {
|
.spectrum-Popover :global(.spectrum-Search) {
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
* template: a HBS or JS binding to use as the value
|
* template: a HBS or JS binding to use as the value
|
||||||
* background: the background color
|
* background: the background color
|
||||||
* color: the text color
|
* color: the text color
|
||||||
|
* borderLeft: show a left border
|
||||||
|
* borderRight: show a right border
|
||||||
*/
|
*/
|
||||||
export let data = []
|
export let data = []
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
|
@ -270,6 +272,14 @@
|
||||||
if (schema[field].align === "Right") {
|
if (schema[field].align === "Right") {
|
||||||
styles[field] += "justify-content: flex-end; text-align: right;"
|
styles[field] += "justify-content: flex-end; text-align: right;"
|
||||||
}
|
}
|
||||||
|
if (schema[field].borderLeft) {
|
||||||
|
styles[field] +=
|
||||||
|
"border-left: 1px solid var(--spectrum-global-color-gray-200);"
|
||||||
|
}
|
||||||
|
if (schema[field].borderLeft) {
|
||||||
|
styles[field] +=
|
||||||
|
"border-right: 1px solid var(--spectrum-global-color-gray-200);"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return styles
|
return styles
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select } from "@budibase/bbui"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { Constants, RoleUtils } from "@budibase/frontend-core"
|
import { Constants, RoleUtils } from "@budibase/frontend-core"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let error
|
export let error
|
||||||
|
@ -9,26 +10,62 @@
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let allowPublic = true
|
export let allowPublic = true
|
||||||
|
export let allowRemove = false
|
||||||
|
|
||||||
$: options = getOptions($roles, allowPublic)
|
const dispatch = createEventDispatcher()
|
||||||
|
const RemoveID = "remove"
|
||||||
|
|
||||||
|
$: options = getOptions($roles, allowPublic, allowRemove)
|
||||||
|
|
||||||
const getOptions = (roles, allowPublic) => {
|
const getOptions = (roles, allowPublic) => {
|
||||||
|
if (allowRemove) {
|
||||||
|
roles = [
|
||||||
|
...roles,
|
||||||
|
{
|
||||||
|
_id: RemoveID,
|
||||||
|
name: "Remove",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
if (allowPublic) {
|
if (allowPublic) {
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
return roles.filter(role => role._id !== Constants.Roles.PUBLIC)
|
return roles.filter(role => role._id !== Constants.Roles.PUBLIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getColor = role => {
|
||||||
|
if (allowRemove && role._id === RemoveID) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return RoleUtils.getRoleColour(role._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIcon = role => {
|
||||||
|
if (allowRemove && role._id === RemoveID) {
|
||||||
|
return "Close"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
if (allowRemove && e.detail === RemoveID) {
|
||||||
|
dispatch("remove")
|
||||||
|
} else {
|
||||||
|
dispatch("change", e.detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
{autoWidth}
|
{autoWidth}
|
||||||
{quiet}
|
{quiet}
|
||||||
bind:value
|
bind:value
|
||||||
on:change
|
on:change={onChange}
|
||||||
{options}
|
{options}
|
||||||
getOptionLabel={role => role.name}
|
getOptionLabel={role => role.name}
|
||||||
getOptionValue={role => role._id}
|
getOptionValue={role => role._id}
|
||||||
getOptionColour={role => RoleUtils.getRoleColour(role._id)}
|
getOptionColour={getColor}
|
||||||
|
getOptionIcon={getIcon}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{error}
|
{error}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,207 +0,0 @@
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
Layout,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
Modal,
|
|
||||||
notifications,
|
|
||||||
Pagination,
|
|
||||||
Icon,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
|
||||||
import { users, groups, apps, licensing, overview } from "stores/portal"
|
|
||||||
import AssignmentModal from "./_components/AssignmentModal.svelte"
|
|
||||||
import { roles } from "stores/backend"
|
|
||||||
import { API } from "api"
|
|
||||||
import { fetchData } from "@budibase/frontend-core"
|
|
||||||
|
|
||||||
let assignmentModal
|
|
||||||
let appGroups
|
|
||||||
let appUsers
|
|
||||||
|
|
||||||
$: app = $overview.selectedApp
|
|
||||||
$: devAppId = app.devId
|
|
||||||
$: prodAppId = apps.getProdAppID(app.devId)
|
|
||||||
$: usersFetch = fetchData({
|
|
||||||
API,
|
|
||||||
datasource: {
|
|
||||||
type: "user",
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
query: {
|
|
||||||
appId: apps.getProdAppID(devAppId),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
$: appUsers = $usersFetch.rows
|
|
||||||
$: appGroups = $groups.filter(group => {
|
|
||||||
if (!group.roles) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return groups.actions.getGroupAppIds(group).includes(prodAppId)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function removeUser(user) {
|
|
||||||
// Remove the user role
|
|
||||||
const filteredRoles = { ...user.roles }
|
|
||||||
delete filteredRoles[prodAppId]
|
|
||||||
await users.save({
|
|
||||||
...user,
|
|
||||||
roles: {
|
|
||||||
...filteredRoles,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await usersFetch.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeGroup(group) {
|
|
||||||
await groups.actions.removeApp(group._id, prodAppId)
|
|
||||||
await groups.actions.init()
|
|
||||||
await usersFetch.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateUserRole(role, user) {
|
|
||||||
user.roles[prodAppId] = role
|
|
||||||
await users.save(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateGroupRole(role, group) {
|
|
||||||
await groups.actions.addApp(group._id, prodAppId, role)
|
|
||||||
await usersFetch.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
try {
|
|
||||||
await roles.fetch()
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Layout noPadding>
|
|
||||||
{#if appGroups.length || appUsers.length}
|
|
||||||
<div>
|
|
||||||
<Heading>Access</Heading>
|
|
||||||
<div class="subtitle">
|
|
||||||
<Body size="S">
|
|
||||||
Assign users and groups to your app and define their access here
|
|
||||||
</Body>
|
|
||||||
<Button on:click={assignmentModal.show} icon="User" cta>
|
|
||||||
Assign access
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if $licensing.groupsEnabled && appGroups.length}
|
|
||||||
<List title="User Groups">
|
|
||||||
{#each appGroups as group}
|
|
||||||
<ListItem
|
|
||||||
title={group.name}
|
|
||||||
icon={group.icon}
|
|
||||||
iconBackground={group.color}
|
|
||||||
>
|
|
||||||
<RoleSelect
|
|
||||||
on:change={e => updateGroupRole(e.detail, group)}
|
|
||||||
autoWidth
|
|
||||||
quiet
|
|
||||||
value={group.roles[
|
|
||||||
groups.actions.getGroupAppIds(group).find(x => x === prodAppId)
|
|
||||||
]}
|
|
||||||
allowPublic={false}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
on:click={() => removeGroup(group)}
|
|
||||||
hoverable
|
|
||||||
size="S"
|
|
||||||
name="Close"
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
{/each}
|
|
||||||
</List>
|
|
||||||
{/if}
|
|
||||||
{#if appUsers.length}
|
|
||||||
<div>
|
|
||||||
<List title="Users">
|
|
||||||
{#each appUsers as user}
|
|
||||||
<ListItem title={user.email} avatar>
|
|
||||||
<RoleSelect
|
|
||||||
on:change={e => updateUserRole(e.detail, user)}
|
|
||||||
autoWidth
|
|
||||||
quiet
|
|
||||||
value={user.roles[
|
|
||||||
Object.keys(user.roles).find(x => x === prodAppId)
|
|
||||||
]}
|
|
||||||
allowPublic={false}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
on:click={() => removeUser(user)}
|
|
||||||
hoverable
|
|
||||||
size="S"
|
|
||||||
name="Close"
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
{/each}
|
|
||||||
</List>
|
|
||||||
<div class="pagination">
|
|
||||||
<Pagination
|
|
||||||
page={$usersFetch.pageNumber + 1}
|
|
||||||
hasPrevPage={$usersFetch.hasPrevPage}
|
|
||||||
hasNextPage={$usersFetch.hasNextPage}
|
|
||||||
goToPrevPage={$usersFetch.loading ? null : usersFetch.prevPage}
|
|
||||||
goToNextPage={$usersFetch.loading ? null : usersFetch.nextPage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<div class="align">
|
|
||||||
<Layout gap="S">
|
|
||||||
<Heading>No users assigned</Heading>
|
|
||||||
<div class="opacity">
|
|
||||||
<Body size="S">
|
|
||||||
Assign users/groups to your app and set their access here
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
<div class="padding">
|
|
||||||
<Button on:click={() => assignmentModal.show()} cta icon="UserArrow">
|
|
||||||
Assign access
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
<Modal bind:this={assignmentModal}>
|
|
||||||
<AssignmentModal {app} {appUsers} on:update={usersFetch.refresh} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.padding {
|
|
||||||
margin-top: var(--spacing-m);
|
|
||||||
}
|
|
||||||
.opacity {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.subtitle {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const rolesContext = getContext("roles")
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let row
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<RoleSelect
|
||||||
|
{value}
|
||||||
|
quiet
|
||||||
|
allowRemove
|
||||||
|
allowPublic={false}
|
||||||
|
on:change={e => rolesContext.updateGroupRole(e.detail, row._id)}
|
||||||
|
on:remove={() => rolesContext.removeGroupRole(row._id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const rolesContext = getContext("roles")
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let row
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<RoleSelect
|
||||||
|
{value}
|
||||||
|
quiet
|
||||||
|
allowRemove
|
||||||
|
allowPublic={false}
|
||||||
|
on:change={e => rolesContext.updateUserRole(e.detail, row._id)}
|
||||||
|
on:remove={() => rolesContext.removeUserRole(row._id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,245 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Layout,
|
||||||
|
Heading,
|
||||||
|
Body,
|
||||||
|
Button,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Modal,
|
||||||
|
notifications,
|
||||||
|
Pagination,
|
||||||
|
Divider,
|
||||||
|
Icon,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { onMount, setContext } from "svelte"
|
||||||
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
|
import { users, groups, apps, licensing, overview } from "stores/portal"
|
||||||
|
import AssignmentModal from "./_components/AssignmentModal.svelte"
|
||||||
|
import { roles } from "stores/backend"
|
||||||
|
import { API } from "api"
|
||||||
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
|
import UserRoleRenderer from "./_components/UserRoleRenderer.svelte"
|
||||||
|
import GroupRoleRenderer from "./_components/GroupRoleRenderer.svelte"
|
||||||
|
|
||||||
|
const userSchema = {
|
||||||
|
email: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
userAppRole: {
|
||||||
|
displayName: "Access",
|
||||||
|
width: "150px",
|
||||||
|
borderLeft: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const groupSchema = {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
groupAppRole: {
|
||||||
|
displayName: "Access",
|
||||||
|
width: "150px",
|
||||||
|
borderLeft: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const customRenderers = [
|
||||||
|
{
|
||||||
|
column: "userAppRole",
|
||||||
|
component: UserRoleRenderer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
column: "groupAppRole",
|
||||||
|
component: GroupRoleRenderer,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let assignmentModal
|
||||||
|
let appGroups
|
||||||
|
let appUsers
|
||||||
|
|
||||||
|
$: app = $overview.selectedApp
|
||||||
|
$: devAppId = app.devId
|
||||||
|
$: prodAppId = apps.getProdAppID(app.devId)
|
||||||
|
$: usersFetch = fetchData({
|
||||||
|
API,
|
||||||
|
datasource: {
|
||||||
|
type: "user",
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
query: {
|
||||||
|
appId: apps.getProdAppID(devAppId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
$: appUsers = getAppUsers($usersFetch.rows, prodAppId)
|
||||||
|
$: appGroups = getAppGroups($groups, prodAppId)
|
||||||
|
|
||||||
|
const getAppUsers = (users, appId) => {
|
||||||
|
return users.map(user => ({
|
||||||
|
...user,
|
||||||
|
userAppRole: user.roles[Object.keys(user.roles).find(x => x === appId)],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAppGroups = (allGroups, appId) => {
|
||||||
|
return allGroups
|
||||||
|
.filter(group => {
|
||||||
|
if (!group.roles) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return groups.actions.getGroupAppIds(group).includes(appId)
|
||||||
|
})
|
||||||
|
.map(group => ({
|
||||||
|
...group,
|
||||||
|
groupAppRole:
|
||||||
|
group.roles[
|
||||||
|
groups.actions.getGroupAppIds(group).find(x => x === appId)
|
||||||
|
],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUserRole = async (role, userId) => {
|
||||||
|
const user = $usersFetch.rows.find(user => user._id === userId)
|
||||||
|
if (!user) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.roles[prodAppId] = role
|
||||||
|
await users.save(user)
|
||||||
|
await usersFetch.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeUserRole = async userId => {
|
||||||
|
const user = $usersFetch.rows.find(user => user._id === userId)
|
||||||
|
if (!user) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const filteredRoles = { ...user.roles }
|
||||||
|
delete filteredRoles[prodAppId]
|
||||||
|
await users.save({
|
||||||
|
...user,
|
||||||
|
roles: {
|
||||||
|
...filteredRoles,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await usersFetch.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateGroupRole = async (role, groupId) => {
|
||||||
|
const group = $groups.find(group => group._id === groupId)
|
||||||
|
if (!group) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await groups.actions.addApp(group._id, prodAppId, role)
|
||||||
|
await usersFetch.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeGroupRole = async groupId => {
|
||||||
|
const group = $groups.find(group => group._id === groupId)
|
||||||
|
if (!group) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await groups.actions.removeApp(group._id, prodAppId)
|
||||||
|
await usersFetch.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext("roles", {
|
||||||
|
updateUserRole,
|
||||||
|
removeUserRole,
|
||||||
|
updateGroupRole,
|
||||||
|
removeGroupRole,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
await roles.fetch()
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Layout noPadding>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading>Access</Heading>
|
||||||
|
<Body>Assign users to your app and set their access</Body>
|
||||||
|
</Layout>
|
||||||
|
<Divider />
|
||||||
|
<Layout noPadding gap="L">
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<div class="title">
|
||||||
|
<Heading size="S">Users</Heading>
|
||||||
|
<Button secondary on:click={assignmentModal.show}>Assign user</Button>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
customPlaceholder
|
||||||
|
data={appUsers}
|
||||||
|
schema={userSchema}
|
||||||
|
allowEditRows={false}
|
||||||
|
{customRenderers}
|
||||||
|
>
|
||||||
|
<div class="placeholder" slot="placeholder">
|
||||||
|
<Heading size="S">You have no users assigned yet</Heading>
|
||||||
|
</div>
|
||||||
|
</Table>
|
||||||
|
{#if $usersFetch.hasPrevPage || $usersFetch.hasNextPage}
|
||||||
|
<div class="pagination">
|
||||||
|
<Pagination
|
||||||
|
page={$usersFetch.pageNumber + 1}
|
||||||
|
hasPrevPage={$usersFetch.hasPrevPage}
|
||||||
|
hasNextPage={$usersFetch.hasNextPage}
|
||||||
|
goToPrevPage={$usersFetch.loading ? null : usersFetch.prevPage}
|
||||||
|
goToNextPage={$usersFetch.loading ? null : usersFetch.nextPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
{#if $licensing.groupsEnabled && appGroups.length}
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<div class="title">
|
||||||
|
<Heading size="S">Groups</Heading>
|
||||||
|
<Button secondary on:click={assignmentModal.show}>
|
||||||
|
Assign group
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
customPlaceholder
|
||||||
|
data={appGroups}
|
||||||
|
schema={groupSchema}
|
||||||
|
allowEditRows={false}
|
||||||
|
{customRenderers}
|
||||||
|
>
|
||||||
|
<div class="placeholder" slot="placeholder">
|
||||||
|
<Heading size="S">You have no groups assigned yet</Heading>
|
||||||
|
</div>
|
||||||
|
</Table>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Modal bind:this={assignmentModal}>
|
||||||
|
<AssignmentModal {app} {appUsers} on:update={usersFetch.refresh} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: calc(-1 * var(--spacing-s));
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue