mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
UI refactor (#973)
* fix(frontend): ui refactor * fix(frontend): WIP * fix(frontend): WIP * fix(frontend): WIP * fix(frontend): remove virtual table * fix(frontend): Add virtual list * fix(frontend): fix number of items * fix(frontend): revert changes * fix(frontend): fix build
This commit is contained in:
@@ -4831,7 +4831,15 @@ components:
|
||||
execution_mode:
|
||||
type: string
|
||||
enum: [viewer, publisher, anonymous]
|
||||
|
||||
required:
|
||||
- id
|
||||
- workspace_id
|
||||
- path
|
||||
- summary
|
||||
- version
|
||||
- extra_perms
|
||||
- edited_at
|
||||
- execution_mode
|
||||
AppWithLastVersion:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Generated
+11
@@ -12,6 +12,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@leeoniya/ufuzzy": "^0.8.0",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@sveltejs/svelte-virtual-list": "^3.0.1",
|
||||
"@types/node": "^18.11.9",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
@@ -483,6 +484,11 @@
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/svelte-virtual-list": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-virtual-list/-/svelte-virtual-list-3.0.1.tgz",
|
||||
"integrity": "sha512-aF9TptS7NKKS7/TqpsxQBSDJ9Q0XBYzBehCeIC5DzdMEgrJZpIYao9LRLnyyo6SVodpapm2B7FE/Lj+FSA5/SQ=="
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.1.tgz",
|
||||
@@ -7341,6 +7347,11 @@
|
||||
"tiny-glob": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"@sveltejs/svelte-virtual-list": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-virtual-list/-/svelte-virtual-list-3.0.1.tgz",
|
||||
"integrity": "sha512-aF9TptS7NKKS7/TqpsxQBSDJ9Q0XBYzBehCeIC5DzdMEgrJZpIYao9LRLnyyo6SVodpapm2B7FE/Lj+FSA5/SQ=="
|
||||
},
|
||||
"@sveltejs/vite-plugin-svelte": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.1.tgz",
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@leeoniya/ufuzzy": "^0.8.0",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@sveltejs/svelte-virtual-list": "^3.0.1",
|
||||
"@types/node": "^18.11.9",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
.monaco-workbench > .notifications-toasts.visible {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
svelte-virtual-list-row {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
svelte-virtual-list-contents > * + * {
|
||||
border-top-width: 1px !important;
|
||||
border-bottom-width: 0px !important
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@@ -31,3 +42,4 @@
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
scale={0.6}
|
||||
class="inline mr-2 {item.type == 'delete' ? 'text-red-500' : 'text-gray-700'}"
|
||||
/>
|
||||
{/if}{item.displayName}
|
||||
{/if}
|
||||
{item.displayName}
|
||||
</button>
|
||||
{:else if item.href}
|
||||
<a
|
||||
@@ -93,21 +94,26 @@
|
||||
tabindex="-1"
|
||||
id="user-menu-item-{name}-{i}}"
|
||||
class:disabled={item.disabled}
|
||||
>{#if item.icon}
|
||||
>
|
||||
{#if item.icon}
|
||||
<Icon
|
||||
data={item.icon}
|
||||
scale={0.6}
|
||||
class="inline mr-2 {item.type == 'delete' ? 'text-red-500' : 'text-gray-700'}"
|
||||
/>
|
||||
{/if}{item.displayName}</a
|
||||
>
|
||||
{/if}
|
||||
{item.displayName}
|
||||
</a>
|
||||
{:else}
|
||||
<span
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-{name}-{i}}">{item.displayName}</span
|
||||
>{/if}
|
||||
id="user-menu-item-{name}-{i}}"
|
||||
>
|
||||
{item.displayName}
|
||||
</span>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { FlowService, ScriptService, type Flow, type Script } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import {
|
||||
faArchive,
|
||||
faCalendarAlt,
|
||||
faCodeFork,
|
||||
faEdit,
|
||||
faEye,
|
||||
faList,
|
||||
faPlay,
|
||||
faShare,
|
||||
faWind
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { Button } from './common'
|
||||
import Dropdown from './Dropdown.svelte'
|
||||
import SharedBadge from './SharedBadge.svelte'
|
||||
import type ShareModal from './ShareModal.svelte'
|
||||
import Star from './Star.svelte'
|
||||
|
||||
export let flow: Flow & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
|
||||
export let shareModal: ShareModal
|
||||
const { summary, path, extra_perms, canWrite, workspace_id } = flow
|
||||
|
||||
async function archiveFlow(path: string): Promise<void> {
|
||||
try {
|
||||
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path })
|
||||
dispatch('change')
|
||||
sendUserToast(`Successfully archived flow ${path}`)
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not archive this flow ${err.body}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="border border-gray-400 py-2 px-4 rounded-sm shadow-sm hover:border-blue-600 text-gray-800 flex flex-row items-center justify-between"
|
||||
href="/flows/get/{path}"
|
||||
>
|
||||
<div class="flex flex-col gap-1 w-full h-full">
|
||||
<div class="font-semibold text-gray-700 truncate">
|
||||
<Icon data={faWind} class="mr-2" scale={1} />
|
||||
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{!summary || summary.length == 0 ? path : summary}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row justify-between w-full grow gap-2 items-start">
|
||||
<div class="text-gray-700 text-xs flex flex-row flex-wrap gap-x-1 items-center"
|
||||
>{path}
|
||||
<Star
|
||||
workspace_id={workspace_id ?? $workspaceStore ?? ''}
|
||||
kind="flow"
|
||||
{path}
|
||||
{starred}
|
||||
on:starred={() => dispatch('change')}
|
||||
/>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</div>
|
||||
<div class="flex flex-row-reverse place gap-x-2 pt-4">
|
||||
<div>
|
||||
<Dropdown
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View flow',
|
||||
icon: faEye,
|
||||
href: `/flows/get/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: faEdit,
|
||||
href: `/flows/edit/${path}`,
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Use as template/Fork',
|
||||
icon: faCodeFork,
|
||||
href: `/flows/add?template=${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'View runs',
|
||||
icon: faList,
|
||||
href: `/runs/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Schedule',
|
||||
icon: faCalendarAlt,
|
||||
href: `/schedule/add?path=${path}&isFlow=true`
|
||||
},
|
||||
{
|
||||
displayName: 'Share',
|
||||
icon: faShare,
|
||||
action: () => {
|
||||
shareModal.openDrawer(path)
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Archive',
|
||||
icon: faArchive,
|
||||
action: () => {
|
||||
path ? archiveFlow(path) : null
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !canWrite
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button color="dark" size="xs" href="/flows/run/{path}" startIcon={{ icon: faPlay }}
|
||||
>Run</Button
|
||||
>
|
||||
</div>
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="dark"
|
||||
variant="border"
|
||||
size="xs"
|
||||
href="/flows/edit/{path}"
|
||||
startIcon={{ icon: faEdit }}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="dark"
|
||||
variant="border"
|
||||
size="xs"
|
||||
href="/flows/add?template={path}"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div></div
|
||||
></a
|
||||
>
|
||||
@@ -6,11 +6,11 @@
|
||||
export let args: any
|
||||
</script>
|
||||
|
||||
<TableCustom class="px-10 py-2">
|
||||
<tr slot="header-row"
|
||||
><th>Argument</th>
|
||||
<th>Value</th></tr
|
||||
>
|
||||
<TableCustom class="py-2">
|
||||
<tr slot="header-row">
|
||||
<th>Argument</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#if args && Object.keys(args).length > 0}
|
||||
{#each Object.entries(args) as [arg, value]}
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
<div class="flex flex-row flex-wrap justify-between mt-4 mb-2 min-h-[48px]">
|
||||
{#if primary}
|
||||
<span class="flex items-center space-x-2">
|
||||
<h1 class="mr-0.5">{title}</h1>
|
||||
<h1>{title}</h1>
|
||||
{#if tooltip}
|
||||
<Tooltip>{tooltip}</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="flex items-center space-x-2">
|
||||
<h2 class="mr-0.5">{title}</h2>
|
||||
<h2>{title}</h2>
|
||||
{#if tooltip}
|
||||
<Tooltip>{tooltip}</Tooltip>
|
||||
{/if}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ScriptService, type Script } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import {
|
||||
faArchive,
|
||||
faCalendarAlt,
|
||||
faCodeFork,
|
||||
faEdit,
|
||||
faEye,
|
||||
faList,
|
||||
faPlay,
|
||||
faScroll,
|
||||
faShare
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { Badge, Button } from './common'
|
||||
import { LanguageIcon } from './common/languageIcons'
|
||||
import Dropdown from './Dropdown.svelte'
|
||||
import SharedBadge from './SharedBadge.svelte'
|
||||
import type ShareModal from './ShareModal.svelte'
|
||||
import Star from './Star.svelte'
|
||||
|
||||
export let script: Script & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
export let shareModal: ShareModal
|
||||
let {
|
||||
summary,
|
||||
path,
|
||||
hash,
|
||||
language,
|
||||
extra_perms,
|
||||
canWrite,
|
||||
lock_error_logs,
|
||||
kind,
|
||||
workspace_id
|
||||
} = script
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
async function archiveScript(path: string): Promise<void> {
|
||||
await ScriptService.archiveScriptByPath({ workspace: $workspaceStore!, path })
|
||||
dispatch('change')
|
||||
sendUserToast(`Successfully archived script ${path}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="border border-gray-400 py-2 px-4 rounded-sm shadow-sm hover:border-blue-600 text-gray-800"
|
||||
href="/scripts/get/{hash}"
|
||||
>
|
||||
<div class="flex flex-col gap-1 w-full h-full">
|
||||
<div class="font-semibold text-gray-700 truncate">
|
||||
<Icon data={faScroll} class="mr-2" scale={1} />
|
||||
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{!summary || summary.length == 0 ? path : summary}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row justify-between w-full grow gap-2 items-start">
|
||||
<div class="text-gray-700 text-xs flex flex-row flex-wrap gap-x-1 items-center">
|
||||
{path}
|
||||
<Star
|
||||
kind="script"
|
||||
{path}
|
||||
{starred}
|
||||
workspace_id={workspace_id ?? $workspaceStore ?? ''}
|
||||
on:starred={() => dispatch('change')}
|
||||
/>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
<div><LanguageIcon height={16} lang={language} /></div>
|
||||
{#if kind != 'script'}
|
||||
<Badge color="blue" capitalize>{kind}</Badge>
|
||||
{/if}
|
||||
{#if lock_error_logs}
|
||||
<Badge color="red">Deployment error</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col items-end grow pt-4">
|
||||
<div class="flex flex-row-reverse place gap-x-2">
|
||||
<div>
|
||||
<Dropdown
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View script',
|
||||
icon: faEye,
|
||||
href: `/scripts/get/${hash}`
|
||||
},
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: faEdit,
|
||||
href: `/scripts/edit/${hash}`,
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Edit code',
|
||||
icon: faEdit,
|
||||
href: `/scripts/edit/${hash}?step=2`,
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Use as template',
|
||||
icon: faCodeFork,
|
||||
href: `/scripts/add?template=${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'View runs',
|
||||
icon: faList,
|
||||
href: `/runs/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Schedule',
|
||||
icon: faCalendarAlt,
|
||||
href: `/schedule/add?path=${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Share',
|
||||
icon: faShare,
|
||||
action: () => {
|
||||
shareModal.openDrawer(path)
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Archive',
|
||||
icon: faArchive,
|
||||
action: () => {
|
||||
path ? archiveScript(path) : null
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !canWrite
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button color="dark" size="xs" startIcon={{ icon: faPlay }} href="/scripts/run/{hash}">
|
||||
Run
|
||||
</Button>
|
||||
</div>
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
size="xs"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/scripts/edit/{hash}?step=2"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="dark"
|
||||
variant="border"
|
||||
size="xs"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/scripts/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div></div
|
||||
>
|
||||
</div></a
|
||||
>
|
||||
@@ -167,7 +167,7 @@
|
||||
<Pane size={40} minSize={10}>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={33}>
|
||||
<div class="w-full bg-gray-100 px-2 text-sm">Preview</div>
|
||||
<div class="w-full border-b p-2 text-xs font-bold">Preview</div>
|
||||
<div class="px-2">
|
||||
<div class="break-words relative font-sans">
|
||||
<SchemaForm compact {schema} bind:args bind:isValid />
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</script>
|
||||
|
||||
{#if kind === 'read' || kind === 'write'}
|
||||
<Badge icon={{ data: faPeopleGroup }} capitalize color="blue">
|
||||
<Badge icon={{ data: faPeopleGroup }} capitalize color="blue" baseClass="border border-blue-200">
|
||||
{kind}
|
||||
{#if reason}
|
||||
<Tooltip>{reason}</Tooltip>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { FavoriteService } from '$lib/gen'
|
||||
import { starStore, workspaceStore } from '$lib/stores'
|
||||
import { starStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { faStar } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { AppService, Policy } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
|
||||
let path: string = ''
|
||||
let initialPath = ''
|
||||
let pathError = ''
|
||||
|
||||
import type { App } from '$lib/components/apps/types'
|
||||
import { goto } from '$app/navigation'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
|
||||
let drawerOpen = false
|
||||
|
||||
function closeDrawer() {
|
||||
drawerOpen = false
|
||||
}
|
||||
|
||||
async function createApp() {
|
||||
const appJson: App = {
|
||||
grid: [],
|
||||
title: 'New app',
|
||||
inlineScripts: {}
|
||||
}
|
||||
|
||||
const policy = {
|
||||
triggerables: {},
|
||||
execution_mode: Policy.execution_mode.PUBLISHER,
|
||||
on_behalf_of: `u/${$userStore?.username}`
|
||||
}
|
||||
try {
|
||||
const appId = await AppService.createApp({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
value: appJson,
|
||||
path,
|
||||
summary: 'App summary',
|
||||
policy
|
||||
}
|
||||
})
|
||||
|
||||
goto(`/apps/edit/${appId}`)
|
||||
} catch (e) {
|
||||
sendUserToast('Error creating app', e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:open={drawerOpen} size="800px">
|
||||
<DrawerContent title="Add an app" on:close={() => closeDrawer()}>
|
||||
<Path bind:error={pathError} bind:path {initialPath} namePlaceholder="my_app" kind="app">
|
||||
<div slot="ownerToolkit">
|
||||
App 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>
|
||||
|
||||
<Button on:click={() => createApp()}>Create app</Button>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
spacingSize="lg"
|
||||
startIcon={{ icon: faPlus }}
|
||||
on:click={() => (drawerOpen = true)}
|
||||
>
|
||||
New app
|
||||
</Button>
|
||||
@@ -3,6 +3,9 @@
|
||||
export let value: string
|
||||
export let debounceDelay: number = 500
|
||||
|
||||
let parentClass: string | undefined = undefined
|
||||
export { parentClass as class }
|
||||
|
||||
let timer: NodeJS.Timeout
|
||||
|
||||
function debounce(event: KeyboardEvent): void {
|
||||
@@ -15,4 +18,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<input {placeholder} on:keyup={debounce} />
|
||||
<input {placeholder} on:keyup={debounce} class={parentClass} />
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
export let large = false
|
||||
export let href = ''
|
||||
export let rounded = false
|
||||
export let index = false
|
||||
export let dismissable = false
|
||||
export let baseClass = 'text-center -mb-0.5'
|
||||
export let baseClass = 'text-center'
|
||||
export let capitalize = false
|
||||
export let icon: BadgeIconProps | undefined = undefined
|
||||
|
||||
@@ -51,10 +50,6 @@
|
||||
href &&
|
||||
(color.startsWith(ColorModifier) ? hovers[color.replace(ColorModifier, '')] : hovers[color]),
|
||||
rounded ? 'rounded-full px-2 py-1' : 'rounded px-2.5 py-0.5',
|
||||
index
|
||||
? 'absolute flex justify-center items-center font-bold overflow-hidden border-2 border-white dark:border-gray-900' +
|
||||
(large ? 'w-7 h-7 -top-3.5 -right-3.5' : 'w-6 h-6 -top-3 -right-3')
|
||||
: '',
|
||||
$$props.class
|
||||
)
|
||||
$: iconProps = icon ? { ...defaulIconProps, ...icon } : { data: undefined }
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
export let target: ButtonType.Target = '_self'
|
||||
export let startIcon: ButtonType.Icon | undefined = undefined
|
||||
export let endIcon: ButtonType.Icon | undefined = undefined
|
||||
export let spacingSize: ButtonType.Size = size
|
||||
|
||||
let ref: ButtonType.Element
|
||||
|
||||
@@ -24,7 +25,8 @@
|
||||
size,
|
||||
color,
|
||||
variant,
|
||||
disabled
|
||||
disabled,
|
||||
spacingSize
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import type { ListableApp } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { faEdit, faEye } from '@fortawesome/free-solid-svg-icons'
|
||||
import Button from '../button/Button.svelte'
|
||||
import Row from './Row.svelte'
|
||||
|
||||
export let app: ListableApp & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
|
||||
let { summary, path, extra_perms, canWrite, workspace_id } = app
|
||||
</script>
|
||||
|
||||
<Row
|
||||
href={`/apps/get/${path}`}
|
||||
kind="app"
|
||||
{marked}
|
||||
{path}
|
||||
{summary}
|
||||
workspaceId={workspace_id ?? $workspaceStore ?? ''}
|
||||
{starred}
|
||||
on:change
|
||||
>
|
||||
<svelte:fragment slot="badges">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Dropdown
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View app',
|
||||
icon: faEye,
|
||||
href: `/apps/get/${path}`
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/apps/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/apps/get/{path}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
@@ -0,0 +1,152 @@
|
||||
<script lang="ts">
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import type ShareModal from '$lib/components/ShareModal.svelte'
|
||||
|
||||
import { FlowService, type Flow } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import {
|
||||
faArchive,
|
||||
faCalendarAlt,
|
||||
faCodeFork,
|
||||
faEdit,
|
||||
faEye,
|
||||
faList,
|
||||
faPlay,
|
||||
faShare
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import Button from '../button/Button.svelte'
|
||||
import Row from './Row.svelte'
|
||||
|
||||
export let flow: Flow & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
export let shareModal: ShareModal
|
||||
|
||||
let { summary, path, extra_perms, canWrite, workspace_id } = flow
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function archiveFlow(path: string): Promise<void> {
|
||||
try {
|
||||
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path })
|
||||
dispatch('change')
|
||||
sendUserToast(`Successfully archived flow ${path}`)
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not archive this flow ${err.body}`, true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Row
|
||||
href={`/flows/get/${path}`}
|
||||
kind="flow"
|
||||
workspaceId={workspace_id ?? $workspaceStore ?? ''}
|
||||
{marked}
|
||||
{path}
|
||||
{summary}
|
||||
{starred}
|
||||
on:change
|
||||
>
|
||||
<svelte:fragment slot="badges">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Dropdown
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View flow',
|
||||
icon: faEye,
|
||||
href: `/flows/get/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: faEdit,
|
||||
href: `/flows/edit/${path}`,
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Use as template/Fork',
|
||||
icon: faCodeFork,
|
||||
href: `/flows/add?template=${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'View runs',
|
||||
icon: faList,
|
||||
href: `/runs/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Schedule',
|
||||
icon: faCalendarAlt,
|
||||
href: `/schedule/add?path=${path}&isFlow=true`
|
||||
},
|
||||
{
|
||||
displayName: 'Share',
|
||||
icon: faShare,
|
||||
action: () => {
|
||||
shareModal.openDrawer && shareModal.openDrawer(path)
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Archive',
|
||||
icon: faArchive,
|
||||
action: () => {
|
||||
path ? archiveFlow(path) : null
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !canWrite
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/flows/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/flows/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/flows/get/{path}"
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
href="/flows/run/{path}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import Star from '$lib/components/Star.svelte'
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import RowIcon from './RowIcon.svelte'
|
||||
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let kind: 'script' | 'flow' | 'app' = 'script'
|
||||
export let summary: string | undefined = undefined
|
||||
export let path: string
|
||||
export let href: string
|
||||
export let workspaceId: string
|
||||
</script>
|
||||
|
||||
<a class="hover:bg-gray-50 cursor-pointer w-full flex items-center p-4 gap-4" {href}>
|
||||
<RowIcon {kind} />
|
||||
|
||||
<div class="w-full">
|
||||
<div class="text-gray-900 flex-wrap text-md font-semibold mb-1">
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{!summary || summary.length == 0 ? path : summary}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-gray-600 text-xs ">
|
||||
{path}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-96 flex flex-row max-w-xs gap-1 items-start flex-wrap">
|
||||
<slot name="badges" />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1 items-center justify-end">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
<div class="text-left text-sm font-semibold text-gray-900">
|
||||
<Star
|
||||
{kind}
|
||||
{path}
|
||||
{starred}
|
||||
workspace_id={workspaceId}
|
||||
on:starred={() => {
|
||||
dispatch('change')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Code2, LayoutDashboard, Wind } from 'svelte-lucide'
|
||||
|
||||
export let kind: 'script' | 'flow' | 'app'
|
||||
|
||||
const icon = {
|
||||
script: Code2,
|
||||
flow: Wind,
|
||||
app: LayoutDashboard
|
||||
}[kind]
|
||||
|
||||
const color = {
|
||||
script: 'bg-blue-50 border-blue-200',
|
||||
flow: 'bg-[#f0fdfa] border-[#99f6e4]',
|
||||
app: 'bg-[#fff7ed] border-orange-200'
|
||||
}[kind]
|
||||
|
||||
const iconColor = {
|
||||
script: '#60A5FA',
|
||||
flow: '#14b8a6',
|
||||
app: '#fb923c'
|
||||
}[kind]
|
||||
</script>
|
||||
|
||||
<div class={classNames('rounded-md p-1 flex justify-center items-center border', color)}>
|
||||
<svelte:component this={icon} size="18px" color={iconColor} />
|
||||
</div>
|
||||
@@ -0,0 +1,167 @@
|
||||
<script lang="ts">
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import type ShareModal from '$lib/components/ShareModal.svelte'
|
||||
|
||||
import { ScriptService, type Script } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { capitalize, sendUserToast } from '$lib/utils'
|
||||
import {
|
||||
faArchive,
|
||||
faCalendarAlt,
|
||||
faCodeFork,
|
||||
faEdit,
|
||||
faEye,
|
||||
faList,
|
||||
faPlay,
|
||||
faShare
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Badge from '../badge/Badge.svelte'
|
||||
import Button from '../button/Button.svelte'
|
||||
import Row from './Row.svelte'
|
||||
|
||||
export let script: Script & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
export let shareModal: ShareModal
|
||||
|
||||
let {
|
||||
summary,
|
||||
path,
|
||||
hash,
|
||||
language,
|
||||
extra_perms,
|
||||
canWrite,
|
||||
lock_error_logs,
|
||||
kind,
|
||||
workspace_id
|
||||
} = script
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function archiveScript(path: string): Promise<void> {
|
||||
await ScriptService.archiveScriptByPath({ workspace: $workspaceStore!, path })
|
||||
dispatch('change')
|
||||
sendUserToast(`Successfully archived script ${path}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Row
|
||||
href={`/scripts/get/${hash}`}
|
||||
kind="script"
|
||||
{marked}
|
||||
{path}
|
||||
{summary}
|
||||
workspaceId={workspace_id ?? $workspaceStore ?? ''}
|
||||
{starred}
|
||||
>
|
||||
<svelte:fragment slot="badges">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
{#if lock_error_logs}
|
||||
<Badge color="red" baseClass="border border-red-200">Deployment failed</Badge>
|
||||
{/if}
|
||||
<Badge color="gray" baseClass="border">{capitalize(kind)}</Badge>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Dropdown
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'View script',
|
||||
icon: faEye,
|
||||
href: `/scripts/get/${hash}`
|
||||
},
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: faEdit,
|
||||
href: `/scripts/edit/${hash}`,
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Edit code',
|
||||
icon: faEdit,
|
||||
href: `/scripts/edit/${hash}?step=2`,
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Use as template',
|
||||
icon: faCodeFork,
|
||||
href: `/scripts/add?template=${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'View runs',
|
||||
icon: faList,
|
||||
href: `/runs/${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Schedule',
|
||||
icon: faCalendarAlt,
|
||||
href: `/schedule/add?path=${path}`
|
||||
},
|
||||
{
|
||||
displayName: 'Share',
|
||||
icon: faShare,
|
||||
action: () => {
|
||||
shareModal.openDrawer && shareModal.openDrawer(path)
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Archive',
|
||||
icon: faArchive,
|
||||
action: () => {
|
||||
path ? archiveScript(path) : null
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !canWrite
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/scripts/edit/{hash}?step=2"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
href="/scripts/add?template={path}"
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
href="/scripts/get/{hash}"
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
href="/scripts/run/{hash}"
|
||||
color="dark"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-hidden rounded-md border">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<slot />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,10 +17,10 @@
|
||||
btnClasses={classNames(
|
||||
'border-gray-200 focus:ring-0 w-full',
|
||||
position === 'left' ? 'rounded-none rounded-l-lg border' : '',
|
||||
position === 'center' ? 'rounded-none border-t border-b' : '',
|
||||
position === 'right' ? 'rounded-none rounded-r-md !border border-l-0' : ''
|
||||
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 ? 'gray' : 'dark') : 'light'}
|
||||
color={$selected === value ? (light ? 'dark' : 'dark') : 'light'}
|
||||
variant="contained"
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class:flex-col={col} class="flex rounded-md shadow-sm" role="group">
|
||||
<div class:flex-col={col} class="flex rounded-md" role="group">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { faPlus, faWind } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
import { loadHubFlows, sendUserToast } from '$lib/utils'
|
||||
import { loadHubFlows } from '$lib/utils'
|
||||
import { Button, ButtonPopup, ButtonPopupItem } from '$lib/components/common'
|
||||
import ItemPicker from '../ItemPicker.svelte'
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { flowStore, initFlow } from '$lib/components/flows/flowStore'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
let hubFlows: any[] | undefined = undefined
|
||||
|
||||
@@ -21,7 +20,7 @@
|
||||
hub: undefined,
|
||||
json: undefined
|
||||
}
|
||||
let hubItems: any[] = []
|
||||
|
||||
let pendingJson: string
|
||||
let hubFilter = ''
|
||||
|
||||
@@ -49,8 +48,8 @@
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-row gap-2">
|
||||
<ButtonPopup size="md" startIcon={{ icon: faPlus }} href="/flows/add">
|
||||
<svelte:fragment slot="main">New Flow<Icon data={faWind} class="ml-1" /></svelte:fragment>
|
||||
<ButtonPopup size="sm" spacingSize="xl" startIcon={{ icon: faPlus }} href="/flows/add">
|
||||
<svelte:fragment slot="main">New Flow</svelte:fragment>
|
||||
<ButtonPopupItem on:click={() => drawers.json?.toggleDrawer()}>
|
||||
Import from raw JSON
|
||||
</ButtonPopupItem>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tabs bind:selected={selectedTab} class="mt-1">
|
||||
<Tab value="logs" size="xs">Logs/Result</Tab>
|
||||
<Tab value="history" size="xs">History</Tab>
|
||||
<Tab value="last_save" size="xs">Last save</Tab>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { ScriptService } from '$lib/gen'
|
||||
import { workspaceStore, hubScripts } from '$lib/stores'
|
||||
import { ButtonPopup, ButtonPopupItem } from '$lib/components/common'
|
||||
import ItemPicker from '../ItemPicker.svelte'
|
||||
import type { HubItem } from '../flows/pickers/model'
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
@@ -17,6 +16,7 @@
|
||||
} = {
|
||||
template: undefined
|
||||
}
|
||||
|
||||
let hubItems: HubItem[]
|
||||
let templateScripts: Script[] = []
|
||||
let templateFilter = ''
|
||||
@@ -45,8 +45,8 @@
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-row gap-2">
|
||||
<ButtonPopup size="md" startIcon={{ icon: faPlus }} href="/scripts/add">
|
||||
<svelte:fragment slot="main">New Script<Icon data={faScroll} class="ml-1" /></svelte:fragment>
|
||||
<ButtonPopup size="sm" spacingSize="xl" startIcon={{ icon: faPlus }} href="/scripts/add">
|
||||
<svelte:fragment slot="main">New Script</svelte:fragment>
|
||||
<ButtonPopupItem on:click={() => drawers.template?.toggleDrawer()}>
|
||||
Import from template
|
||||
</ButtonPopupItem>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<nav class="h-full flex justify-between flex-col px-2">
|
||||
<div class="space-y-1 pt-4">
|
||||
{#each mainMenuLinks as menuLink (menuLink.href)}
|
||||
<MenuLink class="text-lg" {...menuLink} {isCollapsed} />
|
||||
<MenuLink class="text-md" {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
<div class="h-2" />
|
||||
<div class="max-h-40 overflow-y-auto max-w-xs truncate">
|
||||
|
||||
@@ -42,7 +42,7 @@ export function displayDaysAgo(dateString: string): string {
|
||||
} else {
|
||||
let dAgo = daysAgo(date)
|
||||
if (dAgo == 0) {
|
||||
return `yday at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
return `yesterday at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
} else if (dAgo > 7) {
|
||||
return `${dAgo + 1} days ago at ${date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
@@ -59,8 +59,9 @@ export function displayDate(dateString: string | undefined): string {
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
} else {
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
return `${date.getFullYear()}/${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
<script context="module">
|
||||
export function load() {
|
||||
return {
|
||||
stuff: { title: 'Scripts' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { faCodeFork, faEdit, faPlay, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { AppService, Policy, type ListableApp } from '$lib/gen'
|
||||
import { superadmin, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, sendUserToast } from '$lib/utils'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import { Button, Badge, Skeleton, Drawer, DrawerContent } from '$lib/components/common'
|
||||
|
||||
type ListableAppMarked = ListableApp & { canWrite: boolean; marked?: string }
|
||||
|
||||
let apps: ListableAppMarked[] = []
|
||||
let preFilteredApps: ListableAppMarked[] = []
|
||||
let filteredApps: ListableAppMarked[] = []
|
||||
let filter = ''
|
||||
let loading = true
|
||||
|
||||
let path: string = ''
|
||||
let initialPath = ''
|
||||
let pathError = ''
|
||||
|
||||
async function loadApps(): Promise<void> {
|
||||
apps = (await AppService.listApps({ workspace: $workspaceStore! })).map((x) => {
|
||||
return {
|
||||
canWrite:
|
||||
x.path && x.extra_perms
|
||||
? canWrite(x.path, x.extra_perms, $userStore) && x.workspace_id == $workspaceStore
|
||||
: false,
|
||||
...x
|
||||
}
|
||||
})
|
||||
|
||||
loading = false
|
||||
}
|
||||
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
import type { App } from '$lib/components/apps/types'
|
||||
import { goto } from '$app/navigation'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
|
||||
$: owners = Array.from(
|
||||
new Set(filteredApps?.map((x) => x.path?.split('/').slice(0, 2).join('/')) ?? [])
|
||||
).sort()
|
||||
|
||||
$: preFilteredApps =
|
||||
ownerFilter != undefined ? apps.filter((x) => x.path?.startsWith(ownerFilter ?? '')) : apps
|
||||
|
||||
let ownerFilter: string | undefined = undefined
|
||||
|
||||
$: {
|
||||
if ($workspaceStore && ($userStore || $superadmin)) {
|
||||
loadApps()
|
||||
}
|
||||
}
|
||||
async function createApp() {
|
||||
const appJson: App = {
|
||||
grid: [],
|
||||
title: 'New app',
|
||||
inlineScripts: {}
|
||||
}
|
||||
|
||||
const policy = {
|
||||
triggerables: {},
|
||||
execution_mode: Policy.execution_mode.PUBLISHER,
|
||||
on_behalf_of: `u/${$userStore?.username}`
|
||||
}
|
||||
try {
|
||||
const appId = await AppService.createApp({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
value: appJson,
|
||||
path,
|
||||
summary: 'App summary',
|
||||
policy
|
||||
}
|
||||
})
|
||||
|
||||
goto(`/apps/edit/${appId}`)
|
||||
} catch (e) {
|
||||
sendUserToast('Error creating app', e)
|
||||
}
|
||||
}
|
||||
|
||||
let drawerOpen = false
|
||||
|
||||
function closeDrawer() {
|
||||
drawerOpen = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:open={drawerOpen} size="800px">
|
||||
<DrawerContent title="Add an app" on:close={() => closeDrawer()}>
|
||||
<Path bind:error={pathError} bind:path {initialPath} namePlaceholder="my_app" kind="app">
|
||||
<div slot="ownerToolkit">
|
||||
App 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>
|
||||
|
||||
<Button on:click={() => createApp()}>Create app</Button>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<SearchItems
|
||||
{filter}
|
||||
items={preFilteredApps}
|
||||
bind:filteredItems={filteredApps}
|
||||
f={(x) => x.summary + ' (' + x.path + ')'}
|
||||
/>
|
||||
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Apps"
|
||||
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."
|
||||
>
|
||||
<Button size="md" startIcon={{ icon: faPlus }} on:click={() => (drawerOpen = true)}>
|
||||
New app
|
||||
</Button>
|
||||
</PageHeader>
|
||||
|
||||
<div class="mb-1" />
|
||||
|
||||
<input type="text" placeholder="Search Scripts" bind:value={filter} class="text-2xl mt-2" />
|
||||
|
||||
<div class="gap-2 w-full flex flex-wrap pb-1 pt-2">
|
||||
{#each owners as owner}
|
||||
<Badge
|
||||
class="cursor-pointer hover:bg-gray-200"
|
||||
on:click={() => {
|
||||
ownerFilter = ownerFilter == owner ? undefined : owner
|
||||
}}
|
||||
color={owner === ownerFilter ? 'blue' : 'gray'}
|
||||
>
|
||||
{owner}
|
||||
{#if owner === ownerFilter}✗{/if}
|
||||
</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 mt-2 w-full">
|
||||
{#if !loading}
|
||||
{#each filteredApps as { summary, path, extra_perms, marked, canWrite }, index (`${path}-${index}`)}
|
||||
<a
|
||||
class="border border-gray-400 p-2 rounded-sm shadow-sm hover:border-blue-600 text-gray-800"
|
||||
href="/apps/get/{path}"
|
||||
>
|
||||
<div class="flex flex-col gap-1 w-full h-full">
|
||||
<div class="font-semibold text-gray-700 truncate">
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{!summary || summary.length == 0 ? path : summary}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row justify-between w-full grow gap-2 items-start">
|
||||
<div class="text-gray-700 text-xs flex flex-row flex-wrap gap-x-1 items-center">
|
||||
{path}
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</div>
|
||||
<div class="flex flex-col items-end grow pt-4">
|
||||
<div class="flex flex-row-reverse place gap-x-2 items-end">
|
||||
<div>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
startIcon={{ icon: faPlay }}
|
||||
href="/apps/get/{path}"
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
</div>
|
||||
{#if canWrite}
|
||||
<div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
size="xs"
|
||||
startIcon={{ icon: faEdit }}
|
||||
href="/apps/edit/{path}"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="border"
|
||||
color="red"
|
||||
size="xs"
|
||||
startIcon={{ icon: faTrash }}
|
||||
on:click={() => {
|
||||
if ($workspaceStore && path) {
|
||||
AppService.deleteApp({
|
||||
workspace: $workspaceStore,
|
||||
path
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Array(10).fill(0) as _}
|
||||
<Skeleton layout={[[4]]} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</CenteredPage>
|
||||
@@ -4,8 +4,6 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { page } from '$app/stores'
|
||||
|
||||
import gridHelp from 'svelte-grid/build/helper/index.mjs'
|
||||
|
||||
let app: AppWithLastVersion | undefined = undefined
|
||||
let path = $page.params.path
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { AppService, AppWithLastVersion } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { faArrowLeft, faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
let app: AppWithLastVersion | undefined = undefined
|
||||
|
||||
@@ -33,12 +33,8 @@
|
||||
<CenteredPage>
|
||||
{#if app}
|
||||
<div class="flex justify-between my-2 items-center">
|
||||
<div class="flex gap-4 items-center">
|
||||
<Button size="xs" href="/apps" startIcon={{ icon: faArrowLeft }} color="dark">
|
||||
Back to apps
|
||||
</Button>
|
||||
<div>{app.value.title}</div>
|
||||
</div>
|
||||
<div>{app.value.title}</div>
|
||||
|
||||
<Button size="xs" href="/apps/edit/{$page.params.path}" startIcon={{ icon: faPen }}>
|
||||
Edit
|
||||
</Button>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
{#if logs}
|
||||
{#each logs as { id, timestamp, username, operation, action_kind, resource, parameters }}
|
||||
<tr>
|
||||
<td class="">{id}</td>
|
||||
<td>{id}</td>
|
||||
<td class="">
|
||||
<div class="whitespace-nowrap overflow-x-auto no-scrollbar max-w-xs">
|
||||
{displayDate(timestamp)}
|
||||
@@ -153,8 +153,8 @@
|
||||
<td class="">
|
||||
<div class="whitespace-nowrap overflow-x-auto no-scrollbar w-20">
|
||||
{username}
|
||||
</div></td
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
<td class=""><pre>{operation}</pre></td>
|
||||
<td class="">{resource}</td>
|
||||
<td class="">
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import FlowViewer from '$lib/components/FlowViewer.svelte'
|
||||
import { Button, ActionRow, Skeleton, Badge } from '$lib/components/common'
|
||||
|
||||
+223
-146
@@ -2,15 +2,18 @@
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import JobDetail from '$lib/components/jobs/JobDetail.svelte'
|
||||
import {
|
||||
AppService,
|
||||
FlowService,
|
||||
Job,
|
||||
JobService,
|
||||
ListableApp,
|
||||
Script,
|
||||
ScriptService,
|
||||
type Flow,
|
||||
type OpenFlow
|
||||
} from '$lib/gen'
|
||||
import { superadmin, userStore, workspaceStore } from '$lib/stores'
|
||||
import VirtualList from '@sveltejs/svelte-virtual-list'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -25,16 +28,15 @@
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import CreateActionsFlow from '$lib/components/flows/CreateActionsFlow.svelte'
|
||||
import CreateActionsScript from '$lib/components/scripts/CreateActionsScript.svelte'
|
||||
import { canWrite, getScriptByPath, sendUserToast } from '$lib/utils'
|
||||
import { canWrite, classNames, getScriptByPath, pluralize } from '$lib/utils'
|
||||
import type { HubItem } from '$lib/components/flows/pickers/model'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import {
|
||||
faBuilding,
|
||||
faCodeFork,
|
||||
faDashboard,
|
||||
faGlobe,
|
||||
faScroll,
|
||||
faWind
|
||||
faLayerGroup,
|
||||
faPlus
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte'
|
||||
import PickHubFlow from './PickHubFlow.svelte'
|
||||
@@ -42,20 +44,40 @@
|
||||
import HighlightCode from '$lib/components/HighlightCode.svelte'
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import ScriptBox from '$lib/components/ScriptBox.svelte'
|
||||
import FlowBox from '$lib/components/FlowBox.svelte'
|
||||
import type uFuzzy from '@leeoniya/ufuzzy'
|
||||
import { Building, Code2, Globe2, LayoutDashboard, Wind } from 'svelte-lucide'
|
||||
import Table from '$lib/components/common/table/Table.svelte'
|
||||
import ScriptRow from '$lib/components/common/table/ScriptRow.svelte'
|
||||
import FlowRow from '$lib/components/common/table/FlowRow.svelte'
|
||||
import AppRow from '$lib/components/common/table/AppRow.svelte'
|
||||
|
||||
import { fade } from 'svelte/transition'
|
||||
import { flip } from 'svelte/animate'
|
||||
import CreateApp from '$lib/components/apps/CreateApp.svelte'
|
||||
|
||||
let jobs: Job[] = []
|
||||
|
||||
type Tab = 'hubscripts' | 'hubflows' | 'workspace'
|
||||
type ScriptW = Script & { canWrite: boolean; marked?: string }
|
||||
type FlowW = Flow & { canWrite: boolean; marked?: string }
|
||||
let scripts: ScriptW[] = []
|
||||
let flows: FlowW[] = []
|
||||
let filteredItems: ((ScriptW & { type: 'script' }) | (FlowW & { type: 'flow' }))[] = []
|
||||
|
||||
let itemKind: 'script' | 'flow' | 'all' = 'all'
|
||||
type TableItem<T, U extends 'script' | 'flow' | 'app'> = T & {
|
||||
canWrite: boolean
|
||||
marked?: string
|
||||
type?: U
|
||||
time?: number
|
||||
starred?: boolean
|
||||
}
|
||||
|
||||
type TableScript = TableItem<Script, 'script'>
|
||||
type TableFlow = TableItem<Flow, 'flow'>
|
||||
type TableApp = TableItem<ListableApp, 'app'>
|
||||
|
||||
let scripts: TableScript[] = []
|
||||
let flows: TableFlow[] = []
|
||||
let apps: TableApp[] = []
|
||||
|
||||
let filteredItems: (TableScript | TableFlow | TableApp)[] = []
|
||||
|
||||
let itemKind: 'script' | 'flow' | 'app' | 'all' = 'all'
|
||||
|
||||
let tab: Tab = 'workspace'
|
||||
let filter: string = ''
|
||||
@@ -72,15 +94,20 @@
|
||||
let codeViewerObj: HubItem | undefined = undefined
|
||||
|
||||
async function loadScripts(): Promise<void> {
|
||||
scripts = (await ScriptService.listScripts({ workspace: $workspaceStore!, perPage: 300 })).map(
|
||||
(x: Script) => {
|
||||
return {
|
||||
canWrite:
|
||||
canWrite(x.path, x.extra_perms, $userStore) && x.workspace_id == $workspaceStore,
|
||||
...x
|
||||
}
|
||||
const loadedScripts = await ScriptService.listScripts({
|
||||
workspace: $workspaceStore!,
|
||||
perPage: 300
|
||||
})
|
||||
|
||||
scripts = loadedScripts.map((script: Script) => {
|
||||
return {
|
||||
canWrite:
|
||||
canWrite(script.path, script.extra_perms, $userStore) &&
|
||||
script.workspace_id == $workspaceStore,
|
||||
...script
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -102,14 +129,16 @@
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function archiveFlow(path: string): Promise<void> {
|
||||
try {
|
||||
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path })
|
||||
loadFlows()
|
||||
sendUserToast(`Successfully archived flow ${path}`)
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not archive this flow ${err.body}`, true)
|
||||
}
|
||||
async function loadApps(): Promise<void> {
|
||||
apps = (await AppService.listApps({ workspace: $workspaceStore! })).map((app: ListableApp) => {
|
||||
return {
|
||||
canWrite:
|
||||
canWrite(app.path!, app.extra_perms!, $userStore) && app.workspace_id == $workspaceStore,
|
||||
...app
|
||||
}
|
||||
})
|
||||
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function viewFlow(obj: { flow_id: number }): Promise<void> {
|
||||
@@ -122,10 +151,7 @@
|
||||
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
|
||||
).sort()
|
||||
|
||||
let combinedItems: (
|
||||
| (ScriptW & { type: 'script'; time: number })
|
||||
| (FlowW & { type: 'flow'; time: number })
|
||||
)[] = []
|
||||
let combinedItems: (TableScript | TableFlow | TableApp)[] = []
|
||||
|
||||
$: combinedItems = [
|
||||
...flows.map((x) => ({ ...x, type: 'flow' as 'flow', time: new Date(x.edited_at).getTime() })),
|
||||
@@ -133,7 +159,8 @@
|
||||
...x,
|
||||
type: 'script' as 'script',
|
||||
time: new Date(x.created_at).getTime()
|
||||
}))
|
||||
})),
|
||||
...apps.map((x) => ({ ...x, type: 'app' as 'app', time: new Date(x.edited_at).getTime() }))
|
||||
].sort((a, b) => (a.starred != b.starred ? (a.starred ? -1 : 1) : a.time - b.time > 0 ? -1 : 1))
|
||||
|
||||
$: preFilteredItems =
|
||||
@@ -159,6 +186,7 @@
|
||||
if (($userStore || $superadmin) && $workspaceStore) {
|
||||
loadScripts()
|
||||
loadFlows()
|
||||
loadApps()
|
||||
loadJobs()
|
||||
}
|
||||
}
|
||||
@@ -178,7 +206,7 @@
|
||||
interIns
|
||||
} = info
|
||||
|
||||
return idx
|
||||
const sortResult = idx
|
||||
.map((v, i) => i)
|
||||
.sort(
|
||||
(ia, ib) =>
|
||||
@@ -202,8 +230,13 @@
|
||||
(preFilteredItems[idx[ib]].starred ? 100 : 0) -
|
||||
(preFilteredItems[idx[ia]].starred ? 100 : 0)
|
||||
)
|
||||
return sortResult
|
||||
}
|
||||
}
|
||||
|
||||
$: items = filter !== '' ? filteredItems : preFilteredItems
|
||||
|
||||
$: containerHeight = items.length < 5 ? items.length * 80 : 800
|
||||
</script>
|
||||
|
||||
<SearchItems
|
||||
@@ -232,17 +265,22 @@
|
||||
|
||||
<Drawer bind:this={codeViewer} size="900px">
|
||||
<DrawerContent title={codeViewerObj?.summary ?? ''} on:close={codeViewer.closeDrawer}>
|
||||
<div slot="submission" class="flex flex-row gap-2 pr-2"
|
||||
><Button
|
||||
<div slot="submission" class="flex flex-row gap-2 pr-2">
|
||||
<Button
|
||||
href="https://hub.windmill.dev/scripts/{codeViewerObj?.app ?? ''}/{codeViewerObj?.ask_id ??
|
||||
0}"
|
||||
startIcon={{ icon: faGlobe }}
|
||||
variant="border">View on the Hub</Button
|
||||
><Button
|
||||
variant="border"
|
||||
>
|
||||
View on the Hub
|
||||
</Button>
|
||||
<Button
|
||||
href="/scripts/add?hub={encodeURIComponent(codeViewerObj?.path ?? '')}"
|
||||
startIcon={{ icon: faCodeFork }}>Fork</Button
|
||||
></div
|
||||
>
|
||||
startIcon={{ icon: faCodeFork }}
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<HighlightCode language={codeViewerLanguage} code={codeViewerContent} />
|
||||
</DrawerContent>
|
||||
@@ -250,15 +288,18 @@
|
||||
|
||||
<Drawer bind:this={flowViewer} size="900px">
|
||||
<DrawerContent title="Hub flow" on:close={flowViewer.closeDrawer}>
|
||||
<div slot="submission" class="flex flex-row gap-2 pr-2"
|
||||
><Button
|
||||
<div slot="submission" class="flex flex-row gap-2 pr-2">
|
||||
<Button
|
||||
href="https://hub.windmill.dev/flows/{flowViewerFlow?.flow?.id}"
|
||||
startIcon={{ icon: faGlobe }}
|
||||
variant="border">View on the Hub</Button
|
||||
><Button href="/flows/add?hub={flowViewerFlow?.flow?.id}" startIcon={{ icon: faCodeFork }}
|
||||
>Fork</Button
|
||||
></div
|
||||
>
|
||||
variant="border"
|
||||
>
|
||||
View on the Hub
|
||||
</Button>
|
||||
<Button href="/flows/add?hub={flowViewerFlow?.flow?.id}" startIcon={{ icon: faCodeFork }}>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if flowViewerFlow?.flow}
|
||||
<FlowViewer flow={flowViewerFlow.flow} />
|
||||
@@ -273,14 +314,15 @@
|
||||
{:else if $workspaceStore == 'starter'}
|
||||
<div class="my-4" />
|
||||
|
||||
<Alert title="Stater workspace"
|
||||
>The starter workspace has all its elements (variables, resources, scripts, flows) shared
|
||||
<Alert title="Stater workspace">
|
||||
The starter workspace has all its elements (variables, resources, scripts, flows) shared
|
||||
across all other workspaces. Useful to seed workspace with common elements within your
|
||||
organization.</Alert
|
||||
>
|
||||
organization.
|
||||
</Alert>
|
||||
{/if}
|
||||
<PageHeader title="Home">
|
||||
<div class="flex flex-row gap-8">
|
||||
<div class="flex flex-row gap-2">
|
||||
<CreateApp />
|
||||
<CreateActionsScript />
|
||||
<CreateActionsFlow />
|
||||
</div>
|
||||
@@ -288,75 +330,141 @@
|
||||
|
||||
<div class="my-6" />
|
||||
<Tabs bind:selected={tab}>
|
||||
<Tab size="xl" value="workspace"><Icon data={faBuilding} class="mr-2" />Workspace</Tab>
|
||||
<Tab value="hubscripts"><Icon data={faGlobe} class="mr-2" />Hub Scripts</Tab>
|
||||
<Tab value="hubflows"><Icon data={faGlobe} class="mr-2" />Hub Flows</Tab>
|
||||
<Tab size="xs" value="workspace">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size="18px" />
|
||||
Workspace
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="xs" value="hubscripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Globe2 size="18px" />
|
||||
Hub Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="xs" value="hubflows">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Globe2 size="18px" />
|
||||
Hub Flows
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div class="my-2" />
|
||||
<div class="flex flex-col gap-y-16">
|
||||
<div class="max-h-screen h-full flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
{#if tab == 'workspace'}
|
||||
<div class="w-12/12 pb-2 flex flex-row my-1 gap-1">
|
||||
<input
|
||||
type="text"
|
||||
autofocus
|
||||
placeholder="Search Scripts, Flows & Apps"
|
||||
bind:value={filter}
|
||||
class="text-2xl grow"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between items-center gap-2 ">
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={itemKind}>
|
||||
<ToggleButton light position="left" value="all" size="sm">All</ToggleButton>
|
||||
<ToggleButton light position="center" value="script" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<Code2 size="16px" />
|
||||
Scripts
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton light position="center" value="flow" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<Wind size="16px" />
|
||||
Flows
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton light position="right" value="app" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<LayoutDashboard size="16px" />
|
||||
Apps
|
||||
</div>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="max-w-min">
|
||||
<ToggleButtonGroup bind:selected={itemKind}>
|
||||
<ToggleButton light position="left" value="all" size="xs">All</ToggleButton>
|
||||
<ToggleButton light position="center" value="script" size="xs"
|
||||
><Icon data={faScroll} class="mr-1" />Scripts</ToggleButton
|
||||
>
|
||||
<ToggleButton light position="right" value="flow" size="xs"
|
||||
><Icon data={faWind} class="mr-1" />Flows</ToggleButton
|
||||
>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div class="gap-2 w-full flex flex-wrap pb-1 pt-2">
|
||||
{#each owners as owner}
|
||||
<Badge
|
||||
class="cursor-pointer hover:bg-gray-200"
|
||||
on:click={() => {
|
||||
ownerFilter = ownerFilter == owner ? undefined : owner
|
||||
}}
|
||||
color={owner === ownerFilter ? 'blue' : 'gray'}
|
||||
>
|
||||
{owner}
|
||||
{#if owner === ownerFilter}✗{/if}
|
||||
</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 mt-2 w-full overflow-auto">
|
||||
{#if !loading}
|
||||
{#each filter != '' ? filteredItems : preFilteredItems as item (item.type + item.path)}
|
||||
{#if item.type == 'script'}
|
||||
<ScriptBox
|
||||
starred={item.starred}
|
||||
marked={item.marked}
|
||||
on:change={loadScripts}
|
||||
script={item}
|
||||
shareModal={shareModalScripts}
|
||||
<div class="relative text-gray-600 w-full">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
placeholder="Search Scripts, Flows & Apps"
|
||||
bind:value={filter}
|
||||
class="bg-white !h-10 !px-4 !pr-10 !rounded-lg text-sm focus:outline-none"
|
||||
/>
|
||||
<button type="submit" class="absolute right-0 top-0 mt-3 mr-4">
|
||||
<svg
|
||||
class="h-4 w-4 fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 56.966 56.966"
|
||||
style="enable-background:new 0 0 56.966 56.966;"
|
||||
xml:space="preserve"
|
||||
width="512px"
|
||||
height="512px"
|
||||
>
|
||||
<path
|
||||
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"
|
||||
/>
|
||||
{:else if item.type == 'flow'}
|
||||
<FlowBox
|
||||
starred={item.starred ?? false}
|
||||
marked={item.marked}
|
||||
on:change={loadFlows}
|
||||
flow={item}
|
||||
shareModal={shareModalFlows}
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{#if owners.length > 0}
|
||||
<div class="gap-2 w-full flex flex-wrap my-4">
|
||||
{#each owners as owner (owner)}
|
||||
<div in:fade animate:flip={{ duration: 200 }}>
|
||||
<Badge
|
||||
class="cursor-pointer hover:bg-gray-200"
|
||||
on:click={() => {
|
||||
ownerFilter = ownerFilter == owner ? undefined : owner
|
||||
}}
|
||||
color={owner === ownerFilter ? 'blue' : 'gray'}
|
||||
baseClass={owner === ownerFilter ? 'border border-blue-500' : 'border'}
|
||||
>
|
||||
{owner}
|
||||
{#if owner === ownerFilter}✗{/if}
|
||||
</Badge>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
{#if filteredItems.length === 0}
|
||||
<div class="flex justify-center items-center h-48">
|
||||
<div class="text-gray-500 text-center">
|
||||
<div class="text-2xl font-bold">No items found</div>
|
||||
<div class="text-sm">Try changing your search or filters</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#each Array(10).fill(0) as sk}
|
||||
<Skeleton layout={[[4]]} />
|
||||
{/each}
|
||||
<div class={classNames('border rounded-md')} style="height:calc(100vh - 18em)">
|
||||
<VirtualList {items} let:item>
|
||||
{#if item.type == 'script'}
|
||||
<ScriptRow
|
||||
starred={item.starred ?? false}
|
||||
marked={item.marked}
|
||||
on:change={loadScripts}
|
||||
script={item}
|
||||
shareModal={shareModalScripts}
|
||||
/>
|
||||
{:else if item.type == 'flow'}
|
||||
<FlowRow
|
||||
starred={item.starred ?? false}
|
||||
marked={item.marked}
|
||||
on:change={loadFlows}
|
||||
flow={item}
|
||||
shareModal={shareModalFlows}
|
||||
/>
|
||||
{:else if item.type == 'app'}
|
||||
<AppRow
|
||||
starred={item.starred ?? false}
|
||||
marked={item.marked}
|
||||
on:change={loadApps}
|
||||
app={item}
|
||||
/>
|
||||
{/if}
|
||||
</VirtualList>
|
||||
</div>
|
||||
<span class="text-sm">{pluralize(items.length, 'item')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if tab == 'hubscripts'}
|
||||
@@ -365,36 +473,5 @@
|
||||
<PickHubFlow bind:filter on:pick={(e) => viewFlow(e.detail)} />
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="border-b mb-4 py-2">
|
||||
<span class="text-black-gradient">Runs</span>
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 my-4">
|
||||
<Skeleton {loading} layout={[[6], 1, [6], 1, [6]]} />
|
||||
{#each jobs.splice(0, 3) as job}
|
||||
<JobDetail {job} />
|
||||
{/each}
|
||||
<a
|
||||
href="/runs"
|
||||
class="text-sm font-extrabold text-gray-700 hover:underline inline-flex items-center"
|
||||
>
|
||||
All runs
|
||||
<svg
|
||||
class="w-4 h-4 ml-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="M17 8l4 4m0 0l-4 4m4-4H3"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CenteredPage>
|
||||
|
||||
@@ -106,11 +106,11 @@
|
||||
<h2>Are you in the right workspace?</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each $usersWorkspaceStore?.workspaces ?? [] as workspace}
|
||||
<div
|
||||
><Button variant="border" href="/run/{$page.params.run}?workspace={workspace.id}"
|
||||
>See in {workspace.name}</Button
|
||||
></div
|
||||
>
|
||||
<div>
|
||||
<Button variant="border" href="/run/{$page.params.run}?workspace={workspace.id}">
|
||||
See in {workspace.name}
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
<div>
|
||||
<Button href="/runs">Go to runs page</Button>
|
||||
@@ -307,7 +307,7 @@
|
||||
|
||||
<Skeleton loading={!job} layout={[[5]]} />
|
||||
{#if job}
|
||||
<div class="flex flex-row border rounded-md p-3 max-h-1/2 overflow-auto">
|
||||
<div class="flex flex-row border rounded-md p-2 mt-2 max-h-1/2 overflow-auto">
|
||||
{#if viewTab == 'logs'}
|
||||
<div class="w-full">
|
||||
<LogViewer isLoading={!(job && 'logs' in job && job.logs)} content={job?.logs} />
|
||||
|
||||
@@ -176,15 +176,19 @@
|
||||
on:click={() => {
|
||||
goto('/runs?' + $page.url.searchParams.toString())
|
||||
}}
|
||||
size="xs"><Icon data={faSearchMinus} /></Button
|
||||
size="xs"
|
||||
>
|
||||
<Icon data={faSearchMinus} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
goto('/runs?' + $page.url.searchParams.toString())
|
||||
}}
|
||||
size="xs"><Icon data={faSearch} /></Button
|
||||
size="xs"
|
||||
>
|
||||
<Icon data={faSearch} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7x mt-2">
|
||||
@@ -248,8 +252,10 @@
|
||||
maxTs = undefined
|
||||
jobs = await fetchJobs(maxTs, minTs)
|
||||
}}
|
||||
size="xs"><Icon data={faSearchMinus} /></Button
|
||||
size="xs"
|
||||
>
|
||||
<Icon data={faSearchMinus} />
|
||||
</Button>
|
||||
</div>
|
||||
<Skeleton loading={!jobs} layout={[[6], 1, [6], 1, [6], 1, [6], 1, [6]]} />
|
||||
{#if jobs}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
canWrite,
|
||||
defaultIfEmptyString,
|
||||
scriptToHubUrl,
|
||||
copyToClipboard,
|
||||
emptyString
|
||||
copyToClipboard
|
||||
} from '$lib/utils'
|
||||
import {
|
||||
faPlay,
|
||||
@@ -269,9 +268,9 @@
|
||||
startIcon={{ icon: faArrowLeft }}
|
||||
href="/scripts/get/{script.parent_hashes[0]}"
|
||||
>
|
||||
<svelte:fragment slot="main"
|
||||
>Previous version ({script.parent_hashes.length})</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="main">
|
||||
Previous version ({script.parent_hashes.length})
|
||||
</svelte:fragment>
|
||||
|
||||
{#each script.parent_hashes as hash}
|
||||
<ButtonPopupItem href="/scripts/get/{hash}" btnClasses="!m-0">
|
||||
@@ -283,7 +282,7 @@
|
||||
</div>
|
||||
|
||||
{#if script.lock_error_logs || topHash || script.archived || script.deleted}
|
||||
<div class="flex flex-col gap-2 mb-2">
|
||||
<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>
|
||||
|
||||
@@ -161,12 +161,14 @@
|
||||
<div class="flex justify-between items-center mt-10">
|
||||
{#if $superadmin}
|
||||
<Button variant="border" size="sm" on:click={superadminSettings.openDrawer}>
|
||||
<Icon data={faCrown} class="mr-1" scale={1} />Superadmin settings</Button
|
||||
>
|
||||
<Icon data={faCrown} class="mr-1" scale={1} />
|
||||
Superadmin settings
|
||||
</Button>
|
||||
{/if}
|
||||
<Button variant="border" size="sm" on:click={userSettings.openDrawer}>
|
||||
<Icon data={faUserCog} class="mr-1" scale={1} />User settings</Button
|
||||
>
|
||||
<Icon data={faUserCog} class="mr-1" scale={1} />
|
||||
User settings
|
||||
</Button>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
faPlus,
|
||||
faCircle,
|
||||
faEyeSlash,
|
||||
faRotateRight,
|
||||
faArrowRotateRight,
|
||||
faRefresh,
|
||||
faChain,
|
||||
faTrash,
|
||||
@@ -164,8 +162,8 @@
|
||||
<Popover>
|
||||
<Icon data={faRefresh} />
|
||||
<div slot="text">
|
||||
This OAuth token will be kept up-to-date in the background by Windmill using
|
||||
its refresh token
|
||||
This OAuth token will be kept up-to-date in the background by Windmill
|
||||
using its refresh token
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
@@ -83,9 +83,3 @@
|
||||
</TableCustom>
|
||||
{/each}
|
||||
</CenteredPage>
|
||||
|
||||
<style>
|
||||
td {
|
||||
@apply text-xs;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -178,8 +178,7 @@ const config = {
|
||||
},
|
||||
mark: {
|
||||
backgroundColor: theme('colors.yellow.200'),
|
||||
border: `1px solid ${theme('colors.gray.600')}`,
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
borderRadius: theme('borderRadius.sm')
|
||||
},
|
||||
h3: {
|
||||
fontSize: '18px',
|
||||
|
||||
Reference in New Issue
Block a user