mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 00:02:35 +00:00
feat: auto-invite from same domain
This commit is contained in:
@@ -128,9 +128,9 @@
|
||||
deleteConfirmedCallback = async () => {
|
||||
await UserService.globalUserDelete({ email })
|
||||
sendUserToast(`User ${email} removed`)
|
||||
listUsers()
|
||||
await listUsers()
|
||||
}
|
||||
openConfirmation = true
|
||||
openConfirmation = false
|
||||
}}>remove</button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { faDashboard, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { AppService, Policy } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
@@ -60,7 +60,9 @@
|
||||
</div>
|
||||
</Path>
|
||||
|
||||
<Button on:click={() => createApp()}>Create app</Button>
|
||||
<div slot="actions">
|
||||
<Button on:click={() => createApp()}>Create app</Button>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import type { ListableApp } from '$lib/gen'
|
||||
import { AppService, type ListableApp } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { faEdit, faEye } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faEdit, faEye, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Button from '../button/Button.svelte'
|
||||
import Row from './Row.svelte'
|
||||
|
||||
@@ -12,6 +13,8 @@
|
||||
export let starred: boolean
|
||||
|
||||
let { summary, path, extra_perms, canWrite, workspace_id } = app
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<Row
|
||||
@@ -28,38 +31,49 @@
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<span class="hidden md:inline-flex gap-x-1">
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/apps/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
href="/apps/get/{path}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</span>
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View app',
|
||||
icon: faEye,
|
||||
href: `/apps/get/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Delete',
|
||||
icon: faTrashAlt,
|
||||
action: async () => {
|
||||
await AppService.deleteApp({ workspace: $workspaceStore ?? '', path })
|
||||
dispatch('change')
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !canWrite
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/apps/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/apps/get/{path}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -55,7 +55,56 @@
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<span class="hidden md:inline-flex gap-x-1">
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/flows/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/flows/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/flows/get/{path}"
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
href="/flows/run/{path}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View flow',
|
||||
@@ -102,51 +151,5 @@
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/flows/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/flows/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/flows/get/{path}"
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
href="/flows/run/{path}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -29,19 +29,19 @@
|
||||
>
|
||||
<RowIcon {kind} />
|
||||
|
||||
<div class="w-full">
|
||||
<div class="text-gray-900 flex-wrap text-md font-semibold mb-1">
|
||||
<div class="w-full min-w-0 ">
|
||||
<div class="text-gray-900 flex-wrap text-md font-semibold mb-1 truncate">
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{!summary || summary.length == 0 ? path : summary}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-gray-600 text-xs ">
|
||||
<div class="text-gray-600 text-xs truncate">
|
||||
{path}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-96 flex flex-row max-w-xs gap-1 items-start flex-wrap">
|
||||
<div class="w-96 hidden lg:flex flex-row max-w-xs gap-1 items-start flex-wrap">
|
||||
<slot name="badges" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -73,7 +73,55 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<span class="hidden md:inline-flex gap-x-1">
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/scripts/edit/{hash}?step=2"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/scripts/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/scripts/get/{hash}"
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
href="/scripts/run/{hash}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</span>
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View script',
|
||||
@@ -126,51 +174,5 @@
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/scripts/edit/{hash}?step=2"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/scripts/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/scripts/get/{hash}"
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
href="/scripts/run/{hash}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -38,7 +38,7 @@ export const userWorkspaces: Readable<
|
||||
([store, superadmin]) => {
|
||||
const originalWorkspaces = (store?.workspaces ?? []);
|
||||
if (superadmin) {
|
||||
return [...originalWorkspaces, {
|
||||
return [...originalWorkspaces.filter((x) => x.id != 'starter' && x.id != 'admins'), {
|
||||
id: "admins",
|
||||
name: "Admins",
|
||||
username: "superadmin",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
})
|
||||
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
|
||||
workspaceStore.set(workspace_id)
|
||||
goto($page.url.searchParams.get('rd') ?? '/scripts')
|
||||
goto($page.url.searchParams.get('rd') ?? '/')
|
||||
}
|
||||
|
||||
async function validateName(username: string): Promise<void> {
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
import { sendUserToast, validateUsername } from '$lib/utils'
|
||||
import { logoutWithRedirect } from '$lib/logout'
|
||||
import { page } from '$app/stores'
|
||||
import { usersWorkspaceStore, workspaceStore } from '$lib/stores'
|
||||
import { userStore, usersWorkspaceStore, workspaceStore } from '$lib/stores'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
|
||||
const rd = $page.url.searchParams.get('rd')
|
||||
|
||||
let id = ''
|
||||
let name = ''
|
||||
let username = ''
|
||||
let domain = ''
|
||||
|
||||
let errorId = ''
|
||||
let errorUser = ''
|
||||
@@ -39,11 +39,17 @@
|
||||
requestBody: {
|
||||
id,
|
||||
name,
|
||||
username,
|
||||
domain
|
||||
username
|
||||
}
|
||||
})
|
||||
if (auto_invite) {
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: id,
|
||||
requestBody: { set: true }
|
||||
})
|
||||
}
|
||||
sendUserToast(`Successfully created workspace id: ${id}`)
|
||||
|
||||
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
|
||||
workspaceStore.set(id)
|
||||
goto(rd ?? '/')
|
||||
@@ -78,6 +84,16 @@
|
||||
}
|
||||
username = username.toLowerCase()
|
||||
})
|
||||
|
||||
WorkspaceService.isDomainAllowed().then((x) => {
|
||||
isDomainAllowed = x
|
||||
})
|
||||
|
||||
let isDomainAllowed = false
|
||||
|
||||
$: domain = $usersWorkspaceStore?.email.split('@')[1]
|
||||
|
||||
let auto_invite = false
|
||||
</script>
|
||||
|
||||
<CenteredModal title="New Workspace">
|
||||
@@ -99,6 +115,14 @@
|
||||
{/if}
|
||||
<input type="text" bind:value={username} on:keyup={handleKeyUp} class="mt-1" />
|
||||
</label>
|
||||
<Toggle
|
||||
disabled={!isDomainAllowed}
|
||||
bind:checked={auto_invite}
|
||||
options={{ right: `Auto invite users with the same domain (${domain})` }}
|
||||
/>
|
||||
{#if !isDomainAllowed}
|
||||
<div class="text-gray-600 text-sm mb-4 mt-2">{domain} domain not allowed for auto-invite</div>
|
||||
{/if}
|
||||
<div class="flex flex-row justify-between pt-4 gap-x-1">
|
||||
<Button variant="border" size="sm" href="/user/workspaces"
|
||||
>← Back to workspaces</Button
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
import { goto } from '$app/navigation'
|
||||
import InviteUser from '$lib/components/InviteUser.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import { Button, Skeleton } from '$lib/components/common'
|
||||
import { Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { faScroll, faBarsStaggered } from '@fortawesome/free-solid-svg-icons'
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
@@ -36,6 +36,7 @@
|
||||
let scriptPath: string
|
||||
let initialPath: string
|
||||
let team_name: string | undefined
|
||||
let auto_invite_domain: string | undefined
|
||||
let itemKind: 'flow' | 'script' = 'flow'
|
||||
|
||||
// function getDropDownItems(username: string): DropdownItem[] {
|
||||
@@ -71,9 +72,10 @@
|
||||
sendUserToast(`slack command script set to ${scriptPath}`)
|
||||
}
|
||||
|
||||
async function loadSlack(): Promise<void> {
|
||||
async function loadSettings(): Promise<void> {
|
||||
const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
|
||||
team_name = settings.slack_name
|
||||
auto_invite_domain = settings.auto_invite_domain
|
||||
scriptPath = (settings.slack_command_script ?? '').split('/').slice(1).join('/')
|
||||
initialPath = scriptPath
|
||||
}
|
||||
@@ -86,11 +88,20 @@
|
||||
invites = await WorkspaceService.listPendingInvites({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
let allowedAutoDomain = false
|
||||
|
||||
async function getDisallowedAutoDomain() {
|
||||
allowedAutoDomain = await WorkspaceService.isDomainAllowed()
|
||||
}
|
||||
|
||||
$: domain = $userStore?.email.split('@')[1]
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
getDisallowedAutoDomain()
|
||||
listUsers()
|
||||
listInvites()
|
||||
loadSlack()
|
||||
loadSettings()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -210,9 +221,42 @@
|
||||
</TableCustom>
|
||||
</div>
|
||||
<div class="mt-20" />
|
||||
<PageHeader title="Auto Invite" primary={false} />
|
||||
<div class="flex gap-2">
|
||||
{#if auto_invite_domain != domain}
|
||||
<Button
|
||||
disabled={!allowedAutoDomain}
|
||||
on:click={async () => {
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { set: true }
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
}}>Set auto-invite to {domain}</Button
|
||||
>
|
||||
{/if}
|
||||
{#if auto_invite_domain}
|
||||
<Button
|
||||
on:click={async () => {
|
||||
await WorkspaceService.editAutoInvite({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { set: false }
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
}}>Unset auto-invite from {auto_invite_domain}</Button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !allowedAutoDomain}
|
||||
<div class="text-red-400 text-sm mb-2">{domain} domain not allowed for auto-invite</div>
|
||||
{/if}
|
||||
<div class="mt-20" />
|
||||
<PageHeader title="Slack integration" primary={false} />
|
||||
<p class="text-xs text-gray-700 my-1">
|
||||
Status: {#if team_name}Connected to slack workspace {team_name}{:else}Not connected{/if}
|
||||
Status: {#if team_name}Connected to slack workspace <Badge>{team_name}</Badge>{:else}Not
|
||||
connected{/if}
|
||||
</p>
|
||||
|
||||
{#if team_name}
|
||||
@@ -226,7 +270,7 @@
|
||||
await OauthService.disconnectSlack({
|
||||
workspace: $workspaceStore ?? ''
|
||||
})
|
||||
loadSlack()
|
||||
loadSettings()
|
||||
sendUserToast('Disconnected Slack')
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user