path improvements v1

This commit is contained in:
Ruben Fiszel
2022-12-18 05:55:29 +01:00
parent 398c109afe
commit 42d4711503
8 changed files with 258 additions and 220 deletions
+5
View File
@@ -3460,6 +3460,11 @@ paths:
- group
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: only_member_of
in: query
description: only list the groups the user is member of (default false)
schema:
type: boolean
responses:
"200":
description: group list
+17 -7
View File
@@ -8,7 +8,7 @@
use crate::{
db::{UserDB, DB},
users::Authed,
users::{get_groups_for_user, Authed},
};
use axum::{
extract::{Extension, Path, Query},
@@ -91,16 +91,26 @@ async fn list_groups(
Ok(Json(rows))
}
#[derive(Deserialize)]
struct QueryListGroup {
pub only_member_of: Option<bool>,
}
async fn list_group_names(
Authed { username, .. }: Authed,
Extension(db): Extension<DB>,
Query(QueryListGroup { only_member_of }): Query<QueryListGroup>,
Path(w_id): Path<String>,
) -> JsonResult<Vec<String>> {
let rows = sqlx::query_scalar!(
"SELECT name FROM group_ WHERE workspace_id = $1 ORDER BY name desc",
w_id
)
.fetch_all(&db)
.await?;
let rows = if !only_member_of.unwrap_or(false) {
sqlx::query_scalar!(
"SELECT name FROM group_ WHERE workspace_id = $1 ORDER BY name desc",
w_id
)
.fetch_all(&db)
.await?
} else {
get_groups_for_user(&w_id, &username, &db).await?
};
Ok(Json(rows))
}
+1 -1
View File
@@ -771,7 +771,7 @@ async fn get_user(w_id: &str, username: &str, db: &DB) -> Result<Option<UserInfo
}))
}
async fn get_groups_for_user(w_id: &str, username: &str, db: &DB) -> Result<Vec<String>> {
pub async fn get_groups_for_user(w_id: &str, username: &str, db: &DB) -> Result<Vec<String>> {
let groups = sqlx::query_scalar!(
"SELECT group_ FROM usr_to_group where usr = $1 AND workspace_id = $2",
username,
+47 -41
View File
@@ -230,47 +230,53 @@
>Groups managing this group <Tooltip>Any member of those groups can manage this group</Tooltip
></h2
>
<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>
<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 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 _}
+77 -56
View File
@@ -16,8 +16,11 @@
import Required from './Required.svelte'
import Popover from './Popover.svelte'
import { Button, Drawer, DrawerContent } from './common'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { faEye, faPlus } from '@fortawesome/free-solid-svg-icons'
import GroupEditor from './GroupEditor.svelte'
import ToggleButtonGroup from './common/toggleButton/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton/ToggleButton.svelte'
import { Icon } from 'svelte-awesome'
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
let meta: Meta | undefined = undefined
@@ -33,7 +36,7 @@
const dispatch = createEventDispatcher()
let groups: Group[] = []
let groups: string[] = []
$: meta && onMetaChange()
@@ -82,7 +85,10 @@
}
async function loadGroups(): Promise<void> {
groups = await GroupService.listGroups({ workspace: $workspaceStore! })
groups = await GroupService.listGroupNames({
workspace: $workspaceStore!,
onlyMemberOf: !($userStore?.is_admin || $superadmin)
})
}
async function validate(meta: Meta, path: string, kind: PathKind) {
@@ -161,6 +167,7 @@
}
let newGroup: Drawer
let viewGroup: Drawer
let newGroupName: string
let groupCreated: string | undefined = undefined
@@ -190,63 +197,77 @@
</DrawerContent>
</Drawer>
<div>
<div class="flex flex-col sm:grid sm:grid-cols-4 sm:gap-4 pb-0 mb-1">
{#if meta != undefined}
<label class="block">
<span class="text-gray-700 text-sm whitespace-nowrap">
<Popover
>Owner Kind
<span slot="text"
>Select the group <span class="font-mono">all</span>
to share it with all workspace users, and <span class="font-mono">user</span> to keep
it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</span>
</Popover>
</span>
<Drawer bind:this={viewGroup}>
<DrawerContent title="Group {meta?.owner}" on:close={viewGroup.closeDrawer}>
<GroupEditor name={meta?.owner ?? ''} />
</DrawerContent>
</Drawer>
<select
{disabled}
bind:value={meta.ownerKind}
on:change={() => {
if (meta) {
if (meta.ownerKind === 'group') {
meta.owner = 'all'
} else {
meta.owner = $userStore?.username ?? ''
<div>
<div class="flex flex-col sm:grid sm:grid-cols-3 gap-2 sm:gap-4 pb-0 mb-1">
{#if meta != undefined}
<div class="flex gap-4 w-full">
<label class="block">
<span class="text-gray-700 text-sm whitespace-nowrap">
<Popover
>&nbsp;
<span slot="text"
>Select the group <span class="font-mono">all</span>
to share it with all workspace users, and <span class="font-mono">user</span> to
keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</span>
</Popover>
</span>
<ToggleButtonGroup
bind:selected={meta.ownerKind}
on:selected={(e) => {
const kind = e.detail
console.log(kind)
if (meta) {
if (kind === 'group') {
meta.owner = 'all'
} else {
meta.owner = $userStore?.username ?? ''
}
}
}
}}
>
<option>user</option>
<option>group</option>
</select>
</label>
{#if meta.ownerKind === 'user'}
<label class="block">
<span class="text-gray-700 text-sm">Owner</span>
<input
type="text"
bind:value={meta.owner}
placeholder={$userStore?.username ?? ''}
disabled={!($superadmin || ($userStore?.is_admin ?? false))}
/>
</label>
{:else}
<label class="block">
<span class="text-gray-700 text-sm inline-flex justify-between w-full"
>Owner <button class=" text-xs text-blue-500" on:click={newGroup.openDrawer}
>+group</button
></span
}}
>
<select {disabled} bind:value={meta.owner}>
{#each groups as g}
<option>{g.name}</option>
{/each}
</select>
<ToggleButton light size="xs" value="user" position="left">User</ToggleButton>
<ToggleButton light size="xs" value="group" position="right">Group</ToggleButton>
</ToggleButtonGroup>
</label>
{/if}
{#if meta.ownerKind === 'user'}
<label class="block">
<span class="text-gray-700 text-sm">Owner</span>
<input
type="text"
bind:value={meta.owner}
placeholder={$userStore?.username ?? ''}
disabled={!($superadmin || ($userStore?.is_admin ?? false))}
/>
</label>
{:else}
<label class="block w-full">
<span class="text-gray-700 text-sm inline-flex justify-between w-full">Owner</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 col-span-2">
<span class="text-gray-700 text-sm">
Name
+109 -113
View File
@@ -173,129 +173,125 @@
<!-- metadata -->
{#if step === 1}
<CenteredPage>
<div class="space-y-6">
<h2 class="border-b pb-1 mt-4">Path & Summary</h2>
<Path
bind:this={pathC}
bind:error={pathError}
bind:path={script.path}
{initialPath}
on:enter={() => changeStep(2)}
namePlaceholder="my_script"
kind="script"
/>
<label class="block ">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<input
type="text"
bind:this={summaryC}
bind:value={script.summary}
placeholder="A very short summary of the script displayed when the script is listed"
rows="1"
/>
</label>
<h2 class="border-b pb-1 mt-4">Language</h2>
<div class="flex flex-row gap-2 flex-wrap">
{#each langs as [label, lang]}
{@const isPicked = script.language == lang && template == 'script'}
<Button
size="sm"
variant="border"
color={isPicked ? 'blue' : 'dark'}
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = lang
template = 'script'
initContent(lang, script.kind, template)
}}
>
<LanguageIcon {lang} /><span class="ml-2">{label}</span>
</Button>
{/each}
<h2 class="border-b pb-1 mt-4 mb-2">Path</h2>
<Path
bind:this={pathC}
bind:error={pathError}
bind:path={script.path}
{initialPath}
on:enter={() => changeStep(2)}
namePlaceholder="my_script"
kind="script"
/>
<h2 class="border-b pb-1 mt-8 mb-4">Summary</h2>
<input
type="text"
bind:this={summaryC}
bind:value={script.summary}
placeholder="A very short summary of the script displayed when the script is listed"
rows="1"
/>
<h2 class="border-b pb-1 mt-8 mb-6">Language</h2>
<div class="flex flex-row gap-2 flex-wrap">
{#each langs as [label, lang]}
{@const isPicked = script.language == lang && template == 'script'}
<Button
size="sm"
variant="border"
color={template == 'pgsql' ? 'blue' : 'dark'}
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
color={isPicked ? 'blue' : 'dark'}
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = Script.language.DENO
template = 'pgsql'
initContent(script.language, script.kind, template)
script.language = lang
template = 'script'
initContent(lang, script.kind, template)
}}
>
<LanguageIcon lang="pgsql" /><span class="ml-2">PostgreSQL</span>
<LanguageIcon {lang} /><span class="ml-2">{label}</span>
</Button>
<Button
size="sm"
variant="border"
color={template == 'mysql' ? 'blue' : 'dark'}
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = Script.language.DENO
template = 'mysql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="mysql" /><span class="ml-2">MySQL</span>
</Button>
</div>
<h2 class="border-b pb-1 mt-4">Advanced</h2>
<div>
<Button
color="light"
size="sm"
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
on:click={() => (viewScriptKind = !viewScriptKind)}
>
Tag this script as having a specific purpose inside flows
</Button>
</div>
{#if viewScriptKind}
<div class="max-w-lg">
<RadioButton
label="Script Type"
options={[
['Action', Script.kind.SCRIPT],
[
{
title: 'Trigger Script',
desc: `First module of flows to trigger them based on watching changes external periodically using an internal state`
},
Script.kind.TRIGGER
],
[
{
title: 'Error Handler',
desc: `Handle errors for flows after all retries attempts have been exhausted`
},
Script.kind.FAILURE
],
[
{
title: 'Approval Script',
desc: `Send notification externally to ask for approval to continue a flow`
},
Script.kind.APPROVAL
]
{/each}
<Button
size="sm"
variant="border"
color={template == 'pgsql' ? 'blue' : 'dark'}
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = Script.language.DENO
template = 'pgsql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="pgsql" /><span class="ml-2">PostgreSQL</span>
</Button>
<Button
size="sm"
variant="border"
color={template == 'mysql' ? 'blue' : 'dark'}
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = Script.language.DENO
template = 'mysql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="mysql" /><span class="ml-2">MySQL</span>
</Button>
</div>
<h2 class="border-b pb-1 mt-8 mb-4">Advanced</h2>
<div class="mb-4">
<Button
color="light"
size="sm"
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
on:click={() => (viewScriptKind = !viewScriptKind)}
>
Tag this script as having a specific purpose inside flows
</Button>
</div>
{#if viewScriptKind}
<div class="max-w-lg">
<RadioButton
label="Script Type"
options={[
['Action', Script.kind.SCRIPT],
[
{
title: 'Trigger',
desc: `First module of flows to trigger them based on watching changes external periodically using an internal state`
},
Script.kind.TRIGGER
],
[
{
title: 'Approval',
desc: `Send notification externally to ask for approval to continue a flow`
},
Script.kind.APPROVAL
],
[
{
title: 'Error Handler',
desc: `Handle errors for flows after all retries attempts have been exhausted`
},
Script.kind.FAILURE
]
// ['Command Handler', Script.kind.COMMAND]
]}
on:change={(e) => {
if (isInitialCode(script.content)) {
template = 'script'
initContent(script.language, e.detail, template)
}
}}
bind:value={script.kind}
/>
</div>
{/if}
<div class="ml-3">
<Toggle
bind:checked={script.is_template}
options={{ right: 'Save as a workspace template' }}
// ['Command Handler', Script.kind.COMMAND]
]}
on:change={(e) => {
if (isInitialCode(script.content)) {
template = 'script'
initContent(script.language, e.detail, template)
}
}}
bind:value={script.kind}
/>
</div>
{/if}
<div class="ml-3">
<Toggle
bind:checked={script.is_template}
options={{ right: 'Save as a workspace template' }}
/>
</div>
</CenteredPage>
{:else if step === 2}
@@ -70,7 +70,7 @@
<svelte:window on:keydown={onKeyDown} />
<aside class="drawer" class:open class:close={!open && timeout} {style}>
<aside class="drawer {$$props.class}" class:open class:close={!open && timeout} {style}>
<div class="overlay" on:click={handleClickAway} />
<div class="panel {placement}" class:size>
{#if open || !timeout}
@@ -20,7 +20,7 @@
position === 'center' ? 'rounded-none border-t border-b border-r' : '',
position === 'right' ? 'rounded-none rounded-r-md border-r border-y' : ''
)}
color={$selected === value ? (light ? 'dark' : 'dark') : 'light'}
color={$selected === value ? (light ? 'gray' : 'dark') : 'light'}
variant="contained"
>
<slot />