Files
windmill/frontend/src/lib/components/GroupEditor.svelte

305 lines
8.3 KiB
Svelte

<script lang="ts">
import { GranularAclService, GroupService, UserService, type Group } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import AutoComplete from 'simple-svelte-autocomplete'
import { createEventDispatcher } from 'svelte'
import autosize from 'svelte-autosize'
import { Button, ToggleButton, ToggleButtonGroup } from './common'
import Skeleton from './common/skeleton/Skeleton.svelte'
import TableCustom from './TableCustom.svelte'
import Tooltip from './Tooltip.svelte'
import { sendUserToast } from '$lib/toast'
import { canWrite } from '$lib/utils'
export let name: string
let can_write = false
type Role = 'member' | 'manager' | 'admin'
let group: Group | undefined
let members: { member_name: string; role: Role }[] | undefined = undefined
let usernames: string[] | undefined = []
let username: string = ''
const dispatch = createEventDispatcher()
async function loadUsernames(): Promise<void> {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
}
$: {
if ($workspaceStore && $userStore) {
load()
}
}
async function load() {
await loadGroup()
loadUsernames()
}
async function addToGroup() {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { username }
})
loadGroup()
}
async function loadGroup(): Promise<void> {
group = await GroupService.getGroup({ workspace: $workspaceStore!, name })
can_write = canWrite(name!, group.extra_perms ?? {}, $userStore)
members = Array.from(
new Set(
Object.entries(group?.extra_perms ?? {})
.filter(([k, v]) => k.startsWith('u/') && v)
.map(([k, _]) => k.split('/')[1])
.concat(group?.members ?? [])
)
).map((x) => {
return {
member_name: x,
role: getRole(x)
}
})
}
function getRole(x: string): Role {
const writer = 'u/' + x in (group?.extra_perms ?? {}) && (group?.extra_perms ?? {})['u/' + x]
const member = group?.members?.includes(x)
if (writer && member) {
return 'admin'
} else if (writer) {
return 'manager'
} else {
return 'member'
}
}
</script>
<div class="flex flex-col gap-6">
<h1>{name}</h1>
{#if group}
<div class="flex flex-col gap-1">
<textarea
disabled={!can_write}
rows="2"
type="text"
use:autosize
bind:value={group.summary}
placeholder="Summary of the group"
/>
<div class="flex justify-end">
<Button
disabled={!can_write}
size="xs"
on:click={async () => {
await GroupService.updateGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { summary: group?.summary }
})
dispatch('update')
sendUserToast('New summary saved')
}}>Save Summary</Button
>
</div>
</div>
{:else}
<Skeleton layout={[[4]]} />
{/if}
<h2>Members ({members?.length ?? 0})</h2>
{#if can_write}
<div class="flex items-start">
<AutoComplete items={usernames} bind:selectedItem={username} />
<Button variant="contained" color="blue" size="sm" btnClasses="!ml-4" on:click={addToGroup}>
Add member
</Button>
</div>
{/if}
{#if members}
<TableCustom>
<tr slot="header-row">
<th>user</th>
<th />
<th />
</tr>
<tbody slot="body">
{#each members as { member_name, role }}<tr>
<td>{member_name}</td>
<td>
{#if can_write}
<div>
<ToggleButtonGroup
selected={role}
on:selected={async (e) => {
const role = e.detail
// const wasInGroup = (group?.members ?? []).includes(group)
// const inAcl = (
// group?.extra_perms ? Object.keys(group?.extra_perms) : []
// ).includes(group)
if (role == 'member') {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
} else if (role == 'manager') {
await GroupService.removeUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.addGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name,
write: true
}
})
} else if (role == 'admin') {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.addGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name,
write: true
}
})
}
loadGroup()
}}
>
<ToggleButton position="left" value="member" size="xs"
>Member <Tooltip
>A Member of a group can see everything the group can see, write to
everything the group can write, and generally act on behalf of the group</Tooltip
></ToggleButton
>
<!-- <ToggleButton position="center" value="manager" size="xs"
>Manager <Tooltip
>A manager of a group can manage the group, adding and removing users and
change their roles. Being a manager does not make you a member.</Tooltip
></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>
{#if can_write}
<button
class="ml-2 text-red-500"
on:click={async () => {
await GroupService.removeUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { username: member_name }
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
loadGroup()
}}>remove</button
>
{/if}</td
>
</tr>{/each}
</tbody>
</TableCustom>
<!-- <h2 class="mt-10"
>Groups managing this group <Tooltip>Any member of those groups can manage this group</Tooltip
></h2
>
{#if can_write}
<div class="flex items-start">
<AutoComplete items={groups} bind:selectedItem={new_managing_group} />
<Button
variant="contained"
color="blue"
size="sm"
btnClasses="!ml-4"
on:click={addToManagingGroup}
>
Add group managing this group
</Button>
</div>
{/if}
{#if managing_groups.length == 0}
<p class="text-gray-600 text-sm">No group is managing this group</p>
{:else}
<TableCustom>
<tr slot="header-row">
<th>group</th>
<th />
</tr>
<tbody slot="body">
{#each managing_groups as managing_group}<tr>
<td>{managing_group.split('/')[1]}</td>
<td>
{#if can_write}
<button
class="ml-2 text-red-500"
on:click={async () => {
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: managing_group
}
})
loadGroup()
}}>remove</button
>
{/if}</td
>
</tr>{/each}
</tbody>
</TableCustom>
{/if} -->
{:else}
<div class="flex flex-col">
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
</div>
{/if}
</div>