UI improvements

This commit is contained in:
Ruben Fiszel
2022-11-02 08:41:08 +01:00
parent adde9dc10f
commit 1cab1e7bbf
22 changed files with 216 additions and 187 deletions
+10 -3
View File
@@ -15,6 +15,7 @@
import { sleep } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import Required from './Required.svelte'
import Popover from './Popover.svelte'
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule'
export let meta: Meta = {
@@ -164,9 +165,15 @@
<div class="flex flex-col sm:grid sm:grid-cols-4 sm:gap-4 pb-0 mb-1">
<label class="block">
<span class="text-gray-700 text-sm whitespace-nowrap">
Owner Kind <Tooltip>
<slot name="ownerToolkit" />
</Tooltip>
<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>
<select
@@ -0,0 +1,55 @@
<script lang="ts">
import { createPopperActions } from 'svelte-popperjs'
const [popperRef, popperContent] = createPopperActions({
placement: 'auto'
})
const betterPreventOverflow = (options) => ({
name: 'preventOverflow',
options,
effect: ({ state }) => {
const { padding = 0 } = options
state.elements.popper.style.maxWidth = `calc(100vw - ${padding * 2}px)`
}
})
const extraOpts = {
modifiers: [
betterPreventOverflow({ padding: 10 }),
{ name: 'offset', options: { offset: [8, 8] } },
{
name: 'arrow',
options: {
padding: 10 // 5px from the edges of the popper
}
}
]
}
let showTooltip = false
let timeout: NodeJS.Timeout
function open() {
clearTimeout(timeout)
showTooltip = true
}
function close() {
timeout = setTimeout(() => (showTooltip = false), 100)
}
</script>
<button use:popperRef on:mouseenter={open} on:mouseleave={close}>
<slot />
</button>
{#if showTooltip}
<div id="tooltip" use:popperContent={extraOpts} on:mouseenter={open} on:mouseleave={close}>
<div class="max-w-sm">
<slot name="text" />
</div>
</div>
{/if}
<style>
#tooltip {
@apply z-50 text-sm font-normal text-gray-300 bg-gray-800 p-4 rounded-xl whitespace-normal text-left;
}
</style>
@@ -137,15 +137,7 @@
{initialPath}
namePlaceholder="my_resource"
kind="resource"
>
<div slot="ownerToolkit">
Resource permissions depend on their path. Select the group <span class="font-mono"
>all</span
>
to share it, and <span class="font-mono">user</span> to keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div>
</Path>
/>
</div>
<span class=" mt-3 font-semibold text-gray-700 "
>Description <Required required={false} />
@@ -174,14 +174,7 @@
on:enter={() => changeStep(2)}
namePlaceholder="my_script"
kind="script"
>
<div slot="ownerToolkit">
Script permissions depend on their path. Select the group
<span class="font-mono"> all </span>
to share your script, and <span class="font-mono">user</span> to keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div>
</Path>
/>
<label class="block ">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<textarea
@@ -60,15 +60,15 @@
}}
/>
<div class="flex flex-row flex-wrap items-center gap-4">
<div class="w-80 -mb-2">
{#if options.length > 1}
<div class="flex flex-row flex-wrap items-center gap-4 w-full">
{#if options.length > 1}
<div class="w-80 -mb-2">
<RadioButton bind:value={itemKind} {options} />
{/if}
</div>
</div>
{/if}
<div class="flex items-center grow gap-4">
<input type="text" value={scriptPath ?? 'No path chosen yet'} disabled />
<input class="grow w-full" type="text" value={scriptPath ?? 'No path chosen yet'} disabled />
<Button
size="sm"
endIcon={{ icon: faSearch }}
@@ -33,7 +33,7 @@
<tr>
{#each keys as key, i}
<td
class="py-2 text-sm text-gray-700 {i == 0
class="py-2 text-sm text-gray-700 break-all {i == 0
? 'pl-4 pr-3 sm:pl-6 md:pl-0 font-semibold'
: 'px-3'} {twTextSize}"
>
+4 -55
View File
@@ -1,65 +1,14 @@
<script lang="ts">
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import { createPopperActions } from 'svelte-popperjs'
const [popperRef, popperContent] = createPopperActions({
placement: 'auto'
})
const betterPreventOverflow = (options) => ({
name: 'preventOverflow',
options,
effect: ({ state }) => {
const { padding = 0 } = options
state.elements.popper.style.maxWidth = `calc(100vw - ${padding * 2}px)`
}
})
const extraOpts = {
modifiers: [
betterPreventOverflow({ padding: 10 }),
{ name: 'offset', options: { offset: [8, 8] } },
{
name: 'arrow',
options: {
padding: 10 // 5px from the edges of the popper
}
}
]
}
let showTooltip = false
let timeout: NodeJS.Timeout
function open() {
clearTimeout(timeout)
showTooltip = true
}
function close() {
timeout = setTimeout(() => (showTooltip = false), 100)
}
import Popover from './Popover.svelte'
</script>
<button use:popperRef on:mouseenter={open} on:mouseleave={close}>
<Popover>
<Icon
class="text-gray-500 font-thin inline-block align-middle w-4"
data={faInfoCircle}
scale={0.8}
/>
</button>
{#if showTooltip}
<div
id="tooltip"
use:popperContent={extraOpts}
on:mouseenter={open}
on:mouseleave={close}
class="w-96 text-left"
>
<slot />
</div>
{/if}
<style>
#tooltip {
@apply z-50 text-base font-normal text-gray-300 bg-gray-800 p-4 rounded-xl whitespace-normal;
}
</style>
<span slot="text"> <slot /> </span>
</Popover>
@@ -90,21 +90,23 @@
$: endIconClass = classNames(iconOnly ? undefined : isSmall ? 'ml-1' : 'ml-2', endIcon?.classes)
</script>
<svelte:element
this={href ? 'a' : 'button'}
bind:this={element}
on:click={onClick}
on:focus
on:blur
{...buttonProps}
>
{#if startIcon}
<Icon data={startIcon.icon} class={startIconClass} scale={ButtonType.IconScale[size]} />
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon data={endIcon.icon} class={endIconClass} scale={ButtonType.IconScale[size]} />
{/if}
</svelte:element>
<div class="inline-flex">
<svelte:element
this={href ? 'a' : 'button'}
bind:this={element}
on:click={onClick}
on:focus
on:blur
{...buttonProps}
>
{#if startIcon}
<Icon data={startIcon.icon} class={startIconClass} scale={ButtonType.IconScale[size]} />
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon data={endIcon.icon} class={endIconClass} scale={ButtonType.IconScale[size]} />
{/if}
</svelte:element>
</div>
@@ -25,14 +25,7 @@
<svelte:fragment slot="content">
<TabContent value="metadata" class="p-4">
<Path bind:path={$flowStore.path} {initialPath} namePlaceholder="my_flow" kind="flow">
<div slot="ownerToolkit">
Flow permissions depend on their path. Select the group
<span class="font-mono"> all </span>
to share your flow, and <span class="font-mono">user</span> to keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div>
</Path>
<Path bind:path={$flowStore.path} {initialPath} namePlaceholder="my_flow" kind="flow" />
<label class="block my-4">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
+1 -1
View File
@@ -90,7 +90,7 @@
<CenteredPage>
<PageHeader
title="Audit logs"
tooltip="You can only see the audit logs you have visibility on, so only your own as a user."
tooltip="You can only see your own audit logs unless you are an admin."
/>
<Alert type="info" title="Audit logs are a team or enterprise feature - Unlimited during beta">
+4 -1
View File
@@ -68,7 +68,10 @@
<GroupModal bind:this={groupModal} />
<CenteredPage>
<PageHeader title="Groups">
<PageHeader
title="Groups"
tooltip="Group users together to grant roles and homegenous permissions. Same users can be in many groups at the same time."
>
<div class="flex flex-row">
<input
class="mr-2"
+40 -14
View File
@@ -7,7 +7,7 @@
</script>
<script lang="ts">
import { canWrite, emptySchema, sendUserToast } from '$lib/utils'
import { canWrite, emptySchema, sendUserToast, truncate } from '$lib/utils'
import { ResourceService, VariableService } from '$lib/gen'
import type { Resource, ResourceType } from '$lib/gen'
import PageHeader from '$lib/components/PageHeader.svelte'
@@ -50,6 +50,7 @@
let resourceViewer: Modal
let resourceViewerTitle: string = ''
let resourceViewerSchema: Schema = emptySchema()
let resourceViewerDescription = ''
let typeModalMode: 'view' | 'view-type' | 'create' = 'view'
let newResourceTypeName: string
let newResourceTypeSchema: Schema
@@ -153,7 +154,10 @@
</script>
<CenteredPage>
<PageHeader title="Resources">
<PageHeader
title="Resources"
tooltip="Save and permission rich objects (JSON) including credentials obtained through OAuth."
>
<div class="flex flex-row space-x-4">
<Button size="sm" startIcon={{ icon: faChain }} on:click={() => appConnect.open()}>
Connect an API
@@ -174,7 +178,7 @@
<TableCustom>
<tr slot="header-row">
<th>path</th>
<th>resource_type</th>
<th>resource type</th>
<th>description</th>
<th>OAuth</th>
<th />
@@ -189,12 +193,12 @@
href="#{path}"
on:click={async () => {
resourceViewerTitle = `Resource ${path}`
resourceViewerSchema = (
await ResourceService.getResource({
workspace: $workspaceStore ?? 'no_workspace',
path
})
).value
const resource = await ResourceService.getResource({
workspace: $workspaceStore ?? 'no_workspace',
path
})
resourceViewerSchema = resource.value
resourceViewerDescription = resource.description ?? ''
typeModalMode = 'view'
resourceViewer.openModal()
}}>{path}</a
@@ -202,8 +206,12 @@
<div class="mb-1 -mt-1"><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
</td>
<td class="px-2"><IconedResourceType name={resource_type} after={true} /></td>
<td><SvelteMarkdown source={description ?? ''} /></td>
<td>
<td
><span class="text-gray-500 text-xs"
><SvelteMarkdown source={truncate(description ?? '', 30)} /></span
></td
>
<td class="text-center">
{#if is_oauth}
<Icon
class="text-green-600"
@@ -263,7 +271,11 @@
{/if}
</div>
<div class="py-10" />
<PageHeader title="Resources types" primary={false}>
<PageHeader
title="Resources types"
primary={false}
tooltip="Schema and label to filter resources by type"
>
<Button size="sm" startIcon={{ icon: faPlus }} on:click={startNewType}>Add a type</Button>
</PageHeader>
@@ -289,17 +301,23 @@
on:click={() => {
resourceViewerTitle = `Resource type ${name}`
resourceViewerSchema = schema
resourceViewerDescription = description ?? ''
typeModalMode = 'view-type'
resourceViewer.openModal()
}}><IconedResourceType after={true} {name} /></a
></td
>
<td><SvelteMarkdown source={description ?? ''} /></td>
<td
><span class="text-gray-500 text-xs"
><SvelteMarkdown source={truncate(description ?? '', 30)} /></span
></td
>
<td>
{#if canWrite}
<Button
size="sm"
color="red"
variant="border"
startIcon={{ icon: faTrash }}
on:click={() => handleDeleteResourceType(name)}
disabled={!($userStore?.is_admin || false)}
@@ -358,8 +376,16 @@
</div>
</div>
{:else if typeModalMode === 'view'}
<Highlight language={json} code={JSON.stringify(resourceViewerSchema, null, 4)} />
<div class="py-2 bg-gray-100">
<SvelteMarkdown source={resourceViewerDescription} />
</div>
<div class="border p-2">
<Highlight language={json} code={JSON.stringify(resourceViewerSchema, null, 4)} />
</div>
{:else if typeModalMode === 'view-type'}
<div class="py-2 bg-gray-100">
<SvelteMarkdown source={resourceViewerDescription} />
</div>
<SchemaViewer schema={resourceViewerSchema} />
{/if}
</div>
+7 -9
View File
@@ -21,6 +21,7 @@
import { Skeleton } from '$lib/components/common'
import Tooltip from '../../lib/components/Tooltip.svelte'
import { goto } from '$app/navigation'
import PageHeader from '$lib/components/PageHeader.svelte'
let jobs: Job[] | undefined
let error: Error | undefined
@@ -147,16 +148,13 @@
</script>
<CenteredPage>
<div class="flex items-center min-h-[48px] my-4">
<h1 class="mr-1">Runs {path ? `of ${path}` : ''}</h1>
<Tooltip>
All past and schedule executions of scripts and flows, including previews.
<br />
You only see your own runs or runs of groups you belong to unless you are an admin.
</Tooltip>
</div>
<PageHeader
title="Runs {path ? `of ${path}` : ''}"
tooltip="All past and schedule executions of scripts and flows, including previews.
You only see your own runs or runs of groups you belong to unless you are an admin."
/>
<div class="max-w-7x">
<div class="max-w-7x mt-6">
<div class="flex flex-row space-x-4">
<select
bind:value={success}
+1 -9
View File
@@ -126,15 +126,7 @@
{initialPath}
namePlaceholder={'my_schedule'}
kind="schedule"
>
<div slot="ownerToolkit">
Schedule permissions depend on their path. Select the group <span class="font-mono"
>all</span
>
to share it, and <span class="font-mono">user</span> to keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div></Path
>
/>
{/if}
<h2 class="border-b pb-1 mt-8 mb-2">Script</h2>
+1 -1
View File
@@ -64,7 +64,7 @@
</script>
<CenteredPage>
<PageHeader title="Schedules">
<PageHeader title="Schedules" tooltip="Trigger Scripts and Flows according to a cron schedule">
<Button size="sm" startIcon={{ icon: faPlus }} href="/schedule/add">New&nbsp;schedule</Button>
</PageHeader>
<div class="relative mt-10">
+3 -6
View File
@@ -162,12 +162,9 @@
<CenteredPage>
<PageHeader
title="Scripts"
tooltip="A script can either be used standalone or as part of a Flow.
When standalone, it has an auto-generated UI from its parameters whom you can access clicking on 'Run'.
Like everything in windmill, scripts have owners (users or groups) and can be shared to other users and other groups. It is enough to have
read-access on a script to be able to execute it. However, you will also need to have been
granted visibility on the resources and variables it uses, otherwise it will behave as if those
items did not exist at runtime of the script."
tooltip="A Script can be used standalone or as part of a Flow.
When standalone, it has webhooks and an auto-generated UI from its parameters whom you can access clicking on 'Run'.
Scripts have owners (users or groups) and can be shared to users and groups."
>
<CreateActions />
</PageHeader>
@@ -6,7 +6,7 @@
import { page } from '$app/stores'
import { usersWorkspaceStore, workspaceStore } from '$lib/stores'
import CenteredModal from '$lib/components/CenteredModal.svelte'
import { Button } from '$lib/components/common'
import { Alert, Button } from '$lib/components/common'
const rd = $page.url.searchParams.get('rd')
@@ -82,15 +82,12 @@
<CenteredModal title="Create a new workspace">
{#if $page.url.hostname != 'app.windmill.dev'}
<div class="bg-blue-100 border-l-4 border-blue-600 text-blue-700 p-4 m-4" role="alert">
<p class="font-bold">
More than 1 user-created workspace for self-hosted will require a team or enterprise license
- Unlimited during beta
</p>
</div>
<Alert title="Restricted to 1 workspace - unlimited during beta" type="info">
More than 1 user-created workspace for self-hosted will require a team or enterprise license
</Alert>
{/if}
<label class="block pb-2">
<label class="block pb-2 pt-4">
<span class="text-gray-700">workspace name:</span>
<input bind:value={name} class="mt-1" />
</label>
@@ -109,7 +106,9 @@
<input bind:value={username} on:keyup={handleKeyUp} class="mt-1" />
</label>
<div class="flex flex-row justify-between pt-4">
<a href="/user/workspaces">&leftarrow; Back to workspaces</a>
<Button variant="border" size="sm" href="/user/workspaces"
>&leftarrow; Back to workspaces</Button
>
<Button
disabled={errorId != '' || errorUser != '' || !name || !username || !id}
on:click={createWorkspace}
+1 -1
View File
@@ -118,7 +118,7 @@
color="dark"
variant="border"
endIcon={{ icon }}
btnClasses="mt-2"
btnClasses="mt-2 w-full"
on:click={() => storeRedirect(type)}
>
{name}
@@ -100,14 +100,14 @@
</label>
{/each}
<div class="flex flex-row-reverse pt-4">
<a
<Button
href="/user/create_workspace{rd ? `?rd=${encodeURIComponent(rd)}` : ''}"
class="primary-button"
>Create a new workspace &rightarrow;
</a>
variant="border"
>+&nbsp;Create a new workspace
</Button>
</div>
<h2 class="mt-6 mb-4">Invitations</h2>
<h2 class="mt-6 mb-4">Invites to join a Workspace</h2>
{#if invites.length == 0}
<p class="text-sm text-gray-600 mt-2">You have no invites to any workspaces at the moment.</p>
{/if}
@@ -149,12 +149,12 @@
{/each}
<div class="flex justify-between items-center mt-10">
{#if $superadmin}
<a class="mr-10" href="/user/superadmin_settings">
<Icon data={faCrown} class="mr-1" scale={1} />Superadmin settings</a
<Button variant="border" size="sm" href="/user/superadmin_settings">
<Icon data={faCrown} class="mr-1" scale={1} />Superadmin settings</Button
>
{/if}
<a class="mr-10" href="/user/settings">
<Icon data={faUserCog} class="mr-1" scale={1} />User settings</a
<Button variant="border" size="sm" href="/user/settings">
<Icon data={faUserCog} class="mr-1" scale={1} />User settings</Button
>
<Button
variant="border"
+29 -8
View File
@@ -20,10 +20,11 @@
import { userStore, workspaceStore } from '$lib/stores'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import { faPlus, faCircle } from '@fortawesome/free-solid-svg-icons'
import { faPlus, faCircle, faLock, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
import { Button } from '$lib/components/common'
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import { Alert, Badge, Skeleton } from '$lib/components/common'
import Popover from '$lib/components/Popover.svelte'
type ListableVariableW = ListableVariable & { canWrite: boolean }
@@ -74,7 +75,10 @@
</script>
<CenteredPage>
<PageHeader title="Variables">
<PageHeader
title="Variables"
tooltip="Save and permission strings to be reused in Scripts and Flows."
>
<Button size="sm" startIcon={{ icon: faPlus }} on:click={() => variableEditor.initNew()}>
New&nbsp;variable
</Button>
@@ -91,8 +95,8 @@
<TableCustom>
<tr slot="header-row">
<th>path</th>
<th>value</th>
<th>secret</th>
<th>description</th>
<th>OAuth</th>
<th />
@@ -109,13 +113,30 @@
>
<div><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
</td>
<td>{truncate(value ?? '******', 40)}</td>
<td>{is_secret ? 'secret' : 'visible'}</td>
<td>{truncate(description ?? '', 50)}</td>
<td>
<td
><span class="text-sm break-all">
{truncate(value ?? '******', 20)}
{#if is_secret}
<Popover>
<Icon
label="Secret"
class="text-gray-700 mb-2 ml-2"
data={faEyeSlash}
scale={0.8}
/>
<span slot="text">This item is secret</span>
</Popover>
{/if}
</span></td
>
<td class="break-all"
><span class="text-xs text-gray-500">{truncate(description ?? '', 50)}</span></td
>
<td class="text-center">
{#if is_oauth}
<Icon
class="text-green-600"
class="text-green-500"
data={faCircle}
scale={0.7}
label="Variable is tied to an OAuth app"
+11 -10
View File
@@ -28,6 +28,7 @@
import AppConnect from '$lib/components/AppConnect.svelte'
import { onMount } from 'svelte'
import { Button } from '$lib/components/common'
import Tooltip from '$lib/components/Tooltip.svelte'
let users: User[] = []
let invites: WorkspaceInvite[] = []
@@ -226,18 +227,18 @@
Disconnect Slack
</Button>
{/if}
<h3 class="mt-5 text-gray-700">Select a script to run on /windmill command:</h3>
<h3 class="mt-5 text-gray-700"
>Script to run on /windmill command <Tooltip>
A script run from slack /windmill command is run as group 'slack'. The script chosen is
passed the parameters <pre>response_url: string, text: string</pre> respectively the url to
reply directly to the trigger and the text of the command.
<a href="/scripts/add?template=u/admin/triggered_from_slack_command"
>Define your own trigger script from a compatible template</a
></Tooltip
></h3
>
<ScriptPicker kind={Script.kind.SCRIPT} bind:scriptPath on:select={editSlackCommand} />
<p class="text-sm text-gray-700 italic mt-3">
A script run from slack /windmill command is run as group 'slack' and hence every variables or
resources used within it be visible to that group. The script chosen must be able to take as
parameter `response_url: str, text: str`, respectively the url to reply directly to the
trigger and the text of the command.
<a href="/scripts/add?template=u/admin/triggered_from_slack_command"
>Define your own trigger script from a compatible template</a
>
</p>
<div class="mt-10" />
<PageHeader title="Export workspace" primary={false} />
<div class="flex justify-start">
+2 -1
View File
@@ -254,7 +254,8 @@ const config = {
'.table-custom': {
'& th': {
paddingTop: theme('spacing.3'),
paddingRight: theme('spacing.2'),
paddingRight: theme('spacing.1'),
paddingLeft: theme('spacing.1'),
paddingBottom: theme('spacing.3'),
fontSize: theme('fontSize.sm'),
textAlign: 'left',