feat: auto-invite from same domain

This commit is contained in:
Ruben Fiszel
2022-12-12 19:45:25 +01:00
parent 42ea1a6468
commit db4fcfdbec
18 changed files with 520 additions and 204 deletions

View File

@@ -1 +1,2 @@
-- Add down migration script here
ALTER TABLE workspace_settings DROP COLUMN auto_invite_domain;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE workspace_settings ADD COLUMN auto_invite_domain VARCHAR(50);
ALTER TABLE workspace DROP COLUMN domain;

View File

@@ -330,19 +330,14 @@
"ordinal": 2,
"type_info": "Varchar"
},
{
"name": "domain",
"ordinal": 3,
"type_info": "Varchar"
},
{
"name": "deleted",
"ordinal": 4,
"ordinal": 3,
"type_info": "Bool"
},
{
"name": "premium",
"ordinal": 5,
"ordinal": 4,
"type_info": "Bool"
}
],
@@ -350,7 +345,6 @@
false,
false,
false,
true,
false,
false
],
@@ -405,6 +399,11 @@
"name": "slack_email",
"ordinal": 4,
"type_info": "Varchar"
},
{
"name": "auto_invite_domain",
"ordinal": 5,
"type_info": "Varchar"
}
],
"nullable": [
@@ -412,7 +411,8 @@
true,
true,
true,
false
false,
true
],
"parameters": {
"Left": [
@@ -855,6 +855,19 @@
},
"query": "DELETE FROM workspace_invite WHERE\n workspace_id = $1 AND email = $2 AND is_admin = $3"
},
"2c6c2358fc24d92db179306a0f18a83d71bf15d8dc76502c451634dff35715a6": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
}
},
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin)\n VALUES ($1, $2, false)\n ON CONFLICT DO NOTHING"
},
"2e11e3ef361c41e6e055dd4805cb9d5e45eaa486e35fc5f01dae311189d6800f": {
"describe": {
"columns": [
@@ -1120,6 +1133,20 @@
},
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3\n "
},
"4815068e6d833e918613d324b5d694c479d039576cabb88d5be8894f171c3b4e": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
}
},
"query": "INSERT INTO workspace\n (id, name, owner)\n VALUES ($1, $2, $3)"
},
"486f181a9ced2bdc7c8d93da22c9d3e229ef106174bff2c472dbe82622f382b6": {
"describe": {
"columns": [],
@@ -1181,21 +1208,6 @@
},
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)\n WHERE id = $2\n "
},
"4e8ff055a80cc2e6b7fd6653c0c8e9a0e4b223a4bdeab0cf43dc79ce10f635b9": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
}
},
"query": "INSERT INTO workspace\n (id, name, owner, domain)\n VALUES ($1, $2, $3, $4)"
},
"4eb6b80c410e00e8a72eced6c25cdd3ed941e7a46a0619173f272eec7f28a3c1": {
"describe": {
"columns": [
@@ -1450,6 +1462,11 @@
"name": "slack_email",
"ordinal": 4,
"type_info": "Varchar"
},
{
"name": "auto_invite_domain",
"ordinal": 5,
"type_info": "Varchar"
}
],
"nullable": [
@@ -1457,7 +1474,8 @@
true,
true,
true,
false
false,
true
],
"parameters": {
"Left": [
@@ -2093,6 +2111,19 @@
},
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin)\n VALUES ('demo', $1, false)\n ON CONFLICT DO NOTHING"
},
"7b0a6bf71751668eea56923f70e198c36386a82b8a8abbf4f55caeb0618478fe": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
}
},
"query": "UPDATE workspace_settings SET auto_invite_domain = $1 WHERE workspace_id = $2"
},
"7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234": {
"describe": {
"columns": [
@@ -2166,21 +2197,6 @@
},
"query": "INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES ($1, $2, $3, $4)"
},
"8114333781add802159cd2c26d57cea40e27143606c71bf4d87dc14bb7040cd6": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Text"
]
}
},
"query": "UPDATE workspace SET name = $1, owner = $2, domain = $3 WHERE id = $4"
},
"826c6a36020f402169cab2ebe097e9f38f416f6f080a2fe8a9b83e97c4e15150": {
"describe": {
"columns": [
@@ -2625,6 +2641,20 @@
},
"query": "UPDATE password SET password_hash = $1 WHERE email = $2"
},
"903db87d61052a7ed39164be72c365e63acd7f87e103a3bdac870f764ddd7a9f": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
}
},
"query": "INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING"
},
"90719f6230467b08e5f2cc89271bcf5e4a6cec39e9d9b42ef3b90f09b3135b83": {
"describe": {
"columns": [
@@ -3704,6 +3734,26 @@
},
"query": "\n INSERT INTO resume_job\n (id, resume_id, job, flow, value, approver)\n VALUES ($1, $2, $3, $4, $5, $6)\n "
},
"c5429cd91368fcdc4da65ef6d84b32b2726f5ea938bc53bf6ed63465bd0eb0f2": {
"describe": {
"columns": [
{
"name": "workspace_id",
"ordinal": 0,
"type_info": "Varchar"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT workspace_id FROM workspace_settings WHERE auto_invite_domain = $1"
},
"c59dd666b9a316c027e8c319b80ccbab3a220d93b64357981bd4a03324dad1d0": {
"describe": {
"columns": [
@@ -3833,6 +3883,32 @@
},
"query": "UPDATE queue SET running = false WHERE last_ping < now() - ($1 || ' seconds')::interval AND running = true AND job_kind != $2 AND same_worker = false RETURNING id, workspace_id, last_ping"
},
"ccb05bf4c0a0f297f75498a684673ae7e4f6ed47f7a8f5d7154e0e0350865d41": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "UPDATE workspace_settings SET auto_invite_domain = NULL WHERE workspace_id = $1"
},
"d0308abac80575038203b60bb66d3b39b586939da0421a595e47c7a759616431": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Text"
]
}
},
"query": "UPDATE workspace SET name = $1, owner = $2 WHERE id = $3"
},
"d07faf1c9501d1bc5f6fd9588bb3da7b7f4897fcfd26dcb3b8ce406b1f653077": {
"describe": {
"columns": [
@@ -4567,19 +4643,14 @@
"ordinal": 2,
"type_info": "Varchar"
},
{
"name": "domain",
"ordinal": 3,
"type_info": "Varchar"
},
{
"name": "deleted",
"ordinal": 4,
"ordinal": 3,
"type_info": "Bool"
},
{
"name": "premium",
"ordinal": 5,
"ordinal": 4,
"type_info": "Bool"
}
],
@@ -4587,7 +4658,6 @@
false,
false,
false,
true,
false,
false
],
@@ -4753,5 +4823,18 @@
}
},
"query": "UPDATE variable SET value = $1 WHERE workspace_id = $2 AND path = $3"
},
"fceeeaa8d3c97273c8e77b3badd9e7cf82deead59c0d0619e0c303ea665f7d72": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Text",
"Text"
]
}
},
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin)\n SELECT $1::text, email, false FROM password WHERE email LIKE CONCAT('%', $2::text) AND NOT EXISTS (\n SELECT 1 FROM usr WHERE workspace_id = $1::text AND email = password.email\n )\n ON CONFLICT DO NOTHING"
}
}

