mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 16:02:28 +00:00
further apps improvements
This commit is contained in:
@@ -57,6 +57,7 @@ pub struct ListableApp {
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub execution_mode: String,
|
||||
pub starred: bool,
|
||||
pub edited_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Deserialize)]
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
let intervalId: SetIntervalAsyncTimer<unknown[]> | undefined = undefined
|
||||
|
||||
let syncIteration: number = 0
|
||||
let errorIteration = 0
|
||||
|
||||
let ITERATIONS_BEFORE_SLOW_REFRESH = 10
|
||||
let ITERATIONS_BEFORE_SUPER_SLOW_REFRESH = 100
|
||||
|
||||
@@ -119,6 +121,8 @@
|
||||
}
|
||||
|
||||
export async function watchJob(testId: string) {
|
||||
syncIteration = 0
|
||||
errorIteration = 0
|
||||
const isCompleted = await loadTestJob(testId)
|
||||
if (!isCompleted) {
|
||||
isLoading = true
|
||||
@@ -161,10 +165,10 @@
|
||||
}
|
||||
notfound = false
|
||||
} catch (err) {
|
||||
intervalId && clearIntervalAsync(intervalId!)
|
||||
clearCurrentJob()
|
||||
if (err.status === 404) {
|
||||
errorIteration += 1
|
||||
if (errorIteration == 5) {
|
||||
notfound = true
|
||||
await clearCurrentJob()
|
||||
}
|
||||
console.warn(err)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
|
||||
import { RefreshCcw, RefreshCw } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
|
||||
export let componentId: string
|
||||
|
||||
const { runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let loading = false
|
||||
async function refresh() {
|
||||
loading = true
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
|
||||
await $runnableComponents[componentId]?.()
|
||||
loading = false
|
||||
}
|
||||
let loading = false
|
||||
$: $worldStore?.outputsById[componentId]?.['loading']?.subscribe({
|
||||
next: (value) => {
|
||||
loading = value
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<Button
|
||||
iconOnly
|
||||
startIcon={{ icon: faRefresh, classes: loading ? 'animate-spin' : '' }}
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={refresh}
|
||||
/>
|
||||
<button
|
||||
on:pointerdown|preventDefault|stopPropagation
|
||||
on:click|preventDefault|stopPropagation={refresh}
|
||||
class="center-center p-1 rounded border bg-white/60 z-40"
|
||||
>
|
||||
<RefreshCw class={loading ? 'animate-spin' : ''} size={16} />
|
||||
</button>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { fieldTypeToTsType, loadSchema, schemaToInputsSpec } from '../../utils'
|
||||
import InputValue from './InputValue.svelte'
|
||||
import MissingConnectionWarning from './MissingConnectionWarning.svelte'
|
||||
import RefreshButton from './RefreshButton.svelte'
|
||||
|
||||
// Component props
|
||||
export let id: string
|
||||
@@ -78,10 +79,9 @@
|
||||
return areAllArgsValid
|
||||
}
|
||||
|
||||
$: isValid = argMergedArgsValid(
|
||||
{ ...extraQueryParams, ...runnableInputValues, ...args },
|
||||
testJobLoader
|
||||
)
|
||||
$: isValid =
|
||||
Object.keys(fields ?? {}).length == 0 ||
|
||||
argMergedArgsValid({ ...extraQueryParams, ...runnableInputValues, ...args }, testJobLoader)
|
||||
|
||||
// Test job internal state
|
||||
let testJob: CompletedJob | undefined = undefined
|
||||
@@ -297,14 +297,18 @@
|
||||
{/if}
|
||||
|
||||
{#if !runnable && autoRefresh}
|
||||
<Alert type="warning" size="xs" class="mt-2" title="Missing runnable">
|
||||
<Alert type="warning" size="xs" class="mt-2 px-1" title="Missing runnable">
|
||||
Please select a runnable
|
||||
</Alert>
|
||||
{:else if autoRefresh === true}
|
||||
<div class="flex absolute top-1 right-1">
|
||||
<RefreshButton componentId={id} />
|
||||
</div>
|
||||
|
||||
{#if isValid}
|
||||
<slot />
|
||||
{:else}
|
||||
<Alert type="info" size="xs" class="mt-2" title="Missing inputs">
|
||||
<Alert type="info" size="xs" class="mt-2 px-1" title="Missing inputs">
|
||||
Please fill in all the inputs
|
||||
|
||||
{#each Object.keys(fields ?? {}) as key}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
$: result = [] as Array<Record<string, any>>
|
||||
|
||||
let search: 'Runnable' | 'Component' | 'Disabled' = 'Disabled'
|
||||
let search: 'Runnable' | 'Component' | 'Disabled' | undefined = undefined
|
||||
let searchValue = ''
|
||||
|
||||
let pagination: boolean | undefined = undefined
|
||||
@@ -114,16 +114,16 @@
|
||||
<RunnableWrapper bind:componentInput {id} bind:result {extraQueryParams}>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
<div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
|
||||
<div class="py-2 px-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<RefreshButton componentId={id} />
|
||||
{#if search !== 'Disabled'}
|
||||
{#if search !== 'Disabled'}
|
||||
<div class="px-4 py-2">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<DebouncedInput placeholder="Search..." bind:value={searchValue} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="overflow-auto flex-1 w-full">
|
||||
<table class="divide-y divide-gray-300 w-full border-b border-b-gray-200">
|
||||
<thead class="bg-gray-50 text-left">
|
||||
|
||||
@@ -32,10 +32,12 @@
|
||||
import { grid } from 'd3-dag'
|
||||
import SettingsPanel from './SettingsPanel.svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
import type { Policy } from '$lib/gen'
|
||||
|
||||
export let app: App
|
||||
export let path: string
|
||||
export let initialMode: EditorMode = 'dnd'
|
||||
export let policy: Policy
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
@@ -49,7 +51,7 @@
|
||||
input: undefined
|
||||
})
|
||||
|
||||
const runnableComponents = writable<Record<string, () => void>>({})
|
||||
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
worldStore,
|
||||
@@ -88,20 +90,20 @@
|
||||
{/if}
|
||||
|
||||
{#if previewing}
|
||||
<AppPreview app={$appStore} appPath={path} {breakpoint} />
|
||||
<AppPreview app={$appStore} appPath={path} {breakpoint} {policy} />
|
||||
{:else}
|
||||
<SplitPanesWrapper class="max-w-full overflow-hidden">
|
||||
<Pane size={20}>
|
||||
<Pane size={15}>
|
||||
<ContextPanel />
|
||||
</Pane>
|
||||
<Pane size={55}>
|
||||
<Pane size={65}>
|
||||
<SplitPanesWrapper horizontal>
|
||||
<Pane size={70}>
|
||||
<div class="bg-gray-100 w-full p-4 h-full overflow-auto">
|
||||
<div class={classNames('bg-gray-100 mx-auto relative min-h-full', width)}>
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('w-full p-2 h-full bg-white', width)}>
|
||||
<GridEditor />
|
||||
<div class={classNames('w-full px-2 pb-2 h-full bg-white', width)}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $connectingInput.opened}
|
||||
@@ -124,7 +126,7 @@
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
</Pane>
|
||||
<Pane size={25} minSize={20} maxSize={33}>
|
||||
<Pane size={20} minSize={15} maxSize={33}>
|
||||
<div class="relative">
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
import GridEditor from './GridEditor.svelte'
|
||||
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { Policy } from '$lib/gen'
|
||||
|
||||
export let app: App
|
||||
export let appPath: string
|
||||
export let breakpoint: Writable<EditorBreakpoint>
|
||||
export let policy: Policy
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
@@ -29,7 +31,7 @@
|
||||
input: undefined
|
||||
})
|
||||
|
||||
const runnableComponents = writable<Record<string, () => void>>({})
|
||||
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
worldStore,
|
||||
@@ -57,7 +59,7 @@
|
||||
<div class="h-full w-5/6 mx-auto">
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('mx-auto h-full', width)}>
|
||||
<GridEditor />
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<span
|
||||
class={classNames(
|
||||
'px-2 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute z-50',
|
||||
selected ? 'bg-indigo-500 text-white' : 'bg-gray-200/60 text-gray-500'
|
||||
selected ? 'bg-indigo-500/90 text-white' : 'bg-gray-200/60 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{component.id}
|
||||
@@ -24,7 +24,7 @@
|
||||
{#if pointerdown || selected}
|
||||
<button
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-8 z-50 cursor-pointer',
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-10 z-50 cursor-pointer',
|
||||
' hover:bg-gray-800',
|
||||
selected ? 'bg-gray-600/90' : 'bg-gray-600/80'
|
||||
)}
|
||||
@@ -44,19 +44,8 @@
|
||||
<span
|
||||
on:mousedown|stopPropagation|capture
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-16 z-50 cursor-move',
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-20 z-50 cursor-move',
|
||||
'bg-gray-600/80'
|
||||
)}><Move size={16} /></span
|
||||
>
|
||||
<button
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-0 z-50 cursor-pointer',
|
||||
'bg-gray-600/80 hover:bg-gray-800'
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
import ComponentEditor from './ComponentEditor.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { fly } from 'svelte/transition'
|
||||
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import type { Policy } from '$lib/gen'
|
||||
|
||||
export let policy: Policy
|
||||
|
||||
const {
|
||||
selectedComponent,
|
||||
@@ -104,7 +106,15 @@
|
||||
on:pointerup={onpointerup}
|
||||
class="bg-white h-full relative"
|
||||
>
|
||||
<RecomputeAllComponents />
|
||||
<div class="w-full flex justify-between border-b p-1 m-1 items-center gap-4">
|
||||
<h2>{$app.title}</h2>
|
||||
<RecomputeAllComponents />
|
||||
<div class="text-2xs text-gray-600"
|
||||
>{policy.execution_mode} mode {policy.on_behalf_of
|
||||
? `on behalf of ${policy.on_behalf_of}`
|
||||
: ''}</div
|
||||
>
|
||||
</div>
|
||||
<Grid
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
|
||||
import { RefreshCw } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
|
||||
@@ -20,12 +18,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
btnClasses="m-2 mb-6"
|
||||
startIcon={{ icon: faRefresh, classes: classNames(loading ? 'animate-spin' : '', 'mr-2') }}
|
||||
color="dark"
|
||||
on:click={onRefresh}
|
||||
<button
|
||||
on:click|preventDefault|stopPropagation={onRefresh}
|
||||
class="center-center p-1 rounded border bg-white/60 inline-flex gap-2"
|
||||
>
|
||||
Recompute all ({Object.keys($runnableComponents).length})
|
||||
</Button>
|
||||
<RefreshCw class={loading ? 'animate-spin' : ''} size={20} /> ({Object.keys($runnableComponents)
|
||||
.length})
|
||||
</button>
|
||||
|
||||
@@ -8,7 +8,7 @@ const Breakpoints = {
|
||||
}
|
||||
|
||||
const columnConfiguration: ColumnConfiguration = [
|
||||
[Breakpoints.lg, 12],
|
||||
[Breakpoints.lg, 9],
|
||||
[Breakpoints.sm, 3]
|
||||
]
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ export type AppEditorContext = {
|
||||
mode: Writable<EditorMode>
|
||||
connectingInput: Writable<ConnectingInput>
|
||||
breakpoint: Writable<EditorBreakpoint>
|
||||
runnableComponents: Writable<Record<string, () => void>>
|
||||
runnableComponents: Writable<Record<string, () => Promise<void>>>
|
||||
appPath: string
|
||||
}
|
||||
|
||||
|
||||
@@ -53,18 +53,18 @@
|
||||
<nav class="grow flex md:justify-between flex-col overflow-x-hidden scrollbar-hidden px-2 md:pb-4">
|
||||
<div class="space-y-1 pt-4 mb-6 md:mb-10">
|
||||
{#each mainMenuLinks as menuLink (menuLink.href)}
|
||||
<MenuLink class="text-md" {...menuLink} {isCollapsed} />
|
||||
<MenuLink class="!text-md" {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
</div>
|
||||
<div>
|
||||
<div class="space-y-1 mb-6 md:mb-10">
|
||||
<div class="mb-6 md:mb-10">
|
||||
{#each secondaryMenuLinks as menuLink (menuLink.href)}
|
||||
<MenuLink class="text-xs" {...menuLink} {isCollapsed} />
|
||||
<MenuLink class="!text-xs" {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
</div>
|
||||
<div>
|
||||
{#each thirdMenuLinks as menuLink (menuLink.href)}
|
||||
<MenuLink class="text-xs" {...menuLink} {isCollapsed} />
|
||||
<MenuLink class="!text-xs" {...menuLink} {isCollapsed} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
|
||||
import { starStore, superadmin, usageStore, userStore, workspaceStore } from '$lib/stores'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import { beforeNavigate, goto } from '$app/navigation'
|
||||
import { afterNavigate, beforeNavigate, goto } from '$app/navigation'
|
||||
import UserSettings from '$lib/components/UserSettings.svelte'
|
||||
import SuperadminSettings from '$lib/components/SuperadminSettings.svelte'
|
||||
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
|
||||
@@ -81,16 +81,15 @@
|
||||
]
|
||||
}
|
||||
|
||||
$: onAppEditor = $page.url.pathname?.includes('apps')
|
||||
|
||||
$: if (onAppEditor) {
|
||||
isCollapsed = true
|
||||
} else {
|
||||
if (innerWidth < 1248 && innerWidth >= 768) {
|
||||
afterNavigate((n) => {
|
||||
if (n.to?.url.pathname.startsWith('/apps')) {
|
||||
isCollapsed = true
|
||||
} else if (innerWidth >= 1248 || innerWidth < 768) {
|
||||
isCollapsed = false
|
||||
}
|
||||
})
|
||||
$: if (innerWidth < 1248 && innerWidth >= 768) {
|
||||
isCollapsed = true
|
||||
} else if (innerWidth >= 1248 || innerWidth < 768) {
|
||||
isCollapsed = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -199,9 +198,7 @@
|
||||
<WindmillIcon white={true} height="20px" width="20px" />
|
||||
</div>
|
||||
{#if !isCollapsed}
|
||||
<span>
|
||||
Windmill
|
||||
</span>
|
||||
<span> Windmill </span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
@@ -221,7 +218,6 @@
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed
|
||||
}}
|
||||
disabled={onAppEditor}
|
||||
>
|
||||
<Icon
|
||||
data={faArrowLeft}
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
|
||||
{#if app}
|
||||
<div class="h-screen">
|
||||
<AppEditor app={app.value} path={app.path} />
|
||||
<AppEditor app={app.value} path={app.path} policy={app.policy} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -3,12 +3,9 @@
|
||||
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
|
||||
import type { EditorBreakpoint } from '$lib/components/apps/types'
|
||||
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { AppService, AppWithLastVersion } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
let app: AppWithLastVersion | undefined = undefined
|
||||
@@ -24,20 +21,10 @@
|
||||
const breakpoint = writable<EditorBreakpoint>('lg')
|
||||
</script>
|
||||
|
||||
<Skeleton loading={app == undefined} layout={[10]} />
|
||||
|
||||
<CenteredPage>
|
||||
{#if app}
|
||||
<div class="flex justify-between items-center py-4">
|
||||
<h2>{app.value.title}</h2>
|
||||
|
||||
<Button size="xs" href="/apps/edit/{$page.params.path}" startIcon={{ icon: faPen }}>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-2">
|
||||
<AppPreview app={app.value} appPath={app.path} {breakpoint} />
|
||||
</div>
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
{#if app}
|
||||
<div class="border rounded-md p-2 w-full">
|
||||
<AppPreview app={app.value} appPath={app.path} {breakpoint} policy={app.policy} />
|
||||
</div>
|
||||
{:else}
|
||||
<Skeleton layout={[10]} />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user