feat(frontend): rework script detail (#952)

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
Faton Ramadani
2022-11-28 10:19:49 +01:00
committed by GitHub
parent ffa74a3d30
commit fa2ae6d8fc
+283 -270
View File
@@ -30,7 +30,8 @@
faSpinner,
faGlobe,
faCodeFork,
faClipboard
faClipboard,
faArrowLeft
} from '@fortawesome/free-solid-svg-icons'
import Tooltip from '$lib/components/Tooltip.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
@@ -42,7 +43,17 @@
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { onDestroy } from 'svelte'
import HighlightCode from '$lib/components/HighlightCode.svelte'
import { Badge, Tabs, Tab, TabContent, Button, ActionRow, Alert } from '$lib/components/common'
import {
Badge,
Tabs,
Tab,
TabContent,
Button,
ActionRow,
Alert,
ButtonPopup,
ButtonPopupItem
} from '$lib/components/common'
import Skeleton from '../../../lib/components/common/skeleton/Skeleton.svelte'
import UserSettings from '$lib/components/UserSettings.svelte'
import Icon from 'svelte-awesome'
@@ -132,127 +143,53 @@
})
</script>
<UserSettings bind:this={userSettings} />
<Skeleton {loading} class="!px-4 sm:!px-6 md:!px-8 !max-w-6xl" layout={[0.75, [2, 0, 2], 1]} />
{#if script}
<ActionRow applyPageWidth>
<svelte:fragment slot="left">
<Dropdown
left={false}
dropdownItems={[
{
displayName: 'Archive',
icon: faArchive,
type: 'delete',
action: () => {
script?.hash && archiveScript(script.hash)
},
disabled: script.archived || !can_write
},
{
displayName: 'Delete',
icon: faTrash,
type: 'delete',
action: () => {
script?.hash && deleteScript(script.hash)
},
disabled: script.deleted || !($userStore?.is_admin ?? false)
}
]}><span class="text-red-500">delete...</span></Dropdown
>
<Button
disabled={deploymentInProgress}
target="_blank"
href={scriptToHubUrl(
script.content,
script.summary,
script.description ?? '',
script.kind,
script.language,
script.schema,
script.language == 'deno' ? '' : script.lock
).toString()}
variant="border"
color="light"
size="xs"
startIcon={{ icon: faGlobe }}
>
Publish to Hub
</Button>
<Button
on:click={() => shareModal.openDrawer()}
variant="border"
color="light"
size="xs"
startIcon={{ icon: faShare }}
disabled={!can_write}
>
Share
</Button>
<Button
href="/schedule/add?path={script.path}"
variant="border"
color="light"
size="xs"
startIcon={{ icon: faCalendar }}
>
Schedule
</Button>
</svelte:fragment>
<svelte:fragment slot="right">
<Button
href={`/scripts/run/${script.hash}`}
color="blue"
size="xs"
startIcon={{ icon: faPlay }}
>
Run
</Button>
<Button
href={`/scripts/edit/${script.hash}?step=2`}
color="blue"
size="xs"
startIcon={{ icon: faEdit }}
disabled={!can_write}
>
Edit
</Button>
{#if !topHash}
<Button
href={`/scripts/add?template=${script.path}`}
color="blue"
size="xs"
startIcon={{ icon: faCodeFork }}
>
Use as template/Fork
</Button>
{/if}
<Button href={`/runs/${script.path}`} size="xs" startIcon={{ icon: faList }}>
View runs
</Button>
</svelte:fragment>
</ActionRow>
{/if}
<CenteredPage>
<Skeleton {loading} layout={[[{ h: 1.5, w: 40 }], 1, [{ h: 1, w: 30 }]]} />
{#if script}
<div class="flex flex-row flex-wrap justify-between gap-4">
<div>
<div class="flex flex-col mb-2">
<h1 class="break-words py-2 mr-2">
{defaultIfEmptyString(script.summary, script.path)}
</h1>
{#if !emptyString(script.summary)}
<h2 class="font-bold pb-4">{script.path}</h2>
{/if}
<CenteredPage>
<Skeleton {loading} layout={[[{ h: 1.5, w: 40 }], 1, [{ h: 1, w: 30 }]]} />
<div class="relative w-full max-w-4xl mx-auto mt-16">
<div class="prose-sm mx-auto">
<div class="flex justify-between item-center">
<h1 class="mb-1">{defaultIfEmptyString(script.summary, script.path)}</h1>
<div class="flex flex-row-reverse gap-2 h-full">
<Button
href={`/scripts/run/${script.hash}`}
color="blue"
size="md"
startIcon={{ icon: faPlay }}
>
Run
</Button>
<Button
href={`/scripts/edit/${script.hash}?step=2`}
color="blue"
size="md"
startIcon={{ icon: faEdit }}
disabled={!can_write}
>
Edit
</Button>
{#if !topHash}
<Button
href={`/scripts/add?template=${script.path}`}
color="light"
size="md"
startIcon={{ icon: faCodeFork }}
>
Fork
</Button>
{/if}
</div>
</div>
<span class="text-lg font-semibold">{script.path}</span>
<div class="flex gap-2 flex-wrap">
<p>
Edited {displayDaysAgo(script.created_at || '')} by {script.created_by || 'unknown'}
</p>
<div class="flex items-center gap-2">
<span class="text-sm text-gray-500">
{#if script}
Edited {displayDaysAgo(script.created_at || '')} by {script.created_by || 'unknown'}
{/if}
</span>
<Badge color="dark-gray">
{truncateHash(script?.hash ?? '')}
</Badge>
@@ -278,164 +215,240 @@
<SharedBadge canWrite={can_write} extraPerms={script?.extra_perms ?? {}} />
</div>
</div>
</div>
</div>
{/if}
<ShareModal bind:this={shareModal} kind="script" path={script?.path ?? ''} />
<div class="flex my-4 gap-2 flex-wrap">
<Button
disabled={deploymentInProgress}
target="_blank"
href={scriptToHubUrl(
script.content,
script.summary,
script.description ?? '',
script.kind,
script.language,
script.schema,
script.language == 'deno' ? '' : script.lock
).toString()}
variant="border"
color="light"
size="xs"
startIcon={{ icon: faGlobe }}
>
Publish to Hub
</Button>
<Button
on:click={() => shareModal.openDrawer()}
variant="border"
color="light"
size="xs"
startIcon={{ icon: faShare }}
disabled={!can_write}
>
Share
</Button>
<Button
href="/schedule/add?path={script.path}"
variant="border"
color="light"
size="xs"
startIcon={{ icon: faCalendar }}
>
Schedule
</Button>
<Button
href={`/runs/${script.path}`}
size="xs"
startIcon={{ icon: faList }}
color="light"
variant="border"
>
View runs
</Button>
{#if Array.isArray(script.parent_hashes) && script.parent_hashes.length > 0}
<ButtonPopup
color="dark"
variant="contained"
size="xs"
startIcon={{ icon: faArrowLeft }}
href="/scripts/get/{script.parent_hashes[0]}"
>
<svelte:fragment slot="main"
>Previous version ({script.parent_hashes.length})</svelte:fragment
>
<div class="flex flex-col gap-8 max-w-7xl pt-4 pb-2">
<Skeleton {loading} layout={[[3], 1]} />
{#if script}
<div class="flex flex-col lg:flex-row gap-4 mt-4">
<div class="lg:w-1/2">
<h3 class="text-lg mb-1 font-bold text-gray-600">Description</h3>
<div class="prose text-sm box">
<SvelteMarkdown source={defaultIfEmptyString(script.description, 'No description')} />
</div>
{#each script.parent_hashes as hash}
<ButtonPopupItem href="/scripts/get/{hash}" btnClasses="!m-0">
{hash}
</ButtonPopupItem>
{/each}
</ButtonPopup>
{/if}
</div>
<div class="lg:w-1/2">
<h3 class="text-lg mb-1 font-bold text-gray-600">Versions</h3>
<Skeleton {loading} layout={[[8.5]]} />
{#if script}
<div class="box">
<h4 class="font-bold text-gray-500">Current</h4>
<div class="mt-1">
{script?.hash}
{#if script.lock_error_logs || topHash || script.archived || script.deleted}
<div class="flex flex-col gap-2 my-2">
{#if script.lock_error_logs}
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
<p class="font-bold">Error deploying this script</p>
<p
>This script has not been deployed successfully because of the following errors:</p
>
<pre class="w-full text-xs mt-2 whitespace-pre-wrap">{script.lock_error_logs}</pre>
</div>
<h4 class="font-bold text-gray-500 mt-2">Previous</h4>
{#if script?.parent_hashes?.length}
<ul class="max-h-20 overflow-y-auto">
{#each script.parent_hashes as hash}
<li class="mt-1">
<a href="/scripts/get/{hash}">{hash}</a>
</li>
{/each}
</ul>
{:else}
<p class="text-sm text-gray-500">There are no previous versions</p>
{/if}
</div>
{/if}
</div>
</div>
{/if}
{#if topHash}
<Alert type="warning" title="Not HEAD">
This hash is not HEAD (latest non-archived version at this path) :
<a href="/scripts/get/{topHash}">Go to the HEAD of this path</a>
</Alert>
{/if}
{#if script.archived && !topHash}
<Alert type="error" title="Archived">This version was archived</Alert>
{/if}
{#if script.deleted}
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4" role="alert">
<p class="font-bold">Deleted</p>
<p>The content of this script was deleted (by an admin, no less)</p>
</div>
{/if}
</div>
{/if}
{#if script.lock_error_logs || topHash || script.archived || script.deleted}
<div class="flex flex-col gap-2">
{#if script.lock_error_logs}
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
<p class="font-bold">Error deploying this script</p>
<p>This script has not been deployed successfully because of the following errors:</p>
<pre class="w-full text-xs mt-2 whitespace-pre-wrap">{script.lock_error_logs}</pre>
</div>
{/if}
{#if topHash}
<Alert type="warning" title="Not HEAD">
This hash is not HEAD (latest non-archived version at this path) :
<a href="/scripts/get/{topHash}">Go to the HEAD of this path</a>
</Alert>
{/if}
{#if script.archived && !topHash}
<Alert type="error" title="Archived">This version was archived</Alert>
{/if}
{#if script.deleted}
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4" role="alert">
<p class="font-bold">Deleted</p>
<p>The content of this script was deleted (by an admin, no less)</p>
</div>
{/if}
<div>
<h3>Description</h3>
<SvelteMarkdown source={defaultIfEmptyString(script.description, 'No description')} />
</div>
{/if}
{/if}
<div class="mt-4">
<h2 class="text-lg mb-1 font-bold text-gray-600">
Arguments JSON schema
<Tooltip>
The jsonschema defines the constraints that the payload must respect to be compatible with
the input parameters of this script. The UI form is generated automatically from the
script jsonschema. See
<a href="https://json-schema.org/"> jsonschema documentation </a>
</Tooltip>
</h2>
<Skeleton {loading} layout={[[15]]} />
{#if script}
<div class="box mt-2">
<div>
<h3>
Arguments JSON schema
<Tooltip>
The jsonschema defines the constraints that the payload must respect to be compatible
with the input parameters of this script. The UI form is generated automatically from
the script jsonschema. See
<a href="https://json-schema.org/" class="text-blue-500">
jsonschema documentation
</a>
</Tooltip>
</h3>
<Skeleton {loading} layout={[[15]]} />
<SchemaViewer schema={script.schema} />
</div>
{/if}
</div>
<div>
<h2 class="text-lg mb-1 mt-4 font-bold text-gray-600 border-b">Code</h2>
<Skeleton {loading} layout={[[20]]} />
{#if script}
<HighlightCode language={script.language} code={script.content} />
{/if}
</div>
</div>
<h3 id="webhooks" class="text-lg mb-1 mt-8 font-bold text-gray-600"
>Webhooks<Tooltip
>To trigger this script with a webhook, do a POST request to the endpoints below. Scripts are
not public and can only be run by users with at least view rights on them. You will need to
pass a bearer token to authentify as a user. You can either pass it as a Bearer token or as
query arg `?token=XXX`. <a href="https://docs.windmill.dev/docs/getting_started/webhooks"
>See docs</a
></Tooltip
></h3
>
<Skeleton {loading} layout={[[8.5]]} />
{#if script}
<div class="box max-w-3xl">
<Tabs selected="uuid">
<Tab value="uuid">UUID</Tab>
<Tab value="result">Result</Tab>
<svelte:fragment slot="content">
{#each Object.keys(webhooks) as key}
<TabContent value={key}>
<ul>
{#each Object.keys(webhooks[key]) as type}
{@const url = webhooks[key][type]}
<li class="flex justify-between items-center mt-2">
<a
on:click={(e) => {
e.preventDefault()
copyToClipboard(url)
}}
href={$page.url.protocol + '//' + url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
<span class="text-gray-700 ml-2">
<Icon data={faClipboard} />
</span>
</a>
<Badge color="dark-gray" capitalize>
{type}
</Badge>
</li>
{/each}
</ul>
<div class="flex flex-row-reverse mt-2">
<Button size="xs" on:click={userSettings.openDrawer}>Create token</Button>
</div>
</TabContent>
{/each}
</svelte:fragment>
</Tabs>
<div>
<h3>Code</h3>
<Skeleton {loading} layout={[[20]]} />
<Tabs selected="code">
<Tab value="code">Code</Tab>
<Tab value="dependencies">Dependencies lock file</Tab>
<svelte:fragment slot="content">
<TabContent value="code">
<div class="border rounded-sm mt-2">
<HighlightCode language={script.language} code={script.content} />
</div>
</TabContent>
<TabContent value="dependencies">
<div class="border rounded-sm mt-2">
{#if script?.lock}
<pre>{script.lock}</pre>
{:else}
<p>There is no lock file for this script</p>
{/if}
</div>
</TabContent>
</svelte:fragment>
</Tabs>
</div>
<div>
<h3 id="webhooks">
Webhooks
<Tooltip>
To trigger this script with a webhook, do a POST request to the endpoints below.
Scripts are not public and can only be run by users with at least view rights on them.
You will need to pass a bearer token to authentify as a user. You can either pass it
as a Bearer token or as query arg `?token=XXX`.
<a
href="https://docs.windmill.dev/docs/getting_started/webhooks"
class="text-blue-500"
>
See docs
</a>
</Tooltip>
</h3>
<Skeleton {loading} layout={[[8.5]]} />
<Tabs selected="uuid">
<Tab value="uuid">UUID</Tab>
<Tab value="result">Result</Tab>
<svelte:fragment slot="content">
{#each Object.keys(webhooks) as key}
<TabContent value={key}>
<ul>
{#each Object.keys(webhooks[key]) as type}
{@const url = webhooks[key][type]}
<li class="flex justify-between items-center mt-2">
<a
on:click={(e) => {
e.preventDefault()
copyToClipboard(url)
}}
href={$page.url.protocol + '//' + url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
<span class="text-gray-700 ml-2">
<Icon data={faClipboard} />
</span>
</a>
<Badge color="dark-gray" capitalize>
{type}
</Badge>
</li>
{/each}
</ul>
<div class="flex flex-row-reverse mt-2">
<Button size="xs" on:click={userSettings.openDrawer}>Create token</Button>
</div>
</TabContent>
{/each}
</svelte:fragment>
</Tabs>
</div>
<div>
<h3>Danger zone</h3>
<div class="flex gap-2">
<Button
size="xs"
on:click={() => {
script?.hash && deleteScript(script.hash)
}}
color="red"
variant="contained"
startIcon={{ icon: faTrash }}
>
Delete
</Button>
<Button
size="xs"
on:click={() => {
script?.hash && archiveScript(script.hash)
}}
color="red"
variant="border"
startIcon={{ icon: faArchive }}
>
Archive
</Button>
</div>
</div>
</div>
</div>
{/if}
<div>
<h2 class="text-lg mb-1 font-bold text-gray-600 border-b mt-8">Dependencies lock file</h2>
<Skeleton {loading} layout={[[5]]} />
{#if script}
{#if script?.lock}
<pre class="text-xs overflow-auto">{script.lock}</pre>
{:else}
<p class="text-sm text-gray-500">There is no lock file for this script</p>
{/if}
{/if}
</div>
</CenteredPage>
</CenteredPage>
{/if}
<UserSettings bind:this={userSettings} />
<ShareModal bind:this={shareModal} kind="script" path={script?.path ?? ''} />