View File

@@ -0,0 +1 @@
gmail.com

View File

@@ -351,6 +351,20 @@ paths:
items:
$ref: "#/components/schemas/Workspace"
/workspaces/allowed_domain_auto_invite:
get:
summary: is domain allowed for auto invi
operationId: isDomainAllowed
tags:
- workspace
responses:
"200":
description: domain allowed or not
content:
application/json:
schema:
type: boolean
/workspaces/users:
get:
summary: list all workspaces visible to me with user info
@@ -765,6 +779,8 @@ paths:
type: string
slack_command_script:
type: string
auto_invite_domain:
type: string
/w/{workspace}/workspaces/edit_slack_command:
post:
@@ -793,6 +809,35 @@ paths:
schema:
type: string
/w/{workspace}/workspaces/edit_auto_invite:
post:
summary: edit auto invite
operationId: editAutoInvite
tags:
- workspace
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: WorkspaceInvite
required: true
content:
application/json:
schema:
type: object
properties:
set:
type: boolean
required:
- set
responses:
"200":
description: status
content:
text/plain:
schema:
type: string
/w/{workspace}/users/list:
get:
summary: list users
@@ -4783,13 +4828,10 @@ components:
type: string
username:
type: string
domain:
type: string
required:
- id
- name
- username
- domain
Workspace:
type: object

