mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 16:02:28 +00:00
re-enable picking groups
This commit is contained in:
@@ -3806,6 +3806,28 @@
|
||||
},
|
||||
"query": "select hash from script where path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')) AND\n deleted = false"
|
||||
},
|
||||
"b89fc3a68c10e6b80cb4fbe84e22e139389867fcbaa2938871fb04ab6e354d85": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "exists",
|
||||
"ordinal": 0,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM usr_to_group where usr = $1 AND group_ = $2 AND workspace_id = $3)"
|
||||
},
|
||||
"b9468b9e16f55db11b33d8e9793e6e3ae6c5add6ca02414140adb724120a6800": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
||||
+2
-3
@@ -50,11 +50,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
let shutdown_signal = windmill_common::shutdown_signal(tx);
|
||||
|
||||
let base_internal_url =
|
||||
std::env::var("BASE_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
|
||||
|
||||
let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
|
||||
let base_internal_url =
|
||||
std::env::var("BASE_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
|
||||
let timeout = std::env::var("TIMEOUT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{sync::Arc, time::Duration};
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
folders::get_folders_for_user,
|
||||
folders::{get_folderopt, get_folders_for_user},
|
||||
utils::require_super_admin,
|
||||
workspaces::invite_user_to_all_auto_invite_worspaces,
|
||||
CookieDomain, IsSecure,
|
||||
@@ -825,6 +825,19 @@ pub async fn get_groups_for_user(w_id: &str, username: &str, db: &DB) -> Result<
|
||||
Ok(groups)
|
||||
}
|
||||
|
||||
pub async fn is_user_member(w_id: &str, username: &str, group: &str, db: &DB) -> Result<bool> {
|
||||
let is_member = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr_to_group where usr = $1 AND group_ = $2 AND workspace_id = $3)",
|
||||
username,
|
||||
group,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
Ok(is_member)
|
||||
}
|
||||
|
||||
pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: &DB) -> Result<()> {
|
||||
let splitted = path.split("/").collect::<Vec<&str>>();
|
||||
if splitted[0] == "u" {
|
||||
@@ -837,8 +850,7 @@ pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: &
|
||||
)));
|
||||
}
|
||||
} else if splitted[0] == "g" {
|
||||
let groups = get_groups_for_user(w_id, username, db).await?;
|
||||
if groups.contains(&username.to_string()) {
|
||||
if is_user_member(w_id, username, splitted[1], db).await? {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(Error::BadRequest(format!(
|
||||
@@ -846,6 +858,16 @@ pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: &
|
||||
username, splitted[1]
|
||||
)));
|
||||
}
|
||||
} else if splitted[0] == "f" {
|
||||
let folder = get_folderopt(&mut db.begin().await?, w_id, splitted[1]).await?;
|
||||
if folder.is_some() && folder.unwrap().owners.contains(&username.to_string()) {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"{} is not an admin of {} and hence is not authorized to perform this destructive operation",
|
||||
username, splitted[1]
|
||||
)));
|
||||
}
|
||||
}
|
||||
Err(Error::BadRequest(format!("not recognized owner kind")))
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
editor.insertAtCursor(`v, _ := wmill.GetVariable("${path}")`)
|
||||
} else if (lang == 'bash') {
|
||||
editor.insertAtCursor(`curl -s -H "Authorization: Bearer $WM_TOKEN" \\
|
||||
"$WM_BASE_URL/api/w/$WM_WORKSPACE/variables/get/${path}" \\
|
||||
"$BASE_INTERNAL_URL/api/w/$WM_WORKSPACE/variables/get/${path}" \\
|
||||
| jq -r .value`)
|
||||
}
|
||||
sendUserToast(`${name} inserted at cursor`)
|
||||
@@ -194,7 +194,7 @@
|
||||
editor.insertAtCursor(`r, _ := wmill.GetResource("${path}")`)
|
||||
} else if (lang == 'bash') {
|
||||
editor.insertAtCursor(`curl -s -H "Authorization: Bearer $WM_TOKEN" \\
|
||||
"$WM_BASE_URL/api/w/$WM_WORKSPACE/resources/get/${path}" \\
|
||||
"$BASE_INTERNAL_URL/api/w/$WM_WORKSPACE/resources/get/${path}" \\
|
||||
| jq -r .value`)
|
||||
}
|
||||
sendUserToast(`${path} inserted at cursor`)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
export let name: string
|
||||
let can_write = false
|
||||
|
||||
type Role = 'viewer' | 'writer' | 'owner'
|
||||
type Role = 'viewer' | 'writer' | 'admin'
|
||||
let folder: Folder | undefined
|
||||
let perms: { owner_name: string; role: Role }[] | undefined = undefined
|
||||
let managing_folders: string[] = []
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
async function loadFolder(): Promise<void> {
|
||||
folder = await FolderService.getFolder({ workspace: $workspaceStore!, name })
|
||||
can_write = canWrite(name!, folder.extra_perms ?? {}, $userStore)
|
||||
can_write = folder.owners.includes('u/' + $userStore?.username)
|
||||
perms = Array.from(
|
||||
new Set(
|
||||
Object.entries(folder?.extra_perms ?? {})
|
||||
@@ -99,7 +99,7 @@
|
||||
const owner = folder?.owners?.includes(x)
|
||||
|
||||
if (owner) {
|
||||
return 'owner'
|
||||
return 'admin'
|
||||
} else if (writer) {
|
||||
return 'writer'
|
||||
} else {
|
||||
@@ -111,7 +111,7 @@
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<h1>{name}</h1>
|
||||
<h2>Permissions</h2>
|
||||
<h2>Permissions ({perms?.length ?? 0})</h2>
|
||||
{#if can_write}
|
||||
<div class="flex items-center gap-1">
|
||||
<div>
|
||||
@@ -153,7 +153,7 @@
|
||||
// const inAcl = (
|
||||
// folder?.extra_perms ? Object.keys(folder?.extra_perms) : []
|
||||
// ).includes(folder)
|
||||
if (role == 'owner') {
|
||||
if (role == 'admin') {
|
||||
await FolderService.addOwnerToFolder({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
@@ -227,6 +227,8 @@
|
||||
>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
{role}
|
||||
{/if}</td
|
||||
>
|
||||
<td>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
export let name: string
|
||||
let can_write = false
|
||||
|
||||
type Role = 'member' | 'manager' | 'member_manager'
|
||||
type Role = 'member' | 'manager' | 'admin'
|
||||
let group: Group | undefined
|
||||
let members: { member_name: string; role: Role }[] | undefined = undefined
|
||||
let managing_groups: string[] = []
|
||||
@@ -78,7 +78,7 @@
|
||||
const member = group?.members?.includes(x)
|
||||
|
||||
if (writer && member) {
|
||||
return 'member_manager'
|
||||
return 'admin'
|
||||
} else if (writer) {
|
||||
return 'manager'
|
||||
} else {
|
||||
@@ -115,7 +115,7 @@
|
||||
{:else}
|
||||
<Skeleton layout={[[4]]} />
|
||||
{/if}
|
||||
<h2>Members & Managers</h2>
|
||||
<h2>Members ({members?.length ?? 0})</h2>
|
||||
{#if can_write}
|
||||
<div class="flex items-start">
|
||||
<AutoComplete items={usernames} bind:selectedItem={username} />
|
||||
@@ -178,7 +178,7 @@
|
||||
write: true
|
||||
}
|
||||
})
|
||||
} else if (role == 'member_manager') {
|
||||
} else if (role == 'admin') {
|
||||
await GroupService.addUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
@@ -211,11 +211,16 @@
|
||||
change their roles. Being a manager does not make you a member.</Tooltip
|
||||
></ToggleButton
|
||||
> -->
|
||||
<ToggleButton position="right" value="member_manager" size="xs"
|
||||
>Admin</ToggleButton
|
||||
<ToggleButton position="right" value="admin" size="xs"
|
||||
>Admin <Tooltip
|
||||
>An admin of a group is a member of a group that can also add and remove
|
||||
members to the group, or make them admin.</Tooltip
|
||||
></ToggleButton
|
||||
>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
{role}
|
||||
{/if}</td
|
||||
>
|
||||
<td>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {
|
||||
FlowService,
|
||||
FolderService,
|
||||
GroupService,
|
||||
ResourceService,
|
||||
ScheduleService,
|
||||
ScriptService,
|
||||
@@ -19,6 +20,7 @@
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -36,6 +38,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let folders: string[] = []
|
||||
let groups: string[] = []
|
||||
|
||||
$: meta && onMetaChange()
|
||||
|
||||
@@ -47,7 +50,7 @@
|
||||
}
|
||||
|
||||
function metaToPath(meta: Meta): string {
|
||||
return [meta.ownerKind === 'folder' ? 'f' : 'u', meta.owner, meta.name].join('/')
|
||||
return [meta.ownerKind?.charAt(0) ?? '', meta.owner, meta.name].join('/')
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
@@ -92,6 +95,18 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
let initialGroups: string[] = []
|
||||
if (initialPath?.split('/')?.[0] == 'f') {
|
||||
initialGroups.push(initialPath?.split('/')?.[1])
|
||||
}
|
||||
groups = initialGroups.concat(
|
||||
await GroupService.listGroupNames({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function validate(meta: Meta, path: string, kind: PathKind) {
|
||||
error = ''
|
||||
validateName(meta) && validatePath(path, kind)
|
||||
@@ -148,9 +163,12 @@
|
||||
} else if (!/^[\w-]+(\/[\w-]+)*$/.test(meta.name)) {
|
||||
error = 'This name is not valid'
|
||||
return false
|
||||
} else if (meta.owner == '') {
|
||||
} else if (meta.owner == '' && meta.ownerKind == 'folder') {
|
||||
error = 'Folder need to be chosen'
|
||||
return false
|
||||
} else if (meta.owner == '' && meta.ownerKind == 'group') {
|
||||
error = 'Group need to be chosen'
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
@@ -159,6 +177,7 @@
|
||||
$: {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadFolders()
|
||||
loadGroups()
|
||||
initPath()
|
||||
}
|
||||
}
|
||||
@@ -189,10 +208,59 @@
|
||||
}
|
||||
loadFolders()
|
||||
}
|
||||
|
||||
let newGroup: Drawer
|
||||
let viewGroup: Drawer
|
||||
let newGroupName: string
|
||||
let groupCreated: string | undefined = undefined
|
||||
|
||||
async function addGroup() {
|
||||
await GroupService.createGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { name: newGroupName }
|
||||
})
|
||||
groupCreated = newGroupName
|
||||
if (meta) {
|
||||
meta.owner = newGroupName
|
||||
}
|
||||
loadGroups()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={newGroup}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
on:close={() => {
|
||||
newGroup.closeDrawer()
|
||||
groupCreated = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<input class="mr-2" placeholder="New group name" bind:value={newGroupName} />
|
||||
<Button size="md" startIcon={{ icon: faPlus }} disabled={!newGroupName} on:click={addGroup}>
|
||||
New group
|
||||
</Button>
|
||||
</div>
|
||||
{#if groupCreated}
|
||||
<div class="mt-8" />
|
||||
<GroupEditor name={groupCreated} />
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<Drawer bind:this={viewGroup}>
|
||||
<DrawerContent title="Folder {meta?.owner}" on:close={viewGroup.closeDrawer}>
|
||||
<GroupEditor name={meta?.owner ?? ''} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Drawer bind:this={newFolder}>
|
||||
<DrawerContent title="New Folder" on:close={newFolder.closeDrawer}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
on:close={() => {
|
||||
newFolder.closeDrawer()
|
||||
folderCreated = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
|
||||
<Button size="md" startIcon={{ icon: faPlus }} disabled={!newFolderName} on:click={addFolder}>
|
||||
@@ -228,6 +296,8 @@
|
||||
if (meta) {
|
||||
if (kind === 'folder') {
|
||||
meta.owner = $userStore?.folders?.[0] ?? ''
|
||||
} else if (kind === 'group') {
|
||||
meta.owner = 'all'
|
||||
} else {
|
||||
meta.owner = $userStore?.username ?? ''
|
||||
}
|
||||
@@ -235,6 +305,7 @@
|
||||
}}
|
||||
>
|
||||
<ToggleButton light size="xs" value="user" position="left">User</ToggleButton>
|
||||
<ToggleButton light size="xs" value="group" position="center">Group</ToggleButton>
|
||||
<ToggleButton light size="xs" value="folder" position="right">Folder</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</label>
|
||||
@@ -249,9 +320,14 @@
|
||||
disabled={!($superadmin || ($userStore?.is_admin ?? false))}
|
||||
/>
|
||||
</label>
|
||||
{:else}
|
||||
{:else if meta.ownerKind === 'folder'}
|
||||
<label class="block grow w-48">
|
||||
<span class="text-gray-700 text-sm">Folder</span>
|
||||
<span class="text-gray-700 text-sm"
|
||||
>Folder <Tooltip
|
||||
>Read and write permissions are given to groups and users at the folder level and
|
||||
shared by all items inside the folder.</Tooltip
|
||||
></span
|
||||
>
|
||||
|
||||
<div class="flex flex-row gap-1 w-full">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
@@ -267,6 +343,27 @@
|
||||
></div
|
||||
>
|
||||
</label>
|
||||
{:else if meta.ownerKind === 'group'}
|
||||
<label class="block grow w-48">
|
||||
<span class="text-gray-700 text-sm"
|
||||
>Group <Tooltip>Item will be owned by the group and hence all its member</Tooltip
|
||||
></span
|
||||
>
|
||||
|
||||
<div class="flex flex-row gap-1 w-full">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#each groups as g}
|
||||
<option>{g}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button variant="border" size="xs" on:click={viewGroup.openDrawer}>
|
||||
<Icon scale={0.8} data={faEye} /></Button
|
||||
>
|
||||
<Button variant="border" size="xs" on:click={newGroup.openDrawer}>
|
||||
<Icon scale={0.8} data={faPlus} /></Button
|
||||
></div
|
||||
>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
<label class="block grow">
|
||||
|
||||
Reference in New Issue
Block a user