mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
fix(frontend): improve search modal (#4088)
* feat(frontend): wip * feat(frontend): wip * feat(frontend): wip * feat(frontend): improve search modal style * feat(frontend): fix content search height * feat(frontend): fix overflow * feat(frontend): revert unrelated changes
This commit is contained in:
@@ -1,16 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { AppService, FlowService, ResourceService, ScriptService } from '$lib/gen'
|
||||
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
||||
import { Boxes, Code2, Edit, LayoutDashboard, Loader2 } from 'lucide-svelte'
|
||||
import {
|
||||
ArrowDown,
|
||||
Boxes,
|
||||
Code2,
|
||||
Edit,
|
||||
ExternalLink,
|
||||
LayoutDashboard,
|
||||
Loader2
|
||||
} from 'lucide-svelte'
|
||||
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 YAML from 'yaml'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ContentSearchInnerItem from './ContentSearchInnerItem.svelte'
|
||||
|
||||
export let search: string = ''
|
||||
export let classNameInner = 'max-h-[80vh]'
|
||||
|
||||
export async function open(nsearch?: string) {
|
||||
await Promise.all([loadScripts(), loadResources(), loadApps(), loadFlows()])
|
||||
@@ -143,10 +152,10 @@
|
||||
bind:filteredItems={filteredAppItems}
|
||||
/>
|
||||
|
||||
<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">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 flex-wrap sticky top-0 left-0 right-0 bg-surface">
|
||||
<div class="p-2">
|
||||
<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
|
||||
@@ -174,60 +183,37 @@
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div class="relative text-tertiary grow min-w-[100px]">
|
||||
<slot name="input-slot" />
|
||||
<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 class="px-2">
|
||||
<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">
|
||||
<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 {classNameInner}">
|
||||
<div class={twMerge('p-2')}>
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="py-1" />
|
||||
|
||||
@@ -242,126 +228,171 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems?.length > 0}
|
||||
{#each filteredScriptItems.slice(0, showNbScripts) ?? [] 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
|
||||
<ContentSearchInnerItem
|
||||
title={`Script: ${item.path}`}
|
||||
href={`/scripts/get/${item.path}`}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
href={`/scripts/get/${item.path}`}
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: ExternalLink }}
|
||||
>
|
||||
<div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
on:click|once={() => {
|
||||
window.open(`/scripts/edit/${item.path}?no_draft=true`, '_blank')?.focus()
|
||||
}}
|
||||
color="light"
|
||||
size="sm"
|
||||
startIcon={{ icon: Edit }}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Open
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
href={`/scripts/edit/${item.path}?no_draft=true`}
|
||||
target="_blank"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: Edit }}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<pre class="text-xs border rounded-md p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code>
|
||||
</pre>
|
||||
</ContentSearchInnerItem>
|
||||
{/each}
|
||||
{#if filteredScriptItems.length > showNbScripts}
|
||||
<a
|
||||
href="#"
|
||||
class="text-center font-semibold cursor-pointer pb-40"
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
showNbScripts += 30
|
||||
}}
|
||||
startIcon={{
|
||||
icon: ArrowDown
|
||||
}}
|
||||
>
|
||||
({showNbScripts} of {filteredScriptItems.length}) Show more scripts
|
||||
</a>
|
||||
Show more scripts ({showNbScripts} of {filteredScriptItems.length})
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems?.length > 0}
|
||||
{#each filteredResourceItems.slice(0, showNbResources) ?? [] 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
|
||||
<ContentSearchInnerItem
|
||||
title={`Resource: ${item.path}`}
|
||||
href={`/resources#${item.path}`}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
href={`/resources#${item.path}`}
|
||||
color="light"
|
||||
target="_blank"
|
||||
size="xs"
|
||||
startIcon={{ icon: Edit }}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<pre class="text-xs border rounded-md p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code></pre
|
||||
>
|
||||
</ContentSearchInnerItem>
|
||||
{/each}
|
||||
{#if filteredResourceItems.length > showNbResources}
|
||||
<a
|
||||
href="#"
|
||||
class="text-center font-semibold cursor-pointer pb-40"
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
showNbResources += 30
|
||||
}}
|
||||
startIcon={{
|
||||
icon: ArrowDown
|
||||
}}
|
||||
>
|
||||
({showNbResources} of {filteredResourceItems.length}) Show more resources
|
||||
</a>
|
||||
Show more resources ({showNbResources} of {filteredResourceItems.length})
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems?.length > 0}
|
||||
{#each filteredFlowItems.slice(0, showNbFlows) ?? [] 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
|
||||
<ContentSearchInnerItem title={`Flow: ${item.path}`} href={`/flows/get/${item.path}`}>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
href={`/flows/get/${item.path}`}
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: ExternalLink }}
|
||||
>
|
||||
<div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
on:click|once={() => {
|
||||
window.open(`/flows/edit/${item.path}?no_draft=true`, '_blank')?.focus()
|
||||
}}
|
||||
color="light"
|
||||
size="sm"
|
||||
startIcon={{ icon: Edit }}>Edit</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Open
|
||||
</Button>
|
||||
<Button
|
||||
href={`/flows/edit/${item.path}?no_draft=true`}
|
||||
color="light"
|
||||
target="_blank"
|
||||
size="xs"
|
||||
startIcon={{ icon: Edit }}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code></pre
|
||||
>
|
||||
</ContentSearchInnerItem>
|
||||
{/each}
|
||||
{#if filteredFlowItems.length > showNbFlows}
|
||||
<a
|
||||
href="#"
|
||||
class="text-center font-semibold cursor-pointer pb-40"
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
showNbScripts += 30
|
||||
showNbFlows += 30
|
||||
}}
|
||||
startIcon={{
|
||||
icon: ArrowDown
|
||||
}}
|
||||
>
|
||||
({showNbFlows} of {filteredFlowItems.length}) Show more flows
|
||||
</a>
|
||||
Show more flows ({showNbFlows} of {filteredFlowItems.length})
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems?.length > 0}
|
||||
{#each filteredAppItems.slice(0, showNbApps) ?? [] 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
|
||||
<ContentSearchInnerItem title={`App: ${item.path}`} href={`/apps/get/${item.path}`}>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
href={`/apps/get/${item.path}`}
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: ExternalLink }}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
Open
|
||||
</Button>
|
||||
<Button
|
||||
href={`/apps/edit/${item.path}?no_draft=true`}
|
||||
color="light"
|
||||
target="_blank"
|
||||
size="xs"
|
||||
startIcon={{ icon: Edit }}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code></pre
|
||||
>
|
||||
</ContentSearchInnerItem>
|
||||
{/each}
|
||||
{#if filteredAppItems.length > showNbApps}
|
||||
<a
|
||||
href="#"
|
||||
class="text-center font-semibold cursor-pointer pb-40"
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
showNbApps += 30
|
||||
}}
|
||||
startIcon={{
|
||||
icon: ArrowDown
|
||||
}}
|
||||
>
|
||||
({showNbApps} of {filteredAppItems.length}) Show more apps
|
||||
</a>
|
||||
Show more apps ({showNbApps} of {filteredAppItems.length})
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
export let title: string
|
||||
export let href: string
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<a class="text-sm text-primary truncate" {href}>
|
||||
{title}
|
||||
</a>
|
||||
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -5,7 +5,8 @@
|
||||
type Input,
|
||||
type RunnableType,
|
||||
type CreateInput,
|
||||
type Job
|
||||
type Job,
|
||||
JobService
|
||||
} from '$lib/gen/index.js'
|
||||
import { userStore, workspaceStore } from '$lib/stores.js'
|
||||
import { classNames, displayDate, displayDateOnly, sendUserToast } from '$lib/utils.js'
|
||||
@@ -328,6 +329,26 @@
|
||||
`w-full flex items-center justify-between gap-4 py-2 px-4 text-left border rounded-sm hover:bg-surface-hover transition-a`,
|
||||
'border-orange-400'
|
||||
)}
|
||||
on:click={async () => {
|
||||
if (!$workspaceStore) {
|
||||
return
|
||||
}
|
||||
|
||||
const args = await JobService.getJobArgs({
|
||||
workspace: $workspaceStore,
|
||||
id: i.id
|
||||
})
|
||||
|
||||
selectedInput = {
|
||||
id: i.id,
|
||||
name: 'Running job: ' + i.id,
|
||||
created_at: i.created_at ?? '',
|
||||
created_by: i.created_by ?? '',
|
||||
is_public: true
|
||||
}
|
||||
|
||||
selectArgs(args)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="w-full h-full items-center text-xs font-normal grid grid-cols-8 gap-4 min-w-0"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export let small = false
|
||||
|
||||
$: tooBig = code && code?.length > 1000000
|
||||
|
||||
function parseJson() {
|
||||
try {
|
||||
if (code == '') {
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
},
|
||||
{
|
||||
search_id: 'switchto:log-search',
|
||||
label: 'Search windmill logs',
|
||||
label: 'Search Windmill logs',
|
||||
action: () => switchMode('logs'),
|
||||
shortcutKey: LOGS_PREFIX,
|
||||
icon: Search
|
||||
@@ -332,6 +332,7 @@
|
||||
onDestroy(() => {
|
||||
window.removeEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
$: searchTerm, handleSearch()
|
||||
|
||||
function placeholderFromPrefix(text: string): string {
|
||||
@@ -436,87 +437,101 @@
|
||||
await handleSearch()
|
||||
}
|
||||
|
||||
async function openModal() {
|
||||
open = !open
|
||||
async function focusTextInput() {
|
||||
await tick()
|
||||
|
||||
textInput?.focus()
|
||||
textInput?.select()
|
||||
|
||||
if (open) {
|
||||
if (combinedItems == undefined) {
|
||||
combinedItems = await fetchCombinedItems()
|
||||
handleSearch()
|
||||
}
|
||||
selectedItem = selectItem(0)
|
||||
textInput.focus()
|
||||
textInput.select()
|
||||
}
|
||||
}
|
||||
|
||||
async function openModal() {
|
||||
open = !open
|
||||
focusTextInput()
|
||||
}
|
||||
let height: number | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<Portal>
|
||||
<div
|
||||
class={twMerge(
|
||||
`fixed top-0 bottom-0 left-0 right-0 transition-all duration-50`,
|
||||
`fixed top-0 bottom-0 left-0 right-0 transition-all duration-50 flex items-center justify-center`,
|
||||
' bg-black bg-opacity-40',
|
||||
'z-[1100]'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class={'max-w-4xl lg:mx-auto mx-10 mt-40 bg-surface rounded-lg relative'}
|
||||
class={'max-w-4xl w-full h-[80vh] bg-surface rounded-lg relative'}
|
||||
use:clickOutside={false}
|
||||
on:click_outside={() => {
|
||||
open = false
|
||||
}}
|
||||
>
|
||||
<div class="py-2 items-center">
|
||||
<div class="px-4 flex flex-row gap-1 items-center pb-1 mb-2 border-b">
|
||||
<Search />
|
||||
<div class="relative inline-block w-full">
|
||||
<input
|
||||
id="quickSearchInput"
|
||||
bind:this={textInput}
|
||||
type="text"
|
||||
class="quick-search-input"
|
||||
bind:value={searchTerm}
|
||||
/>
|
||||
<label
|
||||
for="quickSearchInput"
|
||||
class="absolute top-1/2 left-2 transform -translate-y-1/2 pointer-events-none text-gray-400 transition-all duration-200 whitespace-pre"
|
||||
>{placeholderFromPrefix(searchTerm)}</label
|
||||
>
|
||||
</div>
|
||||
<div class="px-4 py-2 flex flex-row gap-1 items-center border-b" bind:clientHeight={height}>
|
||||
<Search size="16" />
|
||||
<div class="relative inline-block w-full">
|
||||
<input
|
||||
id="quickSearchInput"
|
||||
bind:this={textInput}
|
||||
type="text"
|
||||
class="quick-search-input"
|
||||
bind:value={searchTerm}
|
||||
/>
|
||||
<label
|
||||
for="quickSearchInput"
|
||||
class="absolute top-1/2 left-2 transform -translate-y-1/2 pointer-events-none text-gray-400 transition-all duration-200 whitespace-pre"
|
||||
>{placeholderFromPrefix(searchTerm)}</label
|
||||
>
|
||||
</div>
|
||||
<div class="px-4 overflow-scroll max-h-[30rem]">
|
||||
{#if tab === 'default' || tab === 'switch-mode'}
|
||||
{#each (itemMap[tab] ?? []).filter((e) => defaultMenuItems.includes(e)) as el}
|
||||
<QuickMenuItem
|
||||
on:select={el?.action}
|
||||
on:hover={() => (selectedItem = el)}
|
||||
id={el?.search_id}
|
||||
hovered={el?.search_id === selectedItem?.search_id}
|
||||
label={el?.label}
|
||||
icon={el?.icon}
|
||||
shortcutKey={el?.shortcutKey}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="overflow-y-auto relative" style={`height: calc(100% - ${height + 1}px);`}>
|
||||
{#if tab === 'default' || tab === 'switch-mode'}
|
||||
{@const items = (itemMap[tab] ?? []).filter((e) => defaultMenuItems.includes(e))}
|
||||
{#if items.length > 0}
|
||||
<div class="p-2 border-b">
|
||||
{#each items as el}
|
||||
<QuickMenuItem
|
||||
on:select={el?.action}
|
||||
on:hover={() => (selectedItem = el)}
|
||||
id={el?.search_id}
|
||||
hovered={el?.search_id === selectedItem?.search_id}
|
||||
label={el?.label}
|
||||
icon={el?.icon}
|
||||
shortcutKey={el?.shortcutKey}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if tab === 'default'}
|
||||
{/if}
|
||||
|
||||
{#if tab === 'default'}
|
||||
<div class="p-2">
|
||||
{#if (itemMap[tab] ?? []).filter((e) => (combinedItems ?? []).includes(e)).length > 0}
|
||||
<div class="mt-2 pt-2 pb-1 px-1 text-xs text-sm font-bold border-t text-tertiary"
|
||||
>Flows/Scripts/Apps</div
|
||||
>
|
||||
<div class="py-2 px-1 text-xs font-semibold text-tertiary">
|
||||
Flows/Scripts/Apps
|
||||
</div>
|
||||
{#each (itemMap[tab] ?? []).filter((e) => (combinedItems ?? []).includes(e)) as el}
|
||||
<QuickMenuItem
|
||||
on:select={() => gotoWindmillItemPage(el)}
|
||||
on:hover={() => (selectedItem = el)}
|
||||
id={el?.search_id}
|
||||
hovered={el?.path === selectedItem?.path}
|
||||
label={(el.summary ? `${el.summary} - ` : '') +
|
||||
el.path +
|
||||
(el.starred ? ' ★' : '')}
|
||||
icon={iconForWindmillItem(el.type)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#each (itemMap[tab] ?? []).filter((e) => (combinedItems ?? []).includes(e)) as el}
|
||||
<QuickMenuItem
|
||||
on:select={() => gotoWindmillItemPage(el)}
|
||||
on:hover={() => (selectedItem = el)}
|
||||
id={el?.search_id}
|
||||
hovered={el?.path === selectedItem?.path}
|
||||
label={(el.summary ? `${el.summary} - ` : '') +
|
||||
el.path +
|
||||
(el.starred ? ' ★' : '')}
|
||||
icon={iconForWindmillItem(el.type)}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if (itemMap[tab] ?? []).length === 0}
|
||||
<div class="flex w-full justify-center items-center h-48">
|
||||
<div class="text-tertiary text-center">
|
||||
@@ -525,65 +540,55 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if tab === 'content'}
|
||||
<ContentSearchInner
|
||||
classNameInner="max-h-[20rem]"
|
||||
search={removePrefix(searchTerm, '#')}
|
||||
bind:this={contentSearch}
|
||||
/>
|
||||
{:else if tab === 'logs'}
|
||||
<div class="p-2">
|
||||
<Alert title="Service log search is coming soon" type="info">
|
||||
Full text search on windmill's service logs is coming soon
|
||||
</Alert>
|
||||
</div>
|
||||
{:else if tab === 'runs'}
|
||||
<div class="flex h-96">
|
||||
{#if loadingCompletedRuns}
|
||||
<div class="flex w-full justify-center items-center h-48">
|
||||
<div class="text-tertiary text-center">
|
||||
<Loader2 size={34} class="animate-spin" />
|
||||
</div>
|
||||
</div>
|
||||
{:else if tab === 'content'}
|
||||
<ContentSearchInner search={removePrefix(searchTerm, '#')} bind:this={contentSearch} />
|
||||
{:else if tab === 'logs'}
|
||||
<div class="p-2">
|
||||
<Alert title="Service log search is coming soon" type="info">
|
||||
Full text search on windmill's service logs is coming soon
|
||||
</Alert>
|
||||
</div>
|
||||
{:else if tab === 'runs'}
|
||||
<div class="flex h-full p-2">
|
||||
{#if loadingCompletedRuns}
|
||||
<div class="flex w-full justify-center items-center h-48">
|
||||
<div class="text-tertiary text-center">
|
||||
<Loader2 size={34} class="animate-spin" />
|
||||
</div>
|
||||
{:else if itemMap['runs'] && itemMap['runs'].length > 0}
|
||||
<div class="w-5/12 overflow-scroll">
|
||||
{#each itemMap['runs'] ?? [] as r}
|
||||
<QuickMenuItem
|
||||
on:hover={() => {
|
||||
selectedItem = r
|
||||
selectedWorkspace = r?.document.workspace_id[0]
|
||||
}}
|
||||
on:select={() => {
|
||||
open = false
|
||||
goto(`/run/${r?.document.id[0]}`)
|
||||
}}
|
||||
id={r?.document.id[0]}
|
||||
hovered={selectedItem && r?.document.id[0] === selectedItem?.document.id[0]}
|
||||
icon={r?.icon}
|
||||
>
|
||||
<svelte:fragment slot="itemReplacement">
|
||||
<button
|
||||
class={twMerge(
|
||||
`w-full flex items-center justify-between gap-1 py-2 px-2 text-left border rounded-sm transition-a`,
|
||||
r?.document.id === selectedItem?.document?.id
|
||||
? 'bg-surface-hover'
|
||||
: ''
|
||||
)}
|
||||
on:click={() => {}}
|
||||
>
|
||||
<div
|
||||
class="w-full h-full items-center text-xs font-normal grid grid-cols-10 gap-0 min-w-0"
|
||||
>
|
||||
<div class="col-span-1">
|
||||
<div
|
||||
class="rounded-full w-2 h-2 {r?.document.success[0]
|
||||
? 'bg-green-400'
|
||||
: 'bg-red-400'}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-5">
|
||||
{r?.document.script_path}
|
||||
</div>
|
||||
</div>
|
||||
{:else if itemMap['runs'] && itemMap['runs'].length > 0}
|
||||
<div class="w-5/12 overflow-y-auto border rounded-md divide-y">
|
||||
{#each itemMap['runs'] ?? [] as r}
|
||||
<QuickMenuItem
|
||||
on:hover={() => {
|
||||
selectedItem = r
|
||||
selectedWorkspace = r?.document.workspace_id[0]
|
||||
}}
|
||||
on:select={() => {
|
||||
open = false
|
||||
goto(`/run/${r?.document.id[0]}`)
|
||||
}}
|
||||
id={r?.document.id[0]}
|
||||
hovered={selectedItem && r?.document.id[0] === selectedItem?.document.id[0]}
|
||||
icon={r?.icon}
|
||||
containerClass="!rounded-none"
|
||||
>
|
||||
<svelte:fragment slot="itemReplacement">
|
||||
<div
|
||||
class={twMerge(
|
||||
`w-full flex flex-row items-center gap-4 p-2 transition-all`,
|
||||
r?.document.id === selectedItem?.document?.id ? 'bg-surface-hover' : ''
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class="rounded-full w-2 h-2 {r?.document.success[0]
|
||||
? 'bg-green-400'
|
||||
: 'bg-red-400'}"
|
||||
/>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="text-xs"> {r?.document.script_path} </div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="whitespace-nowrap col-span-2 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
|
||||
>
|
||||
@@ -595,43 +600,39 @@
|
||||
<TimeAgo date={r?.document.created_at[0] ?? ''} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
</QuickMenuItem>
|
||||
{/each}
|
||||
</div>
|
||||
{#if selectedItem === undefined}
|
||||
select a result to preview
|
||||
{:else}
|
||||
<div class="w-7/12 overflow-y-scroll">
|
||||
<JobPreview
|
||||
id={selectedItem?.document?.id[0]}
|
||||
workspace={selectedWorkspace}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</QuickMenuItem>
|
||||
{/each}
|
||||
</div>
|
||||
{#if selectedItem === undefined}
|
||||
select a result to preview
|
||||
{:else}
|
||||
<div class="flex w-full justify-center items-center h-96">
|
||||
<div class="text-tertiary text-center">
|
||||
<div class="text-2xl font-bold">No runs found</div>
|
||||
<div class="text-sm">There were no completed runs that match your query</div>
|
||||
<div class="text-sm"
|
||||
>Note that new runs might take a while to become searchable (by default
|
||||
~5min)</div
|
||||
>
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="py-6" />
|
||||
|
||||
<Alert title="This is an EE feature" type="warning">
|
||||
Full-text search on jobs is only available on EE.
|
||||
</Alert>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-7/12 overflow-y-scroll">
|
||||
<JobPreview id={selectedItem?.document?.id[0]} workspace={selectedWorkspace} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full justify-center items-center">
|
||||
<div class="text-tertiary text-center">
|
||||
<div class="text-2xl font-bold">No runs found</div>
|
||||
<div class="text-sm">There were no completed runs that match your query</div>
|
||||
<div class="text-sm"
|
||||
>Note that new runs might take a while to become searchable (by default ~5min)</div
|
||||
>
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="py-6" />
|
||||
|
||||
<Alert title="This is an EE feature" type="warning">
|
||||
Full-text search on jobs is only available on EE.
|
||||
</Alert>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
export let label: string = ''
|
||||
export let icon: any = undefined
|
||||
export let shortcutKey: string | undefined = undefined
|
||||
export let containerClass: string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -43,19 +44,26 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
{id}
|
||||
on:click|stopPropagation={runAction}
|
||||
on:mouseenter={() => dispatch('hover')}
|
||||
class={`rounded-md w-full ${hovered ? 'bg-surface-hover' : ''}`}
|
||||
class={twMerge(
|
||||
`rounded-md w-full transition-all cursor-pointer hover:bg-surface-hover ${
|
||||
hovered ? 'bg-surface-hover' : ''
|
||||
}`,
|
||||
containerClass
|
||||
)}
|
||||
>
|
||||
{#if $$slots.itemReplacement}
|
||||
<slot name="itemReplacement" />
|
||||
{:else}
|
||||
<div class="flex flex-row gap-2 items-center px-1 py-0.5 rounded-md pr-6 font-light">
|
||||
<div class="flex flex-row gap-2 items-center px-2 py-1.5 rounded-md pr-6 text-sm">
|
||||
<div class="w-4">
|
||||
{#if icon}
|
||||
<svelte:component this={icon} size={14} />
|
||||
<svelte:component this={icon} size={16} />
|
||||
{:else if shortcutKey != undefined}
|
||||
<div class="font-bold flex items-center justify-center w-full">
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user