View File

@@ -33,6 +33,7 @@ use windmill_audit::{audit_log, ActionKind};
use windmill_common::utils::{not_found_if_none, now_from_db};
use crate::users::Authed;
use crate::workspaces::invite_user_to_all_auto_invite_worspaces;
use crate::{
db::{UserDB, DB},
variables::{build_crypt, encrypt},
@@ -923,6 +924,9 @@ async fn login_callback(
.bind(user.company)
.execute(&mut tx)
.await?;
tx.commit().await?;
invite_user_to_all_auto_invite_worspaces(&db, &email).await?;
tx = db.begin().await?;
crate::users::create_session_token(
&email,
false,

View File

@@ -45,6 +45,7 @@ pub fn workspaced_service() -> Router {
.route("/delete_invite", post(delete_invite))
.route("/get_settings", get(get_settings))
.route("/edit_slack_command", post(edit_slack_command))
.route("/edit_auto_invite", post(edit_auto_invite))
.route("/tarball", get(tarball_workspace))
}
@@ -56,6 +57,7 @@ pub fn global_service() -> Router {
.route("/create", post(create_workspace))
.route("/exists", post(exists_workspace))
.route("/exists_username", post(exists_username))
.route("/allowed_domain_auto_invite", get(is_allowed_auto_domain))
}
#[derive(FromRow, Serialize)]
@@ -63,7 +65,6 @@ struct Workspace {
id: String,
name: String,
owner: String,
domain: Option<String>,
deleted: bool,
premium: bool,
}
@@ -75,6 +76,7 @@ pub struct WorkspaceSettings {
pub slack_name: Option<String>,
pub slack_command_script: Option<String>,
pub slack_email: String,
pub auto_invite_domain: Option<String>,
}
#[derive(FromRow, Serialize, Debug)]
@@ -96,19 +98,23 @@ pub enum WorkspaceKeyKind {
struct EditCommandScript {
slack_command_script: Option<String>,
}
#[derive(Deserialize)]
struct EditAutoInvite {
set: bool,
}
#[derive(Deserialize)]
struct CreateWorkspace {
id: String,
name: String,
username: String,
domain: Option<String>,
}
#[derive(Deserialize)]
struct EditWorkspace {
name: String,
owner: String,
domain: Option<String>,
}
#[derive(Serialize)]
@@ -252,6 +258,76 @@ async fn edit_slack_command(
Ok(format!("Edit command script {}", &w_id))
}
const BANNED_DOMAINS: &str = include_str!("../banned_domains.txt");
async fn is_allowed_auto_domain(Authed { email, .. }: Authed) -> JsonResult<bool> {
let domain = email.split('@').last().unwrap();
return Ok(Json(!BANNED_DOMAINS.contains(domain)));
}
async fn edit_auto_invite(
authed: Authed,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
Authed { is_admin, email, username, .. }: Authed,
Json(ea): Json<EditAutoInvite>,
) -> Result<String> {
require_admin(is_admin, &username)?;
let domain = email.split('@').last().unwrap();
let mut tx = db.begin().await?;
if ea.set {
if BANNED_DOMAINS.contains(domain) {
return Err(Error::BadRequest(format!(
"Domain {} is not allowed",
domain
)));
}
sqlx::query!(
"UPDATE workspace_settings SET auto_invite_domain = $1 WHERE workspace_id = $2",
domain,
&w_id
)
.execute(&mut tx)
.await?;
sqlx::query!(
"INSERT INTO workspace_invite
(workspace_id, email, is_admin)
SELECT $1::text, email, false FROM password WHERE email LIKE CONCAT('%', $2::text) AND NOT EXISTS (
SELECT 1 FROM usr WHERE workspace_id = $1::text AND email = password.email
)
ON CONFLICT DO NOTHING",
&w_id,
&domain
)
.execute(&mut tx)
.await?;
} else {
sqlx::query!(
"UPDATE workspace_settings SET auto_invite_domain = NULL WHERE workspace_id = $1",
&w_id
)
.execute(&mut tx)
.await?;
}
audit_log(
&mut tx,
&authed.username,
"workspaces.edit_auto_invite_domain",
ActionKind::Update,
&w_id,
Some(&authed.email),
Some([("set", &ea.set.to_string()[..])].into()),
)
.await?;
tx.commit().await?;
Ok(format!("Edit command script {}", &w_id))
}
async fn list_workspaces_as_super_admin(
authed: Authed,
Extension(user_db): Extension<UserDB>,
@@ -303,12 +379,11 @@ async fn create_workspace(
let mut tx = db.begin().await?;
sqlx::query!(
"INSERT INTO workspace
(id, name, owner, domain)
VALUES ($1, $2, $3, $4)",
(id, name, owner)
VALUES ($1, $2, $3)",
nw.id,
nw.name,
authed.email,
nw.domain
)
.execute(&mut tx)
.await?;
@@ -384,7 +459,6 @@ async fn create_workspace(
)
.await?;
tx.commit().await?;
Ok(format!("Created workspace {}", &nw.id))
}
@@ -398,10 +472,9 @@ async fn edit_workspace(
require_admin(is_admin, &username)?;
let mut tx = db.begin().await?;
sqlx::query!(
"UPDATE workspace SET name = $1, owner = $2, domain = $3 WHERE id = $4",
"UPDATE workspace SET name = $1, owner = $2 WHERE id = $3",
ew.name,
ew.owner,
ew.domain,
&w_id
)
.execute(&mut tx)
@@ -414,13 +487,7 @@ async fn edit_workspace(
ActionKind::Update,
&w_id,
Some(&authed.email),
Some(
[(
"domain",
ew.domain.unwrap_or("NO_DOMAIN".to_string()).as_str(),
)]
.into(),
),
None,
)
.await?;
tx.commit().await?;
@@ -463,6 +530,31 @@ async fn delete_workspace(
Ok(format!("Deleted workspace {}", &w_id))
}
pub async fn invite_user_to_all_auto_invite_worspaces(db: &DB, email: &str) -> Result<()> {
let mut tx = db.begin().await?;
let domain = email.split('@').last().unwrap();
let workspaces = sqlx::query_scalar!(
"SELECT workspace_id FROM workspace_settings WHERE auto_invite_domain = $1",
domain
)
.fetch_all(&mut tx)
.await?;
for w in workspaces {
sqlx::query!(
"INSERT INTO workspace_invite
(workspace_id, email, is_admin)
VALUES ($1, $2, false)
ON CONFLICT DO NOTHING",
w,
email,
)
.execute(&mut tx)
.await?;
}
tx.commit().await?;
Ok(())
}
async fn invite_user(
Authed { username, is_admin, .. }: Authed,
Extension(db): Extension<DB>,

View File

@@ -128,9 +128,9 @@
deleteConfirmedCallback = async () => {
await UserService.globalUserDelete({ email })
sendUserToast(`User ${email} removed`)
listUsers()
await listUsers()
}
openConfirmation = true
openConfirmation = false
}}>remove</button
>
</div>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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",

View File

@@ -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> {

View File

@@ -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"
>&leftarrow; Back to workspaces</Button

View File

@@ -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')
}}
>