UX improvements

This commit is contained in:
Ruben Fiszel
2022-10-31 11:57:00 +01:00
parent a6b25a9272
commit 0196fab7ba
17 changed files with 148 additions and 173 deletions
+3 -10
View File
@@ -18,8 +18,8 @@
const dispatch = createEventDispatcher()
function openMenu() {
open = true
function toggle() {
open = !open
}
function handleClickOutsideMenu(event: Event) {
@@ -32,14 +32,7 @@
use:clickOutside
on:click_outside={handleClickOutsideMenu}
>
<Button
color="light"
size="xs"
aria-expanded="false"
aria-haspopup="true"
btnClasses="!text-blue-500 bg-transparent"
on:click={openMenu}
>
<Button color="light" size="xs" btnClasses="!text-blue-500 bg-transparent" on:click={toggle}>
{#if !name}
<Icon data={faEllipsisH} scale={1.2} />
{:else}
@@ -25,9 +25,10 @@
export let after: boolean = false
export let height = '24px'
export let width = '24px'
export let center = false
</script>
<div class="flex flex-row gap-2 justify-center items-center break-all whitespace-normal -pl-2">
<div class=" truncate flex flex-row gap-2 {center ? 'justify-center items-center' : ''} -pl-2">
{#if !silent && !after}
{name}
{/if}
@@ -72,6 +73,8 @@
<WindmillIcon {height} {width} />
{:else if name === 'http'}
<HttpIcon {height} {width} />
{:else}
<span style="width: {width}" class="bg-gray-100 rounded-full" />
{/if}
{#if !silent && after}
{name}
@@ -14,7 +14,7 @@
-->
<div class="flex flex-col {$$props.class} min-w-full">
<div class="inline-block min-w-full py-2 align-middle">
<table class="table-custom min-w-full divide-y divide-gray-300 table-auto">
<table class="table-custom min-w-full table-auto divide-y divide-gray-300 ">
<thead>
<slot name="header-row" />
</thead>
@@ -33,7 +33,7 @@
<tr>
{#each keys as key, i}
<td
class="py-4 text-sm text-gray-700 {i == 0
class="py-2 text-sm text-gray-700 {i == 0
? 'pl-4 pr-3 sm:pl-6 md:pl-0 font-semibold'
: 'px-3'} {twTextSize}"
>
@@ -16,20 +16,20 @@
$: selected && updateSelected()
const selectedContent = writable(selected)
const selectedStore = writable(selected)
$: $selectedContent && dispatch('selected', $selectedContent)
$: $selectedStore && dispatch('selected', $selectedStore)
setContext<TabsContext>('Tabs', {
selected: selectedContent,
selected: selectedStore,
update: (value: string) => {
selectedContent.set(value)
selectedStore.set(value)
selected = value
}
})
function updateSelected() {
selectedContent.set(selected)
selectedStore.set(selected)
}
</script>
@@ -34,7 +34,7 @@
{#if job === undefined}
No job found
{:else}
<div class="block text-center align-middle pb-3 pt-2 px-6">
<div class="block text-center align-middle px-6">
{#if 'success' in job && job.success}
{#if job.is_skipped}
<Icon
@@ -82,36 +82,40 @@
{/if}
</div>
<div class="break-all py-2">
{#if job.script_path}
<a class="pr-3" href="/run/{job.id}">{job.script_path} </a>
{:else if 'job_kind' in job && job.job_kind == 'preview'}
<a class="pr-3" href="/run/{job.id}">Preview without path </a>
{:else if 'job_kind' in job && job.job_kind != 'script'}
<a class="pr-3" href="/run/{job.id}">lock dependencies</a>
{/if}
{#if job.script_hash}
<a href="/scripts/get/{job.script_hash}" class="commit-hash"
>{truncateHash(job.script_hash ?? '')}</a
>
{/if}
{#if 'job_kind' in job && job.job_kind != 'script'}<span
class="bg-blue-200 text-gray-700 text-xs rounded px-1 mx-3 whitespace-nowrap"
><a href="/run/{job.id}">{job.job_kind}</a></span
>
{:else if job.is_flow_step}
<span class="bg-blue-200 text-gray-700 text-xs rounded px-1 mx-3"
><a href="/run/{job.parent_job}">step of flow</a></span
>
{/if}
<div class="flex flex-row space-x-2">
<div class="whitespace-nowrap">
{#if job.script_path}
<a href="/run/{job.id}">{job.script_path} </a>
{:else if 'job_kind' in job && job.job_kind == 'preview'}
<a href="/run/{job.id}">Preview without path </a>
{:else if 'job_kind' in job && job.job_kind == 'dependencies'}
<a href="/run/{job.id}">lock deps of {truncateHash(job.script_hash ?? '')}</a>
{:else if 'job_kind' in job && job.job_kind == 'identity'}
<a href="/run/{job.id}">no op</a>
{/if}
</div>
<div class="whitespace-nowrap">
{#if job.script_hash}
<a href="/scripts/get/{job.script_hash}" class="commit-hash"
>{truncateHash(job.script_hash ?? '')}</a
>
{/if}
{#if 'job_kind' in job && job.job_kind != 'script'}<span
class="bg-blue-200 text-gray-700 text-xs rounded px-1 whitespace-nowrap"
><a href="/run/{job.id}">{job.job_kind}</a></span
>
{:else if job.is_flow_step}
<span class="bg-blue-200 text-gray-700 text-xs rounded px-1 "
><a href="/run/{job.parent_job}">step of flow</a></span
>
{/if}
</div>
</div>
{/if}
</div>
<div>
<span class="pl-14 italic text-gray-500 text-2xs whitespace-nowrap overflow-hidden"
>Run {job.id}</span
>
</div>
<div class="pl-14 italic text-gray-500 text-2xs whitespace-nowrap overflow-hidden"
>{truncateRev(job.id, 8, '')}</div
>
</div>
<div class="bg-white grid grid-cols-2 gap-x-2 col-span-2">
<div class="w-full text-gray-500 text-xs text-left flex flex-col gap-1 mx-4 overflow-hidden">
@@ -1,72 +1,42 @@
<script lang="ts">
import { goto } from '$app/navigation'
import type { Flow } from '$lib/gen'
import { faPencil, faPlay } from '@fortawesome/free-solid-svg-icons'
import { Button } from '../common'
export let flow: Flow
</script>
<div
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 flex flex-col justify-between cursor-pointer"
<a
href="#{flow.path}"
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 text-gray-800 flex flex-col justify-between cursor-pointer"
on:click={() => goto(`/flows/get/${flow.path}`)}
>
<div class="font-bold">{flow.summary}</div>
<div class="font-bold">{flow.summary || flow.path}</div>
<div class="inline-flex justify-between w-full h-16">
<div class="inline-flex justify-between w-full break-all">
<div class="text-xs">{flow.path}</div>
</div>
<div
class="inline-flex space-x-2 space-x-reverse flex-row-reverse w-full bottom-4 right-4 flex-wrap"
>
<button
on:click|stopPropagation={() => goto(`/flows/edit/${flow.path}`)}
type="button"
class="inline-flex items-center text-gray-700 bg-transparent border border-gray-700 hover:bg-gray-800 hover:text-white focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-xs px-3 py-1.5 text-center"
data-dismiss-target="#alert-additional-content-5"
aria-label="Close"
<div class="flex flex-row-reverse gap-x-2">
<Button
on:click={() => goto(`/flows/edit/${flow.path}`)}
color="dark"
size="xs"
variant="border"
startIcon={{ icon: faPencil }}
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
/>
</svg>
Edit
</button>
</Button>
<button
on:click|stopPropagation={() => goto(`/flows/run/${flow.path}`)}
type="button"
class="inline-flex items-center text-gray-700 bg-transparent border border-gray-700 hover:bg-gray-800 hover:text-white focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-xs px-3 py-1.5 text-center"
data-dismiss-target="#alert-additional-content-5"
aria-label="Close"
<Button
on:click={() => goto(`/flows/run/${flow.path}`)}
color="dark"
size="xs"
variant="border"
startIcon={{ icon: faPlay }}
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
/><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Run
</button>
</Button>
</div>
</div>
</a>
@@ -9,9 +9,9 @@
export let script: Script
</script>
<div
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 flex flex-col justify-between cursor-pointer"
on:click={() => goto(`/scripts/get/${script.hash}`)}
<a
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 text-gray-800 flex flex-col justify-between"
href="/scripts/get/{script.hash}"
>
<div class="font-bold">{script.summary || script.path}</div>
@@ -32,27 +32,25 @@
</Badge>
{/if}
</div>
<ActionRow>
<svelte:fragment slot="right">
<Button
on:click={() => goto(`/scripts/edit/${script.hash}?step=2`)}
color="dark"
size="xs"
variant="border"
startIcon={{ icon: faPencil }}
>
Edit
</Button>
<div class="flex flex-row-reverse gap-x-2">
<Button
on:click={() => goto(`/scripts/edit/${script.hash}?step=2`)}
color="dark"
size="xs"
variant="border"
startIcon={{ icon: faPencil }}
>
Edit
</Button>
<Button
on:click={() => goto(`/scripts/run/${script.hash}`)}
color="dark"
size="xs"
variant="border"
startIcon={{ icon: faPlay }}
>
Run
</Button>
</svelte:fragment>
</ActionRow>
</div>
<Button
on:click={() => goto(`/scripts/run/${script.hash}`)}
color="dark"
size="xs"
variant="border"
startIcon={{ icon: faPlay }}
>
Run
</Button>
</div>
</a>
+4 -4
View File
@@ -38,13 +38,13 @@ export function displayDaysAgo(dateString: string): string {
if (nbSecondsAgo < 600) {
return `${nbSecondsAgo}s ago`
} else if (isToday(date)) {
return `today at ${date.toLocaleTimeString()}`
return `today at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
} else {
let dAgo = daysAgo(date)
if (dAgo == 0) {
return `yday at ${date.toLocaleTimeString()}`
return `yday at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
} else if (dAgo > 7) {
return `${dAgo + 1} days ago at ${date.toLocaleTimeString()}`
return `${dAgo + 1} days ago at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
} else {
return displayDate(dateString)
}
@@ -57,7 +57,7 @@ export function displayDate(dateString: string | undefined): string {
return ''
} else {
return `${date.getFullYear()}/${date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString()}`
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
}
}
+11 -14
View File
@@ -272,21 +272,18 @@
{:else}
<div class="grid md:grid-cols-2 gap-4 sm:grid-cols-1 xl:grid-cols-3 mt-2">
{#each flows as { summary, path, extra_perms, canWrite }}
<div
class="flex flex-col justify-between max-w-lg overflow-visible
shadow-sm shadow-blue-100 border border-gray-200
hover:border hover:border-gray-600 hover:border-opacity-60 bg-gray-50 py-2"
<a
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 text-gray-800 flex flex-col justify-between"
href="/flows/get/{path}"
>
<a href="/flows/get/{path}">
<div class="px-6 overflow-auto ">
<div class="font-semibold text-gray-700">
{!summary || summary.length == 0 ? path : summary}
</div>
<p class="text-gray-700 text-xs">
<a class="text-gray-700 text-xs" href="/flows/get/{path}">Path: {path} </a>
</p>
<div class="px-6 overflow-auto ">
<div class="font-semibold text-gray-700">
{!summary || summary.length == 0 ? path : summary}
</div>
</a>
<p class="text-gray-700 text-xs">
<a class="text-gray-700 text-xs" href="/flows/get/{path}">{path} </a>
</p>
</div>
<div class="flex flex-row pl-6 pr-2 mt-2">
<div class="mr-3 w-full">
<SharedBadge {canWrite} extraPerms={extra_perms} />
@@ -377,7 +374,7 @@
</div>
</div>
</div>
</div>
</a>
{/each}
</div>
{/if}
@@ -197,7 +197,9 @@
layout={[[{ h: 1.5, w: 40 }], 1, [4], 2.25, [{ h: 1.5, w: 30 }], 1, [10]]}
/>
{#if flow}
<p class="text-sm">Edited {displayDaysAgo(flow.edited_at ?? '')} by {flow.edited_by}</p>
<p class="text-sm text-gray-600"
>Edited {displayDaysAgo(flow.edited_at ?? '')} by {flow.edited_by}</p
>
<h2>{flow.summary}</h2>
<div class="prose">
+4 -3
View File
@@ -185,6 +185,7 @@
<tr>
<td class="my-12"
><a
class="break-all"
href="#{path}"
on:click={async () => {
resourceViewerTitle = `Resource ${path}`
@@ -200,7 +201,7 @@
>
<div class="mb-1 -mt-1"><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
</td>
<td class="px-2"><IconedResourceType name={resource_type} after={false} /></td>
<td class="px-2"><IconedResourceType name={resource_type} after={true} /></td>
<td><SvelteMarkdown source={description ?? ''} /></td>
<td>
{#if is_oauth}
@@ -282,7 +283,7 @@
{#if resourceTypes}
{#each resourceTypes as { name, description, schema, canWrite }}
<tr>
<td class="pr-4"
<td
><a
href="#{name}"
on:click={() => {
@@ -290,7 +291,7 @@
resourceViewerSchema = schema
typeModalMode = 'view-type'
resourceViewer.openModal()
}}><span class="text-gray-700"><IconedResourceType {name} /></span></a
}}><IconedResourceType after={true} {name} /></a
></td
>
<td><SvelteMarkdown source={description ?? ''} /></td>
+18 -14
View File
@@ -20,11 +20,11 @@
import JobDetail from '$lib/components/jobs/JobDetail.svelte'
import { Skeleton } from '$lib/components/common'
import Tooltip from '../../lib/components/Tooltip.svelte'
import { goto } from '$app/navigation'
let jobs: Job[] | undefined
let error: Error | undefined
let intervalId: NodeJS.Timer | undefined
let path: string = $page.params.path
let createdBefore: string | undefined = $page.url.searchParams.get('createdBefore') ?? undefined
let success: boolean | undefined =
@@ -39,7 +39,9 @@
let showOlderJobs = true
const jobsPerPage = 100
let jobKindsCat: string = $page.url.searchParams.get('job_kinds') ?? 'runs'
$: path = $page.params.path
$: jobKindsCat = $page.url.searchParams.get('job_kinds') ?? 'runs'
$: jobKinds = computeJobKinds(jobKindsCat)
@@ -148,14 +150,9 @@
<div class="flex items-center min-h-[48px] my-4">
<h1 class="mr-1">Runs {path ? `of ${path}` : ''}</h1>
<Tooltip>
Below is NOT all the runs that have ever been run. You only see the runs whose execution has
been permissioned with privilege that you are allowed to see. In most cases, it will only be
your personal runs, scheduled runs of groups that you are member of, and runs that are
permissioned at the group level of a group you are a member of. Hence, you can safely run
script with sensitive logs knowing that only the users with the relevant permissions would see
it. The permission of a run constraint the ephemeral bearer token that is passed to at
execution time of that run. This is why runs with less permissions are less sensitive because
the bearer token they use has less privilege.
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>
@@ -179,8 +176,15 @@
</select>
</div>
<div>
<div class="my-2">
<Tabs bind:selected={jobKindsCat}>
<div class="my-2 pb-2">
<Tabs
selected={jobKindsCat}
on:selected={(e) => {
const url = new URL($page.url)
url.searchParams.set('job_kinds', e.detail)
goto(url)
}}
>
<Tab value="all">All</Tab>
<Tab value="runs">Runs</Tab>
<Tab value="previews">Previews</Tab>
@@ -192,7 +196,7 @@
<div class="space-y-0">
{#each jobs as job}
<JobDetail {job} />
<div class="line w-8 h-4" />
<div class="line w-20 h-4" />
{/each}
</div>
{/if}
@@ -215,7 +219,7 @@
<style>
.line {
background: repeating-linear-gradient(to bottom, transparent 0 4px, #000 4px 8px) 50%/1px 100%
background: repeating-linear-gradient(to bottom, transparent 0 4px, #999 4px 8px) 50%/1px 100%
no-repeat;
}
</style>
+2 -2
View File
@@ -86,14 +86,14 @@
</tr>
<tbody slot="body">
{#each schedules as { path, edited_by, edited_at, schedule, offset_, enabled, script_path, is_flow, extra_perms, canWrite }}
<tr class={enabled ? '' : 'bg-gray-100'}>
<tr class={enabled ? '' : 'bg-gray-50'}>
<td class="max-w-sm"
><a class="break-all text-sm" href="/schedule/add?edit={path}&isFlow={is_flow}"
>{path}</a
>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</td>
<td class="whitespace-nowrap"
<td class="break-all"
><a class="text-sm" href="{is_flow ? '/flows/get' : '/scripts/get'}/{script_path}"
>{script_path}</a
><span class="text-2xs text-gray-500 bg-gray-100 font-mono ml-2"
+4 -4
View File
@@ -291,9 +291,9 @@
{:else}
<div class="grid md:grid-cols-2 gap-4 sm:grid-cols-1 xl:grid-cols-3 mt-2">
{#each scripts as { summary, path, hash, language, extra_perms, canWrite, lock_error_logs, kind }}
<div
class="flex flex-col justify-between gap-2 max-w-lg overflow-visible shadow-sm shadow-blue-100
border border-gray-200 bg-gray-50 py-2 hover:border-gray-600 hover:border-opacity-60"
<a
class="border p-4 rounded-sm shadow-sm space-y-2 hover:border-blue-600 text-gray-800 flex flex-col justify-between"
href="/scripts/get/{hash}"
>
<div class="flex flex-col gap-1">
<a href="/scripts/get/{hash}" class="px-6">
@@ -406,7 +406,7 @@
</Button>
</div>
</div>
</div>
</a>
{/each}
</div>
{/if}
+5 -4
View File
@@ -102,15 +102,16 @@
<tr>
<td
><a
class="break-all"
id="edit-{path}"
style="cursor: pointer;"
on:click={() => variableEditor.editVariable(path)}>{path}</a
on:click={() => variableEditor.editVariable(path)}
href="#{path}">{path}</a
>
<div><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
</td>
<td>{truncate(value ?? '******', 40)}</td>
<td>{is_secret ? 'secret' : 'visible'}</td>
<td>{description}</td>
<td>{truncate(description ?? '', 50)}</td>
<td>
{#if is_oauth}
<Icon
@@ -157,7 +158,7 @@
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account,
id: account ?? 0,
requestBody: {
path
}
+4 -2
View File
@@ -254,6 +254,7 @@ const config = {
'.table-custom': {
'& th': {
paddingTop: theme('spacing.3'),
paddingRight: theme('spacing.2'),
paddingBottom: theme('spacing.3'),
fontSize: theme('fontSize.sm'),
textAlign: 'left',
@@ -262,8 +263,9 @@ const config = {
textTransform: 'capitalize',
},
'& td': {
paddingTop: theme('spacing.4'),
paddingBottom: theme('spacing.4'),
paddingRight: theme('spacing.2'),
paddingTop: theme('spacing.2'),
paddingBottom: theme('spacing.2'),
fontSize: theme('fontSize.sm'),
color: theme('colors.gray.700'),
},