mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
fix: solve invite add conflict + deprecate invites (#3594)
* fix: solver invite add conflict + deprecate invites * fix: sqlx build * fix: npm run check * patch: deprecate invite from cloud as well --------- Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM workspace_invite WHERE workspace_id = $1 AND email = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0d7ba88a9810e434aa00fd63bbf416cbe222f2c67ccc8aa92e651c2bea4c2d7b"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS (SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7142222bee1f60bc56752e377b96ac6e4e981ea219c173146088faf1ad9f3822"
|
||||
}
|
||||
@@ -722,6 +722,16 @@ async fn edit_auto_invite(
|
||||
|
||||
for user in users_to_auto_add.as_ref().unwrap() {
|
||||
auto_add_user(&user.email, &w_id, &operator, &mut tx).await?;
|
||||
send_email_if_possible(
|
||||
&format!("Added to Windmill's workspace: {w_id}"),
|
||||
&format!(
|
||||
"You have been granted access to Windmill's workspace {w_id} by {email}.
|
||||
|
||||
Access the workspace at {}/?workspace={w_id}",
|
||||
BASE_URL.read().await.clone()
|
||||
),
|
||||
&user.email,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sqlx::query!(
|
||||
@@ -1936,6 +1946,22 @@ async fn invite_user(
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let already_in_workspace = sqlx::query_scalar!(
|
||||
"SELECT EXISTS (SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
&w_id,
|
||||
nu.email
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if already_in_workspace {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"user with email {} already exists in workspace {}",
|
||||
nu.email, w_id
|
||||
)));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_invite
|
||||
(workspace_id, email, is_admin, operator)
|
||||
@@ -2046,6 +2072,14 @@ async fn add_user(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM workspace_invite WHERE workspace_id = $1 AND email = $2",
|
||||
&w_id,
|
||||
nu.email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query_as!(
|
||||
Group,
|
||||
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
jobKindsCat="jobs"
|
||||
jobKinds="all"
|
||||
user={null}
|
||||
label={null}
|
||||
folder={null}
|
||||
success="running"
|
||||
argFilter={undefined}
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
}
|
||||
|
||||
let nbInviteDisplayed = 50
|
||||
|
||||
let showInvites = false
|
||||
$: showInvites = invites?.length > 0 || (auto_invite_domain != undefined && !autoAdd)
|
||||
</script>
|
||||
|
||||
<SearchItems
|
||||
@@ -122,18 +125,22 @@
|
||||
f={(x) => x.email + ' ' + x.name + ' ' + x.company}
|
||||
/>
|
||||
<div class="flex flex-col gap-4 my-8">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class=" text-primary text-lg font-semibold"> Members & Invites </div>
|
||||
<div class="text-tertiary text-xs">
|
||||
Add members to your workspace and manage their roles. You can also invite or auto-invites users to join your workspace.
|
||||
<a href="https://www.windmill.dev/docs/core_concepts/roles_and_permissions" target="_blank" class="text-blue-500">Learn more</a>.
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-tertiary text-xs">
|
||||
Add members to your workspace and manage their roles. You can also auto-add users to join your
|
||||
workspace.
|
||||
<a
|
||||
href="https://www.windmill.dev/docs/core_concepts/roles_and_permissions"
|
||||
target="_blank"
|
||||
class="text-blue-500">Learn more</a
|
||||
>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<PageHeader
|
||||
title="Members ({filteredUsers?.length ?? users?.length ?? ''})"
|
||||
primary={false}
|
||||
primary={true}
|
||||
tooltip="Manage users manually or enable SSO authentication."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/authentification"
|
||||
/>
|
||||
@@ -142,7 +149,135 @@
|
||||
<input placeholder="Filter members" bind:value={userFilter} class="input !pl-8" />
|
||||
<Search class="absolute left-2" size={14} />
|
||||
|
||||
<AddUser on:new={listUsers} />
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button
|
||||
color={auto_invite_domain != undefined ? 'green' : 'red'}
|
||||
variant="border"
|
||||
size="xs"
|
||||
nonCaptureEvent={true}
|
||||
startIcon={{ icon: Mails }}
|
||||
>Auto-{showInvites && !autoAdd ? 'invite' : 'add'}: {auto_invite_domain != undefined
|
||||
? 'ON'
|
||||
: 'OFF'}
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="text-sm leading-6 font-semibold">
|
||||
{isCloudHosted()
|
||||
? `Auto-add anyone from ${
|
||||
auto_invite_domain != undefined ? auto_invite_domain : domain
|
||||
}`
|
||||
: `Auto-add anyone joining the instance`}
|
||||
</span>
|
||||
|
||||
{#if showInvites}
|
||||
<span class="text-xs mb-1 leading-6 pt-2"
|
||||
>Mode <Tooltip>Whether to invite or add users directly to the workspace.</Tooltip>
|
||||
</span>
|
||||
<ToggleButtonGroup
|
||||
selected={autoAdd ? 'add' : 'invite'}
|
||||
on:selected={async (e) => {
|
||||
if (auto_invite_domain != undefined) {
|
||||
await removeAllInvitesFromDomain()
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
operator: operatorOnly ?? false,
|
||||
invite_all: !isCloudHosted(),
|
||||
auto_add: e.detail === 'add'
|
||||
}
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
listUsers()
|
||||
} else {
|
||||
autoAdd = e.detail === 'add'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="invite" size="xs" label="Auto-invite" />
|
||||
<ToggleButton value="add" size="xs" label="Auto-add" />
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
|
||||
<span class="text-xs mb-1 leading-6 pt-2"
|
||||
>Role <Tooltip>Role of the auto-added users</Tooltip></span
|
||||
>
|
||||
<ToggleButtonGroup
|
||||
selected={operatorOnly ? 'operator' : 'developer'}
|
||||
on:selected={async (e) => {
|
||||
if (auto_invite_domain != undefined) {
|
||||
await removeAllInvitesFromDomain()
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
operator: e.detail === 'operator',
|
||||
invite_all: !isCloudHosted(),
|
||||
auto_add: showInvites ? autoAdd ?? false : true
|
||||
}
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
listUsers()
|
||||
} else {
|
||||
operatorOnly = e.detail === 'operator'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
size="xs"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
/>
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
size="xs"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
<div class="pt-2">
|
||||
<Toggle
|
||||
size="xs"
|
||||
checked={auto_invite_domain != undefined}
|
||||
on:change={async (e) => {
|
||||
await removeAllInvitesFromDomain()
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: e.detail
|
||||
? {
|
||||
operator: operatorOnly ?? false,
|
||||
invite_all: !isCloudHosted(),
|
||||
auto_add: showInvites ? autoAdd ?? false : true
|
||||
}
|
||||
: { operator: undefined, auto_add: undefined }
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
listUsers()
|
||||
}}
|
||||
disabled={isCloudHosted() && !allowedAutoDomain}
|
||||
options={{
|
||||
right: 'Enabled'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if isCloudHosted() && !allowedAutoDomain}
|
||||
<div class="text-red-400 text-xs">{domain} domain not allowed for auto-add</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Popup>
|
||||
<AddUser
|
||||
on:new={() => {
|
||||
listUsers()
|
||||
listInvites()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -175,11 +310,16 @@
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if filteredUsers}
|
||||
{#each filteredUsers.slice(0, nbDisplayed) as { email, username, is_admin, operator, disabled } (email)}
|
||||
{#each filteredUsers.slice(0, nbDisplayed) as { email, username, is_admin, operator, disabled } (email)}
|
||||
<tr class="!hover:bg-surface-hover">
|
||||
<Cell first>{truncate(email, 20)}</Cell>
|
||||
<Cell>{truncate(username, 30)}</Cell>
|
||||
<Cell>{#if usage?.[email] != undefined}{usage?.[email]}{:else}<Loader2 size={14} class="animate-spin" />{/if}</Cell>
|
||||
<Cell
|
||||
>{#if usage?.[email] != undefined}{usage?.[email]}{:else}<Loader2
|
||||
size={14}
|
||||
class="animate-spin"
|
||||
/>{/if}</Cell
|
||||
>
|
||||
<Cell>
|
||||
<div class="flex gap-1">
|
||||
{#if disabled}
|
||||
@@ -297,232 +437,113 @@
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<PageHeader
|
||||
title="Invites ({invites.length ?? ''})"
|
||||
primary={false}
|
||||
tooltip="Manage invites on your workspace."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/authentification#adding-users-to-a-workspace"
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button
|
||||
color={auto_invite_domain != undefined ? 'green' : 'red'}
|
||||
variant="border"
|
||||
size="xs"
|
||||
nonCaptureEvent={true}
|
||||
startIcon={{ icon: Mails }}
|
||||
>Auto-invite: {auto_invite_domain != undefined ? 'ON' : 'OFF'}
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col items-start">
|
||||
<!-- <span class="text-xs mb-1 leading-6">Who </span>
|
||||
<span class="text-sm" /> -->
|
||||
{#if showInvites}
|
||||
<PageHeader
|
||||
title="Invites ({invites.length ?? ''})"
|
||||
primary={false}
|
||||
tooltip="Manage invites on your workspace."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/authentification#adding-users-to-a-workspace"
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<InviteUser on:new={listInvites} />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<span class="text-sm leading-6 font-semibold">
|
||||
{isCloudHosted()
|
||||
? `Auto-invite anyone from ${
|
||||
auto_invite_domain != undefined ? auto_invite_domain : domain
|
||||
}`
|
||||
: 'Auto-invite anyone joining the instance'}
|
||||
</span>
|
||||
<span class="text-xs mb-1 leading-6 pt-2"
|
||||
>Mode <Tooltip>Whether to invite or add users directly to the workspace.</Tooltip>
|
||||
</span>
|
||||
<ToggleButtonGroup
|
||||
selected={autoAdd ? 'add' : 'invite'}
|
||||
on:selected={async (e) => {
|
||||
if (auto_invite_domain != undefined) {
|
||||
await removeAllInvitesFromDomain()
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
operator: operatorOnly ?? false,
|
||||
invite_all: !isCloudHosted(),
|
||||
auto_add: e.detail === 'add'
|
||||
}
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
listUsers()
|
||||
} else {
|
||||
autoAdd = e.detail === 'add'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="invite" size="xs" label="Auto-invite" />
|
||||
<ToggleButton value="add" size="xs" label="Auto-add" />
|
||||
</ToggleButtonGroup>
|
||||
<div>
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Email</Cell>
|
||||
<Cell head>Role</Cell>
|
||||
<Cell head last><span class="sr-only">Actions</span></Cell>
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if invites?.length > 0}
|
||||
{#each invites.slice(0, nbInviteDisplayed) as { email, is_admin, operator }}
|
||||
<Row>
|
||||
<Cell first>{email}</Cell>
|
||||
<Cell>
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={is_admin ? 'admin' : operator ? 'operator' : 'developer'}
|
||||
on:selected={async (e) => {
|
||||
const body =
|
||||
e.detail == 'admin'
|
||||
? { is_admin: true, operator: false }
|
||||
: e.detail == 'operator'
|
||||
? { is_admin: false, operator: true }
|
||||
: { is_admin: false, operator: false }
|
||||
await WorkspaceService.inviteUser({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
email,
|
||||
...body
|
||||
}
|
||||
})
|
||||
listUsers()
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
size="xs"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
/>
|
||||
|
||||
<span class="text-xs mb-1 leading-6 pt-2"
|
||||
>Role <Tooltip>Role of the auto-invited users</Tooltip></span
|
||||
>
|
||||
<ToggleButtonGroup
|
||||
selected={operatorOnly ? 'operator' : 'developer'}
|
||||
on:selected={async (e) => {
|
||||
if (auto_invite_domain != undefined) {
|
||||
await removeAllInvitesFromDomain()
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
operator: e.detail === 'operator',
|
||||
invite_all: !isCloudHosted(),
|
||||
auto_add: autoAdd ?? false
|
||||
}
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
listUsers()
|
||||
} else {
|
||||
operatorOnly = e.detail === 'operator'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
size="xs"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
/>
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
size="xs"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
<div class="pt-2">
|
||||
<Toggle
|
||||
size="xs"
|
||||
checked={auto_invite_domain != undefined}
|
||||
on:change={async (e) => {
|
||||
await removeAllInvitesFromDomain()
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: e.detail
|
||||
? {
|
||||
operator: operatorOnly ?? false,
|
||||
invite_all: !isCloudHosted(),
|
||||
auto_add: autoAdd ?? false
|
||||
}
|
||||
: { operator: undefined, auto_add: undefined }
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
listUsers()
|
||||
}}
|
||||
disabled={isCloudHosted() && !allowedAutoDomain}
|
||||
options={{
|
||||
right: 'Enabled'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if isCloudHosted() && !allowedAutoDomain}
|
||||
<div class="text-red-400 text-xs">{domain} domain not allowed for auto-invite</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Popup>
|
||||
<InviteUser on:new={listInvites} />
|
||||
</div>
|
||||
</PageHeader>
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
size="xs"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
/>
|
||||
|
||||
<div>
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
<Cell head first>Email</Cell>
|
||||
<Cell head>Role</Cell>
|
||||
<Cell head last><span class="sr-only">Actions</span></Cell>
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if invites?.length > 0}
|
||||
{#each invites.slice(0, nbInviteDisplayed) as { email, is_admin, operator }}
|
||||
<Row>
|
||||
<Cell first>{email}</Cell>
|
||||
<Cell>
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={is_admin ? 'admin' : operator ? 'operator' : 'developer'}
|
||||
on:selected={async (e) => {
|
||||
const body =
|
||||
e.detail == 'admin'
|
||||
? { is_admin: true, operator: false }
|
||||
: e.detail == 'operator'
|
||||
? { is_admin: false, operator: true }
|
||||
: { is_admin: false, operator: false }
|
||||
await WorkspaceService.inviteUser({
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
size="xs"
|
||||
label="Admin"
|
||||
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell last>
|
||||
<button
|
||||
class="ml-2 text-red-500"
|
||||
on:click={async () => {
|
||||
await WorkspaceService.deleteInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
email,
|
||||
...body
|
||||
is_admin,
|
||||
operator
|
||||
}
|
||||
})
|
||||
listUsers()
|
||||
listInvites()
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
size="xs"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
size="xs"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
size="xs"
|
||||
label="Admin"
|
||||
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell last>
|
||||
<button
|
||||
class="ml-2 text-red-500"
|
||||
on:click={async () => {
|
||||
await WorkspaceService.deleteInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
email,
|
||||
is_admin,
|
||||
operator
|
||||
}
|
||||
})
|
||||
listInvites()
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-8">
|
||||
<div class="text-xs text-secondary"> No invites yet </div>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{#if invites && invites?.length > 50 && nbInviteDisplayed < invites.length}
|
||||
<span class="text-xs"
|
||||
>{nbInviteDisplayed} invites out of {invites.length}
|
||||
<button class="ml-4" on:click={() => (nbInviteDisplayed += 50)}>load 50 more</button></span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
Cancel
|
||||
</button>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-8">
|
||||
<div class="text-xs text-secondary"> No invites yet </div>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{#if invites && invites?.length > 50 && nbInviteDisplayed < invites.length}
|
||||
<span class="text-xs"
|
||||
>{nbInviteDisplayed} invites out of {invites.length}
|
||||
<button class="ml-4" on:click={() => (nbInviteDisplayed += 50)}>load 50 more</button></span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ConfirmationModal
|
||||
open={Boolean(deleteConfirmedCallback)}
|
||||
|
||||
Reference in New Issue
Block a user