From 8301d86800a65bb70e5b29663c7f4d6bf15b0785 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Fri, 13 Mar 2026 17:45:06 +0100 Subject: [PATCH 001/116] docs: rewrite Code Navigation section with MUST for outline/body and condensed limitations Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f77dbb0600..1e768c2142 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ Open-source platform for internal tools, workflows, API integrations, background ## Workflow -1. **Understand**: Before coding, use `wm-ts-nav` to explore (see Code Navigation below). Use `outline` to understand file structure, `body` to read specific symbols, `def`/`callers`/`callees` to trace code. Read `docs/` for domain context. +1. **Understand**: Before coding, explore the codebase (see Code Navigation below). Use `outline` to understand file structure, `body` to read specific symbols, `def`/`callers`/`callees` to trace code, `Grep` to find usages. Read `docs/` for domain context. 2. **Plan**: For non-trivial changes, use plan mode. For large features, break into reviewable stages 3. **Execute**: Follow coding patterns from skills (`rust-backend`, `svelte-frontend`) 4. **Validate**: After every change, run the appropriate checks per `docs/validation.md` @@ -52,11 +52,9 @@ let { my_prop = $bindable(default_value) }: { my_prop?: string } = $props() ## Code Navigation -`wm-ts-nav` is an AST-aware code navigator. Use **Grep** for regex/pattern search. Use **wm-ts-nav** for structural queries — it skips comments/strings and understands symbol boundaries. +`wm-ts-nav` is an AST-aware code navigator. Use **wm-ts-nav** for structural queries — it skips comments/strings and understands symbol boundaries. -**Prefer wm-ts-nav over Read** to save context window: -- `outline ` instead of reading a full file — understand structure first, then `body` or Read for specifics -- `body "X"` instead of reading a full file to see one function/struct +**MUST use `outline` before `Read`** on unfamiliar files — a 500-line file costs ~500 lines of context, while `outline` costs ~20. Then **MUST use `body "X"`** instead of reading a full file to see one function/struct. Use `Read` with offset/limit only when you need surrounding context that `body` doesn't capture. - `refs "X" --caller` instead of reading files to find which function contains each reference - `callers "X"` / `callees "X"` for call-graph questions @@ -73,20 +71,14 @@ $NAV --root backend callers "X" # who calls X? $NAV --root backend callees "X" # what does X call? ``` -**Limitations** — syntax-level analysis, no type inference: -- Import paths are stored literally — `crate::X` and `super::X` pointing to the same type won't be linked -- Re-export chains (`pub use`) aren't followed — refs through different re-export paths won't connect -- Trait methods can't be resolved to their trait definition -- Nested `use` trees (`use foo::{bar::{A, B}, baz::C}`) aren't parsed correctly -- Glob imports (`use foo::*`) — refs won't show import origin -- Macro-generated symbols (e.g. `sqlx::FromRow`) — invisible to tree-sitter -- Single-char identifiers — intentionally filtered out of refs +**Limitations** — syntax-level analysis, no type inference. Use **Grep** instead when completeness matters (finding all usages, exhaustiveness checks): +- `refs`/`callers`/`callees` can't follow re-exports, glob imports, or different import paths to the same symbol +- Trait impls, macro-generated symbols (`sqlx::FromRow`), and namespace member access (`ns.X`) are invisible - `callees` shows all identifiers in a function body, not just actual calls -- `import * as ns` namespace imports — member accesses through `ns.X` aren't resolved ## Core Principles -- **Use `outline`/`body` to explore, then `Read` with offset/limit from the results before editing** — avoid reading full files +- **MUST `outline` before `Read`** on unfamiliar files — then `body` or `Read` with offset/limit for specifics - Search for existing code to reuse before writing new code - Follow established patterns in the codebase - Keep changes focused — don't refactor beyond what's asked From 060687b1fa6b627a7b06fbdc4b3f4eb0b63411c0 Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:30:23 +0100 Subject: [PATCH 002/116] fix(cli): exclude raw app backend files from script metadata generation (#8362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Files inside .raw_app/backend/ were incorrectly being processed by `script generate-metadata` and `generate-metadata --skip-flows --skip-apps` because the filter only checked isFlowPath and isAppPath, but not isRawAppPath. This caused backend runnables to be treated as standalone scripts, creating incorrect .script.yaml files at wrong locations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 --- .../generate-metadata/generate-metadata.ts | 5 +- cli/src/commands/script/script.ts | 4 +- cli/test/raw_app_sync.test.ts | 55 ++++++++++++++++++- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/cli/src/commands/generate-metadata/generate-metadata.ts b/cli/src/commands/generate-metadata/generate-metadata.ts index 0d7435856c..ce1b1eb051 100644 --- a/cli/src/commands/generate-metadata/generate-metadata.ts +++ b/cli/src/commands/generate-metadata/generate-metadata.ts @@ -19,7 +19,7 @@ import { ignoreF, } from "../sync/sync.ts"; import { exts } from "../script/script.ts"; -import { isFlowPath, isAppPath } from "../../utils/resource_folders.ts"; +import { isFlowPath, isAppPath, isRawAppPath } from "../../utils/resource_folders.ts"; import { listSyncCodebases } from "../../utils/codebase.ts"; interface StaleItem { @@ -82,7 +82,8 @@ async function generateMetadata( (!isD && !exts.some((ext) => p.endsWith(ext))) || ignore(p, isD) || isFlowPath(p) || - isAppPath(p) + isAppPath(p) || + isRawAppPath(p) ); }, false, diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index 3a6556b34d..817b9947d7 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -58,6 +58,7 @@ import { isFlowInlineScriptPath as isFlowInlineScriptPathInternal, isFlowPath, isAppPath, + isRawAppPath, } from "../../utils/resource_folders.ts"; export interface ScriptFile { @@ -1027,7 +1028,8 @@ export async function generateMetadata( (!isD && !exts.some((ext) => p.endsWith(ext))) || ignore(p, isD) || isFlowPath(p) || - isAppPath(p) + isAppPath(p) || + isRawAppPath(p) ); }, false, diff --git a/cli/test/raw_app_sync.test.ts b/cli/test/raw_app_sync.test.ts index f77b4b045b..95d812511f 100644 --- a/cli/test/raw_app_sync.test.ts +++ b/cli/test/raw_app_sync.test.ts @@ -107,7 +107,7 @@ async function readFileContent(filePath: string): Promise { * Create a raw app directory structure on disk * Uses .raw_app folder suffix with raw_app.yaml metadata */ -async function createRawAppOnDisk(appDir: string): Promise { +async function createRawAppOnDisk(appDir: string, includeBackend: boolean = false): Promise { await mkdir(appDir, { recursive: true }); await mkdir(path.join(appDir, "inline_scripts"), { recursive: true }); @@ -131,6 +131,16 @@ async function createRawAppOnDisk(appDir: string): Promise { INLINE_SCRIPT_A_LOCK, "utf-8" ); + + // Optionally create backend runnable (type: inline) + if (includeBackend) { + await mkdir(path.join(appDir, "backend"), { recursive: true }); + await writeFile(path.join(appDir, "backend", "query.yaml"), "type: inline\n", "utf-8"); + await writeFile(path.join(appDir, "backend", "query.ts"), `export async function main(x: number): Promise { + return \`Result: \${x}\`; +} +`, "utf-8"); + } } test("Raw App: full sync workflow - push, pull, modify, push, clear, pull", async () => { @@ -153,7 +163,7 @@ excludes: []`, "utf-8"); // Create folder structure const appDir = path.join(tempDir, "f", "test", "my_raw_app.raw_app"); await mkdir(path.join(tempDir, "f", "test"), { recursive: true }); - await createRawAppOnDisk(appDir); + await createRawAppOnDisk(appDir, true); // Include backend for metadata test // ========================================================================= // STEP 1: Initial push - create raw app on backend @@ -266,6 +276,47 @@ excludes: []`, "utf-8"); const pulledInlineScript = await readFileContent(inlineScriptPath); expect(pulledInlineScript).toContain("modified:"); + + // ========================================================================= + // STEP 7: Test that script generate-metadata does NOT process backend runnables + // ========================================================================= + + // Create a standalone script (should be processed by script generate-metadata) + await writeFile(path.join(tempDir, "f", "test", "standalone.ts"), `export async function main(): Promise { + return "hello"; +} +`, "utf-8"); + + // Run script generate-metadata + const metaResult1 = await backend.runCLICommand( + ['script', 'generate-metadata', '--yes'], + tempDir, "raw_app_test" + ); + expect(metaResult1.code).toEqual(0); + + // Run generate-metadata --skip-flows --skip-apps + const metaResult2 = await backend.runCLICommand( + ['generate-metadata', '--skip-flows', '--skip-apps', '--yes'], + tempDir, "raw_app_test" + ); + expect(metaResult2.code).toEqual(0); + + // Backend runnables should NOT have .script.yaml files + const backendDir = path.join(appDir, "backend"); + expect(await fileExists(path.join(backendDir, "query.yaml"))).toBeTruthy(); + expect(await fileExists(path.join(backendDir, "query.ts"))).toBeTruthy(); + expect(await fileExists(path.join(backendDir, "query.script.yaml"))).toBeFalsy(); + expect(await fileExists(path.join(backendDir, "query.script.lock"))).toBeFalsy(); + + // Bug: raw app backend files get misprocessed and create script files at wrong location + // The path f/test/my_raw_app.raw_app/backend/query.ts gets truncated at first "." + // becoming f/test/my_raw_app.script.yaml (stripping .raw_app/backend/query.ts) + expect(await fileExists(path.join(tempDir, "f", "test", "my_raw_app.script.yaml"))).toBeFalsy(); + expect(await fileExists(path.join(tempDir, "f", "test", "my_raw_app.script.lock"))).toBeFalsy(); + + // Standalone script SHOULD have metadata + expect(await fileExists(path.join(tempDir, "f", "test", "standalone.script.yaml"))).toBeTruthy(); + expect(await fileExists(path.join(tempDir, "f", "test", "standalone.script.lock"))).toBeTruthy(); }); }); From 0d31c35f3e12d637c757a95fe350294002cbf640 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Fri, 13 Mar 2026 21:31:06 +0100 Subject: [PATCH 003/116] fix(frontend): filter webhook/email tokens by scope instead of label (#8363) The backend already filters tokens by scope matching the script/flow path. Remove the redundant client-side label prefix filter so that all tokens with matching scopes are shown, not just those with a specific label convention. Co-authored-by: Claude Opus 4.6 --- .../components/triggers/TriggerTokens.svelte | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/components/triggers/TriggerTokens.svelte b/frontend/src/lib/components/triggers/TriggerTokens.svelte index 4cf1a5d027..11d0bfca74 100644 --- a/frontend/src/lib/components/triggers/TriggerTokens.svelte +++ b/frontend/src/lib/components/triggers/TriggerTokens.svelte @@ -1,5 +1,5 @@
remove - {/each} + + {/each} - {/snippet} + {/snippet} {:else}
- {#each new Array(6) as _} + {#each new Array(6) as _, i (i)} {/each}
diff --git a/frontend/src/lib/components/SuperadminSettingsInner.svelte b/frontend/src/lib/components/SuperadminSettingsInner.svelte index 37fca0037b..964414cafa 100644 --- a/frontend/src/lib/components/SuperadminSettingsInner.svelte +++ b/frontend/src/lib/components/SuperadminSettingsInner.svelte @@ -343,7 +343,7 @@ {#if filteredUsers && users} - {#each filteredUsers.slice(0, nbDisplayed) as { email, super_admin, devops, login_type, name, username, operator_only }, i (email)} + {#each filteredUsers.slice(0, nbDisplayed) as { email, super_admin, devops, login_type, name, username, operator_only, role_source }, i (email)} {/if} - { - if (email == $userStore?.email) { - sendUserToast('You cannot demote yourself', true) - listUsers(activeOnly) - return - } +
diff --git a/frontend/src/routes/(root)/(logged)/groups/+page.svelte b/frontend/src/routes/(root)/(logged)/groups/+page.svelte index 69b5b83c13..0668c4c307 100644 --- a/frontend/src/routes/(root)/(logged)/groups/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/groups/+page.svelte @@ -8,6 +8,7 @@ import Popover from '$lib/components/meltComponents/Popover.svelte' import Dropdown from '$lib/components/DropdownV2.svelte' import GroupEditor from '$lib/components/GroupEditor.svelte' + import InstanceGroupEditor from '$lib/components/InstanceGroupEditor.svelte' import GroupInfo from '$lib/components/GroupInfo.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -69,6 +70,8 @@ }) let editGroupName: string = $state('') + let instanceGroupDrawer: Drawer | undefined = $state() + let editInstanceGroupName: string = $state('') @@ -77,6 +80,15 @@ + + + + + + {#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.groups} {/if} + {#if node.workflow_as_code_status} +
+
Workflow timeline
+ +
+ {/if}
| null ): Promise { return abstractRun( () => @@ -310,7 +311,8 @@ tag, lock, script_hash: hash, - flow_path: flowPath + flow_path: flowPath, + modules: modules ?? undefined } }), callbacks diff --git a/frontend/src/lib/components/NoMainFuncBadge.svelte b/frontend/src/lib/components/NoMainFuncBadge.svelte index 3e2a2d28bb..c72e05583c 100644 --- a/frontend/src/lib/components/NoMainFuncBadge.svelte +++ b/frontend/src/lib/components/NoMainFuncBadge.svelte @@ -5,7 +5,7 @@ {#snippet text()} - The script has no main function exported + Library script (no exported main function) {/snippet} - No main + Library diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 7de0926f0a..eb8709751c 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -107,6 +107,22 @@ import { isRuleActive } from '$lib/workspaceProtectionRules.svelte' import { buildForkEditUrl } from '$lib/utils/editInFork' import OnBehalfOfSelector, { type OnBehalfOfChoice } from './OnBehalfOfSelector.svelte' + import WacExportDrawer from './scripts/WacExportDrawer.svelte' + import Modal from './common/modal/Modal.svelte' + + const WAC_ALPHA_ACK_KEY = 'windmill_wac_alpha_ack' + let wacAlphaModalOpen = $state(false) + + function showWacAlphaModalIfNeeded() { + if (typeof sessionStorage !== 'undefined' && sessionStorage.getItem(WAC_ALPHA_ACK_KEY) !== 'true') { + wacAlphaModalOpen = true + } + } + + function acknowledgeWacAlpha() { + sessionStorage.setItem(WAC_ALPHA_ACK_KEY, 'true') + wacAlphaModalOpen = false + } let { script = $bindable(), @@ -182,6 +198,7 @@ let editor: Editor | undefined = $state(undefined) let scriptEditor: ScriptEditor | undefined = $state(undefined) let captureTable: CaptureTable | undefined = $state(undefined) + let wacExportDrawer: WacExportDrawer | undefined = $state(undefined) // Draft triggers confirmation modal let draftTriggersModalOpen = $state(false) @@ -362,6 +379,13 @@ } if (script.content == '') { + if (template === 'wac_python') { + script.modules = { 'helper.py': { content: 'def main(a: str) -> str:\n return f"hello {a}"\n', language: 'python3' } } + showWacAlphaModalIfNeeded() + } else if (template === 'wac_typescript') { + script.modules = { 'helper.ts': { content: 'export function main(a: string): string {\n return `hello ${a}`\n}\n', language: 'bun' } } + showWacAlphaModalIfNeeded() + } initContent(script.language, script.kind, template) } @@ -388,7 +412,7 @@ async function initContent( language: SupportedLanguage, kind: Script['kind'] | undefined, - template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' | 'claudesandbox' + template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' | 'claudesandbox' | 'wac_python' | 'wac_typescript' ) { scriptEditor?.disableCollaboration() const templateScript = await isTemplateScript() @@ -403,6 +427,7 @@ } async function handleEditScript(stay: boolean, deployMsg?: string): Promise { + scriptEditor?.flushModuleState() // Fetch latest version and fetch entire script after if needed let actual_parent_hash: string | undefined = undefined @@ -510,10 +535,10 @@ script.kind === 'preprocessor' ? 'preprocessor' : undefined ) if (script.kind === 'preprocessor') { - script.no_main_func = undefined + script.auto_kind = undefined script.has_preprocessor = undefined } else { - script.no_main_func = result?.no_main_func || undefined + script.auto_kind = result?.auto_kind || undefined script.has_preprocessor = result?.has_preprocessor || undefined } } catch (error) { @@ -554,12 +579,13 @@ timeout: script.timeout, concurrency_key: emptyString(script.concurrency_key) ? undefined : script.concurrency_key, visible_to_runner_only: script.visible_to_runner_only, - no_main_func: script.no_main_func, + auto_kind: script.auto_kind, has_preprocessor: script.has_preprocessor, deployment_message: deploymentMsg || undefined, on_behalf_of_email: script.on_behalf_of_email, preserve_on_behalf_of: preserveOnBehalfOf || undefined, - assets: script.assets + assets: script.assets, + modules: script.modules } }) @@ -592,7 +618,7 @@ if (!disableHistoryChange) { history.replaceState(history.state, '', `/scripts/edit/${script.path}`) } - if (stay || (script.no_main_func && script.kind !== 'preprocessor' && !isWorkflowAsCode(script.content, script.language))) { + if (stay || (script.auto_kind === 'lib' && script.kind !== 'preprocessor' && !isWorkflowAsCode(script.content, script.language))) { script.parent_hash = newHash sendUserToast('Deployed') } else { @@ -606,6 +632,7 @@ } async function saveDraft(forceSave = false): Promise { + scriptEditor?.flushModuleState() if (initialPath != '' && !savedScript) { return } @@ -643,10 +670,10 @@ script.kind === 'preprocessor' ? 'preprocessor' : undefined ) if (script.kind === 'preprocessor') { - script.no_main_func = undefined + script.auto_kind = undefined script.has_preprocessor = undefined } else { - script.no_main_func = result?.no_main_func || undefined + script.auto_kind = result?.auto_kind || undefined script.has_preprocessor = result?.has_preprocessor || undefined } } catch (error) { @@ -707,10 +734,11 @@ ? undefined : script.concurrency_key, visible_to_runner_only: script.visible_to_runner_only, - no_main_func: script.no_main_func, + auto_kind: script.auto_kind, has_preprocessor: script.has_preprocessor, on_behalf_of_email: script.on_behalf_of_email, - assets: script.assets + assets: script.assets, + modules: script.modules } }) } @@ -816,7 +844,7 @@ } ] : []), - ...(!script.draft_only && script.kind === 'script' && !script.no_main_func + ...(!script.draft_only && script.kind === 'script' && !script.auto_kind ? [ { label: 'Exit & See details', @@ -825,10 +853,34 @@ } } ] + : []), + ...(isWorkflowAsCode(script.content, script.language) + ? [ + { + label: 'Export as YAML/JSON', + onClick: () => { + wacExportDrawer?.open(script) + } + } + ] : []) ] : [] + if ( + dropdownItems.length === 0 && + isWorkflowAsCode(script.content, script.language) + ) { + dropdownItems = [ + { + label: 'Export as YAML/JSON', + onClick: () => { + wacExportDrawer?.open(script) + } + } + ] + } + return dropdownItems.length > 0 ? dropdownItems : undefined } @@ -1201,7 +1253,7 @@ {/if} -
+
Template + + + + + +
{#if customUi?.settingsPanel?.metadata?.disableScriptKind !== true}
@@ -1948,9 +2040,23 @@ bind:hasPreprocessor bind:captureTable bind:assets={script.assets} + bind:modules={script.modules} enablePreprocessorSnippet />
{:else} Script Builder not available to operators {/if} + + + + +
+ diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index f310010c18..600dfde5af 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -2,7 +2,14 @@ import { BROWSER } from 'esm-env' import type { Schema, SupportedLanguage } from '$lib/common' - import { type CompletedJob, type Job, JobService, type Preview, type ScriptLang } from '$lib/gen' + import { + type CompletedJob, + type Job, + JobService, + type Preview, + type ScriptLang, + type ScriptModule + } from '$lib/gen' import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores' import { copyToClipboard, @@ -25,8 +32,10 @@ import WindmillIcon from './icons/WindmillIcon.svelte' import * as Y from 'yjs' import { scriptLangToEditorLang } from '$lib/scripts' + import { langToExt } from '$lib/editorLangUtils' import { WebsocketProvider } from 'y-websocket' import Modal from './common/modal/Modal.svelte' + import Popover from './meltComponents/Popover.svelte' import DiffEditor from './DiffEditor.svelte' import { AlertTriangle, @@ -40,8 +49,11 @@ GitBranch, Play, PlayIcon, + Plus, Terminal, - WandSparkles + Pencil, + WandSparkles, + X } from 'lucide-svelte' import { DebugToolbar, @@ -100,7 +112,16 @@ path: string | undefined lang: Preview['language'] kind?: string | undefined - template?: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' | 'claudesandbox' + template?: + | 'pgsql' + | 'mysql' + | 'script' + | 'docker' + | 'powershell' + | 'bunnative' + | 'claudesandbox' + | 'wac_python' + | 'wac_typescript' tag: string | undefined initialArgs?: Record fixedOverflowWidgets?: boolean @@ -123,6 +144,7 @@ lastDeployedCode?: string | undefined disableAi?: boolean assets?: AssetWithAltAccessType[] + modules?: { [key: string]: ScriptModule } | null editorBarRight?: import('svelte').Snippet enablePreprocessorSnippet?: boolean } @@ -155,6 +177,7 @@ lastDeployedCode = undefined, disableAi = false, assets = $bindable(), + modules = $bindable(undefined), editorBarRight, enablePreprocessorSnippet = false }: Props = $props() @@ -163,6 +186,267 @@ let jsonView = $state(false) let schemaHeight = $state(0) + // Module tab state + let activeModuleTab: string | null = $state(null) + // editorCode is what the editor shows; code always holds the main script content + let editorCode: string = $state(code) + // Sync editorCode when code changes externally (template reset, copilot, etc.) + let lastSyncedCode = code + $effect.pre(() => { + if (activeModuleTab === null && code !== lastSyncedCode) { + editorCode = code + lastSyncedCode = code + } + }) + + function switchToModule(modulePath: string) { + if (activeModuleTab !== null && modules && activeModuleTab !== modulePath) { + // Switching from another module: save its content + modules[activeModuleTab] = { ...modules[activeModuleTab], content: editorCode } + } + if (modules && modules[modulePath]) { + activeModuleTab = modulePath + editorCode = modules[modulePath].content + editor?.setCode(editorCode) + } + } + + function switchToMain() { + if (activeModuleTab !== null && modules) { + // Save current module content + modules[activeModuleTab] = { ...modules[activeModuleTab], content: editorCode } + } + activeModuleTab = null + editorCode = code + lastSyncedCode = code + editor?.setCode(editorCode) + } + + let effectiveLang = $derived( + activeModuleTab && modules?.[activeModuleTab] + ? (modules[activeModuleTab].language as Preview['language']) + : lang + ) + + let isWacV2 = $derived.by(() => { + const mainCode = code + const isTsWac = + mainCode.includes('windmill-client') && + mainCode.includes('workflow') && + mainCode.includes('task') + const isPyWac = + (mainCode.includes('import wmill') || mainCode.includes('from wmill')) && + mainCode.includes('workflow') && + mainCode.includes('task') + return isTsWac || isPyWac + }) + let supportsModules = $derived((lang === 'bun' || lang === 'python3') && isWacV2) + let mainFileName = $derived('script.' + langToExt(scriptLangToEditorLang(lang))) + + let modulePathInput = $state('') + let showAddModulePopover = $state(false) + let modulePathInputEl: HTMLInputElement | undefined = $state(undefined) + let modulePathError = $state('') + + let renameModuleInput = $state('') + let renameModuleError = $state('') + let renameModuleInputEl: HTMLInputElement | undefined = $state(undefined) + + const ALL_MODULE_EXTENSIONS: Record = { + '.ts': 'bun', + '.py': 'python3', + '.go': 'go', + '.sh': 'bash', + '.ps1': 'powershell', + '.sql': 'postgresql', + '.gql': 'graphql', + '.php': 'php', + '.rs': 'rust', + '.yml': 'ansible', + '.cs': 'csharp', + '.nu': 'nu', + '.java': 'java', + '.rb': 'ruby' + } + + /** Map main script language to allowed module file extensions. */ + const LANG_MODULE_EXTENSIONS: Partial> = { + python3: ['.py'], + bun: ['.ts'], + deno: ['.ts'], + nativets: ['.ts'], + go: ['.go'], + bash: ['.sh'], + powershell: ['.ps1'], + postgresql: ['.sql'], + mysql: ['.sql'], + bigquery: ['.sql'], + snowflake: ['.sql'], + mssql: ['.sql'], + oracledb: ['.sql'], + duckdb: ['.sql'], + graphql: ['.gql'], + php: ['.php'], + rust: ['.rs'], + ansible: ['.yml'], + csharp: ['.cs'], + nu: ['.nu'], + java: ['.java'], + ruby: ['.rb'], + bunnative: ['.ts'] + } + + let allowedModuleExtensions = $derived( + lang + ? (LANG_MODULE_EXTENSIONS[lang] ?? Object.keys(ALL_MODULE_EXTENSIONS)) + : Object.keys(ALL_MODULE_EXTENSIONS) + ) + + function inferModuleLang(filePath: string): ScriptModule['language'] | undefined { + for (const [ext, moduleLang] of Object.entries(ALL_MODULE_EXTENSIONS)) { + if (filePath.endsWith(ext)) return moduleLang + } + return undefined + } + + function getModuleDefaultContent(filePath: string): string { + if (filePath.endsWith('.py')) { + return `def hello() -> str:\n return "world"\n` + } else if (filePath.endsWith('.ts')) { + return `export function hello(): string {\n return "world"\n}\n` + } else if (filePath.endsWith('.go')) { + return `package inner\n\nfunc Hello() string {\n\treturn "world"\n}\n` + } else if (filePath.endsWith('.sh')) { + return `#!/bin/bash\necho "world"\n` + } else if (filePath.endsWith('.ps1')) { + return `function Hello {\n return "world"\n}\n` + } else if (filePath.endsWith('.sql')) { + return `SELECT 'world' as result;\n` + } else if (filePath.endsWith('.gql')) { + return `query Hello {\n hello\n}\n` + } else if (filePath.endsWith('.php')) { + return ` String {\n "world".to_string()\n}\n` + } else if (filePath.endsWith('.yml')) { + return `---\n- name: Hello\n debug:\n msg: "world"\n` + } else if (filePath.endsWith('.cs')) { + return `public static string Hello() {\n return "world";\n}\n` + } else if (filePath.endsWith('.nu')) { + return `def hello [] {\n "world"\n}\n` + } else if (filePath.endsWith('.java')) { + return `public class Helper {\n public static String hello() {\n return "world";\n }\n}\n` + } else if (filePath.endsWith('.rb')) { + return `def hello\n "world"\nend\n` + } + return '' + } + + function validateModulePath(path: string): string { + if (!path.trim()) return '' + const moduleLang = inferModuleLang(path) + if (!moduleLang) { + const exts = allowedModuleExtensions.join(', ') + return `File must end with a supported extension: ${exts}` + } + const matchedExt = allowedModuleExtensions.find((ext) => path.endsWith(ext)) + if (!matchedExt) { + const exts = allowedModuleExtensions.join(', ') + return `File must end with a supported extension for this language: ${exts}` + } + if (modules?.[path.trim()]) { + return `Module ${path.trim()} already exists` + } + return '' + } + + function addModule() { + const modulePath = modulePathInput.trim() + if (!modulePath) return + const error = validateModulePath(modulePath) + if (error) { + modulePathError = error + return + } + if (!modules) { + modules = {} + } + modules[modulePath] = { + content: getModuleDefaultContent(modulePath), + language: inferModuleLang(modulePath)! + } + modulePathInput = '' + modulePathError = '' + showAddModulePopover = false + switchToModule(modulePath) + } + + function removeModule(modulePath: string) { + if (!modules) return + if (activeModuleTab === modulePath) { + switchToMain() + } + delete modules[modulePath] + modules = { ...modules } + } + + function validateRenameModulePath(newPath: string, oldPath: string): string { + if (!newPath.trim()) return '' + const moduleLang = inferModuleLang(newPath) + if (!moduleLang) { + const exts = allowedModuleExtensions.join(', ') + return `File must end with a supported extension: ${exts}` + } + const matchedExt = allowedModuleExtensions.find((ext) => newPath.endsWith(ext)) + if (!matchedExt) { + const exts = allowedModuleExtensions.join(', ') + return `File must end with a supported extension for this language: ${exts}` + } + if (newPath.trim() !== oldPath && modules?.[newPath.trim()]) { + return `Module ${newPath.trim()} already exists` + } + return '' + } + + function renameModule(oldPath: string) { + const newPath = renameModuleInput.trim() + if (!newPath || newPath === oldPath) { + return + } + const error = validateRenameModulePath(newPath, oldPath) + if (error) { + renameModuleError = error + return + } + if (!modules) return + const mod = modules[oldPath] + const newLang = inferModuleLang(newPath) + delete modules[oldPath] + modules[newPath] = { ...mod, language: newLang ?? mod.language } + modules = { ...modules } + if (activeModuleTab === oldPath) { + activeModuleTab = newPath + } + renameModuleInput = '' + renameModuleError = '' + } + + /** Save the active module tab's editor content back into the modules map (no UI side-effects). */ + function flushModuleContent() { + if (activeModuleTab !== null && modules) { + modules[activeModuleTab] = { ...modules[activeModuleTab], content: editorCode } + } + } + + /** Flush module content and reset the editor back to the main script tab. */ + export function flushModuleState() { + if (activeModuleTab !== null && modules) { + flushModuleContent() + activeModuleTab = null + editorCode = code + } + } + $effect.pre(() => { if (schema == undefined) { schema = emptySchema() @@ -329,6 +613,8 @@ export async function runTest() { // Not defined if JobProgressBar not loaded jobProgressBar?.reset() + // Flush module edits back to modules map before running preview + flushModuleContent() //@ts-ignore let job = await jobLoader.runPreview( path, @@ -355,7 +641,9 @@ } console.error(error) } - } + }, + undefined, + modules ) logPanel?.setFocusToLogs() return job @@ -410,8 +698,7 @@ selectedTab = 'main' } else { hasPreprocessor = - (selectedTab === 'preprocessor' ? !result?.no_main_func : result?.has_preprocessor) ?? - false + (selectedTab === 'preprocessor' ? !result?.auto_kind : result?.has_preprocessor) ?? false if (!hasPreprocessor && selectedTab === 'preprocessor') { selectedTab = 'main' @@ -1316,140 +1603,333 @@ +{#snippet addModuleForm(close: () => void)} +
+ + { + modulePathError = validateModulePath(modulePathInput) + }} + onkeydown={(e) => { + if (e.key === 'Enter') addModule() + if (e.key === 'Escape') close() + }} + /> + {#if modulePathError} +

{modulePathError}

+ {/if} +

Supports subfolders, e.g. utils/math{allowedModuleExtensions[0] ?? '.ts'}

+
+ + +
+
+{/snippet} + +{#snippet renameModuleForm(oldPath: string, close: () => void)} +
+ + { + renameModuleError = validateRenameModulePath(renameModuleInput, oldPath) + }} + onkeydown={(e) => { + if (e.key === 'Enter') { + renameModule(oldPath) + close() + } + if (e.key === 'Escape') close() + }} + /> + {#if renameModuleError} +

{renameModuleError}

+ {/if} +
+ + +
+
+{/snippet} + {#snippet editorContent()} -
-
- {#if assets?.length} - - {/if} - {#if isDebuggableScript && customUi?.editorBar?.debug != false} - - {/if} - {#if showDebugPanel && !showDebugConsole} - + {#each Object.keys(modules ?? {}) as modulePath} +
+ +
+ + {#snippet trigger()} + { + e.stopPropagation() + renameModuleInput = modulePath + renameModuleError = '' + }} + onkeydown={(e) => { + if (e.key === 'Enter') { + e.stopPropagation() + renameModuleInput = modulePath + renameModuleError = '' + } + }} + > + + + {/snippet} + {#snippet content({ close })} + {@render renameModuleForm(modulePath, close)} + {/snippet} + + { + e.stopPropagation() + removeModule(modulePath) + }} + onkeydown={(e) => { + if (e.key === 'Enter') { + e.stopPropagation() + removeModule(modulePath) + } + }} + > + + +
+
+ {/each} + - Console - - {/if} - {#if lang === 'ansible' && hasDelegateToGitRepo} - - {/if} - {#if testPanelSize === 0} - +
+ {/if} +
+
+ {#if assets?.length} + + {/if} + {#if isDebuggableScript && customUi?.editorBar?.debug != false} + + {/if} + {#if showDebugPanel && !showDebugConsole} + + {/if} + {#if lang === 'ansible' && hasDelegateToGitRepo} + + {/if} + {#if testPanelSize === 0} + btnClasses="bg-marine-400 hover:bg-marine-200 !text-primary-inverse hover:!text-primary-inverse hover:dark:!text-primary-inverse dark:bg-marine-50 dark:hover:bg-marine-50/70" + color="marine" + /> {/if} + {#if !aiChatManager.open && !disableAi} + {#if customUi?.editorBar?.aiGen != false && SUPPORTED_CHAT_SCRIPT_LANGUAGES.includes(lang ?? '')} + + {/if} + {/if} +
+ + {#if debugConsoleVisible} + + + + {@render editorPane()} + + + (showDebugConsole = false)} + workspace={$workspaceStore} + jobId={debugSessionJobId ?? undefined} + /> + + + {:else} + +
+ {@render editorPane()} +
{/if}
- - {#if debugConsoleVisible} - - - - {@render editorPane()} - - - (showDebugConsole = false)} - workspace={$workspaceStore} - jobId={debugSessionJobId ?? undefined} - /> - - - {:else} - -
- {@render editorPane()} -
- {/if}
{/snippet} {#snippet editorPane()} - {#key lang} + {#key effectiveLang} { - inferSchema(e.detail) + if (activeModuleTab === null) { + code = editorCode + lastSyncedCode = code + inferSchema(e.detail) + } else { + flushModuleContent() + } // Refresh breakpoint positions when code changes (decorations track their lines) if (debugMode && breakpointDecorations.length > 0) { refreshBreakpointPositions() @@ -1458,20 +1938,24 @@ on:saveDraft on:toggleTestPanel={toggleTestPanel} cmdEnterAction={async () => { - await inferSchema(code) + if (activeModuleTab === null) { + await inferSchema(editorCode) + } runTest() }} formatAction={async () => { - await inferSchema(code) + if (activeModuleTab === null) { + await inferSchema(editorCode) + } try { - localStorage.setItem(path ?? 'last_save', code) + localStorage.setItem(path ?? 'last_save', activeModuleTab === null ? editorCode : code) } catch (e) { console.error('Could not save last_save to local storage', e) } dispatch('format') }} class="flex flex-1 h-full !overflow-visible" - scriptLang={lang} + scriptLang={effectiveLang} automaticLayout={true} {fixedOverflowWidgets} {args} diff --git a/frontend/src/lib/components/WorkflowTimeline.svelte b/frontend/src/lib/components/WorkflowTimeline.svelte index 66fbb5b6c5..d71a4bc015 100644 --- a/frontend/src/lib/components/WorkflowTimeline.svelte +++ b/frontend/src/lib/components/WorkflowTimeline.svelte @@ -3,17 +3,33 @@ import { displayDate, msToSec } from '$lib/utils' import { onDestroy } from 'svelte' import { getDbClockNow } from '$lib/forLater' - import { Loader2 } from 'lucide-svelte' + import { ChevronDown, ChevronRight, Loader2 } from 'lucide-svelte' import TimelineBar from './TimelineBar.svelte' - import type { WorkflowStatus } from '$lib/gen' + import LogViewer from './LogViewer.svelte' + import ObjectViewer from './propertyPicker/ObjectViewer.svelte' + import { CheckCircle2, XCircle } from 'lucide-svelte' + import { JobService, type Job, type WorkflowStatus } from '$lib/gen' + import { workspaceStore } from '$lib/stores' interface Props { - flow_status: Record; - flowDone?: boolean; + flow_status: Record + flowDone?: boolean + stepResults?: Record + result?: any + success?: boolean + autoExpandResult?: boolean } - let { flow_status, flowDone = false }: Props = $props(); + let { flow_status, flowDone = false, stepResults = {}, result = undefined, success = true, autoExpandResult = false }: Props = $props() + let resultExpanded = $state(false) + + // Auto-expand result row when job completes (only if requested) + $effect(() => { + if (autoExpandResult && flowDone && result !== undefined) { + resultExpanded = true + } + }) let now = $state(getDbClockNow().getTime()) @@ -25,35 +41,94 @@ onDestroy(() => { interval && clearInterval(interval) + pollInterval && clearInterval(pollInterval) }) - let min = $derived(Object.values(flow_status).reduce( - (a, b) => Math.min(a, b.scheduled_for ? new Date(b.scheduled_for).getTime() : Infinity), - Infinity - )) - let max = $derived(flowDone - ? Object.values(flow_status).reduce( - (a, b) => - Math.max(a, b.started_at ? new Date(b.started_at).getTime() + (b.duration_ms ?? 0) : 0), - 0 - ) - : undefined) + + let min = $derived( + Object.values(flow_status).reduce( + (a, b) => Math.min(a, b.scheduled_for ? new Date(b.scheduled_for).getTime() : Infinity), + Infinity + ) + ) + let max = $derived( + flowDone + ? Object.values(flow_status).reduce( + (a, b) => + Math.max( + a, + b.started_at ? new Date(b.started_at).getTime() + (b.duration_ms ?? 0) : 0 + ), + 0 + ) + : undefined + ) let total = $derived(flowDone && max ? max - min : Math.max(now - min, 2000)) + + // Collapsible state + let expandedRows: Record = $state({}) + let childJobs: Record = $state({}) + let loadingJobs: Record = $state({}) + + function isStep(key: string): boolean { + return key.startsWith('_step/') + } + + function stepKey(key: string): string { + return key.slice('_step/'.length) + } + + function toggleRow(id: string) { + expandedRows[id] = !expandedRows[id] + if (expandedRows[id] && !isStep(id) && !childJobs[id]) { + fetchChildJob(id) + } + } + + async function fetchChildJob(id: string) { + const ws = $workspaceStore + if (!ws) return + loadingJobs[id] = true + try { + const job = await JobService.getJob({ workspace: ws, id }) + childJobs[id] = job as Job & { result?: any } + } catch (e) { + console.error(`Failed to fetch job ${id}:`, e) + } finally { + loadingJobs[id] = false + } + } + + // Poll for updates on expanded in-progress jobs + let pollInterval = setInterval(() => { + for (const [id, v] of Object.entries(flow_status)) { + if (isStep(id)) continue + const isRunning = v.duration_ms == undefined && v.started_at != undefined + if (expandedRows[id] && isRunning) { + fetchChildJob(id) + } + } + }, 2000) {#if flow_status}
-
-
{min ? displayDate(new Date(min), true) : ''}
{#if max && min} - {/if}
{max ? displayDate(new Date(max), true) : ''}{#if !max && min}{#if now} +
+
+
+
{min ? displayDate(new Date(min), true) : ''}
+ {#if max && min} + + {/if} +
+ {max ? displayDate(new Date(max), true) : ''} + {#if !max && min} + {#if now} {msToSec(now - min, 3)}s - {/if}{/if}
+ {/if} + + {/if} +
+
@@ -61,26 +136,57 @@
Waiting for executor
-
Execution
- {#each Object.entries(flow_status) as [k, v] (k)} -
-
- {v.name ?? k} -
+ {#each Object.entries(flow_status).sort(([, a], [, b]) => { + const ta = new Date(a.started_at ?? a.scheduled_for ?? 0).getTime() + const tb = new Date(b.started_at ?? b.scheduled_for ?? 0).getTime() + return ta - tb + }) as [k, v] (k)} + {@const isInlineStep = isStep(k)} + {@const isRunning = v.duration_ms == undefined && v.started_at != undefined} + {@const isDone = v.duration_ms != undefined} + {@const isExpanded = expandedRows[k] ?? false} +
+
+ {/if} +
+ + + {#if isExpanded} +
+ {#if isInlineStep} + + {@const result = stepResults[stepKey(k)]} + {#if isDone && result !== undefined} +
+
Result
+
+ +
+
+ {:else} +
Step completed (no result)
+ {/if} + {:else if loadingJobs[k] && !childJobs[k]} +
+ + Loading... +
+ {:else if childJobs[k]} + {@const job = childJobs[k]} + + {#if job.logs || isRunning} +
+
Logs
+ +
+ {/if} + + + {#if isDone && job.result !== undefined} +
+
Result
+
+ +
+
+ {/if} + {:else} +
No data available
+ {/if} +
+ {/if}
{/each} + {#if flowDone && result !== undefined} +
+ + {#if resultExpanded} +
+
+ +
+
+ {/if} +
+ {/if}
{:else} diff --git a/frontend/src/lib/components/common/table/ScriptRow.svelte b/frontend/src/lib/components/common/table/ScriptRow.svelte index c98e326067..91a2bd3b3b 100644 --- a/frontend/src/lib/components/common/table/ScriptRow.svelte +++ b/frontend/src/lib/components/common/table/ScriptRow.svelte @@ -35,11 +35,14 @@ Share, Trash, History, - Globe2 + Globe2, + FileText } from 'lucide-svelte' import ScriptVersionHistory from '$lib/components/ScriptVersionHistory.svelte' + import WacExportDrawer from '$lib/components/scripts/WacExportDrawer.svelte' import { Drawer, DrawerContent } from '..' import NoMainFuncBadge from '$lib/components/NoMainFuncBadge.svelte' + import Popover from '$lib/components/Popover.svelte' import Tooltip from '$lib/components/Tooltip.svelte' import { getDeployUiSettings } from '$lib/components/home/deploy_ui' import { scriptToHubUrl } from '$lib/hub' @@ -106,6 +109,7 @@ const dlt: 'delete' = 'delete' let versionsDrawerOpen: boolean = $state(false) + let wacExportDrawer: WacExportDrawer | undefined = $state(undefined) {#if menuOpen} @@ -115,7 +119,7 @@ Archived {/if} - {#if script.no_main_func && script.kind !== 'preprocessor'} + {#if script.auto_kind === 'lib' && script.kind !== 'preprocessor'} {/if} + {#if script.auto_kind === 'wac'} + + {#snippet text()} + Workflow-as-Code + {/snippet} + wac + + {/if} {#if script.kind !== 'script'} {script.kind === 'failure' ? 'Error handler' : capitalize(script.kind)} { + const fullScript = await ScriptService.getScriptByPath({ + workspace: $workspaceStore!, + path: script.path + }) + wacExportDrawer?.open(fullScript) + } + } + ] + : []), { displayName: 'Duplicate/Fork', icon: GitFork, @@ -412,3 +439,5 @@ {/if} + + diff --git a/frontend/src/lib/components/flows/CreateActionsFlow.svelte b/frontend/src/lib/components/flows/CreateActionsFlow.svelte index f2080fb281..73f8cfedba 100644 --- a/frontend/src/lib/components/flows/CreateActionsFlow.svelte +++ b/frontend/src/lib/components/flows/CreateActionsFlow.svelte @@ -7,11 +7,28 @@ import Drawer from '$lib/components/common/drawer/Drawer.svelte' import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' import { importFlowStore } from '$lib/components/flows/flowStore.svelte' - import { Loader2, Plus } from 'lucide-svelte' + import { importScriptStore } from '$lib/components/scripts/scriptStore.svelte' + import Modal from '$lib/components/common/modal/Modal.svelte' + import Toggle from '$lib/components/Toggle.svelte' + import Tabs from '$lib/components/common/tabs/Tabs.svelte' + import Tab from '$lib/components/common/tabs/Tab.svelte' + import { PythonIcon, TypeScriptIcon } from '$lib/components/common/languageIcons' + import { Code2, Loader2, Plus } from 'lucide-svelte' import YAML from 'yaml' + + const SKIP_FLOW_MODAL_KEY = 'windmill_skip_flow_modal' + let drawer: Drawer | undefined = $state(undefined) + let wacDrawer: Drawer | undefined = $state(undefined) let pendingRaw: string | undefined = $state(undefined) + let pendingWacRaw: string | undefined = $state(undefined) let importType: 'yaml' | 'json' = $state('yaml') + let wacImportType: 'yaml' | 'json' = $state('yaml') + let flowModalOpen = $state(false) + let wacHovered = $state(false) + let skipModal = $state( + typeof localStorage !== 'undefined' && localStorage.getItem(SKIP_FLOW_MODAL_KEY) === 'true' + ) async function importRaw() { $importFlowStore = @@ -19,58 +36,229 @@ await goto('/flows/add') drawer?.closeDrawer?.() } + + async function importWacRaw() { + const parsed = + wacImportType === 'yaml' ? YAML.parse(pendingWacRaw ?? '') : JSON.parse(pendingWacRaw ?? '') + $importScriptStore = parsed + await goto(`${base}/scripts/add?import=true`) + wacDrawer?.closeDrawer?.() + } + + function handleFlowClick() { + if (skipModal) { + goto(`${base}/flows/add?nodraft=true`) + } else { + flowModalOpen = true + } + } + + function selectFlowEditor() { + flowModalOpen = false + goto(`${base}/flows/add?nodraft=true`) + } + + function selectWacPython() { + flowModalOpen = false + goto(`${base}/scripts/add?nodraft=true&wac=python`) + } + + function selectWacTypescript() { + flowModalOpen = false + goto(`${base}/scripts/add?nodraft=true&wac=typescript`) + } + + function toggleSkipModal() { + skipModal = !skipModal + localStorage.setItem(SKIP_FLOW_MODAL_KEY, String(skipModal)) + }
- -
- - - - drawer?.toggleDrawer?.()} + }, + { + label: 'Workflow-as-Code in TypeScript', + onClick: () => selectWacTypescript() + }, + { + label: 'Workflow-as-Code in Python', + onClick: () => selectWacPython() + }, + { + label: 'Import Workflow-as-Code', + onClick: () => { + wacDrawer?.toggleDrawer?.() + } + } + ]} > - {#await import('$lib/components/SimpleEditor.svelte')} - - {:then Module} - - {/await} + Flow + +
+ + + +
+
+ + + + + +
(wacHovered = true)} + onmouseleave={() => (wacHovered = false)} + > + +
+ Alpha +
+ + +
+
+ +
+
+

Workflow-as-Code

+

+ Write workflows as Python or TypeScript code as a regular Windmill script. +

+
+
+ + +
+ + +
+
+
+ +
+ + Always use the Flow editor (skip this modal) +
+
+
+ + + + drawer?.toggleDrawer?.()}> + + + + {#snippet content()} +
+ {#key importType} + {#await import('$lib/components/SimpleEditor.svelte')} + + {:then Module} + + {/await} + {/key} +
+ {/snippet} +
{#snippet actions()} {/snippet}
+ + + + wacDrawer?.toggleDrawer?.()}> + + + + {#snippet content()} +
+ {#key wacImportType} + {#await import('$lib/components/SimpleEditor.svelte')} + + {:then Module} + + {/await} + {/key} +
+ {/snippet} +
+ {#snippet actions()} + + {/snippet} +
+
diff --git a/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte b/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte index abdb497e93..ae9d75daeb 100644 --- a/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte +++ b/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte @@ -50,7 +50,7 @@ dedicated_worker?: boolean visible_to_runner_only?: boolean on_behalf_of_email?: string - no_main_func?: boolean + auto_kind?: string has_preprocessor?: boolean } | undefined = $state(undefined) @@ -71,7 +71,7 @@ dedicated_worker?: boolean visible_to_runner_only?: boolean on_behalf_of_email?: string - no_main_func?: boolean + auto_kind?: string has_preprocessor?: boolean } | undefined = $state(undefined) @@ -82,7 +82,7 @@ script.schema = script.schema ?? emptySchema() try { const result = await inferArgs(script.language, script.content, script.schema) - script.no_main_func = result?.no_main_func || undefined + script.auto_kind = result?.auto_kind || undefined script.has_preprocessor = result?.has_preprocessor || undefined } catch (error) { sendUserToast(`Could not parse code, are you sure it is valid?`, true) diff --git a/frontend/src/lib/components/graph/model.ts b/frontend/src/lib/components/graph/model.ts index d6fcf4a33a..3618406371 100644 --- a/frontend/src/lib/components/graph/model.ts +++ b/frontend/src/lib/components/graph/model.ts @@ -1,4 +1,4 @@ -import type { FlowStatusModule, Job } from '$lib/gen' +import type { FlowStatusModule, Job, WorkflowStatus } from '$lib/gen' import type { StateStore } from '$lib/utils' import type { FlowState } from '../flows/flowState' @@ -67,6 +67,7 @@ export type GraphModuleState = { skipped?: boolean agent_actions?: FlowStatusModule['agent_actions'] script_hash?: string + workflow_as_code_status?: WorkflowStatus } export type NestedNodes = GraphItem[] diff --git a/frontend/src/lib/components/home/ItemsList.svelte b/frontend/src/lib/components/home/ItemsList.svelte index cae0144169..12b1b7c093 100644 --- a/frontend/src/lib/components/home/ItemsList.svelte +++ b/frontend/src/lib/components/home/ItemsList.svelte @@ -478,7 +478,7 @@ {/if} diff --git a/frontend/src/lib/components/runs/JobRunsPreview.svelte b/frontend/src/lib/components/runs/JobRunsPreview.svelte index 274679d3bc..62722daac8 100644 --- a/frontend/src/lib/components/runs/JobRunsPreview.svelte +++ b/frontend/src/lib/components/runs/JobRunsPreview.svelte @@ -50,11 +50,15 @@ if (!x || typeof x !== 'object') return {} const result: Record = {} for (const [k, v] of Object.entries(x)) { - if (!k.startsWith('_')) result[k] = v as WorkflowStatus + if (!k.startsWith('_') || k.startsWith('_step/')) result[k] = v as WorkflowStatus } return result } + function getStepResults(x: any): Record { + return x?._checkpoint?.completed_steps ?? {} + } + function handleFilterByConcurrencyKey(key: string) { dispatch('filterByConcurrencyKey', key) } @@ -156,6 +160,9 @@ {/if} diff --git a/frontend/src/lib/components/scriptEditor/LogPanel.svelte b/frontend/src/lib/components/scriptEditor/LogPanel.svelte index 63eb9d5a4c..51061f3fba 100644 --- a/frontend/src/lib/components/scriptEditor/LogPanel.svelte +++ b/frontend/src/lib/components/scriptEditor/LogPanel.svelte @@ -96,12 +96,18 @@ if (!x || typeof x !== 'object') return {} const result: Record = {} for (const [k, v] of Object.entries(x)) { - if (!k.startsWith('_')) result[k] = v as WorkflowStatus + if (!k.startsWith('_') || k.startsWith('_step/')) result[k] = v as WorkflowStatus } return result } + function getStepResults(x: any): Record { + return x?._checkpoint?.completed_steps ?? {} + } + let forceJson = $state(false) + let isWac = $derived(!!previewJob?.workflow_as_code_status) + let wacDone = $derived(previewJob?.type == 'CompletedJob') @@ -141,16 +147,20 @@ {#snippet content()}
{#if selectedTab === 'logs'} + {#if isWac} +
+ +
+ {:else} - {#if previewJob?.workflow_as_code_status} - - - - {/if} + {/if} {/if} {#if selectedTab === 'history'}
diff --git a/frontend/src/lib/components/script_builder.ts b/frontend/src/lib/components/script_builder.ts index f224c3bd6a..ddc5a01b89 100644 --- a/frontend/src/lib/components/script_builder.ts +++ b/frontend/src/lib/components/script_builder.ts @@ -14,7 +14,7 @@ export interface ScriptBuilderProps { disableAi?: boolean fullyLoaded?: boolean initialPath?: string - template?: 'docker' | 'bunnative' | 'claudesandbox' | 'script' + template?: 'docker' | 'bunnative' | 'claudesandbox' | 'wac_python' | 'wac_typescript' | 'script' initialArgs?: Record lockedLanguage?: boolean showMeta?: boolean diff --git a/frontend/src/lib/components/scripts/CreateActionsScript.svelte b/frontend/src/lib/components/scripts/CreateActionsScript.svelte index 9c847c9a3a..48480918e0 100644 --- a/frontend/src/lib/components/scripts/CreateActionsScript.svelte +++ b/frontend/src/lib/components/scripts/CreateActionsScript.svelte @@ -1,7 +1,7 @@ diff --git a/frontend/src/lib/components/scripts/WacExportDrawer.svelte b/frontend/src/lib/components/scripts/WacExportDrawer.svelte new file mode 100644 index 0000000000..59f1dca1b0 --- /dev/null +++ b/frontend/src/lib/components/scripts/WacExportDrawer.svelte @@ -0,0 +1,113 @@ + + + + + + drawer?.toggleDrawer()}> +
+ + + + {#snippet content()} +
+
+
+ {#key rawType} + + {/key} +
+ {/snippet} +
+
+
+
diff --git a/frontend/src/lib/components/scripts/scriptStore.svelte.ts b/frontend/src/lib/components/scripts/scriptStore.svelte.ts new file mode 100644 index 0000000000..abc1fb0d63 --- /dev/null +++ b/frontend/src/lib/components/scripts/scriptStore.svelte.ts @@ -0,0 +1,4 @@ +import type { NewScript } from '$lib/gen' +import { writable } from 'svelte/store' + +export const importScriptStore = writable(undefined) diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index 9861e9b016..b1b822419b 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -236,7 +236,7 @@ export async function inferArgs( schema: Schema, mainOverride?: string ): Promise<{ - no_main_func: boolean | null + auto_kind: string | null has_preprocessor: boolean | null } | null> { const lastRun = get(loadSchemaLastRun) @@ -398,7 +398,7 @@ export async function inferArgs( await tick() return { - no_main_func: inferedSchema.no_main_func, + auto_kind: inferedSchema.auto_kind, has_preprocessor: inferedSchema.has_preprocessor } } diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index 5744971f32..d05d78f6ef 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -3,6 +3,8 @@ import { type Script } from './gen' import type { SupportedLanguage } from './common' import CLAUDE_SANDBOX_INIT_CODE from './templates/claude_sandbox.ts.template?raw' +import WAC_PYTHON_INIT_CODE from './templates/wac_python.py.template?raw' +import WAC_TYPESCRIPT_INIT_CODE from './templates/wac_typescript.ts.template?raw' const PYTHON_FAILURE_MODULE_CODE = `import os @@ -1378,6 +1380,12 @@ export const INITIAL_CODE = { }, claudesandbox: { script: CLAUDE_SANDBOX_INIT_CODE + }, + wac_python: { + script: WAC_PYTHON_INIT_CODE + }, + wac_typescript: { + script: WAC_TYPESCRIPT_INIT_CODE } // for related places search: ADD_NEW_LANG } @@ -1406,6 +1414,8 @@ export function initialCode( | 'powershell' | 'bunnative' | 'claudesandbox' + | 'wac_python' + | 'wac_typescript' | undefined, templateScript?: boolean ): string { @@ -1436,6 +1446,10 @@ export function initialCode( } else { return INITIAL_CODE.deno.script } + } else if (subkind === 'wac_python') { + return INITIAL_CODE.wac_python.script + } else if (subkind === 'wac_typescript') { + return INITIAL_CODE.wac_typescript.script } else if (language === 'python3') { if (kind === 'trigger') { return INITIAL_CODE.python3.trigger @@ -1538,6 +1552,8 @@ export function getResetCode( | 'powershell' | 'bunnative' | 'claudesandbox' + | 'wac_python' + | 'wac_typescript' | undefined ) { if (language === 'deno') { diff --git a/frontend/src/lib/templates/wac_python.py.template b/frontend/src/lib/templates/wac_python.py.template new file mode 100644 index 0000000000..42f166ecc0 --- /dev/null +++ b/frontend/src/lib/templates/wac_python.py.template @@ -0,0 +1,42 @@ +from wmill import task, task_script, step, sleep, wait_for_approval, get_resume_urls, workflow + +# IMPORTANT: All computation must happen inside @task(), task_script(), or step(). +# Code outside these wrappers is NOT checkpointed and WILL be re-executed +# on every resume or retry. Never put API calls, database writes, or +# non-deterministic logic (e.g. datetime.now()) in the top-level workflow body. + +# task_script() references a module file (see the helper.py tab) +helper = task_script("./helper.py") + + +# @task() wraps a function as a workflow step that runs as a separate job. +# The result is checkpointed — on retry, completed tasks are skipped. +@task() +async def process(x: str) -> str: + return f"processed: {x}" + + +@workflow +async def main(x: str): + a = await process(x) + + # task_script() calls a module file as a separate job (also checkpointed) + b = await helper(a=a) + + # step() runs inline code and checkpoints the result (no child job). + # Use it for lightweight operations you don't want as a separate script. + urls = await step("get_urls", lambda: get_resume_urls()) + + # sleep() suspends the workflow server-side without holding a worker + await sleep(1) + + # wait_for_approval() suspends until an external event resumes it. + # Like sleep(), it does not hold a worker. Approve/reject URLs are + # available in the timeline step's details in the UI. + approval = await wait_for_approval(timeout=3600) + + return { + "processed": a, + "helper_result": b, + "approval": approval, + } diff --git a/frontend/src/lib/templates/wac_typescript.ts.template b/frontend/src/lib/templates/wac_typescript.ts.template new file mode 100644 index 0000000000..0d22d6d402 --- /dev/null +++ b/frontend/src/lib/templates/wac_typescript.ts.template @@ -0,0 +1,48 @@ +import { + task, + taskScript, + step, + sleep, + waitForApproval, + getResumeUrls, + workflow, +} from "windmill-client"; + +// IMPORTANT: All computation must happen inside task(), taskScript(), or step(). +// Code outside these wrappers is NOT checkpointed and WILL be re-executed +// on every resume or retry. Never put API calls, database writes, or +// non-deterministic logic (e.g. Date.now()) in the top-level workflow body. + +// taskScript() references a module file (see the helper.ts tab) +const helper = taskScript("./helper.ts"); + +// task() wraps a function as a workflow step that runs as a separate job. +// The result is checkpointed — on retry, completed tasks are skipped. +const process = task(async (x: string): Promise => { + return `processed: ${x}`; +}); + +export const main = workflow(async (x: string) => { + const a = await process(x); + + // taskScript() calls a module file as a separate job (also checkpointed) + const b = await helper({ a }); + + // step() runs inline code and checkpoints the result (no child job). + // Use it for lightweight operations you don't want as a separate script. + const urls = await step("get_urls", () => getResumeUrls()); + + // sleep() suspends the workflow server-side without holding a worker + await sleep(1); + + // waitForApproval() suspends until an external event resumes it. + // Like sleep(), it does not hold a worker. Approve/reject URLs are + // available in the timeline step's details in the UI. + const approval = await waitForApproval({ timeout: 3600 }); + + return { + processed: a, + helper_result: b, + approval, + }; +}); diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index ac73b934fd..5bf8f21ac5 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -284,11 +284,15 @@ if (!x || typeof x !== 'object') return {} const result: Record = {} for (const [k, v] of Object.entries(x)) { - if (!k.startsWith('_')) result[k] = v as WorkflowStatus + if (!k.startsWith('_') || k.startsWith('_step/')) result[k] = v as WorkflowStatus } return result } + function getStepResults(x: any): Record { + return x?._checkpoint?.completed_steps ?? {} + } + function forkPreview() { if (isFlowPreview(job?.job_kind)) { const state = { @@ -790,6 +794,9 @@
diff --git a/frontend/src/routes/(root)/(logged)/scripts/add/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/add/+page.svelte index afc3f9e270..8f43dde796 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/add/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/add/+page.svelte @@ -14,6 +14,8 @@ import { get } from 'svelte/store' import { untrack } from 'svelte' import ScriptEditorSkeleton from '$lib/components/ScriptEditorSkeleton.svelte' + import { importScriptStore } from '$lib/components/scripts/scriptStore.svelte' + import { isWorkflowAsCode } from '$lib/components/graph/wacToFlow' type Script = NewScript & { draft_triggers?: Trigger[] @@ -29,6 +31,8 @@ const showMeta = /true|1/i.test(page.url.searchParams.get('show_meta') ?? '0') const urlArgs = page.url.searchParams.get('initial_args') const collabLang = page.url.searchParams.get('lang') as ScriptLang | null + const wacParam = page.url.searchParams.get('wac') + const importParam = page.url.searchParams.get('import') let initialArgs = urlArgs ? decodeState(urlArgs) : (get(initialArgsStore) ?? {}) if (get(initialArgsStore)) $initialArgsStore = undefined @@ -59,7 +63,7 @@ schema: schema, is_template: false, extra_perms: {}, - language: collabLang ?? ($defaultScripts?.order?.filter( + language: (wacParam === 'python' ? 'python3' : wacParam === 'typescript' ? 'bun' : null) ?? collabLang ?? ($defaultScripts?.order?.filter( (x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x) )?.[0] ?? 'bun') as ScriptLang, kind: 'script' @@ -122,6 +126,27 @@ loadHub() + let importedWacTemplate: 'wac_python' | 'wac_typescript' | undefined = undefined + if (importParam && $importScriptStore) { + const imported = $importScriptStore + $importScriptStore = undefined + const isWac = isWorkflowAsCode(imported.content ?? '', imported.language ?? '') + script = { + ...defaultScript(), + ...imported, + path: path ?? '', + hash: '', + extra_perms: {} + } + if (isWac) { + importedWacTemplate = + imported.language === 'python3' ? 'wac_python' : 'wac_typescript' + sendUserToast('WAC script loaded from YAML/JSON') + } else { + sendUserToast('Script loaded from YAML/JSON') + } + } + $effect(() => { if ($workspaceStore) { untrack(() => loadTemplate()) @@ -134,6 +159,7 @@ {initialArgs} bind:this={scriptBuilder} lockedLanguage={templatePath != null || hubPath != null} + template={importedWacTemplate ?? (wacParam === 'python' ? 'wac_python' : wacParam === 'typescript' ? 'wac_typescript' : 'script')} onDeploy={(e) => { goto(`/scripts/get/${e.hash}?workspace=${$workspaceStore}`) }} diff --git a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte index 4fa95cf647..4ba269b74a 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte @@ -62,11 +62,14 @@ Trash, Play, ClipboardCopy, - LayoutDashboard + LayoutDashboard, + ChevronDown, + ChevronRight } from 'lucide-svelte' import { SCRIPT_VIEW_SHOW_PUBLISH_TO_HUB } from '$lib/consts' import { scriptToHubUrl } from '$lib/hub' import SharedBadge from '$lib/components/SharedBadge.svelte' + import Popover from '$lib/components/Popover.svelte' import ScriptVersionHistory from '$lib/components/ScriptVersionHistory.svelte' import { createAppFromScript } from '$lib/components/details/createAppFromScript' import { importStore } from '$lib/components/apps/store' @@ -95,6 +98,8 @@ let can_write = $state(false) let isHubScript = $state(false) let deploymentInProgress = $state(false) + let expandedModuleLocks: Record = $state({}) + let expandedModuleCode: Record = $state({}) let deploymentJobId: string | undefined = $state(undefined) let intervalId: number let shareModal: ShareModal | undefined = $state() @@ -208,7 +213,7 @@ kind: 'script', starred: false, schema: hubScript.schema as Script['schema'], - no_main_func: false, + auto_kind: undefined, has_preprocessor: false } can_write = false @@ -352,7 +357,12 @@ }) } - if (script && !$userStore?.operator && !isCloudHosted() && !isRuleActive('DisableWorkspaceForking')) { + if ( + script && + !$userStore?.operator && + !isCloudHosted() && + !isRuleActive('DisableWorkspaceForking') + ) { buttons.push({ label: 'Edit in fork', buttonProps: { @@ -680,6 +690,14 @@ {#if $workspaceStore && script} {/if} + {#if script?.auto_kind === 'wac'} + + {#snippet text()} + Workflow-as-Code + {/snippet} + wac + + {/if} {#if script?.codebase} bundle + {#if script?.modules} + {#each Object.entries(script.modules) as [modulePath, mod]} +
+ + {#if expandedModuleCode[modulePath]} +
+ +
+ {/if} +
+ {/each} + {/if}
@@ -942,6 +987,45 @@ There is no lock file for this script

{/if} + {#if script?.modules} + {@const moduleEntries = Object.entries(script.modules).filter( + ([_, m]) => m.lock + )} + {#each moduleEntries as [modulePath, mod]} +
+ + {#if expandedModuleLocks[modulePath]} +
+
+ {/if} +
+ {/each} + {/if}
diff --git a/python-client/wmill/uv.lock b/python-client/wmill/uv.lock new file mode 100644 index 0000000000..6c1c3ab219 --- /dev/null +++ b/python-client/wmill/uv.lock @@ -0,0 +1,139 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[manifest] + +[manifest.dependency-groups] +dev = [ + { name = "httpx", specifier = ">=0.28.1" }, + { name = "pytest", specifier = ">=9.0.2" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index c8c30de4f1..cdd3342771 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -11,7 +11,7 @@ import time import warnings import json from json import JSONDecodeError -from typing import Dict, Any, Union, Literal, Optional +from typing import Callable, Dict, Any, Union, Literal, Optional import re import httpx @@ -2431,6 +2431,7 @@ class WorkflowCtx: else: return self._never_resolve() + print(f"\n--- WAC: {key} ---") info = {"name": name or key, "script": script or key, "args": kwargs, "key": key, "dispatch_type": dispatch_type} if _task_options: for opt_key in ("timeout", "tag", "cache_ttl", "priority", "concurrent_limit", "concurrency_key", "concurrency_time_window_s"): @@ -2472,6 +2473,7 @@ class WorkflowCtx: if self._executing_key is not None: await _asyncio.Future() + print(f"\n--- WAC: wait_for_approval({key}) ---") raise _StepSuspend({ "mode": "approval", "key": key, @@ -2489,6 +2491,7 @@ class WorkflowCtx: if self._executing_key is not None: await _asyncio.Future() + print(f"\n--- WAC: sleep({key}, {seconds}s) ---") raise _StepSuspend({ "mode": "sleep", "key": key, @@ -2497,6 +2500,10 @@ class WorkflowCtx: }) async def _run_inline_step(self, name: str, fn): + import json as _json_mod + import time as _time_mod + from datetime import datetime as _dt, timezone as _tz + key = self._alloc_key(name or "step") if key in self._completed: @@ -2513,15 +2520,22 @@ class WorkflowCtx: if self._executing_key is not None: await _asyncio.Future() + print(f"\n--- WAC: {key} ---") + started_at = _dt.now(_tz.utc).isoformat() + print(f"WM_WAC_STEP: {_json_mod.dumps({'key': key, 'started_at': started_at})}") + t0 = _time_mod.monotonic() result = fn() if _asyncio.iscoroutine(result): result = await result + duration_ms = int((_time_mod.monotonic() - t0) * 1000) raise _StepSuspend({ "mode": "inline_checkpoint", "steps": [], "key": key, "result": result, + "started_at": started_at, + "duration_ms": duration_ms, }) @@ -2568,7 +2582,7 @@ def task( # Remove None values _task_opts = {k: v for k, v in _task_opts.items() if v is not None} or None - def decorator(func): + def decorator(func) -> Callable[..., Any]: task_path = path task_name = func.__name__ @@ -2585,7 +2599,6 @@ def task( merged[f"arg{i}"] = arg return merged - @functools.wraps(func) def wrapper(*args, **kwargs): # WAC v2: inside a @workflow context ctx = _workflow_ctx.get(None) @@ -2815,11 +2828,16 @@ async def _run_workflow_async(func, checkpoint: dict, input_args: dict): if mode == "step_complete": return {"type": "complete", "result": info.get("result")} if mode == "inline_checkpoint": - return { + out = { "type": "inline_checkpoint", "key": info["key"], "result": info.get("result"), } + if "started_at" in info: + out["started_at"] = info["started_at"] + if "duration_ms" in info: + out["duration_ms"] = info["duration_ms"] + return out if mode == "approval": return { "type": "approval", diff --git a/typescript-client/client.ts b/typescript-client/client.ts index 8578c98bb6..49087a8b60 100644 --- a/typescript-client/client.ts +++ b/typescript-client/client.ts @@ -1558,6 +1558,8 @@ export class WorkflowCtx { this._suspended = true; const steps = [...this.pending]; this.pending = []; + const names = steps.map(s => s.name).join(", "); + console.log(`\n--- WAC: ${names} ---`); throw new StepSuspend({ mode: steps.length > 1 ? "parallel" : "sequential", steps, @@ -1589,6 +1591,7 @@ export class WorkflowCtx { } // Throw immediately — approval is always a blocking step + console.log(`\n--- WAC: approval(${key}) ---`); throw new StepSuspend({ mode: "approval", key, @@ -1609,6 +1612,7 @@ export class WorkflowCtx { return { then: () => new Promise(() => {}) }; } + console.log(`\n--- WAC: sleep(${key}, ${seconds}s) ---`); throw new StepSuspend({ mode: "sleep", key, @@ -1636,8 +1640,13 @@ export class WorkflowCtx { return new Promise(() => {}); } + console.log(`\n--- WAC: ${key} ---`); + const startedAt = new Date().toISOString(); + console.log(`WM_WAC_STEP: ${JSON.stringify({ key, started_at: startedAt })}`); + const t0 = Date.now(); const result = await fn(); - throw new StepSuspend({ mode: "inline_checkpoint", steps: [], key, result }); + const durationMs = Date.now() - t0; + throw new StepSuspend({ mode: "inline_checkpoint", steps: [], key, result, started_at: startedAt, duration_ms: durationMs }); } } From 6165e01e8afb89a1b86049e40faece12a595040b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 01:27:31 +0000 Subject: [PATCH 045/116] fix migration clash --- ..._role.down.sql => 20260316000005_instance_group_role.down.sql} | 0 ...roup_role.up.sql => 20260316000005_instance_group_role.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename backend/migrations/{20260316000000_instance_group_role.down.sql => 20260316000005_instance_group_role.down.sql} (100%) rename backend/migrations/{20260316000000_instance_group_role.up.sql => 20260316000005_instance_group_role.up.sql} (100%) diff --git a/backend/migrations/20260316000000_instance_group_role.down.sql b/backend/migrations/20260316000005_instance_group_role.down.sql similarity index 100% rename from backend/migrations/20260316000000_instance_group_role.down.sql rename to backend/migrations/20260316000005_instance_group_role.down.sql diff --git a/backend/migrations/20260316000000_instance_group_role.up.sql b/backend/migrations/20260316000005_instance_group_role.up.sql similarity index 100% rename from backend/migrations/20260316000000_instance_group_role.up.sql rename to backend/migrations/20260316000005_instance_group_role.up.sql From ced6f622072efe3ac1ae0f24c18cf157180230c7 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 01:36:03 +0000 Subject: [PATCH 046/116] chore: trigger CLI tests on migration changes and release tags Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/cli-tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 20a24d2091..5478e8985e 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -3,13 +3,17 @@ name: CLI Tests on: push: branches: [main] + tags: + - "v*" paths: - "cli/**" + - "backend/migrations/**" - ".github/workflows/cli-tests.yml" pull_request: branches: [main] paths: - "cli/**" + - "backend/migrations/**" - ".github/workflows/cli-tests.yml" env: From 4e7be0d27a392135f111cccda5800e5ba1563923 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 01:39:24 +0000 Subject: [PATCH 047/116] chore: run windows backend tests on release tags Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/backend-test-windows.yml | 2 ++ .github/workflows/cli-tests.yml | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backend-test-windows.yml b/.github/workflows/backend-test-windows.yml index ca9ce2aaac..864693ebda 100644 --- a/.github/workflows/backend-test-windows.yml +++ b/.github/workflows/backend-test-windows.yml @@ -5,6 +5,8 @@ on: push: branches: - "ci-windows-tests" + tags: + - "v*" env: CARGO_INCREMENTAL: 0 diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 5478e8985e..9c87a249a3 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -3,8 +3,6 @@ name: CLI Tests on: push: branches: [main] - tags: - - "v*" paths: - "cli/**" - "backend/migrations/**" From 8cd2d06f0175c3a412883fa2e5ed4b9a608fdfa0 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 01:50:16 +0000 Subject: [PATCH 048/116] nit fix app --- frontend/package-lock.json | 4 ++-- frontend/src/lib/components/apps/editor/appUtilsS3.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 01eea55d67..9f9a907e19 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -162,8 +162,8 @@ } }, "../backend/parsers/windmill-parser-wasm/pkg-ts": { - "name": "windmill-parser-wasm-ts", - "version": "1.657.2" + "name": "windmill-parser-wasm", + "version": "1.654.0" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", diff --git a/frontend/src/lib/components/apps/editor/appUtilsS3.ts b/frontend/src/lib/components/apps/editor/appUtilsS3.ts index 5e35f1a4bd..f9811edd76 100644 --- a/frontend/src/lib/components/apps/editor/appUtilsS3.ts +++ b/frontend/src/lib/components/apps/editor/appUtilsS3.ts @@ -153,8 +153,8 @@ export function computeS3FileViewerPolicy(config: RichConfigurations) { } else if ( config.source.type === 'static' && typeof config.source.value === 'string' && - ((config.sourceKind.type === 'static' && - config.sourceKind.value === 's3 (workspace storage)') || + ((config.sourceKind?.type === 'static' && + config.sourceKind?.value === 's3 (workspace storage)') || config.source.value.startsWith('s3://')) ) { return { From 08215c708bc3c118c0fe4b2f2b10e481a93f83e1 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:33:11 +0100 Subject: [PATCH 049/116] Display workspace ID in select subtitle when creating token (#8407) --- .../src/lib/components/settings/CreateToken.svelte | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/components/settings/CreateToken.svelte b/frontend/src/lib/components/settings/CreateToken.svelte index fbd9e41081..9a2c6106b2 100644 --- a/frontend/src/lib/components/settings/CreateToken.svelte +++ b/frontend/src/lib/components/settings/CreateToken.svelte @@ -44,7 +44,6 @@ displayCreateToken = true }: Props = $props() - let newToken = $state(undefined) let newMcpToken = $state(undefined) let newTokenExpiration = $state(undefined) @@ -464,11 +463,10 @@
Workspace - + updateSetting(wsId, setting.key, e.currentTarget.value)} + /> + {/if} +
+ {/each} + + {#if getAvailableFields(wsId).length > 0} + + {/if} + + {/if} + + {/each} + + + {#if showAddWorkspace} +
+ {#if availableWorkspaces.length > 0} + + {:else} + + {/if} + + +
+ {:else} +
+ +
+ {/if} + From 7d9fb57368ad3b2c719523ef649c9bd5fddf17a5 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 17 Mar 2026 16:26:04 +0100 Subject: [PATCH 052/116] feat: DB-backed instance events webhook with superadmin UI (#8402) * feat: make instance events webhook URL configurable via superadmin UI The instance events webhook was previously only configurable via the INSTANCE_EVENTS_WEBHOOK env var, requiring a restart to change. This adds a DB-backed global setting with a UI in superadmin settings under Monitoring > Webhooks, while keeping the env var as an override. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: address PR review - prometheus timer bug and cleaner cache init - Bind prometheus timer to `let timer` and call `stop_and_record()` after the POST (was silently discarded before) - Use `Option` with `map_or` instead of `checked_sub` trick for clearer "not yet read" semantics Co-Authored-By: Claude Opus 4.6 (1M context) * chore: remove env var mention from webhook setting description Co-Authored-By: Claude Opus 4.6 (1M context) * chore: list all instance events explicitly in webhook description Co-Authored-By: Claude Opus 4.6 (1M context) * fix: restore send_instance_event guard with AtomicBool for DB setting Use a shared Arc between send_instance_event and the event loop so we skip channel sends when no webhook is configured (env or DB). Starts optimistic (true) so the first event triggers a DB read, then the loop updates it after each cache refresh. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: use static AtomicBool + notify handler for webhook guard Replace the Arc instance field with a global static INSTANCE_EVENTS_WEBHOOK_DB_ENABLED, updated by the notify_global_setting_change handler in main.rs. This follows the established pattern (like REQUIRE_PREEXISTING_USER_FOR_OAUTH) and avoids the deadlock where the bool could never flip back to true. Co-Authored-By: Claude Opus 4.6 (1M context) * refactor: single Arc>> for instance webhook URL Replace the separate INSTANCE_EVENTS_WEBHOOK env var lazy_static and INSTANCE_EVENTS_WEBHOOK_DB_ENABLED AtomicBool with a single shared variable. Initialized from env var, then the reload function overwrites from DB (falls back to env var when DB has no value). Follows the same pattern as SCIM_TOKEN and other settings. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Ruben Fiszel --- backend/src/main.rs | 13 +++++++--- backend/src/monitor.rs | 21 ++++++++++++++++ .../windmill-common/src/global_settings.rs | 1 + .../windmill-common/src/instance_config.rs | 2 ++ backend/windmill-common/src/webhook.rs | 25 +++++++++++++------ .../src/lib/components/instanceSettings.ts | 19 ++++++++++++++ 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/backend/src/main.rs b/backend/src/main.rs index f0876427e4..fc0a4c29d4 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -44,9 +44,10 @@ use windmill_common::{ DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING, - INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, - JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, - MAVEN_SETTINGS_XML_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING, + INSTANCE_EVENTS_WEBHOOK_SETTING, INSTANCE_PYTHON_VERSION_SETTING, + JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING, + KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MAVEN_SETTINGS_XML_SETTING, + MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, @@ -102,7 +103,8 @@ use crate::monitor::{ reload_base_url_setting, reload_bunfig_install_scopes_setting, reload_critical_alert_mute_ui_setting, reload_critical_alerts_on_token_expiry_setting, reload_critical_error_channels_setting, reload_extra_pip_index_url_setting, - reload_hub_api_secret_setting, reload_hub_base_url_setting, reload_job_default_timeout_setting, + reload_hub_api_secret_setting, reload_hub_base_url_setting, + reload_instance_events_webhook_setting, reload_job_default_timeout_setting, reload_job_isolation_setting, reload_jwt_secret_setting, reload_license_key, reload_npm_config_registry_setting, reload_otel_tracing_proxy_setting, reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting, @@ -1850,6 +1852,9 @@ async fn process_notify_event( tracing::error!(error = %e, "Could not reload critical alerts on token expiry setting"); } } + INSTANCE_EVENTS_WEBHOOK_SETTING => { + reload_instance_events_webhook_setting(db).await; + } "workspace_telemetry_enabled" => { // Read the new value from the database and log it let enabled = sqlx::query_scalar!( diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index b174da7905..579bfbc49e 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -235,6 +235,7 @@ pub async fn initial_load( ) } windmill_common::min_version::store_min_keep_alive_version(db).await; + reload_instance_events_webhook_setting(db).await; } } @@ -1360,6 +1361,26 @@ async fn delete_log_files_from_disk_and_store( let _: Vec<_> = delete_futures.collect().await; } +pub async fn reload_instance_events_webhook_setting(db: &DB) { + use windmill_common::global_settings::INSTANCE_EVENTS_WEBHOOK_SETTING; + use windmill_common::webhook::INSTANCE_EVENTS_WEBHOOK; + + let value = load_value_from_global_settings(db, INSTANCE_EVENTS_WEBHOOK_SETTING).await; + match value { + Ok(Some(serde_json::Value::String(s))) if !s.is_empty() => { + *INSTANCE_EVENTS_WEBHOOK.write().await = Some(s); + } + Ok(None) | Ok(Some(serde_json::Value::Null)) | Ok(Some(serde_json::Value::String(_))) => { + // Fall back to env var if DB has no value + *INSTANCE_EVENTS_WEBHOOK.write().await = std::env::var("INSTANCE_EVENTS_WEBHOOK").ok(); + } + Err(e) => { + tracing::error!("Error loading instance_events_webhook setting: {e:#}"); + } + _ => (), + }; +} + pub async fn reload_scim_token_setting(conn: &Connection) { reload_option_setting_with_tracing(conn, SCIM_TOKEN_SETTING, "SCIM_TOKEN", SCIM_TOKEN.clone()) .await; diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index d6cd953fbd..937bebf09c 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -60,6 +60,7 @@ pub const APP_WORKSPACED_ROUTE_SETTING: &str = "app_workspaced_route"; pub const SECRET_BACKEND_SETTING: &str = "secret_backend"; pub const MIN_KEEP_ALIVE_VERSION_SETTING: &str = "min_keep_alive_version"; pub const GITHUB_ENTERPRISE_APP_SETTING: &str = "github_enterprise_app"; +pub const INSTANCE_EVENTS_WEBHOOK_SETTING: &str = "instance_events_webhook"; pub const WORKSPACE_REGISTRIES_SETTING: &str = "workspace_registries"; pub const ENV_SETTINGS: &[&str] = &[ diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index 1c79f2f9a0..6e791906f9 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -261,6 +261,8 @@ pub struct GlobalSettings { #[serde(skip_serializing_if = "Option::is_none")] pub saml_metadata: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub instance_events_webhook: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub openai_azure_base_path: Option, #[serde(skip_serializing_if = "Option::is_none")] pub min_keep_alive_version: Option, diff --git a/backend/windmill-common/src/webhook.rs b/backend/windmill-common/src/webhook.rs index 4677084fbe..caf8ec08fc 100644 --- a/backend/windmill-common/src/webhook.rs +++ b/backend/windmill-common/src/webhook.rs @@ -1,7 +1,9 @@ +use std::sync::Arc; use std::time::Duration; use quick_cache::sync::Cache; use serde::Serialize; +use tokio::sync::RwLock; use tokio::{select, sync::mpsc}; #[cfg(feature = "prometheus")] @@ -24,7 +26,8 @@ lazy_static::lazy_static! { lazy_static::lazy_static! { - pub static ref INSTANCE_EVENTS_WEBHOOK: Option = std::env::var("INSTANCE_EVENTS_WEBHOOK").ok(); + pub static ref INSTANCE_EVENTS_WEBHOOK: Arc>> = + Arc::new(RwLock::new(std::env::var("INSTANCE_EVENTS_WEBHOOK").ok())); pub static ref WEBHOOK_CACHE: Cache> = Cache::new(100); @@ -208,11 +211,16 @@ impl WebhookShared { } }, Some(WebhookPayload::InstanceEvent(event)) => { - #[cfg(feature = "prometheus")] - if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) { Some(WEBHOOK_REQUEST_COUNT.start_timer()) } else { None }; - let r = client.post(INSTANCE_EVENTS_WEBHOOK.as_ref().unwrap()).json(&event).send().await; - if let Err(e) = r { - tracing::error!("Error sending instance event: {}", e); + let url = INSTANCE_EVENTS_WEBHOOK.read().await.clone(); + if let Some(url) = url { + #[cfg(feature = "prometheus")] + let timer = if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) { Some(WEBHOOK_REQUEST_COUNT.start_timer()) } else { None }; + let r = client.post(&url).json(&event).send().await; + if let Err(e) = r { + tracing::error!("Error sending instance event: {}", e); + } + #[cfg(feature = "prometheus")] + timer.map(|x| x.stop_and_record()); } }, None => break, @@ -232,7 +240,10 @@ impl WebhookShared { } pub fn send_instance_event(&self, event: InstanceEvent) { - if INSTANCE_EVENTS_WEBHOOK.is_none() { + if INSTANCE_EVENTS_WEBHOOK + .try_read() + .is_ok_and(|v| v.is_none()) + { return; } let _ = self.channel.send(WebhookPayload::InstanceEvent(event)); diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 22077b79a7..05783ab91d 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -611,6 +611,17 @@ export const settings: Record = { ee_only: '' } ], + Webhooks: [ + { + label: 'Instance Events Webhook', + description: + 'URL to receive POST requests for instance events (user added, OAuth signup, user invited/added/joined workspace).', + key: 'instance_events_webhook', + fieldType: 'text', + placeholder: 'https://example.com/webhook', + storage: 'setting' + } + ], 'OTEL/Prom': [ { label: 'OpenTelemetry', @@ -789,6 +800,12 @@ export const instanceSettingsNavigationGroups = [ aiDescription: 'Instance alerts settings', isEE: true }, + { + id: 'webhooks', + label: 'Webhooks', + aiId: 'instance-settings-webhooks', + aiDescription: 'Instance events webhook settings' + }, { id: 'otel_prom', label: 'OTEL/Prometheus', @@ -852,6 +869,7 @@ export const tabToCategoryMap: Record = { smtp: 'SMTP', registries: 'Registries', alerts: 'Alerts', + webhooks: 'Webhooks', otel_prom: 'OTEL/Prom', indexer: 'Indexer', telemetry: 'Telemetry', @@ -883,6 +901,7 @@ export const categoryToTabMap: Record = { 'Auth/OAuth/SAML': 'sso', Registries: 'registries', Alerts: 'alerts', + Webhooks: 'webhooks', 'OTEL/Prom': 'otel_prom', Indexer: 'indexer', Telemetry: 'telemetry', From d43eca7b4bf9fd4f3a53a9177274b48a7fb81cab Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 17 Mar 2026 17:02:54 +0100 Subject: [PATCH 053/116] chore(frontend): add missing integration icons and fix dark mode visibility (#8413) * feat: add 93 missing integration icons and fix dark mode visibility Co-Authored-By: Claude Opus 4.6 (1M context) * feat: add 11 more integration icons (round 2) Co-Authored-By: Claude Opus 4.6 (1M context) * feat: add 5 more integration icons (round 3) Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../icons/ActiveCampaignIcon.svelte | 15 + .../lib/components/icons/AlgoliaIcon.svelte | 12 + .../lib/components/icons/ApolloIcon.svelte | 12 + .../lib/components/icons/BambooHrIcon.svelte | 12 + .../components/icons/BaremetricsIcon.svelte | 12 + .../src/lib/components/icons/BitlyIcon.svelte | 12 + .../lib/components/icons/BloggerIcon.svelte | 12 + .../lib/components/icons/BlueskyIcon.svelte | 12 + .../src/lib/components/icons/BoxIcon.svelte | 12 + .../src/lib/components/icons/BrevoIcon.svelte | 12 + .../src/lib/components/icons/BrexIcon.svelte | 12 + .../components/icons/BrowserlessIcon.svelte | 12 + .../lib/components/icons/BubbleIcon.svelte | 16 + .../lib/components/icons/BuildkiteIcon.svelte | 12 + .../lib/components/icons/CalcomIcon.svelte | 2 +- .../lib/components/icons/CalendlyIcon.svelte | 12 + .../lib/components/icons/CircleCiIcon.svelte | 12 + .../src/lib/components/icons/CiscoIcon.svelte | 12 + .../lib/components/icons/ClearbitIcon.svelte | 12 + .../src/lib/components/icons/ClerkIcon.svelte | 12 + .../src/lib/components/icons/CloseIcon.svelte | 42 +++ .../components/icons/CloudinaryIcon.svelte | 12 + .../components/icons/CockroachDbIcon.svelte | 12 + .../src/lib/components/icons/CodaIcon.svelte | 12 + .../lib/components/icons/CohereIcon.svelte | 21 ++ .../components/icons/CoinMarketCapIcon.svelte | 12 + .../lib/components/icons/CoinbaseIcon.svelte | 12 + .../components/icons/ConfluenceIcon.svelte | 12 + .../components/icons/ContentfulIcon.svelte | 12 + .../components/icons/ConvertKitIcon.svelte | 12 + .../lib/components/icons/DatoCmsIcon.svelte | 12 + .../src/lib/components/icons/DeelIcon.svelte | 12 + .../src/lib/components/icons/DeepLIcon.svelte | 12 + .../components/icons/DigitalOceanIcon.svelte | 12 + .../lib/components/icons/DiscourseIcon.svelte | 12 + .../lib/components/icons/DocusignIcon.svelte | 12 + .../lib/components/icons/DropboxIcon.svelte | 12 + .../lib/components/icons/EdgeDbIcon.svelte | 46 ++- .../components/icons/EventbriteIcon.svelte | 15 + .../src/lib/components/icons/FigmaIcon.svelte | 12 + .../src/lib/components/icons/FlyIcon.svelte | 12 + .../lib/components/icons/FreshdeskIcon.svelte | 12 + .../lib/components/icons/FrontAppIcon.svelte | 12 + .../lib/components/icons/GhostCmsIcon.svelte | 12 + .../src/lib/components/icons/GiphyIcon.svelte | 12 + .../lib/components/icons/GitBookIcon.svelte | 12 + .../src/lib/components/icons/GroqIcon.svelte | 12 + .../components/icons/HoneybadgerIcon.svelte | 12 + .../src/lib/components/icons/HttpIcon.svelte | 7 +- .../src/lib/components/icons/IftttIcon.svelte | 12 + .../lib/components/icons/IntercomIcon.svelte | 12 + .../lib/components/icons/JoomlaIcon.svelte | 12 + .../src/lib/components/icons/LineIcon.svelte | 12 + .../lib/components/icons/LinearIcon.svelte | 12 + .../lib/components/icons/LinodeIcon.svelte | 12 + .../lib/components/icons/LumaAiIcon.svelte | 44 +++ .../lib/components/icons/MSTeamsIcon.svelte | 60 ++-- .../lib/components/icons/MagentoIcon.svelte | 12 + .../lib/components/icons/MailchimpIcon.svelte | 44 ++- .../lib/components/icons/MandrillIcon.svelte | 12 + .../lib/components/icons/MauticIcon.svelte | 12 + .../lib/components/icons/MediumIcon.svelte | 12 + .../src/lib/components/icons/MiroIcon.svelte | 12 + .../lib/components/icons/MistralIcon.svelte | 12 + .../lib/components/icons/MixpanelIcon.svelte | 12 + .../lib/components/icons/MondayIcon.svelte | 12 + .../lib/components/icons/NeonDbIcon.svelte | 28 ++ .../lib/components/icons/NetlifyIcon.svelte | 12 + .../lib/components/icons/OneSignalIcon.svelte | 14 + .../components/icons/OpenWeatherIcon.svelte | 12 + .../lib/components/icons/PagerDutyIcon.svelte | 12 + .../lib/components/icons/PandaDocIcon.svelte | 12 + .../lib/components/icons/PaypalIcon.svelte | 12 + .../lib/components/icons/PersonioIcon.svelte | 12 + .../lib/components/icons/PinterestIcon.svelte | 12 + .../lib/components/icons/PipedriveIcon.svelte | 16 + .../components/icons/PlanetScaleIcon.svelte | 12 + .../lib/components/icons/PostmarkIcon.svelte | 12 + .../lib/components/icons/PusherIcon.svelte | 12 + .../components/icons/QuickbooksIcon.svelte | 20 +- .../lib/components/icons/RaindropIcon.svelte | 32 ++ .../lib/components/icons/ReadwiseIcon.svelte | 17 + .../lib/components/icons/RenderIcon.svelte | 12 + .../lib/components/icons/ReplicateIcon.svelte | 12 + .../lib/components/icons/ResendIcon.svelte | 2 +- .../src/lib/components/icons/RestIcon.svelte | 7 +- .../components/icons/RingCentralIcon.svelte | 12 + .../components/icons/RocketChatIcon.svelte | 12 + .../lib/components/icons/RunPodIcon.svelte | 13 + .../src/lib/components/icons/RustIcon.svelte | 140 ++++---- .../components/icons/SalesforceIcon.svelte | 12 + .../lib/components/icons/SegmentIcon.svelte | 12 + .../lib/components/icons/SentryIcon.svelte | 12 + .../components/icons/ServiceNowIcon.svelte | 12 + .../lib/components/icons/ShortcutIcon.svelte | 12 + .../lib/components/icons/SigNozIcon.svelte | 13 + .../components/icons/SmartsheetIcon.svelte | 12 + .../lib/components/icons/SpeechifyIcon.svelte | 12 + .../lib/components/icons/SplitwiseIcon.svelte | 12 + .../lib/components/icons/StravaIcon.svelte | 12 + .../src/lib/components/icons/TallyIcon.svelte | 12 + .../lib/components/icons/TelnyxIcon.svelte | 24 ++ .../lib/components/icons/ThreadsIcon.svelte | 12 + .../lib/components/icons/TodoistIcon.svelte | 12 + .../components/icons/TogetherAiIcon.svelte | 29 ++ .../lib/components/icons/TrelloIcon.svelte | 29 +- .../src/lib/components/icons/TursoIcon.svelte | 12 + .../lib/components/icons/TwitchIcon.svelte | 12 + .../lib/components/icons/TwitterIcon.svelte | 12 + .../lib/components/icons/TypeformIcon.svelte | 2 +- .../lib/components/icons/VercelIcon.svelte | 12 + .../lib/components/icons/WebflowIcon.svelte | 12 + .../components/icons/WooCommerceIcon.svelte | 12 + .../lib/components/icons/WordpressIcon.svelte | 12 + .../src/lib/components/icons/XataIcon.svelte | 18 + .../src/lib/components/icons/YelpIcon.svelte | 12 + .../src/lib/components/icons/YnabIcon.svelte | 12 + .../lib/components/icons/YoutubeIcon.svelte | 12 + .../lib/components/icons/ZendeskIcon.svelte | 2 +- .../lib/components/icons/ZeroTierIcon.svelte | 12 + .../src/lib/components/icons/ZoomIcon.svelte | 12 + frontend/src/lib/components/icons/index.ts | 337 +++++++++++++++++- 122 files changed, 2011 insertions(+), 160 deletions(-) create mode 100644 frontend/src/lib/components/icons/ActiveCampaignIcon.svelte create mode 100644 frontend/src/lib/components/icons/AlgoliaIcon.svelte create mode 100644 frontend/src/lib/components/icons/ApolloIcon.svelte create mode 100644 frontend/src/lib/components/icons/BambooHrIcon.svelte create mode 100644 frontend/src/lib/components/icons/BaremetricsIcon.svelte create mode 100644 frontend/src/lib/components/icons/BitlyIcon.svelte create mode 100644 frontend/src/lib/components/icons/BloggerIcon.svelte create mode 100644 frontend/src/lib/components/icons/BlueskyIcon.svelte create mode 100644 frontend/src/lib/components/icons/BoxIcon.svelte create mode 100644 frontend/src/lib/components/icons/BrevoIcon.svelte create mode 100644 frontend/src/lib/components/icons/BrexIcon.svelte create mode 100644 frontend/src/lib/components/icons/BrowserlessIcon.svelte create mode 100644 frontend/src/lib/components/icons/BubbleIcon.svelte create mode 100644 frontend/src/lib/components/icons/BuildkiteIcon.svelte create mode 100644 frontend/src/lib/components/icons/CalendlyIcon.svelte create mode 100644 frontend/src/lib/components/icons/CircleCiIcon.svelte create mode 100644 frontend/src/lib/components/icons/CiscoIcon.svelte create mode 100644 frontend/src/lib/components/icons/ClearbitIcon.svelte create mode 100644 frontend/src/lib/components/icons/ClerkIcon.svelte create mode 100644 frontend/src/lib/components/icons/CloseIcon.svelte create mode 100644 frontend/src/lib/components/icons/CloudinaryIcon.svelte create mode 100644 frontend/src/lib/components/icons/CockroachDbIcon.svelte create mode 100644 frontend/src/lib/components/icons/CodaIcon.svelte create mode 100644 frontend/src/lib/components/icons/CohereIcon.svelte create mode 100644 frontend/src/lib/components/icons/CoinMarketCapIcon.svelte create mode 100644 frontend/src/lib/components/icons/CoinbaseIcon.svelte create mode 100644 frontend/src/lib/components/icons/ConfluenceIcon.svelte create mode 100644 frontend/src/lib/components/icons/ContentfulIcon.svelte create mode 100644 frontend/src/lib/components/icons/ConvertKitIcon.svelte create mode 100644 frontend/src/lib/components/icons/DatoCmsIcon.svelte create mode 100644 frontend/src/lib/components/icons/DeelIcon.svelte create mode 100644 frontend/src/lib/components/icons/DeepLIcon.svelte create mode 100644 frontend/src/lib/components/icons/DigitalOceanIcon.svelte create mode 100644 frontend/src/lib/components/icons/DiscourseIcon.svelte create mode 100644 frontend/src/lib/components/icons/DocusignIcon.svelte create mode 100644 frontend/src/lib/components/icons/DropboxIcon.svelte create mode 100644 frontend/src/lib/components/icons/EventbriteIcon.svelte create mode 100644 frontend/src/lib/components/icons/FigmaIcon.svelte create mode 100644 frontend/src/lib/components/icons/FlyIcon.svelte create mode 100644 frontend/src/lib/components/icons/FreshdeskIcon.svelte create mode 100644 frontend/src/lib/components/icons/FrontAppIcon.svelte create mode 100644 frontend/src/lib/components/icons/GhostCmsIcon.svelte create mode 100644 frontend/src/lib/components/icons/GiphyIcon.svelte create mode 100644 frontend/src/lib/components/icons/GitBookIcon.svelte create mode 100644 frontend/src/lib/components/icons/GroqIcon.svelte create mode 100644 frontend/src/lib/components/icons/HoneybadgerIcon.svelte create mode 100644 frontend/src/lib/components/icons/IftttIcon.svelte create mode 100644 frontend/src/lib/components/icons/IntercomIcon.svelte create mode 100644 frontend/src/lib/components/icons/JoomlaIcon.svelte create mode 100644 frontend/src/lib/components/icons/LineIcon.svelte create mode 100644 frontend/src/lib/components/icons/LinearIcon.svelte create mode 100644 frontend/src/lib/components/icons/LinodeIcon.svelte create mode 100644 frontend/src/lib/components/icons/LumaAiIcon.svelte create mode 100644 frontend/src/lib/components/icons/MagentoIcon.svelte create mode 100644 frontend/src/lib/components/icons/MandrillIcon.svelte create mode 100644 frontend/src/lib/components/icons/MauticIcon.svelte create mode 100644 frontend/src/lib/components/icons/MediumIcon.svelte create mode 100644 frontend/src/lib/components/icons/MiroIcon.svelte create mode 100644 frontend/src/lib/components/icons/MistralIcon.svelte create mode 100644 frontend/src/lib/components/icons/MixpanelIcon.svelte create mode 100644 frontend/src/lib/components/icons/MondayIcon.svelte create mode 100644 frontend/src/lib/components/icons/NeonDbIcon.svelte create mode 100644 frontend/src/lib/components/icons/NetlifyIcon.svelte create mode 100644 frontend/src/lib/components/icons/OneSignalIcon.svelte create mode 100644 frontend/src/lib/components/icons/OpenWeatherIcon.svelte create mode 100644 frontend/src/lib/components/icons/PagerDutyIcon.svelte create mode 100644 frontend/src/lib/components/icons/PandaDocIcon.svelte create mode 100644 frontend/src/lib/components/icons/PaypalIcon.svelte create mode 100644 frontend/src/lib/components/icons/PersonioIcon.svelte create mode 100644 frontend/src/lib/components/icons/PinterestIcon.svelte create mode 100644 frontend/src/lib/components/icons/PipedriveIcon.svelte create mode 100644 frontend/src/lib/components/icons/PlanetScaleIcon.svelte create mode 100644 frontend/src/lib/components/icons/PostmarkIcon.svelte create mode 100644 frontend/src/lib/components/icons/PusherIcon.svelte create mode 100644 frontend/src/lib/components/icons/RaindropIcon.svelte create mode 100644 frontend/src/lib/components/icons/ReadwiseIcon.svelte create mode 100644 frontend/src/lib/components/icons/RenderIcon.svelte create mode 100644 frontend/src/lib/components/icons/ReplicateIcon.svelte create mode 100644 frontend/src/lib/components/icons/RingCentralIcon.svelte create mode 100644 frontend/src/lib/components/icons/RocketChatIcon.svelte create mode 100644 frontend/src/lib/components/icons/RunPodIcon.svelte create mode 100644 frontend/src/lib/components/icons/SalesforceIcon.svelte create mode 100644 frontend/src/lib/components/icons/SegmentIcon.svelte create mode 100644 frontend/src/lib/components/icons/SentryIcon.svelte create mode 100644 frontend/src/lib/components/icons/ServiceNowIcon.svelte create mode 100644 frontend/src/lib/components/icons/ShortcutIcon.svelte create mode 100644 frontend/src/lib/components/icons/SigNozIcon.svelte create mode 100644 frontend/src/lib/components/icons/SmartsheetIcon.svelte create mode 100644 frontend/src/lib/components/icons/SpeechifyIcon.svelte create mode 100644 frontend/src/lib/components/icons/SplitwiseIcon.svelte create mode 100644 frontend/src/lib/components/icons/StravaIcon.svelte create mode 100644 frontend/src/lib/components/icons/TallyIcon.svelte create mode 100644 frontend/src/lib/components/icons/TelnyxIcon.svelte create mode 100644 frontend/src/lib/components/icons/ThreadsIcon.svelte create mode 100644 frontend/src/lib/components/icons/TodoistIcon.svelte create mode 100644 frontend/src/lib/components/icons/TogetherAiIcon.svelte create mode 100644 frontend/src/lib/components/icons/TursoIcon.svelte create mode 100644 frontend/src/lib/components/icons/TwitchIcon.svelte create mode 100644 frontend/src/lib/components/icons/TwitterIcon.svelte create mode 100644 frontend/src/lib/components/icons/VercelIcon.svelte create mode 100644 frontend/src/lib/components/icons/WebflowIcon.svelte create mode 100644 frontend/src/lib/components/icons/WooCommerceIcon.svelte create mode 100644 frontend/src/lib/components/icons/WordpressIcon.svelte create mode 100644 frontend/src/lib/components/icons/XataIcon.svelte create mode 100644 frontend/src/lib/components/icons/YelpIcon.svelte create mode 100644 frontend/src/lib/components/icons/YnabIcon.svelte create mode 100644 frontend/src/lib/components/icons/YoutubeIcon.svelte create mode 100644 frontend/src/lib/components/icons/ZeroTierIcon.svelte create mode 100644 frontend/src/lib/components/icons/ZoomIcon.svelte diff --git a/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte b/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte new file mode 100644 index 0000000000..836b5a242b --- /dev/null +++ b/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/frontend/src/lib/components/icons/AlgoliaIcon.svelte b/frontend/src/lib/components/icons/AlgoliaIcon.svelte new file mode 100644 index 0000000000..e003c778e4 --- /dev/null +++ b/frontend/src/lib/components/icons/AlgoliaIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ApolloIcon.svelte b/frontend/src/lib/components/icons/ApolloIcon.svelte new file mode 100644 index 0000000000..85dbb1730a --- /dev/null +++ b/frontend/src/lib/components/icons/ApolloIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BambooHrIcon.svelte b/frontend/src/lib/components/icons/BambooHrIcon.svelte new file mode 100644 index 0000000000..efbeec2ece --- /dev/null +++ b/frontend/src/lib/components/icons/BambooHrIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BaremetricsIcon.svelte b/frontend/src/lib/components/icons/BaremetricsIcon.svelte new file mode 100644 index 0000000000..be550a88fc --- /dev/null +++ b/frontend/src/lib/components/icons/BaremetricsIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BitlyIcon.svelte b/frontend/src/lib/components/icons/BitlyIcon.svelte new file mode 100644 index 0000000000..3cc3b43122 --- /dev/null +++ b/frontend/src/lib/components/icons/BitlyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BloggerIcon.svelte b/frontend/src/lib/components/icons/BloggerIcon.svelte new file mode 100644 index 0000000000..ab1d2d262f --- /dev/null +++ b/frontend/src/lib/components/icons/BloggerIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BlueskyIcon.svelte b/frontend/src/lib/components/icons/BlueskyIcon.svelte new file mode 100644 index 0000000000..1f8545e325 --- /dev/null +++ b/frontend/src/lib/components/icons/BlueskyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BoxIcon.svelte b/frontend/src/lib/components/icons/BoxIcon.svelte new file mode 100644 index 0000000000..a6a5d5dc07 --- /dev/null +++ b/frontend/src/lib/components/icons/BoxIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BrevoIcon.svelte b/frontend/src/lib/components/icons/BrevoIcon.svelte new file mode 100644 index 0000000000..2907c346f7 --- /dev/null +++ b/frontend/src/lib/components/icons/BrevoIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BrexIcon.svelte b/frontend/src/lib/components/icons/BrexIcon.svelte new file mode 100644 index 0000000000..5f9fbf8de1 --- /dev/null +++ b/frontend/src/lib/components/icons/BrexIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BrowserlessIcon.svelte b/frontend/src/lib/components/icons/BrowserlessIcon.svelte new file mode 100644 index 0000000000..768d51145c --- /dev/null +++ b/frontend/src/lib/components/icons/BrowserlessIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/BubbleIcon.svelte b/frontend/src/lib/components/icons/BubbleIcon.svelte new file mode 100644 index 0000000000..fdfb049e70 --- /dev/null +++ b/frontend/src/lib/components/icons/BubbleIcon.svelte @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/frontend/src/lib/components/icons/BuildkiteIcon.svelte b/frontend/src/lib/components/icons/BuildkiteIcon.svelte new file mode 100644 index 0000000000..282d782e31 --- /dev/null +++ b/frontend/src/lib/components/icons/BuildkiteIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CalcomIcon.svelte b/frontend/src/lib/components/icons/CalcomIcon.svelte index e11dc76d24..382b8e7a56 100644 --- a/frontend/src/lib/components/icons/CalcomIcon.svelte +++ b/frontend/src/lib/components/icons/CalcomIcon.svelte @@ -18,6 +18,6 @@ > diff --git a/frontend/src/lib/components/icons/CalendlyIcon.svelte b/frontend/src/lib/components/icons/CalendlyIcon.svelte new file mode 100644 index 0000000000..7868621afd --- /dev/null +++ b/frontend/src/lib/components/icons/CalendlyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CircleCiIcon.svelte b/frontend/src/lib/components/icons/CircleCiIcon.svelte new file mode 100644 index 0000000000..92402ac6cd --- /dev/null +++ b/frontend/src/lib/components/icons/CircleCiIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CiscoIcon.svelte b/frontend/src/lib/components/icons/CiscoIcon.svelte new file mode 100644 index 0000000000..d2d4b14bd8 --- /dev/null +++ b/frontend/src/lib/components/icons/CiscoIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ClearbitIcon.svelte b/frontend/src/lib/components/icons/ClearbitIcon.svelte new file mode 100644 index 0000000000..fc150c1dfe --- /dev/null +++ b/frontend/src/lib/components/icons/ClearbitIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ClerkIcon.svelte b/frontend/src/lib/components/icons/ClerkIcon.svelte new file mode 100644 index 0000000000..21baeae38d --- /dev/null +++ b/frontend/src/lib/components/icons/ClerkIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CloseIcon.svelte b/frontend/src/lib/components/icons/CloseIcon.svelte new file mode 100644 index 0000000000..73e6f414f9 --- /dev/null +++ b/frontend/src/lib/components/icons/CloseIcon.svelte @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CloudinaryIcon.svelte b/frontend/src/lib/components/icons/CloudinaryIcon.svelte new file mode 100644 index 0000000000..a61f3bbe7c --- /dev/null +++ b/frontend/src/lib/components/icons/CloudinaryIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CockroachDbIcon.svelte b/frontend/src/lib/components/icons/CockroachDbIcon.svelte new file mode 100644 index 0000000000..d88f7c52de --- /dev/null +++ b/frontend/src/lib/components/icons/CockroachDbIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CodaIcon.svelte b/frontend/src/lib/components/icons/CodaIcon.svelte new file mode 100644 index 0000000000..d8486224d9 --- /dev/null +++ b/frontend/src/lib/components/icons/CodaIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CohereIcon.svelte b/frontend/src/lib/components/icons/CohereIcon.svelte new file mode 100644 index 0000000000..c0a4aea8ee --- /dev/null +++ b/frontend/src/lib/components/icons/CohereIcon.svelte @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte b/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte new file mode 100644 index 0000000000..770b60f40a --- /dev/null +++ b/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/CoinbaseIcon.svelte b/frontend/src/lib/components/icons/CoinbaseIcon.svelte new file mode 100644 index 0000000000..b58a45fff0 --- /dev/null +++ b/frontend/src/lib/components/icons/CoinbaseIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ConfluenceIcon.svelte b/frontend/src/lib/components/icons/ConfluenceIcon.svelte new file mode 100644 index 0000000000..60dbcef880 --- /dev/null +++ b/frontend/src/lib/components/icons/ConfluenceIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ContentfulIcon.svelte b/frontend/src/lib/components/icons/ContentfulIcon.svelte new file mode 100644 index 0000000000..2af55d48fc --- /dev/null +++ b/frontend/src/lib/components/icons/ContentfulIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ConvertKitIcon.svelte b/frontend/src/lib/components/icons/ConvertKitIcon.svelte new file mode 100644 index 0000000000..2fbcf0e53f --- /dev/null +++ b/frontend/src/lib/components/icons/ConvertKitIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DatoCmsIcon.svelte b/frontend/src/lib/components/icons/DatoCmsIcon.svelte new file mode 100644 index 0000000000..d462832e17 --- /dev/null +++ b/frontend/src/lib/components/icons/DatoCmsIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DeelIcon.svelte b/frontend/src/lib/components/icons/DeelIcon.svelte new file mode 100644 index 0000000000..0b93a4a98f --- /dev/null +++ b/frontend/src/lib/components/icons/DeelIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DeepLIcon.svelte b/frontend/src/lib/components/icons/DeepLIcon.svelte new file mode 100644 index 0000000000..028b061dc0 --- /dev/null +++ b/frontend/src/lib/components/icons/DeepLIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DigitalOceanIcon.svelte b/frontend/src/lib/components/icons/DigitalOceanIcon.svelte new file mode 100644 index 0000000000..206e395175 --- /dev/null +++ b/frontend/src/lib/components/icons/DigitalOceanIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DiscourseIcon.svelte b/frontend/src/lib/components/icons/DiscourseIcon.svelte new file mode 100644 index 0000000000..fd1e53f9a3 --- /dev/null +++ b/frontend/src/lib/components/icons/DiscourseIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DocusignIcon.svelte b/frontend/src/lib/components/icons/DocusignIcon.svelte new file mode 100644 index 0000000000..91d96bac36 --- /dev/null +++ b/frontend/src/lib/components/icons/DocusignIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DropboxIcon.svelte b/frontend/src/lib/components/icons/DropboxIcon.svelte new file mode 100644 index 0000000000..3facd1dda2 --- /dev/null +++ b/frontend/src/lib/components/icons/DropboxIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/EdgeDbIcon.svelte b/frontend/src/lib/components/icons/EdgeDbIcon.svelte index 2b94ecb6d0..cc31f328d4 100644 --- a/frontend/src/lib/components/icons/EdgeDbIcon.svelte +++ b/frontend/src/lib/components/icons/EdgeDbIcon.svelte @@ -1,30 +1,26 @@ - - - + + + diff --git a/frontend/src/lib/components/icons/EventbriteIcon.svelte b/frontend/src/lib/components/icons/EventbriteIcon.svelte new file mode 100644 index 0000000000..f851a933f3 --- /dev/null +++ b/frontend/src/lib/components/icons/EventbriteIcon.svelte @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/FigmaIcon.svelte b/frontend/src/lib/components/icons/FigmaIcon.svelte new file mode 100644 index 0000000000..e31fe0a71d --- /dev/null +++ b/frontend/src/lib/components/icons/FigmaIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/FlyIcon.svelte b/frontend/src/lib/components/icons/FlyIcon.svelte new file mode 100644 index 0000000000..02118212b2 --- /dev/null +++ b/frontend/src/lib/components/icons/FlyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/FreshdeskIcon.svelte b/frontend/src/lib/components/icons/FreshdeskIcon.svelte new file mode 100644 index 0000000000..72f2d7b1fa --- /dev/null +++ b/frontend/src/lib/components/icons/FreshdeskIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/FrontAppIcon.svelte b/frontend/src/lib/components/icons/FrontAppIcon.svelte new file mode 100644 index 0000000000..dfba63c8b7 --- /dev/null +++ b/frontend/src/lib/components/icons/FrontAppIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/GhostCmsIcon.svelte b/frontend/src/lib/components/icons/GhostCmsIcon.svelte new file mode 100644 index 0000000000..018960d9b7 --- /dev/null +++ b/frontend/src/lib/components/icons/GhostCmsIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/GiphyIcon.svelte b/frontend/src/lib/components/icons/GiphyIcon.svelte new file mode 100644 index 0000000000..521a358840 --- /dev/null +++ b/frontend/src/lib/components/icons/GiphyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/GitBookIcon.svelte b/frontend/src/lib/components/icons/GitBookIcon.svelte new file mode 100644 index 0000000000..b310c868f7 --- /dev/null +++ b/frontend/src/lib/components/icons/GitBookIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/GroqIcon.svelte b/frontend/src/lib/components/icons/GroqIcon.svelte new file mode 100644 index 0000000000..e8a32f8328 --- /dev/null +++ b/frontend/src/lib/components/icons/GroqIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/HoneybadgerIcon.svelte b/frontend/src/lib/components/icons/HoneybadgerIcon.svelte new file mode 100644 index 0000000000..cad2bb50d1 --- /dev/null +++ b/frontend/src/lib/components/icons/HoneybadgerIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/HttpIcon.svelte b/frontend/src/lib/components/icons/HttpIcon.svelte index eb0833ed0e..f6a29899d1 100644 --- a/frontend/src/lib/components/icons/HttpIcon.svelte +++ b/frontend/src/lib/components/icons/HttpIcon.svelte @@ -1,10 +1,10 @@ diff --git a/frontend/src/lib/components/icons/IftttIcon.svelte b/frontend/src/lib/components/icons/IftttIcon.svelte new file mode 100644 index 0000000000..166a28a3c3 --- /dev/null +++ b/frontend/src/lib/components/icons/IftttIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/IntercomIcon.svelte b/frontend/src/lib/components/icons/IntercomIcon.svelte new file mode 100644 index 0000000000..718a8179c0 --- /dev/null +++ b/frontend/src/lib/components/icons/IntercomIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/JoomlaIcon.svelte b/frontend/src/lib/components/icons/JoomlaIcon.svelte new file mode 100644 index 0000000000..34916abd9b --- /dev/null +++ b/frontend/src/lib/components/icons/JoomlaIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/LineIcon.svelte b/frontend/src/lib/components/icons/LineIcon.svelte new file mode 100644 index 0000000000..a33626338f --- /dev/null +++ b/frontend/src/lib/components/icons/LineIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/LinearIcon.svelte b/frontend/src/lib/components/icons/LinearIcon.svelte new file mode 100644 index 0000000000..e30e78639b --- /dev/null +++ b/frontend/src/lib/components/icons/LinearIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/LinodeIcon.svelte b/frontend/src/lib/components/icons/LinodeIcon.svelte new file mode 100644 index 0000000000..1a3c80e92e --- /dev/null +++ b/frontend/src/lib/components/icons/LinodeIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/LumaAiIcon.svelte b/frontend/src/lib/components/icons/LumaAiIcon.svelte new file mode 100644 index 0000000000..e98a2d3a4f --- /dev/null +++ b/frontend/src/lib/components/icons/LumaAiIcon.svelte @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/MSTeamsIcon.svelte b/frontend/src/lib/components/icons/MSTeamsIcon.svelte index d761a645a0..cbe8589023 100644 --- a/frontend/src/lib/components/icons/MSTeamsIcon.svelte +++ b/frontend/src/lib/components/icons/MSTeamsIcon.svelte @@ -1,78 +1,62 @@ - - - - - + diff --git a/frontend/src/lib/components/icons/MagentoIcon.svelte b/frontend/src/lib/components/icons/MagentoIcon.svelte new file mode 100644 index 0000000000..4723fd7d35 --- /dev/null +++ b/frontend/src/lib/components/icons/MagentoIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MailchimpIcon.svelte b/frontend/src/lib/components/icons/MailchimpIcon.svelte index 55930c704b..2e1f756585 100644 --- a/frontend/src/lib/components/icons/MailchimpIcon.svelte +++ b/frontend/src/lib/components/icons/MailchimpIcon.svelte @@ -1,23 +1,35 @@ - - - - - - - + + + + + + + - \ No newline at end of file diff --git a/frontend/src/lib/components/icons/MandrillIcon.svelte b/frontend/src/lib/components/icons/MandrillIcon.svelte new file mode 100644 index 0000000000..c0098e9594 --- /dev/null +++ b/frontend/src/lib/components/icons/MandrillIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MauticIcon.svelte b/frontend/src/lib/components/icons/MauticIcon.svelte new file mode 100644 index 0000000000..f040bf76d6 --- /dev/null +++ b/frontend/src/lib/components/icons/MauticIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MediumIcon.svelte b/frontend/src/lib/components/icons/MediumIcon.svelte new file mode 100644 index 0000000000..7e96527f85 --- /dev/null +++ b/frontend/src/lib/components/icons/MediumIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MiroIcon.svelte b/frontend/src/lib/components/icons/MiroIcon.svelte new file mode 100644 index 0000000000..64634c09b4 --- /dev/null +++ b/frontend/src/lib/components/icons/MiroIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MistralIcon.svelte b/frontend/src/lib/components/icons/MistralIcon.svelte new file mode 100644 index 0000000000..efe6881da7 --- /dev/null +++ b/frontend/src/lib/components/icons/MistralIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MixpanelIcon.svelte b/frontend/src/lib/components/icons/MixpanelIcon.svelte new file mode 100644 index 0000000000..aab286a515 --- /dev/null +++ b/frontend/src/lib/components/icons/MixpanelIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/MondayIcon.svelte b/frontend/src/lib/components/icons/MondayIcon.svelte new file mode 100644 index 0000000000..b9069f934a --- /dev/null +++ b/frontend/src/lib/components/icons/MondayIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/NeonDbIcon.svelte b/frontend/src/lib/components/icons/NeonDbIcon.svelte new file mode 100644 index 0000000000..eb226a64c1 --- /dev/null +++ b/frontend/src/lib/components/icons/NeonDbIcon.svelte @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/NetlifyIcon.svelte b/frontend/src/lib/components/icons/NetlifyIcon.svelte new file mode 100644 index 0000000000..5f1349d0ba --- /dev/null +++ b/frontend/src/lib/components/icons/NetlifyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/OneSignalIcon.svelte b/frontend/src/lib/components/icons/OneSignalIcon.svelte new file mode 100644 index 0000000000..f52f181cf0 --- /dev/null +++ b/frontend/src/lib/components/icons/OneSignalIcon.svelte @@ -0,0 +1,14 @@ + + + + + + + diff --git a/frontend/src/lib/components/icons/OpenWeatherIcon.svelte b/frontend/src/lib/components/icons/OpenWeatherIcon.svelte new file mode 100644 index 0000000000..709c96e230 --- /dev/null +++ b/frontend/src/lib/components/icons/OpenWeatherIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PagerDutyIcon.svelte b/frontend/src/lib/components/icons/PagerDutyIcon.svelte new file mode 100644 index 0000000000..5b0e9a45e3 --- /dev/null +++ b/frontend/src/lib/components/icons/PagerDutyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PandaDocIcon.svelte b/frontend/src/lib/components/icons/PandaDocIcon.svelte new file mode 100644 index 0000000000..b5191210d9 --- /dev/null +++ b/frontend/src/lib/components/icons/PandaDocIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PaypalIcon.svelte b/frontend/src/lib/components/icons/PaypalIcon.svelte new file mode 100644 index 0000000000..d35b228e7f --- /dev/null +++ b/frontend/src/lib/components/icons/PaypalIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PersonioIcon.svelte b/frontend/src/lib/components/icons/PersonioIcon.svelte new file mode 100644 index 0000000000..e33dbd0817 --- /dev/null +++ b/frontend/src/lib/components/icons/PersonioIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PinterestIcon.svelte b/frontend/src/lib/components/icons/PinterestIcon.svelte new file mode 100644 index 0000000000..ac10cf2d7a --- /dev/null +++ b/frontend/src/lib/components/icons/PinterestIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PipedriveIcon.svelte b/frontend/src/lib/components/icons/PipedriveIcon.svelte new file mode 100644 index 0000000000..70cce8aef5 --- /dev/null +++ b/frontend/src/lib/components/icons/PipedriveIcon.svelte @@ -0,0 +1,16 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/PlanetScaleIcon.svelte b/frontend/src/lib/components/icons/PlanetScaleIcon.svelte new file mode 100644 index 0000000000..e2948bffb0 --- /dev/null +++ b/frontend/src/lib/components/icons/PlanetScaleIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PostmarkIcon.svelte b/frontend/src/lib/components/icons/PostmarkIcon.svelte new file mode 100644 index 0000000000..7e2cba89d5 --- /dev/null +++ b/frontend/src/lib/components/icons/PostmarkIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/PusherIcon.svelte b/frontend/src/lib/components/icons/PusherIcon.svelte new file mode 100644 index 0000000000..5f595760ea --- /dev/null +++ b/frontend/src/lib/components/icons/PusherIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/QuickbooksIcon.svelte b/frontend/src/lib/components/icons/QuickbooksIcon.svelte index aec9eebc79..8a24b47488 100644 --- a/frontend/src/lib/components/icons/QuickbooksIcon.svelte +++ b/frontend/src/lib/components/icons/QuickbooksIcon.svelte @@ -1,10 +1,22 @@ - \ No newline at end of file + diff --git a/frontend/src/lib/components/icons/RaindropIcon.svelte b/frontend/src/lib/components/icons/RaindropIcon.svelte new file mode 100644 index 0000000000..06d1fec6f4 --- /dev/null +++ b/frontend/src/lib/components/icons/RaindropIcon.svelte @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/ReadwiseIcon.svelte b/frontend/src/lib/components/icons/ReadwiseIcon.svelte new file mode 100644 index 0000000000..17e97e359b --- /dev/null +++ b/frontend/src/lib/components/icons/ReadwiseIcon.svelte @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/RenderIcon.svelte b/frontend/src/lib/components/icons/RenderIcon.svelte new file mode 100644 index 0000000000..e73b8b232f --- /dev/null +++ b/frontend/src/lib/components/icons/RenderIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ReplicateIcon.svelte b/frontend/src/lib/components/icons/ReplicateIcon.svelte new file mode 100644 index 0000000000..133f2b75ab --- /dev/null +++ b/frontend/src/lib/components/icons/ReplicateIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ResendIcon.svelte b/frontend/src/lib/components/icons/ResendIcon.svelte index 28e51bc152..6d224a5cd6 100644 --- a/frontend/src/lib/components/icons/ResendIcon.svelte +++ b/frontend/src/lib/components/icons/ResendIcon.svelte @@ -8,5 +8,5 @@ - + \ No newline at end of file diff --git a/frontend/src/lib/components/icons/RestIcon.svelte b/frontend/src/lib/components/icons/RestIcon.svelte index eb0833ed0e..f6a29899d1 100644 --- a/frontend/src/lib/components/icons/RestIcon.svelte +++ b/frontend/src/lib/components/icons/RestIcon.svelte @@ -1,10 +1,10 @@ diff --git a/frontend/src/lib/components/icons/RingCentralIcon.svelte b/frontend/src/lib/components/icons/RingCentralIcon.svelte new file mode 100644 index 0000000000..1850e965e8 --- /dev/null +++ b/frontend/src/lib/components/icons/RingCentralIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/RocketChatIcon.svelte b/frontend/src/lib/components/icons/RocketChatIcon.svelte new file mode 100644 index 0000000000..eaadd9f70f --- /dev/null +++ b/frontend/src/lib/components/icons/RocketChatIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/RunPodIcon.svelte b/frontend/src/lib/components/icons/RunPodIcon.svelte new file mode 100644 index 0000000000..c5e94c4f3f --- /dev/null +++ b/frontend/src/lib/components/icons/RunPodIcon.svelte @@ -0,0 +1,13 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/RustIcon.svelte b/frontend/src/lib/components/icons/RustIcon.svelte index 364dece5be..c2bcbf614c 100644 --- a/frontend/src/lib/components/icons/RustIcon.svelte +++ b/frontend/src/lib/components/icons/RustIcon.svelte @@ -1,10 +1,10 @@ - - + diff --git a/frontend/src/lib/components/icons/SalesforceIcon.svelte b/frontend/src/lib/components/icons/SalesforceIcon.svelte new file mode 100644 index 0000000000..48e37a8cd5 --- /dev/null +++ b/frontend/src/lib/components/icons/SalesforceIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/SegmentIcon.svelte b/frontend/src/lib/components/icons/SegmentIcon.svelte new file mode 100644 index 0000000000..c7f6231d73 --- /dev/null +++ b/frontend/src/lib/components/icons/SegmentIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/SentryIcon.svelte b/frontend/src/lib/components/icons/SentryIcon.svelte new file mode 100644 index 0000000000..637923b99b --- /dev/null +++ b/frontend/src/lib/components/icons/SentryIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ServiceNowIcon.svelte b/frontend/src/lib/components/icons/ServiceNowIcon.svelte new file mode 100644 index 0000000000..9b2994ed54 --- /dev/null +++ b/frontend/src/lib/components/icons/ServiceNowIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ShortcutIcon.svelte b/frontend/src/lib/components/icons/ShortcutIcon.svelte new file mode 100644 index 0000000000..f03d6ea6cc --- /dev/null +++ b/frontend/src/lib/components/icons/ShortcutIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/SigNozIcon.svelte b/frontend/src/lib/components/icons/SigNozIcon.svelte new file mode 100644 index 0000000000..7243776c88 --- /dev/null +++ b/frontend/src/lib/components/icons/SigNozIcon.svelte @@ -0,0 +1,13 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/SmartsheetIcon.svelte b/frontend/src/lib/components/icons/SmartsheetIcon.svelte new file mode 100644 index 0000000000..9085ff8cb0 --- /dev/null +++ b/frontend/src/lib/components/icons/SmartsheetIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/SpeechifyIcon.svelte b/frontend/src/lib/components/icons/SpeechifyIcon.svelte new file mode 100644 index 0000000000..e2fc29e099 --- /dev/null +++ b/frontend/src/lib/components/icons/SpeechifyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/SplitwiseIcon.svelte b/frontend/src/lib/components/icons/SplitwiseIcon.svelte new file mode 100644 index 0000000000..3d88d66dd0 --- /dev/null +++ b/frontend/src/lib/components/icons/SplitwiseIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/StravaIcon.svelte b/frontend/src/lib/components/icons/StravaIcon.svelte new file mode 100644 index 0000000000..d0d1d52ca6 --- /dev/null +++ b/frontend/src/lib/components/icons/StravaIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TallyIcon.svelte b/frontend/src/lib/components/icons/TallyIcon.svelte new file mode 100644 index 0000000000..f494f124bb --- /dev/null +++ b/frontend/src/lib/components/icons/TallyIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TelnyxIcon.svelte b/frontend/src/lib/components/icons/TelnyxIcon.svelte new file mode 100644 index 0000000000..a326114085 --- /dev/null +++ b/frontend/src/lib/components/icons/TelnyxIcon.svelte @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/ThreadsIcon.svelte b/frontend/src/lib/components/icons/ThreadsIcon.svelte new file mode 100644 index 0000000000..67a09e86bf --- /dev/null +++ b/frontend/src/lib/components/icons/ThreadsIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TodoistIcon.svelte b/frontend/src/lib/components/icons/TodoistIcon.svelte new file mode 100644 index 0000000000..fee8f85134 --- /dev/null +++ b/frontend/src/lib/components/icons/TodoistIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TogetherAiIcon.svelte b/frontend/src/lib/components/icons/TogetherAiIcon.svelte new file mode 100644 index 0000000000..fd71832e7d --- /dev/null +++ b/frontend/src/lib/components/icons/TogetherAiIcon.svelte @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/TrelloIcon.svelte b/frontend/src/lib/components/icons/TrelloIcon.svelte index c93ecc1b70..b37ffa6eac 100644 --- a/frontend/src/lib/components/icons/TrelloIcon.svelte +++ b/frontend/src/lib/components/icons/TrelloIcon.svelte @@ -1,10 +1,31 @@ - \ No newline at end of file + diff --git a/frontend/src/lib/components/icons/TursoIcon.svelte b/frontend/src/lib/components/icons/TursoIcon.svelte new file mode 100644 index 0000000000..91514bcde0 --- /dev/null +++ b/frontend/src/lib/components/icons/TursoIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TwitchIcon.svelte b/frontend/src/lib/components/icons/TwitchIcon.svelte new file mode 100644 index 0000000000..1b88756005 --- /dev/null +++ b/frontend/src/lib/components/icons/TwitchIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TwitterIcon.svelte b/frontend/src/lib/components/icons/TwitterIcon.svelte new file mode 100644 index 0000000000..55e510c346 --- /dev/null +++ b/frontend/src/lib/components/icons/TwitterIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/TypeformIcon.svelte b/frontend/src/lib/components/icons/TypeformIcon.svelte index c9f0101c0b..6e2f07560d 100644 --- a/frontend/src/lib/components/icons/TypeformIcon.svelte +++ b/frontend/src/lib/components/icons/TypeformIcon.svelte @@ -18,6 +18,6 @@ > diff --git a/frontend/src/lib/components/icons/VercelIcon.svelte b/frontend/src/lib/components/icons/VercelIcon.svelte new file mode 100644 index 0000000000..89be3c8070 --- /dev/null +++ b/frontend/src/lib/components/icons/VercelIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/WebflowIcon.svelte b/frontend/src/lib/components/icons/WebflowIcon.svelte new file mode 100644 index 0000000000..a445732c22 --- /dev/null +++ b/frontend/src/lib/components/icons/WebflowIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/WooCommerceIcon.svelte b/frontend/src/lib/components/icons/WooCommerceIcon.svelte new file mode 100644 index 0000000000..cadf03e232 --- /dev/null +++ b/frontend/src/lib/components/icons/WooCommerceIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/WordpressIcon.svelte b/frontend/src/lib/components/icons/WordpressIcon.svelte new file mode 100644 index 0000000000..b84d5b9c17 --- /dev/null +++ b/frontend/src/lib/components/icons/WordpressIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/XataIcon.svelte b/frontend/src/lib/components/icons/XataIcon.svelte new file mode 100644 index 0000000000..033dd2e331 --- /dev/null +++ b/frontend/src/lib/components/icons/XataIcon.svelte @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/YelpIcon.svelte b/frontend/src/lib/components/icons/YelpIcon.svelte new file mode 100644 index 0000000000..636b466b07 --- /dev/null +++ b/frontend/src/lib/components/icons/YelpIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/YnabIcon.svelte b/frontend/src/lib/components/icons/YnabIcon.svelte new file mode 100644 index 0000000000..24372aab69 --- /dev/null +++ b/frontend/src/lib/components/icons/YnabIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/YoutubeIcon.svelte b/frontend/src/lib/components/icons/YoutubeIcon.svelte new file mode 100644 index 0000000000..674c3e8052 --- /dev/null +++ b/frontend/src/lib/components/icons/YoutubeIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ZendeskIcon.svelte b/frontend/src/lib/components/icons/ZendeskIcon.svelte index c52b7295f2..45423b520e 100644 --- a/frontend/src/lib/components/icons/ZendeskIcon.svelte +++ b/frontend/src/lib/components/icons/ZendeskIcon.svelte @@ -9,7 +9,7 @@ - + diff --git a/frontend/src/lib/components/icons/ZeroTierIcon.svelte b/frontend/src/lib/components/icons/ZeroTierIcon.svelte new file mode 100644 index 0000000000..57bc4ba1a1 --- /dev/null +++ b/frontend/src/lib/components/icons/ZeroTierIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/ZoomIcon.svelte b/frontend/src/lib/components/icons/ZoomIcon.svelte new file mode 100644 index 0000000000..e68d208181 --- /dev/null +++ b/frontend/src/lib/components/icons/ZoomIcon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/frontend/src/lib/components/icons/index.ts b/frontend/src/lib/components/icons/index.ts index 07f74d477f..78767697a1 100644 --- a/frontend/src/lib/components/icons/index.ts +++ b/frontend/src/lib/components/icons/index.ts @@ -104,6 +104,116 @@ import McpIcon from './McpIcon.svelte' import SageIcon from './SageIcon.svelte' import ZohoIcon from './ZohoIcon.svelte' import PocketIdIcon from './PocketIdIcon.svelte' +import DuckDbIcon from './DuckDbIcon.svelte' +import ActiveCampaignIcon from './ActiveCampaignIcon.svelte' +import AlgoliaIcon from './AlgoliaIcon.svelte' +import BambooHrIcon from './BambooHrIcon.svelte' +import BaremetricsIcon from './BaremetricsIcon.svelte' +import BitlyIcon from './BitlyIcon.svelte' +import BloggerIcon from './BloggerIcon.svelte' +import BlueskyIcon from './BlueskyIcon.svelte' +import BoxIcon from './BoxIcon.svelte' +import BrevoIcon from './BrevoIcon.svelte' +import BuildkiteIcon from './BuildkiteIcon.svelte' +import CalendlyIcon from './CalendlyIcon.svelte' +import CircleCiIcon from './CircleCiIcon.svelte' +import CiscoIcon from './CiscoIcon.svelte' +import ClerkIcon from './ClerkIcon.svelte' +import CloudinaryIcon from './CloudinaryIcon.svelte' +import CockroachDbIcon from './CockroachDbIcon.svelte' +import CodaIcon from './CodaIcon.svelte' +import CoinbaseIcon from './CoinbaseIcon.svelte' +import CoinMarketCapIcon from './CoinMarketCapIcon.svelte' +import ConfluenceIcon from './ConfluenceIcon.svelte' +import ContentfulIcon from './ContentfulIcon.svelte' +import DatoCmsIcon from './DatoCmsIcon.svelte' +import DeepLIcon from './DeepLIcon.svelte' +import DigitalOceanIcon from './DigitalOceanIcon.svelte' +import DiscourseIcon from './DiscourseIcon.svelte' +import DocusignIcon from './DocusignIcon.svelte' +import DropboxIcon from './DropboxIcon.svelte' +import EventbriteIcon from './EventbriteIcon.svelte' +import FigmaIcon from './FigmaIcon.svelte' +import FlyIcon from './FlyIcon.svelte' +import FreshdeskIcon from './FreshdeskIcon.svelte' +import FrontAppIcon from './FrontAppIcon.svelte' +import GhostCmsIcon from './GhostCmsIcon.svelte' +import GiphyIcon from './GiphyIcon.svelte' +import GitBookIcon from './GitBookIcon.svelte' +import HoneybadgerIcon from './HoneybadgerIcon.svelte' +import IftttIcon from './IftttIcon.svelte' +import IntercomIcon from './IntercomIcon.svelte' +import LineIcon from './LineIcon.svelte' +import LinearIcon from './LinearIcon.svelte' +import LinodeIcon from './LinodeIcon.svelte' +import MediumIcon from './MediumIcon.svelte' +import MiroIcon from './MiroIcon.svelte' +import MistralIcon from './MistralIcon.svelte' +import MixpanelIcon from './MixpanelIcon.svelte' +import MondayIcon from './MondayIcon.svelte' +import NeonDbIcon from './NeonDbIcon.svelte' +import NetlifyIcon from './NetlifyIcon.svelte' +import OneSignalIcon from './OneSignalIcon.svelte' +import PagerDutyIcon from './PagerDutyIcon.svelte' +import PandaDocIcon from './PandaDocIcon.svelte' +import PaypalIcon from './PaypalIcon.svelte' +import PersonioIcon from './PersonioIcon.svelte' +import PinterestIcon from './PinterestIcon.svelte' +import PipedriveIcon from './PipedriveIcon.svelte' +import PlanetScaleIcon from './PlanetScaleIcon.svelte' +import PostmarkIcon from './PostmarkIcon.svelte' +import PusherIcon from './PusherIcon.svelte' +import RenderIcon from './RenderIcon.svelte' +import ReplicateIcon from './ReplicateIcon.svelte' +import RingCentralIcon from './RingCentralIcon.svelte' +import SalesforceIcon from './SalesforceIcon.svelte' +import SegmentIcon from './SegmentIcon.svelte' +import SentryIcon from './SentryIcon.svelte' +import ServiceNowIcon from './ServiceNowIcon.svelte' +import ShortcutIcon from './ShortcutIcon.svelte' +import SmartsheetIcon from './SmartsheetIcon.svelte' +import StravaIcon from './StravaIcon.svelte' +import ThreadsIcon from './ThreadsIcon.svelte' +import TodoistIcon from './TodoistIcon.svelte' +import TursoIcon from './TursoIcon.svelte' +import TwitchIcon from './TwitchIcon.svelte' +import TwitterIcon from './TwitterIcon.svelte' +import VercelIcon from './VercelIcon.svelte' +import WebflowIcon from './WebflowIcon.svelte' +import WooCommerceIcon from './WooCommerceIcon.svelte' +import WordpressIcon from './WordpressIcon.svelte' +import XataIcon from './XataIcon.svelte' +import YelpIcon from './YelpIcon.svelte' +import YoutubeIcon from './YoutubeIcon.svelte' +import ZoomIcon from './ZoomIcon.svelte' +import CohereIcon from './CohereIcon.svelte' +import TallyIcon from './TallyIcon.svelte' +import ClearbitIcon from './ClearbitIcon.svelte' +import RaindropIcon from './RaindropIcon.svelte' +import MagentoIcon from './MagentoIcon.svelte' +import DeelIcon from './DeelIcon.svelte' +import GroqIcon from './GroqIcon.svelte' +import TogetherAiIcon from './TogetherAiIcon.svelte' +import RunPodIcon from './RunPodIcon.svelte' +import SigNozIcon from './SigNozIcon.svelte' +import ReadwiseIcon from './ReadwiseIcon.svelte' +import LumaAiIcon from './LumaAiIcon.svelte' +import BrexIcon from './BrexIcon.svelte' +import CloseIcon from './CloseIcon.svelte' +import RocketChatIcon from './RocketChatIcon.svelte' +import ApolloIcon from './ApolloIcon.svelte' +import BubbleIcon from './BubbleIcon.svelte' +import JoomlaIcon from './JoomlaIcon.svelte' +import MauticIcon from './MauticIcon.svelte' +import ZeroTierIcon from './ZeroTierIcon.svelte' +import SplitwiseIcon from './SplitwiseIcon.svelte' +import TelnyxIcon from './TelnyxIcon.svelte' +import MandrillIcon from './MandrillIcon.svelte' +import OpenWeatherIcon from './OpenWeatherIcon.svelte' +import YnabIcon from './YnabIcon.svelte' +import SpeechifyIcon from './SpeechifyIcon.svelte' +import ConvertKitIcon from './ConvertKitIcon.svelte' +import BrowserlessIcon from './BrowserlessIcon.svelte' import type { Component } from 'svelte' export const APP_TO_ICON_COMPONENT = { postgresql: PostgresIcon, @@ -194,6 +304,7 @@ export const APP_TO_ICON_COMPONENT = { pushover: PushoverIcon, quickbooks: QuickbooksIcon, ms_teams_webhook: MsTeamsIcon, + teams: MsTeamsIcon, mailgun: MailgunIcon, ipinfo: IpinfoIcon, gworkspace: GoogleIcon, @@ -213,7 +324,119 @@ export const APP_TO_ICON_COMPONENT = { apify: ApifyIcon, mcp: McpIcon, zoho: ZohoIcon, - pocketid: PocketIdIcon + pocketid: PocketIdIcon, + duckdb: DuckDbIcon, + activecampaign: ActiveCampaignIcon, + algolia: AlgoliaIcon, + bamboo_hr: BambooHrIcon, + baremetrics: BaremetricsIcon, + bitly: BitlyIcon, + blogger: BloggerIcon, + bluesky: BlueskyIcon, + box: BoxIcon, + brevo: BrevoIcon, + sendinblue: BrevoIcon, + buildkite: BuildkiteIcon, + calendly: CalendlyIcon, + circleci: CircleCiIcon, + cisco: CiscoIcon, + clerk: ClerkIcon, + cloudinary: CloudinaryIcon, + cockroachdb: CockroachDbIcon, + coda: CodaIcon, + coinbase: CoinbaseIcon, + coinmarketcap: CoinMarketCapIcon, + confluence: ConfluenceIcon, + contentful: ContentfulIcon, + datocms: DatoCmsIcon, + deepl: DeepLIcon, + digitalocean: DigitalOceanIcon, + discourse: DiscourseIcon, + docusign: DocusignIcon, + dropbox: DropboxIcon, + eventbrite: EventbriteIcon, + figma: FigmaIcon, + fly: FlyIcon, + freshdesk: FreshdeskIcon, + frontapp: FrontAppIcon, + ghostcms: GhostCmsIcon, + giphy: GiphyIcon, + gitbook: GitBookIcon, + honeybadger: HoneybadgerIcon, + ifttt: IftttIcon, + intercom: IntercomIcon, + line: LineIcon, + linear: LinearIcon, + linode: LinodeIcon, + medium: MediumIcon, + miro: MiroIcon, + mistral: MistralIcon, + mixpanel: MixpanelIcon, + monday: MondayIcon, + neondb: NeonDbIcon, + netlify: NetlifyIcon, + onesignal: OneSignalIcon, + pagerduty: PagerDutyIcon, + pandadoc: PandaDocIcon, + paypal: PaypalIcon, + personio: PersonioIcon, + pinterest: PinterestIcon, + pipedrive: PipedriveIcon, + planetscale: PlanetScaleIcon, + postmark: PostmarkIcon, + pusher: PusherIcon, + render: RenderIcon, + replicate: ReplicateIcon, + ringcentral: RingCentralIcon, + salesforce: SalesforceIcon, + segment: SegmentIcon, + sentry: SentryIcon, + servicenow: ServiceNowIcon, + shortcut: ShortcutIcon, + smartsheet: SmartsheetIcon, + strava: StravaIcon, + threads: ThreadsIcon, + todoist: TodoistIcon, + turso: TursoIcon, + twitch: TwitchIcon, + twitter: TwitterIcon, + vercel: VercelIcon, + webflow: WebflowIcon, + woocommerce: WooCommerceIcon, + wordpress: WordpressIcon, + xata: XataIcon, + yelp: YelpIcon, + youtube: YoutubeIcon, + zoom: ZoomIcon, + cohere: CohereIcon, + tally: TallyIcon, + clearbit: ClearbitIcon, + raindrop: RaindropIcon, + magento: MagentoIcon, + deel: DeelIcon, + groqai: GroqIcon, + togetherai: TogetherAiIcon, + runpod: RunPodIcon, + signoz: SigNozIcon, + readwise: ReadwiseIcon, + lumaai: LumaAiIcon, + git: GitIcon, + brex: BrexIcon, + close: CloseIcon, + rocketchat: RocketChatIcon, + apollo: ApolloIcon, + bubble: BubbleIcon, + joomla: JoomlaIcon, + mautic: MauticIcon, + zerotier: ZeroTierIcon, + splitwise: SplitwiseIcon, + telnyx: TelnyxIcon, + mandrill: MandrillIcon, + openweather: OpenWeatherIcon, + ynab: YnabIcon, + speechify: SpeechifyIcon, + convertkit: ConvertKitIcon, + browserless: BrowserlessIcon } as unknown as Record // to generate correct svelte package types export { @@ -314,5 +537,115 @@ export { MqttIcon, ApifyIcon, McpIcon, - ZohoIcon + ZohoIcon, + DuckDbIcon, + ActiveCampaignIcon, + AlgoliaIcon, + BambooHrIcon, + BaremetricsIcon, + BitlyIcon, + BloggerIcon, + BlueskyIcon, + BoxIcon, + BrevoIcon, + BuildkiteIcon, + CalendlyIcon, + CircleCiIcon, + CiscoIcon, + ClerkIcon, + CloudinaryIcon, + CockroachDbIcon, + CodaIcon, + CoinbaseIcon, + CoinMarketCapIcon, + ConfluenceIcon, + ContentfulIcon, + DatoCmsIcon, + DeepLIcon, + DigitalOceanIcon, + DiscourseIcon, + DocusignIcon, + DropboxIcon, + EventbriteIcon, + FigmaIcon, + FlyIcon, + FreshdeskIcon, + FrontAppIcon, + GhostCmsIcon, + GiphyIcon, + GitBookIcon, + HoneybadgerIcon, + IftttIcon, + IntercomIcon, + LineIcon, + LinearIcon, + LinodeIcon, + MediumIcon, + MiroIcon, + MistralIcon, + MixpanelIcon, + MondayIcon, + NeonDbIcon, + NetlifyIcon, + OneSignalIcon, + PagerDutyIcon, + PandaDocIcon, + PaypalIcon, + PersonioIcon, + PinterestIcon, + PipedriveIcon, + PlanetScaleIcon, + PostmarkIcon, + PusherIcon, + RenderIcon, + ReplicateIcon, + RingCentralIcon, + SalesforceIcon, + SegmentIcon, + SentryIcon, + ServiceNowIcon, + ShortcutIcon, + SmartsheetIcon, + StravaIcon, + ThreadsIcon, + TodoistIcon, + TursoIcon, + TwitchIcon, + TwitterIcon, + VercelIcon, + WebflowIcon, + WooCommerceIcon, + WordpressIcon, + XataIcon, + YelpIcon, + YoutubeIcon, + ZoomIcon, + CohereIcon, + TallyIcon, + ClearbitIcon, + RaindropIcon, + MagentoIcon, + DeelIcon, + GroqIcon, + TogetherAiIcon, + RunPodIcon, + SigNozIcon, + ReadwiseIcon, + LumaAiIcon, + BrexIcon, + CloseIcon, + RocketChatIcon, + ApolloIcon, + BubbleIcon, + JoomlaIcon, + MauticIcon, + ZeroTierIcon, + SplitwiseIcon, + TelnyxIcon, + MandrillIcon, + OpenWeatherIcon, + YnabIcon, + SpeechifyIcon, + ConvertKitIcon, + BrowserlessIcon } From 920a7f9fa4719015885947b9de0c35e5e618fcc8 Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:04:18 +0100 Subject: [PATCH 054/116] fix: devops getting logged out on workers page (#8416) * fix: devops getting logged out on workers page * rename local vars --- backend/windmill-api-workers/src/lib.rs | 24 +++++++++---------- .../windmill-api-workspaces/src/workspaces.rs | 4 ++-- .../src/lib/components/WorkerGroup.svelte | 6 ++++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/backend/windmill-api-workers/src/lib.rs b/backend/windmill-api-workers/src/lib.rs index 6cfa398ac4..d4a6225e9d 100644 --- a/backend/windmill-api-workers/src/lib.rs +++ b/backend/windmill-api-workers/src/lib.rs @@ -24,7 +24,7 @@ use windmill_common::{ DB, }; -use windmill_api_auth::{require_super_admin, ApiAuthed}; +use windmill_api_auth::{require_devops_role, ApiAuthed}; pub fn global_service() -> Router { Router::new() @@ -98,8 +98,8 @@ async fn list_worker_pings( Extension(user_db): Extension, Query(query): Query, ) -> JsonResult> { - let is_super_admin = require_super_admin(&db, &authed.email).await.is_ok(); - if *HIDE_WORKERS_FOR_NON_ADMINS && !is_super_admin { + let has_devops_role = require_devops_role(&db, &authed.email).await.is_ok(); + if *HIDE_WORKERS_FOR_NON_ADMINS && !has_devops_role { return Ok(Json(vec![])); } let mut tx = user_db.begin(&authed).await?; @@ -117,13 +117,13 @@ async fn list_worker_pings( query.ping_since, per_page as i64, offset as i64, - is_super_admin + has_devops_role ) .fetch_all(&mut *tx) .await?; tx.commit().await?; - let rows = if *TAGS_ARE_SENSITIVE && !is_super_admin { + let rows = if *TAGS_ARE_SENSITIVE && !has_devops_role { rows.into_iter() .map(|mut w| { w.custom_tags = None; @@ -154,8 +154,8 @@ async fn exists_workers_with_tags( // When TAGS_ARE_SENSITIVE is enabled, filter tags based on workspace visibility if *TAGS_ARE_SENSITIVE { - let is_super_admin = require_super_admin(&db, &authed.email).await.is_ok(); - if !is_super_admin { + let has_devops_role = require_devops_role(&db, &authed.email).await.is_ok(); + if !has_devops_role { if let Some(ref workspace) = tags_query.workspace { // Filter to only tags visible in this workspace let custom_tags = CUSTOM_TAGS_PER_WORKSPACE.read().await; @@ -208,8 +208,8 @@ async fn get_custom_tags( return Ok(Json(all_tags)); } if *TAGS_ARE_SENSITIVE { - let is_super_admin = require_super_admin(&db, &authed.email).await.is_ok(); - if !is_super_admin { + let has_devops_role = require_devops_role(&db, &authed.email).await.is_ok(); + if !has_devops_role { return Ok(Json(vec![])); } } @@ -245,7 +245,7 @@ async fn get_queue_metrics( authed: ApiAuthed, Extension(db): Extension, ) -> JsonResult> { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; let queue_metrics = sqlx::query_as!( QueueMetric, @@ -270,7 +270,7 @@ async fn get_queue_counts( authed: ApiAuthed, Extension(db): Extension, ) -> JsonResult> { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; let queue_counts = windmill_common::queue::get_queue_counts(&db).await; Ok(Json(queue_counts)) } @@ -279,7 +279,7 @@ async fn get_queue_running_counts( authed: ApiAuthed, Extension(db): Extension, ) -> JsonResult> { - require_super_admin(&db, &authed.email).await?; + require_devops_role(&db, &authed.email).await?; let queue_running_counts = windmill_common::queue::get_queue_running_counts(&db).await; Ok(Json(queue_running_counts)) } diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index ea6923c6d8..92c604de33 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -6,7 +6,7 @@ * LICENSE-AGPL for a copy of the license. */ -use windmill_api_auth::{require_super_admin, ApiAuthed}; +use windmill_api_auth::{require_devops_role, require_super_admin, ApiAuthed}; use windmill_api_users::users::WorkspaceInvite; use windmill_common::email_oss::send_email_if_possible; use windmill_common::usernames::{get_instance_username_or_create_pending, VALID_USERNAME}; @@ -2586,7 +2586,7 @@ async fn list_workspaces_as_super_admin( Query(pagination): Query, ApiAuthed { email, .. }: ApiAuthed, ) -> JsonResult> { - require_super_admin(&db, &email).await?; + require_devops_role(&db, &email).await?; let (per_page, offset) = paginate(pagination); let mut tx = user_db.begin(&authed).await?; diff --git a/frontend/src/lib/components/WorkerGroup.svelte b/frontend/src/lib/components/WorkerGroup.svelte index c050d2b5c0..fdb0320c2a 100644 --- a/frontend/src/lib/components/WorkerGroup.svelte +++ b/frontend/src/lib/components/WorkerGroup.svelte @@ -230,7 +230,11 @@ let workspaces: Workspace[] = $state([]) async function listWorkspaces() { - workspaces = await WorkspaceService.listWorkspacesAsSuperAdmin() + try { + workspaces = await WorkspaceService.listWorkspacesAsSuperAdmin() + } catch (e) { + console.error('Failed to list workspaces', e) + } } // Centralized permission logic From c4c524fade0193d00e9d87e8ad5c27846eb47acf Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 19:55:22 +0000 Subject: [PATCH 055/116] wac v2 improvements (#8419) * all * sqlx --- ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- backend/windmill-worker/src/bun_executor.rs | 63 +++- backend/windmill-worker/src/wac_executor.rs | 15 + .../lib/components/WorkflowTimeline.svelte | 289 ++++++++++-------- typescript-client/package-lock.json | 4 +- typescript-client/package.json | 2 +- 6 files changed, 240 insertions(+), 135 deletions(-) diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 36ddb8ab9f..713ccb9dd3 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - true + null ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index b4b0b0b112..c3065be7d2 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -2678,6 +2678,33 @@ pub async fn handle_wac_v2_output( error::Error::internal_err(format!("Failed to save approval meta: {e}")) })?; + // Write timeline entry for the approval step + { + let now_str = chrono::Utc::now().to_rfc3339(); + let timeline_val = serde_json::json!({ + "scheduled_for": &now_str, + "started_at": &now_str, + "name": key, + "approval": true, + }); + let step_timeline_key = format!("_step/{}", key); + sqlx::query( + "UPDATE v2_job_status SET workflow_as_code_status = jsonb_set( + COALESCE(workflow_as_code_status, '{}'::jsonb), + ARRAY[$2], + $3 + ) WHERE id = $1", + ) + .bind(&job.id) + .bind(&step_timeline_key) + .bind(&timeline_val) + .execute(&mut *tx) + .await + .map_err(|e| { + error::Error::internal_err(format!("Failed to write approval timeline: {e}")) + })?; + } + // Suspend parent with suspend=1 (waiting for 1 approval event) sqlx::query!( "UPDATE v2_job_queue SET suspend = 1, suspend_until = now() + make_interval(secs => $2) WHERE id = $1", @@ -2743,6 +2770,34 @@ pub async fn handle_wac_v2_output( .await .map_err(|e| error::Error::internal_err(format!("Failed to save checkpoint: {e}")))?; + // Write a "sleep" marker in the timeline. Unlike real steps it + // carries no execution bar — the frontend renders it as a + // minimal label row (e.g. "sleep (2s)"). + { + let now_str = chrono::Utc::now().to_rfc3339(); + let timeline_val = serde_json::json!({ + "scheduled_for": &now_str, + "name": key, + "sleep_duration_s": seconds, + }); + let step_timeline_key = format!("_step/{}", key); + sqlx::query( + "UPDATE v2_job_status SET workflow_as_code_status = jsonb_set( + COALESCE(workflow_as_code_status, '{}'::jsonb), + ARRAY[$2], + $3 + ) WHERE id = $1", + ) + .bind(&job.id) + .bind(&step_timeline_key) + .bind(&timeline_val) + .execute(&mut *tx) + .await + .map_err(|e| { + error::Error::internal_err(format!("Failed to write sleep timeline: {e}")) + })?; + } + // Suspend parent — it will auto-resume when suspend_until passes. // Use suspend=1 (not 0) so the suspended pull query only picks it up // when `suspend_until <= now()`, not via `suspend <= 0`. @@ -2828,8 +2883,12 @@ pub async fn handle_wac_v2_output( error::Error::internal_err(format!("Failed to save WAC checkpoint: {e}")) })?; - // Write timeline entry for the inline step (keyed as _step/) - if let Some(ref sa) = started_at { + // Write timeline entry for the inline step (keyed as _step/). + // Fall back to now() when the client doesn't provide started_at + // (older windmill-client versions omit it). + { + let now_str = chrono::Utc::now().to_rfc3339(); + let sa = started_at.as_deref().unwrap_or(&now_str); let mut timeline_val = serde_json::json!({ "scheduled_for": sa, "started_at": sa, diff --git a/backend/windmill-worker/src/wac_executor.rs b/backend/windmill-worker/src/wac_executor.rs index 1e7ed4b090..208012ccce 100644 --- a/backend/windmill-worker/src/wac_executor.rs +++ b/backend/windmill-worker/src/wac_executor.rs @@ -259,6 +259,21 @@ pub async fn prepare_checkpoint_for_resume( checkpoint.pending_steps = None; save_checkpoint(db, job_id, &checkpoint).await?; + // Update the approval step's timeline entry with duration_ms + let step_timeline_key = format!("_step/{}", approval_key); + sqlx::query( + "UPDATE v2_job_status SET workflow_as_code_status = jsonb_set( + workflow_as_code_status, + ARRAY[$2, 'duration_ms'], + to_jsonb(EXTRACT(EPOCH FROM (now() - (workflow_as_code_status->$2->>'started_at')::timestamptz)) * 1000) + ) WHERE id = $1 AND workflow_as_code_status ? $2", + ) + .bind(job_id) + .bind(&step_timeline_key) + .execute(db) + .await + .ok(); // best-effort + tracing::info!( job_id = %job_id, approval_key = %approval_key, diff --git a/frontend/src/lib/components/WorkflowTimeline.svelte b/frontend/src/lib/components/WorkflowTimeline.svelte index d71a4bc015..037ff62df2 100644 --- a/frontend/src/lib/components/WorkflowTimeline.svelte +++ b/frontend/src/lib/components/WorkflowTimeline.svelte @@ -3,7 +3,7 @@ import { displayDate, msToSec } from '$lib/utils' import { onDestroy } from 'svelte' import { getDbClockNow } from '$lib/forLater' - import { ChevronDown, ChevronRight, Loader2 } from 'lucide-svelte' + import { ChevronDown, ChevronRight, Loader2, Moon, ShieldCheck } from 'lucide-svelte' import TimelineBar from './TimelineBar.svelte' import LogViewer from './LogViewer.svelte' import ObjectViewer from './propertyPicker/ObjectViewer.svelte' @@ -20,7 +20,14 @@ autoExpandResult?: boolean } - let { flow_status, flowDone = false, stepResults = {}, result = undefined, success = true, autoExpandResult = false }: Props = $props() + let { + flow_status, + flowDone = false, + stepResults = {}, + result = undefined, + success = true, + autoExpandResult = false + }: Props = $props() let resultExpanded = $state(false) @@ -54,10 +61,7 @@ flowDone ? Object.values(flow_status).reduce( (a, b) => - Math.max( - a, - b.started_at ? new Date(b.started_at).getTime() + (b.duration_ms ?? 0) : 0 - ), + Math.max(a, b.started_at ? new Date(b.started_at).getTime() + (b.duration_ms ?? 0) : 0), 0 ) : undefined @@ -148,145 +152,172 @@ return ta - tb }) as [k, v] (k)} {@const isInlineStep = isStep(k)} + {@const isSleep = (v as any).sleep_duration_s != undefined} + {@const isApproval = (v as any).approval === true} {@const isRunning = v.duration_ms == undefined && v.started_at != undefined} {@const isDone = v.duration_ms != undefined} {@const isExpanded = expandedRows[k] ?? false}
- - - {#if isExpanded} -
- {#if isInlineStep} - - {@const result = stepResults[stepKey(k)]} - {#if isDone && result !== undefined} -
-
Result
-
- -
-
+ {#if isInlineStep} + + {v.name ?? stepKey(k)} + {:else} -
Step completed (no result)
- {/if} - {:else if loadingJobs[k] && !childJobs[k]} -
- - Loading... -
- {:else if childJobs[k]} - {@const job = childJobs[k]} - - {#if job.logs || isRunning} -
-
Logs
- -
+ e.stopPropagation()} + > + {v.name ?? k} + {/if} +
+
+ {#if min && total} + {@const scheduledFor = v?.scheduled_for + ? new Date(v?.scheduled_for).getTime() + : undefined} + {@const startedAt = v?.started_at ? new Date(v?.started_at).getTime() : undefined} + {@const waitingLen = scheduledFor + ? startedAt + ? startedAt - scheduledFor + : now - scheduledFor + : 0} - - {#if isDone && job.result !== undefined} -
-
Result
-
- -
+
+ {#if isInlineStep} + + {#if startedAt} + + {/if} + {:else} + + {#if startedAt} + + {/if} + {/if}
{/if} - {:else} -
No data available
- {/if} -
+
+ + + {#if isExpanded} +
+ {#if isInlineStep} + + {@const result = stepResults[stepKey(k)]} + {#if isDone && result !== undefined} +
+
Result
+
+ +
+
+ {:else} +
Step completed (no result)
+ {/if} + {:else if loadingJobs[k] && !childJobs[k]} +
+ + Loading... +
+ {:else if childJobs[k]} + {@const job = childJobs[k]} + + {#if job.logs || isRunning} +
+
Logs
+ +
+ {/if} + + + {#if isDone && job.result !== undefined} +
+
Result
+
+ +
+
+ {/if} + {:else} +
No data available
+ {/if} +
+ {/if} {/if}
{/each} diff --git a/typescript-client/package-lock.json b/typescript-client/package-lock.json index a25b51cbcb..20b2270c6e 100644 --- a/typescript-client/package-lock.json +++ b/typescript-client/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-client", - "version": "1.651.1", + "version": "1.999.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-client", - "version": "1.651.1", + "version": "1.999.21", "license": "Apache 2.0", "devDependencies": { "@types/node": "^20.17.16", diff --git a/typescript-client/package.json b/typescript-client/package.json index 2f3d36abf2..1206bae22a 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.658.0", + "version": "1.999.21", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, From 9f10b44c188749d075bfd946c8ae383be0eee67b Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Tue, 17 Mar 2026 16:12:04 -0400 Subject: [PATCH 056/116] =?UTF-8?q?update=20cloudformation=20template=20to?= =?UTF-8?q?=20use=20latest=20cli/images=20+=20fix=20cl=E2=80=A6=20(#8417)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: update cloudformation template to use latest cli/images + fix cleanup script * fix: narrow SG cleanup to k8s-created groups + add CLI install error handling Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../aws-eks-cloudformation/quicklaunch.yaml | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/examples/deploy/aws-eks-cloudformation/quicklaunch.yaml b/examples/deploy/aws-eks-cloudformation/quicklaunch.yaml index 3dda495ebe..5f2b4c42dc 100644 --- a/examples/deploy/aws-eks-cloudformation/quicklaunch.yaml +++ b/examples/deploy/aws-eks-cloudformation/quicklaunch.yaml @@ -58,38 +58,10 @@ Parameters: - false Description: Enable Windmill Enterprise features (requires license key) -Mappings: - RegionMap: - us-east-1: - AMI: ami-0cff7528ff583bf9a - us-east-2: - AMI: ami-0cd3c7f72edd5b06d - us-west-1: - AMI: ami-0d9858aa3c6322f73 - us-west-2: - AMI: ami-098e42ae54c764c35 - ca-central-1: - AMI: ami-00f881f027a6d74a0 - eu-west-1: - AMI: ami-04dd4500af104442f - eu-west-2: - AMI: ami-0eb260c4d5475b901 - eu-west-3: - AMI: ami-05e8e20cef0eaa9d0 - eu-central-1: - AMI: ami-0bad4a5e987bdebde - ap-northeast-1: - AMI: ami-0b7546e839d7ace12 - ap-northeast-2: - AMI: ami-0fd0765afb77bcca7 - ap-southeast-1: - AMI: ami-0c802847a7dd848c0 - ap-southeast-2: - AMI: ami-07620139298af599e - ap-south-1: - AMI: ami-0851b76e8b1bce90b - sa-east-1: - AMI: ami-054a31f1b3bf90920 + LatestAmiId: + Type: AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64 + Description: Latest Amazon Linux 2023 AMI (automatically resolved via SSM) Resources: VPC: @@ -345,7 +317,7 @@ Resources: - EKSNodeGroup - WindmillDB Properties: - ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI] + ImageId: !Ref LatestAmiId InstanceType: t3.micro IamInstanceProfile: !Ref WindmillInstallerInstanceProfile SubnetId: !Ref PublicSubnet1 @@ -358,7 +330,15 @@ Resources: # Install required tools yum update -y - yum install -y aws-cli jq postgresql15 aws-cfn-bootstrap + yum install -y jq postgresql15 aws-cfn-bootstrap unzip + + # Install AWS CLI v2 (yum aws-cli package is v1 and outdated) + echo "Installing AWS CLI v2..." + if ! (curl -sf "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && unzip -q awscliv2.zip && ./aws/install); then + echo "ERROR: Failed to install AWS CLI v2" + exit 1 + fi + rm -rf aws awscliv2.zip # Set up logging directory with correct permissions mkdir -p /var/log/windmill-installer @@ -602,6 +582,8 @@ Resources: ZipFile: | const { ElasticLoadBalancingClient, DescribeLoadBalancersCommand, DeleteLoadBalancerCommand } = require('@aws-sdk/client-elastic-load-balancing'); + const { EC2Client, DescribeSecurityGroupsCommand, + DeleteSecurityGroupCommand } = require('@aws-sdk/client-ec2'); const response = require('cfn-response'); exports.handler = async (event, context) => { @@ -611,6 +593,7 @@ Resources: try { const elb = new ElasticLoadBalancingClient(); + const ec2 = new EC2Client(); const vpcId = event.ResourceProperties.VpcId; // Find and delete Classic Load Balancers in the VPC @@ -628,14 +611,29 @@ Resources: } if (deleted) { - // Wait for deletion to complete console.log('Waiting 30 seconds for load balancer deletion to complete...'); await new Promise(r => setTimeout(r, 30000)); } + // Delete Kubernetes-created security groups (e.g. k8s-elb-*) + const sgResponse = await ec2.send(new DescribeSecurityGroupsCommand({ + Filters: [{ Name: 'vpc-id', Values: [vpcId] }] + })); + + for (const sg of sgResponse.SecurityGroups || []) { + if (sg.GroupName !== 'default' && (sg.GroupName.startsWith('k8s-') || (sg.Tags || []).some(t => t.Key.startsWith('kubernetes.io/')))) { + console.log(`Deleting security group: ${sg.GroupId} (${sg.GroupName})`); + try { + await ec2.send(new DeleteSecurityGroupCommand({ GroupId: sg.GroupId })); + } catch (e) { + console.log(`Could not delete ${sg.GroupId}: ${e.message}`); + } + } + } + return response.send(event, context, response.SUCCESS); } catch (error) { - console.error('Error deleting load balancers:', error); + console.error('Error during VPC cleanup:', error); return response.send(event, context, response.FAILED, {error: error.message}); } }; @@ -662,6 +660,8 @@ Resources: - ec2:DescribeAddresses - ec2:DisassociateAddress - ec2:DescribeNetworkInterfaces + - ec2:DescribeSecurityGroups + - ec2:DeleteSecurityGroup - elasticloadbalancing:DescribeLoadBalancers - elasticloadbalancing:DeleteLoadBalancer - elasticloadbalancingv2:DescribeLoadBalancers From fe051aa22b59cc1c450b14af9c5f203448bb3dd5 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 17 Mar 2026 21:13:04 +0100 Subject: [PATCH 057/116] feat(cli): add --env alias for --branch and environments config alias (#8415) * feat(cli): add --env alias for --branch and environments config alias Add --env as a CLI alias for --branch on sync pull, sync push, workspace bind, and workspace unbind commands. Add environments as a permanent config alias for gitBranches in wmill.yaml. This helps users who use single-branch multi-environment workflows where "branch" terminology is confusing. Co-Authored-By: Claude Opus 4.6 (1M context) * chore: regenerate auto-generated system prompts Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- cli/src/commands/sync/sync.ts | 8 ++++---- cli/src/commands/workspace/workspace.ts | 4 ++-- cli/src/core/conf.ts | 14 ++++++++++++++ cli/src/guidance/skills.ts | 8 ++++---- system_prompts/auto-generated/cli/cli-commands.md | 8 ++++---- system_prompts/auto-generated/prompts.ts | 8 ++++---- .../auto-generated/skills/cli-commands/SKILL.md | 8 ++++---- 7 files changed, 36 insertions(+), 22 deletions(-) diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 5de149cad3..bc0eee2275 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -3488,8 +3488,8 @@ const command = new Command() "Use promotionOverrides from the specified branch instead of regular overrides", ) .option( - "--branch ", - "Override the current git branch (works even outside a git repository)", + "--branch, --env ", + "Override the current git branch/environment (works even outside a git repository)", ) .action(pull as any) .command("push") @@ -3544,8 +3544,8 @@ const command = new Command() "Specify repository path (e.g., u/user/repo) when multiple repositories exist", ) .option( - "--branch ", - "Override the current git branch (works even outside a git repository)", + "--branch, --env ", + "Override the current git branch/environment (works even outside a git repository)", ) .option("--lint", "Run lint validation before pushing") .option( diff --git a/cli/src/commands/workspace/workspace.ts b/cli/src/commands/workspace/workspace.ts index 6124bd1e1c..d82290e743 100644 --- a/cli/src/commands/workspace/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -568,11 +568,11 @@ const command = new Command() .action(listRemote as any) .command("bind") .description("Bind the current Git branch to the active workspace") - .option("--branch ", "Specify branch (defaults to current)") + .option("--branch, --env ", "Specify branch/environment (defaults to current)") .action((opts) => bind(opts as any, true)) .command("unbind") .description("Remove workspace binding from the current Git branch") - .option("--branch ", "Specify branch (defaults to current)") + .option("--branch, --env ", "Specify branch/environment (defaults to current)") .action((opts) => bind(opts as any, false)) .command("fork") .description("Create a forked workspace") diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index 22acd536f0..e72aa14414 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -75,6 +75,8 @@ export interface SyncOptions { }; }; }; + // Alias for gitBranches - for users who prefer environment-based terminology + environments?: SyncOptions["gitBranches"]; // Legacy field - deprecated, use gitBranches instead git_branches?: { commonSpecificItems?: { @@ -231,6 +233,18 @@ export async function readConfigFile(): Promise { } } + // Handle environments -> gitBranches alias (permanent alias, not a deprecation) + if (conf && "environments" in conf) { + if (!conf.gitBranches) { + conf.gitBranches = conf.environments as any; + } else { + log.warn( + "⚠️ Both 'environments' and 'gitBranches' found in wmill.yaml. Using 'gitBranches' and ignoring 'environments'." + ); + } + delete (conf as any).environments; + } + // Handle git_branches to gitBranches migration if (conf && "git_branches" in conf) { if (!conf.gitBranches) { diff --git a/cli/src/guidance/skills.ts b/cli/src/guidance/skills.ts index e97d9babe2..a742d52f28 100644 --- a/cli/src/guidance/skills.ts +++ b/cli/src/guidance/skills.ts @@ -5328,7 +5328,7 @@ sync local with a remote workspaces or the opposite (push or pull) - \`--extra-includes \` - Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string). Useful to still take wmill.yaml into account and act as a second pattern to satisfy - \`--repository \` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - \`--promotion \` - Use promotionOverrides from the specified branch instead of regular overrides - - \`--branch \` - Override the current git branch (works even outside a git repository) + - \`--branch, --env \` - Override the current git branch/environment (works even outside a git repository) - \`sync push\` - Push any local changes and apply them remotely. - \`--yes\` - Push without needing confirmation - \`--dry-run\` - Show changes that would be pushed without actually pushing @@ -5358,7 +5358,7 @@ sync local with a remote workspaces or the opposite (push or pull) - \`--message \` - Include a message that will be added to all scripts/flows/apps updated during this push - \`--parallel \` - Number of changes to process in parallel - \`--repository \` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - - \`--branch \` - Override the current git branch (works even outside a git repository) + - \`--branch, --env \` - Override the current git branch/environment (works even outside a git repository) - \`--lint\` - Run lint validation before pushing - \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks @@ -5460,9 +5460,9 @@ workspace related commands - \`workspace list\` - List local workspace profiles - \`workspace list-remote\` - List workspaces on the remote server that you have access to - \`workspace bind\` - Bind the current Git branch to the active workspace - - \`--branch \` - Specify branch (defaults to current) + - \`--branch, --env \` - Specify branch/environment (defaults to current) - \`workspace unbind\` - Remove workspace binding from the current Git branch - - \`--branch \` - Specify branch (defaults to current) + - \`--branch, --env \` - Specify branch/environment (defaults to current) - \`workspace fork [workspace_name:string] [workspace_id:string]\` - Create a forked workspace - \`--create-workspace-name \` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id. - \`workspace delete-fork \` - Delete a forked workspace and git branch diff --git a/system_prompts/auto-generated/cli/cli-commands.md b/system_prompts/auto-generated/cli/cli-commands.md index 94519d6aaa..4b0f934175 100644 --- a/system_prompts/auto-generated/cli/cli-commands.md +++ b/system_prompts/auto-generated/cli/cli-commands.md @@ -375,7 +375,7 @@ sync local with a remote workspaces or the opposite (push or pull) - `--extra-includes ` - Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string). Useful to still take wmill.yaml into account and act as a second pattern to satisfy - `--repository ` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - `--promotion ` - Use promotionOverrides from the specified branch instead of regular overrides - - `--branch ` - Override the current git branch (works even outside a git repository) + - `--branch, --env ` - Override the current git branch/environment (works even outside a git repository) - `sync push` - Push any local changes and apply them remotely. - `--yes` - Push without needing confirmation - `--dry-run` - Show changes that would be pushed without actually pushing @@ -405,7 +405,7 @@ sync local with a remote workspaces or the opposite (push or pull) - `--message ` - Include a message that will be added to all scripts/flows/apps updated during this push - `--parallel ` - Number of changes to process in parallel - `--repository ` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - - `--branch ` - Override the current git branch (works even outside a git repository) + - `--branch, --env ` - Override the current git branch/environment (works even outside a git repository) - `--lint` - Run lint validation before pushing - `--locks-required` - Fail if scripts or flow inline scripts that need locks have no locks @@ -507,9 +507,9 @@ workspace related commands - `workspace list` - List local workspace profiles - `workspace list-remote` - List workspaces on the remote server that you have access to - `workspace bind` - Bind the current Git branch to the active workspace - - `--branch ` - Specify branch (defaults to current) + - `--branch, --env ` - Specify branch/environment (defaults to current) - `workspace unbind` - Remove workspace binding from the current Git branch - - `--branch ` - Specify branch (defaults to current) + - `--branch, --env ` - Specify branch/environment (defaults to current) - `workspace fork [workspace_name:string] [workspace_id:string]` - Create a forked workspace - `--create-workspace-name ` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id. - `workspace delete-fork ` - Delete a forked workspace and git branch diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index 63d3a702b8..d7f2d14922 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -1749,7 +1749,7 @@ sync local with a remote workspaces or the opposite (push or pull) - \`--extra-includes \` - Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string). Useful to still take wmill.yaml into account and act as a second pattern to satisfy - \`--repository \` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - \`--promotion \` - Use promotionOverrides from the specified branch instead of regular overrides - - \`--branch \` - Override the current git branch (works even outside a git repository) + - \`--branch, --env \` - Override the current git branch/environment (works even outside a git repository) - \`sync push\` - Push any local changes and apply them remotely. - \`--yes\` - Push without needing confirmation - \`--dry-run\` - Show changes that would be pushed without actually pushing @@ -1779,7 +1779,7 @@ sync local with a remote workspaces or the opposite (push or pull) - \`--message \` - Include a message that will be added to all scripts/flows/apps updated during this push - \`--parallel \` - Number of changes to process in parallel - \`--repository \` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - - \`--branch \` - Override the current git branch (works even outside a git repository) + - \`--branch, --env \` - Override the current git branch/environment (works even outside a git repository) - \`--lint\` - Run lint validation before pushing - \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks @@ -1881,9 +1881,9 @@ workspace related commands - \`workspace list\` - List local workspace profiles - \`workspace list-remote\` - List workspaces on the remote server that you have access to - \`workspace bind\` - Bind the current Git branch to the active workspace - - \`--branch \` - Specify branch (defaults to current) + - \`--branch, --env \` - Specify branch/environment (defaults to current) - \`workspace unbind\` - Remove workspace binding from the current Git branch - - \`--branch \` - Specify branch (defaults to current) + - \`--branch, --env \` - Specify branch/environment (defaults to current) - \`workspace fork [workspace_name:string] [workspace_id:string]\` - Create a forked workspace - \`--create-workspace-name \` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id. - \`workspace delete-fork \` - Delete a forked workspace and git branch diff --git a/system_prompts/auto-generated/skills/cli-commands/SKILL.md b/system_prompts/auto-generated/skills/cli-commands/SKILL.md index 8db057690a..b31f479626 100644 --- a/system_prompts/auto-generated/skills/cli-commands/SKILL.md +++ b/system_prompts/auto-generated/skills/cli-commands/SKILL.md @@ -380,7 +380,7 @@ sync local with a remote workspaces or the opposite (push or pull) - `--extra-includes ` - Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string). Useful to still take wmill.yaml into account and act as a second pattern to satisfy - `--repository ` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - `--promotion ` - Use promotionOverrides from the specified branch instead of regular overrides - - `--branch ` - Override the current git branch (works even outside a git repository) + - `--branch, --env ` - Override the current git branch/environment (works even outside a git repository) - `sync push` - Push any local changes and apply them remotely. - `--yes` - Push without needing confirmation - `--dry-run` - Show changes that would be pushed without actually pushing @@ -410,7 +410,7 @@ sync local with a remote workspaces or the opposite (push or pull) - `--message ` - Include a message that will be added to all scripts/flows/apps updated during this push - `--parallel ` - Number of changes to process in parallel - `--repository ` - Specify repository path (e.g., u/user/repo) when multiple repositories exist - - `--branch ` - Override the current git branch (works even outside a git repository) + - `--branch, --env ` - Override the current git branch/environment (works even outside a git repository) - `--lint` - Run lint validation before pushing - `--locks-required` - Fail if scripts or flow inline scripts that need locks have no locks @@ -512,9 +512,9 @@ workspace related commands - `workspace list` - List local workspace profiles - `workspace list-remote` - List workspaces on the remote server that you have access to - `workspace bind` - Bind the current Git branch to the active workspace - - `--branch ` - Specify branch (defaults to current) + - `--branch, --env ` - Specify branch/environment (defaults to current) - `workspace unbind` - Remove workspace binding from the current Git branch - - `--branch ` - Specify branch (defaults to current) + - `--branch, --env ` - Specify branch/environment (defaults to current) - `workspace fork [workspace_name:string] [workspace_id:string]` - Create a forked workspace - `--create-workspace-name ` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id. - `workspace delete-fork ` - Delete a forked workspace and git branch From 8c769aebbf2f957b6f2012ca92cae5c686cf2b64 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 17 Mar 2026 21:14:02 +0100 Subject: [PATCH 058/116] improve analytics (#8418) * [ee] improve analytics: add git sync & AI chat telemetry, HMAC-signed download - Add ai_chat_usage table to track chat sessions (session_id, provider, model, mode, message_count) - Add POST /w/{workspace}/workspaces/log_chat endpoint with upsert on session_id - Frontend fires logAiChat on every sendRequest, using HistoryManager's existing chat ID - EE stats: add git_sync_usage (sync vs promotion repo count) and ai_chat_usage (30-day aggregates) - Replace RSA+AES-GCM encrypted telemetry download with plaintext JSON + HMAC-SHA256 signature - Signature (12 hex chars) included in download filename for verification - Update instance settings telemetry descriptions for both EE and CE Co-Authored-By: Claude Opus 4.6 (1M context) * fix: make StatsDownload struct pub to fix private-interfaces error Co-Authored-By: Claude Opus 4.6 (1M context) * chore: update ee-repo-ref to 878cc2044717e0177228529a50433fe2768e70b5 This commit updates the EE repository reference after PR #464 was merged in windmill-ee-private. Previous ee-repo-ref: 33eb863b6b881bd54ed69a540e0c65d5fe125024 New ee-repo-ref: 878cc2044717e0177228529a50433fe2768e70b5 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: windmill-internal-app[bot] --- ...34600b68125fb55f77ad3abf3333ebab22416.json | 20 +++++++++ ...83030e7a7ce32971a5621aceb738a3673f943.json | 44 +++++++++++++++++++ ...86122e4ceb34cc8993937dff24cdd7ba3fe5f.json | 12 +++++ ...d523ec2219920a64783cd5d2972f1826114cc.json | 17 +++++++ backend/ee-repo-ref.txt | 2 +- .../20260317000000_ai_chat_usage.down.sql | 1 + .../20260317000000_ai_chat_usage.up.sql | 12 +++++ backend/windmill-api-settings/src/lib.rs | 19 ++++++-- .../windmill-api-workspaces/src/workspaces.rs | 26 +++++++++++ backend/windmill-api/openapi.yaml | 44 +++++++++++++++++-- backend/windmill-common/src/stats_oss.rs | 4 +- .../lib/components/InstanceSettings.svelte | 14 ++++-- .../copilot/chat/AIChatManager.svelte.ts | 17 ++++++- .../copilot/chat/HistoryManager.svelte.ts | 4 ++ 14 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 backend/.sqlx/query-1bf189625a4f14e12e0d0510eb534600b68125fb55f77ad3abf3333ebab22416.json create mode 100644 backend/.sqlx/query-3ec92c1682f3ce701028f66f7ce83030e7a7ce32971a5621aceb738a3673f943.json create mode 100644 backend/.sqlx/query-98746829ab854dff922a04822bc86122e4ceb34cc8993937dff24cdd7ba3fe5f.json create mode 100644 backend/.sqlx/query-b64f0f337aed44840cd68f4c81bd523ec2219920a64783cd5d2972f1826114cc.json create mode 100644 backend/migrations/20260317000000_ai_chat_usage.down.sql create mode 100644 backend/migrations/20260317000000_ai_chat_usage.up.sql diff --git a/backend/.sqlx/query-1bf189625a4f14e12e0d0510eb534600b68125fb55f77ad3abf3333ebab22416.json b/backend/.sqlx/query-1bf189625a4f14e12e0d0510eb534600b68125fb55f77ad3abf3333ebab22416.json new file mode 100644 index 0000000000..e7cd2e52b7 --- /dev/null +++ b/backend/.sqlx/query-1bf189625a4f14e12e0d0510eb534600b68125fb55f77ad3abf3333ebab22416.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT git_sync FROM workspace_settings WHERE git_sync IS NOT NULL", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "git_sync", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true + ] + }, + "hash": "1bf189625a4f14e12e0d0510eb534600b68125fb55f77ad3abf3333ebab22416" +} diff --git a/backend/.sqlx/query-3ec92c1682f3ce701028f66f7ce83030e7a7ce32971a5621aceb738a3673f943.json b/backend/.sqlx/query-3ec92c1682f3ce701028f66f7ce83030e7a7ce32971a5621aceb738a3673f943.json new file mode 100644 index 0000000000..3d91feace1 --- /dev/null +++ b/backend/.sqlx/query-3ec92c1682f3ce701028f66f7ce83030e7a7ce32971a5621aceb738a3673f943.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT provider, model, mode,\n COUNT(*)::BIGINT as \"session_count!\",\n COALESCE(SUM(message_count), 0)::BIGINT as \"message_count!\"\n FROM ai_chat_usage\n WHERE created_at > NOW() - INTERVAL '30 days'\n GROUP BY provider, model, mode\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "provider", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "model", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "mode", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "session_count!", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "message_count!", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + null, + null + ] + }, + "hash": "3ec92c1682f3ce701028f66f7ce83030e7a7ce32971a5621aceb738a3673f943" +} diff --git a/backend/.sqlx/query-98746829ab854dff922a04822bc86122e4ceb34cc8993937dff24cdd7ba3fe5f.json b/backend/.sqlx/query-98746829ab854dff922a04822bc86122e4ceb34cc8993937dff24cdd7ba3fe5f.json new file mode 100644 index 0000000000..58f8729225 --- /dev/null +++ b/backend/.sqlx/query-98746829ab854dff922a04822bc86122e4ceb34cc8993937dff24cdd7ba3fe5f.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM ai_chat_usage WHERE created_at < NOW() - INTERVAL '60 days'", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "98746829ab854dff922a04822bc86122e4ceb34cc8993937dff24cdd7ba3fe5f" +} diff --git a/backend/.sqlx/query-b64f0f337aed44840cd68f4c81bd523ec2219920a64783cd5d2972f1826114cc.json b/backend/.sqlx/query-b64f0f337aed44840cd68f4c81bd523ec2219920a64783cd5d2972f1826114cc.json new file mode 100644 index 0000000000..ed4288ea5c --- /dev/null +++ b/backend/.sqlx/query-b64f0f337aed44840cd68f4c81bd523ec2219920a64783cd5d2972f1826114cc.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO ai_chat_usage (session_id, provider, model, mode) VALUES ($1, $2, $3, $4)\n ON CONFLICT (session_id) DO UPDATE SET message_count = ai_chat_usage.message_count + 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "b64f0f337aed44840cd68f4c81bd523ec2219920a64783cd5d2972f1826114cc" +} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 648a079815..ebad6f5c76 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -278a3887f759f9d1146554baa0765518d5bc70f2 +878cc2044717e0177228529a50433fe2768e70b5 diff --git a/backend/migrations/20260317000000_ai_chat_usage.down.sql b/backend/migrations/20260317000000_ai_chat_usage.down.sql new file mode 100644 index 0000000000..5823ee37a1 --- /dev/null +++ b/backend/migrations/20260317000000_ai_chat_usage.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS ai_chat_usage; diff --git a/backend/migrations/20260317000000_ai_chat_usage.up.sql b/backend/migrations/20260317000000_ai_chat_usage.up.sql new file mode 100644 index 0000000000..015a7c4daa --- /dev/null +++ b/backend/migrations/20260317000000_ai_chat_usage.up.sql @@ -0,0 +1,12 @@ +-- Table to track AI chat sessions and message counts for telemetry +CREATE TABLE IF NOT EXISTS ai_chat_usage ( + id BIGSERIAL PRIMARY KEY, + session_id VARCHAR(36) NOT NULL UNIQUE, + provider VARCHAR(50) NOT NULL, + model VARCHAR(255) NOT NULL, + mode VARCHAR(50) NOT NULL, + message_count INT NOT NULL DEFAULT 1, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_ai_chat_usage_created_at ON ai_chat_usage (created_at); diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index e026386474..3166e31bbc 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -579,8 +579,17 @@ pub async fn send_stats(Extension(db): Extension, authed: ApiAuthed) -> Resu Ok("Sent stats".to_string()) } +#[derive(serde::Serialize)] +pub struct StatsDownload { + pub signature: String, + pub data: String, +} + #[cfg(feature = "enterprise")] -pub async fn get_stats(Extension(db): Extension, authed: ApiAuthed) -> Result { +pub async fn get_stats( + Extension(db): Extension, + authed: ApiAuthed, +) -> error::JsonResult { require_super_admin(&db, &authed.email).await?; let stats = windmill_common::stats_oss::get_stats_payload( &db, @@ -588,12 +597,14 @@ pub async fn get_stats(Extension(db): Extension, authed: ApiAuthed) -> Resul false, ) .await?; - let encrypted = windmill_common::stats_oss::encrypt_stats(&stats)?; - Ok(encrypted) + let json = + serde_json::to_string(&stats).map_err(|e| error::Error::InternalErr(e.to_string()))?; + let signature = windmill_common::stats_oss::sign_stats(&json); + Ok(axum::Json(StatsDownload { signature, data: json })) } #[cfg(not(feature = "enterprise"))] -pub async fn get_stats() -> Result { +pub async fn get_stats() -> error::JsonResult { Err(error::Error::BadRequest( "Downloading telemetry is only available on enterprise edition".to_string(), )) diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index 92c604de33..cc2cb0c797 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -157,6 +157,7 @@ pub fn workspaced_service() -> Router { "/protection_rules/:rule_name", post(update_protection_rule).delete(delete_protection_rule), ) + .route("/log_chat", post(log_ai_chat)) } pub fn global_service() -> Router { Router::new() @@ -5372,3 +5373,28 @@ async fn compare_two_folders( exists_in_fork: target_folder.is_some(), }); } + +#[derive(Deserialize)] +struct LogAiChatPayload { + session_id: String, + provider: String, + model: String, + mode: String, +} + +async fn log_ai_chat( + Extension(db): Extension, + Json(payload): Json, +) -> Result { + sqlx::query!( + "INSERT INTO ai_chat_usage (session_id, provider, model, mode) VALUES ($1, $2, $3, $4) + ON CONFLICT (session_id) DO UPDATE SET message_count = ai_chat_usage.message_count + 1", + &payload.session_id, + &payload.provider, + &payload.model, + &payload.mode + ) + .execute(&db) + .await?; + Ok(StatusCode::NO_CONTENT) +} diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 5b88630d71..4213eb39e7 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1370,17 +1370,22 @@ paths: /settings/get_stats: get: - summary: get encrypted telemetry stats (EE only) + summary: get telemetry stats with HMAC signature (EE only) operationId: getStats tags: - setting responses: "200": - description: base64-encoded encrypted telemetry blob + description: telemetry stats JSON with signature content: - text/plain: + application/json: schema: - type: string + type: object + properties: + signature: + type: string + data: + type: string /settings/latest_key_renewal_attempt: get: @@ -4455,6 +4460,37 @@ paths: type: string "404": description: protection rule not found + /w/{workspace}/workspaces/log_chat: + post: + summary: log AI chat message + operationId: logAiChat + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - session_id + - provider + - model + - mode + properties: + session_id: + type: string + provider: + type: string + model: + type: string + mode: + type: string + responses: + "204": + description: logged /w/{workspace}/workspaces/public_app_rate_limit: post: summary: Set public app rate limit for this workspace diff --git a/backend/windmill-common/src/stats_oss.rs b/backend/windmill-common/src/stats_oss.rs index d3cc2e2b44..99a3c49bc0 100644 --- a/backend/windmill-common/src/stats_oss.rs +++ b/backend/windmill-common/src/stats_oss.rs @@ -67,7 +67,7 @@ pub async fn get_stats_payload( } #[cfg(not(feature = "private"))] -pub fn encrypt_stats(_stats: &Stats) -> Result { +pub fn sign_stats(_json: &str) -> String { // stats details are closed source - Ok(String::new()) + String::new() } diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index 9049b9b08a..18514b3836 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -268,12 +268,13 @@ async function downloadStats() { try { downloadingStats = true - const encryptedData = await SettingService.getStats() - const blob = new Blob([encryptedData], { type: 'application/octet-stream' }) + const result = await SettingService.getStats() + const blob = new Blob([result.data ?? ''], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url - a.download = `windmill-telemetry-${new Date().toISOString().split('T')[0]}.enc` + const date = new Date().toISOString().split('T')[0] + a.download = `windmill-telemetry-${date}-${result.signature}.json` document.body.appendChild(a) a.click() document.body.removeChild(a) @@ -977,6 +978,10 @@
When minimal telemetry is disabled, the following is also collected:
  • job usage (language, total duration, count)
  • +
  • git sync repo count (sync vs promotion mode)
  • +
  • AI chat usage (provider, model, mode, session count, message count — last 30 days)

For air-gapped instances, you can download the telemetry data and send it manually. @@ -1012,6 +1017,9 @@
  • worker usage (worker, worker instance, vCPUs, memory)
  • user usage (author count, operator count)
  • development instance status
  • +
  • AI chat usage (provider, model, mode, session count, message count — last 30 days)
  • {/if} diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index 9ba412fb4a..00a1a11fdf 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -1,4 +1,5 @@ import type { AIProviderModel, ScriptLang } from '$lib/gen/types.gen' +import { WorkspaceService } from '$lib/gen' import type { FlowOptions, ScriptOptions } from './ContextManager.svelte' import { flowTools, @@ -42,7 +43,8 @@ import { getStringError } from './utils' import type { FlowModuleState, FlowState } from '$lib/components/flows/flowState' import type { CurrentEditor, ExtendedOpenFlow } from '$lib/components/flows/types' import { untrack } from 'svelte' -import { type DBSchemas } from '$lib/stores' +import { get } from 'svelte/store' +import { workspaceStore, type DBSchemas } from '$lib/stores' import { askTools, prepareAskSystemMessage, prepareAskUserMessage } from './ask/core' import { chatState, DEFAULT_SIZE, triggerablesByAi } from './sharedChatState.svelte' import type { @@ -660,6 +662,19 @@ class AIChatManager { this.#automaticScroll = true this.abortController = new AbortController() + const model = tryGetCurrentModel() + if (model) { + WorkspaceService.logAiChat({ + workspace: get(workspaceStore) ?? '', + requestBody: { + session_id: this.historyManager.getCurrentChatId(), + provider: model.provider, + model: model.model, + mode: this.mode + } + }).catch(() => {}) + } + if (this.mode === AIMode.FLOW && !this.flowAiChatHelpers) { throw new Error('No flow helpers found') } diff --git a/frontend/src/lib/components/copilot/chat/HistoryManager.svelte.ts b/frontend/src/lib/components/copilot/chat/HistoryManager.svelte.ts index d4396e0ea6..87be590dcc 100644 --- a/frontend/src/lib/components/copilot/chat/HistoryManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/HistoryManager.svelte.ts @@ -65,6 +65,10 @@ export default class HistoryManager { this.indexDB?.close() } + getCurrentChatId() { + return this.currentChatId + } + getPastChats() { return this.pastChats } From ebf9347d3fd876689dba58bc24399e9036ef5b67 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:14:33 +0100 Subject: [PATCH 059/116] fix: Folders as presets in FilterSearchbar (#8409) * Folder presets in filter search bar * nit max preset height --- .../src/lib/components/FilterSearchbar.svelte | 2 +- .../(root)/(logged)/resources/+page.svelte | 21 ++++++++++++------- .../(root)/(logged)/variables/+page.svelte | 21 ++++++++++++------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/FilterSearchbar.svelte b/frontend/src/lib/components/FilterSearchbar.svelte index 8b902a8896..0161e21dd9 100644 --- a/frontend/src/lib/components/FilterSearchbar.svelte +++ b/frontend/src/lib/components/FilterSearchbar.svelte @@ -604,7 +604,7 @@ {#if !currentTag || !schema[currentTag]} {#if presets.length}
    Presets
    -
    +
    {#each presets as preset} {@render presetTag(preset)} {/each} diff --git a/frontend/src/routes/(root)/(logged)/resources/+page.svelte b/frontend/src/routes/(root)/(logged)/resources/+page.svelte index eecfd72729..600cc45183 100644 --- a/frontend/src/routes/(root)/(logged)/resources/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/resources/+page.svelte @@ -30,7 +30,7 @@ import Toggle from '$lib/components/Toggle.svelte' import Tooltip from '$lib/components/Tooltip.svelte' import type { ResourceType, WorkspaceDeployUISettings } from '$lib/gen' - import { OauthService, ResourceService, WorkspaceService, type ListableResource } from '$lib/gen' + import { FolderService, OauthService, ResourceService, WorkspaceService, type ListableResource } from '$lib/gen' import { enterpriseLicense, userStore, @@ -127,6 +127,7 @@ }) let showCreateButtons = $state(false) + let folders: string[] = $state([]) // FilterSearchbar setup let userFoldersFilterType = $derived( @@ -147,6 +148,12 @@ }) ) let filters = useUrlSyncedFilterInstance(untrack(() => resourcesFilterSchema)) + let folderPresets = $derived([ + ...folders.map((f) => ({ name: `f/${f}`, value: `path_start:\\ f/${f}/` })), + ...(resourcesFilterSchema.user_folders_only + ? [{ name: resourcesFilterSchema.user_folders_only.label ?? '?', value: 'user_folders_only:\\ true' }] + : []) + ]) async function loadResources(): Promise { resources = await loadResourceInternal(undefined, 'cache,state,app_theme') @@ -538,11 +545,16 @@ }) } }) + async function loadFolders() { + folders = await FolderService.listFolderNames({ workspace: $workspaceStore! }) + } + $effect(() => { if ($workspaceStore && $userStore) { untrack(() => { loadResources() loadResourceTypes() + loadFolders() }) } }) @@ -895,12 +907,7 @@ class="max-w-[26rem] grow" bind:value={filters.val} placeholder="Filter resources..." - presets={[ - { - name: resourcesFilterSchema.user_folders_only?.label ?? '?', - value: 'user_folders_only:\\ true' - } - ]} + presets={folderPresets} />
    diff --git a/frontend/src/routes/(root)/(logged)/variables/+page.svelte b/frontend/src/routes/(root)/(logged)/variables/+page.svelte index 870bf8e7c4..ea48db42cd 100644 --- a/frontend/src/routes/(root)/(logged)/variables/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/variables/+page.svelte @@ -22,7 +22,7 @@ import Tooltip from '$lib/components/Tooltip.svelte' import VariableEditor from '$lib/components/VariableEditor.svelte' import type { ContextualVariable, ListableVariable, WorkspaceDeployUISettings } from '$lib/gen' - import { OauthService, VariableService, WorkspaceService } from '$lib/gen' + import { FolderService, OauthService, VariableService, WorkspaceService } from '$lib/gen' import { enterpriseLicense, userStore, workspaceStore, userWorkspaces } from '$lib/stores' import { sendUserToast } from '$lib/toast' import { canWrite, isOwner, truncate } from '$lib/utils' @@ -51,6 +51,7 @@ // Collect unique values for filter autocomplete let allPaths: string[] = $state([]) let allOwners: string[] = $state([]) + let folders: string[] = $state([]) // FilterSearchbar setup let userFoldersFilterType = $derived( @@ -70,6 +71,12 @@ }) ) let filters = useUrlSyncedFilterInstance(untrack(() => variablesFilterSchema)) + let folderPresets = $derived([ + ...folders.map((f) => ({ name: `f/${f}`, value: `path_start:\\ f/${f}/` })), + ...(variablesFilterSchema.user_folders_only + ? [{ name: variablesFilterSchema.user_folders_only.label ?? '?', value: 'user_folders_only:\\ true' }] + : []) + ]) let contextualVariables: ContextualVariable[] = $state([]) let shareModal: ShareModal | undefined = $state() let variableEditor: VariableEditor | undefined = $state() @@ -175,11 +182,16 @@ sendUserToast(`Variable ${path} was deleted`) } + async function loadFolders() { + folders = await FolderService.listFolderNames({ workspace: $workspaceStore! }) + } + $effect(() => { if ($workspaceStore && $userStore) { untrack(() => { loadVariables() loadContextualVariables() + loadFolders() }) } }) @@ -285,12 +297,7 @@ schema={variablesFilterSchema} bind:value={filters.val} placeholder="Filter variables..." - presets={[ - { - name: variablesFilterSchema.user_folders_only?.label ?? '?', - value: 'user_folders_only:\\ true' - } - ]} + presets={folderPresets} /> {/if} From 7d800f209d1e6590ba1f7e5d5fcfbf1f4c6d6e97 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 20:26:15 +0000 Subject: [PATCH 060/116] chore(main): release 1.659.0 (#8397) * chore(main): release 1.659.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 21 +++ backend/Cargo.lock | 162 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 54 +++++- frontend/package.json | 2 +- lsp/Pipfile | 2 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 15 files changed, 168 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3522f9c43..4364acf6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [1.659.0](https://github.com/windmill-labs/windmill/compare/v1.658.0...v1.659.0) (2026-03-17) + + +### Features + +* add end_user_email claim to OIDC ID tokens ([#8401](https://github.com/windmill-labs/windmill/issues/8401)) ([de5b13b](https://github.com/windmill-labs/windmill/commit/de5b13b840f90e23df1871f80317fdcc2b98174d)) +* add ws_base_url instance setting for WebSocket URL override ([#8405](https://github.com/windmill-labs/windmill/issues/8405)) ([372023e](https://github.com/windmill-labs/windmill/commit/372023e99560885a76e8da3487ae705fd2f861d4)) +* **cli:** add --env alias for --branch and environments config alias ([#8415](https://github.com/windmill-labs/windmill/issues/8415)) ([fe051aa](https://github.com/windmill-labs/windmill/commit/fe051aa22b59cc1c450b14af9c5f203448bb3dd5)) +* DB-backed instance events webhook with superadmin UI ([#8402](https://github.com/windmill-labs/windmill/issues/8402)) ([7d9fb57](https://github.com/windmill-labs/windmill/commit/7d9fb57368ad3b2c719523ef649c9bd5fddf17a5)) +* instance groups instance-level role support ([#8404](https://github.com/windmill-labs/windmill/issues/8404)) ([18b3528](https://github.com/windmill-labs/windmill/commit/18b3528ba4188721d918fd47f0f86a6b41209453)) +* script module mode with CLI sync, preview, and WAC UI improvements ([#8380](https://github.com/windmill-labs/windmill/issues/8380)) ([31d6660](https://github.com/windmill-labs/windmill/commit/31d6660d56cd23d9269133d430b0607d58314229)) +* store hashed tokens instead of plaintext ([#8217](https://github.com/windmill-labs/windmill/issues/8217)) ([f2be625](https://github.com/windmill-labs/windmill/commit/f2be625348ef308e9768d487e110abbd44d27855)) +* workspace-specific registry overrides ([#8406](https://github.com/windmill-labs/windmill/issues/8406)) ([73fe45b](https://github.com/windmill-labs/windmill/commit/73fe45b6cb97ce50d029240c6bd63917b301abe1)) + + +### Bug Fixes + +* devops getting logged out on workers page ([#8416](https://github.com/windmill-labs/windmill/issues/8416)) ([920a7f9](https://github.com/windmill-labs/windmill/commit/920a7f9fa4719015885947b9de0c35e5e618fcc8)) +* Folders as presets in FilterSearchbar ([#8409](https://github.com/windmill-labs/windmill/issues/8409)) ([ebf9347](https://github.com/windmill-labs/windmill/commit/ebf9347d3fd876689dba58bc24399e9036ef5b67)) +* improve OOM killer observability for debugging pod-level kills ([#8398](https://github.com/windmill-labs/windmill/issues/8398)) ([fd41cd1](https://github.com/windmill-labs/windmill/commit/fd41cd12b444fb2439214fcd25536280e5baacb2)) + ## [1.658.0](https://github.com/windmill-labs/windmill/compare/v1.657.2...v1.658.0) (2026-03-16) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 1343e441ad..1b157f616a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -14467,11 +14467,11 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow 0.7.15", + "winnow 1.0.0", ] [[package]] @@ -15741,7 +15741,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-nats", @@ -15808,7 +15808,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15821,7 +15821,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "argon2", @@ -15962,7 +15962,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15985,7 +15985,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15998,7 +15998,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16024,7 +16024,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.658.0" +version = "1.659.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16034,7 +16034,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16051,7 +16051,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16074,7 +16074,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16097,7 +16097,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16113,7 +16113,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16133,7 +16133,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16153,7 +16153,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16167,7 +16167,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-nats", @@ -16195,7 +16195,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16220,7 +16220,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "flate2", @@ -16238,7 +16238,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16259,7 +16259,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16279,7 +16279,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16309,7 +16309,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16336,7 +16336,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.658.0" +version = "1.659.0" dependencies = [ "lazy_static", "serde", @@ -16348,7 +16348,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.658.0" +version = "1.659.0" dependencies = [ "argon2", "axum 0.7.9", @@ -16371,7 +16371,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16385,7 +16385,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.658.0" +version = "1.659.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16416,7 +16416,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.658.0" +version = "1.659.0" dependencies = [ "chrono", "lazy_static", @@ -16430,7 +16430,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16449,7 +16449,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.658.0" +version = "1.659.0" dependencies = [ "aes-gcm", "anyhow", @@ -16548,7 +16548,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.658.0" +version = "1.659.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16567,7 +16567,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.658.0" +version = "1.659.0" dependencies = [ "regex", "serde", @@ -16582,7 +16582,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16606,7 +16606,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "futures", @@ -16623,7 +16623,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.658.0" +version = "1.659.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16639,7 +16639,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -16660,7 +16660,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -16691,7 +16691,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-oauth2", @@ -16715,7 +16715,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-stream", @@ -16749,7 +16749,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "futures", @@ -16767,7 +16767,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.658.0" +version = "1.659.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16776,7 +16776,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "lazy_static", @@ -16788,7 +16788,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "serde_json", @@ -16800,7 +16800,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "gosyn", @@ -16812,7 +16812,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "lazy_static", @@ -16824,7 +16824,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "serde_json", @@ -16836,7 +16836,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "nu-parser", @@ -16847,7 +16847,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16858,7 +16858,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16870,7 +16870,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "rustpython-ast", @@ -16881,7 +16881,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-recursion", @@ -16905,7 +16905,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "lazy_static", @@ -16919,7 +16919,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16936,7 +16936,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "lazy_static", @@ -16950,7 +16950,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "serde", @@ -16962,7 +16962,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "lazy_static", @@ -16980,7 +16980,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -16996,7 +16996,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "rustpython-ast", @@ -17012,7 +17012,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "serde", @@ -17023,7 +17023,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-recursion", @@ -17060,7 +17060,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "const_format", @@ -17098,7 +17098,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.658.0" +version = "1.659.0" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -17109,7 +17109,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-recursion", @@ -17138,7 +17138,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -17161,7 +17161,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17194,7 +17194,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17214,7 +17214,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17248,7 +17248,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17283,7 +17283,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17306,7 +17306,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17330,7 +17330,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-nats", @@ -17354,7 +17354,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17389,7 +17389,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17417,7 +17417,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-trait", @@ -17440,7 +17440,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "bitflags 2.9.4", @@ -17458,7 +17458,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.658.0" +version = "1.659.0" dependencies = [ "anyhow", "async-once-cell", @@ -17565,7 +17565,7 @@ dependencies = [ [[package]] name = "windmill-worker-volumes" -version = "1.658.0" +version = "1.659.0" dependencies = [ "bytes", "futures", @@ -18172,6 +18172,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" + [[package]] name = "winreg" version = "0.50.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2cabf47d41..3cf78e7f6b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.658.0" +version = "1.659.0" authors.workspace = true edition.workspace = true @@ -82,7 +82,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.658.0" +version = "1.659.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 4213eb39e7..f4cd1c9a0b 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.658.0 + version: 1.659.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 6e6459b2c9..86935f23be 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.658.0"; +export const VERSION = "v1.659.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 01d4040f4f..616a2dc4b3 100755 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { workspaceAdd, }; -export const VERSION = "1.658.0"; +export const VERSION = "1.659.0"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9f9a907e19..7500929aa4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.658.0", + "version": "1.659.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.658.0", + "version": "1.659.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -842,6 +842,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -853,6 +854,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -863,6 +865,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1352,6 +1355,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1508,6 +1512,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1524,6 +1529,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1540,6 +1546,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1556,6 +1563,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1572,6 +1580,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1588,6 +1597,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1604,6 +1614,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1620,6 +1631,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1636,6 +1648,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1652,6 +1665,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1668,6 +1682,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1684,6 +1699,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1700,6 +1716,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1716,6 +1733,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1732,6 +1750,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2037,6 +2056,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -6845,7 +6865,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -7344,6 +7364,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7364,6 +7385,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7384,6 +7406,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7404,6 +7427,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7424,6 +7448,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7444,6 +7469,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7464,6 +7490,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7484,6 +7511,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7504,6 +7532,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7524,6 +7553,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7544,6 +7574,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -12119,6 +12150,21 @@ } } }, + "node_modules/svelte-check/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", @@ -12849,7 +12895,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/frontend/package.json b/frontend/package.json index 854d85fa75..0246731c21 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.658.0", + "version": "1.659.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 6cace22096..ae5db6d86e 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.658.0" +wmill = ">=1.659.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 0e9a0a8214..290ebe484b 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.658.0 + version: 1.659.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index d3f906eccc..728d52d5b3 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.658.0' + ModuleVersion = '1.659.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index b0ce39509a..1b4cb18717 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.658.0" +version = "1.659.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 96450d49f6..0637a231ba 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.658.0", + "version": "1.659.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 1206bae22a..c8b1f7131e 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.999.21", + "version": "1.659.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index d460528676..aaa364516b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.658.0 +1.659.0 From 0f261695a3cb2c3a95d16390e54aa7a6ac3e11e7 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 18 Mar 2026 07:49:04 +0000 Subject: [PATCH 061/116] fix: per-tab test panel in script editor for WAC v2 modules (#8422) When switching to a non-main module tab, the test panel now infers args from the module's code and runs the module's code on Test/Cmd+Enter. Per-module args and schema are persisted across tab switches. Co-authored-by: Claude Opus 4.6 (1M context) --- .../src/lib/components/ScriptEditor.svelte | 112 ++++++++++++++---- 1 file changed, 88 insertions(+), 24 deletions(-) diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 4324b45ba6..3da73fa132 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -187,6 +187,10 @@ // Module tab state let activeModuleTab: string | null = $state(null) + // Per-module test panel state (args + schema), persisted across tab switches + let moduleTestState: Record; schema: Schema }> = $state({}) + let testPanelArgs: Record = $state({}) + let testPanelSchema: Schema = $state(emptySchema()) // editorCode is what the editor shows; code always holds the main script content let editorCode: string = $state(code) // Sync editorCode when code changes externally (template reset, copilot, etc.) @@ -200,20 +204,31 @@ function switchToModule(modulePath: string) { if (activeModuleTab !== null && modules && activeModuleTab !== modulePath) { - // Switching from another module: save its content + // Switching from another module: save its content and test state modules[activeModuleTab] = { ...modules[activeModuleTab], content: editorCode } + moduleTestState[activeModuleTab] = { args: testPanelArgs, schema: testPanelSchema } } if (modules && modules[modulePath]) { activeModuleTab = modulePath editorCode = modules[modulePath].content editor?.setCode(editorCode) + // Restore or initialize test state for the new module + if (moduleTestState[modulePath]) { + testPanelArgs = moduleTestState[modulePath].args + testPanelSchema = moduleTestState[modulePath].schema + } else { + testPanelArgs = {} + testPanelSchema = emptySchema() + inferModuleSchema() + } } } function switchToMain() { if (activeModuleTab !== null && modules) { - // Save current module content + // Save current module content and test state modules[activeModuleTab] = { ...modules[activeModuleTab], content: editorCode } + moduleTestState[activeModuleTab] = { args: testPanelArgs, schema: testPanelSchema } } activeModuleTab = null editorCode = code @@ -386,6 +401,7 @@ switchToMain() } delete modules[modulePath] + delete moduleTestState[modulePath] modules = { ...modules } } @@ -423,6 +439,10 @@ delete modules[oldPath] modules[newPath] = { ...mod, language: newLang ?? mod.language } modules = { ...modules } + if (moduleTestState[oldPath]) { + moduleTestState[newPath] = moduleTestState[oldPath] + delete moduleTestState[oldPath] + } if (activeModuleTab === oldPath) { activeModuleTab = newPath } @@ -441,6 +461,7 @@ export function flushModuleState() { if (activeModuleTab !== null && modules) { flushModuleContent() + moduleTestState[activeModuleTab] = { args: testPanelArgs, schema: testPanelSchema } activeModuleTab = null editorCode = code } @@ -614,14 +635,22 @@ jobProgressBar?.reset() // Flush module edits back to modules map before running preview flushModuleContent() + + const testCode = activeModuleTab !== null ? editorCode : code + const testLang = activeModuleTab !== null ? effectiveLang : lang + const testArgs = + activeModuleTab !== null + ? testPanelArgs + : selectedTab === 'preprocessor' || kind === 'preprocessor' + ? { _ENTRYPOINT_OVERRIDE: 'preprocessor', ...(args ?? {}) } + : (args ?? {}) + //@ts-ignore let job = await jobLoader.runPreview( path, - code, - lang, - selectedTab === 'preprocessor' || kind === 'preprocessor' - ? { _ENTRYPOINT_OVERRIDE: 'preprocessor', ...(args ?? {}) } - : (args ?? {}), + testCode, + testLang, + testArgs, tag, undefined, undefined, @@ -642,7 +671,7 @@ } }, undefined, - modules + activeModuleTab !== null ? undefined : modules ) logPanel?.setFocusToLogs() return job @@ -718,6 +747,17 @@ } } + async function inferModuleSchema() { + if (activeModuleTab === null) return + try { + await inferArgs(effectiveLang, editorCode, testPanelSchema) + testPanelSchema = testPanelSchema + moduleTestState[activeModuleTab] = { args: testPanelArgs, schema: testPanelSchema } + } catch (e) { + // Module code may be in-progress; silently ignore + } + } + let gitRepoResourcePickerOpen = $state(false) let commitHashForGitRepo = $derived(ansibleAlternativeExecutionMode?.commit) @@ -1505,7 +1545,11 @@ { if (e.detail) { - args = e.detail + if (activeModuleTab !== null) { + testPanelArgs = e.detail + } else { + args = e.detail + } } }} updateOnBlur={false} @@ -1516,20 +1560,37 @@
    {#key argsRender} - + {#if activeModuleTab !== null} + + {:else} + + {/if} {/key}
    @@ -1554,7 +1615,7 @@ : testIsLoading} {editor} {diffEditor} - {args} + args={activeModuleTab !== null ? testPanelArgs : args} {showCaptures} customUi={customUi?.previewPanel} showCustomResultPanel={showDebugPanel} @@ -1926,6 +1987,7 @@ inferSchema(e.detail) } else { flushModuleContent() + inferModuleSchema() } // Refresh breakpoint positions when code changes (decorations track their lines) if (debugMode && breakpointDecorations.length > 0) { @@ -1937,6 +1999,8 @@ cmdEnterAction={async () => { if (activeModuleTab === null) { await inferSchema(editorCode) + } else { + await inferModuleSchema() } runTest() }} From f481ea4059b4e5cb01273cffeb53ff340e8bd5bd Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 18 Mar 2026 08:15:04 +0000 Subject: [PATCH 062/116] fix(frontend): fix output of resultnode + svelte5 nits (#8424) * fix(frontend): remove banned $bindable('') pattern from ClearableInput Switching format types in the flow input editor caused a props_invalid_value error because ClearableInput used value = $bindable(''), which conflicts with undefined bindings. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(frontend): restore flow result display in result node The fix in #8390 changed updateLastJob() to only use testJob when actively running/streaming, preferring flowStateStore for completed results. But the result node has moduleId='' and no flowStateStore entry, so the early return made it always show the empty state. Add !moduleId to the testJob condition so the result node (which has no flowStateStore entry) still uses testJob as its only data source. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../clearableInput/ClearableInput.svelte | 32 ++++++++----------- .../flows/propPicker/OutputPickerInner.svelte | 5 +-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/frontend/src/lib/components/common/clearableInput/ClearableInput.svelte b/frontend/src/lib/components/common/clearableInput/ClearableInput.svelte index 5e5a28c5fc..3d9a0e9ad7 100644 --- a/frontend/src/lib/components/common/clearableInput/ClearableInput.svelte +++ b/frontend/src/lib/components/common/clearableInput/ClearableInput.svelte @@ -1,25 +1,25 @@ @@ -58,7 +54,7 @@ > {#if type === 'textarea'}