diff --git a/frontend/src/lib/components/Dropdown.svelte b/frontend/src/lib/components/Dropdown.svelte index f1462e8eed..b0282b1ece 100644 --- a/frontend/src/lib/components/Dropdown.svelte +++ b/frontend/src/lib/components/Dropdown.svelte @@ -29,7 +29,7 @@
diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index 7fd4221476..415b2128bc 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -68,7 +68,9 @@ const workspaceScripts: { path: string; summary?: string }[] = await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' }) - await loadHubScripts() + if (!$hubScripts) { + await loadHubScripts() + } const hubScripts_ = $hubScripts ?? [] return workspaceScripts.concat(hubScripts_) diff --git a/frontend/src/lib/components/SearchItems.svelte b/frontend/src/lib/components/SearchItems.svelte new file mode 100644 index 0000000000..b781a549a9 --- /dev/null +++ b/frontend/src/lib/components/SearchItems.svelte @@ -0,0 +1,36 @@ + diff --git a/frontend/src/lib/components/common/button/model.ts b/frontend/src/lib/components/common/button/model.ts index aec253dbca..5813d0d0c1 100644 --- a/frontend/src/lib/components/common/button/model.ts +++ b/frontend/src/lib/components/common/button/model.ts @@ -23,7 +23,7 @@ export namespace ButtonType { export const SpacingClasses: Record = { xs: 'px-3 py-1.5', sm: 'px-3 py-1.5', - md: 'px-4 py-2', + md: 'px-3 py-1.5', lg: 'px-4 py-2', xl: 'px-4 py-2' } as const diff --git a/frontend/src/lib/components/flows/pickers/PickHubScript.svelte b/frontend/src/lib/components/flows/pickers/PickHubScript.svelte index aefd3a164e..cfef4bcd1c 100644 --- a/frontend/src/lib/components/flows/pickers/PickHubScript.svelte +++ b/frontend/src/lib/components/flows/pickers/PickHubScript.svelte @@ -1,44 +1,36 @@ + x.summary} /> +
-
- +
+
@@ -56,9 +48,12 @@ {/each}
- {#if filteredItems} -
-
    +
    +
      + {#if $hubScripts} + {#if filter.length > 0 && filteredItems.length == 0} +

      No items found

      + {/if} {#each filteredItems as obj}
    • {/each} -
    -
    - {/if} + {:else} + {#each Array(10).fill(0) as sk} + + {/each} + {/if} +
+
diff --git a/frontend/src/lib/components/landing/FlowGettingStarted.svelte b/frontend/src/lib/components/landing/FlowGettingStarted.svelte index 2b036a9e14..6a392003c5 100644 --- a/frontend/src/lib/components/landing/FlowGettingStarted.svelte +++ b/frontend/src/lib/components/landing/FlowGettingStarted.svelte @@ -5,18 +5,10 @@ import HatIcon from '../icons/HatIcon.svelte' -
-
-

- - Getting started -

-
-
+
+
Flows allow you to streamline complex processes and operations by chaining simple steps - together. Each Flow is composed of one or more steps. Create a new flow or find inspiration on - the Hub! -
+ together. Each Flow is composed of one or more steps.
+ + {/each} + {:else} + {#each Array(10).fill(0) as sk} + + {/each} + {/if} + +
+
diff --git a/frontend/src/routes/flows.svelte b/frontend/src/routes/flows.svelte index 94e147d66a..c0c59bd04d 100644 --- a/frontend/src/routes/flows.svelte +++ b/frontend/src/routes/flows.svelte @@ -7,122 +7,59 @@ + x.summary + ' (' + x.path + ')'} +/> + - - {#if flowViewerFlow} - + +
+ + {#if flowViewerFlow?.flow} + {/if}
@@ -169,228 +129,148 @@ - All - Hub - {`Personal space (${$userStore?.username})`} - Groups - Shared + Workspace + Hub +
{#if tab != 'hub'} - - {/if} -
- {#each tab == 'all' ? ['personal', 'groups', 'shared', 'hub'] : [tab] as sectionTab} -
- {#if sectionTab == 'personal'} -

- Personal - - ({`u/${$userStore?.username}`}) - All flows owned by you (and visible only to you if you do not explicitly share them) - -

- {:else if sectionTab == 'groups'} -

- Groups All flows being owned by groups that you are member of -

- {:else if sectionTab == 'shared'} -

- Shared All flows visible to you because they have been shared to you -

- {:else if sectionTab == 'hub'} -

- Approved flows from the WindmillHub - All approved Flow from the WindmillHub. - Approved flows have been potentially contributed by the community but reviewed and - selected carefully by the Windmill team. - -

- -
- {#if loading.hub} - - {:else if hubFlows != undefined} - - - Apps - Summary - - - - {#each filteredHubFlows ?? [] as { summary, apps, flow_id }} - - {apps.join(', ')} - - - | - hub's page - - | fork - - - {/each} - - - {:else} - - Hub not reachable. If your environment is air gapped, contact sales@windmill.dev to - setup a local mirror. - - {/if} -
- {/if} - {#each groupedFlows.filter((x) => tabFromPath(x[0]) == sectionTab) as [section, flows]} - {#if sectionTab != 'personal'} -
- {section} - {#if section == 'g/all'} - 'g/all' is the namespace for the group all. Every user is a member of all. - Everything in this namespace is visible by all users. At the opposite, 'u/myuser' - are private user namespaces. - {/if} -
- {/if} - {#if loading.general} -
- {#each new Array(3) as _} - - {/each} -
- {:else if flows.length == 0 && sectionTab == 'personal'} -

No flows yet

- {:else} -
- {#each flows as { summary, path, extra_perms, canWrite }} - - -
-
- -
-
-
- { - shareModal.openDrawer(path) - }, - disabled: !canWrite - }, - { - displayName: 'Archive', - icon: faArchive, - action: () => { - path ? archiveFlow(path) : null - }, - type: 'delete', - disabled: !canWrite - } - ]} - /> -
- {#if canWrite} -
- -
- {:else} -
- -
- {/if} + -
- -
-
+
+ {#each owners as owner} + { + ownerFilter = ownerFilter == owner ? undefined : owner + }} + color={owner === ownerFilter ? 'blue' : 'gray'} + > + {owner} + {#if owner === ownerFilter}✗{/if} + + {/each} +
+ {/if} + + {#if tab == 'workspace'} + {/each} -
- {/each} -
+ {:else} + {#each Array(10).fill(0) as sk} + + {/each} + {/if} +
+ {:else} + viewFlow(e.detail)} /> + {/if} import { faArchive, + faBuilding, faCalendarAlt, faCodeFork, faEdit, @@ -18,108 +19,55 @@ faPlay, faShare } from '@fortawesome/free-solid-svg-icons' - import Fuse from 'fuse.js' import type { Script } from '$lib/gen' import { ScriptService } from '$lib/gen' - import { superadmin, userStore, workspaceStore, hubScripts } from '$lib/stores' - import { - canWrite, - getScriptByPath, - groupBy, - loadHubScripts, - sendUserToast, - truncateHash - } from '$lib/utils' + import { superadmin, userStore, workspaceStore } from '$lib/stores' + import { canWrite, getScriptByPath, sendUserToast } from '$lib/utils' import CenteredPage from '$lib/components/CenteredPage.svelte' import Dropdown from '$lib/components/Dropdown.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' import ShareModal from '$lib/components/ShareModal.svelte' - import Tooltip from '$lib/components/Tooltip.svelte' - import TableCustom from '$lib/components/TableCustom.svelte' import { Button, Tabs, Tab, Badge, Skeleton, DrawerContent } from '$lib/components/common' import CreateActions from '$lib/components/scripts/CreateActions.svelte' import HighlightCode from '$lib/components/HighlightCode.svelte' import Drawer from '$lib/components/common/drawer/Drawer.svelte' import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte' import type { HubItem } from '$lib/components/flows/pickers/model' + import Icon from 'svelte-awesome' - type Tab = 'all' | 'personal' | 'groups' | 'shared' | 'examples' | 'hub' - type Section = [string, ScriptW[]] - type ScriptW = Script & { canWrite: boolean; tab: Tab } + type Tab = 'workspace' | 'hub' + type ScriptW = Script & { canWrite: boolean; marked?: string } let scripts: ScriptW[] = [] - let filteredScripts: ScriptW[] - let scriptFilter = '' - let hubFilter = '' - let groupedScripts: Section[] = [] - let communityScripts: Section[] = [] + let preFilteredScripts: ScriptW[] = [] + let filteredScripts: ScriptW[] = [] + let filter = '' + let loading = true - let tab: Tab = 'all' + let tab: Tab = 'workspace' let shareModal: ShareModal - const fuseOptions = { - includeScore: false, - keys: ['description', 'path', 'content', 'hash', 'summary'] - } - const fuse: Fuse = new Fuse(scripts, fuseOptions) - - const hubScriptsFuse: Fuse = new Fuse($hubScripts ?? [], { - includeScore: false, - keys: ['app', 'path', 'summary'] - }) - let codeViewer: Drawer let codeViewerContent: string = '' let codeViewerLanguage: 'deno' | 'python3' | 'go' | 'bash' = 'deno' let codeViewerObj: HubItem | undefined = undefined - $: filteredScripts = - scriptFilter.length > 0 ? fuse.search(scriptFilter).map((value) => value.item) : scripts - - $: { - let defaults: string[] = [] - - if (tab == 'all' || tab == 'personal') { - defaults = defaults.concat(`u/${$userStore?.username}`) - } - if (tab == 'all' || tab == 'groups') { - defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? []) - } - groupedScripts = groupBy( - filteredScripts.filter((x) => x.tab != 'examples'), - (sc: Script) => sc.path.split('/').slice(0, 2).join('/'), - defaults - ) - communityScripts = [['examples', filteredScripts.filter((x) => x.tab == 'examples')]] - } - - function tabFromPath(path: string) { - let t: Tab = 'shared' - let path_prefix = path.split('/').slice(0, 2) - if (path_prefix[0] == 'u' && path_prefix[1] == $userStore?.username) { - t = 'personal' - } else if (path_prefix[0] == 'g' && $userStore?.groups.includes(path_prefix[1])) { - t = 'groups' - } - return t - } + import SearchItems from '$lib/components/SearchItems.svelte' + import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte' async function loadScripts(): Promise { - const allScripts = ( - await ScriptService.listScripts({ workspace: $workspaceStore!, perPage: 300 }) - ).map((x: Script) => { - let t: Tab = x.workspace_id == $workspaceStore ? tabFromPath(x.path) : 'examples' - return { - canWrite: canWrite(x.path, x.extra_perms, $userStore) && x.workspace_id == $workspaceStore, - tab: t, - ...x + 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 + } } - }) - scripts = tab == 'all' ? allScripts : allScripts.filter((x) => x.tab == tab) + ) loading = false - fuse.setCollection(scripts) } async function archiveScript(path: string): Promise { @@ -136,20 +84,29 @@ codeViewer.openDrawer() } - async function loadHubScriptsWFuse(): Promise { - await loadHubScripts() - hubScriptsFuse.setCollection($hubScripts ?? []) - } + $: owners = Array.from( + new Set(filteredScripts?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? []) + ).sort() - loadHubScriptsWFuse() + $: preFilteredScripts = + ownerFilter != undefined ? scripts.filter((x) => x.path.startsWith(ownerFilter ?? '')) : scripts + + let ownerFilter: string | undefined = undefined $: { - if ($workspaceStore && ($userStore || $superadmin) && tab) { + if ($workspaceStore && ($userStore || $superadmin)) { loadScripts() } } + x.summary + ' (' + x.path + ')'} +/> +
- All - Hub - {`Personal space (${$userStore?.username})`} - Groups - Shared - Examples + Workspace + Hub - {#if tab != 'hub'} - - {/if} +
-
- {#each tab == 'all' ? ['personal', 'groups', 'shared', 'examples', 'hub'] : [tab] as sectionTab} -
- {#if sectionTab == 'personal'} -

- Personal - - ({`u/${$userStore?.username}`}) - All scripts owned by you (and visible only to you if you do not explicitly share - them) - - -

- {:else if sectionTab == 'groups'} -

- Groups All scripts being owned by groups that you are member of -

- {:else if sectionTab == 'shared'} -

- Shared All scripts visible to you because they have been shared to you -

- {:else if sectionTab == 'examples'} -

- Public - Template and examples shared across all workspaces of this instance. They are managed - from a special workspace called 'starter' that only superadmin can change. - -

- {:else if sectionTab == 'hub'} -

- Approved scripts from the WindmillHub - All approved Deno scripts from the WindmillHub. - Approved scripts have been reviewed by the Windmill team and are safe to use in - production. - -

+ {#if tab == 'workspace'} + -
- {#if $hubScripts != undefined} - viewCode(e.detail)} /> - {:else} - - Hub not reachable. If your environment is air gapped, contact sales@windmill.dev to - setup a local mirror. - - {/if} -
- {/if} - {#each sectionTab == 'examples' ? communityScripts : groupedScripts.filter((x) => tabFromPath(x[0]) == sectionTab) as [section, scripts]} - {#if sectionTab != 'personal' && sectionTab != 'examples'} -
- {section} - {#if section == 'g/all'} - 'g/all' is the namespace for the group all. Every user is a member of all. - Everything in this namespace is visible by all users. At the opposite, 'u/myuser' - are private user namespaces. - {/if} -
- {/if} - {#if loading} -
- {#each new Array(3) as _} - - {/each} -
- {:else if scripts.length == 0 && sectionTab == 'personal'} -

No scripts yet

- {:else} -
- {#each scripts as { summary, path, hash, language, extra_perms, canWrite, lock_error_logs, kind }} - -
- -
- {!summary || summary.length == 0 ? path : summary} -
-

- {path} - {truncateHash(hash)} -

-
-
- - {language} - {#if kind != 'script'} - {kind} - {/if} - {#if lock_error_logs} - Deployment error - {/if} -
-
-
+
+ {#each owners as owner} + { + ownerFilter = ownerFilter == owner ? undefined : owner + }} + color={owner === ownerFilter ? 'blue' : 'gray'} + > + {owner} + {#if owner === ownerFilter}✗{/if} + + {/each} +
+ {/each} -
- {/each} -
+ {:else} + {#each Array(10).fill(0) as sk} + + {/each} + {/if} +
+ {:else} + viewCode(e.detail)} /> + {/if}