mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
363 lines
11 KiB
Svelte
363 lines
11 KiB
Svelte
<script lang="ts">
|
|
import { AppService, FlowService, ResourceService, ScriptService } from '$lib/gen'
|
|
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
|
import { clickOutside } from '$lib/utils'
|
|
import { Boxes, Code2, Edit, LayoutDashboard, Loader2, X } from 'lucide-svelte'
|
|
import Portal from 'svelte-portal'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import SearchItems from './SearchItems.svelte'
|
|
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
|
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
|
import FlowIcon from './home/FlowIcon.svelte'
|
|
import { Alert, Button } from './common'
|
|
import { goto } from '$app/navigation'
|
|
import YAML from 'yaml'
|
|
|
|
let search: string = ''
|
|
|
|
export async function open(nsearch?: string) {
|
|
isOpen = true
|
|
await Promise.all([loadScripts(), loadResources(), loadApps(), loadFlows()])
|
|
if (nsearch) {
|
|
search = nsearch
|
|
}
|
|
}
|
|
|
|
export async function loadScripts() {
|
|
scripts = await ScriptService.listSearchScript({ workspace: $workspaceStore ?? '' })
|
|
}
|
|
|
|
export async function loadResources() {
|
|
resources = await ResourceService.listSearchResource({ workspace: $workspaceStore ?? '' })
|
|
}
|
|
|
|
export async function loadApps() {
|
|
apps = await AppService.listSearchApp({ workspace: $workspaceStore ?? '' })
|
|
}
|
|
|
|
export async function loadFlows() {
|
|
flows = await FlowService.listSearchFlow({ workspace: $workspaceStore ?? '' })
|
|
}
|
|
|
|
let searchKind: 'all' | 'scripts' | 'flows' | 'apps' | 'resources' = 'all'
|
|
|
|
let isOpen = false
|
|
|
|
let scripts: undefined | { path: string; content: string }[] = undefined
|
|
let filteredScriptItems: { path: string; content: string; marked: any }[] = []
|
|
|
|
let resources: undefined | { path: string; value: any }[] = undefined
|
|
let filteredResourceItems: { path: string; value: any; marked: any }[] = []
|
|
|
|
let flows: undefined | { path: string; value: any }[] = undefined
|
|
let filteredFlowItems: { path: string; value: any; marked: any }[] = []
|
|
|
|
let apps: undefined | { path: string; value: any }[] = undefined
|
|
let filteredAppItems: { path: string; value: any; marked: any }[] = []
|
|
|
|
function getCounts(n: number) {
|
|
return ` (${n})`
|
|
}
|
|
$: counts =
|
|
search == '' ||
|
|
!scripts ||
|
|
!resources ||
|
|
!flows ||
|
|
!apps ||
|
|
!filteredAppItems ||
|
|
!filteredFlowItems ||
|
|
!filteredResourceItems ||
|
|
!filteredScriptItems
|
|
? {
|
|
all: '',
|
|
apps: '',
|
|
flows: '',
|
|
resources: '',
|
|
scripts: ''
|
|
}
|
|
: {
|
|
all: getCounts(
|
|
filteredAppItems.length +
|
|
filteredFlowItems.length +
|
|
filteredResourceItems.length +
|
|
filteredScriptItems.length
|
|
),
|
|
apps: getCounts(filteredAppItems.length),
|
|
resources: getCounts(filteredResourceItems.length),
|
|
flows: getCounts(filteredFlowItems.length),
|
|
scripts: getCounts(filteredScriptItems.length)
|
|
}
|
|
</script>
|
|
|
|
<SearchItems
|
|
filter={search}
|
|
items={scripts}
|
|
f={(s) => {
|
|
return s.content
|
|
}}
|
|
bind:filteredItems={filteredScriptItems}
|
|
/>
|
|
|
|
<SearchItems
|
|
filter={search}
|
|
items={resources}
|
|
f={(s) => {
|
|
return YAML.stringify(s.value)
|
|
}}
|
|
bind:filteredItems={filteredResourceItems}
|
|
/>
|
|
|
|
<SearchItems
|
|
filter={search}
|
|
items={flows}
|
|
f={(s) => {
|
|
return YAML.stringify(s.value, null, 4)
|
|
}}
|
|
bind:filteredItems={filteredFlowItems}
|
|
/>
|
|
|
|
<SearchItems
|
|
filter={search}
|
|
items={apps}
|
|
f={(s) => {
|
|
return YAML.stringify(s.value, null, 4)
|
|
}}
|
|
bind:filteredItems={filteredAppItems}
|
|
/>
|
|
|
|
{#if isOpen}
|
|
<Portal>
|
|
<div
|
|
class={twMerge(
|
|
`fixed top-0 bottom-0 left-0 right-0 transition-all duration-50`,
|
|
' bg-black bg-opacity-60',
|
|
'z-[1100]'
|
|
)}
|
|
>
|
|
<div
|
|
class={'max-w-4xl lg:mx-auto mx-10 mt-8 bg-surface rounded-lg relative'}
|
|
use:clickOutside={false}
|
|
on:click_outside={() => {
|
|
isOpen = false
|
|
}}
|
|
>
|
|
<div class="px-4 py-2 border-b flex justify-between items-center">
|
|
<div>Search by content</div>
|
|
<div class="w-8">
|
|
<button
|
|
on:click|stopPropagation={() => {
|
|
isOpen = false
|
|
}}
|
|
class="hover:bg-surface-hover bg-surface-secondary rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
|
>
|
|
<X class="text-tertiary" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="px-2 py-2 overflow-auto">
|
|
<div class="flex gap-2 flex-wrap">
|
|
<div class="flex justify-start">
|
|
<ToggleButtonGroup bind:selected={searchKind} class="h-10">
|
|
<ToggleButton small light value="all" label={'All' + counts.all} />
|
|
<ToggleButton
|
|
small
|
|
light
|
|
value="scripts"
|
|
icon={Code2}
|
|
label={'Scripts' + counts.scripts}
|
|
/>
|
|
<ToggleButton
|
|
small
|
|
light
|
|
value="resources"
|
|
icon={Boxes}
|
|
label={'Resources' + counts.resources}
|
|
/>
|
|
<ToggleButton
|
|
small
|
|
light
|
|
value="flows"
|
|
label={'Flows' + counts.flows}
|
|
icon={FlowIcon}
|
|
selectedColor="#14b8a6"
|
|
/>
|
|
<ToggleButton
|
|
small
|
|
light
|
|
value="apps"
|
|
label={'Apps' + counts.apps}
|
|
icon={LayoutDashboard}
|
|
selectedColor="#fb923c"
|
|
/>
|
|
</ToggleButtonGroup>
|
|
</div>
|
|
<div class="relative text-tertiary grow min-w-[100px]">
|
|
<!-- svelte-ignore a11y-autofocus -->
|
|
<input
|
|
autofocus
|
|
placeholder={'Search in the content of resources, scripts, flows and apps'}
|
|
bind:value={search}
|
|
class="bg-surface !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"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="mt-1">
|
|
<div class="text-xs text-secondary"
|
|
>Searching among <div class="inline-flex"
|
|
>{#if scripts}{scripts?.length}{:else}
|
|
<Loader2 size={10} class="animate-spin " />
|
|
{/if}</div
|
|
>
|
|
scripts,
|
|
<div class="inline-flex"
|
|
>{#if resources}{resources?.length}{:else}
|
|
<Loader2 size={10} class="animate-spin " />
|
|
{/if}</div
|
|
>
|
|
resources,
|
|
<div class="inline-flex"
|
|
>{#if flows}{flows?.length}{:else}
|
|
<Loader2 size={10} class="animate-spin " />
|
|
{/if}</div
|
|
>
|
|
flows,
|
|
<div class="inline-flex"
|
|
>{#if apps}{apps?.length}{:else}
|
|
<Loader2 size={10} class="animate-spin " />
|
|
{/if}</div
|
|
>
|
|
apps</div
|
|
>
|
|
</div>
|
|
|
|
<div class="mt-1 overflow-auto max-h-[80vh]">
|
|
{#if !$enterpriseLicense}
|
|
<div class="py-1" />
|
|
|
|
<Alert title="Content Search is an EE feature" type="warning">
|
|
Without EE, content search will only search among 10 scripts, 3 flows, 3 apps and 3
|
|
resources.
|
|
</Alert>
|
|
<div class="py-1" />
|
|
{/if}
|
|
|
|
{#if search.length > 0}
|
|
<div class="flex flex-col gap-4">
|
|
{#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems?.length > 0}
|
|
{#each filteredScriptItems ?? [] as item}
|
|
<div>
|
|
<div class="text-sm font-semibold"
|
|
><a href="/scripts/get/{item.path}">Script: {item.path}</a></div
|
|
>
|
|
<div class="flex gap-2 justify-between">
|
|
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full max-w-2xl"
|
|
><code>{@html item.marked}</code></pre
|
|
>
|
|
<div>
|
|
<div class="flex gap-2">
|
|
<Button
|
|
on:click|once={() => {
|
|
goto(`/scripts/edit/${item.path}?no_draft=true`)
|
|
}}
|
|
color="light"
|
|
size="sm"
|
|
startIcon={{ icon: Edit }}
|
|
>
|
|
Edit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
{#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems?.length > 0}
|
|
{#each filteredResourceItems ?? [] as item}
|
|
<div>
|
|
<div class="text-sm font-semibold">Resource: {item.path}</div>
|
|
<div class="flex gap-2 justify-between">
|
|
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full max-w-2xl"
|
|
><code>{@html item.marked}</code></pre
|
|
>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
{#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems?.length > 0}
|
|
{#each filteredFlowItems ?? [] as item}
|
|
<div>
|
|
<div class="text-sm font-semibold"
|
|
><a href="/flows/get/{item.path}">Flow: {item.path}</a></div
|
|
>
|
|
<div class="flex gap-2 justify-between">
|
|
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full max-w-2xl"
|
|
><code>{@html item.marked}</code></pre
|
|
>
|
|
<div>
|
|
<div class="flex gap-2">
|
|
<Button
|
|
on:click|once={() => {
|
|
goto(`/flows/edit/${item.path}?no_draft=true`)
|
|
}}
|
|
color="light"
|
|
size="sm"
|
|
startIcon={{ icon: Edit }}>Edit</Button
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
{#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems?.length > 0}
|
|
{#each filteredAppItems ?? [] as item}
|
|
<div>
|
|
<div class="text-sm font-semibold"
|
|
><a href="/apps/get/{item.path}">App: {item.path}</a></div
|
|
>
|
|
<div class="flex gap-2 justify-between">
|
|
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full max-w-2xl"
|
|
><code>{@html item.marked}</code></pre
|
|
>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<div class="flex justify-center items-center h-48">
|
|
<div class="text-tertiary text-center">
|
|
<div class="text-2xl font-bold">Empty Search Filter</div>
|
|
<div class="text-sm"
|
|
>Start writing, search everywhere a path is referenced for instance</div
|
|
>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div></div
|
|
></div
|
|
></Portal
|
|
>
|
|
{/if}
|