mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
feat: UX/UI full overhaul to meet new design system
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 ?? (() => {})}>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 === ''}
|
||||
|
||||
{: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 === ''}
|
||||
|
||||
{: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 <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) => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 <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
|
||||
|
||||
@@ -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 <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>
|
||||
|
||||
@@ -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()
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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?.()}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'] ?? '')
|
||||
}}
|
||||
|
||||
@@ -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 != ''}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user