feat: Instance group management page (#2994)

This commit is contained in:
Guillaume Bouvignies
2024-01-12 13:37:06 +01:00
committed by GitHub
parent c6cfee5d89
commit 165d5cbfc8
6 changed files with 247 additions and 8 deletions
+4 -4
View File
@@ -1,6 +1,6 @@
openapi: 3.0.3
info:
version: 1.240.0
version: 1.241.0
title: Windmill API
contact:
name: Windmill Team
@@ -8695,7 +8695,7 @@ paths:
text/plain:
schema:
type: string
/groups/udpate/{name}:
/groups/update/{name}:
post:
summary: update instance group
operationId: updateInstanceGroup
@@ -8744,7 +8744,7 @@ paths:
schema:
type: string
/groups/adduser/{name}:
delete:
post:
summary: add user to instance group
operationId: addUserToInstanceGroup
tags:
@@ -8774,7 +8774,7 @@ paths:
schema:
type: string
/groups/removeuser/{name}:
delete:
post:
summary: remove user from instance group
operationId: removeUserFromInstanceGroup
tags:
+3 -3
View File
@@ -5656,7 +5656,7 @@ paths:
schema:
type: string
/groups/udpate/{name}:
/groups/update/{name}:
post:
summary: update instance group
operationId: updateInstanceGroup
@@ -5701,7 +5701,7 @@ paths:
type: string
/groups/adduser/{name}:
delete:
post:
summary: add user to instance group
operationId: addUserToInstanceGroup
tags:
@@ -5729,7 +5729,7 @@ paths:
type: string
/groups/removeuser/{name}:
delete:
post:
summary: remove user from instance group
operationId: removeUserFromInstanceGroup
tags:
+1 -1
View File
@@ -316,7 +316,7 @@ async fn update_igroup(
sqlx::query("UPDATE instance_group SET summary = $1 WHERE name = $2")
.bind(igroup_update.new_summary)
.bind(&name)
.fetch_one(&mut *tx)
.execute(&mut *tx)
.await?;
audit_log(
@@ -0,0 +1,123 @@
<script lang="ts">
import { GroupService, UserService, type InstanceGroup } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import AutoComplete from 'simple-svelte-autocomplete'
import { createEventDispatcher } from 'svelte'
import autosize from 'svelte-autosize'
import { Button } from './common'
import Skeleton from './common/skeleton/Skeleton.svelte'
import TableCustom from './TableCustom.svelte'
import { sendUserToast } from '$lib/toast'
export let name: string
let instance_group: InstanceGroup | undefined
let members: { member_email: string }[] | undefined = undefined
let usernames: string[] | undefined = []
let username: string = ''
const dispatch = createEventDispatcher()
async function loadUsernames(): Promise<void> {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
}
$: {
load()
}
async function load() {
return Promise.all([loadInstanceGroup(), loadUsernames()])
}
async function loadInstanceGroup(): Promise<void> {
instance_group = await GroupService.getInstanceGroup({ name })
members = instance_group?.emails
? instance_group.emails.map((x) => {
return { member_email: x }
})
: []
}
</script>
<div class="flex flex-col gap-6">
<h1>{name}</h1>
{#if instance_group !== undefined}
<div class="flex flex-col gap-1">
<textarea
rows="2"
type="text"
use:autosize
bind:value={instance_group.summary}
placeholder="Summary of the group"
/>
<div class="flex justify-end">
<Button
size="xs"
on:click={async () => {
await GroupService.updateInstanceGroup({
name,
requestBody: { new_summary: instance_group?.summary ?? '' }
})
dispatch('update')
sendUserToast('New summary saved')
}}>Save Summary</Button
>
</div>
</div>
<h2>Members ({members?.length ?? 0})</h2>
<div class="flex items-start">
<AutoComplete noInputStyles items={usernames} bind:selectedItem={username} />
<Button
variant="contained"
color="blue"
size="sm"
btnClasses="!ml-4"
on:click={async () => {
await GroupService.addUserToInstanceGroup({
name,
requestBody: { email: username }
})
dispatch('update')
sendUserToast('User added')
loadInstanceGroup()
}}
>
Add member
</Button>
</div>
{#if members}
<TableCustom>
<tr slot="header-row">
<th>user</th>
<th />
</tr>
<tbody slot="body">
{#each members as { member_email }}<tr>
<td>{member_email}</td>
<td>
<button
class="ml-2 text-red-500"
on:click={async () => {
await GroupService.removeUserFromInstanceGroup({
name,
requestBody: { email: member_email }
})
dispatch('update')
sendUserToast('User removed')
loadInstanceGroup()
}}>remove</button
>
</td>
</tr>{/each}
</tbody>
</TableCustom>
{:else}
<div class="flex flex-col">
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
</div>
{/if}
{/if}
</div>
@@ -0,0 +1,5 @@
export function load() {
return {
stuff: { title: 'Instance groups' }
}
}
@@ -0,0 +1,111 @@
<script lang="ts">
import type { InstanceGroup } from '$lib/gen'
import { GroupService } from '$lib/gen'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button, Drawer, DrawerContent, Popup } from '$lib/components/common'
import InstanceGroupEditor from '$lib/components/InstanceGroupEditor.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import { Plus } from 'lucide-svelte'
let newGroupName: string = ''
let instanceGroups: InstanceGroup[] | undefined = undefined
let groupDrawer: Drawer
async function loadInstanceGroups(): Promise<void> {
try {
instanceGroups = await GroupService.listInstanceGroups()
} catch (e) {
instanceGroups = undefined
}
}
async function addInstanceGroup() {
await GroupService.createInstanceGroup({
requestBody: { name: newGroupName }
})
loadInstanceGroups()
editGroupName = newGroupName
groupDrawer.openDrawer()
}
$: {
loadInstanceGroups()
}
let editGroupName: string = ''
</script>
<Drawer bind:this={groupDrawer}>
<DrawerContent title="Instance Group {editGroupName}" on:close={groupDrawer.closeDrawer}>
<InstanceGroupEditor on:update={loadInstanceGroups} name={editGroupName} />
</DrawerContent>
</Drawer>
<CenteredPage>
<PageHeader title="Instance Groups">
<div class="flex flex-row">
<div>
<Popup
let:close
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New&nbsp;group</Button>
</svelte:fragment>
<div class="flex-col flex gap-2">
<input class="mr-2" placeholder="New instance group name" bind:value={newGroupName} />
<Button
size="md"
startIcon={{ icon: Plus }}
disabled={!newGroupName}
on:click={() => {
addInstanceGroup()
close(null)
}}
>
Create
</Button>
</div>
</Popup>
</div>
</div>
</PageHeader>
{#if instanceGroups && instanceGroups.length > 0}
<div class="relative mb-20 pt-8">
<TableCustom>
<tr slot="header-row">
<th>Name</th>
<th>Summary</th>
<th>Members</th>
</tr>
<tbody slot="body">
{#each instanceGroups as { name, summary, emails }}
<tr>
<td>
<a
href="#{name}"
on:click={() => {
editGroupName = name
groupDrawer.openDrawer()
}}
>{name}
</a>
</td>
<td>
{summary ? summary.slice(0, 50) + (summary.length > 50 ? '...' : '') : '-'}
</td>
<td>{emails?.length ?? 0} members</td>
</tr>
{/each}
</tbody>
</TableCustom>
</div>
{/if}
</CenteredPage>
<style>
</style>