mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 00:01:37 +00:00
26f5cd6b8e
* feat: replace hub error toasts with warning alerts and add disable hub setting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: guard hub script cache refresh when hub is disabled Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
5.5 KiB
Svelte
216 lines
5.5 KiB
Svelte
<script lang="ts">
|
|
import { createEventDispatcher, untrack } from 'svelte'
|
|
import { Alert, Badge, ButtonType, Skeleton } from '$lib/components/common'
|
|
import { capitalize } from '$lib/utils'
|
|
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
|
|
import { APP_TO_ICON_COMPONENT } from '$lib/components/icons'
|
|
import ListFilters from '$lib/components/home/ListFilters.svelte'
|
|
import { IntegrationService, ScriptService, type HubScriptKind } from '$lib/gen'
|
|
import { Loader2 } from 'lucide-svelte'
|
|
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
|
import { disableHubStore } from '$lib/stores'
|
|
|
|
interface Props {
|
|
kind?: HubScriptKind & string
|
|
filter?: string
|
|
syncQuery?: boolean
|
|
children?: import('svelte').Snippet
|
|
size?: ButtonType.UnifiedSize
|
|
}
|
|
|
|
let {
|
|
kind = 'script',
|
|
filter = $bindable(''),
|
|
syncQuery = false,
|
|
children,
|
|
size = 'md'
|
|
}: Props = $props()
|
|
|
|
let loading = $state(false)
|
|
let hubNotAvailable = $state(false)
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
let appFilter: string | undefined = $state(undefined)
|
|
let items: {
|
|
path: string
|
|
summary: string
|
|
id: number
|
|
version_id: number
|
|
ask_id: number
|
|
app: string
|
|
kind: HubScriptKind
|
|
}[] = $state([])
|
|
|
|
let allApps: string[] = $state([])
|
|
let apps: string[] = $derived.by(() =>
|
|
filter.length > 0 ? Array.from(new Set(items?.map((x) => x.app) ?? [])).sort() : allApps
|
|
)
|
|
|
|
async function getAllApps(filterKind: typeof kind) {
|
|
if ($disableHubStore) return
|
|
try {
|
|
hubNotAvailable = false
|
|
allApps = (
|
|
await IntegrationService.listHubIntegrations({
|
|
kind: filterKind
|
|
})
|
|
).map((x) => x.name)
|
|
} catch (err) {
|
|
console.error('Hub is not available')
|
|
allApps = []
|
|
hubNotAvailable = true
|
|
}
|
|
}
|
|
|
|
let startTs = 0
|
|
async function applyFilter(
|
|
filter: string,
|
|
filterKind: typeof kind,
|
|
appFilter: string | undefined
|
|
) {
|
|
if ($disableHubStore) return
|
|
try {
|
|
loading = true
|
|
hubNotAvailable = false
|
|
const ts = Date.now()
|
|
startTs = ts
|
|
await new Promise((r) => setTimeout(r, 100))
|
|
if (ts < startTs) return
|
|
const scripts =
|
|
filter.length > 0
|
|
? await ScriptService.queryHubScripts({
|
|
text: `${filter}`,
|
|
limit: 20,
|
|
kind: filterKind,
|
|
app: appFilter
|
|
})
|
|
: ((
|
|
await ScriptService.getTopHubScripts({
|
|
limit: 20,
|
|
app: appFilter,
|
|
kind: filterKind
|
|
})
|
|
).asks ?? [])
|
|
if (ts === startTs) {
|
|
loading = false
|
|
}
|
|
items = scripts.map(
|
|
(x: {
|
|
summary: string
|
|
version_id: number
|
|
id: number
|
|
ask_id: number
|
|
app: string
|
|
kind: HubScriptKind
|
|
}) => ({
|
|
...x,
|
|
path: `hub/${x.version_id}/${x.app}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
|
|
summary: `${x.summary} (${x.app})`
|
|
})
|
|
)
|
|
hubNotAvailable = false
|
|
} catch (err) {
|
|
hubNotAvailable = true
|
|
console.error('Hub not available')
|
|
loading = false
|
|
}
|
|
}
|
|
|
|
async function handlePick(item: (typeof items)[number]) {
|
|
if (item.path.startsWith('hub/')) {
|
|
try {
|
|
await ScriptService.pickHubScriptByPath({ path: item.path })
|
|
} catch (error) {
|
|
console.error('Failed to track hub script pick:', error)
|
|
// Don't block the flow if tracking fails
|
|
}
|
|
}
|
|
|
|
// Dispatch the event to continue with the selection
|
|
dispatch('pick', item)
|
|
}
|
|
|
|
$effect(() => {
|
|
;[filter, kind, appFilter]
|
|
untrack(() => applyFilter(filter, kind, appFilter))
|
|
})
|
|
$effect(() => {
|
|
kind
|
|
untrack(() => getAllApps(kind))
|
|
})
|
|
</script>
|
|
|
|
{#if $disableHubStore}
|
|
<!-- Hub disabled, show nothing -->
|
|
{:else}
|
|
<div class="w-full flex items-center gap-2">
|
|
{@render children?.()}
|
|
<div class="relative w-full">
|
|
<TextInput
|
|
inputProps={{
|
|
placeholder: 'Search Hub Scripts'
|
|
}}
|
|
bind:value={filter}
|
|
class="grow !pr-9"
|
|
{size}
|
|
/>
|
|
{#if loading}
|
|
<Loader2 class="animate-spin text-gray-400 absolute right-2 top-1" />
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
{#if hubNotAvailable}
|
|
<Alert type="warning" title="Hub not available">
|
|
Could not connect to the Windmill Hub. If you are in a closed environment, you can disable the Hub in the <a href="/#superadmin-settings?tab=private_hub">instance settings</a>.
|
|
</Alert>
|
|
{:else if (items.length > 0 && apps.length > 0) || !loading}
|
|
<ListFilters {syncQuery} filters={apps} bind:selectedFilter={appFilter} resourceType />
|
|
{#if items.length == 0}
|
|
<NoItemFound />
|
|
{:else}
|
|
<ul class="divide-y border rounded-md bg-surface-tertiary">
|
|
{#each items as item (item.path)}
|
|
<li class="flex flex-row w-full">
|
|
<button
|
|
class="p-4 gap-4 flex flex-row grow hover:bg-surface-hover transition-all items-center"
|
|
onclick={() => handlePick(item)}
|
|
>
|
|
<div class="flex items-center gap-4">
|
|
<div class="flex justify-center items-center">
|
|
{#if item['app'] in APP_TO_ICON_COMPONENT}
|
|
{@const SvelteComponent = APP_TO_ICON_COMPONENT[item['app']]}
|
|
<SvelteComponent height={18} width={18} />
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="w-full text-left">
|
|
<div class="text-emphasis flex-wrap text-xs font-semibold mb-1">
|
|
{item.summary ?? ''}
|
|
</div>
|
|
<div class="text-secondary text-2xs font-normal">
|
|
{item.path}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{#if kind !== 'script'}
|
|
<Badge color="gray" baseClass="border">{capitalize(kind)}</Badge>
|
|
{/if}
|
|
</button>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
{/if}
|
|
{#if items.length == 20}
|
|
<div class="text-primary text-xs font-normal py-4">
|
|
There are more items than being displayed. Refine your search.
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
{#each Array(10).fill(0) as _}
|
|
<Skeleton layout={[0.5, [4]]} />
|
|
{/each}
|
|
{/if}
|
|
{/if}
|