feat: UX/UI full overhaul to meet new design system

This commit is contained in:
Guilhem
2025-10-22 16:56:36 +01:00
committed by GitHub
parent ad53ca609e
commit 888837431c
578 changed files with 8840 additions and 7405 deletions
+15 -2
View File
@@ -3,6 +3,12 @@
@tailwind components;
@tailwind utilities;
@media (min-width: 1760px) {
:root {
font-size: 18px;
}
}
@layer base {
/* Light mode: default border color */
@@ -10,7 +16,7 @@
.border-x, .border-y,
.divide-x > :not([hidden]) ~ :not([hidden]),
.divide-y > :not([hidden]) ~ :not([hidden]) {
border-color: #e5e7eb; /* gray-200 */
border-color: rgb(var(--color-border-light));
}
/* Dark mode: change border color */
@@ -18,7 +24,7 @@
.dark .border-x, .dark .border-y,
.dark .divide-x > :not([hidden]) ~ :not([hidden]),
.dark .divide-y > :not([hidden]) ~ :not([hidden]) {
border-color: #4b5563; /* gray-700 */
border-color: rgb(var(--color-border-light));
}
/* Chrome, Edge, and Safari */
@@ -193,3 +199,10 @@ svelte-virtual-list-contents > * + * {
);
}
/* Prevent clock icon in input[type="time"] making the input taller */
/* Chrome, Safari, Edge, Opera */
input[type="time"]::-webkit-calendar-picker-indicator {
margin: 0;
padding: 0;
}
+1
View File
@@ -0,0 +1 @@
The content of tokens.json is auto-generated by the 'Figma Variables to JSON' extension
@@ -0,0 +1,2 @@
export const lightModeName = 'light' as const
export const darkModeName = 'dark-3' as const
+403
View File
@@ -0,0 +1,403 @@
{
"tokens": {
"light": {
"surface-accent-primary": "#758ff8",
"surface-accent-hover": "#5074f6",
"surface-accent-clicked": "#2c5beb",
"text-primary": "#3d4758",
"text-secondary": "#718096",
"text-primary-inverse": "#f3f6f8",
"text-secondary-inverse": "#d3d6d8",
"text-tertiary-inverse": "#a8a9ac",
"surface-selected": "#ffffff",
"surface-disabled": "#d8d8e433",
"surface-secondary": "#efeff4",
"surface-hover": "#eeeef4",
"surface-primary": "#fbfbfd",
"border-light": "#e5e7eb",
"border-normal": "#9ca3af",
"border-accent": "#2c5beb",
"surface-accent-selected": "#bfdbfe4c",
"surface-accent-secondary": "#293676",
"surface-tertiary": "#ffffff",
"text-emphasis": "#1d2430",
"text-hint": "#8d93a1",
"text-disabled": "#a0aec0",
"surface-accent-secondary-hover": "#1e255f",
"surface-accent-secondary-clicked": "#303f82",
"component-button-accent-secondary": "#ffffff",
"text-emphasis-inverse": "#f3f4f6",
"reserved-ai": "#a02cde",
"component-virtual-node": "#dce0f1",
"text-accent": "#5074f6",
"border-selected": "#a0affa",
"surface-sunken": "#e8e8ef",
"text-tertiary": "#505c70",
"surface-input": "#ffffff"
},
"dark": {
"surface-accent-primary": "#758ff8",
"surface-accent-hover": "#5074f6",
"surface-accent-clicked": "#2c5beb",
"text-primary": "#d4d7dd",
"text-secondary": "#aab0bb",
"text-primary-inverse": "#2d3748",
"text-secondary-inverse": "#666e7b",
"text-tertiary-inverse": "#a4a9b2",
"surface-selected": "#434c5e",
"surface-disabled": "#21273266",
"surface-secondary": "#2e3440",
"surface-hover": "#454f64",
"surface-primary": "#3b4252",
"border-light": "#485971",
"border-normal": "#718096",
"border-accent": "#a0affa",
"surface-accent-selected": "#6790c34c",
"surface-accent-secondary": "#e8ebfb",
"surface-tertiary": "#434c5e",
"text-emphasis": "#f3f4f6",
"text-hint": "#b3bac8",
"text-disabled": "#717b88",
"surface-accent-secondary-hover": "#c3c9df",
"surface-accent-secondary-clicked": "#9da6ca",
"component-button-accent-secondary": "#f3f4f6",
"text-emphasis-inverse": "#2d3748",
"reserved-ai": "#f0c6fb",
"component-virtual-node": "#4c566a",
"text-accent": "#c7cefc",
"border-selected": "#758ff8",
"surface-sunken": "#2e3440",
"text-tertiary": "#a8aeb7",
"surface-input": "#2e3440"
},
"dark-3": {
"surface-accent-primary": "#7085db",
"surface-accent-hover": "#5670d5",
"surface-accent-clicked": "#425bbd",
"text-primary": "#d4d7dd",
"text-secondary": "#a9b0ba",
"text-primary-inverse": "#2d3748",
"text-secondary-inverse": "#666e7b",
"text-tertiary-inverse": "#a4a9b2",
"surface-selected": "#434c5e",
"surface-disabled": "#212732",
"surface-secondary": "#272c35",
"surface-hover": "#454f64",
"surface-primary": "#2e3441",
"border-light": "#374457",
"border-normal": "#a9b0ba",
"border-accent": "#a0affa",
"surface-accent-selected": "#6790c44c",
"surface-accent-secondary": "#e8ebfb",
"surface-tertiary": "#353c4a",
"text-emphasis": "#eeeff2",
"text-hint": "#8d93a1",
"text-disabled": "#9098a2",
"surface-accent-secondary-hover": "#c3c9df",
"surface-accent-secondary-clicked": "#9da6ca",
"component-button-accent-secondary": "#f3f4f6",
"text-emphasis-inverse": "#2d3748",
"reserved-ai": "#f0c6fb",
"component-virtual-node": "#4c566a",
"text-accent": "#c7cefc",
"border-selected": "#758ff8",
"surface-sunken": "#242832",
"text-tertiary": "#a8aeb7",
"surface-input": "#292e38"
}
},
"primitives": {
"light": {
"light-blue": "#bcd4fc",
"blue": "#5e81ac",
"dark-blue": "#394a6d",
"accent-blue": "#2c5beb",
"transparent": "#ffffff00",
"deep-blue-900": "#1e255f",
"deep-blue-800": "#293676",
"deep-blue-700": "#303f82",
"deep-blue-600": "#39498e",
"deep-blue-500": "#3f5097",
"deep-blue-400": "#5b6aa5",
"deep-blue-300": "#7784b4",
"deep-blue-200": "#9da6ca",
"deep-blue-100": "#c3c9df",
"deep-blue-50": "#e7eaf2",
"luminance-blue-900": "#1e3a8a",
"luminance-blue-800": "#0d3cc5",
"luminance-blue-700": "#1847d2",
"luminance-blue-600": "#2652df",
"luminance-blue-500": "#2c5beb",
"luminance-blue-400": "#5074f6",
"luminance-blue-300": "#758ff8",
"luminance-blue-200": "#a0affa",
"luminance-blue-100": "#c7cefc",
"luminance-blue-50": "#e9ecfe",
"nord-0": "#2e3440",
"nord-1": "#3b4252",
"nord-2": "#434c5e",
"nord-3": "#4c566a",
"nord-5": "#e5e9f0",
"nord-6": "#eceff4",
"nord-7": "#8fbcbb",
"nord-8": "#88c0d0",
"nord-9": "#81a1c1",
"nord-10": "#5e81ac",
"nord-4": "#d8dee9",
"nord-11": "#bf616a",
"nord-12": "#d08770",
"nord-13": "#ebcb8b",
"nord-14": "#a3be8c",
"nord-15": "#b48ead"
}
},
"guidelines": { "mode-1": { "blue": "#5e81ac", "demo-background": "#ffffff00" } },
"tailwind-c-s-s-v-3-3-2": {
"mode-1": {
"black": "#000000",
"white": "#ffffff",
"slate-50": "#f8fafc",
"slate-100": "#f1f5f9",
"slate-200": "#e2e8f0",
"slate-300": "#cbd5e1",
"slate-400": "#94a3b8",
"slate-500": "#64748b",
"slate-600": "#475569",
"slate-700": "#334155",
"slate-800": "#1e293b",
"slate-900": "#0f172a",
"slate-950": "#020617",
"gray-50": "#f9fafb",
"gray-100": "#f3f4f6",
"gray-200": "#e5e7eb",
"gray-300": "#d1d5db",
"gray-400": "#9ca3af",
"gray-500": "#6b7280",
"gray-600": "#4b5563",
"gray-700": "#374151",
"gray-800": "#1f2937",
"gray-900": "#111827",
"gray-950": "#030712",
"zinc-50": "#fafafa",
"zinc-100": "#f4f4f5",
"zinc-200": "#e4e4e7",
"zinc-300": "#d4d4d8",
"zinc-400": "#a1a1aa",
"zinc-500": "#71717a",
"zinc-600": "#52525b",
"zinc-700": "#3f3f46",
"zinc-800": "#27272a",
"zinc-900": "#18181b",
"zinc-950": "#09090b",
"neutral-50": "#fafafa",
"neutral-100": "#f5f5f5",
"neutral-200": "#e5e5e5",
"neutral-300": "#d4d4d4",
"neutral-400": "#a3a3a3",
"neutral-500": "#737373",
"neutral-600": "#525252",
"neutral-700": "#404040",
"neutral-800": "#262626",
"neutral-900": "#171717",
"neutral-950": "#0a0a0a",
"stone-50": "#fafaf9",
"stone-100": "#f5f5f4",
"stone-200": "#e7e5e4",
"stone-300": "#d6d3d1",
"stone-400": "#a8a29e",
"stone-500": "#78716c",
"stone-600": "#57534e",
"stone-700": "#44403c",
"stone-800": "#292524",
"stone-900": "#1c1917",
"stone-950": "#0c0a09",
"red-50": "#fef2f2",
"red-100": "#fee2e2",
"red-200": "#fecaca",
"red-300": "#fca5a5",
"red-400": "#f87171",
"red-500": "#ef4444",
"red-600": "#dc2626",
"red-700": "#b91c1c",
"red-800": "#991b1b",
"red-900": "#7f1d1d",
"red-950": "#450a0a",
"orange-50": "#fff7ed",
"orange-100": "#ffedd5",
"orange-200": "#fed7aa",
"orange-300": "#fdba74",
"orange-400": "#fb923c",
"orange-500": "#f97316",
"orange-600": "#ea580c",
"orange-700": "#c2410c",
"orange-800": "#9a3412",
"orange-900": "#7c2d12",
"orange-950": "#431407",
"amber-50": "#fffbeb",
"amber-100": "#fef3c7",
"amber-200": "#fde68a",
"amber-300": "#fcd34d",
"amber-400": "#fbbf24",
"amber-500": "#f59e0b",
"amber-600": "#d97706",
"amber-700": "#b45309",
"amber-800": "#92400e",
"amber-900": "#78350f",
"amber-950": "#451a03",
"yellow-50": "#fefce8",
"yellow-100": "#fef9c3",
"yellow-200": "#fef08a",
"yellow-300": "#fde047",
"yellow-400": "#facc15",
"yellow-500": "#eab308",
"yellow-600": "#ca8a04",
"yellow-700": "#a16207",
"yellow-800": "#854d0e",
"yellow-900": "#713f12",
"yellow-950": "#422006",
"lime-50": "#f7fee7",
"lime-100": "#ecfccb",
"lime-200": "#d9f99d",
"lime-300": "#bef264",
"lime-400": "#a3e635",
"lime-500": "#84cc16",
"lime-600": "#65a30d",
"lime-700": "#4d7c0f",
"lime-800": "#3f6212",
"lime-900": "#365314",
"lime-950": "#1a2e05",
"green-50": "#f0fdf4",
"green-100": "#dcfce7",
"green-200": "#bbf7d0",
"green-300": "#86efac",
"green-400": "#4ade80",
"green-500": "#22c55e",
"green-600": "#16a34a",
"green-700": "#15803d",
"green-800": "#166534",
"green-900": "#14532d",
"green-950": "#052e16",
"emerald-50": "#ecfdf5",
"emerald-100": "#d1fae5",
"emerald-200": "#a7f3d0",
"emerald-300": "#6ee7b7",
"emerald-400": "#34d399",
"emerald-500": "#10b981",
"emerald-600": "#059669",
"emerald-700": "#047857",
"emerald-800": "#065f46",
"emerald-900": "#064e3b",
"emerald-950": "#022c22",
"teal-50": "#f0fdfa",
"teal-100": "#ccfbf1",
"teal-200": "#99f6e4",
"teal-300": "#5eead4",
"teal-400": "#2dd4bf",
"teal-500": "#14b8a6",
"teal-600": "#0d9488",
"teal-700": "#0f766e",
"teal-800": "#115e59",
"teal-900": "#134e4a",
"teal-950": "#042f2e",
"cyan-50": "#ecfeff",
"cyan-100": "#cffafe",
"cyan-200": "#a5f3fc",
"cyan-300": "#67e8f9",
"cyan-400": "#22d3ee",
"cyan-500": "#06b6d4",
"cyan-600": "#0891b2",
"cyan-700": "#0e7490",
"cyan-800": "#155e75",
"cyan-900": "#164e63",
"cyan-950": "#083344",
"sky-50": "#f0f9ff",
"sky-100": "#e0f2fe",
"sky-200": "#bae6fd",
"sky-300": "#7dd3fc",
"sky-400": "#38bdf8",
"sky-500": "#0ea5e9",
"sky-600": "#0284c7",
"sky-700": "#0369a1",
"sky-800": "#075985",
"sky-900": "#0c4a6e",
"sky-950": "#082f49",
"blue-50": "#eff6ff",
"blue-100": "#dbeafe",
"blue-200": "#bfdbfe",
"blue-300": "#93c5fd",
"blue-400": "#60a5fa",
"blue-500": "#3b82f6",
"blue-600": "#2563eb",
"blue-700": "#1d4ed8",
"blue-800": "#1e40af",
"blue-900": "#1e3a8a",
"blue-950": "#172554",
"indigo-50": "#eef2ff",
"indigo-100": "#e0e7ff",
"indigo-200": "#c7d2fe",
"indigo-300": "#a5b4fc",
"indigo-400": "#818cf8",
"indigo-500": "#6366f1",
"indigo-600": "#4f46e5",
"indigo-700": "#4338ca",
"indigo-800": "#3730a3",
"indigo-900": "#312e81",
"indigo-950": "#1e1b4b",
"violet-50": "#f5f3ff",
"violet-100": "#ede9fe",
"violet-200": "#ddd6fe",
"violet-300": "#c4b5fd",
"violet-400": "#a78bfa",
"violet-500": "#8b5cf6",
"violet-600": "#7c3aed",
"violet-700": "#6d28d9",
"violet-800": "#5b21b6",
"violet-900": "#4c1d95",
"violet-950": "#2e1065",
"purple-50": "#faf5ff",
"purple-100": "#f3e8ff",
"purple-200": "#e9d5ff",
"purple-300": "#d8b4fe",
"purple-400": "#c084fc",
"purple-500": "#a855f7",
"purple-600": "#9333ea",
"purple-700": "#7e22ce",
"purple-800": "#6b21a8",
"purple-900": "#581c87",
"purple-950": "#3b0764",
"fuchsia-50": "#fdf4ff",
"fuchsia-100": "#fae8ff",
"fuchsia-200": "#f5d0fe",
"fuchsia-300": "#f0abfc",
"fuchsia-400": "#e879f9",
"fuchsia-500": "#d946ef",
"fuchsia-600": "#c026d3",
"fuchsia-700": "#a21caf",
"fuchsia-800": "#86198f",
"fuchsia-900": "#701a75",
"fuchsia-950": "#4a044e",
"pink-50": "#fdf2f8",
"pink-100": "#fce7f3",
"pink-200": "#fbcfe8",
"pink-300": "#f9a8d4",
"pink-400": "#f472b6",
"pink-500": "#ec4899",
"pink-600": "#db2777",
"pink-700": "#be185d",
"pink-800": "#9d174d",
"pink-900": "#831843",
"pink-950": "#500724",
"rose-50": "#fff1f2",
"rose-100": "#ffe4e6",
"rose-200": "#fecdd3",
"rose-300": "#fda4af",
"rose-400": "#fb7185",
"rose-500": "#f43f5e",
"rose-600": "#e11d48",
"rose-700": "#be123c",
"rose-800": "#9f1239",
"rose-900": "#881337",
"rose-950": "#4c0519"
}
}
}
@@ -229,36 +229,33 @@
})
</script>
<div
class="w-full flex flex-col gap-1 bg-surface-secondary border-[1px] border-nord-400 dark:border-nord-300 rounded-md"
>
<div class="w-full flex flex-col gap-1 border rounded-md p-4">
<!-- Provider Selection -->
<div class="flex flex-col gap-2 m-[-1px] mt-[-1px]">
<ToggleButtonGroup
selected={value?.kind}
onSelected={onProviderChange}
{disabled}
wrap
tabListClass="w-full bg-transparent"
>
{#snippet children({ item })}
{#each providerOptions.slice(0, 3) as option}
<ToggleButton value={option.value} label={option.label} {item} class="bg-transparent" />
{/each}
<ToggleButtonMore
class="ml-auto"
togglableItems={providerOptions.slice(3)}
{item}
bind:selected={() => value?.kind, (v) => v && onProviderChange(v)}
/>
{/snippet}
</ToggleButtonGroup>
</div>
<ToggleButtonGroup
selected={value?.kind}
onSelected={onProviderChange}
{disabled}
wrap
tabListClass="w-full"
>
{#snippet children({ item })}
{#each providerOptions.slice(0, 3) as option}
<ToggleButton value={option.value} label={option.label} {item} />
{/each}
<ToggleButtonMore
class="ml-auto"
btnText={providerOptions.findIndex((p) => p.value === value.kind) >= 3 ? '' : 'More'}
togglableItems={providerOptions.slice(3)}
{item}
bind:selected={() => value?.kind, (v) => v && onProviderChange(v)}
/>
{/snippet}
</ToggleButtonGroup>
<!-- Resource Selection -->
<div class="flex flex-col rounded-md p-2 gap-2">
<div class="flex flex-col rounded-md pt-2 gap-2">
<div class="flex flex-col gap-1">
<p class="text-sm font-normal text-tertiary">resource</p>
<p class="text-xs font-normal text-primary">resource</p>
<ResourcePicker
bind:value={
() => resourceValueToPath(value?.resource),
@@ -272,13 +269,12 @@
disabled={disabled || !value?.kind}
placeholder="Select resource"
selectFirst={true}
selectInputClass="!bg-surface"
/>
</div>
<!-- Model Selection -->
<div class="flex flex-col gap-1">
<p class="text-sm font-normal text-tertiary">model</p>
<p class="text-xs font-normal text-primary">model</p>
<Select
{items}
bind:value={value.model}
@@ -293,7 +289,6 @@
clearable={false}
noItemsMsg={'No models available'}
bind:filterText
inputClass="min-h-10 !bg-surface disabled:!bg-surface-disabled"
/>
</div>
+2 -3
View File
@@ -75,7 +75,7 @@
<Popover placement="bottom-end">
{#snippet trigger()}
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: UserPlus }}>
<Button variant="accent" size="xs" nonCaptureEvent={true} startIcon={{ icon: UserPlus }}>
Add new user
</Button>
{/snippet}
@@ -115,8 +115,7 @@
{/snippet}
</ToggleButtonGroup>
<Button
variant="contained"
color="blue"
variant="accent"
size="sm"
on:click={() => {
addUser().then(() => {
@@ -139,7 +139,7 @@
</script>
{#if !notFound}
<div class="w-full flex gap-4 flex-row-reverse items-center">
<div class="w-full flex gap-2 flex-row-reverse items-center">
<Toggle
on:change={(e) => switchTab(e.detail)}
options={{
@@ -154,14 +154,7 @@
}}
>
{#snippet trigger()}
<Button
spacingSize="sm"
size="xs"
btnClasses="h-8"
color="light"
variant="border"
nonCaptureEvent
>
<Button spacingSize="sm" size="xs" variant="default" nonCaptureEvent>
From connection string
</Button>
{/snippet}
@@ -217,7 +210,7 @@
/>
</div>
{:else}
<p class="italic text-tertiary text-xs mb-4"
<p class="text-primary font-normal text-xs mb-4"
>No corresponding resource type found in your workspace for {resourceType}. Define the value in
JSON directly</p
>
@@ -45,7 +45,7 @@
size="800px"
>
<DrawerContent
title="Add a Resource"
title="Add a resource"
on:close={drawer.closeDrawer}
tooltip="Resources represent connections to third party systems. Learn more on how to integrate external APIs."
documentationLink="https://www.windmill.dev/docs/integrations/integrations_on_windmill"
@@ -64,7 +64,7 @@
{#snippet actions()}
<div class="flex gap-1">
{#if step > 1}
<Button variant="border" on:click={appConnectInner?.back ?? (() => {})}>Back</Button>
<Button variant="default" on:click={appConnectInner?.back ?? (() => {})}>Back</Button>
{/if}
{#if isGoogleSignin}
<button {disabled} on:click={appConnectInner?.next}>
@@ -75,7 +75,7 @@
/>
</button>
{:else}
<Button {disabled} on:click={appConnectInner?.next ?? (() => {})}>
<Button variant="accent" {disabled} on:click={appConnectInner?.next ?? (() => {})}>
{#if step == 2 && !manual}
Connect
{:else if step == 1}
+179 -150
View File
@@ -29,6 +29,7 @@
import { apiTokenApps, forceSecretValue, linkedSecretValue } from './app_connect'
import type { SchemaProperty } from '$lib/common'
import Tooltip from './Tooltip.svelte'
import TextInput from './text_input/TextInput.svelte'
interface Props {
step?: number
@@ -540,15 +541,14 @@
/>
</div>
<h2 class="mb-4">OAuth APIs</h2>
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
<h2 class="mb-4 text-sm font-semibold text-emphasis">OAuth APIs</h2>
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center">
{#if filteredConnects}
{#each filteredConnects as { key }}
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
unifiedSize="md"
variant="default"
selected={key === resourceType}
on:click={() => {
manual = false
resourceType = key
@@ -565,7 +565,7 @@
{/if}
</div>
{#if connects && connects.length == 0}
<div class="text-secondary text-sm w-full"
<div class="text-secondary text-xs w-full"
>No OAuth APIs has been setup on the instance. To add oauth APIs, first sync the resource
types with the hub, then add oauth configuration. See <a
href="https://www.windmill.dev/docs/misc/setup_oauth">documentation</a
@@ -573,10 +573,10 @@
</div>
{/if}
<h2 class="mt-8 mb-4">Others</h2>
<h2 class="mt-8 mb-2 text-sm font-semibold text-emphasis">Others</h2>
{#if connectsManual && connectsManual?.length < 10}
<div class="text-secondary p-2">
<div class="text-secondary text-xs p-2">
Resource Types have not been synced with the hub. Go to the admins workspace to sync them
(and add a schedule to do daily):
<p class="mt-4"
@@ -590,15 +590,14 @@
</div>
{/if}
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center">
{#if filteredConnectsManual}
{#each filteredConnectsManual as { key }}
{#if nativeLanguagesCategory.includes(key)}
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
unifiedSize="md"
variant="default"
selected={key === resourceType}
on:click={() => {
manual = true
resourceType = key
@@ -612,7 +611,6 @@
{/if}
</div>
<div class="mt-8 mb-4"></div>
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnectsManual}
{#each filteredConnectsManual as { key }}
@@ -622,7 +620,7 @@
aiId={`app-connect-inner-${key}`}
aiDescription={`Connect to ${key}`}
size="sm"
variant="border"
variant="default"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
@@ -642,60 +640,75 @@
{/if}
</div>
{:else if step == 2 && manual}
<Path
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder={resourceType}
kind="resource"
/>
<div class="flex flex-col gap-8">
<Path
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder={resourceType}
kind="resource"
/>
{#if apiTokenApps[resourceType]}
<h2 class="mt-4 mb-2">Instructions</h2>
<div class="pl-10">
<ol class="list-decimal">
{#each apiTokenApps[resourceType].instructions as step}
<li>
{@html step}
</li>
{/each}
</ol>
</div>
{#if apiTokenApps[resourceType].img}
<div class="mt-4 w-full overflow-hidden">
<img class="m-auto max-h-60" alt="connect" src={base + apiTokenApps[resourceType].img} />
{#if apiTokenApps[resourceType]}
<h2 class="mt-4 mb-2">Instructions</h2>
<div class="pl-10">
<ol class="list-decimal">
{#each apiTokenApps[resourceType].instructions as step}
<li>
{@html step}
</li>
{/each}
</ol>
</div>
{#if apiTokenApps[resourceType].img}
<div class="mt-4 w-full overflow-hidden">
<img
class="m-auto max-h-60"
alt="connect"
src={base + apiTokenApps[resourceType].img}
/>
</div>
{/if}
{:else if !emptyString(resourceTypeInfo?.description)}
<label class="flex flex-col gap-1">
<span class="text-sm font-semibold text-emphasis">
{resourceTypeInfo?.name} description
</span>
<div class="text-xs text-primary font-normal">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
</label>
{/if}
{#if resourceType == 'postgresql' || resourceType == 'mysql' || resourceType == 'mongodb'}
<WhitelistIp />
{/if}
{:else if !emptyString(resourceTypeInfo?.description)}
<h4 class="mt-8 mb-2">{resourceTypeInfo?.name} description</h4>
<div class="text-sm">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
{/if}
{#if resourceType == 'postgresql' || resourceType == 'mysql' || resourceType == 'mongodb'}
<WhitelistIp />
{/if}
<h4 class="mt-8 inline-flex items-center gap-4"
>Resource description <Required required={false} />
<div class="flex gap-1 items-center">
<Toggle size="xs" bind:checked={renderDescription} />
<Pen size={14} />
<div class="flex flex-col gap-1">
<label class="inline-flex items-center gap-2" for="resource-description">
<span class="text-xs font-semibold text-emphasis">Resource description</span>
<Required required={false} />
<div class="flex gap-1 items-center">
<Toggle size="xs" bind:checked={renderDescription} />
<Pen size={14} />
</div>
</label>
{#if renderDescription}
<div>
<div class="flex flex-row-reverse text-2xs text-primary -mt-4">GH Markdown</div>
<textarea
id="resource-description"
use:autosize
bind:value={description}
placeholder={'Resource description'}
></textarea>
</div>
{:else if description == undefined || description == ''}
<div class="text-xs text-primary font-normal">No description provided</div>
{:else}
<GfmMarkdown md={description} />
{/if}
</div>
</h4>
{#if renderDescription}
<div>
<div class="flex flex-row-reverse text-2xs text-tertiary -mt-1">GH Markdown</div>
<textarea use:autosize bind:value={description} placeholder={'Resource description'}
></textarea>
</div>
{:else if description == undefined || description == ''}
<div class="text-sm text-tertiary">No description provided</div>
{:else}
<div class="mt-2"></div>
<GfmMarkdown md={description} />
{/if}
<div class="mt-12">
{#key resourceTypeInfo}
<ApiConnectForm
bind:linkedSecret
@@ -710,95 +723,111 @@
</div>
{:else if step == 2 && !manual}
{#if manual == false && resourceType != ''}
<h1 class="mb-4">{resourceType}</h1>
<div class="my-4 text-secondary"
>Create a resource backed by an OAuth connection, whose token is fetched from the external
services and refreshed automatically if needed before expiration.</div
>
<h4 class="mb-2">Description</h4>
<div class="text-sm mb-8">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-1">
<h2 class="text-lg font-semibold text-emphasis">{resourceType}</h2>
<div class="text-primary font-normal text-xs"
>Create a resource backed by an OAuth connection, whose token is fetched from the
external services and refreshed automatically if needed before expiration.</div
>
</div>
{#if supportsClientCredentials}
<div style="margin-bottom: 16px;">
<h3 class="mb-4">Authentication Method</h3>
<div style="display: flex; align-items: center; gap: 8px;">
<input
type="checkbox"
style="width: 16px; height: 16px; margin: 0;"
bind:checked={useClientCredentials}
/>
<span style="font-size: 14px; font-weight: 600;">Use Client Credentials Flow</span>
<Tooltip>
Server-to-server authentication without user interaction.
<br /><br />
Provide your own OAuth client credentials for this resource.
</Tooltip>
{#if resourceTypeInfo?.description}
<div class="flex flex-col gap-1">
<h3 class="text-sm font-semibold text-emphasis">Description</h3>
<div class="text-xs text-primary font-normal">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
</div>
{/if}
{#if useClientCredentials}
<div style="margin-top: 16px;">
<label style="display: block; margin-bottom: 8px;">
<span style="font-weight: 600;">Client ID</span>
<input
type="text"
bind:value={clientId}
placeholder="Enter OAuth client ID"
class="w-full p-2 border border-gray-300 rounded mt-1"
required
/>
</label>
<label style="display: block;">
<span style="font-weight: 600;">Client Secret</span>
<input
type="password"
bind:value={clientSecret}
placeholder="Enter OAuth client secret"
class="w-full p-2 border border-gray-300 rounded mt-1"
required
/>
</label>
<label style="display: block; margin-top: 8px;">
<span style="font-weight: 600;">Token URL Override (Optional)</span>
<input
type="url"
bind:value={tokenUrl}
placeholder="Custom token endpoint URL"
class="w-full p-2 border border-gray-300 rounded mt-1"
/>
<div style="font-size: 12px; color: #666; margin-top: 4px;">
Override the instance-level token URL for this resource
</div>
</label>
{#if supportsClientCredentials}
<div>
<h3 class="text-sm font-semibold text-emphasis mb-1">Authentication Method</h3>
<div class="flex items-center gap-2 mb-2">
<input
type="checkbox"
style="width: 16px; height: 16px; margin: 0;"
bind:checked={useClientCredentials}
id="useClienCrediential"
/>
<label for="useClienCrediential" class="text-xs font-semibold text-emphasis"
>Use Client Credentials Flow</label
>
<Tooltip>
Server-to-server authentication without user interaction.
<br /><br />
Provide your own OAuth client credentials for this resource.
</Tooltip>
</div>
{#if useClientCredentials}
<form class="flex flex-col gap-6">
<label class="flex flex-col gap-1">
<span class="text-xs font-semibold text-emphasis">Client ID</span>
<TextInput
bind:value={clientId}
inputProps={{ placeholder: 'Enter OAuth client ID', required: true }}
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs font-semibold text-emphasis">Client Secret</span>
<TextInput
inputProps={{
type: 'password',
placeholder: 'Enter OAuth client secret',
required: true
}}
bind:value={clientSecret}
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs font-semibold text-emphasis"
>Token URL Override (Optional)</span
>
<div class="text-xs text-primary font-normal">
Override the instance-level token URL for this resource
</div>
<TextInput
inputProps={{
type: 'url',
placeholder: 'Custom token endpoint URL',
required: false
}}
bind:value={tokenUrl}
/>
</label>
</form>
{/if}
</div>
{/if}
<div class="flex flex-col gap-1">
<h3 class="text-xs font-semibold text-emphasis flex gap-4"
>Scopes <button
onclick={() => {
editScopes = !editScopes
}}><Pen size={14} /></button
></h3
>
{#if editScopes}
<OauthScopes bind:scopes />
{:else}
<div class="flex flex-col gap-1">
{#each scopes as scope}
<div class="py-0.5 pl-2 text-xs">- {scope}</div>
{/each}
</div>
{/if}
</div>
{/if}
<h3 class="mb-4 flex gap-4"
>Scopes <button
onclick={() => {
editScopes = !editScopes
}}><Pen size={14} /></button
></h3
>
{#if editScopes}
<OauthScopes bind:scopes />
{:else}
<div class="flex flex-col gap-1">
{#each scopes as scope}
<div class="py-0.5 pl-2 text-xs">- {scope}</div>
{/each}
</div>
{/if}
</div>
{/if}
{:else if step == 3 && !manual && !express}
{#if useClientCredentials}
Connecting with client credentials...
<span class="text-xs text-primary font-normal"> Connecting with client credentials... </span>
{:else}
Finish connection in popup window
<span class="text-xs text-primary font-normal"> Finish connection in popup window </span>
{/if}
{:else}
<Path
@@ -809,16 +838,16 @@
kind="resource"
/>
{#if apiTokenApps[resourceType] || !manual}
<ul class="mt-10">
<li>
<ul class="mt-6">
<li class="text-xs text-primary font-normal">
1. A secret variable containing the {apiTokenApps[resourceType]?.linkedSecret ?? 'token'}
<span class="font-bold">{truncateRev(value, 5, '*****')}</span>
<span class="font-semibold text-emphasis">{truncateRev(value, 5, '*****')}</span>
will be stored a
<span class="font-mono whitespace-nowrap">{path}</span>.
<span class="font-mono whitespace-nowrap text-emphasis">{path}</span>.
</li>
<li class="mt-4">
<li class="mt-2 text-xs text-primary font-normal">
2. The resource containing that token will be stored at the same path <span
class="font-mono whitespace-nowrap">{path}</span
class="font-mono whitespace-nowrap text-emphasis">{path}</span
>. The Variable and Resource will be "linked together", they will be deleted and renamed
together.
</li></ul
@@ -39,7 +39,7 @@
<div class="flex flex-row-reverse w-full pb-2">
<div class="flex gap-2">
{#if step > 2}
<Button variant="border" on:click={appConnect?.back ?? (() => {})}>Back</Button>
<Button variant="default" on:click={appConnect?.back ?? (() => {})}>Back</Button>
{/if}
<Button {disabled} on:click={appConnect?.next ?? (() => {})}>
+8 -8
View File
@@ -40,15 +40,15 @@
{#snippet actions()}
<Button
on:click={() => copyToClipboard(JSON.stringify(jsonViewerContent, null, 4))}
color="light"
size="xs"
variant="subtle"
unifiedSize="md"
startIcon={{ icon: ClipboardCopy }}
>
Copy
</Button>
{/snippet}
{#if isString(jsonViewerContent)}
<pre>{jsonViewerContent}</pre>
<pre class="text-xs whitespace-pre-wrap">{jsonViewerContent}</pre>
{:else}
<ObjectViewer pureViewer json={jsonViewerContent} />
{/if}
@@ -56,7 +56,7 @@
</Drawer>
{#if value == undefined || value == null}
<span class="text-tertiary">null</span>
<span class="text-primary">null</span>
{:else if value === '<function call>'}
{'<function call>'}<Tooltip
>The arg was none and the default argument of the script is a function call, hence the actual
@@ -64,7 +64,7 @@
>
{:else if isString(value) && value.startsWith('$res:')}
<button
class="text-xs text-blue-500"
class="text-xs text-accent"
onclick={async () => {
await getResource(value.substring('$res:'.length))
jsonViewer?.toggleDrawer()
@@ -72,7 +72,7 @@
>
{:else if isString(value) && value.startsWith('$var:')}
<button
class="text-xs text-blue-500"
class="text-xs text-accent"
onclick={async () => {
await getVariable(value.substring('$res:'.length))
jsonViewer?.toggleDrawer()
@@ -83,7 +83,7 @@
{truncate(JSON.stringify(value), 80)}
{#if JSON.stringify(value).length > 80}
<button
class="text-xs text-blue-500"
class="text-xs text-accent"
onclick={() => {
jsonViewerContent = value
jsonViewer?.toggleDrawer()
@@ -95,7 +95,7 @@
<div class="relative">
{#if JSON.stringify(value).length > 120}
<button
class="text-xs absolute top-0 right-8 text-tertiary"
class="text-xs absolute top-0 right-8 text-primary"
onclick={() => {
jsonViewerContent = value
jsonViewer?.toggleDrawer()
+48 -56
View File
@@ -47,6 +47,7 @@
import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte'
import AIProviderPicker from './AIProviderPicker.svelte'
import TextInput, { inputBaseClass, inputBorderClass } from './text_input/TextInput.svelte'
import FileInput from './common/fileInput/FileInput.svelte'
interface Props {
label?: string
@@ -368,17 +369,16 @@
evalValueToRaw()
})
function fileChanged(e: any, cb: (v: string | undefined) => void) {
let t = e.target
if (t && 'files' in t && t.files.length > 0) {
let reader = new FileReader()
reader.onload = (e: any) => {
cb(e.target.result.split('base64,')[1])
}
reader.readAsDataURL(t.files[0])
} else {
function fileChangedInner(file: File | undefined, cb: (v: string | undefined) => void) {
if (!file) {
cb(undefined)
return
}
let reader = new FileReader()
reader.onload = (e: any) => {
cb(e.target.result.split('base64,')[1])
}
reader.readAsDataURL(file)
}
export function focus() {
@@ -531,10 +531,10 @@
{#snippet variableInput()}
{#if variableEditor}
<div class="text-sm text-hint">
<div class="text-2xs text-hint">
{#if value && typeof value == 'string' && value?.startsWith('$var:')}
Linked to variable <button
class="text-nord-950 underline font-normal"
class="text-accent underline font-normal"
onclick={() => variableEditor?.editVariable?.(value.slice(5))}>{value.slice(5)}</button
>
{/if}
@@ -543,7 +543,7 @@
{/snippet}
{#snippet resourceInput()}
{#if variableEditor}
<div class="text-sm text-tertiary">
<div class="text-sm text-primary">
{#if value && typeof value == 'string' && value?.startsWith('$res:')}
Linked to resource <a
target="_blank"
@@ -558,7 +558,7 @@
<!-- svelte-ignore a11y_autofocus -->
<div
class={twMerge(
'flex flex-col w-full rounded-md relative group',
'flex flex-col gap-1 w-full rounded-md relative group',
minW ? 'min-w-[250px]' : '',
diffStatus?.diff ? 'px-2' : '',
diffStatus?.diff == 'added'
@@ -600,7 +600,7 @@
</div>
{/if}
{#if displayHeader}
<div class="flex min-h-7 items-end pb-1">
<div class="flex items-end">
<FieldHeader
prettify={prettifyHeader}
label={title && !emptyString(title) ? title : label}
@@ -612,10 +612,7 @@
{simpleTooltip}
{lightHeader}
{displayType}
labelClass={twMerge(
lightHeaderFont ? '!font-normal !text-sm text-tertiary' : '',
css?.label?.class
)}
labelClass={twMerge(lightHeaderFont ? '!font-normal text-primary' : '', css?.label?.class)}
/>
<div class="ml-auto flex gap-2">
{#if displayJsonToggleHeader}
@@ -640,6 +637,12 @@
</div>
{/if}
{#if description}
<div class={twMerge('text-xs text-secondary', css?.description?.class)}>
<pre class="font-main whitespace-normal">{description}</pre>
</div>
{/if}
<div class="flex space-x-1">
{#if inputCat == 'number'}
{#if extra['min'] != undefined && extra['max'] != undefined}
@@ -770,7 +773,7 @@
<Module.default code={JSON.stringify(value, null, 2)} bind:value />
{/await}
{:else}
<div class="px-3 pt-6 border rounded-md w-full">
<div class="px-4 pt-4 border rounded-md w-full">
<SchemaForm
lightHeaderFont
{onlyMaskPassword}
@@ -846,10 +849,10 @@
{@render deleteItemBtn()}
</div>
{:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'}
<input
type="file"
class="my-6"
onchange={(x) => fileChanged(x, (val) => (value[i] = val))}
<FileInput
class="w-full"
on:change={(x) =>
fileChangedInner(x.detail?.[0], (val) => (value[i] = val))}
multiple={false}
/>
{@render deleteItemBtn()}
@@ -955,10 +958,9 @@
{/key}
</div>
<Button
variant="border"
variant="default"
color="light"
size="xs"
btnClasses="text-tertiary py-2.5"
wrapperClasses="w-full {Array.isArray(value) && value.length > 0 ? 'mt-1.5' : ''}"
on:click={() => {
if (value == undefined || !Array.isArray(value)) {
@@ -988,7 +990,7 @@
{/if}
</div>
{#if !displayHeader}
<div class="block mt-2.5 pl-2">
<div class="block mt-2 pl-2">
<Toggle
on:change={(e) => {
// Once the user has changed the input type, we should not change it back automatically
@@ -1010,7 +1012,7 @@
{:else if inputCat == 'dynamic'}
<DynamicInput name={label} {otherArgs} {helperScript} bind:value format={format ?? ''} />
{:else if inputCat == 'resource-object' && resourceTypes == undefined}
<span class="text-2xs text-tertiary">Loading resource types...</span>
<span class="text-2xs text-primary">Loading resource types...</span>
{:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format && format?.split('-').length > 1 && resourceTypes.includes(format?.substring('resource-'.length))))}
<!-- {JSON.stringify(value)} -->
<ObjectResourceInput
@@ -1028,7 +1030,7 @@
/>
{:else if inputCat == 'object' || inputCat == 'resource-object' || isListJson}
{#if oneOf && oneOf.length >= 2}
<div class="flex flex-col gap-2 w-full border rounded-md p-2">
<div class="flex flex-col gap-2 w-full border rounded-md p-4">
{#if oneOf && oneOf.length >= 2}
<ToggleButtonGroup
selected={oneOfSelected}
@@ -1178,7 +1180,7 @@
{/if}
</div>
{:else if properties && Object.keys(properties).length > 0 && inputCat !== 'list'}
<div class={hideNested ? 'hidden' : 'px-3 pt-6 border rounded-md w-full'}>
<div class={hideNested ? 'hidden' : 'px-4 pt-4 border rounded-md w-full'}>
{#if orderEditable}
<SchemaFormDnd
lightHeaderFont
@@ -1293,7 +1295,6 @@
dispatch('blur')
}}
enumLabels={extra['enumLabels']}
selectClass="min-h-10"
/>
</div>
{:else if inputCat == 'date'}
@@ -1331,19 +1332,15 @@
</div>
{/if}
{:else if inputCat == 'base64'}
<div class="flex flex-col my-6 w-full">
<input
{autofocus}
type="file"
onchange={(x) => fileChanged(x, (val) => (value = val))}
<div class="flex flex-col w-full">
<FileInput
on:change={(x) => fileChangedInner(x.detail?.[0], (val) => (value = val))}
multiple={false}
/>
{#if value?.length}
<div class="text-2xs text-tertiary mt-1"
>File length: {value.length} base64 chars ({(value.length / 1024 / 1024).toFixed(
2
)}MB)</div
>
<div class="text-2xs text-primary mt-1">
File length: {value.length} base64 chars ({(value.length / 1024 / 1024).toFixed(2)}MB)
</div>
{/if}
</div>
{:else if inputCat == 'resource-string'}
@@ -1413,16 +1410,17 @@
{#if !disabled && itemPicker && extra?.['disableVariablePicker'] != true}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<button
class="absolute {password || extra?.['password'] == true
? 'right-16 top-1.5'
: 'right-1 top-[7px]'} opacity-0 group-hover:opacity-100 duration-200 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
class={twMerge(
'absolute opacity-0 group-hover:opacity-100 duration-200 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer',
password || extra?.['password'] == true ? 'right-16' : 'right-1'
)}
onclick={() => {
pickForField = label
itemPicker?.openDrawer?.()
}}
title="Insert a Variable"
>
<DollarSign class="!text-tertiary" size={14} />
<DollarSign class="!text-primary" size={14} />
</button>
{/if}
</div>
@@ -1433,20 +1431,14 @@
{@render actions?.()}
</div>
{#if description}
<div class={twMerge('text-2xs italic py-1 text-hint', css?.description?.class)}>
<pre class="font-main whitespace-normal">{description}</pre>
</div>
{/if}
{#if !compact || (error && error != '')}
{#if disabled || error === ''}
&nbsp;
{:else}
<div class="text-right text-xs text-red-600 dark:text-red-400 mb-2">
<div class="text-right text-xs leading-3 text-red-600 dark:text-red-400 mb-2">
{#if disabled || error === ''}
&nbsp;
{:else}
{error}
</div>
{/if}
{/if}
</div>
{:else if !noMargin}
<div class="mb-2"></div>
{/if}
@@ -146,8 +146,7 @@
{#if canEditResourceType || originalType == 'string[]' || originalType == 'object[]'}
<div class="flex flex-row mb-1 mt-2">
<Button
color="light"
variant="border"
variant="default"
size="sm"
on:click={() => {
if (itemsType?.enum) {
@@ -160,8 +159,7 @@
<Plus size={14} />
</Button>
<Button
color="light"
variant="border"
variant="default"
size="sm"
btnClasses="ml-2"
on:click={() => itemsType?.enum && (itemsType.enum = undefined)}
@@ -8,14 +8,14 @@
interface Props {
placement?: 'bottom-end' | 'top-end'
color?: 'nord' | 'dark'
variant?: 'default' | 'accent'
disabled?: boolean
showWorkspaceRestriction?: boolean
}
let {
placement = 'bottom-end',
color = 'dark',
variant = 'default',
disabled = false,
showWorkspaceRestriction = false
}: Props = $props()
@@ -28,7 +28,7 @@
usePointerDownOutside
>
{#snippet trigger()}
<Button {color} size="xs" nonCaptureEvent={true} {disabled}>
<Button {variant} unifiedSize="md" nonCaptureEvent={true} {disabled}>
<div class="flex flex-row gap-1 items-center"
><Pen size={14} /> Custom tags&nbsp;<Tooltip light
>Tags are assigned to scripts and flows. Workers only accept jobs that correspond to their
@@ -93,7 +93,7 @@
</div>
<input type="text" bind:value={newTag} />
{#if extractedCustomTag}
<div class="text-2xs text-tertiary p-2 bg-gray-50 rounded border">
<div class="text-2xs text-primary p-2 bg-gray-50 rounded border">
<div class="font-medium mb-1">Workspace specific tag</div>
<div>
<b>Tag:</b>
@@ -110,7 +110,7 @@
</div>
{:else if newTag.trim()}
{#if newTag.includes('(') || newTag.includes(')') || newTag.includes('+') || newTag.includes('^') || ((newTag.includes('.') || newTag.includes('$args[')) && !dynamicTag)}
<div class="text-2xs text-tertiary p-2 bg-gray-50 rounded border">
<div class="text-2xs text-primary p-2 bg-gray-50 rounded border">
<div class="font-medium mb-1 text-red-500">Invalid tag</div>
<div>
<b>Tag:</b>
@@ -118,7 +118,7 @@
</div>
</div>
{:else}
<div class="text-2xs text-tertiary p-2 bg-gray-50 rounded border">
<div class="text-2xs text-primary p-2 bg-gray-50 rounded border">
<div class="font-medium mb-1">
{#if newTag.includes('$workspace') || newTag.includes('$args')}
Dynamic tag
@@ -141,8 +141,7 @@
{/if}
<Button
variant="contained"
color="blue"
variant="accent"
size="sm"
on:click={async () => {
await SettingService.setGlobal({
@@ -158,7 +157,7 @@
disabled={newTag.trim() == '' || !($superadmin || $devopsRole)}
>
Add {#if !($superadmin || $devopsRole)}
<span class="text-2xs text-tertiary">superadmin or devops only</span>
<span class="text-2xs text-primary">superadmin or devops only</span>
{/if}
</Button>
<span class="text-sm text-primary"
@@ -166,16 +165,16 @@
>worker groups <ExternalLink size={12} /></a
> to listen to tags</span
>
<span class="text-2xs text-tertiary"
<span class="text-2xs text-primary"
>For tags specific to some workspaces, use <pre class="inline">tag(workspace1+workspace2)</pre
></span
>
<span class="text-2xs text-tertiary"
<span class="text-2xs text-primary"
>To exclude 'workspace1' and 'workspace2' from a tag, use <pre class="inline"
>tag(^workspace1^workspace2)</pre
></span
>
<span class="text-2xs text-tertiary"
<span class="text-2xs text-primary"
>For <a
href="https://www.windmill.dev/docs/core_concepts/worker_groups#dynamic-tag"
target="_blank">dynamic tags</a
@@ -183,7 +182,7 @@
based on the workspace, use <pre class="inline">$workspace</pre>, e.g:
<pre class="inline">tag-$workspace</pre></span
>
<span class="text-2xs text-tertiary"
<span class="text-2xs text-primary"
>For <a
href="https://www.windmill.dev/docs/core_concepts/worker_groups#dynamic-tag"
target="_blank">dynamic tags</a
@@ -44,7 +44,7 @@
<div class="flex flex-col gap-1">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="text-sm font-medium text-primary flex gap-4 items-center"
<label class="text-xs font-semibold text-emphasis flex gap-4 items-center"
><div class="w-[120px]"><IconedResourceType name="auth0" after={true} /></div><Toggle
checked={enabled}
on:change={(e) => {
+16 -18
View File
@@ -82,9 +82,9 @@
<div>
<Tabs bind:selected={tab} class="mt-2 mb-4">
<Tab value="sso">SSO</Tab>
<Tab value="oauth">OAuth</Tab>
<Tab value="scim">SCIM/SAML</Tab>
<Tab value="sso" label="SSO" />
<Tab value="oauth" label="OAuth" />
<Tab value="scim" label="SCIM/SAML" />
</Tabs>
</div>
@@ -99,7 +99,7 @@
<div class="py-1"></div>
<div class="mb-2">
<span class="text-primary text-sm"
<span class="text-primary text-xs"
>When at least one of the below options is set, users will be able to login to Windmill
via their third-party account.
<br /> To test SSO, the recommended workflow is to to save the settings and try to login
@@ -108,7 +108,7 @@
></span
>
</div>
<div class="flex flex-col gap-3 py-4">
<div class="flex flex-col gap-4 py-4">
<OAuthSetting name="google" bind:value={oauths['google']} />
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
<OktaSetting bind:value={oauths['okta']} />
@@ -128,7 +128,7 @@
<div class="flex flex-col gap-2 pb-4">
<div class="flex flex-row items-center gap-2">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="text-md font-medium text-primary">{k}</label>
<label class="text-md font-semibold text-primary">{k}</label>
<CloseButton
on:close={() => {
if (oauths) {
@@ -140,7 +140,7 @@
</div>
<div class="p-2 border rounded">
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Custom Name</span>
<span class="text-primary font-semibold text-xs">Custom Name</span>
<input
type="text"
placeholder="Custom Name"
@@ -148,11 +148,11 @@
/>
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Client Id</span>
<span class="text-primary font-semibold text-xs">Client Id</span>
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Client Secret</span>
<span class="text-primary font-semibold text-xs">Client Secret</span>
<input
type="text"
placeholder="Client Secret"
@@ -171,8 +171,7 @@
<div class="flex gap-2 py-4">
<input type="text" placeholder="client_id" bind:value={clientName} />
<Button
variant="border"
color="blue"
variant="accent"
hover="yo"
size="sm"
endIcon={{ icon: Plus }}
@@ -197,7 +196,7 @@
</div>
{:else if tab === 'oauth'}
<div class="mb-2">
<span class="text-primary text-sm"
<span class="text-primary text-xs"
>When one of the below options is set, you will be able to create a specific resource
containing a token automatically generated by the third-party provider.
<br />
@@ -220,7 +219,7 @@
<div class="flex flex-col gap-2 pb-4">
<div class="flex flex-row items-center gap-2">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="text-md font-medium text-primary">{k}</label>
<label class="text-md font-semibold text-primary">{k}</label>
<CloseButton
on:close={() => {
if (oauths) {
@@ -232,11 +231,11 @@
</div>
<div class="p-2 border rounded">
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Client Id</span>
<span class="text-primary font-semibold text-xs">Client Id</span>
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Client Secret</span>
<span class="text-primary font-semibold text-xs">Client Secret</span>
<input type="text" placeholder="Client Secret" bind:value={oauths[k]['secret']} />
</label>
{#if k === 'visma' || !windmillBuiltins.includes(k)}
@@ -289,7 +288,7 @@
{/if}
{#if k == 'snowflake_oauth'}
<label class="block pb-2">
<span class="text-primary font-semibold text-sm flex gap-2 items-center"
<span class="text-primary font-semibold text-xs flex gap-2 items-center"
><a
href="https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier"
target="_blank">Snowflake Account Identifier</a
@@ -323,8 +322,7 @@
<input type="text" value={oauth_name ?? ''} disabled />
{/if}
<Button
variant="border"
color="blue"
variant="accent"
hover="yo"
size="sm"
endIcon={{ icon: Plus }}
@@ -27,17 +27,17 @@
async function checkKubernetesHealth() {
if (!config?.integration || config.integration.type !== 'kubernetes') return
healthCheckLoading = true
healthCheckResult = null
try {
await ConfigService.nativeKubernetesAutoscalingHealthcheck()
healthCheckResult = { success: true }
} catch (error: any) {
healthCheckResult = {
success: false,
error: error.body || error.message || 'Unknown error'
healthCheckResult = {
success: false,
error: error.body || error.message || 'Unknown error'
}
} finally {
healthCheckLoading = false
@@ -323,14 +323,14 @@
<div class="flex mt-6 gap-2">
<Button
color="dark"
variant="accent"
target="_blank"
endIcon={{ icon: ExternalLink }}
href="/scripts/add?hub=hub%2F9204%2Fhelper%2FScale%20a%20worker%20group%20deployed%20as%20a%20kubernetes%20service&workspace=admins"
>Create from template</Button
>
<Button
color="dark"
variant="accent"
target="_blank"
href={`/runs/${config.integration.path}?workspace=admins`}
endIcon={{ icon: ExternalLink }}
@@ -349,25 +349,24 @@
{#if config.integration.type === 'kubernetes'}
<div class="text-sm text-secondary mb-3">
Kubernetes configuration is automatically inferred from the cluster environment.
The worker group name and namespace will be detected automatically.
Kubernetes configuration is automatically inferred from the cluster environment. The
worker group name and namespace will be detected automatically.
</div>
<div class="flex flex-col gap-3 mt-4">
<div class="flex items-center gap-2">
<Button
color="blue"
size="xs"
variant="contained"
<Button
size="xs"
variant="accent"
startIcon={{ icon: ExternalLink }}
href="https://windmill.dev/docs/core_concepts/autoscaling#kubernetes"
target="_blank"
>
Setup Guide (Roles & Bindings)
</Button>
<Button
color="light"
size="xs"
<Button
color="light"
size="xs"
variant="contained"
onclick={checkKubernetesHealth}
disabled={healthCheckLoading}
@@ -375,15 +374,21 @@
{healthCheckLoading ? 'Checking...' : 'Check Health'}
</Button>
</div>
{#if healthCheckResult !== null}
<div class="p-2 rounded-md text-sm {healthCheckResult.success ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300' : 'bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400'}">
<div
class="p-2 rounded-md text-sm {healthCheckResult.success
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400'}"
>
{#if healthCheckResult.success}
Kubernetes autoscaling is healthy
{:else}
{healthCheckResult.error}
{#if healthCheckResult.error?.includes('permissions') || healthCheckResult.error?.includes('role')}
<br><small>Please follow the setup guide above to configure proper RBAC permissions.</small>
<br /><small
>Please follow the setup guide above to configure proper RBAC permissions.</small
>
{/if}
{/if}
</div>
@@ -40,9 +40,9 @@
<div>
<h6
class={!$enterpriseLicense || (events != undefined && events.length == 0)
? 'text-tertiary'
? 'text-xs text-emphasis font-semibold'
: ''}
>Autoscaling events {#if $enterpriseLicense}<span class="text-xs text-tertiary">(5 last)</span>
>Autoscaling events {#if $enterpriseLicense}<span class="text-xs text-primary">(5 last)</span>
<span class="inline-flex ml-6">
<Button
startIcon={{
@@ -61,21 +61,21 @@
</span>{/if}
</h6>
{#if !$enterpriseLicense}
<div class="text-xs pt-2 text-tertiary">Autoscaling is an EE feature</div>
<div class="text-xs pt-1 text-secondary">Autoscaling is an EE feature</div>
{:else if loading}
<Skeleton layout={[[12], 1]} />
{:else if events}
{#if events.length == 0}
<div class="text-xs pt-2 text-tertiary"
<div class="text-xs pt-2 text-primary"
>No events, is autoscaling set in the worker group config?</div
>
{:else}
<div class="flex flex-col gap-2 text-xs text-tertiary pt-4">
<div class="flex flex-col gap-2 text-xs text-primary pt-4">
{#each events as event}
<div class="flex flex-row gap-4">
<div class="text-primary">{event.event_type} to {event.desired_workers}</div>
<div class="text-secondary">{event.reason}</div>
<div class="text-tertiary"><TimeAgo date={event.applied_at ?? ''} /></div>
<div class="text-primary"><TimeAgo date={event.applied_at ?? ''} /></div>
</div>
{/each}
</div>
@@ -84,7 +84,7 @@
<Button color="light" size="xs2" on:click={() => (limit = limit + 25)}>Show more</Button>
</div>
{#if limit > 50}
<div class="mt-4 flex text-xs text-tertiary">
<div class="mt-4 flex text-xs text-primary">
Note that autoscaling events are only stored for the last 30 days.
</div>
{/if}
+16 -11
View File
@@ -3,17 +3,22 @@
import { BROWSER } from 'esm-env'
let url = BROWSER ? window.location.pathname : ''
let entries = $derived(
Object.entries($awarenessStore ?? {}).filter(([a, b]) => b == url && a != $userStore?.username)
)
</script>
<div class="isolate flex -space-x-1 overflow-hidden w-20">
{#each Object.entries($awarenessStore ?? {}).filter(([a, b]) => b == url && a != $userStore?.username) as [user, _]}
<span
class="inline-flex h-6 w-6 items-center justify-center rounded-full ring-2 ring-white bg-gray-600"
title={user}
>
<span class="text-sm font-medium leading-none text-white"
>{user.substring(0, 2).toLocaleUpperCase()}</span
{#if entries.length > 0}
<div class="isolate flex -space-x-1 overflow-hidden w-20">
{#each entries as [user, _]}
<span
class="inline-flex h-6 w-6 items-center justify-center rounded-full ring-2 ring-white bg-gray-600"
title={user}
>
</span>
{/each}
</div>
<span class="text-sm font-medium leading-none text-white"
>{user.substring(0, 2).toLocaleUpperCase()}</span
>
</span>
{/each}
</div>
{/if}
@@ -19,21 +19,21 @@
<div>
<WindmillIcon height="100px" width="100px" spin="slow" />
</div>
<h2 class="text-center pt-2 text-primary">Windmill</h2>
<h2 class="text-center py-2 text-primary">Windmill</h2>
</div>
{/if}
<div
class="border rounded-md shadow-md bg-surface w-full {large
class="rounded-md bg-surface w-full {large
? 'max-w-5xl'
: 'max-w-[640px]'} p-4 sm:py-8 sm:px-10 mb-6 md:mb-20 z-10"
>
<div class="mb-10">
<h1 class="text-center text-primary">
<h1 class="text-center text-lg text-emphasis font-semibold">
{title}
</h1>
{#if subtitle}
<p class="text-sm text-center text-secondary mt-2">
<p class="text-sm font-semibold text-emphasis text-center mt-2">
{subtitle}
</p>
{/if}
@@ -74,10 +74,10 @@
<div class="flex flex-col max-w-2xl {noPadding ? '' : 'p-4'}">
{#if isConflict}
<span class="text-sm mb-2 leading-6 font-semibold">Fix username conflict</span>
<span class="text-sm mb-2 leading-6 font-semibold text-emphasis">Fix username conflict</span>
{/if}
<span class="text-sm font-semibold mb-1 leading-6"
<span class="text-xs font-semibold text-emphasis mb-1 leading-6"
>{isConflict ? 'Auto-generated instance username' : 'New username'}</span
>
<input
@@ -120,9 +120,8 @@
</Alert>
<Button
variant="contained"
color="blue"
size="xs"
variant="default"
unifiedSize="md"
on:click={() => {
renameUser().then(() => {
dispatch('close')
+17 -13
View File
@@ -1,27 +1,31 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import ClipboardPanel from './details/ClipboardPanel.svelte'
import Section from './Section.svelte'
let url = $derived(`${window.location.protocol}//${window.location.hostname}/`)
</script>
<div>
<div class="text-sm font-semibold">CLI quick setup </div>
<div class="text-sm" role="alert" id="dynamic-input-help-box">
<ul class="pl-0 pt-2 list-decimal list-inside flex flex-col gap-2">
<Section label="CLI quick setup">
<div class="text-xs" role="alert" id="dynamic-input-help-box">
<ul class="pl-0 list-decimal list-inside flex flex-col gap-4">
<li>
Install the latest wmill CLI from npm:
<ClipboardPanel content={'npm install -g windmill-cli'} />
<span class="font-medium">Install the latest wmill CLI from npm: </span>
<div class="mt-1">
<ClipboardPanel content={'npm install -g windmill-cli'} />
</div>
</li>
<li>
Setup the wmill cli for this workspace & remote:
<ClipboardPanel
content={`wmill workspace add ${$workspaceStore} ${$workspaceStore} ${url}`}
/>
<span class="font-medium">Setup the wmill cli for this workspace & remote:</span>
<div class="mt-1">
<ClipboardPanel
content={`wmill workspace add ${$workspaceStore} ${$workspaceStore} ${url}`}
/>
</div>
</li>
<li>Follow the prompts in your terminal</li>
<li>Use the run command above!</li>
<li><span class="font-medium">Follow the prompts in your terminal</span></li>
<li><span class="font-medium">Use the run command above!</span></li>
</ul>
</div>
</div>
</Section>
@@ -11,8 +11,7 @@
<div class={twMerge('flex', $$props.class)}>
<Button
variant="border"
color="light"
variant="default"
btnClasses="text-primary {small ? 'text-xs' : ''} "
on:click={() => (open = !open)}
endIcon={{ icon: ChevronDown, classes: open ? 'transform rotate-180' : '' }}
@@ -79,7 +79,6 @@
size="sm"
endIcon={{ icon: platform === 'slack' ? Slack : MsTeamsIcon }}
btnClasses="mt-2"
variant="border"
disabled={!$enterpriseLicense && platform === 'teams'}
on:click={onDisconnect}
>
@@ -104,8 +103,7 @@
<div class="flex flex-row gap-2 items-center">
{#if platform === 'teams'}
<Button
size="xs"
color="dark"
unifiedSize="md"
on:click={connectTeams}
startIcon={{ icon: MsTeamsIcon }}
disabled={!selectedTeam || !$enterpriseLicense}
@@ -123,7 +121,7 @@
/>
{/if}
{:else}
<Button size="xs" color="dark" href={connectHref} startIcon={{ icon: Slack }}>
<Button size="xs" variant="accent" href={connectHref} startIcon={{ icon: Slack }}>
Connect to {platform.charAt(0).toUpperCase() + platform.slice(1)}
</Button>
{/if}
@@ -136,7 +134,7 @@
<div class="text-primary font-md font-semibold"> Script or flow to run on /windmill command </div>
<div class="relative">
{#if !teamName || (!$enterpriseLicense && platform === 'teams')}
<div class="absolute top-0 right-0 bottom-0 left-0 bg-surface-disabled/50 z-40"></div>
<div class="absolute top-0 right-0 bottom-0 left-0 bg-surface-disabled/20 z-40"></div>
{/if}
<ScriptPicker
kinds={['script']}
@@ -148,7 +146,7 @@
/>
</div>
<div class="prose text-2xs text-tertiary">
<div class="prose text-2xs text-primary">
Pick a script or flow meant to be triggered when the `/windmill` command is invoked. Upon
connection, templates for a <a href="{$hubBaseUrlStore}/scripts/{platform}/1405/">script</a>
and <a href="{$hubBaseUrlStore}/flows/28/">flow</a> are available.
@@ -164,10 +164,9 @@
<div class="p-2">
<ToggleButtonGroup bind:selected={searchKind} class="h-10 ">
{#snippet children({ item })}
<ToggleButton small light value="all" label={'All' + counts.all} {item} />
<ToggleButton small value="all" label={'All' + counts.all} {item} />
<ToggleButton
small
light
value="scripts"
icon={Code2}
label={'Scripts' + counts.scripts}
@@ -175,7 +174,6 @@
/>
<ToggleButton
small
light
value="resources"
icon={Boxes}
label={'Resources' + counts.resources}
@@ -183,7 +181,6 @@
/>
<ToggleButton
small
light
value="flows"
label={'Flows' + counts.flows}
icon={FlowIcon}
@@ -192,7 +189,6 @@
/>
<ToggleButton
small
light
value="apps"
label={'Apps' + counts.apps}
icon={LayoutDashboard}
@@ -436,7 +432,7 @@
</div>
{:else}
<div class="flex justify-center items-center h-48">
<div class="text-tertiary text-center">
<div class="text-primary 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
@@ -6,9 +6,10 @@
import Drawer from './common/drawer/Drawer.svelte'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import { sendUserToast } from '$lib/toast'
import Section from './Section.svelte'
import { Save } from 'lucide-svelte'
import autosize from '$lib/autosize'
import Label from './Label.svelte'
import TextInput from './text_input/TextInput.svelte'
const dispatch = createEventDispatcher()
@@ -61,21 +62,25 @@
>
<div class="flex flex-col gap-8">
{#if !edit}
<Section label="Name">
<input type="text" bind:value={name} placeholder="Variable name" />
</Section>
<Label for="name" label="Name">
<TextInput
inputProps={{ type: 'text', placeholder: 'Variable name', id: 'name' }}
bind:value={name}
/>
</Label>
{/if}
<Section label="Value">
<textarea rows="4" use:autosize bind:value placeholder="Variable value"></textarea>
</Section>
<Label for="value" label="Value">
<textarea rows="4" use:autosize bind:value placeholder="Variable value" id="value"
></textarea>
</Label>
</div>
{#snippet actions()}
<Button
on:click={() => updateVariable()}
disabled={value === '' || name === ''}
startIcon={{ icon: Save }}
color="dark"
size="sm"
variant="accent"
unifiedSize="md"
>
{edit ? 'Update' : 'Save'}
</Button>
+10 -9
View File
@@ -1,5 +1,4 @@
<script lang="ts">
import Section from './Section.svelte'
import { Button } from './common'
import { Clock } from 'lucide-svelte'
import Popover from './meltComponents/Popover.svelte'
@@ -10,17 +9,19 @@
let { children }: Props = $props()
</script>
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }} closeButton>
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
closeButton
contentClasses="p-4"
>
{#snippet trigger()}
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: Clock }}>
Use simplified builder
<Button variant="accent" size="xs" nonCaptureEvent={true} startIcon={{ icon: Clock }}>
Cron builder
</Button>
{/snippet}
{#snippet content({ close })}
<Section label="CRON Builder" wrapperClass="p-4">
<div class="flex flex-col w-72">
{@render children?.({ close })}
</div>
</Section>
<div class="flex flex-col w-72">
{@render children?.({ close })}
</div>
{/snippet}
</Popover>
+166 -167
View File
@@ -11,6 +11,7 @@
import MultiSelect from './select/MultiSelect.svelte'
import { safeSelectItems } from './select/utils.svelte'
import { untrack } from 'svelte'
import TextInput from './text_input/TextInput.svelte'
interface Props {
schedule: string
@@ -200,30 +201,40 @@
<div class="w-full flex space-x-8">
<div class="w-full flex flex-col gap-4">
<Label label="Cron" class="font-semibold" primary={true}>
{#snippet error()}
{#if !validCRON}
<div class="text-red-600 text-xs"> Invalid cron syntax </div>
<div class="flex flex-col gap-1">
<label for="cron-schedule" class="text-xs font-semibold text-emphasis">Cron</label>
<div class="flex flex-row gap-2 items-center">
<TextInput
inputProps={{
type: 'text',
id: 'cron-schedule',
name: 'cron-schedule',
placeholder: '0 0 */1 * * *',
disabled: disabled
}}
bind:value={schedule}
error={!validCRON}
/>
{#if !disabled}
<div class="flex flex-row gap-2">
{@render cronBuilder()}
<CronGen bind:schedule bind:cronVersion />
</div>
{/if}
{/snippet}
<div class="flex flex-row-reverse text-2xs text-tertiary -mt-1 hover:underline">
</div>
{#if !validCRON}
<div class="text-red-600 text-2xs"> Invalid cron syntax </div>
{/if}
<div class="flex flex-row-reverse text-2xs text-secondary hover:underline">
<a
class="text-tertiary"
class="text-primary"
href="https://www.windmill.dev/docs/core_concepts/scheduling#cron-syntax"
target="_blank">Croner</a
>
</div>
<input
class="inline-block"
type="text"
id="cron-schedule"
name="cron-schedule"
placeholder="0 0 */1 * * *"
bind:value={schedule}
{disabled}
/>
</Label>
<Label label="Timezone" class="font-semibold" primary>
</div>
<Label label="Timezone">
{#if disabled}
<div>
<Badge><span class="text-primary dark:text-primary-inverse">{timezone}</span></Badge>
@@ -242,162 +253,150 @@
/>
{/if}
</Label>
{#if !disabled}
<div class="flex flex-row gap-2 mb-2">
<CronBuilder>
{#snippet children({ close })}
<div class="w-full flex flex-col">
<div class="w-full flex flex-col gap-1">
<div class="text-secondary text-sm leading-none">Execute schedule every</div>
<div class="w-full flex gap-4">
<div class="w-full flex flex-col gap-1 mb-2">
<select
{disabled}
name="execute_every"
id="execute_every"
bind:value={executeEvery}
>
<option value="second">Second(s)</option>
<option value="minute">Minute(s)</option>
<option value="hour">Hour(s)</option>
<option value="day-month">Day of the month</option>
<option value="month">Month(s)</option>
<option value="day-week">Day of the week</option>
</select>
</div>
<div class="w-full flex flex-col gap-1 justify-center">
{#if executeEvery == 'second'}
<input {disabled} type="number" min="0" max="59" bind:value={seconds} />
<small>Valid range 0-59</small>
{:else if executeEvery == 'minute'}
<input {disabled} type="number" min="0" max="59" bind:value={minutes} />
<small>Valid range 0-59</small>
{:else if executeEvery == 'hour'}
<input {disabled} type="number" min="0" max="23" bind:value={hours} />
<small>Valid range 0-23</small>
{:else if executeEvery == 'day-month'}
<!-- <div class="w-full flex">
<label for="lastDayOfMonth" class="w-full flex items-center gap-2">
<div class="flex">
<input type="checkbox" id="lastDayOfMonth" bind:checked={lastDayOfMonth} />
</div>
<small> Last day of the month </small>
</label>
</div> -->
{/if}
</div>
</div>
</div>
<div class="w-full flex flex-col gap-4">
{#if executeEvery == 'month'}
<div class="w-full flex flex-col">
<MultiSelect
disablePortal
{disabled}
bind:value={monthsOfYear}
items={safeSelectItems(monthsOfYearOptions)}
placeholder="Every month"
/>
</div>
{/if}
{#if executeEvery == 'day-week'}
<div class="w-full flex flex-col">
<MultiSelect
disablePortal
{disabled}
bind:value={daysOfWeek}
items={safeSelectItems(daysOfWeekOptions)}
placeholder="Every day"
/>
</div>
{/if}
{#if executeEvery == 'day-month' || executeEvery == 'month'}
<div class="w-full flex flex-col gap-1">
{#if executeEvery == 'month'}
<small class="font-bold">On day of the month</small>
{/if}
<div class="w-full flex gap-4">
<div class="w-full flex">
<MultiSelect
disablePortal
{disabled}
bind:value={daysOfMonth}
items={safeSelectItems(daysOfMonthOptions)}
placeholder="Every day"
/>
</div>
<!-- {#if executeEvery == 'month'}
<div class="w-full flex">
<label for="lastDayOfMonth" class="w-full flex items-center gap-2">
<div class="flex">
<input type="checkbox" id="lastDayOfMonth" bind:checked={lastDayOfMonth} />
</div>
<small> Last day of the month </small>
</label>
</div>
{/if} -->
</div>
<small>Schedule will only execute on valid calendar days</small>
</div>
{/if}
{#if executeEvery == 'day-month' || executeEvery == 'month' || executeEvery == 'day-week'}
<div class="w-full flex flex-col gap-1">
<small class="font-bold">At Time</small>
<input
{disabled}
type="time"
name="atUTCTime"
id="atUTCTime"
bind:value={UTCTime}
/>
</div>
{/if}
</div>
<div class="w-full flex flex-col gap-1">
<div class="text-secondary text-sm leading-none">Preview New Cron</div>
<div class="flex p-2 px-4 rounded-md bg-surface-secondary">
<span>{nschedule}</span>
</div>
</div>
</div>
<div class="mt-4">
<Button
color="dark"
size="xs"
on:click={() => {
schedule = nschedule
close()
}}
>
Set cron schedule
</Button>
</div>
{/snippet}
</CronBuilder>
<CronGen bind:schedule bind:cronVersion />
</div>
{/if}
</div>
<div class="w-full flex flex-col space-y-2">
<div class="text-sm font-semibold leading-none">Estimated upcoming events ({timezone})</div>
<div class="text-xs font-semibold text-emphasis leading-none"
>Estimated upcoming events ({timezone})</div
>
<div class="flex flex-col space-y-2">
<div class="flex flex-col rounded-md p-4 border text-tertiary bg-surface-secondary gap-0.5">
<div class="flex flex-col rounded-md p-4 border text-primary gap-0.5">
{#each preview as date}
<span class="text-sm">{dateFormatter(new Date(date))}</span>
<span class="text-xs">{dateFormatter(new Date(date))}</span>
{/each}
</div>
</div>
</div>
</div>
{#snippet cronBuilder()}
<CronBuilder>
{#snippet children({ close })}
<div class="w-full flex flex-col gap-6">
<div class="w-full flex flex-col gap-1">
<div class="text-emphasis font-semibold text-xs leading-none">Execute schedule every</div>
<div class="w-full flex gap-4">
<div class="w-full flex flex-col gap-1 mb-2">
<select {disabled} name="execute_every" id="execute_every" bind:value={executeEvery}>
<option value="second">Second(s)</option>
<option value="minute">Minute(s)</option>
<option value="hour">Hour(s)</option>
<option value="day-month">Day of the month</option>
<option value="month">Month(s)</option>
<option value="day-week">Day of the week</option>
</select>
</div>
<div class="w-full flex flex-col gap-1 justify-center">
{#if executeEvery == 'second'}
<input {disabled} type="number" min="0" max="59" bind:value={seconds} />
<small class="text-secondary text-2xs">Valid range 0-59</small>
{:else if executeEvery == 'minute'}
<input {disabled} type="number" min="0" max="59" bind:value={minutes} />
<small class="text-secondary text-2xs">Valid range 0-59</small>
{:else if executeEvery == 'hour'}
<input {disabled} type="number" min="0" max="23" bind:value={hours} />
<small class="text-secondary text-2xs">Valid range 0-23</small>
{:else if executeEvery == 'day-month'}
<!-- <div class="w-full flex">
<label for="lastDayOfMonth" class="w-full flex items-center gap-2">
<div class="flex">
<input type="checkbox" id="lastDayOfMonth" bind:checked={lastDayOfMonth} />
</div>
<small> Last day of the month </small>
</label>
</div> -->
{/if}
</div>
</div>
</div>
{#if executeEvery == 'month'}
<div class="w-full flex flex-col">
<MultiSelect
disablePortal
{disabled}
bind:value={monthsOfYear}
items={safeSelectItems(monthsOfYearOptions)}
placeholder="Every month"
/>
</div>
{/if}
{#if executeEvery == 'day-week'}
<div class="w-full flex flex-col">
<MultiSelect
disablePortal
{disabled}
bind:value={daysOfWeek}
items={safeSelectItems(daysOfWeekOptions)}
placeholder="Every day"
/>
</div>
{/if}
{#if executeEvery == 'day-month' || executeEvery == 'month'}
<div class="w-full flex flex-col gap-1">
{#if executeEvery == 'month'}
<small class="text-secondary text-2xs font-normal">On day of the month</small>
{/if}
<div class="w-full flex gap-4">
<div class="w-full flex">
<MultiSelect
disablePortal
{disabled}
bind:value={daysOfMonth}
items={safeSelectItems(daysOfMonthOptions)}
placeholder="Every day"
/>
</div>
<!-- {#if executeEvery == 'month'}
<div class="w-full flex">
<label for="lastDayOfMonth" class="w-full flex items-center gap-2">
<div class="flex">
<input type="checkbox" id="lastDayOfMonth" bind:checked={lastDayOfMonth} />
</div>
<small> Last day of the month </small>
</label>
</div>
{/if} -->
</div>
<small class="text-secondary text-2xs font-normal"
>Schedule will only execute on valid calendar days</small
>
</div>
{/if}
{#if executeEvery == 'day-month' || executeEvery == 'month' || executeEvery == 'day-week'}
<div class="w-full flex flex-col gap-1">
<small class="font-semibold text-emphasis text-xs">At time</small>
<input {disabled} type="time" name="atUTCTime" id="atUTCTime" bind:value={UTCTime} />
</div>
{/if}
<div class="w-full flex flex-col gap-1">
<div class="text-emphasis font-semibold text-xs">Preview New Cron</div>
<div class="flex p-2 px-4 rounded-md bg-surface-secondary text-xs text-primary">
<span>{nschedule}</span>
</div>
</div>
</div>
<div class="mt-8">
<Button
variant="accent"
size="xs"
on:click={() => {
schedule = nschedule
close()
}}
>
Set cron schedule
</Button>
</div>
{/snippet}
</CronBuilder>
{/snippet}
+7 -7
View File
@@ -28,7 +28,7 @@
</script>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Auth URL</span>
<span class="text-primary font-semibold text-xs">Auth URL</span>
<input
type="text"
placeholder="https://github.com/login/oauth/authorize"
@@ -36,7 +36,7 @@
/>
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Token URL</span>
<span class="text-primary font-semibold text-xs">Token URL</span>
<input
type="text"
placeholder="https://github.com/login/oauth/access_token"
@@ -44,7 +44,7 @@
/>
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Userinfo URL</span>
<span class="text-primary font-semibold text-xs">Userinfo URL</span>
<input
type="text"
placeholder="https://github.com/login/oauth/userinfo"
@@ -53,12 +53,12 @@
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Scopes</span>
<span class="text-primary font-semibold text-xs">Scopes</span>
<OauthScopes bind:scopes={login_config.scopes} />
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block pb-2">
<span class="text-primary font-semibold text-sm"
<span class="text-primary font-semibold text-xs"
>Extra Query Args for Authorize Request&nbsp;<Tooltip
>Not needed in most cases. Examples of uses: google apis require the 2 extra args
"access_type=offline&prompt=consent"</Tooltip
@@ -68,14 +68,14 @@
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block pb-2">
<span class="text-primary font-semibold text-sm"
<span class="text-primary font-semibold text-xs"
>Extra Query Args for Token request <Tooltip>Not needed in most cases</Tooltip></span
>
<OauthExtraParams bind:extra_params={login_config.extra_params_callback} />
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block pb-2">
<span class="text-primary font-semibold text-sm"
<span class="text-primary font-semibold text-xs"
>Payload <Tooltip
>Auth is passed in query most commonly. LinkedIn is an example of OAuth using
x-www-form-urlencoded
+3 -4
View File
@@ -109,8 +109,8 @@
(selected.tableKey === tableKey ? 'bg-gray-500/25' : 'hover:bg-gray-500/10')}
onclick={() => (selected.tableKey = tableKey)}
>
<Table2 class="text-gray-500/40 shrink-0" size={16} />
<p class="truncate text-ellipsis grow text-left">{tableKey}</p>
<Table2 class="text-primary shrink-0" size={16} />
<p class="truncate text-ellipsis grow text-left text-emphasis text-xs">{tableKey}</p>
{#if dbTableActionsFactory}
{@const dbTableActions = dbTableActionsFactory.map((f) =>
f({
@@ -156,7 +156,6 @@
{/each}
</div>
<Button
variant="border"
on:click={() => (dbTableEditorState = { open: true })}
wrapperClasses="mx-2 my-2 text-sm"
startIcon={{ icon: Plus }}
@@ -203,4 +202,4 @@
/>
</DrawerContent>
</Drawer>
{/if}
{/if}
@@ -7,7 +7,7 @@
import { Clock, X } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
import { inputBaseClass, inputBorderClass } from './text_input/TextInput.svelte'
import TextInput from './text_input/TextInput.svelte'
// import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
interface Props {
@@ -113,32 +113,24 @@
</script>
<div
class="flex flex-row gap-1 items-center w-full"
class="flex flex-row gap-1 items-center w-full relative"
id={randomId}
onpointerdown={bubble('pointerdown')}
onfocus={bubble('focus')}
>
<!-- svelte-ignore a11y_autofocus -->
<input
type="date"
<TextInput
inputProps={{ type: 'date', autofocus, disabled, min: minDate, max: maxDate }}
bind:value={date}
{autofocus}
{disabled}
class={twMerge(inputBaseClass, inputBorderClass(), 'text-sm !w-3/4 ', inputClass)}
min={minDate}
max={maxDate}
class={twMerge('!w-3/4 ', inputClass)}
/>
<input
type="time"
<TextInput
inputProps={{ type: 'time', disabled }}
bind:value={time}
class={twMerge(inputBaseClass, inputBorderClass(), 'text-sm !w-1/4 min-w-[100px] ', inputClass)}
{disabled}
class={twMerge('!w-1/4 min-w-[100px] !max-h-full', inputClass)}
/>
<Button
variant="contained"
color="light"
wrapperClasses="h-full"
btnClasses="bg-surface-secondary"
variant="default"
startIcon={{
icon: Clock
}}
@@ -181,8 +173,8 @@
</Button>
{#if clearable}
<Button
variant="border"
color="light"
variant="default"
unifiedSize="md"
wrapperClasses="h-full"
{disabled}
on:click={() => {
@@ -19,14 +19,7 @@
<DefaultScriptsInner />
</DrawerContent>
</Drawer>
<Button
on:click={drawer?.openDrawer}
startIcon={{ icon: SettingsIcon }}
color="light"
{size}
btnClasses="!text-tertiary"
variant="contained"
>
<Button on:click={drawer?.openDrawer} startIcon={{ icon: SettingsIcon }} variant="subtle" {size}>
{noText ? '' : 'defaults'}
</Button>
{/if}
@@ -42,7 +42,8 @@
class="w-full p-2 rounded {small
? ''
: 'border border-secondary'} grid grid-cols-3 items-center"
><h3 class={small ? 'text-xs font-medium justify-center' : ''}>{lang}</h3>
>
<p class={small ? 'text-2xs font-medium justify-center' : 'text-sm'}>{lang}</p>
<div>
{#if i > 0}
<button
@@ -21,7 +21,7 @@
<Popover floatingConfig={{ strategy: 'absolute', placement: placement }} contentClasses="p-4">
{#snippet trigger()}
<Button color="dark" size="xs" nonCaptureEvent={true}>
<Button variant="default" unifiedSize="md" nonCaptureEvent={true}>
<div class="flex flex-row gap-1 items-center"
><Pen size={14} /> Default tags&nbsp;<Tooltip light
>Scripts and steps that have not been specifically assigned tags will use a default tag
@@ -80,8 +80,7 @@
{/if}
</div>
<Button
variant="contained"
color="blue"
variant="accent"
size="sm"
on:click={async () => {
await SettingService.setGlobal({
@@ -105,11 +104,11 @@
disabled={!$enterpriseLicense || !$superadmin}
>
Save {#if !$superadmin}
<span class="text-2xs text-tertiary">superadmin only</span>
<span class="text-2xs text-primary">superadmin only</span>
{/if}
</Button>
<span class="text-2xs text-tertiary"
<span class="text-2xs text-primary"
>When tags use <pre class="inline">$workspace</pre>, the final tag has
<pre class="inline">$workspace</pre> replaced with the workspace id, allowing multi-vpc setup with
more ease, without having to assign a specific tag each time.</span
@@ -29,7 +29,8 @@
<Button
disabled={loading}
loading={loadingSave}
size="xs"
variant="accent"
unifiedSize="md"
startIcon={{ icon: Save }}
on:click={() => dispatch('save')}
dropdownItems={!newFlow ? dropdownItems : undefined}
@@ -70,7 +71,8 @@
bind:this={msgInput}
/>
<Button
size="sm"
unifiedSize="md"
variant="accent"
on:click={async () => dispatch('save', deploymentMsg)}
endIcon={{ icon: CornerDownLeft }}
loading={loadingSave}
@@ -87,8 +87,8 @@
}
</script>
<h3 class="mt-8">Workspace to link to</h3>
<div class="flex min-w-0 mt-2">
<h3 class="mt-6 text-xs font-semibold text-emphasis">Workspace to link to</h3>
<div class="flex min-w-0 mt-1">
<select
bind:value={workspaceToDeployTo}
on:change={async (e) => {
@@ -113,11 +113,11 @@
{/each}
</select>
</div>
<h3 class="mt-6 mb-3">Deployable items</h3>
<h3 class="mt-6 mb-3 text-sm font-semibold text-emphasis">Deployable items</h3>
<div class="flex flex-wrap gap-20">
<div class="max-w-md w-full">
{#if Array.isArray(deployUiSettings?.include_path)}
<h4 class="flex gap-2 mb-4"
<h4 class="flex gap-2 mb-2 text-xs font-semibold text-emphasis"
>Filter on path<Tooltip>
Only scripts, flows and apps with their path matching one of those filters will be allowed
to be deployed in the deploy UI. The filters allow '*'' and '**' characters, with '*''
@@ -144,8 +144,7 @@
{/if}
<div class="flex mt-2">
<Button
variant="border"
color="light"
variant="default"
size="xs"
btnClasses="mt-1"
on:click={() => {
@@ -160,7 +159,7 @@
</div>
<div class="max-w-md w-full">
<h4 class="flex gap-2 mb-4"
<h4 class="flex gap-2 mb-2 text-xs font-semibold text-emphasis"
>Filter on type<Tooltip>
You can filter which types of item can be deployed to the production workspace. By default
everything is deployable.
@@ -201,7 +200,7 @@
{#if $enterpriseLicense}
<div class="flex mt-5 mb-5 gap-1">
<Button
color="blue"
variant="accent"
disabled={workspaceToDeployTo == undefined}
on:click={() => {
editWindmillDeploymentUISettings()
@@ -4,7 +4,7 @@
export let link: string | undefined = undefined
</script>
<div class={twMerge('text-xs text-tertiary font-normal opacity-80', $$props.class)}>
<div class={twMerge('text-xs text-primary font-normal', $$props.class)}>
<slot />
{#if link}
<a href={link} target="_blank">Learn more</a>
+2 -2
View File
@@ -684,7 +684,7 @@
>
</div>
<div class="absolute top-2 right-2 !text-tertiary text-xs">
<div class="absolute top-2 right-2 !text-primary text-xs">
{#if $userStore != undefined}
As {$userStore?.username} in {$workspaceStore}
{:else}
@@ -743,7 +743,7 @@
{:else}
<Button
disabled={currentScript === undefined}
color="dark"
variant="accent"
on:click={(e) => {
runTest()
}}
+27 -20
View File
@@ -136,23 +136,27 @@
{#if diffType && data}
<Tabs bind:selected={diffType} wrapperClass="shrink-0">
{#if data.mode === 'simple'}
<Tab value="custom">{data.title}</Tab>
<Tab value="custom" label={data.title} />
{:else}
<Tab value="deployed" disabled={!data.deployed}
>{'Deployed <> Current'}{!data.deployed ? ' (no deployed version)' : ''}</Tab
>
<Tab value="draft" disabled={!data.draft}
>{'Latest saved draft <> Current'}{!data.draft ? ' (no draft)' : ''}</Tab
>
<Tab
value="deployed"
disabled={!data.deployed}
label="{'Deployed <> Current'}{!data.deployed ? ' (no deployed version)' : ''}"
/>
<Tab
value="draft"
disabled={!data.draft}
label="{'Latest saved draft <> Current'}{!data.draft ? ' (no draft)' : ''}"
/>
{/if}
</Tabs>
{/if}
{#if data?.mode === 'normal'}
{#if diffType === 'draft'}
<Button
size="xs"
color="light"
variant="border"
unifiedSize="md"
variant="default"
wrapperClasses="self-start"
on:click={restoreDraft}
disabled={orderedJsonStringify(data.draft) === orderedJsonStringify(data.current)}
@@ -160,9 +164,8 @@
>
{:else if diffType === 'deployed'}
<Button
size="xs"
color="light"
variant="border"
unifiedSize="md"
variant="default"
wrapperClasses="self-start"
on:click={restoreDeployed}
disabled={!data.draft &&
@@ -195,12 +198,16 @@
<div class="flex flex-col h-full gap-4">
{#if data.current.content !== undefined}
<Tabs bind:selected={contentType}>
<Tab value="content" disabled={content === data.current.content}
>Content{content === data.current.content ? ' (no changes)' : ''}</Tab
>
<Tab value="metadata" disabled={metadata === data.current.metadata}
>Metadata{metadata === data.current.metadata ? ' (no changes)' : ''}</Tab
>
<Tab
value="content"
disabled={content === data.current.content}
label={`Content${content === data.current.content ? ' (no changes)' : ''}`}
/>
<Tab
value="metadata"
disabled={metadata === data.current.metadata}
label={`Metadata${metadata === data.current.metadata ? ' (no changes)' : ''}`}
/>
</Tabs>
{/if}
<div class="flex-1">
@@ -256,7 +263,7 @@
{#snippet actions()}
{#if data?.button}
<Button
color="light"
variant="subtle"
on:click={() => {
if (data?.button) {
data.button.onClick()
@@ -505,7 +505,7 @@
{#if result_stream && result == undefined}
<div class="flex flex-col w-full gap-2">
<div class="flex items-center gap-2 text-tertiary">
<div class="flex items-center gap-2 text-primary">
<Loader2 class="animate-spin" size={16} /> Streaming result
</div>
<ResultStreamDisplay {result_stream} />
@@ -513,18 +513,16 @@
{:else if is_render_all}
<div class="flex flex-col w-full gap-2">
{#if !noControls}
<div class="text-tertiary text-sm">
<div class="text-primary text-sm">
<ToggleButtonGroup
class="h-6"
selected={globalForceJson ? 'json' : 'pretty'}
on:selected={(ev) => {
globalForceJson = ev.detail === 'json'
}}
>
{#snippet children({ item })}
<ToggleButton class="px-1.5" value="pretty" label="Pretty" icon={Highlighter} {item} />
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
<ToggleButton value="pretty" label="Pretty" icon={Highlighter} {item} />
<ToggleButton value="json" label="JSON" icon={Braces} {item} />
{/snippet}
</ToggleButtonGroup>
</div>
@@ -559,11 +557,10 @@
{#if result != undefined && length != undefined && largeObject != undefined}
<div class="flex justify-between items-center w-full">
<div
class="text-tertiary text-sm flex flex-row gap-2 items-center"
class="text-primary text-sm flex flex-row gap-2 items-center"
bind:clientHeight={resultHeaderHeight}
>
{#if !hideAsJson && !['json', 's3object'].includes(resultKind ?? '') && typeof result === 'object'}<ToggleButtonGroup
class="h-6"
selected={forceJson ? 'json' : resultKind?.startsWith('table-') ? 'table' : 'pretty'}
on:selected={(ev) => {
forceJson = ev.detail === 'json'
@@ -571,17 +568,11 @@
>
{#snippet children({ item })}
{#if ['table-col', 'table-row', 'table-row-object'].includes(resultKind ?? '')}
<ToggleButton class="px-1.5" value="table" label="Table" icon={Table2} {item} />
<ToggleButton size="sm" value="table" label="Table" icon={Table2} {item} />
{:else}
<ToggleButton
class="px-1.5"
value="pretty"
label="Pretty"
icon={Highlighter}
{item}
/>
<ToggleButton size="sm" value="pretty" label="Pretty" icon={Highlighter} {item} />
{/if}
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
<ToggleButton size="sm" value="json" label="JSON" icon={Braces} {item} />
{/snippet}
</ToggleButtonGroup>
{/if}
@@ -648,7 +639,7 @@
Warning
</div>
<p
class="text-tertiary mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
class="text-primary mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
>Rendering HTML can expose you to <a
href="https://owasp.org/www-community/attacks/xss/"
target="_blank"
@@ -658,7 +649,7 @@
</p>
</div>
<div class="center-center">
<Button size="sm" color="dark" on:click={() => (enableHtml = true)}>
<Button unifiedSize="md" variant="default" on:click={() => (enableHtml = true)}>
Enable HTML rendering
</Button>
</div>
@@ -723,10 +714,11 @@
<Button
on:click={() =>
copyToClipboard(typeof result === 'string' ? result : result?.['result'])}
color="light"
size="xs"
variant="subtle"
unifiedSize="sm"
endIcon={{ icon: ClipboardCopy }}
>
<div class="flex gap-2 items-center">Copy <ClipboardCopy size={12} /> </div>
Copy
</Button>
</div>
{/if}
@@ -788,8 +780,7 @@
class="flex flex-col gap-3 mt-2 mx-4"
>
<Button
color="green"
variant="border"
variant="accent"
on:click={() =>
fetch(result['resume'], {
method: 'POST',
@@ -799,7 +790,7 @@
>
Resume</Button
>
<Button color="red" variant="border" on:click={() => fetch(result['cancel'])}
<Button variant="default" destructive on:click={() => fetch(result['cancel'])}
>Cancel</Button
>
<div class="center-center"
@@ -926,7 +917,7 @@
code={toJsonStr(s3object).replace(/\\n/g, '\n')}
/>
<button
class="text-secondary text-2xs whitespace-nowrap"
class="text-primary text-2xs whitespace-nowrap"
onclick={() => {
s3FileViewer?.open?.(s3object)
}}
@@ -937,7 +928,7 @@
{:else if !s3object?.disable_download}
<FileDownload {workspaceId} {s3object} {appPath} />
{:else}
<div class="flex text-secondary pt-2">{s3object?.s3} (download disabled)</div>
<div class="flex text-primary pt-2">{s3object?.s3} (download disabled)</div>
{/if}
{#if s3object?.s3?.endsWith('.parquet') || s3object?.s3?.endsWith('.csv')}
{#if seeS3PreviewFileFromList == s3object?.s3}
@@ -948,7 +939,7 @@
storage={s3object?.storage}
/>{:else}
<button
class="text-secondary whitespace-nowrap flex gap-2 items-center"
class="text-primary whitespace-nowrap flex gap-2 items-center"
onclick={() => {
seeS3PreviewFileFromList = s3object?.s3
}}
@@ -968,7 +959,7 @@
</div>
{:else}
<button
class="text-secondary whitespace-nowrap flex gap-2 items-center"
class="text-primary whitespace-nowrap flex gap-2 items-center"
onclick={() => {
seeS3PreviewFileFromList = s3object?.s3
}}
@@ -1007,7 +998,7 @@
</div>
{:else}
{#if largeObject}
<div class="text-sm text-tertiary"
<div class="text-xs text-emphasis"
><a
download="{filename ?? 'result'}.json"
href={workspaceId && jobId
@@ -1042,14 +1033,17 @@
{/if}
{:else if typeof result === 'string' && result.length > 0}
<pre class="text-sm">{result}</pre>{#if !noControls}<div class="flex">
<Button on:click={() => copyToClipboard(result)} color="light" size="xs">
<div class="flex gap-2 items-center">Copy <ClipboardCopy size={12} /> </div>
</Button>
<Button
on:click={() => copyToClipboard(result)}
variant="subtle"
unifiedSize="sm"
endIcon={{ icon: ClipboardCopy }}
></Button>
</div>
{/if}
{:else}
<Highlight
class={forceJson ? 'pt-1' : 'h-full w-full'}
class={twMerge(forceJson ? 'pt-1' : 'h-full w-full', '!bg-surface-primary')}
language={json}
code={toJsonStr(result).replace(/\\n/g, '\n')}
/>
@@ -1060,14 +1054,17 @@
<pre>{result}</pre>
{#if !noControls}
<div class="flex">
<Button on:click={() => copyToClipboard(result)} color="light" size="xs">
<div class="flex gap-2 items-center">Copy <ClipboardCopy size={12} /> </div>
</Button>
<Button
on:click={() => copyToClipboard(result)}
variant="subtle"
unifiedSize="md"
endIcon={{ icon: ClipboardCopy }}
></Button>
</div>
{/if}
</div>
{:else}
<div class="text-tertiary text-sm">No result: {toJsonStr(result)}</div>
<div class="text-primary text-xs">No result: {toJsonStr(result)}</div>
{/if}
</div>
@@ -1084,16 +1081,16 @@
: `${base}/api/w/${workspaceId}/jobs_u/completed/get_result/${jobId}`
: `data:text/json;charset=utf-8,${encodeURIComponent(toJsonStr(result))}`}
startIcon={{ icon: Download }}
color="light"
size="xs"
variant="subtle"
unifiedSize="md"
>
Download
</Button>
{/if}
<Button
on:click={() => copyToClipboard(toJsonStr(result))}
color="light"
size="xs"
variant="subtle"
unifiedSize="md"
startIcon={{
icon: ClipboardCopy
}}
@@ -39,7 +39,7 @@
}
</script>
<div class={twMerge('flex flex-row gap-2.5 z-10 text-tertiary -mt-1 items-center')}>
<div class={twMerge('flex flex-row gap-2.5 z-10 text-primary -mt-1 items-center')}>
{#if customUi?.disableDownload !== true}
<a
download="{filename ?? 'result'}.json"
@@ -16,14 +16,14 @@
{#snippet text()}
Never deployed and is only a draft
{/snippet}
<Badge small color="indigo" baseClass="border border-indigo-200">Draft only</Badge>
<Badge small color="indigo">Draft only</Badge>
</Popover>
{:else}
<Popover notClickable>
{#snippet text()}
Is deployed and has a draft
{/snippet}
<Badge small color="indigo" baseClass="border border-indigo-200">+Draft</Badge>
<Badge small color="indigo">+Draft</Badge>
</Popover>
{/if}
{/if}
+14 -6
View File
@@ -7,7 +7,7 @@
</script>
<script lang="ts">
import { MoreVertical } from 'lucide-svelte'
import { EllipsisVertical } from 'lucide-svelte'
import type { Placement } from '@floating-ui/core'
import type { Item } from '$lib/utils'
import DropdownV2Inner from './DropdownV2Inner.svelte'
@@ -19,6 +19,7 @@
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import { untrack } from 'svelte'
import { fly } from 'svelte/transition'
import { ButtonType } from './common/button/model'
interface Props {
aiId?: string | undefined
@@ -35,6 +36,8 @@
customMenu?: boolean
class?: string | undefined
enableFlyTransition?: boolean
size?: ButtonType.UnifiedSize
btnText?: string
buttonReplacement?: import('svelte').Snippet
menu?: import('svelte').Snippet
}
@@ -54,6 +57,8 @@
customMenu = false,
class: classNames = undefined,
enableFlyTransition = false,
size = 'md',
btnText = '',
buttonReplacement,
menu
}: Props = $props()
@@ -142,11 +147,14 @@
{:else}
<Button
nonCaptureEvent
size="xs"
color="light"
startIcon={{ icon: MoreVertical }}
unifiedSize={size}
variant="subtle"
startIcon={{ icon: EllipsisVertical }}
btnClasses="bg-transparent"
/>
iconOnly
>
{btnText}
</Button>
{/if}
</button>
@@ -161,7 +169,7 @@
{@render menu?.()}
{:else}
<div
class="bg-surface border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
class="bg-surface-tertiary dark:border-gray-700 w-56 origin-top-right rounded-lg shadow-lg focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
style={customWidth ? `width: ${customWidth}px` : ''}
>
<DropdownV2Inner {aiId} items={computeItems} meltItem={item} />
@@ -34,13 +34,13 @@
target={item?.hrefTarget}
disabled={item?.disabled}
class={twMerge(
'px-4 py-2 text-primary font-semibold hover:bg-surface-hover cursor-pointer text-xs transition-all w-full',
'px-4 py-2 text-primary font-normal hover:bg-surface-hover cursor-pointer text-xs transition-colors w-full',
'data-[highlighted]:bg-surface-hover',
'flex flex-row gap-2 items-center',
item?.disabled && 'text-gray-400 cursor-not-allowed',
'flex flex-row gap-2 items-center rounded-sm',
item?.disabled && 'text-disabled cursor-not-allowed',
item?.type === 'delete' &&
!item?.disabled &&
'text-red-500 hover:bg-red-100 hover:text-red-500 data-[highlighted]:text-red-500 data-[highlighted]:bg-red-100'
'text-red-600 dark:text-red-400 data-[highlighted]:bg-red-500/10 dark:data-[highlighted]:bg-red-900/80 dark:data-[highlighted]:text-red-300 '
)}
item={meltItem}
aiId={`${aiId ? `${aiId}-${item.displayName}` : undefined}`}
@@ -171,7 +171,7 @@
</div>
{:else}
<div class="flex flex-col gap-1 w-full">
<div class="text-xs text-tertiary"
<div class="text-xs text-primary"
>Dynamic input ({inputType}) is not available in this mode, write value directly</div
>
{#await import('$lib/components/JsonEditor.svelte')}
@@ -476,7 +476,7 @@
<div class="flex flex-col gap-2 h-full">
{#if dynamicFunctions.length > 0}
<div class="bg-blue-50 dark:bg-blue-900/20 p-3 rounded-md">
<div class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">
<div class="text-sm font-semibold text-blue-800 dark:text-blue-200 mb-2">
Expected Functions for Dynamic Input Fields:
</div>
<ul class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
@@ -530,7 +530,7 @@
{:else}
<!-- WIP -->
{#if jsonEnabled && customUi?.jsonOnly != true}
<div class="w-full px-3 flex gap-4 justify-end items-center">
<div class="w-full px-4 py-2 flex gap-4 justify-end items-center">
{#if addPropertyInEditorTab}
<AddPropertyV2
bind:schema
@@ -539,14 +539,13 @@
}}
>
{#snippet trigger()}
<Button color="light" size="xs" iconOnly startIcon={{ icon: Plus }} />
<Button variant="subtle" size="xs" iconOnly startIcon={{ icon: Plus }} />
{/snippet}
</AddPropertyV2>
{/if}
<div class="shrink-0">
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
left: 'JSON editor',
@@ -589,15 +588,15 @@
}
}}
>
<div class="flex flex-row gap-2">
<div class="flex flex-row gap-2 text-sm">
{argName}
{#if !uiOnly}
<div onclick={stopPropagation(preventDefault(bubble('click')))}>
<Popover placement="bottom-end" containerClasses="p-4" closeButton>
{#snippet trigger()}
<Button
color="light"
size="xs2"
variant="subtle"
size="xs3"
nonCaptureEvent
startIcon={{ icon: Pen }}
iconOnly
@@ -619,8 +618,7 @@
}}
/>
<Button
variant="border"
color="light"
variant="default"
size="xs"
on:click={() => {
renameProperty(argName, argName + i)
@@ -849,7 +847,7 @@
</div>
{/each}
{:else if !shouldHideNoInputs}
<div class="text-secondary text-sm p-2">No inputs</div>
<div class="text-secondary text-xs p-2">No inputs</div>
{/if}
</div>
{:else}
@@ -909,8 +907,7 @@
{#snippet submission()}
<div>
<Button
variant="border"
color="blue"
variant="default"
size="sm"
startIcon={{ icon: Plus }}
on:click={() => variableEditor?.initNew?.()}
+3 -2
View File
@@ -256,7 +256,7 @@
export function insertAtCurrentLine(code: string): void {
if (editor) {
insertAtLine(code, editor.getPosition()?.lineNumber ?? 0);
insertAtLine(code, editor.getPosition()?.lineNumber ?? 0)
}
}
@@ -1255,7 +1255,8 @@
lineNumbersMinChars,
// overflowWidgetsDomNode: widgets,
tabSize: lang == 'python' ? 4 : 2,
folding
folding,
padding: { bottom: 7, top: 7 }
})
if (key && editorPositionMap?.[key]) {
editor.setPosition(editorPositionMap[key])
+50 -72
View File
@@ -1,5 +1,5 @@
<script module lang="ts">
export const EDITOR_BAR_WIDTH_THRESHOLD = 1044
export const EDITOR_BAR_WIDTH_THRESHOLD = 1300
</script>
<script lang="ts">
@@ -202,9 +202,9 @@
let showResourceTypePicker = $derived(
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
lang === 'python3' ||
lang === 'php' ||
lang === 'rust'
lang === 'python3' ||
lang === 'php' ||
lang === 'rust'
)
let codeViewer: Drawer | undefined = $state()
@@ -329,9 +329,9 @@
name,
JSON.stringify(resourceType.schema),
{
"leading-comments": false,
"density": "dense",
"derive-debug": true
'leading-comments': false,
density: 'dense',
'derive-debug': true
}
)
editor.insertAtCurrentLine(lines.join('\n'))
@@ -587,9 +587,7 @@ string ${windmillPathToCamelCaseName(path)} = await client.GetStringAsync(uri);
{#snippet submission()}
<div class="flex flex-row">
<Button
variant="border"
color="blue"
size="sm"
variant="accent"
startIcon={{ icon: Plus }}
on:click={() => {
variableEditor?.initNew()
@@ -695,9 +693,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
<Button
startIcon={{ icon: Plus }}
target="_blank"
variant="border"
color="blue"
size="sm"
variant="accent"
href="{base}/resources?connect_app=undefined"
>
Add resource
@@ -787,22 +783,20 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
/>
<div class="flex justify-between items-center overflow-y-auto w-full p-0.5">
<div class="flex items-center">
<div class="flex gap-3 items-center">
<div
title={validCode ? 'Main function parsable' : 'Main function not parsable'}
class="rounded-full w-2 h-2 mx-2 {validCode ? 'bg-green-300' : 'bg-red-300'}"
></div>
<div class="flex items-center gap-0.5">
<div class="flex items-center gap-2">
{#if showContextVarPicker && customUi?.contextVar != false}
<Button
aiId="editor-bar-add-context-variable"
aiDescription="Add context variable"
title="Add context variable"
color="light"
variant="subtle"
on:click={contextualVariablePicker.openDrawer}
size="xs"
btnClasses="!font-medium text-tertiary"
spacingSize="md"
unifiedSize="sm"
startIcon={{ icon: DollarSign }}
{iconOnly}
>+Context var
@@ -813,11 +807,9 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-add-variable"
aiDescription="Add variable"
title="Add variable"
color="light"
btnClasses="!font-medium text-tertiary"
variant="subtle"
on:click={variablePicker.openDrawer}
size="xs"
spacingSize="md"
unifiedSize="sm"
startIcon={{ icon: DollarSign }}
{iconOnly}
>
@@ -830,11 +822,9 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-add-s3-object"
aiDescription="Add S3 Object"
title="Add S3 object"
color="light"
variant="subtle"
on:click={() => s3FilePicker?.open()}
size="xs"
btnClasses="!font-medium text-tertiary"
spacingSize="md"
unifiedSize="sm"
startIcon={{ icon: File }}
{iconOnly}
>+S3 Object
@@ -846,10 +836,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-add-resource"
aiDescription="Add resource"
title="Add resource"
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
unifiedSize="sm"
variant="subtle"
on:click={resourcePicker.openDrawer}
{iconOnly}
startIcon={{ icon: Package }}
@@ -867,10 +855,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-add-git-repo"
aiDescription="Delegate to Git repository"
title="Delegate to Git repository"
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
unifiedSize="sm"
variant="subtle"
on:click={() => (gitRepoPickerOpen = true)}
{iconOnly}
startIcon={{ icon: GitBranch }}
@@ -885,10 +871,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-add-resource-type"
aiDescription="Add resource type"
title="Add resource type"
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
variant="subtle"
unifiedSize="sm"
on:click={() => resourceTypePicker?.openDrawer()}
{iconOnly}
startIcon={{ icon: Package }}
@@ -902,11 +886,9 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-add-database"
aiDescription="Add database"
title="Add database"
color="light"
variant="subtle"
on:click={() => databasePicker?.openDrawer()}
size="xs"
btnClasses="!font-medium text-tertiary"
spacingSize="md"
unifiedSize="sm"
startIcon={{ icon: DatabaseIcon }}
{iconOnly}
>+Database
@@ -918,11 +900,9 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-use-ducklake"
aiDescription="Use Ducklake"
title="Use Ducklake"
color="light"
variant="subtle"
on:click={() => ducklakePicker?.openDrawer()}
size="xs"
btnClasses="!font-medium text-tertiary"
spacingSize="md"
unifiedSize="sm"
startIcon={{ icon: DucklakeIcon }}
{iconOnly}
>+Ducklake
@@ -934,10 +914,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
aiId="editor-bar-reset-content"
aiDescription="Reset content"
title="Reset Content"
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
unifiedSize="sm"
variant="subtle"
on:click={clearContent}
{iconOnly}
startIcon={{ icon: RotateCw }}
@@ -951,10 +929,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
<Button
aiId="editor-bar-reload-assistants"
aiDescription="Reload assistants"
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
unifiedSize="sm"
variant="subtle"
on:click={() => editor?.reloadWebsocket()}
startIcon={{
icon: RotateCw,
@@ -986,7 +962,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
<div class="flex items-center px-3">
<Toggle
options={{ right: '' }}
size="xs"
size="sm"
checked={diffMode}
disabled={!lastDeployedCode}
on:change={(e) => {
@@ -998,7 +974,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
{#snippet text()}
Toggle diff mode
{/snippet}
<DiffIcon class="ml-1 text-tertiary" size={14} />
<DiffIcon class="ml-1 text-primary" size={14} />
</Popover>
</div>
{/if}
@@ -1015,7 +991,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
{#snippet text()}
Multiplayer
{/snippet}
<Users class="ml-1 text-tertiary" size={14} />
<Users class="ml-1 text-primary" size={14} />
</Popover>
{#if collabLive}
<button
@@ -1029,7 +1005,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
class="inline-flex h-6 w-6 items-center justify-center rounded-full ring-2 ring-white bg-gray-600"
title={user.name}
>
<span class="text-sm font-medium leading-none text-white"
<span class="text-sm font-semibold leading-none text-white"
>{user.name.substring(0, 2).toLocaleUpperCase()}</span
>
</span>
@@ -1041,13 +1017,19 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
{#if customUi?.aiGen != false}
{#if openAiChat}
<FlowInlineScriptAiButton {moduleId} />
<FlowInlineScriptAiButton {moduleId} btnProps={{ variant: 'subtle' }} />
{:else}
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
<ScriptGen
{editor}
{diffEditor}
{lang}
btnProps={{ variant: 'subtle', iconOnly: true }}
{args}
/>
{/if}
{/if}
<EditorSettings {customUi} />
<EditorSettings {customUi} btnProps={{ variant: 'subtle', unifiedSize: 'md' }} />
</div>
</div>
@@ -1055,10 +1037,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
{@render right?.()}
{#if scriptPath && !noHistory}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
unifiedSize="sm"
variant="subtle"
on:click={() => (showHistoryDrawer = true)}
{iconOnly}
startIcon={{ icon: History }}
@@ -1069,10 +1049,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
{/if}
{#if SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS && customUi?.library != false}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
unifiedSize="sm"
variant="subtle"
on:click={scriptPicker.openDrawer}
{iconOnly}
startIcon={{ icon: Library }}
@@ -1083,8 +1061,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
{/if}
{#if saveToWorkspace}
<Button
size="xs"
color="light"
unifiedSize="sm"
variant="subtle"
startIcon={{ icon: Save }}
on:click={() => dispatch('createScriptFromInlineScript')}
iconOnly={false}
@@ -7,12 +7,14 @@
import CodeCompletionStatus from './copilot/CodeCompletionStatus.svelte'
import type { EditorBarUi } from './custom_ui'
import Popover from './meltComponents/Popover.svelte'
import type { ComponentProps } from 'svelte'
interface Props {
customUi?: EditorBarUi
btnProps?: ComponentProps<typeof Button>
}
let { customUi = {} }: Props = $props()
let { customUi = {}, btnProps }: Props = $props()
</script>
{#if customUi?.autoformatting != false || customUi?.vimMode != false || customUi?.aiCompletion != false}
@@ -24,13 +26,11 @@
{#snippet trigger()}
{#if customUi.editorSettings != false}
<Button
btnClasses="text-tertiary"
color="light"
size="xs"
nonCaptureEvent={true}
startIcon={{ icon: Settings }}
iconOnly
title="Editor settings"
{...btnProps}
/>
{/if}
{/snippet}
@@ -58,7 +58,7 @@
handlerExtraArgs = $bindable(),
customScriptTemplate,
customHandlerKind = $bindable('script'),
customTabTooltip,
customTabTooltip
}: Props = $props()
let customHandlerSchema: Schema | undefined = $state()
@@ -324,18 +324,15 @@
<div>
<Tabs bind:selected={handlerSelected} class="mt-2 mb-4">
<Tab value="slack" disabled={!isEditable}>Slack</Tab>
<Tab value="teams" disabled={!isEditable}>Teams</Tab>
<Tab value="email" disabled={!isEditable}>Email</Tab>
<Tab value="custom" disabled={!isEditable}>
Custom
{@render customTabTooltip?.()}
</Tab>
<Tab value="slack" disabled={!isEditable} label="Slack" />
<Tab value="teams" disabled={!isEditable} label="Teams" />
<Tab value="email" disabled={!isEditable} label="Email" />
<Tab value="custom" disabled={!isEditable} label="Custom" extra={customTabTooltip} />
</Tabs>
</div>
{#if handlerSelected === 'custom'}
<div class="flex flex-row mb-2">
<div class="flex flex-row mb-6">
<ScriptPicker
disabled={!isEditable || !$enterpriseLicense}
kinds={['script', 'failure']}
@@ -348,8 +345,8 @@
{#if !handlerPath}
<Button
btnClasses="ml-4 mt-2"
color="dark"
btnClasses="ml-4"
variant="default"
size="xs"
href={customScriptTemplate}
disabled={!isEditable}
@@ -360,7 +357,7 @@
{/if}
</div>
{#if showScriptHelpText}
<div class="text-xs">
<div class="text-2xs text-secondary">
Example of error handler scripts can be found on <a
target="_blank"
href="{$hubBaseUrlStore}/failures"
@@ -370,7 +367,7 @@
</div>
{/if}
{#if handlerPath}
<p class="font-semibold text-sm mt-4 mb-2">Extra arguments</p>
<p class="font-semibold text-xs mt-6 mb-1">Extra arguments</p>
{#await import('$lib/components/SchemaForm.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
@@ -434,21 +431,16 @@
href="{base}/workspace_settings?tab=slack">configure it here</a
>.
</p>
<Button
variant="border"
color="light"
on:click={loadSlackResources}
startIcon={{ icon: RotateCw }}
/>
<Button variant="default" on:click={loadSlackResources} startIcon={{ icon: RotateCw }} />
</div>
</Alert>
{:else}
<Button
disabled={emptyString(handlerExtraArgs['channel'])}
btnClasses="w-32 text-center"
color="dark"
variant="default"
on:click={() => sendSlackMessage(handlerExtraArgs['channel'])}
size="xs">Send test message</Button
unifiedSize="md">Send test message</Button
>
{#if connectionTestJob !== undefined}
<p class="text-normal text-2xs mt-1 gap-2">
@@ -493,7 +485,7 @@
<div class="w-2/3 flex flex-col gap-2">
<div class="flex flex-row items-center gap-2">
<div class="pt-1 flex-shrink-0">
<MsTeamsIcon height="24px" width="24px" />
<MsTeamsIcon size={24} />
</div>
<p class="text-sm">Teams Channel</p>
</div>
@@ -513,8 +505,8 @@
}
: undefined,
(channel) => {
handlerExtraArgs['channel'] = channel?.channel_id;
handlerExtraArgs['channel_name'] = channel?.channel_name;
handlerExtraArgs['channel'] = channel?.channel_id
handlerExtraArgs['channel_name'] = channel?.channel_name
}
}
onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)}
@@ -547,19 +539,14 @@
>workspace settings</a
>.
</p>
<Button
variant="border"
color="light"
on:click={loadTeamsResources}
startIcon={{ icon: RotateCw }}
/>
<Button variant="default" on:click={loadTeamsResources} startIcon={{ icon: RotateCw }} />
</div>
</Alert>
{:else}
<Button
disabled={emptyString(handlerExtraArgs['channel'])}
btnClasses="w-32 text-center mt-2"
color="dark"
variant="default"
on:click={() => sendTeamsMessage(handlerExtraArgs['channel'] ?? '')}
size="xs">Send test message</Button
>
@@ -615,7 +602,7 @@
class="w-full"
/>
{#if handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length > 0}
<span class="text-sm text-tertiary">
<span class="text-sm text-primary">
{handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length} email{handlerExtraArgs[
EMAIL_RECIPIENTS_KEY
]?.length === 1
@@ -30,7 +30,7 @@
onClick,
class: className = '',
noText = false,
buttonVariant = 'border',
buttonVariant = 'default',
btnClasses = '',
disabled = false
}: {
@@ -50,9 +50,8 @@
<Button
disabled={$userStore?.operator || disabled}
size="xs"
unifiedSize={'md'}
variant={buttonVariant}
spacingSize="xs2"
wrapperClasses={className}
{btnClasses}
on:click={async () => {
@@ -69,12 +68,19 @@
}
onClick?.()
}}
endIcon={asset.kind === 's3object'
? { icon: File }
: asset.kind === 'resource'
? { icon: Database }
: asset.kind === 'ducklake'
? { icon: DucklakeIcon }
: undefined}
>
{#if asset.kind === 's3object'}
<span class:hidden={noText}>Explore</span> <File size={18} />
<span class:hidden={noText}>Explore</span>
{:else if asset.kind === 'resource'}
<span class:hidden={noText}>Manage</span> <Database size={18} />
<span class:hidden={noText}>Manage</span>
{:else if asset.kind === 'ducklake'}
<span class:hidden={noText}>Manage</span> <DucklakeIcon size={18} />
<span class:hidden={noText}>Manage</span>
{/if}
</Button>
@@ -98,7 +98,7 @@
style="position: absolute; overflow: hidden; width: 1.67772e+07px; height: 1.67772e+07px; transform: translate3d(0px, 0px, 0px); contain: strict; top: 0px; left: 0px;"
>
<div
class="view-lines monaco-mouse-cursor-text text-tertiary/60"
class="view-lines monaco-mouse-cursor-text text-primary/60"
style="line-height: {lineHeight}px; position: absolute; font-family: {fontFamily}; font-weight: normal; font-size: {fontSize}px; font-feature-settings: 'liga' 0, 'calt' 0; font-variation-settings: normal; line-height: {lineHeight}px; letter-spacing: 0px; width: 1143px; height: 789px;"
>
{#each lines as line, i}
@@ -23,9 +23,9 @@
<div class="inline-flex flex-row items-baseline truncated">
<span
class={twMerge(
disabled ? 'text-tertiary' : '',
'font-semibold',
lightHeader ? 'text-secondary text-sm font-normal' : '',
disabled ? 'text-primary' : '',
'font-semibold text-xs',
lightHeader ? 'text-secondary font-normal' : 'text-emphasis',
labelClass
)}
>
@@ -40,7 +40,7 @@
{/if}
{#if displayType}
<span class="text-xs italic ml-2 text-hint">
<span class="text-2xs italic ml-2 text-hint">
{#if format && !format.startsWith('resource') && !format.startsWith('jsonschema-')}
{format}
{:else}
+22 -28
View File
@@ -88,6 +88,7 @@
import type { FlowBuilderProps } from './flow_builder'
import { ModulesTestStates } from './modulesTest.svelte'
import FlowAssetsHandler, { initFlowGraphAssetsCtx } from './flows/FlowAssetsHandler.svelte'
import { inputSizeClasses } from './text_input/TextInput.svelte'
let {
initialPath = $bindable(''),
@@ -1017,8 +1018,7 @@
<Button
btnClasses="hidden lg:inline-flex"
startIcon={{ icon: Calendar }}
variant="contained"
color="light"
variant="subtle"
size="xs"
on:click={async () => {
select('triggers')
@@ -1036,26 +1036,24 @@
{#if customUi?.topBar?.path != false}
<div class="flex justify-start w-full">
<div>
<button
onclick={async () => {
select('settings-metadata')
document.getElementById('path')?.focus()
}}
<button
onclick={async () => {
select('settings-metadata')
document.getElementById('path')?.focus()
}}
>
<Badge
color="gray"
class="text-primary rounded-r-none border border-r-0 {inputSizeClasses.md}"
>
<Badge
color="gray"
class="center-center !bg-gray-300 !text-tertiary dark:!bg-gray-700 dark:!text-gray-300 !h-[28px] !w-[70px] rounded-r-none"
>
<Pen size={12} class="mr-2" /> Path
</Badge>
</button>
</div>
<Pen size={12} class="mr-2" /> Path
</Badge>
</button>
<input
type="text"
readonly
value={$pathStore && $pathStore != '' ? $pathStore : 'Choose a path'}
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none"
class="font-mono !text-2xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 cursor-default !rounded-l-none {inputSizeClasses.md}"
onfocus={({ currentTarget }) => {
currentTarget.select()
}}
@@ -1081,9 +1079,8 @@
{/if}
{#if customUi?.topBar?.diff != false}
<Button
color="light"
variant="border"
size="xs"
variant="default"
unifiedSize="md"
on:click={async () => {
if (!savedFlow) {
return
@@ -1110,11 +1107,9 @@
})
}}
disabled={!savedFlow}
startIcon={{ icon: DiffIcon }}
>
<div class="flex flex-row gap-2 items-center">
<DiffIcon size={14} />
Diff
</div>
Diff
</Button>
{/if}
<FlowPreviewButtons
@@ -1138,13 +1133,12 @@
/>
<Button
loading={loadingDraft}
size="xs"
unifiedSize="md"
variant="accent"
startIcon={{ icon: Save }}
on:click={() => saveDraft()}
disabled={(!newFlow && !savedFlow) || loading}
shortCut={{
key: 'S'
}}
shortCut={{ key: 'S' }}
>
Draft
</Button>
@@ -45,9 +45,8 @@
{#snippet buttonReplacement()}
<Button
nonCaptureEvent
size="xs"
color="light"
variant="border"
unifiedSize="md"
variant="default"
id="tutorials-button"
startIcon={{ icon: BookOpen }}
/>
@@ -266,7 +266,7 @@
<div class="flex-col flex gap-2">
<div class="flex flex-row gap-4 text-sm p-2">
<Badge large={true} color="blue">Default branch</Badge>
<p class="italic text-tertiary"
<p class="italic text-primary"
>If none of the predicates' expressions evaluated in-order match, this branch is chosen</p
>
</div>
@@ -4,6 +4,7 @@
import LogViewer from './LogViewer.svelte'
import type { CompletedJob, Job } from '$lib/gen'
import AiAgentLogViewer from './AIAgentLogViewer.svelte'
import { twMerge } from 'tailwind-merge'
import type { AgentTool } from './flows/agentToolUtils'
interface Props {
@@ -49,18 +50,19 @@
<div
class:border={!noBorder}
class="{!col
? 'grid grid-cols-2'
: 'flex flex-col max-h-screen gap-4'} shadow border border-tertiary-inverse grow overflow-hidden"
class={twMerge(
'rounded-md grow bg-surface-tertiary overflow-hidden text-xs',
!col ? 'grid grid-cols-2' : 'flex flex-col max-h-screen gap-2 overflow-hidden'
)}
>
<div class="bg-surface {col ? 'max-h-1/2 grow' : 'max-h-80'} p-1 overflow-auto relative">
<span class="text-tertiary">Result</span>
<div class="{col ? 'max-h-1/2 grow' : 'max-h-80'} p-2 overflow-auto relative">
<span class="text-primary text-xs font-normal">Result</span>
{#if result !== undefined || result_stream !== undefined}
<DisplayResult {workspaceId} {jobId} {filename} {result} {result_stream} growVertical />
{:else if loading}
<Loader2 class="animate-spin" />
{:else}
<div class="text-gray-400">No result (result is undefined)</div>
<div class="text-secondary">No result (result is undefined)</div>
{/if}
</div>
<div class="overflow-auto {col ? 'grow' : 'max-h-80'} relative">
@@ -44,7 +44,7 @@
>
<button
class={twMerge(
'py-1 leading-tight w-full flex items-center justify-left text-xs text-tertiary hover:text-primary hover:bg-surface-hover ',
'py-1 leading-tight w-full flex items-center justify-left text-xs text-primary hover:text-primary hover:bg-surface-hover ',
isCurrent(id) ? 'bg-surface-hover text-primary' : '',
className
)}
@@ -486,10 +486,10 @@
{#snippet text()}
Keyboard navigation available - ↑/↓/Enter
{/snippet}
<Keyboard size={16} class="text-tertiary" />
<Keyboard size={16} class="text-primary" />
</Tooltip>
<div class="flex items-center gap-2 whitespace-nowrap">
<label for="showTimeline" class="text-xs text-tertiary hover:text-primary transition-colors"
<label for="showTimeline" class="text-xs text-primary hover:text-primary transition-colors"
>Show timeline</label
>
<div class="flex-shrink-0">
@@ -505,7 +505,7 @@
<div class="flex items-center gap-2 whitespace-nowrap">
<label
for="showResultsInputs"
class="text-xs text-tertiary hover:text-primary transition-colors"
class="text-xs text-primary hover:text-primary transition-colors"
>Show inputs/results</label
>
<div class="flex-shrink-0">
@@ -520,7 +520,7 @@
</div>
<button
onclick={toggleExpandAll}
class="text-xs text-tertiary hover:text-primary transition-colors flex items-center gap-2 min-w-24 justify-end"
class="text-xs text-primary hover:text-primary transition-colors flex items-center gap-2 min-w-24 justify-end"
>
{allExpanded ? 'Collapse All' : 'Expand All'}
{#if allExpanded}
@@ -553,7 +553,7 @@
{#if flowInfo?.label}
: {flowInfo.label}
{/if}
<span class="text-tertiary">{getStepProgress(rootJob, modules.length)}</span>
<span class="text-primary">{getStepProgress(rootJob, modules.length)}</span>
</div>
<div
@@ -723,7 +723,7 @@
: {module.summary}
{/if}
{#if hasEmptySubflowValue}
<span class="text-tertiary">
<span class="text-primary">
{#if module.value.type === 'forloopflow' || module.value.type === 'whileloopflow'}
(empty loop)
{:else if module.value.type === 'branchall' || module.value.type === 'branchone'}
@@ -917,7 +917,7 @@
: ''
)}
>
<div class="text-xs text-tertiary font-mono"> No logs available </div>
<div class="text-xs text-primary font-mono"> No logs available </div>
</div>
{/if}
@@ -115,15 +115,16 @@
on:click={() => dispatch('close')}
startIcon={{ icon: X }}
iconOnly
size="sm"
color="light"
unifiedSize="md"
variant="default"
btnClasses="hover:bg-surface-hover bg-surface-secondaryw-8 h-8 rounded-full p-0"
/>
</div>
{#if isRunning}
<Button
color="red"
variant="accent"
destructive
on:click={async () => {
isRunning = false
try {
@@ -135,7 +136,7 @@
}))
} catch {}
}}
size="sm"
unifiedSize="md"
btnClasses="w-full max-w-lg"
loading={true}
clickableWhileLoading
@@ -144,10 +145,9 @@
</Button>
{:else}
<Button
variant="contained"
variant="accent"
startIcon={{ icon: isRunning ? RefreshCw : Play }}
color="dark"
size="sm"
unifiedSize="md"
btnClasses="w-full max-w-lg"
on:click={() => runPreview(previewArgs, undefined)}
id="flow-editor-test-flow-drawer"
@@ -184,7 +184,7 @@
bind:selectedJobStep
/>
{:else}
<div class="italic text-tertiary h-full grow"> Flow status will be displayed here </div>
<div class="italic text-primary h-full grow"> Flow status will be displayed here </div>
{/if}
</div>
</div>
+19 -19
View File
@@ -20,13 +20,13 @@
</script>
<div
class="rounded-md p-3 bg-surface-secondary shadow-sm sm:text-sm md:text-base overflow-x-auto"
class="rounded-md p-4 bg-surface-secondary overflow-x-auto text-xs text-primary font-normal flex flex-col gap-1"
style="min-height: 150px;"
>
<JobStatus {job} />
<div class="flex flex-row gap-2 items-center">
<Clock size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<span class="text-2xs text-secondary whitespace-nowrap">
<Clock size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
{#if job['success'] != undefined}
Received job: {displayDate(job.created_at ?? '')}
{:else}
@@ -36,8 +36,8 @@
</span>
</div>
{#if job && 'started_at' in job && job.started_at}
<div class="flex flex-row gap-2 items-center text-sm">
<Clock size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<div class="flex flex-row gap-2 items-center">
<Clock size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
Started <TimeAgo agoOnlyIfRecent date={job.started_at ?? ''} />
<Tooltip small>{job?.started_at}</Tooltip>
@@ -45,17 +45,17 @@
</div>
{/if}
{#if job && job['mem_peak']}
<div class="flex flex-row gap-2 items-center text-sm">
<div class="flex flex-row gap-2 items-center">
<MemoryStick size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<span> Mem peak: {(job['mem_peak'] / 1024).toPrecision(5)}MB</span>
</div>
{/if}
<div>
<div class="text-xs text-primary font-normal flex flex-col gap-1">
{#if job && job.parent_job}
{#if job.is_flow_step}
<div class="flex flex-row gap-2 items-center text-sm">
<BarsStaggered size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<span class="whitespace-nowrap text-sm">
<div class="flex flex-row gap-2 items-center">
<BarsStaggered size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
Step of flow
<a href={`${base}/run/${job.parent_job}?workspace=${$workspaceStore}`}>
{truncateRev(job.parent_job, 18)}
@@ -63,8 +63,8 @@
</span>
</div>
{:else}
<div class="flex flex-row gap-2 items-center text-sm">
<Bot size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<div class="flex flex-row gap-2 items-center">
<Bot size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
Triggered by parent
<a href={`${base}/run/${job.parent_job}?workspace=${$workspaceStore}`}>
@@ -74,8 +74,8 @@
</div>
{/if}
{:else if job && job.schedule_path}
<div class="flex flex-row gap-2 w-full items-center text-sm">
<Calendar size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<div class="flex flex-row gap-2 w-full items-center">
<Calendar size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
Schedule:
<!-- svelte-ignore a11y_invalid_attribute -->
@@ -97,18 +97,18 @@
{@const viewHref = `${base}/${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
<div class="flex flex-row gap-2 items-center">
{#if isScript}
<Code2 size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<Code2 size={SMALL_ICON_SIZE} class="min-w-3.5" />
{:else}
<BarsStaggered size={SMALL_ICON_SIZE} class="text-secondary" />
<BarsStaggered size={SMALL_ICON_SIZE} />
{/if}
<span class="truncate text-sm">
<span class="truncate">
<a href={viewHref}>{isScript ? job?.script_hash : job?.script_path}</a>
</span>
</div>
{/if}
<div class="flex items-center gap-2 text-sm">
<User size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
<div class="flex items-center gap-2">
<User size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span>
By {truncateRev(
@@ -11,12 +11,10 @@
<AnimatedButton animate={connecting} baseRadius="6px" animationDuration="2s" marginWidth="2px">
<Button
variant="border"
color="light"
size="xs2"
variant="default"
btnClasses={twMerge(
connecting ? 'text-blue-500' : 'text-tertiary',
'group/plug-btn overflow-clip flex p-0'
connecting ? 'text-accent' : '',
'bg-surface group/plug-btn overflow-clip flex p-0'
)}
on:click
{...id ? { id } : {}}
@@ -299,8 +299,8 @@
on:click={() => dispatch('close')}
startIcon={{ icon: X }}
iconOnly
size="sm"
color="light"
unifiedSize="md"
variant="default"
btnClasses="hover:bg-surface-hover bg-surface-secondaryw-8 h-8 rounded-full p-0"
/>
</div>
@@ -308,11 +308,12 @@
{#if isRunning}
<div class="mx-auto">
<Button
color="red"
variant="accent"
destructive
on:click={async () => {
cancelTest()
}}
size="sm"
unifiedSize="md"
btnClasses="w-full max-w-lg"
loading={true}
clickableWhileLoading
@@ -321,13 +322,12 @@
</Button>
</div>
{:else}
<div class="grow justify-center flex flex-row gap-4">
<div class="grow justify-center flex flex-row gap-2">
{#if jobId !== undefined && selectedJobStep !== undefined && selectedJobStepIsTopLevel && aiChatManager.flowAiChatHelpers?.getModuleAction(selectedJobStep) !== 'removed'}
{#if selectedJobStepType == 'single'}
<Button
size="xs"
color="light"
variant="border"
unifiedSize="md"
variant="default"
title={`Re-start this flow from step ${selectedJobStep} (included).`}
on:click={() => {
runPreview(previewArgs.val, {
@@ -351,8 +351,7 @@
{#snippet button()}
<Button
title={`Re-start this flow from step ${selectedJobStep} (included).`}
variant="border"
color="blue"
variant="default"
startIcon={{ icon: RefreshCw }}
on:click={() => {
runPreview(previewArgs.val, {
@@ -420,9 +419,8 @@
{/if}
{#if !flowStore.val.value?.chat_input_enabled}
<Button
variant="contained"
variant="accent"
startIcon={{ icon: isRunning ? RefreshCw : Play }}
color="dark"
size="sm"
btnClasses="w-full max-w-lg"
on:click={() => runPreview(previewArgs.val, undefined)}
@@ -499,7 +497,6 @@
<div class="flex flex-row gap-2">
<Toggle
bind:checked={jsonView}
label="JSON View"
size="xs"
options={{
right: 'JSON',
@@ -618,11 +615,11 @@
{customUi}
/>
{:else if loadingHistory}
<div class="italic text-tertiary h-full grow mx-auto flex flex-row items-center gap-2">
<div class="italic text-primary h-full grow mx-auto flex flex-row items-center gap-2">
<Loader2 class="animate-spin" /> <span> Loading history... </span>
</div>
{:else}
<div class="italic text-tertiary h-full grow"> Flow status will be displayed here </div>
<div class="italic text-primary h-full grow"> Flow status will be displayed here </div>
{/if}
</div>
</div>
@@ -1314,15 +1314,17 @@
{#if render}
{#if innerModules && innerModules.length > 0 && !isListJob}
<Tabs class="mx-auto {wideResults ? '' : 'max-w-7xl'}" bind:selected>
<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
<Tab value="graph" label="Graph" />
<Tab
value="logs"
class={animateLogsTab
? 'animate-pulse animate-duration-1000 bg-surface-inverse text-primary-inverse'
: ''}><span class="font-semibold">Logs</span></Tab
>
<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
<Tab value="assets"><span class="font-semibold">Assets</span></Tab>
: ''}
label="Logs"
/>
<Tab value="sequence" label="Details" />
<Tab value="assets" label="Assets" />
</Tabs>
{:else}
<div class="h-[30px]"></div>
@@ -1334,13 +1336,13 @@
style="min-height: {minTabHeight}px"
>
{#if isListJob}
<h3 class="text-md leading-6 font-bold text-tertiary border-b mb-4">
<h3 class="text-md leading-6 font-bold text-primary border-b mb-4">
Subflows ({flowJobIds?.flowJobs.length})
</h3>
<div class="overflow-auto max-h-1/2">
{#each flowJobIds?.flowJobs ?? [] as loopJobId, j (loopJobId)}
{#if render && j + subflowsSize + 1 == (flowJobIds?.flowJobs.length ?? 0)}
<Button variant="border" color="light" on:click={() => (subflowsSize += 500)}
<Button variant="default" on:click={() => (subflowsSize += 500)}
>Load 500 more...</Button
>
{/if}
@@ -1447,7 +1449,7 @@
{#each modules as mod, i}
{#if render}
<div class="line w-8 h-10"></div>
<h3 class="text-tertiary mb-2 w-full">
<h3 class="text-primary mb-2 w-full">
{#if mod.id === 'preprocessor'}
Preprocessor module
{:else if job?.raw_flow?.modules && i < job?.raw_flow?.modules.length + hasPreprocessor}
@@ -1674,7 +1676,7 @@
{/each}
</ul>
{:else}
<div class="p-2 text-tertiary text-sm italic">Empty flow</div>
<div class="p-2 text-primary text-sm italic">Empty flow</div>
{/if}
</div>
{#if selected == 'logs' && render}
@@ -1782,15 +1784,14 @@
>
<Tabs bind:selected={rightColumnSelect}>
{#if !hideTimeline}
<Tab value="timeline"><span class="font-semibold text-md">Timeline</span></Tab>
<Tab value="timeline" label="Timeline" />
{/if}
<Tab value="node_status"><span class="font-semibold">Node status</span></Tab>
<Tab value="node_status" label="Node status" />
{#if !hideNodeDefinition}
<Tab value="node_definition"><span class="font-semibold">Node definition</span></Tab
>
<Tab value="node_definition" label="Node definition" />
{/if}
{#if Object.keys(job?.flow_status?.user_states ?? {}).length > 0}
<Tab value="user_states"><span class="font-semibold">User States</span></Tab>
<Tab value="user_states" label="User States" />
{/if}
</Tabs>
{#if rightColumnSelect == 'timeline'}
@@ -1809,7 +1810,7 @@
durationStatuses={localDurationStatuses}
/>
{:else if rightColumnSelect == 'node_status'}
<div class="pt-2 grow flex flex-col">
<div class="p-2 grow flex flex-col gap-6">
{#if selectedNode?.startsWith(AI_TOOL_MESSAGE_PREFIX)}
<div class="pt-2 px-4 pb-4">
<Alert type="info" title="Message output is available on the AI agent node" />
@@ -1870,100 +1871,110 @@
module && module.value.type === 'aiagent' ? module.value.tools : undefined}
{@const parentLoopsPrefix = getParentLoopsPrefix(module?.id ?? '')}
{#if node.flow_jobs_results}
<span class="pl-1 text-tertiary"
>Result of step as collection of all subflows</span
>
<div class="overflow-auto max-h-[200px] p-2">
<DisplayResult
workspaceId={job?.workspace_id}
result={node.flow_jobs_results}
nodeId={selectedNode}
jobId={job?.id}
language={job?.language}
/>
</div>
<span class="pl-1 text-tertiary text-lg pt-4">Selected subflow</span>
{/if}
<div class="px-2 flex gap-2 min-w-0 w-full">
<ModuleStatus
type={node.type}
scheduled_for={node.scheduled_for}
skipped={node.skipped}
/>
{#if node.duration_ms}
<Badge>
<Hourglass class="mr-2" size={10} />
{msToSec(node.duration_ms)} s
</Badge>
{/if}
{#if node.job_id}
<div class="grow w-full flex flex-row-reverse">
<a
class="text-right text-xs"
rel="noreferrer"
target="_blank"
href="{base}/run/{node.job_id ?? ''}?workspace={job?.workspace_id}"
>
{truncateRev(node.job_id ?? '', 10)}
</a>
<div>
<span class="pl-1 text-emphasis text-xs font-medium"
>Result of step as collection of all subflows</span
>
<div class="overflow-auto max-h-[200px] p-2">
<DisplayResult
workspaceId={job?.workspace_id}
result={node.flow_jobs_results}
nodeId={selectedNode}
jobId={job?.id}
language={job?.language}
/>
</div>
{/if}
</div>
{#if !node.isListJob}
<div class="px-1 py-1">
<JobArgs
id={node.job_id}
workspace={job.workspace_id ?? $workspaceStore ?? 'no_w'}
args={node.args}
/>
</div>
{/if}
<FlowJobResult
tagLabel={customUi?.tagLabel}
workspaceId={job?.workspace_id}
jobId={node.job_id}
noBorder
loading={node.type != 'Success' && node.type != 'Failure'}
waitingForExecutor={node.type == 'WaitingForExecutor'}
refreshLog={node.type == 'InProgress'}
col
result_stream={resultStreams[node.job_id ?? '']}
result={node.result}
tag={node.tag}
logs={node.logs}
downloadLogs={!hideDownloadLogs}
aiAgentStatus={agentTools &&
node?.job_id &&
(node.type === 'Success' || node.type === 'Failure')
? {
tools: agentTools,
agentJob: {
id: node.job_id,
result: node.result,
logs: node.logs,
args: node.args,
success: node.type === 'Success',
type: 'CompletedJob'
},
storedToolCallJobs: module
? toolCallStore?.getLocalToolCallJobs(parentLoopsPrefix)
: undefined,
onToolJobLoaded: (job, idx) => {
if (module) {
const storeKey = parentLoopsPrefix + module.id + '-' + idx
toolCallStore?.setStoredToolCallJob(storeKey, job)
<div class="flex flex-col gap-2">
{#if node.flow_jobs_results}
<span class="pl-1 text-xs font-medium text-emphasis pt-4"
>Selected subflow</span
>
{/if}
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-1">
<div class="flex gap-2 min-w-0 w-full items-center">
<ModuleStatus
type={node.type}
scheduled_for={node.scheduled_for}
skipped={node.skipped}
/>
{#if node.duration_ms}
<Badge>
<Hourglass class="mr-2" size={10} />
{msToSec(node.duration_ms)} s
</Badge>
{/if}
{#if node.job_id}
<div class="grow w-full flex flex-row-reverse">
<a
class="text-right text-xs"
rel="noreferrer"
target="_blank"
href="{base}/run/{node.job_id ??
''}?workspace={job?.workspace_id}"
>
{truncateRev(node.job_id ?? '', 10)}
</a>
</div>
{/if}
</div>
{#if !node.isListJob}
<JobArgs
id={node.job_id}
workspace={job.workspace_id ?? $workspaceStore ?? 'no_w'}
args={node.args}
/>
{/if}
</div>
<FlowJobResult
tagLabel={customUi?.tagLabel}
workspaceId={job?.workspace_id}
jobId={node.job_id}
loading={node.type != 'Success' && node.type != 'Failure'}
waitingForExecutor={node.type == 'WaitingForExecutor'}
refreshLog={node.type == 'InProgress'}
col
result_stream={resultStreams[node.job_id ?? '']}
result={node.result}
tag={node.tag}
logs={node.logs}
downloadLogs={!hideDownloadLogs}
aiAgentStatus={agentTools &&
node?.job_id &&
(node.type === 'Success' || node.type === 'Failure')
? {
tools: agentTools,
agentJob: {
id: node.job_id,
result: node.result,
logs: node.logs,
args: node.args,
success: node.type === 'Success',
type: 'CompletedJob'
},
storedToolCallJobs: module
? toolCallStore?.getLocalToolCallJobs(parentLoopsPrefix)
: undefined,
onToolJobLoaded: (job, idx) => {
if (module) {
const storeKey = parentLoopsPrefix + module.id + '-' + idx
toolCallStore?.setStoredToolCallJob(storeKey, job)
}
}
}
}
}
: undefined}
/>
: undefined}
/>
</div>
</div>
{:else}
<p class="p-2 text-tertiary italic"
<p class="p-2 text-primary italic"
>The execution of this node has no information attached to it. The job likely
did not run yet</p
>
{/if}
{:else}<p class="p-2 text-tertiary italic">Select a node to see its details here</p
{:else}<p class="p-2 text-primary italic">Select a node to see its details here</p
>{/if}
</div>
{:else if rightColumnSelect == 'node_definition'}
@@ -115,7 +115,7 @@
}
</script>
<div class="w-full h-full mt-2 text-sm text-tertiary">
<div class="w-full h-full mt-2 text-xs text-primary">
{#if !light}
<p>Waiting to be resumed</p>
{/if}
@@ -128,18 +128,18 @@
{#if cancelUrl && !hide_cancel}
<div>
<Button
color="red"
title="Cancel the flow"
iconOnly
startIcon={{ icon: X }}
variant="border"
variant="default"
destructive
on:click={() => continu(false)}
/>
</div>
{/if}
<div>
<Button color="green" variant="border" on:click={() => continu(true)}
>Resume <Tooltip
<Button variant="accent" on:click={() => continu(true)}
>Resume <Tooltip class="text-primary-inverse"
>Since you are an owner of this flow, you can send resume events without necessarily
knowing the resume id sent by the approval step</Tooltip
></Button
@@ -290,7 +290,7 @@
</div>
{#if selectedLen > 0}
<span
class="text-2xs text-tertiary font-mono font-normal w-10 truncate"
class="text-2xs text-primary font-mono font-normal w-10 truncate"
title={msToReadableTime(selectedLen, 1)}>{msToReadableTimeShort(selectedLen, 1)}</span
>
{/if}
@@ -43,10 +43,10 @@
<Tabs bind:selected={tab}>
{#if !noGraph}
<Tab value="ui">Graph</Tab>
<Tab value="ui" label="Graph" />
{/if}
<Tab value="raw">Raw</Tab>
<Tab value="schema">Input Schema</Tab>
<Tab value="raw" label="Raw" />
<Tab value="schema" label="Input Schema" />
{#snippet content()}
<TabContent value="ui">
@@ -66,8 +66,8 @@
maxLines = 100
}}
>
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<Tab value="yaml" label="YAML" />
<Tab value="json" label="JSON" />
{#snippet content()}
<div class="relative pt-2">
<Button
+210 -207
View File
@@ -13,13 +13,13 @@
import GroupEditor from './GroupEditor.svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Section from './Section.svelte'
import { Eye, Plus } from 'lucide-svelte'
import { Eye, Plus, Trash } from 'lucide-svelte'
import Label from './Label.svelte'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher, untrack } from 'svelte'
import Select from './select/Select.svelte'
import { safeSelectItems } from './select/utils.svelte'
import TextInput from './text_input/TextInput.svelte'
interface Props {
name: string
@@ -173,212 +173,220 @@
</DrawerContent>
</Drawer>
<Section label="Metadata" class="mb-4">
<div class="flex flex-col gap-6">
<Label label="Summary">
<div class="flex flex-row gap-2">
<input placeholder="Short summary to be displayed when listed" bind:value={summary} />
<Button size="sm" on:click={updateFolder} disabled={!can_write}>Save</Button>
<TextInput
inputProps={{ placeholder: 'Short summary to be displayed when listed' }}
bind:value={summary}
size="md"
/>
<Button variant="accent" unifiedSize="md" on:click={updateFolder} disabled={!can_write}
>Save</Button
>
</div>
</Label>
</Section>
<Section label={`Permissions (${perms?.length ?? 0})`}>
<div class="flex flex-col gap-6">
{#if can_write}
<Alert type="info" title="New permissions may take up to 60s to apply">
<span class="text-xs text-tertiary">Due to permissions cache invalidation </span>
</Alert>
<div class="flex items-center gap-1">
<div>
<ToggleButtonGroup bind:selected={ownerKind} on:selected={() => (ownerItem = '')}>
{#snippet children({ item })}
<ToggleButton value="user" label="User" {item} />
<ToggleButton value="group" label="Group" {item} />
{/snippet}
</ToggleButtonGroup>
<Label label={`Permissions (${perms?.length ?? 0})`}>
<div class="flex flex-col gap-2">
{#if can_write}
<Alert type="info" title="New permissions may take up to 60s to apply">
Due to permissions cache invalidation
</Alert>
<div class="flex items-center gap-1">
<div>
<ToggleButtonGroup bind:selected={ownerKind} on:selected={() => (ownerItem = '')}>
{#snippet children({ item })}
<ToggleButton value="user" label="User" {item} />
<ToggleButton value="group" label="Group" {item} />
{/snippet}
</ToggleButtonGroup>
</div>
{#key ownerKind}
{@const items =
ownerKind === 'user'
? usernames.filter((x) => !perms?.map((y) => y.owner_name).includes('u/' + x))
: groups.filter((x) => !perms?.map((y) => y.owner_name).includes('g/' + x))}
<Select items={safeSelectItems(items)} bind:value={ownerItem} class="grow min-w-24" />
{#if ownerKind == 'group'}
<Button
title="View Group"
variant="default"
unifiedSize="md"
disabled={!ownerItem || ownerItem == ''}
on:click={viewGroup.openDrawer}
startIcon={{ icon: Eye }}
iconOnly
/>
<Button
title="New Group"
variant="default"
on:click={newGroup.openDrawer}
unifiedSize="md"
startIcon={{ icon: Plus }}
iconOnly
/>
{/if}
{/key}
<Button
disabled={ownerItem == ''}
variant="accent"
unifiedSize="md"
on:click={addToFolder}
>
Grant
</Button>
</div>
{/if}
{#key ownerKind}
{@const items =
ownerKind === 'user'
? usernames.filter((x) => !perms?.map((y) => y.owner_name).includes('u/' + x))
: groups.filter((x) => !perms?.map((y) => y.owner_name).includes('g/' + x))}
<Select items={safeSelectItems(items)} bind:value={ownerItem} />
{#if ownerKind == 'group'}
<Button
title="View Group"
btnClasses="!p-1.5"
variant="border"
size="xs"
disabled={!ownerItem || ownerItem == ''}
on:click={viewGroup.openDrawer}
startIcon={{ icon: Eye }}
iconOnly
/>
<Button
title="New Group"
btnClasses="!p-1.5"
variant="border"
on:click={newGroup.openDrawer}
size="xs"
startIcon={{ icon: Plus }}
iconOnly
/>
{/if}
{/key}
{#if folderNotFound}
<Alert type="warning" title="Folder not found" size="xs">
The folder "{name}" does not exist in the workspace. You can create it by clicking the
button below. An item can seemingly be in a folder given its path without the folder
existing. A windmill folder has settable permissions that its children inherit. If an item
is within a non-existing folders, only admins will see it.
</Alert>
<Button
disabled={ownerItem == ''}
variant="contained"
color="blue"
size="sm"
on:click={addToFolder}
variant="default"
wrapperClasses="w-min"
startIcon={{ icon: Plus }}
size="xs"
on:click={() => {
FolderService.createFolder({
workspace: $workspaceStore ?? '',
requestBody: { name }
}).then(() => {
loadFolder()
})
}}
>
Grant
Create folder "{name}"
</Button>
</div>
{/if}
{/if}
{#if perms}
<TableCustom>
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
<tr slot="header-row">
<th>user/group</th>
<th></th>
<th></th>
</tr>
{#snippet body()}
<tbody>
{#each perms ?? [] as { owner_name, role }}<tr>
<td>{owner_name}</td>
<td>
{#if can_write}
<div>
<ToggleButtonGroup
disabled={owner_name == 'u/' + $userStore?.username &&
!($userStore?.is_admin || $userStore?.is_super_admin)}
selected={role}
on:selected={async (e) => {
const role = e.detail
// const wasInFolder = (folder?.owners ?? []).includes(folder)
// const inAcl = (
// folder?.extra_perms ? Object.keys(folder?.extra_perms) : []
// ).includes(folder)
if (role == 'admin') {
await FolderService.addOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: {
owner: owner_name
}
})
} else if (role == 'writer') {
await FolderService.removeOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: {
owner: owner_name,
write: true
}
})
} else if (role == 'viewer') {
await FolderService.removeOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: {
owner: owner_name,
write: false
}
})
}
loadFolder()
}}
>
{#snippet children({ item })}
<ToggleButton
value="viewer"
label="Viewer"
tooltip="A viewer of a folder has read-only access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
{item}
size="sm"
/>
{#if folderNotFound}
<Alert type="warning" title="Folder not found" size="xs">
The folder "{name}" does not exist in the workspace. You can create it by clicking the
button below. An item can seemingly be in a folder given its path without the folder
existing. A windmill folder has settable permissions that its children inherit. If an item
is within a non-existing folders, only admins will see it.
</Alert>
<Button
color="light"
variant="border"
wrapperClasses="w-min"
startIcon={{ icon: Plus }}
size="xs"
on:click={() => {
FolderService.createFolder({
workspace: $workspaceStore ?? '',
requestBody: { name }
}).then(() => {
loadFolder()
})
}}
>
Create folder "{name}"
</Button>
{/if}
{#if perms}
<TableCustom>
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
<tr slot="header-row">
<th>user/group</th>
<th></th>
<th></th>
</tr>
{#snippet body()}
<tbody>
{#each perms ?? [] as { owner_name, role }}<tr>
<td>{owner_name}</td>
<td>
{#if can_write}
<div>
<ToggleButtonGroup
disabled={owner_name == 'u/' + $userStore?.username &&
!($userStore?.is_admin || $userStore?.is_super_admin)}
selected={role}
on:selected={async (e) => {
const role = e.detail
// const wasInFolder = (folder?.owners ?? []).includes(folder)
// const inAcl = (
// folder?.extra_perms ? Object.keys(folder?.extra_perms) : []
// ).includes(folder)
if (role == 'admin') {
await FolderService.addOwnerToFolder({
<ToggleButton
value="writer"
label="Writer"
tooltip="A writer of a folder has read AND write access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
{item}
size="sm"
/>
<ToggleButton
value="admin"
label="Admin"
tooltip="An admin of a folder has read AND write access to all the elements inside the folders and can manage the permissions as well as add new admins"
{item}
size="sm"
/>
{/snippet}
</ToggleButtonGroup>
</div>
{:else}
{role}
{/if}</td
>
<td class="flex items-center justify-end">
{#if can_write && (owner_name != 'u/' + $userStore?.username || $userStore?.is_admin)}
<Button
variant="subtle"
destructive
unifiedSize="md"
startIcon={{ icon: Trash }}
iconOnly
onclick={async () => {
await Promise.all([
FolderService.removeOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: { owner: owner_name }
}),
GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'folder',
requestBody: {
owner: owner_name
}
})
} else if (role == 'writer') {
await FolderService.removeOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: {
owner: owner_name,
write: true
}
})
} else if (role == 'viewer') {
await FolderService.removeOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: {
owner: owner_name,
write: false
}
})
}
])
loadFolder()
}}
>
{#snippet children({ item })}
<ToggleButton
value="viewer"
label="Viewer"
tooltip="A viewer of a folder has read-only access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
{item}
/>
<ToggleButton
value="writer"
label="Writer"
tooltip="A writer of a folder has read AND write access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
{item}
/>
<ToggleButton
value="admin"
label="Admin"
tooltip="An admin of a folder has read AND write access to all the elements inside the folders and can manage the permissions as well as add new admins"
{item}
/>
{/snippet}
</ToggleButtonGroup>
</div>
{:else}
{role}
{/if}</td
>
<td>
{#if can_write && (owner_name != 'u/' + $userStore?.username || $userStore?.is_admin)}
<button
class="ml-2 text-red-500"
onclick={async () => {
await Promise.all([
FolderService.removeOwnerToFolder({
workspace: $workspaceStore ?? '',
name,
requestBody: { owner: owner_name }
}),
GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'folder',
requestBody: {
owner: owner_name
}
})
])
loadFolder()
}}>remove</button
>
{:else}
<span class="text-tertiary text-xs">cannot remove yourself</span>
{/if}</td
>
</tr>{/each}
</tbody>
{/snippet}
</TableCustom>
<!-- <h2 class="mt-10"
/>
{:else}
<span class="text-primary text-xs">cannot remove yourself</span>
{/if}</td
>
</tr>{/each}
</tbody>
{/snippet}
</TableCustom>
<!-- <h2 class="mt-10"
>Folders managing this folder <Tooltip
>Any owner of those folders can manage this folder</Tooltip
></h2
@@ -386,19 +394,13 @@
{#if can_write}
<div class="flex items-start">
<AutoComplete items={folders} bind:selectedItem={new_managing_folder} />
<Button
variant="contained"
color="blue"
size="sm"
btnClasses="!ml-4"
on:click={addToManagingFolder}
>
<Button variant="accent" size="sm" btnClasses="!ml-4" on:click={addToManagingFolder}>
Add folder managing this folder
</Button>
</div>
{/if} -->
<!-- {#if managing_folders.length == 0}
<p class="text-tertiary text-sm">No folder is managing this folder</p>
<!-- {#if managing_folders.length == 0}
<p class="text-primary text-sm">No folder is managing this folder</p>
{:else}
<TableCustom>
<tr slot="header-row">
@@ -430,12 +432,13 @@
</tbody>
</TableCustom>
{/if} -->
{:else if folderNotFound === undefined}
<div class="flex flex-col">
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
</div>
{/if}
</div>
</Section>
{:else if folderNotFound === undefined}
<div class="flex flex-col">
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
</div>
{/if}
</div>
</Label>
</div>
@@ -9,10 +9,10 @@
</script>
<Popover>
<div class="inline-flex gap-1 items-center">
<span class="text-tertiary text-xs">({members.length}) </span>
<div class="inline-flex gap-1 items-center text-primary text-xs font-normal">
<span>({members.length}) </span>
<div class="max-w-xs truncate">
<span class="text-tertiary text-xs">{members?.join(', ')} </span>
<span>{members?.join(', ')} </span>
</div>
</div>
{#snippet text()}
@@ -103,10 +103,8 @@
</select>
<Button
title="View folder"
btnClasses="!p-1.5"
variant="border"
color="light"
size="xs"
variant="default"
unifiedSize="md"
disabled={!folderName || folderName == ''}
on:click={viewFolder.openDrawer}
iconOnly
@@ -114,10 +112,8 @@
/>
<Button
title="New folder"
btnClasses="!p-1.5"
variant="border"
color="light"
size="xs"
variant="default"
unifiedSize="md"
{disabled}
on:click={newFolder.openDrawer}
iconOnly
@@ -5,16 +5,22 @@
previewResult: SyncResponse | undefined
}>()
let added = $derived(previewResult?.changes?.filter(c => c.type === 'added').map(c => c.path) || [])
let deleted = $derived(previewResult?.changes?.filter(c => c.type === 'deleted').map(c => c.path) || [])
let edited = $derived(previewResult?.changes?.filter(c => c.type === 'edited').map(c => c.path) || [])
let added = $derived(
previewResult?.changes?.filter((c) => c.type === 'added').map((c) => c.path) || []
)
let deleted = $derived(
previewResult?.changes?.filter((c) => c.type === 'deleted').map((c) => c.path) || []
)
let edited = $derived(
previewResult?.changes?.filter((c) => c.type === 'edited').map((c) => c.path) || []
)
</script>
<div class="border rounded p-2 text-xs max-h-40 overflow-y-auto bg-surface-secondary">
<div class="font-semibold text-[11px] mb-1 text-tertiary">Preview of changes:</div>
<div class="font-semibold text-[11px] mb-1 text-primary">Preview of changes:</div>
{#if !added.length && !deleted.length && !edited.length}
<div class="mt-2 text-tertiary">No changes found! The workspace is up to date.</div>
<div class="mt-2 text-primary">No changes found! The workspace is up to date.</div>
{:else}
{#if added.length}
<div class="mt-2">
@@ -166,8 +166,7 @@
{#if showGitHubApp}
{#if !githubState.loadingGithubInstallations}
<Button
color="light"
variant="contained"
variant="default"
size="xs"
on:click={handleRefreshInstallations}
disabled={!$enterpriseLicense}
@@ -187,8 +186,7 @@
>
{#snippet trigger()}
<Button
color="none"
variant="border"
variant="default"
size="xs"
disabled={!$enterpriseLicense || githubState.loadingGithubInstallations}
startIcon={{
@@ -232,7 +230,7 @@
<div class="pt-[26px]">
<Button
size="xs"
color="blue"
variant="accent"
buttonType="button"
disabled={!githubState.selectedGHAppRepository}
on:click={() => handleApplyRepositoryURL(close)}
@@ -254,8 +252,7 @@
<div class="flex flex-col gap-4">
<div class="flex">
<Button
color="none"
variant="border"
variant="default"
size="xs"
href={githubState.githubInstallationUrl}
startIcon={{
@@ -283,7 +280,7 @@
<div class="flex flex-col gap-1">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-xs text-tertiary">
<tr class="text-left text-xs text-primary">
<th class="pb-2 w-1/3">Org</th>
<th class="pb-2 w-1/6">Workspace</th>
<th class="pb-2 w-1/6">Repos</th>
@@ -295,18 +292,17 @@
<tr class="border-t border-gray-200 dark:border-gray-700">
<td class="py-2">{installation.account_id}</td>
<td class="py-2">
<span class="text-xs text-tertiary"
>{installation.workspace_id}</span
<span class="text-xs text-primary">{installation.workspace_id}</span
>
</td>
<td class="py-2 text-tertiary">
<td class="py-2 text-primary">
{installation.repositories.length} repos
</td>
<td class="py-2 text-right">
<div class="flex justify-end gap-1">
<Button
size="xs2"
color="blue"
variant="accent"
title="Export installation to other instance"
startIcon={{ icon: Download }}
on:click={() =>
@@ -316,7 +312,8 @@
</Button>
<Button
size="xs2"
color="red"
variant="default"
destructive
title="Remove installation from workspace"
startIcon={{ icon: Minus }}
on:click={() =>
@@ -341,7 +338,7 @@
<div class="flex flex-col gap-1">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-xs text-tertiary">
<tr class="text-left text-xs text-primary">
<th class="pb-2 w-1/3">Org</th>
<th class="pb-2 w-1/6">Workspace</th>
<th class="pb-2 w-1/6">Repos</th>
@@ -353,17 +350,16 @@
<tr class="border-t border-gray-200 dark:border-gray-700">
<td class="py-2">{installation.account_id}</td>
<td class="py-2">
<span class="text-xs text-tertiary"
>{installation.workspace_id}</span
<span class="text-xs text-primary">{installation.workspace_id}</span
>
</td>
<td class="py-2 text-tertiary">
<td class="py-2 text-primary">
{installation.repositories.length} repos
</td>
<td class="pl-8 py-2 text-right">
<Button
size="xs2"
color="blue"
variant="accent"
title="Add installation to workspace"
startIcon={{ icon: Plus }}
on:click={() => {
@@ -400,7 +396,7 @@
class="flex-1"
/>
<Button
color="blue"
variant="accent"
on:click={handleImportInstallation}
disabled={!githubState.importJwt}
>
@@ -414,8 +410,7 @@
</Popover>
{:else}
<Button
color="none"
variant="border"
variant="default"
size="xs"
disabled={!$enterpriseLicense || githubState.loadingGithubInstallations}
startIcon={{
@@ -10,10 +10,7 @@
children?: import('svelte').Snippet
}
let {
isOpen = $bindable(false),
children
}: Props = $props()
let { isOpen = $bindable(false), children }: Props = $props()
const dispatch = createEventDispatcher<{
selected: { resourcePath: string }
@@ -72,9 +69,9 @@
</div>
{:else if gitRepoResources.length === 0}
<div class="p-4 text-center">
<GitBranch size={20} class="mx-auto mb-2 text-tertiary" />
<GitBranch size={20} class="mx-auto mb-2 text-primary" />
<p class="text-sm text-secondary mb-1">No git repositories found</p>
<p class="text-xs text-tertiary">Create a git repository resource first</p>
<p class="text-xs text-primary">Create a git repository resource first</p>
</div>
{:else}
<div class="py-1">
@@ -83,11 +80,11 @@
class="w-full text-left px-3 py-2 hover:bg-surface-hover text-sm flex items-center gap-2 transition-colors"
onclick={() => handleSelect(resource.path)}
>
<GitBranch size={14} class="text-tertiary flex-shrink-0" />
<GitBranch size={14} class="text-primary flex-shrink-0" />
<div class="flex-1 min-w-0">
<div class="font-medium text-primary truncate">{resource.path}</div>
{#if resource.description}
<div class="text-xs text-tertiary truncate">{resource.description}</div>
<div class="text-xs text-primary truncate">{resource.description}</div>
{/if}
</div>
</button>
@@ -96,4 +93,4 @@
{/if}
</div>
{/snippet}
</Popover>
</Popover>
@@ -123,7 +123,7 @@
const result = await ResourceService.getGitCommitHash({
workspace: $workspaceStore!,
path: selectedResource,
gitSshIdentity: gitSshIdentity?.join(",")
gitSshIdentity: gitSshIdentity?.join(',')
})
commitHash = result.commit_hash
} catch (err) {
@@ -164,8 +164,8 @@
tooltip="Select a git repository resource to delegate ansible execution to"
>
<div class="flex flex-col gap-4 p-4">
<div class="flex flex-col gap-2">
<div class="text-sm font-medium text-primary"> Git Repository Resource </div>
<div class="flex flex-col gap-1">
<div class="text-xs font-semibold text-emphasis"> Git Repository Resource </div>
{#if currentResource}
<div
@@ -174,10 +174,10 @@
<div class="flex items-center gap-2">
<GitBranch size={16} class="text-blue-600 dark:text-blue-400 flex-shrink-0" />
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-blue-800 dark:text-blue-200">
<div class="text-xs font-semibold text-blue-800 dark:text-blue-200">
Currently delegating to:
</div>
<div class="text-sm text-blue-600 dark:text-blue-300 truncate">
<div class="text-xs text-blue-600 dark:text-blue-300 truncate">
{currentResource}
</div>
</div>
@@ -188,13 +188,13 @@
{#if loading}
<div class="flex items-center gap-2 p-2">
<Loader2 size={16} class="animate-spin" />
<span class="text-sm text-secondary">Loading git repository resources...</span>
<span class="text-xs text-primary">Loading git repository resources...</span>
</div>
{:else if gitRepoResources.length === 0}
<div class="p-4 text-center border border-gray-200 dark:border-gray-700 rounded-md">
<GitBranch size={24} class="mx-auto mb-2 text-tertiary" />
<p class="text-sm text-secondary mb-2">No git repository resources found</p>
<p class="text-xs text-tertiary">
<div class="p-4 text-center border border-border-light rounded-md">
<GitBranch size={24} class="mx-auto mb-2 text-primary" />
<p class="text-xs text-primary mb-2">No git repository resources found</p>
<p class="text-2xs text-secondary">
Create a git repository resource first to use this feature
</p>
</div>
@@ -209,28 +209,27 @@
</div>
<!-- Playbook Configuration -->
<div class="flex flex-col gap-2">
<div class="text-sm font-medium text-primary">
<div class="flex flex-col gap-1">
<div class="text-xs font-semibold text-emphasis">
Playbook Path
<span class="text-xs text-tertiary font-normal ml-1">(optional)</span>
<span class="text-xs text-primary font-normal ml-1">(optional)</span>
</div>
<p class="text-xs text-primary">
Specify the path to your main playbook file relative to the git repository root
</p>
<input
type="text"
bind:value={playbook}
placeholder="e.g., ./playbooks/site.yml"
class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-surface text-primary placeholder-tertiary focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<p class="text-xs text-tertiary">
Specify the path to your main playbook file relative to the git repository root
</p>
</div>
<!-- Inventories Location Configuration -->
<div class="flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-700">
<Button color="light" variant="border" on:click={handleClose}>Cancel</Button>
<Button variant="default" on:click={handleClose}>Cancel</Button>
<Button
color="dark"
variant="accent"
disabled={!selectedResource || loading}
on:click={handleSelect}
startIcon={{ icon: GitBranch }}
@@ -240,25 +239,22 @@
</div>
{#if selectedResource}
<div class="flex flex-col gap-2">
<div class="text-sm font-medium text-primary">
Inventories Location
</div>
<div class="text-xs font-semibold text-emphasis"> Inventories Location </div>
<input
type="text"
bind:value={inventoriesLocation}
placeholder="e.g., hosts"
class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-surface text-primary placeholder-tertiary focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<p class="text-xs text-tertiary">
<p class="text-xs text-primary">
Specify the directory containing your inventory files relative to the git repository
root
</p>
<div class="mt-2">
<Button
color="light"
variant="border"
size="xs"
variant="default"
unifiedSize="md"
disabled={loadingInventories || !inventoriesLocation.trim()}
startIcon={{ icon: loadingInventories ? Loader2 : FolderOpen }}
onclick={handleAddInventories}
+174 -164
View File
@@ -15,10 +15,11 @@
import { canWrite } from '$lib/utils'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Section from './Section.svelte'
import Label from './Label.svelte'
import Select from './select/Select.svelte'
import { safeSelectItems } from './select/utils.svelte'
import TextInput from './text_input/TextInput.svelte'
import { Trash } from 'lucide-svelte'
interface Props {
name: string
@@ -111,12 +112,17 @@
})
</script>
<Section label="Metadata" class="mb-4">
<Label label="Summary">
<div class="flex flex-col gap-6">
<Label label="Summary" for="summary">
<div class="flex flex-row gap-2">
<input placeholder="Short summary to be displayed when listed" bind:value={summary} />
<TextInput
inputProps={{ placeholder: 'Short summary to be displayed when listed', id: 'summary' }}
bind:value={summary}
size="md"
/>
<Button
size="sm"
unifiedSize="md"
variant="accent"
on:click={async () => {
await GroupService.updateGroup({
workspace: $workspaceStore ?? '',
@@ -130,175 +136,179 @@
>
</div>
</Label>
</Section>
<Section label={`Members (${members?.length ?? 0})`}>
{#if can_write}
<div class="flex items-start">
<Select items={safeSelectItems(usernames)} bind:value={username} />
<Button variant="contained" color="blue" size="sm" btnClasses="!ml-4" on:click={addToGroup}>
Add member
</Button>
</div>
{/if}
{#if members}
<TableCustom>
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
<tr slot="header-row">
<th>user</th>
<th></th>
<th></th>
</tr>
{#snippet body()}
<tbody>
{#each members ?? [] as { member_name, role }}<tr>
<td>{member_name}</td>
<td>
{#if can_write}
<div>
<ToggleButtonGroup
selected={role}
on:selected={async (e) => {
const role = e.detail
// const wasInGroup = (group?.members ?? []).includes(group)
// const inAcl = (
// group?.extra_perms ? Object.keys(group?.extra_perms) : []
// ).includes(group)
if (role == 'member') {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
} else if (role == 'manager') {
await GroupService.removeUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.addGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name,
write: true
}
})
} else if (role == 'admin') {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.addGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name,
write: true
}
})
}
loadGroup()
}}
>
{#snippet children({ item })}
<ToggleButton
value="member"
small
label="Member"
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
{item}
/>
<ToggleButton
value="admin"
small
label="Admin"
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
{item}
/>
{#if role === 'manager'}
<ToggleButton
value="manager"
small
label="Manager"
tooltip="A manager of a group can manage the group, adding and removing users and
change their roles. Being a manager does not make you a member"
{item}
/>
{/if}
{/snippet}
</ToggleButtonGroup>
</div>
{:else}
{role}
{/if}</td
>
<td>
{#if can_write}
<button
class="ml-2 text-red-500"
onclick={async () => {
await GroupService.removeUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { username: member_name }
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
loadGroup()
}}>remove</button
>
{/if}</td
>
</tr>{/each}
</tbody>
{/snippet}
</TableCustom>
{#if instance_group?.emails}
<h2 class="mt-10">Members from the instance group</h2>
<Label label={`Members (${members?.length ?? 0})`}>
{#if can_write}
<div class="flex items-start gap-1">
<Select items={safeSelectItems(usernames)} bind:value={username} size="md" class="grow" />
<Button variant="accent" color="blue" unifiedSize="md" on:click={addToGroup}>
Add member
</Button>
</div>
{/if}
{#if members}
<TableCustom>
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
<tr slot="header-row">
<th>user</th>
<th></th>
<th></th>
</tr>
{#snippet body()}
<tbody>
{#each instance_group?.emails ?? [] as email}<tr>
<td>{email}</td>
{#each members ?? [] as { member_name, role }}<tr>
<td>{member_name}</td>
<td>
{#if can_write}
<div>
<ToggleButtonGroup
selected={role}
on:selected={async (e) => {
const role = e.detail
// const wasInGroup = (group?.members ?? []).includes(group)
// const inAcl = (
// group?.extra_perms ? Object.keys(group?.extra_perms) : []
// ).includes(group)
if (role == 'member') {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
} else if (role == 'manager') {
await GroupService.removeUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.addGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name,
write: true
}
})
} else if (role == 'admin') {
await GroupService.addUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: {
username: member_name
}
})
await GranularAclService.addGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name,
write: true
}
})
}
loadGroup()
}}
>
{#snippet children({ item })}
<ToggleButton
value="member"
small
label="Member"
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
{item}
/>
<ToggleButton
value="admin"
small
label="Admin"
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
{item}
/>
{#if role === 'manager'}
<ToggleButton
value="manager"
small
label="Manager"
tooltip="A manager of a group can manage the group, adding and removing users and
change their roles. Being a manager does not make you a member"
{item}
/>
{/if}
{/snippet}
</ToggleButtonGroup>
</div>
{:else}
{role}
{/if}</td
>
<td class="flex justify-end">
{#if can_write}
<Button
variant="subtle"
destructive
unifiedSize="md"
startIcon={{ icon: Trash }}
iconOnly
onclick={async () => {
await GroupService.removeUserToGroup({
workspace: $workspaceStore ?? '',
name,
requestBody: { username: member_name }
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
loadGroup()
}}
/>
{/if}</td
>
</tr>{/each}
</tbody>
{/snippet}
</TableCustom>
{#if instance_group?.emails}
<h2 class="mt-6 text-emphasis text-xs font-semibold">Members from the instance group</h2>
<TableCustom>
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
<tr slot="header-row">
<th>user</th>
</tr>
{#snippet body()}
<tbody>
{#each instance_group?.emails ?? [] as email}<tr>
<td>{email}</td>
</tr>{/each}
</tbody>
{/snippet}
</TableCustom>
{/if}
{:else}
<div class="flex flex-col">
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
</div>
{/if}
{:else}
<div class="flex flex-col">
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
</div>
{/if}
</Section>
</Label>
</div>
+27 -6
View File
@@ -13,7 +13,30 @@
let members: string[] | undefined = $state([])
async function loadMembers() {
members = (await GroupService.getGroup({ workspace: $workspaceStore!, name })).members
// FAKE DATA FOR TESTING - Set to false to remove all fake data
const ENABLE_FAKE_DATA = false
if (ENABLE_FAKE_DATA) {
// Fake member data for different groups
const fakeMembers: Record<string, string[]> = {
developers: ['john.doe', 'jane.smith', 'bob.wilson', 'alice.johnson', 'charlie.brown'],
'readonly-users': ['viewer1', 'viewer2'],
admins: ['admin1', 'admin2', 'admin3'],
testers: ['tester1', 'tester2', 'tester3', 'tester4'],
'external-contractors': ['contractor1', 'contractor2']
}
if (fakeMembers[name]) {
members = fakeMembers[name]
return
}
}
try {
members = (await GroupService.getGroup({ workspace: $workspaceStore!, name })).members
} catch (e) {
members = []
}
}
$effect(() => {
$workspaceStore && untrack(() => loadMembers())
@@ -22,11 +45,9 @@
{#if members}
<Popover
><div class="inline-flex gap-1 items-end"
><span class="text-tertiary text-xs">({members.length})</span>
<div class="max-w-xs truncate"
><span class="text-tertiary text-xs">{members?.join(', ')}</span></div
></div
><div class="inline-flex gap-1 items-end text-primary text-xs font-normal"
><span>({members.length})</span>
<div class="max-w-xs truncate"><span>{members?.join(', ')}</span></div></div
>
{#snippet text()}
<span>{members?.join(', ')}</span>
@@ -12,9 +12,7 @@
code.hljs {
padding: 3px 5px;
}
.dark .hljs {
background: #2e3440 !important;
}
.dark .hljs,
.dark .hljs-subst {
color: #d8dee9 !important;
@@ -238,8 +236,7 @@
color: #d08770 !important;
}
.hljs {
color: #24292e;
background: #ffffff;
color: #4a5568;
}
.hljs-doctag,
.hljs-keyword,
@@ -120,11 +120,11 @@
/>
{/if}
<div class="h-full max-h-full min-h-0 w-full flex flex-col gap-4 relative">
<div class="h-full max-h-full min-h-0 w-full flex flex-col gap-2 relative">
<div class="grow-0" data-schema-picker>
<DataTable size="xs" bind:currentPage={page} hasMore={hasMoreCurrentRuns} tableFixed={true}>
{#if loading}
<div class="text-tertiary absolute top-2 right-2">
<div class="text-primary absolute top-2 right-2">
<Loader2 class="animate-spin" size={14} />
</div>
{/if}
@@ -145,13 +145,13 @@
/>
{/each}
{#if jobs?.length == 5}
<tr class="text-left text-tertiary text-xs w-full">
<tr class="text-left text-primary text-xs w-full">
<td class="w-full px-2" colspan="3">limited to 5 runs</td>
</tr>
{/if}
</tbody>
{:else}
<div class="text-center text-tertiary text-xs py-2">No job currently running</div>
<div class="text-center text-primary text-xs py-2">No job currently running</div>
{/if}
</DataTable>
</div>
@@ -141,7 +141,7 @@
/>
{/snippet}
{#snippet empty()}
<div class="text-center text-tertiary text-xs py-2">
<div class="text-center text-primary text-xs py-2">
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
</div>
{/snippet}
@@ -109,8 +109,8 @@
</script>
<Tabs bind:selected={selectedTab}>
<Tab value="create">Create</Tab>
<Tab value="blacklist">Blacklist</Tab>
<Tab value="create" label="Create" />
<Tab value="blacklist" label="Blacklist" />
{#snippet content()}
<div class="flex flex-col gap-y-4 pt-2">
{#if selectedTab === 'create'}
@@ -42,7 +42,7 @@
{/if}
{#if iconComponent}
{@const SvelteComponent = iconComponent}
<span class={isSelected ? 'text-secondary' : 'text-secondary grayscale'}>
<span class={isSelected ? 'text-secondary' : 'text-secondary'}>
<SvelteComponent {height} {width} size={widthInPixels} />
</span>
{:else if formatExtension}
@@ -450,9 +450,9 @@
</script>
{#if arg != undefined && !hidden}
<div class={twMerge('pt-2 pb-2 relative group', className)}>
<div class="flex flex-row justify-between gap-1 pb-1">
<div class="flex flex-wrap grow min-h-7 items-end">
<div class={twMerge('relative group flex flex-col gap-1', className)}>
<div class="flex flex-row flex-wrap justify-between gap-1">
<div class="flex grow min-h-7 items-end">
<FieldHeader
label={argName}
simpleTooltip={headerTooltip}
@@ -598,20 +598,25 @@
propertyType = 'static'
}
}}
class="h-6"
>
{#snippet children({ item })}
{#if isStaticTemplate(inputCat)}
<ToggleButton
light
small
size="sm"
tooltip={`Write text or surround javascript with \`\$\{\` and \`\}\`. Use \`results\` to connect to another node\'s output.`}
value="static"
label={'${}'}
{item}
class="h-full text-xs"
/>
{:else}
<ToggleButton light small label="static" value="static" {item} />
<ToggleButton
size="sm"
label="static"
value="static"
{item}
class="h-full text-xs"
/>
{/if}
{#if codeInjectionDetected && propertyType == 'static'}
@@ -630,11 +635,11 @@
<ToggleButton
disabled={inputCat === 'dynamic'}
small
light
tooltip="JavaScript expression ('flow_input' or 'results')."
value="javascript"
icon={FunctionSquare}
{item}
class="h-full"
/>
{/if}
{/snippet}
@@ -644,7 +649,6 @@
{/if}
</div>
<div class="max-w-xs"></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="relative" onkeyup={handleKeyUp}>
<!-- {#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
@@ -656,12 +660,20 @@
{/if} -->
<!-- {inputCat}
{propertyType} -->
<div class="relative flex flex-row items-top gap-2 justify-between">
<div class="relative flex flex-row items-top gap-1 justify-between">
<div class="min-w-0 grow">
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
<div>
<div class="flex flex-col gap-1">
{#if argName && schema?.properties?.[argName]?.description}
<div class="text-xs text-secondary">
<pre class="font-main whitespace-normal">
{schema.properties[argName].description}
</pre>
</div>
{/if}
{#if arg}
<TemplateEditor
yPadding={7}
bind:this={monacoTemplate}
{extraLib}
on:focus={onFocus}
@@ -669,21 +681,14 @@
focused = false
}}
bind:code={arg.value}
fontSize={14}
fontSize={12}
on:change={() => {
dispatch('change', { argName, arg })
}}
loadAsync
class="bg-surface-secondary"
class="bg-surface-input"
/>
{/if}
{#if argName && schema?.properties?.[argName]?.description}
<div class="text-xs italic py-1 text-hint">
<pre class="font-main whitespace-normal"
>{schema.properties[argName].description}</pre
>
</div>
{/if}
</div>
{:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]}
<ArgInput
@@ -743,12 +748,13 @@
</ArgInput>
{:else if arg.expr != undefined}
<div
class={`bg-surface-secondary rounded-md flex flex-col pl-4 ${inputBorderClass({ forceFocus: focused })}`}
class={`bg-surface-input rounded-md flex flex-col pl-3 ${inputBorderClass({ forceFocus: focused })}`}
>
<SimpleEditor
small
bind:this={monaco}
bind:code={arg.expr}
yPadding={8}
yPadding={7}
{extraLib}
lang="javascript"
shouldBindKey={false}
@@ -779,7 +785,7 @@
{/if}
{#if argName && schema?.properties?.[argName]?.description}
<div class="text-xs italic py-1 text-hint">
<div class="text-xs italic py-1 text-secondary">
<pre class="font-main whitespace-normal"
>{schema.properties[argName].description}</pre
>
@@ -797,7 +803,7 @@
</span>
<div class="flex mt-2">
<Button
variant="border"
variant="default"
size="xs"
on:click={() => {
arg.expr = ''
@@ -83,7 +83,7 @@
<div class="w-full mb-6 {clazz}">
{#if enableAi}
<div class="mt-2">
<div class="pt-2">
<StepInputsGen
{pickableProperties}
argNames={keys
@@ -102,7 +102,7 @@
{#if keys.length > 0}
{#each keys as argName, index (argName)}
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
<ResizeTransitionWrapper class="mt-2 relative" innerClass="w-full" vertical>
<ResizeTransitionWrapper class="mt-6 relative" innerClass="w-full" vertical>
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
@@ -127,7 +127,7 @@
{/if}
{/each}
{:else}
<div class="text-tertiary text-sm">No inputs</div>
<div class="text-primary text-xs mt-2">No inputs</div>
{/if}
</div>
@@ -149,8 +149,7 @@
{#snippet submission()}
<div class="flex flex-row-reverse w-full border-t border-gray-200 rounded-bl-lg rounded-br-lg">
<Button
variant="border"
color="blue"
variant="accent"
size="sm"
startIcon={{ icon: Plus }}
on:click={() => {
@@ -29,5 +29,5 @@
{/each}
</ul>
{:else}
<div class="text-tertiary text-sm"> No inputs </div>
<div class="text-primary text-xs"> No inputs </div>
{/if}
@@ -29,7 +29,7 @@
members = instance_group?.emails
? instance_group.emails.map((x) => {
return { member_email: x }
})
})
: []
}
</script>
@@ -67,8 +67,7 @@
<div class="flex items-start">
<input type="text" placeholder="john@acme.com" bind:value={email} />
<Button
variant="contained"
color="blue"
variant="accent"
size="sm"
btnClasses="!ml-4"
on:click={async () => {
@@ -7,6 +7,7 @@
import ChangeInstanceUsernameInner from './ChangeInstanceUsernameInner.svelte'
import { UserService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import TextInput from './text_input/TextInput.svelte'
export let value: string | undefined
export let email: string
@@ -54,28 +55,32 @@
<ChangeInstanceUsernameInner {email} {username} on:renamed noPadding />
{/if}
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Name</div>
<div class="pb-1 text-xs font-semibold text-emphasis">Name</div>
<div class="flex w-full">
<input
type="text"
bind:value
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
saveName()
<TextInput
inputProps={{
onclick: (e) => {
e.stopPropagation()
},
onkeydown: (e) => {
e.stopPropagation()
},
onkeypress: ({ key }) => {
if (key === 'Enter') {
saveName()
}
}
}}
bind:value
/>
</div>
<Button
size="xs"
color="blue"
unifiedSize="md"
variant="default"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save ID"
on:click={() => {
onclick={() => {
saveName()
}}
>
@@ -83,7 +88,7 @@
</Button>
</label>
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Password</div>
<div class="pb-1 text-xs font-semibold text-emphasis">Password</div>
<div class="flex w-full">
<input
type="password"
@@ -99,8 +104,8 @@
/>
</div>
<Button
size="xs"
color="blue"
unifiedSize="md"
variant="default"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save ID"
@@ -112,11 +117,7 @@
</Button>
</label>
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Login type</div>
<div class="text-xs text-secondary mb-2">
Must match exact SSO name, "password" or "saml". Examples: password, google, saml,
microsoft
</div>
<div class="mb-1 text-xs font-semibold text-emphasis">Login type</div>
<div class="flex w-full">
<input
@@ -132,9 +133,13 @@
}}
/>
</div>
<div class="text-2xs text-secondary mb-1">
Must match exact SSO name, "password" or "saml". Examples: password, google, saml,
microsoft
</div>
<Button
size="xs"
color="blue"
unifiedSize="md"
variant="default"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save login type"
@@ -204,10 +204,10 @@
{#if setting.fieldType == 'select'}
<div>
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">{setting.label}</span>
<label class="block pb-1">
<span class="text-emphasis font-semibold text-xs pb-1">{setting.label}</span>
{#if setting.description}
<span class="text-secondary text-xs">
<span class="text-secondary text-xs font-normal">
{@html setting.description}
</span>
{/if}
@@ -228,10 +228,10 @@
{:else if setting.fieldType == 'select_python'}
<div>
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">{setting.label}</span>
<label class="mb-1 flex flex-col gap-1">
<span class="text-emphasis font-semibold text-xs">{setting.label}</span>
{#if setting.description}
<span class="text-secondary text-xs">
<span class="text-secondary text-xs font-normal">
{@html setting.description}
</span>
{/if}
@@ -247,19 +247,17 @@
item={toggleButtonn}
/>
{/each}
<PopoverMelt closeButton={!isPyFetching}>
<PopoverMelt closeButton={!isPyFetching} contentClasses="max-w-md">
{#snippet trigger()}
{#if setting.select_items?.some((e) => e.label == $values[setting.key] || e.value == $values[setting.key])}
<Button
variant="border"
color="dark"
variant="default"
btnClasses="px-1.5 py-1.5 text-2xs bg-surface-secondary border-0"
nonCaptureEvent={true}>Select Custom</Button
>
{:else}
<Button
variant="border"
color="dark"
variant="default"
btnClasses="px-1.5 py-1.5 text-2xs border-0 shadow-md"
nonCaptureEvent={true}>Custom | {$values[setting.key]}</Button
>
@@ -290,12 +288,14 @@
</div>
{:else}
{#snippet settingContent()}
<span class="text-primary font-semibold text-sm">{setting.label}</span>
{#if setting.description}
<span class="text-secondary text-xs">
{@html setting.description}
</span>
{/if}
<div class="text-emphasis font-semibold text-xs flex flex-col gap-1 mb-1"
>{setting.label}
{#if setting.description}
<span class="text-secondary font-normal text-xs">
{@html setting.description}
</span>
{/if}
</div>
{#if setting.tooltip}
<Tooltip>{setting.tooltip}</Tooltip>
{/if}
@@ -371,8 +371,9 @@
bind:password={$values[setting.key]}
/>
<Button
variant={$values[setting.key] ? 'contained' : 'border'}
size="xs"
variant="accent"
unifiedSize="md"
disabled={!$values[setting.key]}
on:click={async () => {
await SettingService.testLicenseKey({
requestBody: { license_key: $values[setting.key] }
@@ -387,9 +388,8 @@
{#if $values[setting.key]?.length > 0}
{#if valid}
<div class="flex flex-row gap-1 items-center">
<Info size={12} class="text-tertiary" />
<span class="text-tertiary text-xs"
>License key expires on {expiration ?? ''}</span
<Info size={12} class="text-primary" />
<span class="text-primary text-xs">License key expires on {expiration ?? ''}</span
>
</div>
{:else if expiration}
@@ -486,10 +486,10 @@
{#if valid || expiration}
<div class="flex flex-row gap-2 mt-1">
<Button on:click={renewLicenseKey} loading={renewing} size="xs" color="dark"
<Button on:click={renewLicenseKey} loading={renewing} size="xs" variant="accent"
>Renew key
</Button>
<Button color="dark" size="xs" loading={opening} on:click={openCustomerPortal}>
<Button variant="accent" size="xs" loading={opening} on:click={openCustomerPortal}>
Open customer portal
</Button>
</div>
@@ -506,8 +506,7 @@
/>
<div class="flex flex-row">
<Button
variant="border"
color="light"
variant="default"
disabled={!$enterpriseLicense}
size="xs"
on:click={() => {
@@ -636,8 +635,7 @@
</div>
<div class="flex mt-2 gap-20 items-center">
<Button
variant="border"
color="light"
variant="default"
size="xs"
btnClasses="mt-1"
on:click={() => {
@@ -656,8 +654,7 @@
<div class="flex mt-1">
<Button
disabled={!$enterpriseLicense}
variant="border"
color="light"
variant="default"
size="xs"
on:click={async () => {
try {
@@ -684,7 +681,7 @@
size="sm"
endIcon={{ icon: Slack }}
btnClasses="mt-2"
variant="border"
variant="default"
on:click={async () => {
$values[setting.key] = undefined
}}
@@ -694,7 +691,7 @@
{:else}
<Button
size="xs"
color="dark"
variant="accent"
href="{base}/api/oauth/connect_slack?instance=true"
startIcon={{ icon: Slack }}
disabled={!$enterpriseLicense}
@@ -707,7 +704,7 @@
<div class="flex flex-col gap-4 mt-4">
{#if $values[setting.key]}
<div>
<label for="writer_memory_budget" class="block text-sm font-medium">
<label for="writer_memory_budget" class="block text-xs font-semibold text-emphasis">
Index writer memory budget (MB)
<Tooltip>
The allocated memory arena for the indexer. A bigger value means less writing to
@@ -730,9 +727,12 @@
value={$values[setting.key].writer_memory_budget / (1024 * 1024)}
/>
</div>
<h3>Completed Job Index</h3>
<h3 class="text-sm font-semibold text-emphasis mt-8">Completed Job Index</h3>
<div>
<label for="commit_job_max_batch_size" class="block text-sm font-medium">
<label
for="commit_job_max_batch_size"
class="block text-xs font-semibold text-emphasis"
>
Commit max batch size <Tooltip>
The max amount of documents (here jobs) per commit. To optimize indexing
throughput, it is best to keep this as high as possible. However, especially
@@ -751,7 +751,7 @@
/>
</div>
<div>
<label for="refresh_index_period" class="block text-sm font-medium">
<label for="refresh_index_period" class="block text-xs font-semibold text-emphasis">
Refresh index period (s) <Tooltip>
The index will query new jobs periodically and write them on the index. This
setting sets that period.
@@ -766,7 +766,10 @@
/>
</div>
<div>
<label for="max_indexed_job_log_size" class="block text-sm font-medium">
<label
for="max_indexed_job_log_size"
class="block text-xs font-semibold text-emphasis"
>
Max indexed job log size (KB) <Tooltip>
Job logs are included when indexing, but to avoid the index size growing
artificially, the logs will be truncated after a size has been reached.
@@ -788,9 +791,11 @@
value={$values[setting.key].max_indexed_job_log_size / 1024}
/>
</div>
<h3>Service Logs Index</h3>
<h3 class="text-sm font-semibold text-emphasis mt-8">Service logs index</h3>
<div>
<label for="commit_log_max_batch_size" class="block text-sm font-medium"
<label
for="commit_log_max_batch_size"
class="block text-xs font-semibold text-emphasis"
>Commit max batch size <Tooltip>
The max amount of documents per commit. In this case 1 document is one log file
representing all logs during 1 minute for a specific host. To optimize indexing
@@ -810,7 +815,10 @@
/>
</div>
<div>
<label for="refresh_log_index_period" class="block text-sm font-medium">
<label
for="refresh_log_index_period"
class="block text-xs font-semibold text-emphasis"
>
Refresh index period (s) <Tooltip>
The index will query new service logs peridically and write them on the index.
This setting sets that period.
@@ -824,9 +832,11 @@
bind:value={$values[setting.key].refresh_log_index_period}
/>
</div>
<h3>Reset Index</h3>
This buttons will clear the whole index, and the service will start reindexing from scratch.
Full text search might be down during this time.
<h3 class="text-sm font-semibold text-emphasis mt-8">Reset Index</h3>
<p class="text-xs text-primary font-normal">
This buttons will clear the whole index, and the service will start reindexing from
scratch. Full text search might be down during this time.
</p>
<div>
<ConfirmButton
on:click={async () => {
@@ -844,7 +854,7 @@
})
console.log('asasd')
sendUserToast(r)
}}>Clear <b>Service Logs</b> Index</ConfirmButton
}}>Clear <b>Service logs</b> index</ConfirmButton
>
</div>
{/if}
@@ -877,7 +887,8 @@
></div
>
<div>
<label for="smtp_host" class="block text-sm font-medium">Host</label>
<label for="smtp_host" class="block text-xs font-semibold text-emphasis">Host</label
>
<input
type="text"
id="smtp_host"
@@ -886,7 +897,8 @@
/>
</div>
<div>
<label for="smtp_port" class="block text-sm font-medium">Port</label>
<label for="smtp_port" class="block text-xs font-semibold text-emphasis">Port</label
>
<input
type="number"
id="smtp_port"
@@ -895,7 +907,9 @@
/>
</div>
<div>
<label for="smtp_username" class="block text-sm font-medium">Username</label>
<label for="smtp_username" class="block text-xs font-semibold text-emphasis"
>Username</label
>
<input
type="text"
id="smtp_username"
@@ -904,11 +918,15 @@
/>
</div>
<div>
<label for="smtp_password" class="block text-sm font-medium">Password</label>
<label for="smtp_password" class="block text-xs font-semibold text-emphasis"
>Password</label
>
<Password bind:password={$values[setting.key].smtp_password} />
</div>
<div>
<label for="smtp_from" class="block text-sm font-medium">From Address</label>
<label for="smtp_from" class="block text-xs font-semibold text-emphasis"
>From Address</label
>
<input
type="email"
id="smtp_from"
@@ -922,7 +940,6 @@
id="smtp_tls_implicit"
bind:checked={$values[setting.key].smtp_tls_implicit}
options={{ right: 'Implicit TLS' }}
label="Implicit TLS"
/>
</div>
<div>
@@ -936,7 +953,6 @@
}
}}
options={{ right: 'Disable TLS' }}
label="Disable TLS"
/>
</div>
{/if}
@@ -950,27 +966,25 @@
id="tracing_enabled"
bind:checked={$values[setting.key].tracing_enabled}
options={{ right: 'Tracing' }}
label="Tracing"
/>
<Toggle
disabled={!$enterpriseLicense}
id="logs_enabled"
bind:checked={$values[setting.key].logs_enabled}
options={{ right: 'Logs' }}
label="logs"
/>
<Toggle
disabled
id="metrics_enabled"
bind:checked={$values[setting.key].logs_enabled}
options={{ right: 'Metrics (coming soon)' }}
label="metrics"
/>
</div>
<div>
<label for="OTEL_EXPORTER_OTLP_ENDPOINT" class="block text-sm font-medium"
>Endpoint</label
<label
for="OTEL_EXPORTER_OTLP_ENDPOINT"
class="block text-xs font-semibold text-emphasis">Endpoint</label
>
<input
disabled={!$enterpriseLicense}
@@ -981,8 +995,9 @@
/>
</div>
<div>
<label for="OTEL_EXPORTER_OTLP_HEADERS" class="block text-sm font-medium"
>Headers</label
<label
for="OTEL_EXPORTER_OTLP_HEADERS"
class="block text-xs font-semibold text-emphasis">Headers</label
>
<input
disabled={!$enterpriseLicense}
@@ -993,14 +1008,15 @@
/>
</div>
<div>
<label for="OTEL_EXPORTER_OTLP_PROTOCOL" class="block text-sm font-medium"
>Protocol</label
<label
for="OTEL_EXPORTER_OTLP_PROTOCOL"
class="block text-xs font-semibold text-emphasis">Protocol</label
>
gRPC
</div>
<!-- <div>
<label for="OTEL_EXPORTER_OTLP_PROTOCOL" class="block text-sm font-medium"
>Protocol<span class="text-2xs text-tertiary ml-4"
<label for="OTEL_EXPORTER_OTLP_PROTOCOL" class="block text-sm font-semibold"
>Protocol<span class="text-2xs text-primary ml-4"
>grpc, http/protobuf, http/json</span
></label
>
@@ -1012,8 +1028,8 @@
/>
</div>
<div>
<label for="OTEL_EXPORTER_OTLP_COMPRESSION" class="block text-sm font-medium"
>Compression <span class="text-2xs text-tertiary ml-4">none, gzip</span></label
<label for="OTEL_EXPORTER_OTLP_COMPRESSION" class="block text-sm font-semibold"
>Compression <span class="text-2xs text-primary ml-4">none, gzip</span></label
>
<input
type="text"
@@ -125,9 +125,7 @@
return isValidTeamsChannel(entry.teams_channel)
}
if ('slack_channel' in entry) {
return (
typeof entry.slack_channel === 'string' && entry.slack_channel.trim() !== ''
)
return typeof entry.slack_channel === 'string' && entry.slack_channel.trim() !== ''
}
if ('email' in entry) {
return typeof entry.email === 'string' && entry.email.trim() !== ''
@@ -247,7 +245,7 @@
<!-- svelte-ignore a11y_label_has_associated_control -->
<Tabs {hideTabs} bind:selected={tab}>
{#each settingsKeys as category}
<Tab value={category}>{category}</Tab>
<Tab value={category} label={category}></Tab>
{/each}
{#snippet content()}
@@ -299,7 +297,7 @@
Enable debug mode to get more detailed logs.
</div>
{:else if category == 'Telemetry'}
<div class="text-secondary pb-4 text-xs">
<div class="text-primary pb-4 text-xs">
Anonymous usage data is collected to help improve Windmill.
<br />The following information is collected:
<ul class="list-disc list-inside pl-2">
@@ -316,15 +314,14 @@
</ul>
</div>
{#if $enterpriseLicense}
<div class="text-secondary pb-4 text-xs">
<div class="text-primary pb-4 text-xs">
On Enterprise Edition, you must send data to check that usage is in line with the
terms of the subscription. You can either enable telemetry or regularly send usage
data by clicking the button below.
</div>
<Button
on:click={sendStats}
variant="border"
color="light"
variant="default"
btnClasses="w-auto"
wrapperClasses="mb-4"
loading={sendingStats}
@@ -375,6 +372,6 @@
</div>
{#if !hideSave}
<Button on:click={saveSettings}>Save settings</Button>
<Button on:click={saveSettings} variant="accent">Save settings</Button>
<div class="pb-8"></div>
{/if}
@@ -31,26 +31,25 @@
}
</script>
<h3 class="mb-4">Add new user to instance</h3>
<h3 class="text-sm font-semibold text-emphasis">Add new user to instance</h3>
<div class="flex flex-row flex-wrap gap-2 mb-2 items-end">
<label class="block shrink min-w-0">
<span class="text-secondary text-sm">Email</span>
<span class="text-xs font-semibold text-emphasis">Email</span>
<input type="email" placeholder="email" bind:value={$globalEmailInvite} />
</label>
<label class="block shrink min-w-0">
<span class="text-secondary text-sm">Password</span>
<span class="text-xs font-semibold text-emphasis">Password</span>
<input bind:value={password} />
</label>
<div>
<span class="text-secondary text-sm">Name (optional)</span>
<span class="text-xs font-semibold text-emphasis">Name (optional)</span>
<input type="text" placeholder="name (optional)" bind:value={name} />
</div>
<Toggle class="mx-2 mb-1" bind:checked={is_super_admin} options={{ right: 'Superadmin' }} />
<div class="flex flex-row-reverse grow">
<div class="flex">
<Button
variant="contained"
color="dark"
variant="accent"
size="sm"
on:click={addUser}
disabled={$globalEmailInvite == '' || password == undefined}
@@ -61,5 +60,5 @@
</div>
</div>
<div class="flex gap-2 items-end">
<div class="text-xs text-tertiary grow text-right"> Email will be sent if SMTP configured </div>
<div class="text-2xs text-secondary grow text-right"> Email will be sent if SMTP configured </div>
</div>
@@ -67,7 +67,7 @@
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
{#snippet trigger()}
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: MailPlus }}>
<Button variant="accent" size="xs" nonCaptureEvent={true} startIcon={{ icon: MailPlus }}>
Invite
</Button>
{/snippet}
@@ -104,13 +104,7 @@
/>
{/snippet}
</ToggleButtonGroup>
<Button
variant="contained"
color="blue"
size="sm"
on:click={inviteUser}
disabled={email === undefined}
>
<Button variant="accent" size="sm" on:click={inviteUser} disabled={email === undefined}>
Invite
</Button>
</div>
@@ -92,8 +92,6 @@
autofocus
/>
<Button
color="light"
variant="border"
on:click={() => {
refreshing = true
loadItems()
@@ -114,7 +112,7 @@
<Skeleton layout={[[5], 0.2]} />
{/each}
{:else if !items?.length}
<div class="text-center text-sm text-tertiary mt-2">
<div class="text-center text-sm text-primary mt-2">
{@html noItemMessage}
</div>
{:else if filteredItems?.length}
@@ -174,7 +172,7 @@
<div>
<Button
size="sm"
variant="border"
variant="default"
on:click={() => {
button(obj['path'] ?? '')
}}
+1 -1
View File
@@ -145,7 +145,7 @@ ${Object.entries(args)
</Button>
{/snippet}
{#if jsonStr.length > 100000 || (id && workspace && args && isWindmillTooBigObject(args))}
<div class="text-sm mb-2 text-tertiary">
<div class="text-sm mb-2 text-primary">
<a
download="windmill-args.json"
href={id && workspace
@@ -25,7 +25,7 @@
value = $bindable(undefined),
error = $bindable(),
editor = $bindable(undefined),
small = false,
small = true,
loadAsync = false,
class: clazz = undefined,
disabled = false,
@@ -61,7 +61,7 @@
{#if tooBig && !loadTooBigAnyway}
<div class="flex-1 text-sm">
JSON is too big
<Button size="xs2" variant="border" on:click={() => (loadTooBigAnyway = true)}>
<Button size="xs2" variant="default" on:click={() => (loadTooBigAnyway = true)}>
Load anyway
</Button>
</div>
@@ -69,7 +69,7 @@
<div class="flex flex-col w-full">
<div
class={twMerge(
'w-full rounded-md bg-surface-secondary',
'w-full rounded-md overflow-auto',
inputBorderClass({ error: !!error, forceFocus: focused })
)}
>
@@ -87,7 +87,7 @@
{disabled}
{fixedOverflowWidgets}
renderLineHighlight="none"
yPadding={8}
yPadding={7}
/>
</div>
{#if error != ''}
+13 -6
View File
@@ -1,15 +1,17 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
import Required from './Required.svelte'
import Tooltip from './Tooltip.svelte'
interface Props {
label?: string | undefined
primary?: boolean
disabled?: boolean
headless?: boolean
required?: boolean
headerClass?: string
class?: string | undefined
for?: string | undefined
tooltip?: string | undefined
header?: import('svelte').Snippet
error?: import('svelte').Snippet
action?: import('svelte').Snippet
@@ -18,12 +20,13 @@
let {
label = undefined,
primary = false,
disabled = false,
headless = false,
required = false,
headerClass = '',
class: clazz = undefined,
for: forAttr = undefined,
tooltip = undefined,
header,
error,
action,
@@ -31,16 +34,20 @@
}: Props = $props()
</script>
<div class={twMerge(disabled ? 'opacity-60 pointer-events-none' : '', clazz)}>
<div
class={twMerge('flex flex-col gap-1', disabled ? 'opacity-60 pointer-events-none' : '', clazz)}
>
<div class="flex flex-row justify-between items-center w-full">
{#if !headless}
<div class={twMerge('flex flex-row items-center gap-2', headerClass)}>
<span
class="{primary ? 'text-primary' : 'text-secondary'} text-sm leading-6 whitespace-nowrap"
>{label}
<span class="text-emphasis text-xs font-semibold whitespace-nowrap"
><label for={forAttr}>{label}</label>
{#if required}
<Required required={true} />
{/if}
{#if tooltip}
<Tooltip>{tooltip}</Tooltip>
{/if}
</span>
{@render header?.()}
</div>
@@ -98,7 +98,7 @@
{:else}
<Drawer bind:this={drawer} size="800px">
<DrawerContent
title="Add a Resource"
title="Add a resource"
on:close={drawer.closeDrawer}
tooltip="Resources represent connections to third party systems. Learn more on how to integrate external APIs."
documentationLink="https://www.windmill.dev/docs/integrations/integrations_on_windmill"
+11 -13
View File
@@ -206,7 +206,7 @@
{/snippet}
<div>
<pre
class="bg-surface-secondary text-secondary text-xs w-full p-2 whitespace-pre-wrap border rounded-md"
class="bg-surface-secondary text-primary text-xs w-full p-2 whitespace-pre-wrap border rounded-md"
>{#if content}{@const len =
(content?.length ?? 0) +
(loadedFromObjectStore?.length ?? 0)}{#if downloadStartUrl}<button
@@ -227,7 +227,7 @@
class="w-full h-full overflow-auto bg-surface-secondary pt-4 {noMaxH ? '' : 'max-h-screen'}"
data-nav-id={navigationId}
>
<div class="absolute z-10 top-0 right-0 flex flex-row-reverse justify-between text-sm">
<div class="absolute z-10 top-0 right-0 flex flex-row-reverse justify-between text-xs">
<div class="flex gap-2">
{#if jobId && download}
<div class="flex items-center">
@@ -242,14 +242,14 @@
{/if}
<button onclick={logViewer.openDrawer}><Expand size="12" /></button>
{#if !noAutoScroll}
<div
class="{small ? '' : 'py-2'} pr-2 {small
? '!text-2xs'
: '!text-xs'} flex gap-2 text-tertiary items-center"
<label
class="{small
? ''
: 'py-2'} pr-2 text-2xs flex gap-2 font-normal text-primary items-center"
>
Auto scroll
<input class="windmillapp" type="checkbox" bind:checked={scroll} />
</div>
</label>
{/if}
</div>
</div>
@@ -258,9 +258,7 @@
<Loader2 class="animate-spin" />
{#if tag}
<div class="flex flex-row items-center gap-1">
<div class="text-secondary {small ? '!text-2xs' : '!text-xs'}"
>{tagLabel ?? 'tag'}: {tag}</div
>
<div class="text-primary text-2xs">{tagLabel ?? 'tag'}: {tag}</div>
<NoWorkerWithTagWarning {tagLabel} {tag} />
</div>
{/if}
@@ -271,7 +269,7 @@
{:else if duration}
<span
class={twMerge(
'absolute text-tertiary dark:text-gray-400',
'absolute text-primary dark:text-gray-400',
small ? '!text-2xs' : '!text-xs',
small ? 'top-0' : 'top-2',
noPadding ? '' : 'left-2'
@@ -280,14 +278,14 @@
{/if}
{#if mem}
<span
class="absolute {small ? '!text-2xs' : '!text-xs'} text-tertiary dark:text-gray-400 {small
class="absolute {small ? '!text-2xs' : '!text-xs'} text-primary dark:text-gray-400 {small
? 'top-0'
: 'top-2'} left-36">mem peak: {(mem / 1024).toPrecision(4)}MB</span
>
{/if}
<pre
class={twMerge(
'whitespace-pre break-words w-full',
'whitespace-pre break-words w-full ',
small ? '!text-2xs' : '!text-xs',
noPadding ? '' : 'p-2'
)}
@@ -12,9 +12,8 @@
<div class="flex justify-end gap-4 items-center p-2 bg-surface-secondary border-b">
<div class="flex items-center gap-2 whitespace-nowrap">
<label
for="showResultsInputs"
class="text-xs text-tertiary hover:text-primary transition-colors">Show inputs/results</label
<label for="showResultsInputs" class="text-xs text-primary hover:text-primary transition-colors"
>Show inputs/results</label
>
<div class="flex-shrink-0">
<input
@@ -28,7 +27,7 @@
</div>
<button
onclick={toggleExpandAll}
class="text-xs text-tertiary hover:text-primary transition-colors flex items-center gap-2 min-w-24 justify-end"
class="text-xs text-primary hover:text-primary transition-colors flex items-center gap-2 min-w-24 justify-end"
>
{allExpanded ? 'Collapse All' : 'Expand All'}
{#if allExpanded}

Some files were not shown because too many files have changed in this diff Show More