Compare commits

...

116 Commits

Author SHA1 Message Date
Ruben Fiszel
461306ab04 nits 2025-05-06 17:48:41 +02:00
Ruben Fiszel
062e904141 nits 2025-05-06 17:43:21 +02:00
Ruben Fiszel
b0ba8350f7 update 2025-05-06 17:22:08 +02:00
Ruben Fiszel
2adafcd265 orderByRefactor 2025-05-06 17:17:33 +02:00
Ruben Fiszel
9bdd301f52 fix: ansible in agent mode can use inventory.ini 2025-05-06 11:57:08 +02:00
Ruben Fiszel
afc9cf5940 update deps versions (#5703) 2025-05-05 16:19:39 +02:00
pyranota
4fd0561239 feat: critical alert if disk near full (#5549)
* feat: critical alert if disk near full

* update logic to cover edge-case

* update logic

* windows support

* Update README.md

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* make it periodically

* add extra safety

* respect killpills

* do not check every 3 secs

* move to monitor.rs

* rework

* alter readme

* extending functionality

* fix worker_mode

* fix compilation

* fix typo

* make use of AI suggestion

* update ee-repo-ref

* update ee ref

* logs are CE

* update ee repo ref

* remove systemstat from worker crate

* fix comp error + sync cargo.lock

* more comptime fixing

* Update ee-repo-ref.txt

* fix compilation error

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-05-05 08:54:48 +02:00
Ruben Fiszel
815c3b6d00 chore(main): release 1.486.1 (#5701)
* chore(main): release 1.486.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-05-04 18:24:52 +02:00
Ruben Fiszel
36da8aec08 fix: improve MultiSelectWrapper behavior 2025-05-04 18:18:58 +02:00
Ruben Fiszel
d2ad2cdf33 nit feature flag better backend tests 2025-05-04 17:58:06 +02:00
Eugene Chernyavsky
6b2ba3f261 google-genai and google.generativeai requirement from import (#5699) 2025-05-02 19:50:20 +02:00
Ruben Fiszel
d103f5a466 chore(main): release 1.486.0 (#5690)
* chore(main): release 1.486.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-05-02 00:58:16 +02:00
Ruben Fiszel
7eb9d7d46c fix: do not track relative deps for scripts with raw defined deps from CLI (#5696) 2025-05-02 00:37:28 +02:00
Alexander Petric
8ad730b290 extra logging for agent_token flake (#5693)
* extra logging for agent_token flake

* extra logging for agent_token flake
2025-05-01 19:46:52 +02:00
centdix
ec701a9ee7 feat: Integrate MCP with hub (#5685)
* draft

* working draft

* tool to list scripts

* working sse server

* add proxy + parse query params

* working mcp that runs script

* remove useless dependencies

* log context

* update crate

* mcp no proxy

* use custom sdk

* integrate in windmill api

* draft

* put rmcp sdk here

* do not use mcp as crate

* use extensions for get scripts

* remove unused crate

* list actual scripts

* fix

* give schema in list tools

* cleaning + take workspace id from context

* implement calling the script with tool

* cleaning + fix ctrl-c

* make post path a param

* cleaning

* better name for tools

* fix error with tool name

* cleaning

* draft cleaning

* more cleaning

* list script based on settings

* fix query

* add params in openapi

* use rmcp fork from git

* remove files

* cleaning + fix query

* remove settings and use favorite by default

* add flows in tools list

* handle running flows

* remove frontend for mcp settings

* handle resource args

* send list of resource in shema

* handle mcp url setting with token scope

* cleaning

* avoid calling list tools in call tool

* apply scope to flows

* cleaning

* cleaning

* cleaning

* cleaning

* format files

* fix typos

* remove log

* add back missing dispatch

* fix transform for resource-obj + put every resource in description

* transform obj to string

* cleaner code

* better frontend

* cleaner code

* cleaner logic

* add parentheses just in case

* add func to fetch hub scripts

* fix typos

* working fetch and run hub script

* also fetch flows from hub

* improvments

* merge create tool logic

* add integrations in description

* cleaning

* cleaning

* small fix

* get schema for flow

* filter tools fetch by token scope

* remove hub flows

* remove prints

* add hub script integration choice

* higher limits

* cleaning

* fix merge

* better naming for hub scripts

* no workspace for hub

* alow multiple app in one token

* plural

* fix

* cleaning

* add documentation

* fix bad code

* use id directly

* cleaner bindings

* fix disabled condition

* add cancel button + reset apps if not hub token

* reset mcp apps

* combine all/favorites + hub

* small fix
2025-05-01 19:31:56 +02:00
centdix
9c55040e47 fix: Ai Chat: do not send tools if empty + respond even if tool fails (#5692)
* do not send tools if empty

* respond to user request even if tool call fails

* remove test line

* add missing await
2025-05-01 15:36:22 +02:00
Diego Imbert
41c15fc78a feat: Database Manager (#5586)
* hack fix dnd with tick

* DBExplorer table left table selector

* kinda works

* correct table metadata

* separated columnDefs creation logic

* Removed dependency on AppDbExplorer

* (tweak) loadTableMetaData much faster

* nit for darkmode

* DBExplorerDrawerButton

* footer

* count footer

* reload

* update

* fix height

* db explorer btn in resources table

* delete row

* InsertRowDrawerButton

* insert

* refresh on insert and delete

* moved db logic to ts file

* better update ux

* moved all IO upwards

* fix: Remaining svelte 5 bugs (#5563)

* hack fix dnd with tick

* fix: infinite loading in CodeDisplay after update to svelte 5

* regen package-lock

* fix tutorial (#5562)

* fix tutorial first part

* fix tutorial

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>

* feat: Implement sending diff to ai (#5510)

* allow mentioning specific files in instructions

* remove not working highlight implementation

* make highlighted text work

* fix tooltip position

* clean code

* cleaning

* use lib for tooltip positioning

* fix logic

* draft for db in context

* use tools for db in context

* fixes

* cleaning and bug fixes

* fix

* cleaning

* fix when script is db type

* simplify logic

* put schema in context if already here

* fix imports

* fix tooltip position and make it scrollable

* remove console logs

* check if selected is in available

* fix tooltip list

* add back lost logic

* last fix

* fix type errors

* use loaded schema from dbSchemas

* fix typing, content and lang are always there

* remove from context if not available anymore

* add not loaded yet mention if schema not loaded

* add missing callback logic

* fix prompt

* fix usage of updateselectedContext function

* fix styling for white theme

* handle tab and arrows

* fix schemas not being refreshed on contexts

* also refresh displayMessages when dbschemas change

* fix duplicate available contexts

* fix logic for new scripts

* fix new lines inside text area

* implement sending diff in context

* add button in deploy options to ask ai about diff

* also visualize change when asking for diff

* better prompt

* add limit to diff size

* put diff mode toggle in editor bar

* add button to see history from editor

* adjustements

* put see diff button in dropdown

* fixes

* better styling

* highlight if diff mode

* format files

* change buttons based on diffmode

* remove diff after sending message

* fix type error

* smaller buttons

* draft

* use existing editor in diff editor

* fix number of db resources fetches

* fix apply and add buttons on diff mode

* cleaning

* undo ai gen button show

* better buttons

* styling asjustements + show diff in badge

* styling

* fix deployed code check

* cleaning and styling

* better quick actions

* dont send code when analyzing

* remove apply in chat if only code and no diff

* fix bad code refactor

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>

* fix: prevent invalid returned ai completion object errors (#5564)

* fix(frontend): app builder - force json configuration in rich result (#5565)

* feat: make azure a standalone AI provider (#5558)

* feat: make azure a standalone AI provider

* oups

* nit

* fix: openai/azure oauth

* nit

* nits

* feat(frontend) add flow step result viewer (#5398)

* fix(cli): properly handle enabled/disabled updates of schedules

* fix benchmarks

* feat: handle sending selected lines to ai context (#5527)

* allow mentioning specific files in instructions

* remove not working highlight implementation

* make highlighted text work

* fix tooltip position

* clean code

* cleaning

* use lib for tooltip positioning

* fix logic

* draft for db in context

* use tools for db in context

* fixes

* cleaning and bug fixes

* fix

* cleaning

* fix when script is db type

* simplify logic

* put schema in context if already here

* fix imports

* fix tooltip position and make it scrollable

* remove console logs

* check if selected is in available

* fix tooltip list

* add back lost logic

* last fix

* fix type errors

* use loaded schema from dbSchemas

* fix typing, content and lang are always there

* remove from context if not available anymore

* add not loaded yet mention if schema not loaded

* add missing callback logic

* fix prompt

* fix usage of updateselectedContext function

* fix styling for white theme

* handle tab and arrows

* fix schemas not being refreshed on contexts

* also refresh displayMessages when dbschemas change

* fix duplicate available contexts

* fix logic for new scripts

* fix new lines inside text area

* implement sending diff in context

* add button in deploy options to ask ai about diff

* also visualize change when asking for diff

* better prompt

* add limit to diff size

* put diff mode toggle in editor bar

* add button to see history from editor

* adjustements

* put see diff button in dropdown

* fixes

* better styling

* handle adding code piece to context

* add code piece in context

* draft start end markers

* adapt code

* draft

* apply code pieces before sending request

* cleaning

* highlight if diff mode

* format files

* change buttons based on diffmode

* remove diff after sending message

* fix type error

* smaller buttons

* draft

* use existing editor in diff editor

* fix number of db resources fetches

* fix apply and add buttons on diff mode

* cleaning

* undo ai gen button show

* better buttons

* better prompt

* remove console log

* fix merge

* avoid duplicates

* fix merge

* fix

* fix apply logic

* remove useless if

* focus text area + close chat if no selected lines

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>

* fix: flow editor svelte 5 issues (#5567)

* feat: add diff toggle to flow inline scripts (#5550)

* draft flow diff

* add missing import

* cleaning

* code cleaning

* fix for recursive renderings

* fix typo

* cleaning

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>

* fix(frontend): proper each block binding + better app settings reactivity (#5568)

* fix: properly bind to array elements in Svelte each loops

This commit fixes an issue where binding directly to loop variables in Svelte's #each loops doesn't properly update the original array. Instead of binding directly to the loop variable, we now bind to the array elements using index variables.

The pattern used is: - Change: {#each arr as el} -> {#each arr as _, index} - Change: bind:value={el} -> bind:value={arr[index]}

Modified files: - frontend/src/lib/components/ArrayTypeNarrowing.svelte - frontend/src/lib/components/apps/editor/AppInputs.svelte - frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte

* better app settings panel reactivity

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: HugoCasa <hugo@casademont.ch>

* fix: app editor svelte 5 fixes (#5570)

* fix: properly bind to array elements in Svelte each loops

This commit fixes an issue where binding directly to loop variables in Svelte's #each loops doesn't properly update the original array. Instead of binding directly to the loop variable, we now bind to the array elements using index variables.

The pattern used is: - Change: {#each arr as el} -> {#each arr as _, index} - Change: bind:value={el} -> bind:value={arr[index]}

Modified files: - frontend/src/lib/components/ArrayTypeNarrowing.svelte - frontend/src/lib/components/apps/editor/AppInputs.svelte - frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte

* better app settings panel reactivity

* fix: app editor table svelte 5 fixes

---------

Co-authored-by: Guilhem <guilhemlemouel@gmail.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>

* select border (#5571)

* fix: properly bind to array elements in Svelte each loops

This commit fixes an issue where binding directly to loop variables in Svelte's #each loops doesn't properly update the original array. Instead of binding directly to the loop variable, we now bind to the array elements using index variables.

The pattern used is: - Change: {#each arr as el} -> {#each arr as _, index} - Change: bind:value={el} -> bind:value={arr[index]}

Modified files: - frontend/src/lib/components/ArrayTypeNarrowing.svelte - frontend/src/lib/components/apps/editor/AppInputs.svelte - frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte

* better app settings panel reactivity

* fix: app editor table svelte 5 fixes

* fix: select border

---------

Co-authored-by: Guilhem <guilhemlemouel@gmail.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>

* feat: add windmill context to autocomplete (#5548)

* add windmill context to autocomplete

* fix formatting

* remove console log

* do not mention tool call for autocomplete

* apply logic to php

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>

* fix tabs selected behavior change from svelte 5

* fix: fix list jobs by tag

* fix: tenant id to never be undefined on teams (#5572)

* fix: tenant id to never be undefined

* simplify azure oauth

* simplify azure oauth

* update ee ref

* sqlx prepare

* sqlx prepare

* fix: legacy script gen model selection (#5574)

* feat: add wildcards filter for worker/label/tags

* fix: Dynamic select does not work with tag //native (#5576)

closes #5490

* function takes 13 arguments but 14 arguments were supplied (#5577)

* fix(frontend): prevent deploy popover to show if deploy dropdown is open (#5542)

* prevent deploy popover to show if deploy dropdown is open

* wip

* Revert "wip"

This reverts commit 85434654af.

* Revert "prevent deploy popover to show if deploy dropdown is open"

This reverts commit edd9eda156.

* add prop to hide popup fro dropdown

* feat: button can have tooltip

* improve deploy tooltip and dropdown behavior

* rename tooltip to tooltipPopover

* nit

* add deploy button component

* use svelte 5 runes

* use new deploy button for script builder

* add delay to deploy popover when dropdown is open

* add delay to deploy popover when dropdown is open

# Conflicts:
#	frontend/src/lib/components/DeployButton.svelte
#	frontend/src/lib/components/common/button/Button.svelte

* Update frontend/src/lib/components/common/button/Button.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* remove unsused field

* nit

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore: update hub sync script (#5579)

* fix monaco suggestion z-index (#5578)

* fix: validate saved module before passing to flow module editor (#5580)

* fix: freeze when clicking script history diff button (#5581)

* fix: validate saved module before passing to flow module editor

* fix: freeze when clicking script history diff button

* add space (#5582)

* fix: binding not working in nested array script arg (#5585)

* fix: mssql ca_cert deserializing (#5587)

* fix: improve app image picker UX (#5589)

* DBTableAction

* delete table

* fix intempestive error toasts

* fullscreen mode

* rename db explorer to db manager

* use drawer open state instead of oo open method

* create table btn

* factor away sucess text

* basic table creation form

* uniq check

* better select

* better add btn

* extra settings

* create table works in pgsql

* MySQL kinda works

* CRUD works in mysql

* fix lowercase

* allow create table with no schema

* handle default value

* sql repl pane

* execute sql queries

* db ops opt in

* SQL Repl v0

* ux

* UX

* better refresh

* better placeholder sql

* sql code clipboard copy

* handle multiple primary keys in table creation

* fix all fields being required on insert

* fix postgres enum not properly converted

* use InsertRowDrawerButton in App db studio

* insert shortcut

* fix mysql datetime parser for non tz dates

* npm run check

* Revert "fix all fields being required on insert"

This reverts commit 6bec952fb7.

* fk ui

* don't commit .env...

* tweak

* fk ui bindings

* fk dropdowns right values

* schema notation fixes

* handle on delete / update cascade etc

* better loading button without flicker

* fix infinite loop caused by getDbSchemas

* foreign key error validation

* type error

* cache col defs

* fix label hover

* fix fk select overflow

* Fix Select styling

* mssql fixes

* fix wrong typecast failing with mssql

* extract makeLoadTableMetaDataQuery

* Fetch all col defs in one go (mysql)

* loadAllTablesMetaData for postgres

* for some reason factoring transformColumnDefs broke ag infinite table

* mssql loads all coldefs at once

* snowflake preload all col defs

* filter out information schema snowflake

* default schema select

* fix original pg col def logic broken

* Fix ugly flickers

* fix updateGrid before grid ready

* better auto sizing

* smoother CRUD UI refreshes

* fix col defs qury for bigquery

* bigquery works

* nits

* do not change queries used in policies !

* fix runPreviewJobAndPollResult on WINDMILL_TOO_BIG

* select padding

* ellipsis typos

* fix mysterious ugly red flash when mounting ag grid in dark mode only

* Load on click

* Remove schema explorer mode

* repl min size

* fix flash on dark mode

* dirty fix ag grid not refreshing sometimes

* sql repl history

* fix ag theme

* close sql repl result viewer by clicking outside

* Select styling fix in dark mode

* better default query sqlrepl

* Buttons less aggressive

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: centdix <40307056+centdix@users.noreply.github.com>
Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com>
Co-authored-by: Guilhem <guilhemlemouel@gmail.com>
Co-authored-by: Piyush मिश्र <piyushxcoder@gmail.com>
Co-authored-by: Piyush मिश्र <piyush.raj.kit@gmail.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-05-01 10:33:42 +02:00
Ruben Fiszel
09169784bd fix: improve CLI file scanning performances 2025-04-30 19:41:15 +02:00
pyranota
4c660a8869 nit(python): map azure-storage-file-datalake (#5682)
* feat(python): map azure-storage-file-datalake

* add comma
2025-04-30 10:04:10 +02:00
dieriba
dfb947ff37 feat: add run now directly on schedule drawer and duplicate schedule option (#5674)
* add run now on schedule drawer

* add duplicate option for schedules

* fix

* add missing await keywork
2025-04-30 09:36:28 +02:00
Ruben Fiszel
257040ab28 chore(main): release 1.485.3 (#5686)
* chore(main): release 1.485.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-29 23:59:51 +02:00
Alexander Petric
dda92b5491 agent integration tests (#5684)
* agent integration tests

* agent integration tests

* Update integration_tests/requirements.txt

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-29 23:55:38 +02:00
Ruben Fiszel
18dced3c74 fix: improve performance of background cleanup monitoring operations 2025-04-29 23:47:48 +02:00
Ruben Fiszel
c641550eee improve metrics performance 2025-04-29 23:32:30 +02:00
Maxime Thiebaut
fbdaf28fb5 nit(python): add virustotal to imports mapping (#5683) 2025-04-29 22:55:58 +02:00
pyranota
75ee10ea65 fix integration tests failing (#5681)
* fix: make `#(extra_)requirements:` work better with pins

* fix(python): fix integration tests failing
2025-04-29 20:30:34 +02:00
Ruben Fiszel
f120f4ac4b chore(main): release 1.485.2 (#5676)
* chore(main): release 1.485.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-29 20:21:20 +02:00
Ruben Fiszel
46a3c29c9f improve agent workers 2025-04-29 20:15:49 +02:00
pyranota
1ab41603f4 fix: make #(extra_)requirements: work better with pins (#5680) 2025-04-29 19:59:51 +02:00
Ruben Fiszel
60018aadf6 fix: improve agent workers for deployed scripts 2025-04-29 19:35:52 +02:00
Ruben Fiszel
193186ae75 nits 2025-04-29 15:43:30 +02:00
Ruben Fiszel
e298c8eea5 dark mode nit 2025-04-29 15:42:38 +02:00
Ruben Fiszel
f662cf5d75 fix(python): handle better relative imports with requirements or extra_requirements 2025-04-29 15:23:44 +02:00
centdix
418c36ed62 add current workspace if not in workspace list (#5673) 2025-04-28 21:33:34 +02:00
Ruben Fiszel
0b03b06144 chore(main): release 1.485.1 (#5669)
* chore(main): release 1.485.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-28 19:59:25 +02:00
Ruben Fiszel
486ad2a2b5 fix compile 2025-04-28 19:52:37 +02:00
Ruben Fiszel
36b39f2c91 update ee repo 2025-04-28 19:44:55 +02:00
Ruben Fiszel
99c69418e7 border style nit 2025-04-28 19:29:56 +02:00
Ruben Fiszel
cf77ff088b fix: improve mcp mode api 2025-04-28 19:17:24 +02:00
Ruben Fiszel
2fc7960b62 nit error message ai 2025-04-28 18:30:43 +02:00
Diego Imbert
2ea42a626a fix random infinite loop in flow input (#5672)
* fix random infinite loop in flow input

* move to function
2025-04-28 18:28:23 +02:00
centdix
71d400e4d1 fix double apply on ai chat (#5671) 2025-04-28 18:18:48 +02:00
Diego Imbert
35b69da25c fix: skip_flow_update for dependency tracking table (#5670) 2025-04-28 17:57:35 +02:00
centdix
eadae95a42 fix: MCP handle long names + invalid char in prop key + fix for not found resource type (#5668) 2025-04-28 16:46:48 +02:00
Ruben Fiszel
90ebcfb51e nit error for loading inputs 2025-04-28 15:34:31 +02:00
Diego Imbert
2b8af4e314 fix extraConfig overriding defaultColDef (WIN 1183) (#5667)
* fix extraConfig overriding defaultColDef

* Update frontend/src/lib/components/ScriptEditor.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-28 13:35:41 +02:00
Ruben Fiszel
b67de13b78 chore(main): release 1.485.0 (#5664)
* chore(main): release 1.485.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-28 11:45:28 +02:00
centdix
12c32026e5 fix: Fix object handling on some MCP clients + better frontend for MCP (#5663)
* fix transform for resource-obj + put every resource in description

* transform obj to string

* cleaner code

* better frontend

* cleaner code

* cleaner logic

* add parentheses just in case

* fix typos
2025-04-28 11:35:02 +02:00
Ruben Fiszel
654e3e857e slower pull if no tag is available 2025-04-28 00:34:30 +02:00
Ruben Fiszel
72547437fe feat: add universal search to object viewer 2025-04-27 16:30:12 +02:00
Ruben Fiszel
c599857e4a improve output picker floating config 2025-04-27 14:50:27 +02:00
Ruben Fiszel
1b1691837a fix: add svelte 5 boundaries to app components to contain errors 2025-04-27 14:11:49 +02:00
Ruben Fiszel
03553f7f49 chore(main): release 1.484.0 (#5656)
* chore(main): release 1.484.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-27 00:48:47 +02:00
Ruben Fiszel
a7b2b51444 fix: improve skip failure on parallel branchall 2025-04-27 00:37:25 +02:00
Ruben Fiszel
a74815f143 nit flow bind 2025-04-26 22:15:13 +02:00
Ruben Fiszel
02ab233a85 persist flow state better between drawer close/open 2025-04-26 22:03:44 +02:00
Ruben Fiszel
a71f92db35 improve flow history picker 2025-04-26 21:01:12 +02:00
Ruben Fiszel
5e1f47ae29 improve flow script editor performances 2025-04-26 20:53:45 +02:00
Ruben Fiszel
97ae0d80df border nit 2025-04-26 20:45:52 +02:00
Ruben Fiszel
4bb9d64d4f improve flow script editor performances 2025-04-26 20:42:31 +02:00
Ruben Fiszel
42edbfac66 nit reactiveness 2025-04-26 07:56:05 +02:00
Ruben Fiszel
7264618ad8 improve flow script editor responsivity 2025-04-26 00:56:01 +02:00
Ruben Fiszel
8435eb3adf fix: improve full-scaleout of autoscaling event logging 2025-04-25 20:04:21 +02:00
Ruben Fiszel
ac129d9b65 nit fix token 2025-04-25 19:32:05 +02:00
Ruben Fiszel
6e833cbf2f nit fix token 2025-04-25 19:30:29 +02:00
Ruben Fiszel
58140cde2f nit fix token 2025-04-25 19:25:54 +02:00
Ruben Fiszel
2b5dfcfb25 fix: fix token creation after mcp mode change to make it non workspace specific 2025-04-25 19:20:21 +02:00
Alexander Petric
dc5c8d8c5f fix: check for valid teams_channel config when saving critical alerts settings (#5660)
* fix: check for valid teams_channel config when saving critical alerts settings

* Update frontend/src/lib/components/InstanceSettings.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* sqlx prepare

* Update InstanceSettings.svelte

* Update InstanceSettings.svelte

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-04-25 14:27:33 +02:00
centdix
1625524311 feat: Add MCP only mode (#5661)
* add mcp mode

* fix flag usage

* change mcp path

* do not migrate db in mcp mode

* handle null values
2025-04-25 14:22:45 +02:00
wendrul
fdd1642ce1 feat: Ansible improvements (vault, roles and git repos) (#5655)
* Ansible vault + roles

* Clone arbitrary repos

* Fix cloning logic after merge

* Make function for cloning without history any commit

* Cloning repos and lockfile on the commit

* Improve error messages

* Create lockfile for roles and collections

* Simplify ansible ssh identity interface

* Ansible vault password: pass just a variable instead of 2 step approach

* Lock lockfiles for roles and collections

* fix typo

* Change git ssh identity section name

* Rename variable

* Update init script for ansible

* Suppress error when no roles

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-04-24 16:04:28 +02:00
centdix
c4148d7756 add missing ci (#5659) 2025-04-24 14:37:56 +02:00
centdix
b199a77d48 fix: Fix CI for MCP + optimization (#5657)
* add mcp feature to docker build

* avoid deserializing value

* apply fix to flows
2025-04-24 14:14:22 +02:00
centdix
a34ac4fa24 feat: Add MCP endpoints (#5639) 2025-04-23 23:28:41 +02:00
Ruben Fiszel
8d4d2db507 chore(main): release 1.483.2 (#5648)
* chore(main): release 1.483.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-23 19:24:48 +02:00
Guilhem
c41c1eb587 fix(frontend): fix validity check (#5654) 2025-04-23 19:20:46 +02:00
Ruben Fiszel
b146de281b improve warning message for self-approval for superadmins 2025-04-23 19:07:16 +02:00
Ruben Fiszel
60a848af48 improve label filtering with wildcards 2025-04-23 16:05:34 +02:00
dieriba
494d662b2d fix cli gcp types (#5644)
* update types

* fix types cli

* fix ci

* fix gcp types and fix used triggers

* update repo ref revert .env and nits

* fix build

* Update frontend/src/lib/utils.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* fix type and deploy to

* update ee-repo-ref

* update .sqlx

* add missing fields

* fix: add sqlx derive macro

* hanfle missing field, fix deploy to other workspace and update ee-repo-ref

* update repo ref

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-04-23 12:18:36 +02:00
Ruben Fiszel
0a866da274 delay triggering dependent script when lockfile is present to account for CLI push 2025-04-23 09:28:31 +02:00
Ruben Fiszel
e316dbd9bd fix: track relative imports in python and ts even if lockfile is provided 2025-04-23 08:40:22 +02:00
Diego Imbert
444a6abad6 fix: batch reruns query missing workspace_id check in subquery (#5652) 2025-04-22 11:37:18 +02:00
Ruben Fiszel
128f6c1383 fix e2e test 2025-04-21 17:41:24 +02:00
Ruben Fiszel
855a079744 fix .env 2025-04-21 16:53:23 +02:00
Ruben Fiszel
2e25f1559e add disabled to date component 2025-04-21 16:46:37 +02:00
Ruben Fiszel
42514f2b1d add event handlers to more components 2025-04-21 16:45:20 +02:00
Alexander Petric
2620300ce9 AWS cloudformation quicklaunch (#5650) 2025-04-21 16:26:28 +02:00
Diego Imbert
5bca8f60e9 fix: improve MySQL datetime parser timezone handling (WIN-1155) (#5645)
* fix mysql datetime parser for non tz dates

* static regexes
2025-04-21 14:46:28 +02:00
Diego Imbert
80658a4b21 chore: npx sv migrate self-closing-tags (#5647) 2025-04-21 14:43:39 +02:00
dieriba
d4f4bb0295 increse colum length of url column of websocket table (#5646) 2025-04-21 14:43:15 +02:00
Ruben Fiszel
941adea42a make edit and add schema order consistent 2025-04-21 14:39:21 +02:00
Ruben Fiszel
989a4f7e61 make https an env variable for dev 2025-04-21 13:52:26 +02:00
Ruben Fiszel
2bc929b4c6 fix build 2025-04-20 10:07:44 +02:00
Ruben Fiszel
0fdf5e4056 update locks 2025-04-20 02:18:57 +02:00
Ruben Fiszel
8006be5883 fix build 2025-04-20 02:08:50 +02:00
Ruben Fiszel
2e3de4e6b0 fix build 2025-04-20 01:38:26 +02:00
Ruben Fiszel
c769da6022 fix build 2025-04-20 01:34:12 +02:00
Ruben Fiszel
3c68fef2ab ui code builder v0 (at secret path) (#4964) 2025-04-20 01:19:02 +02:00
Ruben Fiszel
e88909aa93 add missing sqlx files for tests 2025-04-19 12:09:11 +02:00
Ruben Fiszel
2bf070ee5c chore(main): release 1.483.1 (#5642)
* chore(main): release 1.483.1

* change versions
2025-04-19 11:31:27 +02:00
Ruben Fiszel
e5595e41b5 fix: pin libxml to 0.3.3 2025-04-19 11:20:00 +02:00
Ruben Fiszel
236762641f chore(main): release 1.483.0 (#5632)
* chore(main): release 1.483.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-04-19 11:06:30 +02:00
Diego Imbert
64c72b6fce fix: output picker output opening doesn't change id (#5641) 2025-04-19 11:00:39 +02:00
dieriba
02a25ba62e rename zoom key for http auth (#5640) 2025-04-19 00:51:42 +02:00
Ruben Fiszel
ab3ff1d701 fix run script preview for bundle scripts 2025-04-18 19:04:02 +02:00
Ruben Fiszel
8f7854dc63 update vite, rm circular deps 2025-04-18 15:48:33 +02:00
Diego Imbert
cdbe534a5d Schedule Reports Custom Resolution (#5634)
* Schedule Reports Custom Resolution

* enable for pdf too

* fix flow args and load custom res
2025-04-18 14:31:03 +02:00
Alexander Petric
6a9bdfd3bd fix: linter in early stop doesn't include flow_input (#5638) 2025-04-18 01:54:07 +02:00
Ruben Fiszel
b9bb071a8a improve agGrid behavior with extraConfig 2025-04-18 00:14:18 +02:00
dieriba
5b123b01a1 feat: handle different aws auth resource type (#5637)
* feat: oidc auth with sqs

* rafctor

* update: add id token struct

* update has_expired function

* fix duration

* fix typo

* Update backend/windmill-common/src/auth.rs

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Update backend/windmill-common/src/auth.rs

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* update ref

* update sqlx

* update resource type

* update repo ref

* Update settings.json

* add new resource type

* update repo ref and clear resource path on resource type change

* update .sqlx

* add missing key

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-17 21:51:18 +02:00
centdix
da13014c41 allow deleting code context (#5635) 2025-04-17 17:23:23 +02:00
Diego Imbert
58fa4c8006 fix: improve flow editor step switch performance
* replace on click wt on pointer down on flow node click

* pointerdown on virtualitems

* Load monaco async with a placeholder to avoid size flash

* monaco placeholder for editor

* less flashing

* simulate first line bg

* better match to monaco

* more fine tune

* fix for increased browser font sizes

* flow nodes feel much better to click on

* move setTimeout upwards

* only load async in flow editor

* load async monaco in app

* lots of components dont respect the type and pass undefined

* weird outline when opening and closing OutputPicker

* fixed hover flow nodes

* moved setTimeout upwards

* hover color for virtual items

* wrong Cargo.lock

* disable interaction if not selectable

* pixel perfect editor placeholder

* fake monaco editor perfect in flow editor

* fake monaco for app json editor

* (temp) never load editor monaco

* os dependant constants in monaco

* Revert " (temp) never load editor monaco"

This reverts commit c20fca134d.

---------

Co-authored-by: Diego Imbert <diego@windmill.dev>
2025-04-17 15:41:41 +02:00
dieriba
34b307b2be feat: oidc support for sqs trigger (#5614)
* feat: oidc auth with sqs

* rafctor

* update: add id token struct

* update has_expired function

* fix duration

* fix typo

* Update backend/windmill-common/src/auth.rs

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Update backend/windmill-common/src/auth.rs

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* update ref

* update sqlx

* update resource type

* update repo ref

* Update settings.json

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-17 15:36:17 +02:00
dieriba
2655da0796 add old row (#5633) 2025-04-17 13:12:34 +00:00
Diego Imbert
2fa0717ee0 Fix batch rerun not working on pg 14 (#5624)
* fix sql query for postgres 14

* deno_core when building backend image in github workflows

* sqlx prepare

---------

Co-authored-by: Diego Imbert <diego@windmill.dev>
2025-04-17 09:33:52 +02:00
Diego Imbert
081145726a fix: fix click outside popover fullscreen (#5631)
Co-authored-by: Diego Imbert <diego@windmill.dev>
2025-04-17 09:33:30 +02:00
Ruben Fiszel
4198a574d4 fix checks 2025-04-17 09:22:20 +02:00
373 changed files with 18016 additions and 32728 deletions

2
.env
View File

@@ -10,4 +10,4 @@ WM_IMAGE=ghcr.io/windmill-labs/windmill:main
# To rotate logs, set the following variables:
#LOG_MAX_SIZE=10m
#LOG_MAX_FILE=3
#LOG_MAX_FILE=3

View File

@@ -24,4 +24,4 @@ sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock
cd ${root_dirpath}/frontend && npm i --package-lock-only
cd ${root_dirpath}/frontend && npm i --package-lock-only --ignore-scripts

View File

@@ -64,7 +64,7 @@ jobs:
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -81,7 +81,7 @@ jobs:
platforms: linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -111,8 +111,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: RHEL9-amd64 build
path:
${{ steps.extract-ee-amd64.outputs.destination
path: ${{ steps.extract-ee-amd64.outputs.destination
}}/windmill-ee-amd64-rhel9
# - uses: actions/upload-artifact@v4

View File

@@ -51,7 +51,7 @@ jobs:
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,mcp
- name: Rename binary with corresponding architecture
run: |
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"

View File

@@ -67,7 +67,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,license,http_trigger,zip,oauth2,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages
features=embedding,parquet,openidconnect,license,http_trigger,zip,oauth2,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ steps.meta-public.outputs.tags }}

View File

@@ -1,10 +1,8 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME:
${{ github.event_name != 'pull_request' && github.event_name !=
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.event_name !=
'workflow_dispatch' && github.repository || 'windmill-labs/windmill-test' }}
DEV_SHA:
${{ github.event_name != 'pull_request' && github.event_name !=
DEV_SHA: ${{ github.event_name != 'pull_request' && github.event_name !=
'workflow_dispatch' && 'dev' || github.event.inputs.tag || github.sha }}
name: Build windmill:main
on:
@@ -40,8 +38,7 @@ permissions: write-all
jobs:
build:
runs-on: ubicloud
if:
(github.event_name != 'workflow_dispatch') || (github.event.inputs &&
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs &&
!github.event.inputs.ee)
steps:
- uses: actions/checkout@v4
@@ -95,7 +92,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
${{ steps.meta-public.outputs.tags }}
@@ -157,7 +154,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}
@@ -315,7 +312,7 @@ jobs:
needs: [run_integration_test, build]
if:
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
steps:
- uses: actions/checkout@v4
with:
@@ -355,7 +352,7 @@ jobs:
verify_ee_image_vulnerabilities:
runs-on: ubicloud
needs: [tag_latest_ee]
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -397,8 +394,7 @@ jobs:
build_ee_nsjail:
needs: [build_ee]
runs-on: ubicloud
if:
(github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
if: (github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
steps:
- uses: actions/checkout@v4
@@ -437,7 +433,7 @@ jobs:
run: |
sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail
cat ./docker/DockerfileNsjail | grep "FROM"
- name: Build and push publicly ee
uses: depot/build-push-action@v1
with:
@@ -451,12 +447,10 @@ jobs:
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
publish_ecr_s3:
needs: [build_ee_nsjail]
runs-on: ubicloud-standard-2-arm
if:
(github.event_name != 'pull_request') && (github.event_name !=
if: (github.event_name != 'pull_request') && (github.event_name !=
'workflow_dispatch')
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}

View File

@@ -53,7 +53,7 @@ jobs:
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,mcp
- name: Rename binary with corresponding architecture
run: |
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"

View File

@@ -1,5 +1,117 @@
# Changelog
## [1.486.1](https://github.com/windmill-labs/windmill/compare/v1.486.0...v1.486.1) (2025-05-04)
### Bug Fixes
* improve MultiSelectWrapper behavior ([36da8ae](https://github.com/windmill-labs/windmill/commit/36da8aec080742e13f23e1dee12b3954947f53dd))
## [1.486.0](https://github.com/windmill-labs/windmill/compare/v1.485.3...v1.486.0) (2025-05-01)
### Features
* add run now directly on schedule drawer and duplicate schedule option ([#5674](https://github.com/windmill-labs/windmill/issues/5674)) ([dfb947f](https://github.com/windmill-labs/windmill/commit/dfb947ff37c688f54a32de5aa3c5c3d142cb80f4))
* Database Manager ([#5586](https://github.com/windmill-labs/windmill/issues/5586)) ([41c15fc](https://github.com/windmill-labs/windmill/commit/41c15fc78aaf844c559d3d6c772e04ecce436e9d))
* Integrate MCP with hub ([#5685](https://github.com/windmill-labs/windmill/issues/5685)) ([ec701a9](https://github.com/windmill-labs/windmill/commit/ec701a9ee74c9d890b54234362392deca63a77c7))
### Bug Fixes
* Ai Chat: do not send tools if empty + respond even if tool fails ([#5692](https://github.com/windmill-labs/windmill/issues/5692)) ([9c55040](https://github.com/windmill-labs/windmill/commit/9c55040e47e76af8b7e2864b82fa30505545dcb5))
* do not track relative deps for scripts with raw defined deps from CLI ([#5696](https://github.com/windmill-labs/windmill/issues/5696)) ([7eb9d7d](https://github.com/windmill-labs/windmill/commit/7eb9d7d46cb48ae69a3fd3ff852a57abae450a3b))
* improve CLI file scanning performances ([0916978](https://github.com/windmill-labs/windmill/commit/09169784bd2d0ab7acf5f40dc86f36f1cae967b7))
## [1.485.3](https://github.com/windmill-labs/windmill/compare/v1.485.2...v1.485.3) (2025-04-29)
### Bug Fixes
* improve performance of background cleanup monitoring operations ([18dced3](https://github.com/windmill-labs/windmill/commit/18dced3c748cd5305f0934b26e50d69899563723))
## [1.485.2](https://github.com/windmill-labs/windmill/compare/v1.485.1...v1.485.2) (2025-04-29)
### Bug Fixes
* improve agent workers for deployed scripts ([60018aa](https://github.com/windmill-labs/windmill/commit/60018aadf62cecadf111e019d3600513a89810f1))
* make `#(extra_)requirements:` work better with pins ([#5680](https://github.com/windmill-labs/windmill/issues/5680)) ([1ab4160](https://github.com/windmill-labs/windmill/commit/1ab41603f4fd1526d0c944396ef250b184aed1f4))
* **python:** handle better relative imports with requirements or extra_requirements ([f662cf5](https://github.com/windmill-labs/windmill/commit/f662cf5d75beed8fd114ba171cbe0fa8e4b2773f))
## [1.485.1](https://github.com/windmill-labs/windmill/compare/v1.485.0...v1.485.1) (2025-04-28)
### Bug Fixes
* improve mcp mode api ([cf77ff0](https://github.com/windmill-labs/windmill/commit/cf77ff088b8382b861113120589de58f7cf241d0))
* MCP handle long names + invalid char in prop key + fix for not found resource type ([#5668](https://github.com/windmill-labs/windmill/issues/5668)) ([eadae95](https://github.com/windmill-labs/windmill/commit/eadae95a42d679bf8792bdefd8b9d19dbcbc4b57))
* skip_flow_update for dependency tracking table ([#5670](https://github.com/windmill-labs/windmill/issues/5670)) ([35b69da](https://github.com/windmill-labs/windmill/commit/35b69da25c5bd17deff5a54b635e9150cb865cc0))
## [1.485.0](https://github.com/windmill-labs/windmill/compare/v1.484.0...v1.485.0) (2025-04-28)
### Features
* add universal search to object viewer ([7254743](https://github.com/windmill-labs/windmill/commit/72547437fead0a071fceac27dae8628cdcae6a3e))
### Bug Fixes
* add svelte 5 boundaries to app components to contain errors ([1b16918](https://github.com/windmill-labs/windmill/commit/1b1691837a7e6b88afbacf7d88c14ca5e475b493))
* Fix object handling on some MCP clients + better frontend for MCP ([#5663](https://github.com/windmill-labs/windmill/issues/5663)) ([12c3202](https://github.com/windmill-labs/windmill/commit/12c32026e5879a65fc0f1cc9f2481087c4b95111))
## [1.484.0](https://github.com/windmill-labs/windmill/compare/v1.483.2...v1.484.0) (2025-04-26)
### Features
* Add MCP endpoints ([#5639](https://github.com/windmill-labs/windmill/issues/5639)) ([a34ac4f](https://github.com/windmill-labs/windmill/commit/a34ac4fa24c2a5482e45724e76316d57f64f7040))
* Add MCP only mode ([#5661](https://github.com/windmill-labs/windmill/issues/5661)) ([1625524](https://github.com/windmill-labs/windmill/commit/162552431138d68002c7060cad4ae31f1ec4c69c))
* Ansible improvements (vault, roles and git repos) ([#5655](https://github.com/windmill-labs/windmill/issues/5655)) ([fdd1642](https://github.com/windmill-labs/windmill/commit/fdd1642ce10866da1d8d373bda44f050e2e0f403))
### Bug Fixes
* check for valid teams_channel config when saving critical alerts settings ([#5660](https://github.com/windmill-labs/windmill/issues/5660)) ([dc5c8d8](https://github.com/windmill-labs/windmill/commit/dc5c8d8c5f8577b7ded3da1d684cdb735fa7a936))
* Fix CI for MCP + optimization ([#5657](https://github.com/windmill-labs/windmill/issues/5657)) ([b199a77](https://github.com/windmill-labs/windmill/commit/b199a77d486c5bfd086ca73a58a14bb747e386b5))
* fix token creation after mcp mode change to make it non workspace specific ([2b5dfcf](https://github.com/windmill-labs/windmill/commit/2b5dfcfb251471dcc39b04c54e25008d617cc34f))
* improve full-scaleout of autoscaling event logging ([8435eb3](https://github.com/windmill-labs/windmill/commit/8435eb3adff8429a73db88b12204f7cf8f14d3d2))
* improve skip failure on parallel branchall ([a7b2b51](https://github.com/windmill-labs/windmill/commit/a7b2b51444d757964560de3a89024b1c9b0fefe9))
## [1.483.2](https://github.com/windmill-labs/windmill/compare/v1.483.1...v1.483.2) (2025-04-23)
### Bug Fixes
* batch reruns query missing workspace_id check in subquery ([#5652](https://github.com/windmill-labs/windmill/issues/5652)) ([444a6ab](https://github.com/windmill-labs/windmill/commit/444a6abad670114c52e44f3606bf6fefc5d3fd98))
* **frontend:** fix validity check ([#5654](https://github.com/windmill-labs/windmill/issues/5654)) ([c41c1eb](https://github.com/windmill-labs/windmill/commit/c41c1eb587bf22364f1202310a0c64b6040ab968))
* improve MySQL datetime parser timezone handling (WIN-1155) ([#5645](https://github.com/windmill-labs/windmill/issues/5645)) ([5bca8f6](https://github.com/windmill-labs/windmill/commit/5bca8f60e970cc67839edb5dc491685f36cf0499))
* track relative imports in python and ts even if lockfile is provided ([e316dbd](https://github.com/windmill-labs/windmill/commit/e316dbd9bdd5c59e9aaba6a4472bb7d832834e84))
## [1.483.1](https://github.com/windmill-labs/windmill/compare/v1.483.0...v1.483.1) (2025-04-19)
### Bug Fixes
* pin libxml to 0.3.3 ([e5595e4](https://github.com/windmill-labs/windmill/commit/e5595e41b5c87704d814eff95bc00b82195728ba))
## [1.483.0](https://github.com/windmill-labs/windmill/compare/v1.482.1...v1.483.0) (2025-04-19)
### Features
* handle different aws auth resource type ([#5637](https://github.com/windmill-labs/windmill/issues/5637)) ([5b123b0](https://github.com/windmill-labs/windmill/commit/5b123b01a1318208450789b5bcade447a0b331c7))
* oidc support for sqs trigger ([#5614](https://github.com/windmill-labs/windmill/issues/5614)) ([34b307b](https://github.com/windmill-labs/windmill/commit/34b307b2be1f6cf92a81694325f4c333bdd7b055))
### Bug Fixes
* fix click outside popover fullscreen ([#5631](https://github.com/windmill-labs/windmill/issues/5631)) ([0811457](https://github.com/windmill-labs/windmill/commit/081145726a5d4ab81510a7637126a036823a1565))
* improve flow editor step switch performance ([58fa4c8](https://github.com/windmill-labs/windmill/commit/58fa4c80062a5704bbd13ddda1b2f00c7c9e40dd))
* linter in early stop doesn't include flow_input ([#5638](https://github.com/windmill-labs/windmill/issues/5638)) ([6a9bdfd](https://github.com/windmill-labs/windmill/commit/6a9bdfd3bd52ff802b6a71c3ae9504bfe7d0421f))
* output picker output opening doesn't change id ([#5641](https://github.com/windmill-labs/windmill/issues/5641)) ([64c72b6](https://github.com/windmill-labs/windmill/commit/64c72b6fce669e47f04dc620750840857cbe66cf))
## [1.482.1](https://github.com/windmill-labs/windmill/compare/v1.482.0...v1.482.1) (2025-04-16)

View File

@@ -25,6 +25,7 @@ FROM node:20-alpine as frontend
# install dependencies
WORKDIR /frontend
COPY ./frontend/package.json ./frontend/package-lock.json ./
COPY ./frontend/scripts/ ./scripts/
RUN npm ci
# Copy all local files into the image.

View File

@@ -363,6 +363,7 @@ you to have it being synced automatically everyday.
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
| DISABLE_RESPONSE_LOGS | false | Disable response logs | Server |
| CREATE_WORKSPACE_REQUIRE_SUPERADMIN | true | If true, only superadmins can create new workspaces | Server |
| MIN_FREE_DISK_SPACE_MB | 15000 | Minimum amount of free space on worker. Sends critical alert if worker has less free space. | Worker |
## Run a local dev setup

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval \n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ",
"query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval\n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ",
"describe": {
"columns": [
{
@@ -36,5 +36,5 @@
null
]
},
"hash": "f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45"
"hash": "00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"describe": {
"columns": [
{
@@ -45,56 +45,71 @@
},
{
"ordinal": 6,
"name": "subscription_mode: _",
"type_info": {
"Custom": {
"name": "gcp_subscription_mode",
"kind": {
"Enum": [
"create_update",
"existing"
]
}
}
}
},
{
"ordinal": 7,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 7,
"ordinal": 8,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 10,
"ordinal": 11,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 12,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"ordinal": 13,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 13,
"ordinal": 14,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 14,
"ordinal": 15,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 15,
"ordinal": 16,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 16,
"ordinal": 17,
"name": "enabled",
"type_info": "Bool"
}
@@ -118,6 +133,7 @@
false,
false,
false,
false,
true,
true,
false,
@@ -125,5 +141,5 @@
false
]
},
"hash": "f4f6336fc671b00bed7835124892f7a4d3bbe673f7c48153819dab385a5cb357"
"hash": "1312b7fd622cc814a406c85dbbff61f003c29185c267642cfd898075ebda855d"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT jsonb_build_object(\n 'kind', jb.kind,\n 'script_path', jb.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),\n 'job_ids', ARRAY_AGG(DISTINCT j.id),\n 'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1]\n ) FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'\n WHERE j.id = ANY(ARRAY_AGG(jb.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM v2_job jb\n WHERE (jb.kind = 'flow' OR jb.kind = 'script')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n GROUP BY jb.kind, jb.runnable_path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "jsonb_build_object",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"UuidArray"
]
},
"nullable": [
null
]
},
"hash": "26761fbd7953416eb391de47b1694e0f4ab2bb96a6d838f1b1fdce4b58a8f5d4"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))",
"query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
"describe": {
"columns": [
{
@@ -19,5 +19,5 @@
null
]
},
"hash": "ea2b88dc050aec038641ea37399d68d4385c5bdc721d1351609f27ca45e4dbdc"
"hash": "2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
"describe": {
"columns": [
{
@@ -45,56 +45,71 @@
},
{
"ordinal": 6,
"name": "subscription_mode: _",
"type_info": {
"Custom": {
"name": "gcp_subscription_mode",
"kind": {
"Enum": [
"create_update",
"existing"
]
}
}
}
},
{
"ordinal": 7,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 7,
"ordinal": 8,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 10,
"ordinal": 11,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 12,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"ordinal": 13,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 13,
"ordinal": 14,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 14,
"ordinal": 15,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 15,
"ordinal": 16,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 16,
"ordinal": 17,
"name": "enabled",
"type_info": "Bool"
}
@@ -115,6 +130,7 @@
false,
false,
false,
false,
true,
true,
false,
@@ -122,5 +138,5 @@
false
]
},
"hash": "15fbe481789a7817bf37415fb935f9ed537fd7d3b266d928af4d5d3dd8bb5c18"
"hash": "2dcff8b16da75740c362015b2293f578a21813f038d75184a5034c37d4daf36e"
}

View File

@@ -1,79 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n j.id,\n j.kind AS \"kind: _\",\n COALESCE(s.path, f.path) AS \"script_path!\",\n COALESCE(s.hash, f.id) AS \"script_hash!: _\",\n COALESCE(jc.started_at, jq.scheduled_for, make_date(1970, 1, 1)) AS \"scheduled_for!: _\",\n args AS input\n FROM v2_job j\n LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script'\n LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow'\n LEFT JOIN v2_job_completed jc ON jc.id = j.id\n LEFT JOIN v2_job_queue jq ON jq.id = j.id\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "kind: _",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
]
}
}
}
},
{
"ordinal": 2,
"name": "script_path!",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "script_hash!: _",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "scheduled_for!: _",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "input",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"UuidArray",
"Text"
]
},
"nullable": [
false,
false,
null,
null,
null,
true
]
},
"hash": "3376b42d273c2499f3517c7754f4cdb6eae2e71e56bc8e5215559af967c81bd8"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval \n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker",
"query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval\n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker",
"describe": {
"columns": [
{
@@ -22,5 +22,5 @@
null
]
},
"hash": "ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1"
"hash": "3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM metrics WHERE created_at < NOW() - INTERVAL '180 day'",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "45dde0dc2da12fe46b5975ed53b520fc4160c008c3d29fe7d99e609c0b6e3e6d"
}

View File

@@ -1,80 +1,95 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "aws_auth_resource_type: _",
"type_info": {
"Custom": {
"name": "aws_auth_resource_type",
"kind": {
"Enum": [
"oidc",
"credentials"
]
}
}
}
},
{
"ordinal": 1,
"name": "aws_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"ordinal": 2,
"name": "message_attributes",
"type_info": "TextArray"
},
{
"ordinal": 2,
"ordinal": 3,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 3,
"ordinal": 4,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 5,
"ordinal": 6,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 6,
"ordinal": 7,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 7,
"ordinal": 8,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"ordinal": 11,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 12,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"ordinal": 13,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 13,
"ordinal": 14,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 14,
"ordinal": 15,
"name": "enabled",
"type_info": "Bool"
}
@@ -86,6 +101,7 @@
]
},
"nullable": [
false,
false,
true,
false,
@@ -103,5 +119,5 @@
false
]
},
"hash": "2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867"
"hash": "5237f9d3f57f4e799968a4a9630c984775d23c7a4dc1a7ad37528a46ab89d6bd"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2::text::json, $3) RETURNING id",
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n VALUES ($1, $2::text::json, $3, $4) RETURNING id",
"describe": {
"columns": [
{
@@ -13,12 +13,13 @@
"Left": [
"Int8",
"Text",
"Varchar"
"Varchar",
"Bool"
]
},
"nullable": [
false
]
},
"hash": "2a3992b5e9abcfbb032d10e142d98efa969dae26a7242eb7ac12593ed5421ef3"
"hash": "56264b88a9f428e79c6e531a0011e0dbed634f3b12dedaa5fee0362d670887f6"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) \n SELECT worker_ids.worker FROM worker_ids \n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker \n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval",
"query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker)\n SELECT worker_ids.worker FROM worker_ids\n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker\n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval",
"describe": {
"columns": [
{
@@ -18,5 +18,5 @@
null
]
},
"hash": "ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927"
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT jsonb_build_object(\n 'kind', jb.kind,\n 'script_path', jb.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),\n 'job_ids', ARRAY_AGG(DISTINCT j.id),\n 'schema', ANY_VALUE(COALESCE(s.schema, f.schema))\n ) FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'\n WHERE j.id = ANY(ARRAY_AGG(jb.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM v2_job jb\n WHERE (jb.kind = 'flow' OR jb.kind = 'script')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n GROUP BY jb.kind, jb.runnable_path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "jsonb_build_object",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"UuidArray"
]
},
"nullable": [
null
]
},
"hash": "5ea1d8c87a17690942ca9e70e0b953e3b32a9a7a73784219d48f8e8932cdb0c8"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by created_at DESC",
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC",
"describe": {
"columns": [
{
@@ -25,5 +25,5 @@
true
]
},
"hash": "362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675"
"hash": "726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de"
}

View File

@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO sqs_trigger (\n aws_auth_resource_type,\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10,\n $11\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
{
"Custom": {
"name": "aws_auth_resource_type",
"kind": {
"Enum": [
"oidc",
"credentials"
]
}
}
},
"Varchar",
"Varchar",
"TextArray",
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Varchar",
"Bool",
"Varchar"
]
},
"nullable": []
},
"hash": "776909d3452aaf9e0ed41a5ec314b7bfc5bc8b6a76c98bf4daf7d715d18c52e6"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE script \n SET ws_error_handler_muted = $3 \n WHERE ctid = (\n SELECT ctid FROM script\n WHERE path = $1 AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1\n )\n",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "848c8371eeb17ebd4b36a33f7d8a61eb8f07c54d291bb857ddd41a549cbc88dd"
}

View File

@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET \n aws_auth_resource_type = $1,\n aws_resource_path = $2,\n queue_url = $3,\n message_attributes = $4, \n is_flow = $5, \n edited_by = $6, \n email = $7,\n script_path = $8,\n path = $9,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $10 AND \n path = $11\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
{
"Custom": {
"name": "aws_auth_resource_type",
"kind": {
"Enum": [
"oidc",
"credentials"
]
}
}
},
"Varchar",
"Varchar",
"TextArray",
"Bool",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "8490024b96aa689d3c1bc5cbf94fcd8c5491732818404fee2397f00cccee0ad3"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger\n WHERE workspace_id = $1",
"query": "\n SELECT \n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n filters AS \"filters: _\",\n initial_messages AS \"initial_messages: _\",\n url_runnable_args AS \"url_runnable_args: _\",\n can_return_message\n FROM \n websocket_trigger\n WHERE \n workspace_id = $1\n ",
"describe": {
"columns": [
{
@@ -114,5 +114,5 @@
false
]
},
"hash": "1aa8ead10f8d994f6685d266fcbd409b0fff43111d9600e64b2348401ed8929d"
"hash": "8eabeee5527da4aad3f46ad185015262b4304667449f3e1a71efd7477a39f7fc"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)\n VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)\n ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7",
"describe": {
"columns": [],
"parameters": {
@@ -17,5 +17,5 @@
},
"nullable": []
},
"hash": "33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe"
"hash": "92faee8714a45a403b623e04d789f7f99067a05e9dfe270223164db8a1df2e4b"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO sqs_trigger (\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"TextArray",
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Varchar",
"Bool",
"Varchar"
]
},
"nullable": []
},
"hash": "9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8"
}

View File

@@ -1,80 +1,95 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM sqs_trigger\n WHERE workspace_id = $1",
"query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
"name": "aws_auth_resource_type: _",
"type_info": {
"Custom": {
"name": "aws_auth_resource_type",
"kind": {
"Enum": [
"oidc",
"credentials"
]
}
}
}
},
{
"ordinal": 1,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "aws_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"ordinal": 2,
"name": "message_attributes",
"type_info": "TextArray"
},
{
"ordinal": 4,
"name": "script_path",
"ordinal": 3,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 6,
"ordinal": 4,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 8,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 11,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 12,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 13,
"ordinal": 12,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 13,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 14,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 15,
"name": "enabled",
"type_info": "Bool"
}
@@ -85,7 +100,6 @@
]
},
"nullable": [
false,
false,
false,
true,
@@ -95,6 +109,8 @@
false,
false,
false,
false,
false,
true,
true,
true,
@@ -102,5 +118,5 @@
false
]
},
"hash": "2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7"
"hash": "a7df493316f632fc636e4c3c90bef4c98035b5dc808bb8379251c0b35d945ba0"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2::text::json, $3) RETURNING id",
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n VALUES ($1, $2::text::json, $3, $4) RETURNING id",
"describe": {
"columns": [
{
@@ -13,12 +13,13 @@
"Left": [
"Int8",
"Text",
"Varchar"
"Varchar",
"Bool"
]
},
"nullable": [
false
]
},
"hash": "83d18f4e1cda2c1867168551d855d5626e766b5469c6976c986bdafc8b9407c5"
"hash": "a9f6e5720f748418ae52cf0e31778b9bbef26008803f2dd2a043bdb091b2ff20"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND workspace_id = $2)",
"query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
@@ -19,5 +19,5 @@
false
]
},
"hash": "a17260a1f1ee02e786690994d98c84ddf81e2eeb883f895c9cfc47e144d422cb"
"hash": "b5860f6a7672a368d740dcd367a8d5ab98fa93e0382a57a698564695db6c40ac"
}

View File

@@ -1,73 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n j.kind AS \"kind!: JobKind\",\n j.runnable_id AS \"script_hash: _\",\n j.runnable_path AS script_path,\n COUNT(*) AS \"count!\",\n ANY_VALUE(COALESCE(f.schema, s.schema)) AS schema\n FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND f.path = j.runnable_path AND j.kind = 'flow'\n WHERE COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL\n AND j.workspace_id = $1 AND j.id = ANY($2)\n GROUP BY j.runnable_id, j.runnable_path, j.kind",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "kind!: JobKind",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
]
}
}
}
},
{
"ordinal": 1,
"name": "script_hash: _",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "count!",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "schema",
"type_info": "Json"
}
],
"parameters": {
"Left": [
"Text",
"UuidArray"
]
},
"nullable": [
false,
true,
true,
null,
null
]
},
"hash": "b61a00e6a8ca0a1d24e64fdc9223b077c957da8455ea747b0680923d8282425b"
}

View File

@@ -1,80 +1,95 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
"query": "\n SELECT\n aws_auth_resource_type AS \"aws_auth_resource_type: _\",\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "aws_auth_resource_type: _",
"type_info": {
"Custom": {
"name": "aws_auth_resource_type",
"kind": {
"Enum": [
"oidc",
"credentials"
]
}
}
}
},
{
"ordinal": 1,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 1,
"ordinal": 2,
"name": "aws_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"ordinal": 3,
"name": "message_attributes",
"type_info": "TextArray"
},
{
"ordinal": 3,
"ordinal": 4,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 5,
"ordinal": 6,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 6,
"ordinal": 7,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 7,
"ordinal": 8,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"ordinal": 11,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 12,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"ordinal": 13,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 13,
"ordinal": 14,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 14,
"ordinal": 15,
"name": "enabled",
"type_info": "Bool"
}
@@ -83,6 +98,7 @@
"Left": []
},
"nullable": [
false,
false,
false,
true,
@@ -100,5 +116,5 @@
false
]
},
"hash": "e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a"
"hash": "bc0bedddcafad216c30c5061eef3f4ed8573cda89f655d6490d22f182e3f2f36"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ",
"query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ",
"describe": {
"columns": [
{
@@ -45,56 +45,71 @@
},
{
"ordinal": 6,
"name": "subscription_mode: _",
"type_info": {
"Custom": {
"name": "gcp_subscription_mode",
"kind": {
"Enum": [
"create_update",
"existing"
]
}
}
}
},
{
"ordinal": 7,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 7,
"ordinal": 8,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 10,
"ordinal": 11,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 12,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"ordinal": 13,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 13,
"ordinal": 14,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 14,
"ordinal": 15,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 15,
"ordinal": 16,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 16,
"ordinal": 17,
"name": "enabled",
"type_info": "Bool"
}
@@ -117,6 +132,7 @@
false,
false,
false,
false,
true,
true,
false,
@@ -124,5 +140,5 @@
false
]
},
"hash": "0102308ffa1c0dbfba54d29246535bb81146a4cfae0ec408435570e0813a3bef"
"hash": "c2925a28212265bd9ac8e2d498b3588fc040c7a29ae6d9bab1f05af0b535e2ac"
}

View File

@@ -1,73 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT DISTINCT ON (j.runnable_path, j.kind) \n j.kind AS \"kind!: JobKind\",\n j.runnable_path AS script_path,\n NULL as \"script_hash: _\",\n -1::bigint as \"count!: _\",\n COALESCE(f.schema, s.schema) AS schema\n FROM v2_job j\n LEFT JOIN script s ON s.path = j.runnable_path AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.path = j.runnable_path AND j.kind = 'flow'\n WHERE COALESCE(s.hash, f.id) IS NOT NULL\n AND j.workspace_id = $1 AND j.id = ANY($2)\n ORDER BY j.runnable_path, j.kind, COALESCE(f.created_at, s.created_at) DESC",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "kind!: JobKind",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
]
}
}
}
},
{
"ordinal": 1,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "script_hash: _",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "count!: _",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "schema",
"type_info": "Json"
}
],
"parameters": {
"Left": [
"Text",
"UuidArray"
]
},
"nullable": [
false,
true,
null,
null,
null
]
},
"hash": "c4f382045e5c47986e02f1e57667e6b3cb1d60f15fed9a24e3f05b1bd60c0fc8"
}

View File

@@ -1,67 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT j.kind AS \"kind: _\", COALESCE(s.path, f.path) AS \"script_path!\", COALESCE(s.hash, f.id) AS \"script_hash!: _\", args\n FROM v2_job j\n LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script'\n LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow'\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "kind: _",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
]
}
}
}
},
{
"ordinal": 1,
"name": "script_path!",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "script_hash!: _",
"type_info": "Int8"
},
{
"ordinal": 3,
"name": "args",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"UuidArray",
"Text"
]
},
"nullable": [
false,
null,
null,
true
]
},
"hash": "ccd4c7fe5fbdf1ab4bbbbd7d2a9b1ec3fc844ed286ff563f0c9a5c4778cfb718"
}

View File

@@ -0,0 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "hash",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "deployment_msg",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
true
]
},
"hash": "cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE \n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update",
"query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE\n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update",
"describe": {
"columns": [
{
@@ -37,5 +37,5 @@
null
]
},
"hash": "abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5"
"hash": "daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET \n aws_resource_path = $1,\n queue_url = $2,\n message_attributes = $3, \n is_flow = $4, \n edited_by = $5, \n email = $6,\n script_path = $7,\n path = $8,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $9 AND \n path = $10\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"TextArray",
"Bool",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5"
}

View File

@@ -11,5 +11,5 @@
"remote.autoForwardPorts": true,
"conventionalCommits.scopes": [
"restructring triggers, decoding trigger message on work"
],
]
}

1897
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.482.1"
version = "1.486.1"
authors.workspace = true
edition.workspace = true
@@ -32,7 +32,7 @@ members = [
]
[workspace.package]
version = "1.482.1"
version = "1.486.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -72,8 +72,9 @@ dind = ["windmill-worker/dind"]
websocket = ["windmill-api/websocket"]
http_trigger = ["windmill-api/http_trigger"]
postgres_trigger = ["windmill-api/postgres_trigger"]
mcp = ["windmill-api/mcp"]
mqtt_trigger = ["windmill-api/mqtt_trigger"]
sqs_trigger = ["windmill-api/sqs_trigger"]
sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"]
gcp_trigger = ["windmill-api/gcp_trigger"]
smtp = ["windmill-api/smtp", "windmill-common/smtp"]
license = ["windmill-api/license"]
@@ -131,6 +132,8 @@ quote.workspace = true
memchr.workspace = true
v8 = { workspace = true, optional = true }
rustls.workspace = true
systemstat.workspace = true
size.workspace = true
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { optional = true, workspace = true }
@@ -191,7 +194,7 @@ serde_json = { version = "^1", features = ["preserve_order", "raw_value"] }
uuid = { version = "^1", features = ["serde", "v4"] }
thiserror = "^2"
anyhow = "^1"
chrono = { version = "=0.4.39", features = ["serde"] }
chrono = { version = "^0.4", features = ["serde"] }
chrono-tz = "^0.10.1"
tracing = "^0"
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
@@ -304,6 +307,7 @@ postgres-native-tls = "^0"
native-tls = "^0"
# samael will break compilation on MacOS. Use this fork instead to make it work
# samael = { git="https://github.com/njaremko/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = ["xmlsec"] }
libxml = { version = "=0.3.3" }
samael = { version="0.0.14", features = ["xmlsec"] }
gcp_auth = "0.9.0"
rust_decimal = { version = "^1", features = ["db-postgres", "serde-float"]}
@@ -313,9 +317,9 @@ nix = { version = "0.27.1", features = ["process", "signal"] }
tinyvector = { git = "https://github.com/windmill-labs/tinyvector", rev = "20823b94c20f2b9093f318badd24026cf54dcc85" }
hf-hub = "0.3.2"
tokenizers = "0.14.1"
candle-core = "0.3.0"
candle-transformers = "0.3.0"
candle-nn = "0.3.0"
candle-core = "0.9.1"
candle-transformers = "0.9.1"
candle-nn = "0.9.1"
tiberius = { version = "0.12.3", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]}
pin-project = "1"
indexmap = { version = "2.2.5", features = ["serde"]}
@@ -329,8 +333,8 @@ async-nats = "0.38.0"
nkeys = "0.4.4"
nu-parser = { version = "0.101.0", default-features = false }
datafusion = "39.0.0"
object_store = { version = "0.10.0", features = ["aws", "azure"] }
datafusion = "47.0.0"
object_store = { version = "0.12.0", features = ["aws", "azure"] }
openidconnect = { version = "4.0.0-rc.1" }
aws-config = "^1"
aws-sdk-sqs = "1.57.0"
@@ -353,9 +357,6 @@ bollard = "0.18.1"
tonic = { version = "=0.12.3", features = ["tls-native-roots"] }
byteorder = "1.5.0"
# todo remove
half = "=2.4.1"
tikv-jemallocator = { version = "0.5" }
tikv-jemalloc-sys = { version = "^0.5" }
tikv-jemalloc-ctl = { version = "^0.5" }
@@ -366,6 +367,8 @@ pin-project-lite = "^0"
tantivy = "0.22.0"
backon = "1.3.0"
systemstat = "0.2.4"
size = "0.5.0"
flume = { version = "0.11.1", features = ["async"] }

View File

@@ -1 +1 @@
1475a504133766e005ec551004c2af2283663352
868ccad87afb804fe22818ecd3d5a091199bcdbf

View File

@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE app_version DROP COLUMN IF EXISTS raw_app;

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE app_version ADD COLUMN IF NOT EXISTS raw_app BOOLEAN NOT NULL DEFAULT FALSE;

View File

@@ -0,0 +1,3 @@
-- Add down migration script here
ALTER TABLE sqs_trigger DROP COLUMN aws_auth_resource_type;
DROP TYPE IF EXISTS AWS_AUTH_RESOURCE_TYPE;

View File

@@ -0,0 +1,4 @@
-- Add up migration script here
CREATE TYPE AWS_AUTH_RESOURCE_TYPE AS ENUM ('oidc', 'credentials');
ALTER TABLE sqs_trigger
ADD COLUMN aws_auth_resource_type AWS_AUTH_RESOURCE_TYPE DEFAULT 'credentials'::AWS_AUTH_RESOURCE_TYPE NOT NULL;

View File

@@ -0,0 +1,3 @@
-- Add down migration script here
ALTER TABLE gcp_trigger DROP COLUMN subscription_mode;
DROP TYPE GCP_SUBSCRIPTION_MODE;

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
CREATE TYPE GCP_SUBSCRIPTION_MODE AS ENUM ('create_update', 'existing');
ALTER TABLE gcp_trigger ADD COLUMN subscription_mode GCP_SUBSCRIPTION_MODE NOT NULL DEFAULT 'create_update'::GCP_SUBSCRIPTION_MODE;

View File

@@ -0,0 +1,3 @@
-- Add down migration script here
ALTER TABLE websocket_trigger
ALTER COLUMN url TYPE VARCHAR(255);

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE websocket_trigger
ALTER COLUMN url TYPE VARCHAR(1000);

View File

@@ -0,0 +1 @@
DELETE FROM global_settings WHERE name = 'critical_alerts_on_db_oversize';

View File

@@ -0,0 +1,3 @@
INSERT INTO global_settings (name, value)
VALUES ('critical_alerts_on_db_oversize', '{}')
ON CONFLICT (name) DO NOTHING;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TYPE log_mode ADD VALUE 'mcp';

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
CREATE INDEX IF NOT EXISTS idx_metrics_id_created_at ON public.metrics (id, created_at DESC) WHERE id LIKE 'queue_%';

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
CREATE INDEX IF NOT EXISTS job_stats_id ON job_stats (job_id);

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
DROP INDEX IF EXISTS index_script_on_path_created_at;
CREATE INDEX IF NOT EXISTS index_script_on_path_created_at ON script (workspace_id, path, created_at DESC);

View File

@@ -44,6 +44,7 @@ fn replace_full_import(x: &str) -> Option<String> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^\#\s?(\S+)\s*$").unwrap();
static ref PIN_RE: Regex = Regex::new(r"(?:\s*#\s*(pin|repin):\s*)(\S*)").unwrap();
static ref PKG_RE: Regex = Regex::new(r"^([^!=<>]+)(?:[!=<>]|$)").unwrap();
}
fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<NImport> {
@@ -167,30 +168,28 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result<Vec<NImport>>
.take_while(|e| *e != '\n')
.collect::<String>();
if hs.trim_start().is_empty(){
if hs.trim_start().is_empty() {
return None;
}
PIN_RE
.captures(&hs)
.and_then(|x| {
x.get(1).zip(x.get(2)).and_then(|(ty_m, pkg_m)| {
let pkg = pkg_m.as_str().to_owned();
if ty_m.as_str() == "pin" {
Some(vec![NImport::Pin {
pins: vec![ImportPin { pkg, path: path.to_owned() }],
key,
}])
} else if ty_m.as_str() == "repin" {
Some(vec![NImport::Repin {
pin: ImportPin { pkg, path: path.to_owned() },
key,
}])
} else {
None
}
})
PIN_RE.captures(&hs).and_then(|x| {
x.get(1).zip(x.get(2)).and_then(|(ty_m, pkg_m)| {
let pkg = pkg_m.as_str().to_owned();
if ty_m.as_str() == "pin" {
Some(vec![NImport::Pin {
pins: vec![ImportPin { pkg, path: path.to_owned() }],
key,
}])
} else if ty_m.as_str() == "repin" {
Some(vec![NImport::Repin {
pin: ImportPin { pkg, path: path.to_owned() },
key,
}])
} else {
None
}
})
})
};
let mut nimports: Vec<NImport> = ast
@@ -268,7 +267,14 @@ pub async fn parse_python_imports(
Ok(p.pkg)
}).collect_vec(),
NImportResolved::Repin { pin: ImportPin { pkg, .. }, .. } => vec![Ok(pkg)],
NImportResolved::Auto { pkg, ..} => vec![Ok(pkg)],
NImportResolved::Auto { pkg, key } => vec![
if let Some(key) = key {
Ok(format!("{pkg} # (mapped from {key})"))
} else {
Ok(pkg)
}
],
})
.flatten()
.collect::<error::Result<Vec<String>>>()?
@@ -284,6 +290,13 @@ pub async fn parse_python_imports(
Ok((imports, compile_error_hint))
}
fn extract_pkg_name(requirement: &str) -> String {
PKG_RE
.captures(requirement)
.map(|x| x.get(1).map(|m| m.as_str().to_string()).unwrap_or_default())
.unwrap_or_default()
}
#[async_recursion]
async fn parse_python_imports_inner(
code: &str,
@@ -343,11 +356,15 @@ async fn parse_python_imports_inner(
RE.captures(x).and_then(|x| {
x.get(1).map(|m| {
let requirement = m.as_str().to_string();
let key = extract_pkg_name(&requirement);
requirements.insert(
requirement.clone(),
NImportResolved::Repin {
pin: ImportPin { pkg: requirement, path: Default::default() },
key: Default::default(),
key.clone(),
NImportResolved::Pin {
pins: vec![ImportPin {
pkg: requirement.clone(),
path: Default::default(),
}],
key,
},
);
})
@@ -368,9 +385,16 @@ async fn parse_python_imports_inner(
RE.captures(x).and_then(|x| {
x.get(1).map(|m| {
let requirement = m.as_str().to_string();
let key = extract_pkg_name(&requirement);
imports.insert(
requirement.clone(),
NImportResolved::Auto { key: None, pkg: requirement },
key.clone(),
NImportResolved::Pin {
pins: vec![ImportPin {
pkg: requirement,
path: Default::default(),
}],
key,
},
);
})
})

View File

@@ -142,6 +142,8 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
"google.cloud.dns" => "google-cloud-dns",
"google.cloud.runtimeconfig" => "google-cloud-runtimeconfig",
"google.cloud.iot" => "google-cloud-iot",
"google.generativeai" => "google-generativeai",
"google.genai" => "google-genai",
// Azure
"azure.mgmt.hybridkubernetes" => "azure-mgmt-hybridkubernetes",
"azure.mgmt.sql" => "azure-mgmt-sql",
@@ -325,6 +327,7 @@ pub static FULL_IMPORTS_MAP: PyMap = phf_map! {
"azure.mgmt.nspkg" => "azure-mgmt-nspkg",
"azure.keyvault.secrets" => "azure-keyvault-secrets",
"azure.storage.blob" => "azure-storage-blob",
"azure.storage.filedatalake" => "azure-storage-file-datalake",
// Add new entry here ^
};
@@ -376,5 +379,6 @@ pub static SHORT_IMPORTS_MAP: PyMap = phf_map! {
"socks" => "PySocks",
"taiga" => "python-taiga",
"docx" => "python-docx",
"vt" => "vt-py",
// Add new entry here ^
};

View File

@@ -29,7 +29,15 @@ def main():
)
.await?;
// println!("{}", serde_json::to_string(&r)?);
assert_eq!(r, vec!["matplotlib", "wmill", "zanzibar"]);
assert_eq!(
r,
vec![
"matplotlib # (mapped from matplotlib.pyplot)",
"wmill",
"zanzibar # (mapped from zanzibar.estonie)"
]
);
Ok(())
}

View File

@@ -49,8 +49,12 @@ impl Visit for ImportsFinder {
pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.into());
let mut tss = TsSyntax::default();
tss.disallow_ambiguous_jsx_like;
tss.tsx = true;
tss.no_early_errors = true;
let lexer = Lexer::new(
Syntax::Typescript(TsSyntax::default()),
Syntax::Typescript(tss),
// EsVersion defaults to es5
Default::default(),
StringInput::from(&*fm),

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use anyhow::anyhow;
use serde_json::json;
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ};
@@ -208,15 +210,52 @@ pub struct AnsibleInventory {
resource_type: Option<String>,
pub pinned_resource: Option<String>,
}
#[derive(Debug, Clone)]
pub struct GitRepo {
pub url: String,
pub commit: Option<String>,
pub branch: Option<String>,
pub target_path: String,
}
#[derive(Debug, Clone)]
pub struct AnsibleRequirements {
pub python_reqs: Vec<String>,
pub collections: Option<String>,
pub roles_and_collections: Option<String>,
pub file_resources: Vec<FileResource>,
pub inventories: Vec<AnsibleInventory>,
pub vars: Vec<(String, String)>,
pub resources: Vec<(String, String)>,
pub options: AnsiblePlaybookOptions,
pub vault_password: Option<String>,
pub vault_id: Vec<String>,
pub git_repos: Vec<GitRepo>,
pub git_ssh_identity: Vec<String>,
}
impl Default for AnsibleRequirements {
fn default() -> Self {
Self {
python_reqs: vec![],
roles_and_collections: None,
file_resources: vec![],
inventories: vec![],
vars: vec![],
resources: vec![],
options: AnsiblePlaybookOptions {
verbosity: None,
forks: None,
timeout: None,
flush_cache: None,
force_handlers: None,
},
vault_password: None,
vault_id: vec![],
git_repos: vec![],
git_ssh_identity: vec![],
}
}
}
fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result<Vec<AnsibleInventory>> {
@@ -275,22 +314,7 @@ pub fn parse_ansible_reqs(
return Ok((logs, None, inner_content.to_string()));
}
let opts = AnsiblePlaybookOptions {
verbosity: None,
forks: None,
timeout: None,
flush_cache: None,
force_handlers: None,
};
let mut ret = AnsibleRequirements {
python_reqs: vec![],
collections: None,
file_resources: vec![],
inventories: vec![],
vars: vec![],
resources: vec![],
options: opts,
};
let mut ret = AnsibleRequirements::default();
if let Yaml::Hash(doc) = &docs[0] {
for (key, value) in doc {
@@ -303,7 +327,7 @@ pub fn parse_ansible_reqs(
let mut out_str = String::new();
let mut emitter = YamlEmitter::new(&mut out_str);
emitter.dump(galaxy_requirements)?;
ret.collections = Some(out_str);
ret.roles_and_collections = Some(out_str);
}
if let Some(Yaml::Array(py_reqs)) =
deps.get(&Yaml::String("python".to_string()))
@@ -345,11 +369,60 @@ pub fn parse_ansible_reqs(
Yaml::String(key) if key == "inventory" => {
ret.inventories = parse_inventories(value)?;
}
Yaml::String(key) if key == "vault_password" => {
let Yaml::String(filename) = value else {
return Err(anyhow!(
"Vault Password File expects a String containing the file name"
));
};
ret.vault_password = Some(filename.to_string());
}
Yaml::String(key) if key == "vault_id" => {
let Yaml::Array(filenames) = value else {
return Err(anyhow!("Vault ID field expects an array of strings in the format: `label@filename`"));
};
for f in filenames {
let Yaml::String(filename) = f else {
return Err(anyhow!("The elements of the vault_id field should be strings in the format: `label@filename`"));
};
ret.vault_id.push(filename.to_string());
}
}
Yaml::String(key) if key == "options" => {
if let Yaml::Array(opts) = &value {
ret.options = parse_ansible_options(opts);
}
}
Yaml::String(key) if key == "git_repos" => {
let Yaml::Array(repos) = &value else {
return Err(anyhow!("git_repos field expects an array of repos"));
};
for r in repos {
ret.git_repos.push(
parse_git_repo(r)
.map_err(|e| anyhow!("Failed to parse git repo: {e}"))?,
);
}
}
Yaml::String(key) if key == "git_ssh_identity" => {
let Yaml::Array(indentities) = &value else {
return Err(anyhow!(
"git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs"
));
};
for r in indentities {
let Yaml::String(file_name) = r else {
return Err(anyhow!(
"Git ssh identity file must be a string path to a Windmill variable/secret"
));
};
ret.git_ssh_identity.push(file_name.clone());
}
}
Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)),
_ => (),
}
@@ -364,6 +437,38 @@ pub fn parse_ansible_reqs(
Ok((logs, Some(ret), out_str))
}
fn parse_git_repo(r: &Yaml) -> anyhow::Result<GitRepo> {
let Yaml::Hash(repo) = r else {
return Err(anyhow!("Should be a Map"));
};
let url = repo
.get(&Yaml::String("url".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.ok_or(anyhow!("Expected `url` field"))?;
let target_path = repo
.get(&Yaml::String("target".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.ok_or(anyhow!(
"Expected `target` field (target directory for cloning the repo)"
))?;
let branch = repo
.get(&Yaml::String("branch".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let commit = repo
.get(&Yaml::String("commit".to_string()))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(GitRepo { url, commit, branch, target_path })
}
fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
let mut ret = AnsiblePlaybookOptions {
verbosity: None,
@@ -529,3 +634,78 @@ fn yaml_to_json(yaml: &Yaml) -> serde_json::Value {
_ => serde_json::Value::Null,
}
}
fn update_versions(
section: &str,
yaml: &mut Yaml,
versions: &HashMap<String, String>,
) -> anyhow::Result<String> {
let mut logs = String::new();
let Yaml::Hash(ref mut m) = yaml else {
return Err(anyhow!("{section} dependency should be a map"));
};
if let Some(Yaml::Array(elements)) = m.get_mut(&Yaml::String(section.to_string())) {
for el in elements {
let Yaml::Hash(ref mut h) = el else {
return Err(anyhow!("{section} dependency element should be a map"));
};
if let Some(name) = h
.get(&Yaml::String("name".to_string()))
.and_then(|n| n.as_str())
{
if let Some(version) = versions.get(name) {
h.insert(
Yaml::String("version".to_string()),
Yaml::String(version.to_string()),
);
} else {
logs.push_str(&format!("WARNING: {section} dependency `{name}` has no locked version, using the latest or system installed version.\n"));
}
} else {
return Err(anyhow!(
"{section} dependency element: missing or invalid `name` field"
));
}
}
}
Ok(logs)
}
pub fn add_versions_to_requirements_yaml(
input: &str,
role_versions: &HashMap<String, String>,
collection_versions: &HashMap<String, String>,
) -> anyhow::Result<(String,String)> {
let mut docs =
YamlLoader::load_from_str(input).map_err(|e| anyhow!("YAML parse error: {}", e))?;
let doc = &mut docs[0];
let mut logs = String::new();
logs.push_str(
&update_versions("roles", doc, role_versions)
.map_err(|e| anyhow!("Error updating role versions: {e}"))?,
);
logs.push_str(
&update_versions("collections", doc, collection_versions)
.map_err(|e| anyhow!("Error updating collection versions: {e}"))?,
);
if !logs.is_empty() {
logs.push_str("WARNING: You might want to try adding manual versions for these, otherwise there could be breaking changes on deployed scripts\n");
}
let mut out_str = String::new();
{
let mut emitter = YamlEmitter::new(&mut out_str);
emitter
.dump(doc)
.map_err(|e| anyhow!("YAML emit error: {}", e))?;
}
Ok((out_str, logs))
}

View File

@@ -8,11 +8,12 @@
use anyhow::Context;
use monitor::{
load_base_url, load_otel, reload_delete_logs_periodically_setting, reload_indexer_config,
load_base_url, load_otel, reload_critical_alerts_on_db_oversize,
reload_delete_logs_periodically_setting, reload_indexer_config,
reload_instance_python_version_setting, reload_maven_repos_setting,
reload_no_default_maven_setting, reload_nuget_config_setting,
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
send_logs_to_object_store,
send_logs_to_object_store, WORKERS_NAMES,
};
use rand::Rng;
use sqlx::postgres::PgListener;
@@ -33,15 +34,16 @@ use windmill_common::{
agent_workers::build_agent_http_client,
get_database_url,
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING,
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING,
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING,
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING,
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING,
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING,
TIMEOUT_WAIT_RESULT_SETTING,
@@ -265,6 +267,8 @@ async fn windmill_main() -> anyhow::Result<()> {
if mode == Mode::Standalone {
println!("Running in standalone mode");
} else if mode == Mode::MCP {
println!("Running in MCP mode");
}
#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
@@ -297,7 +301,7 @@ async fn windmill_main() -> anyhow::Result<()> {
}
#[allow(unused_mut)]
let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer {
let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer || mode == Mode::MCP {
0
} else {
std::env::var("NUM_WORKERS")
@@ -319,8 +323,9 @@ async fn windmill_main() -> anyhow::Result<()> {
&& (mode == Mode::Server || mode == Mode::Standalone);
let indexer_mode = mode == Mode::Indexer;
let mcp_mode = mode == Mode::MCP;
let server_bind_address: IpAddr = if server_mode || indexer_mode {
let server_bind_address: IpAddr = if server_mode || indexer_mode || mcp_mode {
std::env::var("SERVER_BIND_ADDR")
.ok()
.and_then(|x| x.parse().ok())
@@ -380,7 +385,7 @@ async fn windmill_main() -> anyhow::Result<()> {
.is_some_and(|x| x == "1" || x == "true");
if let Some(db) = conn.as_sql() {
if !is_agent && !indexer_mode {
if !is_agent && !indexer_mode && !mcp_mode {
let skip_migration = std::env::var("SKIP_MIGRATION")
.map(|val| val == "true")
.unwrap_or(false);
@@ -443,7 +448,7 @@ Windmill Community Edition {GIT_VERSION}
if !valid_key && !server_mode {
tracing::error!("Invalid license key, workers require a valid license key");
}
if server_mode {
if server_mode || mcp_mode {
if let Some(db) = conn.as_sql() {
// only force renewal if invalid but not empty (= expired)
let renewed_now = maybe_renew_license_key_on_start(
@@ -463,10 +468,10 @@ Windmill Community Edition {GIT_VERSION}
}
}
if server_mode || worker_mode || indexer_mode {
if server_mode || worker_mode || indexer_mode || mcp_mode {
let port_var = std::env::var("PORT").ok().and_then(|x| x.parse().ok());
let port = if server_mode || indexer_mode {
let port = if server_mode || indexer_mode || mcp_mode {
port_var.unwrap_or(DEFAULT_PORT as u16)
} else {
port_var.unwrap_or(0)
@@ -647,6 +652,7 @@ Windmill Community Edition {GIT_VERSION}
server_killpill_rx,
base_internal_tx,
server_mode,
mcp_mode,
base_internal_url.clone(),
)
.await?;
@@ -913,6 +919,12 @@ Windmill Community Edition {GIT_VERSION}
tracing::error!(error = %e, "Could not reload critical error emails setting");
}
},
CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING => {
if let Err(e) = reload_critical_alerts_on_db_oversize(&db).await {
tracing::error!(error = %e, "Could not reload critical alerts on db oversize setting");
}
},
JWT_SECRET_SETTING => {
if let Err(e) = reload_jwt_secret_setting(&db).await {
tracing::error!(error = %e, "Could not reload jwt secret setting");
@@ -1049,15 +1061,19 @@ Windmill Community Edition {GIT_VERSION}
}
}
futures::try_join!(
shutdown_signal,
workers_f,
monitor_f,
server_f,
metrics_f,
indexer_f,
log_indexer_f
)?;
if mcp_mode {
futures::try_join!(shutdown_signal, workers_f, server_f)?;
} else {
futures::try_join!(
shutdown_signal,
workers_f,
monitor_f,
server_f,
metrics_f,
indexer_f,
log_indexer_f
)?;
}
} else {
tracing::info!("Nothing to do, exiting.");
}
@@ -1206,10 +1222,12 @@ pub async fn run_workers(
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
*windmill_worker::SLEEP_QUEUE
);
for i in 1..(num_workers + 1) {
let wk_conf = &workers[i as usize - 1];
let conn1 = wk_conf.conn.clone();
let worker_name = wk_conf.worker_name.clone();
WORKERS_NAMES.write().await.push(worker_name.clone());
let ip = ip.clone();
let rx = killpill_rxs.pop().unwrap();
let tx = tx.clone();

View File

@@ -13,7 +13,7 @@ use std::{
use chrono::{NaiveDateTime, Utc};
use futures::{stream::FuturesUnordered, StreamExt};
use serde::de::DeserializeOwned;
use serde::{de::DeserializeOwned, Deserialize};
use sqlx::{Pool, Postgres};
use tokio::{
join,
@@ -30,28 +30,54 @@ use windmill_api::{
#[cfg(feature = "enterprise")]
use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts};
#[cfg(feature = "enterprise")]
use windmill_common::ee::low_disk_alerts;
#[cfg(feature = "oauth2")]
use windmill_common::global_settings::OAUTH_SETTING;
use windmill_common::{
utils::empty_string_as_none,
agent_workers::DECODED_AGENT_TOKEN, auth::create_token_for_owner, ee::CriticalErrorChannel, error, flow_status::{FlowStatus, FlowStatusModule}, global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NUGET_CONFIG_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
agent_workers::DECODED_AGENT_TOKEN,
auth::create_token_for_owner,
ee::CriticalErrorChannel,
error,
flow_status::{FlowStatus, FlowStatusModule},
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING,
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING,
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
}, indexer::load_indexer_config, jobs::QueuedJob, jwt::JWT_SECRET, oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH, server::load_smtp_config, tracing_init::JSON_FMT, users::truncate_token, utils::{now_from_db, rd_string, report_critical_error, Mode}, worker::{
load_worker_config, reload_custom_tags_setting, store_pull_query, store_suspended_pull_query, update_min_version, Connection, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR, WORKER_CONFIG, WORKER_GROUP
}, KillpillSender, BASE_URL, CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED, MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
},
indexer::load_indexer_config,
jobs::QueuedJob,
jwt::JWT_SECRET,
oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH,
server::load_smtp_config,
tracing_init::JSON_FMT,
users::truncate_token,
utils::empty_string_as_none,
utils::{now_from_db, rd_string, report_critical_error, Mode},
worker::{
load_worker_config, reload_custom_tags_setting, store_pull_query,
store_suspended_pull_query, update_min_version, Connection, DEFAULT_TAGS_PER_WORKSPACE,
DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR,
WORKER_CONFIG, WORKER_GROUP,
},
KillpillSender, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE, CRITICAL_ALERT_MUTE_UI_ENABLED,
CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
METRICS_DEBUG_ENABLED, METRICS_ENABLED, MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED,
OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
};
use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload};
use windmill_worker::{
handle_job_error, AuthedClient, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES, INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL
handle_job_error, AuthedClient, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
};
#[cfg(feature = "parquet")]
@@ -111,7 +137,7 @@ lazy_static::lazy_static! {
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
pub static ref WORKERS_NAMES: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
static ref QUEUE_COUNT_TAGS: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true");
@@ -135,7 +161,6 @@ pub async fn initial_load(
}
}
if let Err(e) = load_metrics_enabled(conn).await {
tracing::error!("Error loading expose metrics: {e:#}");
}
@@ -161,6 +186,12 @@ pub async fn initial_load(
if server_mode {
if let Some(db) = conn.as_sql() {
load_require_preexisting_user(db).await;
if let Err(e) = reload_critical_alerts_on_db_oversize(db).await {
tracing::error!(
"Error reloading critical alerts on db oversize setting: {:?}",
e
)
}
}
}
@@ -172,11 +203,13 @@ pub async fn initial_load(
}
Connection::Http(_) => {
// TODO: reload worker config from http
WORKER_CONFIG.write().await.worker_tags = DECODED_AGENT_TOKEN.as_ref().map(|x| x.tags.clone()).unwrap_or_default();
WORKER_CONFIG.write().await.worker_tags = DECODED_AGENT_TOKEN
.as_ref()
.map(|x| x.tags.clone())
.unwrap_or_default();
}
}
}
if let Err(e) = reload_hub_base_url_setting(conn, server_mode).await {
tracing::error!("Error reloading hub base url: {:?}", e)
@@ -190,7 +223,6 @@ pub async fn initial_load(
if let Err(e) = reload_custom_tags_setting(db).await {
tracing::error!("Error reloading custom tags: {:?}", e)
}
}
#[cfg(feature = "parquet")]
@@ -225,7 +257,8 @@ pub async fn initial_load(
}
pub async fn load_metrics_enabled(conn: &Connection) -> error::Result<()> {
let metrics_enabled = load_value_from_global_settings_with_conn(conn, EXPOSE_METRICS_SETTING, true).await;
let metrics_enabled =
load_value_from_global_settings_with_conn(conn, EXPOSE_METRICS_SETTING, true).await;
match metrics_enabled {
Ok(Some(serde_json::Value::Bool(t))) => METRICS_ENABLED.store(t, Ordering::Relaxed),
_ => (),
@@ -345,13 +378,13 @@ pub async fn reload_critical_alert_mute_ui_setting(conn: &Connection) -> error::
load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true).await
{
CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed);
}
Ok(())
}
pub async fn load_metrics_debug_enabled(conn: &Connection) -> error::Result<()> {
let metrics_enabled = load_value_from_global_settings_with_conn(conn, EXPOSE_DEBUG_METRICS_SETTING, true).await;
let metrics_enabled =
load_value_from_global_settings_with_conn(conn, EXPOSE_DEBUG_METRICS_SETTING, true).await;
match metrics_enabled {
Ok(Some(serde_json::Value::Bool(t))) => {
METRICS_DEBUG_ENABLED.store(t, Ordering::Relaxed);
@@ -575,7 +608,9 @@ async fn send_log_file_to_object_store(
};
let exists = LAST_LOG_FILE_SENT.lock().map(|last_log_file_sent| {
last_log_file_sent.map(|last_log_file_sent| last_log_file_sent >= ts).unwrap_or(false)
last_log_file_sent
.map(|last_log_file_sent| last_log_file_sent >= ts)
.unwrap_or(false)
});
if exists.unwrap_or(false) {
@@ -612,7 +647,9 @@ async fn send_log_file_to_object_store(
let (ok_lines, err_lines) = read_log_counters(ts_str);
if let Some(db) = conn.as_sql() {
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt)
VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)
ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7",
hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT)
.execute(db)
.await {
@@ -1002,11 +1039,21 @@ pub async fn reload_nuget_config_setting(conn: &Connection) {
.await;
}
pub async fn reload_maven_repos_setting(conn: &Connection) {
reload_option_setting_with_tracing(conn, windmill_common::global_settings::MAVEN_REPOS_SETTING, "MAVEN_REPOS", MAVEN_REPOS.clone())
.await;
reload_option_setting_with_tracing(
conn,
windmill_common::global_settings::MAVEN_REPOS_SETTING,
"MAVEN_REPOS",
MAVEN_REPOS.clone(),
)
.await;
}
pub async fn reload_no_default_maven_setting(conn: &Connection) {
let value = load_value_from_global_settings_with_conn(conn, windmill_common::global_settings::NO_DEFAULT_MAVEN_SETTING, true).await;
let value = load_value_from_global_settings_with_conn(
conn,
windmill_common::global_settings::NO_DEFAULT_MAVEN_SETTING,
true,
)
.await;
match value {
Ok(Some(serde_json::Value::Bool(t))) => NO_DEFAULT_MAVEN.store(t, Ordering::Relaxed),
Err(e) => {
@@ -1175,7 +1222,6 @@ pub async fn load_value_from_global_settings(
Ok(r)
}
pub async fn load_value_from_global_settings_with_conn(
conn: &Connection,
setting_name: &str,
@@ -1185,14 +1231,18 @@ pub async fn load_value_from_global_settings_with_conn(
Connection::Sql(db) => Ok(load_value_from_global_settings(db, setting_name).await?),
Connection::Http(client) => {
if load_from_http {
client.get::<Option<serde_json::Value>>(&format!("/api/agent_workers/get_global_setting/{}", setting_name)).await
.map_err(|e| anyhow::anyhow!("Error loading setting {}: {}", setting_name, e))
client
.get::<Option<serde_json::Value>>(&format!(
"/api/agent_workers/get_global_setting/{}",
setting_name
))
.await
.map_err(|e| anyhow::anyhow!("Error loading setting {}: {}", setting_name, e))
} else {
Ok(None)
}
}
}
}
pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
@@ -1314,12 +1364,12 @@ pub async fn monitor_db(
let zombie_jobs_f = async {
if server_mode && !initial_load && !*DISABLE_ZOMBIE_JOBS_MONITORING {
if let Some(db) = conn.as_sql() {
handle_zombie_jobs(db, base_internal_url, "server").await;
match handle_zombie_flows(db).await {
Err(err) => {
tracing::error!("Error handling zombie flows: {:?}", err);
},
_ => {}
handle_zombie_jobs(db, base_internal_url, "server").await;
match handle_zombie_flows(db).await {
Err(err) => {
tracing::error!("Error handling zombie flows: {:?}", err);
}
_ => {}
}
}
}
@@ -1327,7 +1377,7 @@ pub async fn monitor_db(
let expired_items_f = async {
if server_mode && !initial_load {
if let Some(db) = conn.as_sql() {
delete_expired_items(&db).await;
delete_expired_items(&db).await;
}
}
};
@@ -1365,6 +1415,23 @@ pub async fn monitor_db(
}
};
let low_disk_alerts_f = async {
#[cfg(feature = "enterprise")]
if let Some(db) = conn.as_sql() {
low_disk_alerts(
&db,
server_mode,
_worker_mode,
WORKERS_NAMES.read().await.clone(),
)
.await;
}
#[cfg(not(feature = "enterprise"))]
{
()
}
};
let apply_autoscaling_f = async {
#[cfg(feature = "enterprise")]
if server_mode && !initial_load {
@@ -1387,6 +1454,7 @@ pub async fn monitor_db(
verify_license_key_f,
worker_groups_alerts_f,
jobs_waiting_alerts_f,
low_disk_alerts_f,
apply_autoscaling_f,
update_min_worker_version_f,
);
@@ -1496,11 +1564,7 @@ pub async fn reload_indexer_config(db: &Pool<Postgres>) {
}
}
pub async fn reload_worker_config(
db: &DB,
tx: KillpillSender,
kill_if_change: bool,
) {
pub async fn reload_worker_config(db: &DB, tx: KillpillSender, kill_if_change: bool) {
let config = load_worker_config(db, tx.clone()).await;
if let Err(e) = config {
tracing::error!("Error reloading worker config: {:?}", e)
@@ -1543,7 +1607,8 @@ pub async fn reload_worker_config(
}
pub async fn load_base_url(conn: &Connection) -> error::Result<String> {
let q_base_url = load_value_from_global_settings_with_conn(conn, BASE_URL_SETTING, false).await?;
let q_base_url =
load_value_from_global_settings_with_conn(conn, BASE_URL_SETTING, false).await?;
let std_base_url = std::env::var("BASE_URL")
.ok()
@@ -1574,10 +1639,9 @@ pub async fn load_base_url(conn: &Connection) -> error::Result<String> {
}
pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
#[cfg(feature = "oauth2")]
let oauths = if let Some(db) = conn.as_sql() {
let q_oauth = load_value_from_global_settings (db, OAUTH_SETTING).await?;
let q_oauth = load_value_from_global_settings(db, OAUTH_SETTING).await?;
if let Some(q) = q_oauth {
if let Ok(v) = serde_json::from_value::<
@@ -1652,7 +1716,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
increment_counter AS (
INSERT INTO zombie_job_counter (job_id, counter)
SELECT id, 1 FROM to_update WHERE counter < $2
ON CONFLICT (job_id) DO UPDATE
ON CONFLICT (job_id) DO UPDATE
SET counter = zombie_job_counter.counter + 1
),
update_concurrency AS (
@@ -1744,7 +1808,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
let same_worker_timeout_jobs = {
let long_same_worker_jobs = sqlx::query!(
"SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval
"SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval
AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker",
)
.fetch_all(db)
@@ -1758,9 +1822,9 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
.collect::<Vec<_>>();
let long_dead_workers: std::collections::HashSet<String> = sqlx::query_scalar!(
"WITH worker_ids AS (SELECT unnest($1::text[]) as worker)
SELECT worker_ids.worker FROM worker_ids
LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker
"WITH worker_ids AS (SELECT unnest($1::text[]) as worker)
SELECT worker_ids.worker FROM worker_ids
LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker
WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval",
&worker_ids[..]
)
@@ -1804,7 +1868,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
let non_restartable_jobs = if *RESTART_ZOMBIE_JOBS {
vec![]
} else {
sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval
sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false")
.bind(ZOMBIE_JOB_TIMEOUT.as_str())
.fetch_all(db)
@@ -1863,7 +1927,8 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
mpsc::channel::<SameWorkerPayload>(1);
let same_worker_tx_never_used =
SameWorkerSender(same_worker_tx_never_used, Arc::new(AtomicU16::new(0)));
let (send_result_never_used, _send_result_rx_never_used) = JobCompletedSender::new_never_used();
let (send_result_never_used, _send_result_rx_never_used) =
JobCompletedSender::new_never_used();
let label = if job.permissioned_as != format!("u/{}", job.created_by)
&& job.permissioned_as != job.created_by
@@ -2001,7 +2066,7 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> {
}
);
report_critical_error(reason.clone(), db.clone(), Some(&flow.workspace_id), None).await;
cancel_zombie_flow_job(db, flow.id, &flow.workspace_id,
cancel_zombie_flow_job(db, flow.id, &flow.workspace_id,
format!(r#"{reason}
This would happen if a worker was interrupted, killed or crashed while doing a state transition at the end of a job which is always an unexpected behavior that should never happen.
Please check your worker logs for more details and feel free to report it to the Windmill team on our Discord or support@windmill.dev (response for non EE customers will be best effort) with as much context as possible, ideally:
@@ -2018,7 +2083,7 @@ Please check your worker logs for more details and feel free to report it to the
r#"
DELETE
FROM parallel_monitor_lock
WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval
WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval
RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q
WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL
) AS workspace_id
@@ -2073,8 +2138,12 @@ async fn cancel_zombie_flow_job(
Ok(())
}
pub async fn reload_hub_base_url_setting(conn: &Connection, server_mode: bool) -> error::Result<()> {
let hub_base_url = load_value_from_global_settings_with_conn(conn, HUB_BASE_URL_SETTING, true).await?;
pub async fn reload_hub_base_url_setting(
conn: &Connection,
server_mode: bool,
) -> error::Result<()> {
let hub_base_url =
load_value_from_global_settings_with_conn(conn, HUB_BASE_URL_SETTING, true).await?;
let base_url = if let Some(q) = hub_base_url {
if let Ok(v) = serde_json::from_value::<String>(q.clone()) {
@@ -2126,7 +2195,7 @@ pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result<
v
} else {
tracing::error!(
"Could not parse critical_error_emails setting as an array of channels, found: {:#?}",
"Could not parse critical_error_channels setting as an array of channels, found: {:#?}",
&q
);
vec![]
@@ -2141,6 +2210,37 @@ pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result<
Ok(())
}
pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<()> {
#[derive(Deserialize)]
struct DBOversize {
enabled: bool,
value: f32,
}
let db_oversize_value =
load_value_from_global_settings(conn, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING).await?;
let db_oversize = if let Some(q) = db_oversize_value {
match serde_json::from_value::<DBOversize>(q.clone()) {
Ok(DBOversize { enabled: true, value }) => Some(value),
Ok(_) => None,
Err(q) => {
tracing::error!(
"Could not parse critical_alerts_on_db_oversize setting, found: {:#?}",
&q
);
None
}
}
} else {
None
};
let mut l = CRITICAL_ALERTS_ON_DB_OVERSIZE.write().await;
*l = db_oversize;
Ok(())
}
async fn generate_and_save_jwt_secret(db: &DB) -> error::Result<String> {
let secret = rd_string(32);
sqlx::query!(

View File

@@ -16,16 +16,20 @@ use tokio::sync::RwLock;
#[cfg(feature = "enterprise")]
use tokio::time::{timeout, Duration};
#[cfg(feature = "python")]
use windmill_api_client::types::{CreateFlowBody, RawScript};
#[cfg(feature = "enterprise")]
use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs};
use windmill_api_client::types::{NewScript, ScriptLang as NewScriptLanguage};
use serde::Serialize;
#[cfg(feature = "deno_core")]
use windmill_common::flows::InputTransform;
use windmill_common::worker::WORKER_CONFIG;
use windmill_common::{
flow_status::{FlowStatus, FlowStatusModule, RestartedFrom},
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
flows::{FlowModule, FlowModuleValue, FlowValue},
jobs::{JobKind, JobPayload, RawCode},
jwt::JWT_SECRET,
scripts::{ScriptHash, ScriptLang},
@@ -137,10 +141,11 @@ impl ApiServer {
rx,
port_tx,
false,
false,
format!("http://localhost:{}", addr.port()),
));
_port_rx.await.unwrap();
_port_rx.await.expect("failed to receive port");
// clear the cache between tests
windmill_common::cache::clear();
@@ -166,6 +171,7 @@ impl ApiServer {
// Ok(())
// }
#[cfg(feature = "python")]
fn get_module(cjob: &CompletedJob, id: &str) -> Option<FlowStatusModule> {
cjob.flow_status.clone().and_then(|fs| {
find_module_in_vec(
@@ -175,6 +181,7 @@ fn get_module(cjob: &CompletedJob, id: &str) -> Option<FlowStatusModule> {
})
}
#[cfg(feature = "python")]
fn find_module_in_vec(modules: Vec<FlowStatusModule>, id: &str) -> Option<FlowStatusModule> {
modules.into_iter().find(|s| s.id() == id)
}
@@ -283,6 +290,7 @@ mod suspend_resume {
.unwrap()
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -365,6 +373,7 @@ mod suspend_resume {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn cancel_from_job(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -390,6 +399,7 @@ mod suspend_resume {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn cancel_after_suspend(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -563,6 +573,7 @@ def main(last, port):
.unwrap()
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_pass(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -608,6 +619,7 @@ def main(last, port):
assert_eq!(json!([3, 5, 7, 9]), result);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_fail_step_zero(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -651,6 +663,7 @@ def main(last, port):
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_fail_step_one(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -692,6 +705,7 @@ def main(last, port):
.contains("index out of range"));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_with_failure_module(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -768,6 +782,7 @@ def main(error, port):
}
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_iteration(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -826,6 +841,7 @@ async fn test_iteration(db: Pool<Postgres>) {
.contains("2"));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_iteration_parallel(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1104,6 +1120,7 @@ trait StreamFind: futures::Stream + Unpin + Sized {
impl<T: futures::Stream + Unpin + Sized> StreamFind for T {}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_deno_flow(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1222,6 +1239,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
}
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_identity(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1259,6 +1277,7 @@ async fn test_identity(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!(42));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1534,6 +1553,7 @@ async fn test_flow_result_by_id(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([[42]]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_stop_after_if(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1587,6 +1607,7 @@ async fn test_stop_after_if(db: Pool<Postgres>) {
assert_eq!(json!(-123), result);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_stop_after_if_nested(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1645,6 +1666,7 @@ async fn test_stop_after_if_nested(db: Pool<Postgres>) {
assert_eq!(json!([-123]), result);
}
#[cfg(all(feature = "deno_core", feature = "python"))]
#[sqlx::test(fixtures("base"))]
async fn test_python_flow(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1702,6 +1724,7 @@ async fn test_python_flow(db: Pool<Postgres>) {
}
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_flow_2(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -1776,6 +1799,7 @@ func main(derp string) (string, error) {
assert_eq!(result, serde_json::json!("hello world"));
}
#[cfg(feature = "rust")]
#[sqlx::test(fixtures("base"))]
async fn test_rust_job(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2031,6 +2055,7 @@ public class Main {
assert_eq!(job.json_result(), Some(json!("hello world")));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2064,6 +2089,7 @@ def main():
assert_eq!(result, serde_json::json!("hello world"));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job_heavy_dep(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2100,6 +2126,7 @@ def main():
assert_eq!(result, serde_json::json!(3));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job_with_imports(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2203,6 +2230,7 @@ export async function main(a: Date) {
assert_eq!(result, serde_json::json!("object"));
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_python_job_datetime_and_bytes(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2238,6 +2266,7 @@ def main(a: datetime, b: bytes):
assert_eq!(result, serde_json::json!([true, true]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_empty_loop_1(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2294,6 +2323,7 @@ async fn test_empty_loop_1(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!(0));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_invalid_first_step(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2374,6 +2404,7 @@ async fn test_empty_loop_2(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_step_after_loop(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2497,6 +2528,7 @@ async fn test_branchone_simple(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([1, 2]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchone_with_cond(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2533,6 +2565,7 @@ async fn test_branchone_with_cond(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([1, 3]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchall_sequential(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2571,6 +2604,7 @@ async fn test_branchall_sequential(db: Pool<Postgres>) {
assert_eq!(result, serde_json::json!([[1, 2], [1, 3]]));
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchall_simple(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2698,6 +2732,7 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_branchone_nested(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2817,6 +2852,7 @@ async fn test_branchall_nested(db: Pool<Postgres>) {
);
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_failure_module(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -2929,6 +2965,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
assert_eq!(json!({ "l": [0, 1, 2] }), result);
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base"))]
async fn test_flow_lock_all(db: Pool<Postgres>) {
use futures::StreamExt;
@@ -3067,6 +3104,7 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
});
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base"))]
async fn test_complex_flow_restart(db: Pool<Postgres>) {
@@ -3751,6 +3789,7 @@ export async function main() {
run_preview_relative_imports(&db, content, ScriptLang::Bun).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "relative_bun"))]
async fn test_nested_imports_bun(db: Pool<Postgres>) {
let content = r#"
@@ -3799,6 +3838,7 @@ export async function main() {
run_preview_relative_imports(&db, content, ScriptLang::Deno).await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "relative_python"))]
async fn test_relative_imports_python(db: Pool<Postgres>) {
let content = r#"
@@ -3816,6 +3856,7 @@ def main():
run_preview_relative_imports(&db, content, ScriptLang::Python3).await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "relative_python"))]
async fn test_nested_imports_python(db: Pool<Postgres>) {
let content = r#"
@@ -3831,6 +3872,7 @@ def main():
run_preview_relative_imports(&db, content, ScriptLang::Python3).await;
}
#[cfg(feature = "python")]
async fn assert_lockfile(
db: &Pool<Postgres>,
script_content: String,
@@ -3923,6 +3965,8 @@ async fn assert_lockfile(
)
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_requirements_python(db: Pool<Postgres>) {
let content = r#"
@@ -3948,6 +3992,8 @@ def main():
)
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_extra_requirements_python(db: Pool<Postgres>) {
{
@@ -3975,9 +4021,10 @@ def main():
.await;
}
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_extra_requirements_python2(db: Pool<Postgres>) {
let content = r#"
# py311
# extra_requirements:
@@ -3993,26 +4040,23 @@ def main():
&db,
content,
ScriptLang::Python3,
vec![
"# py311",
"simplejson==3.20.1",
"tiny==0.1.3"
],
vec!["# py311", "simplejson==3.20.1", "tiny==0.1.3"],
)
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_pins_python(db: Pool<Postgres>) {
let content = r#"
# py311
# extra_requirements:
# tiny==0.1.3
# bottle==0.13.2
import f.system.requirements
import f.system.pins
import tiny # repin: bottle==0.13.0
import tiny # repin: tiny==0.1.3
import simplejson
def main():
@@ -4026,10 +4070,10 @@ def main():
ScriptLang::Python3,
vec![
"# py311",
"bottle==0.13.0",
"bottle==0.13.2",
"microdot==2.2.0",
"simplejson==3.19.3",
"tiny==0.1.3"
"tiny==0.1.3",
],
)
.await;
@@ -4285,6 +4329,7 @@ mod job_payload {
];
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_script_hash_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4445,6 +4490,7 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_flow_node_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4629,6 +4675,7 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_flow_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4671,6 +4718,7 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_flow_payload_with_preprocessor(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4738,6 +4786,7 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_restarted_flow_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4790,6 +4839,7 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_raw_flow_payload(db: Pool<Postgres>) {
initialize_tracing().await;
@@ -4836,6 +4886,7 @@ mod job_payload {
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
}
#[cfg(feature = "deno_core")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_raw_flow_payload_with_restarted_from(db: Pool<Postgres>) {
initialize_tracing().await;

View File

@@ -13,9 +13,9 @@ default = []
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker/enterprise"]
stripe = []
agent_worker_server = []
enterprise_saml = ["dep:samael"]
enterprise_saml = ["dep:samael", "dep:libxml"]
benchmark = []
embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn", "dep:half"]
embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn"]
parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet", "windmill-worker/parquet"]
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker/prometheus"]
openidconnect = ["dep:openidconnect"]
@@ -35,8 +35,10 @@ sqs_trigger = ["dep:aws-sdk-sqs", "dep:thiserror", "dep:aws-config"]
deno_core = ["dep:deno_core", "dep:deno_error"]
gcp_trigger = ["dep:thiserror", "dep:google-cloud-pubsub", "dep:google-cloud-googleapis", "dep:tonic"]
cloud = ["windmill-common/cloud"]
mcp = ["dep:rmcp"]
[dependencies]
rmcp = { git = "https://github.com/windmill-labs/rust-sdk", features = ["transport-sse-server"], optional = true }
windmill-queue.workspace = true
windmill-common = { workspace = true, default-features = false }
windmill-audit.workspace = true
@@ -52,7 +54,6 @@ tokio-stream.workspace = true
anyhow.workspace = true
argon2.workspace = true
axum.workspace = true
half = { workspace = true, optional = true}
futures.workspace = true
git-version.workspace = true
tower.workspace = true
@@ -100,6 +101,7 @@ async_zip = { workspace = true, optional = true }
regex.workspace = true
bytes.workspace = true
samael = { workspace = true, optional = true }
libxml = { workspace = true, optional = true }
async-recursion.workspace = true
rsa = { workspace = true, optional = true}
uuid.workspace = true
@@ -133,6 +135,7 @@ rust-postgres-native-tls = { workspace = true, optional = true}
rumqttc = { workspace = true, optional = true }
aws-sdk-sqs = { workspace = true, optional = true }
aws-config = { workspace = true, optional = true }
aws-sdk-sts = { workspace = true, optional = true }
google-cloud-pubsub = { workspace = true, optional = true }
google-cloud-googleapis = { workspace = true , optional = true }
tonic = { workspace = true, optional = true }

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.482.1
version: 1.486.1
title: Windmill API
contact:
@@ -5985,6 +5985,55 @@ paths:
schema:
type: string
/w/{workspace}/apps/create_raw:
post:
summary: create app raw
operationId: createAppRaw
tags:
- app
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: new app
required: true
content:
multipart/form-data:
schema:
type: object
properties:
app:
type: object
properties:
path:
type: string
value: {}
summary:
type: string
policy:
$ref: "#/components/schemas/Policy"
draft_only:
type: boolean
deployment_message:
type: string
custom_path:
type: string
required:
- path
- value
- summary
- policy
js:
type: string
css:
type: string
responses:
"201":
description: app created
content:
text/plain:
schema:
type: string
/w/{workspace}/apps/exists/{path}:
get:
summary: does an app exisst at path
@@ -6344,6 +6393,49 @@ paths:
schema:
type: string
/w/{workspace}/apps/update_raw/{path}:
post:
summary: update app
operationId: updateAppRaw
tags:
- app
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
requestBody:
description: update app
required: true
content:
multipart/form-data:
schema:
type: object
properties:
app:
type: object
properties:
path:
type: string
summary:
type: string
value: {}
policy:
$ref: "#/components/schemas/Policy"
deployment_message:
type: string
custom_path:
type: string
js:
type: string
css:
type: string
responses:
"200":
description: app updated
content:
text/plain:
schema:
type: string
/w/{workspace}/apps/custom_path_exists/{custom_path}:
get:
summary: check if custom path exists
@@ -9447,7 +9539,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/NewGcpTrigger"
$ref: "#/components/schemas/GcpTriggerData"
responses:
"201":
description: gcp trigger created
@@ -9471,7 +9563,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/EditGcpTrigger"
$ref: "#/components/schemas/GcpTriggerData"
responses:
"200":
description: gcp trigger updated
@@ -9665,7 +9757,7 @@ paths:
type: array
items:
type: string
/w/{workspace}/gcp_triggers/subscriptions/list/{path}:
post:
summary: list all subscription of a give topic from google cloud service
@@ -14040,7 +14132,7 @@ components:
csharp,
nu,
java
# for related places search: ADD_NEW_LANG
# for related places search: ADD_NEW_LANG
]
Preview:
@@ -14953,8 +15045,6 @@ components:
type: string
authenticate:
type: boolean
base_endpoint:
type: string
required:
- authenticate
- base_endpoint
@@ -14976,6 +15066,8 @@ components:
$ref: "#/components/schemas/DeliveryType"
delivery_config:
$ref: "#/components/schemas/PushConfig"
subscription_mode:
$ref: "#/components/schemas/SubscriptionMode"
last_server_ping:
type: string
format: date-time
@@ -14989,7 +15081,9 @@ components:
- subscription_id
- enabled
- delivery_type
- subscription_mode
SubscriptionMode:
type: string
enum:
@@ -14997,55 +15091,24 @@ components:
- create_update
description: "The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new subscription."
GcpExistingSubscription:
GcpTriggerData:
type: object
properties:
gcp_resource_path:
type: string
subscription_mode:
$ref: "#/components/schemas/SubscriptionMode"
topic_id:
type: string
subscription_id:
type: string
base_endpoint:
type: string
required:
- subscription_id
- base_endpoint
GcpCreateUpdateSubscription:
type: object
properties:
subscription_id:
type: string
delivery_type:
$ref: "#/components/schemas/DeliveryType"
delivery_config:
$ref: "#/components/schemas/PushConfig"
required:
- delivery_type
GcpSubscriptionModeConfig:
type: object
properties:
subscription_mode:
$ref: "#/components/schemas/SubscriptionMode"
required:
- subscription_mode
allOf:
- oneOf:
- $ref: "#/components/schemas/GcpExistingSubscription"
- $ref: "#/components/schemas/GcpCreateUpdateSubscription"
description: |
"This is a union type representing the subscription mode.
- 'existing': Represents an existing GCP subscription, and should be accompanied by an 'ExistingGcpSubscription' object.
- 'create_update': Represents a new or updated GCP subscription, and should be accompanied by a 'CreateUpdateConfig' object."
NewGcpTrigger:
type: object
properties:
gcp_resource_path:
type: string
topic_id:
type: string
subscription_mode:
$ref: "#/components/schemas/GcpSubscriptionModeConfig"
path:
type: string
script_path:
@@ -15062,34 +15125,6 @@ components:
- topic_id
- subscription_mode
EditGcpTrigger:
type: object
properties:
gcp_resource_path:
type: string
topic_id:
type: string
subscription_mode:
$ref: "#/components/schemas/GcpSubscriptionModeConfig"
path:
type: string
script_path:
type: string
is_flow:
type: boolean
enabled:
type: boolean
required:
- path
- script_path
- is_flow
- enabled
- mqtt_resource_path
- subscription_id
- delivery_type
- topic_id
- subscription_mode
GetAllTopicSubscription:
type: object
properties:
@@ -15106,7 +15141,13 @@ components:
type: string
required:
- subscription_id
AwsAuthResourceType:
type: string
enum:
- oidc
- credentials
SqsTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
@@ -15114,6 +15155,8 @@ components:
properties:
queue_url:
type: string
aws_auth_resource_type:
$ref: "#/components/schemas/AwsAuthResourceType"
aws_resource_path:
type: string
message_attributes:
@@ -15134,12 +15177,15 @@ components:
- queue_url
- aws_resource_path
- enabled
- aws_auth_resource_type
NewSqsTrigger:
type: object
properties:
queue_url:
type: string
aws_auth_resource_type:
$ref: "#/components/schemas/AwsAuthResourceType"
aws_resource_path:
type: string
message_attributes:
@@ -15160,12 +15206,15 @@ components:
- path
- script_path
- is_flow
- aws_auth_resource_type
EditSqsTrigger:
type: object
properties:
queue_url:
type: string
aws_auth_resource_type:
$ref: "#/components/schemas/AwsAuthResourceType"
aws_resource_path:
type: string
message_attributes:
@@ -15187,6 +15236,7 @@ components:
- script_path
- is_flow
- enabled
- aws_auth_resource_type
Slot:
type: object
@@ -15906,6 +15956,8 @@ components:
execution_mode:
type: string
enum: [viewer, publisher, anonymous]
raw_app:
type: boolean
required:
- id
- workspace_id
@@ -16652,4 +16704,29 @@ components:
presigned:
type: string
required:
- s3
- s3
TeamsChannel:
type: object
required:
- team_id
- team_name
- channel_id
- channel_name
properties:
team_id:
type: string
description: Microsoft Teams team ID
minLength: 1
team_name:
type: string
description: Microsoft Teams team name
minLength: 1
channel_id:
type: string
description: Microsoft Teams channel ID
minLength: 1
channel_name:
type: string
description: Microsoft Teams channel name
minLength: 1

View File

@@ -21,8 +21,8 @@ pub fn workspaced_service(
_base_internal_url: String,
) -> (
Router,
Option<tokio::task::JoinHandle<()>>,
windmill_worker::JobCompletedSender,
Vec<tokio::task::JoinHandle<()>>,
Option<windmill_worker::JobCompletedSender>,
) {
use windmill_common::worker::Connection;
use windmill_worker::JobCompletedSender;
@@ -32,7 +32,7 @@ pub fn workspaced_service(
let router = Router::new();
(router, None, job_completed_tx)
(router, vec![], Some(job_completed_tx))
}
#[derive(Clone, Debug, Deserialize, Serialize)]

View File

@@ -24,10 +24,10 @@ use crate::{
},
users::fetch_api_authed_from_permissioned_as,
};
#[cfg(feature = "parquet")]
use axum::response::Response;
use axum::{
extract::{Extension, Json, Path, Query},
body::Body,
extract::{Extension, Json, Multipart, Path, Query},
response::IntoResponse,
routing::{delete, get, post},
Router,
@@ -88,10 +88,13 @@ pub fn workspaced_service() -> Router {
.route("/get/draft/*path", get(get_app_w_draft))
.route("/secret_of/*path", get(get_secret_id))
.route("/get/v/*id", get(get_app_by_id))
.route("/get_data/v/*id", get(get_raw_app_data))
.route("/exists/*path", get(exists_app))
.route("/update/*path", post(update_app))
.route("/update_raw/*path", post(update_app_raw))
.route("/delete/*path", delete(delete_app))
.route("/create", post(create_app))
.route("/create_raw", post(create_app_raw))
.route("/history/p/*path", get(get_app_history))
.route("/get_latest_version/*path", get(get_latest_version))
.route("/history_update/a/:id/v/:version", post(update_app_history))
@@ -135,6 +138,12 @@ pub struct ListableApp {
#[sqlx(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub deployment_msg: Option<String>,
#[serde(skip_serializing_if = "is_false")]
pub raw_app: bool,
}
fn is_false(b: &bool) -> bool {
!b
}
#[derive(FromRow, Serialize, Deserialize)]
@@ -328,7 +337,8 @@ async fn list_apps(
"app.extra_perms",
"favorite.path IS NOT NULL as starred",
"draft.path IS NOT NULL as has_draft",
"draft_only"
"draft_only",
"app_version.raw_app",
])
.left()
.join("favorite")
@@ -387,6 +397,44 @@ async fn list_apps(
Ok(Json(rows))
}
async fn get_raw_app_data(Path((w_id, version_id)): Path<(String, String)>) -> Result<Response> {
let file_path = format!("/tmp/wmill/{}/{}", w_id, version_id);
let file = tokio::fs::File::open(file_path).await?;
let stream = tokio_util::io::ReaderStream::new(file);
let res = Response::builder().header(
http::header::CONTENT_TYPE,
if version_id.ends_with(".css") {
"text/css"
} else {
"text/javascript"
},
);
Ok(res.body(Body::from_stream(stream)).unwrap())
}
// async fn get_app_version(
// authed: ApiAuthed,
// Extension(user_db): Extension<UserDB>,
// Path((w_id, path)): Path<(String, StripPath)>,
// ) -> JsonResult<i64> {
// let path = path.to_path();
// let mut tx = user_db.begin(&authed).await?;
// let version_o = sqlx::query_scalar!(
// "SELECT app.versions[array_upper(app.versions, 1)] as version FROM app
// WHERE app.path = $1 AND app.workspace_id = $2",
// path,
// &w_id,
// )
// .fetch_optional(&mut *tx)
// .await?
// .flatten();
// tx.commit().await?;
// let version = not_found_if_none(version_o, "App", path)?;
// Ok(Json(version))
// }
async fn get_app(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
@@ -727,6 +775,97 @@ async fn get_secret_id(
Ok(hx)
}
macro_rules! process_app_multipart {
($authed:expr, $user_db:expr, $db:expr, $w_id:expr, $path:expr, $multipart:expr, $internal_fn:expr) => {
async {
let mut saved_app = None;
let mut uploaded_js = false;
//todo: use s3 instead
let file_path = format!("/tmp/wmill/{}", $w_id);
std::fs::create_dir_all(&file_path).unwrap();
let mut multipart = $multipart;
while let Some(field) = multipart.next_field().await.unwrap() {
let name = field.name().unwrap().to_string();
let data = field.bytes().await.unwrap();
if name == "app" {
let app = serde_json::from_slice(&data).map_err(to_anyhow)?;
let (ntx, npath, nid) = $internal_fn(
$authed.clone(),
$db.clone(),
$user_db.clone(),
$w_id,
$path,
true,
app,
)
.await?;
saved_app = Some((npath, nid, ntx));
} else if name == "js" {
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
let file_path = format!("{}/{}.js", file_path, id);
std::fs::write(file_path, data).unwrap();
uploaded_js = true;
} else {
return Err(Error::BadRequest(
"App payload need to be created first".to_string(),
));
}
} else if name == "css" {
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
let file_path = format!("{}/{}.css", file_path, id);
std::fs::write(file_path, data).unwrap();
} else {
return Err(Error::BadRequest(
"App payload need to be created first".to_string(),
));
}
} else {
return Err(Error::BadRequest(format!("Unsupported field: {}", name)));
}
}
if !uploaded_js {
return Err(Error::BadRequest("js or css file not uploaded".to_string()));
}
if let Some((npath, id, tx)) = saved_app {
tx.commit().await?;
Ok((npath, id))
} else {
Err(Error::BadRequest("App not created".to_string()))
}
}
};
}
async fn create_app_raw<'a>(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(webhook): Extension<WebhookShared>,
Path(w_id): Path<String>,
multipart: Multipart,
) -> Result<(StatusCode, String)> {
let (path, _id) = process_app_multipart!(
authed,
user_db,
db,
&w_id,
"",
multipart,
|authed, db, user_db, w_id, _path, raw_app, app| create_app_internal(
authed, db, user_db, w_id, raw_app, app
)
)
.await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::CreateApp { workspace: w_id, path: path.clone() },
);
Ok((StatusCode::CREATED, path))
}
async fn list_paths_from_workspace_runnable(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
@@ -755,17 +894,36 @@ async fn create_app(
Extension(db): Extension<DB>,
Extension(webhook): Extension<WebhookShared>,
Path(w_id): Path<String>,
Json(mut app): Json<CreateApp>,
Json(app): Json<CreateApp>,
) -> Result<(StatusCode, String)> {
let mut tx = user_db.clone().begin(&authed).await?;
let path = app.path.clone();
let (new_tx, _path, _id) = create_app_internal(authed, db, user_db, &w_id, false, app).await?;
new_tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::CreateApp { workspace: w_id, path: path.clone() },
);
Ok((StatusCode::CREATED, path))
}
async fn create_app_internal<'a>(
authed: ApiAuthed,
db: sqlx::Pool<sqlx::Postgres>,
user_db: UserDB,
w_id: &String,
raw_app: bool,
mut app: CreateApp,
) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> {
let mut tx = user_db.clone().begin(&authed).await?;
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
app.policy.on_behalf_of_email = Some(authed.email.clone());
let path = app.path.clone();
if &app.path == "" {
return Err(Error::BadRequest("App path cannot be empty".to_string()));
}
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM app WHERE path = $1 AND workspace_id = $2)",
&app.path,
@@ -774,21 +932,19 @@ async fn create_app(
.fetch_one(&mut *tx)
.await?
.unwrap_or(false);
if exists {
return Err(Error::BadRequest(format!(
"App with path {} already exists",
&app.path
)));
}
if let Some(custom_path) = &app.custom_path {
require_admin(authed.is_admin, &authed.username)?;
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))",
custom_path,
if *CLOUD_HOSTED { Some(&w_id) } else { None }
if *CLOUD_HOSTED { Some(w_id) } else { None }
)
.fetch_one(&mut *tx)
.await?.unwrap_or(false);
@@ -800,7 +956,6 @@ async fn create_app(
)));
}
}
sqlx::query!(
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
&app.path,
@@ -808,7 +963,6 @@ async fn create_app(
)
.execute(&mut *tx)
.await?;
let id = sqlx::query_scalar!(
"INSERT INTO app
(workspace_id, path, summary, policy, versions, draft_only, custom_path)
@@ -819,24 +973,24 @@ async fn create_app(
json!(app.policy),
app.draft_only,
app.custom_path
.as_ref()
.map(|s| if s.is_empty() { None } else { Some(s) })
.flatten()
)
.fetch_one(&mut *tx)
.await?;
let v_id = sqlx::query_scalar!(
"INSERT INTO app_version
(app_id, value, created_by)
VALUES ($1, $2::text::json, $3) RETURNING id",
(app_id, value, created_by, raw_app)
VALUES ($1, $2::text::json, $3, $4) RETURNING id",
id,
//to preserve key orders
serde_json::to_string(&app.value).unwrap(),
authed.username,
raw_app
)
.fetch_one(&mut *tx)
.await?;
sqlx::query!(
"UPDATE app SET versions = array_append(versions, $1::bigint) WHERE id = $2",
v_id,
@@ -850,22 +1004,20 @@ async fn create_app(
&authed,
"apps.create",
ActionKind::Create,
&w_id,
w_id,
Some(&app.path),
None,
)
.await?;
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
if let Some(dm) = app.deployment_message {
if let Some(dm) = &app.deployment_message {
args.insert("deployment_message".to_string(), to_raw_value(&dm));
}
let tx = PushIsolationLevel::Transaction(tx);
let (dependency_job_uuid, new_tx) = push(
&db,
tx,
&w_id,
w_id,
JobPayload::AppDependencies { path: app.path.clone(), version: v_id },
PushArgs { args: &args, extra: None },
&authed.username,
@@ -889,14 +1041,7 @@ async fn create_app(
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
new_tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::CreateApp { workspace: w_id, path: app.path.clone() },
);
Ok((StatusCode::CREATED, app.path))
Ok((new_tx, path, v_id))
}
async fn list_hub_apps(Extension(db): Extension<DB>) -> impl IntoResponse {
@@ -1017,12 +1162,76 @@ async fn update_app(
Path((w_id, path)): Path<(String, StripPath)>,
Json(ns): Json<EditApp>,
) -> Result<String> {
use sql_builder::prelude::*;
// create_app_internal(authed, user_db, db, &w_id, &mut app).await?;
let path = path.to_path();
let opath = path.to_string();
let (new_tx, npath, _v_id) =
update_app_internal(authed, db, user_db, &w_id, path, false, ns).await?;
new_tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::UpdateApp {
workspace: w_id.clone(),
old_path: opath.clone(),
new_path: npath.clone(),
},
);
Ok(format!("app {} updated (npath: {:?})", opath, npath))
}
async fn update_app_raw<'a>(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(webhook): Extension<WebhookShared>,
Path((w_id, path)): Path<(String, StripPath)>,
multipart: Multipart,
) -> Result<String> {
let path = path.to_path();
let opath = path.to_string();
let (npath, _id) = process_app_multipart!(
authed,
user_db,
db,
&w_id,
path,
multipart,
update_app_internal
)
.await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::UpdateApp {
workspace: w_id.clone(),
old_path: opath.to_owned(),
new_path: npath.clone(),
},
);
Ok(format!("app {} updated (npath: {:?})", opath, npath))
}
// async fn create_app_internal<'a>(
// authed: ApiAuthed,
// db: sqlx::Pool<sqlx::Postgres>,
// user_db: UserDB,
// w_id: &String,
// app: &mut CreateApp,
// )
async fn update_app_internal<'a>(
authed: ApiAuthed,
db: sqlx::Pool<sqlx::Postgres>,
user_db: UserDB,
w_id: &str,
path: &str,
raw_app: bool,
ns: EditApp,
) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> {
use sql_builder::prelude::*;
let mut tx = user_db.clone().begin(&authed).await?;
let npath = if ns.policy.is_some()
|| ns.path.is_some()
|| ns.summary.is_some()
@@ -1069,7 +1278,7 @@ async fn update_app(
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))",
ncustom_path,
if *CLOUD_HOSTED { Some(&w_id) } else { None },
if *CLOUD_HOSTED { Some(w_id) } else { None },
path,
w_id
)
@@ -1116,12 +1325,13 @@ async fn update_app(
let v_id = sqlx::query_scalar!(
"INSERT INTO app_version
(app_id, value, created_by)
VALUES ($1, $2::text::json, $3) RETURNING id",
(app_id, value, created_by, raw_app)
VALUES ($1, $2::text::json, $3, $4) RETURNING id",
app_id,
//to preserve key orders
serde_json::to_string(&nvalue).unwrap(),
authed.username,
raw_app
)
.fetch_one(&mut *tx)
.await?;
@@ -1152,7 +1362,6 @@ async fn update_app(
)));
}
};
sqlx::query!(
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
path,
@@ -1160,29 +1369,26 @@ async fn update_app(
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed,
"apps.update",
ActionKind::Update,
&w_id,
w_id,
Some(&npath),
None,
)
.await?;
let tx = PushIsolationLevel::Transaction(tx);
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
if let Some(dm) = ns.deployment_message {
args.insert("deployment_message".to_string(), to_raw_value(&dm));
}
args.insert("parent_path".to_string(), to_raw_value(&path));
let (dependency_job_uuid, new_tx) = push(
&db,
tx,
&w_id,
w_id,
JobPayload::AppDependencies { path: npath.clone(), version: v_id },
PushArgs { args: &args, extra: None },
&authed.username,
@@ -1205,18 +1411,7 @@ async fn update_app(
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
new_tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::UpdateApp {
workspace: w_id,
old_path: path.to_owned(),
new_path: npath.clone(),
},
);
Ok(format!("app {} updated (npath: {:?})", path, npath))
Ok((new_tx, npath, v_id))
}
#[derive(Debug, Deserialize, Clone)]

View File

@@ -511,6 +511,13 @@ where
let path_vec: Vec<&str> = original_uri.path().split("/").collect();
let workspace_id = if path_vec.len() >= 4 && path_vec[0] == "" && path_vec[2] == "w" {
Some(path_vec[3].to_owned())
} else if path_vec.len() >= 5
&& path_vec[0] == ""
&& path_vec[1] == "api"
&& path_vec[2] == "mcp"
&& path_vec[3] == "w"
{
Some(path_vec[4].to_string())
} else {
if path_vec.len() >= 5
&& path_vec[0] == ""

View File

@@ -18,16 +18,22 @@ use {
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
use crate::gcp_triggers_ee::{
manage_google_subscription, process_google_push_request, validate_jwt_token, SubscriptionMode,
manage_google_subscription, process_google_push_request, validate_jwt_token,
CreateUpdateConfig, SubscriptionMode,
};
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
use windmill_common::auth::aws::AwsAuthResourceType;
#[cfg(any(
feature = "http_trigger",
all(feature = "enterprise", feature = "gcp_trigger")
))]
use {
axum::extract::Request, http::HeaderMap, serde::de::DeserializeOwned,
windmill_common::error::Error,
axum::extract::Request,
http::HeaderMap,
serde::de::DeserializeOwned,
windmill_common::{error::Error, utils::empty_string_as_none},
};
#[cfg(all(feature = "enterprise", feature = "kafka"))]
@@ -150,14 +156,20 @@ pub struct SqsTriggerConfig {
pub queue_url: String,
pub aws_resource_path: String,
pub message_attributes: Option<Vec<String>>,
pub aws_auth_resource_type: AwsAuthResourceType,
}
#[cfg(all(feature = "enterprise", feature = "gcp_trigger"))]
#[derive(Debug, Serialize, Deserialize)]
pub struct GcpTriggerConfig {
pub gcp_resource_path: String,
#[serde(flatten)]
pub subscription_mode: SubscriptionMode,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub subscription_id: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub base_endpoint: Option<String>,
#[serde(flatten)]
pub create_update: Option<CreateUpdateConfig>,
pub topic_id: String,
}
@@ -366,11 +378,15 @@ async fn set_gcp_trigger_config(
&gcp_config.gcp_resource_path,
&capture_config.path,
&gcp_config.topic_id,
&mut gcp_config.subscription_id,
&mut gcp_config.base_endpoint,
gcp_config.subscription_mode,
gcp_config.create_update,
false,
)
.await?;
gcp_config.subscription_mode = SubscriptionMode::CreateUpdate(config);
gcp_config.create_update = Some(config);
gcp_config.subscription_mode = SubscriptionMode::CreateUpdate;
capture_config.trigger_config = Some(TriggerConfig::Gcp(gcp_config));
Ok(capture_config)
@@ -887,7 +903,7 @@ async fn gcp_payload(
let authed = fetch_api_authed(owner.clone(), email, &w_id, &db, None).await?;
let SubscriptionMode::CreateUpdate(config) = &gcp_trigger_config.subscription_mode else {
let Some(config) = &gcp_trigger_config.create_update else {
return Err(Error::BadConfig("Bad config".to_string()));
};

View File

@@ -253,7 +253,7 @@ impl ModelInstance {
let token_ids = Tensor::new(&tokens[..], &Device::Cpu)?.unsqueeze(0)?;
let token_type_ids = token_ids.zeros_like()?;
let embedding = self.model.forward(&token_ids, &token_type_ids)?;
let embedding = self.model.forward(&token_ids, &token_type_ids, None)?;
let embedding = (embedding.sum(1)? / embedding.dim(1)? as f64)?;
let embedding = normalize_l2(&embedding)?;

View File

@@ -52,11 +52,12 @@ pub struct ExistingGcpSubscription {
pub base_endpoint: String,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "subscription_mode", rename_all = "snake_case")]
#[derive(Debug, Deserialize, Serialize, sqlx::Type)]
#[serde(rename_all = "snake_case")]
#[sqlx(type_name = "GCP_SUBSCRIPTION_MODE", rename_all = "snake_case")]
pub enum SubscriptionMode {
Existing(ExistingGcpSubscription),
CreateUpdate(CreateUpdateConfig),
Existing,
CreateUpdate,
}
pub fn workspaced_service() -> Router {
@@ -77,7 +78,11 @@ pub async fn manage_google_subscription(
_gcp_resource_path: &str,
_path: &str,
_topic_id: &str,
_subscription_id: &mut Option<String>,
_base_endpoint: &mut Option<String>,
_subscription_mode: SubscriptionMode,
_create_update_config: Option<CreateUpdateConfig>,
_trigger_mode: bool,
) -> WindmillResult<CreateUpdateConfig> {
Ok(CreateUpdateConfig::default())
}
@@ -117,6 +122,7 @@ pub struct GcpTrigger {
pub subscription_id: String,
pub delivery_type: DeliveryType,
pub delivery_config: Option<SqlxJson<PushConfig>>,
pub subscription_mode: SubscriptionMode,
pub topic_id: String,
pub path: String,
pub script_path: String,

View File

@@ -253,8 +253,8 @@ mod zoom {
use super::*;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct ZoomPayload {
#[serde(rename = "plainToken")]
plain_token: String,
}

View File

@@ -674,14 +674,14 @@ async fn list_selected_job_groups(
'kind', jb.kind,
'script_path', jb.runnable_path,
'latest_schema', COALESCE(
(SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),
(SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow')
(SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),
(SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow')
),
'schemas', ARRAY(
SELECT jsonb_build_object(
'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),
'job_ids', ARRAY_AGG(DISTINCT j.id),
'schema', ANY_VALUE(COALESCE(s.schema, f.schema))
'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1]
) FROM v2_job j
LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'
LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'
@@ -3441,7 +3441,11 @@ async fn batch_rerun_handle_job(
}
InputTransform::Javascript { expr } => {
#[cfg(not(feature = "deno_core"))]
tracing::error!("deno_core feature is not activated, cannot evaluate: {expr}");
Err(error::Error::ExecutionErr(
format!("deno_core feature is not activated, cannot evaluate: {expr}")
.to_string(),
))?;
#[cfg(feature = "deno_core")]
args.insert(
property_name.clone(),
@@ -4618,7 +4622,6 @@ pub async fn run_wait_result_script_by_hash(
check_license_key_valid().await?;
let args = args.to_push_args_owned(&authed, &db, &w_id).await?;
check_queue_too_long(&db, run_query.queue_limit).await?;
let hash = script_hash.0;
@@ -5835,7 +5838,7 @@ pub fn filter_list_completed_query(
if let Some(label) = &lq.label {
if lq.allow_wildcards.unwrap_or(false) {
let wh = format!(
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE label LIKE '{}')",
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE jsonb_typeof(result->'wm_labels') = 'array' AND label LIKE '{}')",
&label.replace("*", "%").replace("'", "''")
);
sqlb.and_where("result ? 'wm_labels'");

View File

@@ -18,6 +18,8 @@ use crate::oauth2_ee::SlackVerifier;
#[cfg(feature = "smtp")]
use crate::smtp_server_ee::SmtpServer;
#[cfg(feature = "mcp")]
use crate::mcp::{setup_mcp_server, Runner as McpRunner};
use crate::tracing_init::MyOnFailure;
use crate::{
tracing_init::{MyMakeSpan, MyOnResponse},
@@ -27,6 +29,7 @@ use crate::{
#[cfg(feature = "agent_worker_server")]
use agent_workers_ee::AgentCache;
use anyhow::Context;
use argon2::Argon2;
use axum::extract::DefaultBodyLimit;
@@ -139,6 +142,9 @@ mod workspaces_ee;
mod workspaces_export;
mod workspaces_extra;
#[cfg(feature = "mcp")]
mod mcp;
pub const DEFAULT_BODY_LIMIT: usize = 2097152 * 100; // 200MB
lazy_static::lazy_static! {
@@ -219,6 +225,7 @@ pub async fn run_server(
mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
port_tx: tokio::sync::oneshot::Sender<String>,
server_mode: bool,
mcp_mode: bool,
_base_internal_url: String,
) -> anyhow::Result<()> {
let user_db = UserDB::new(db.clone());
@@ -404,7 +411,7 @@ pub async fn run_server(
Router::new()
};
if !*CLOUD_HOSTED && server_mode {
if !*CLOUD_HOSTED && server_mode && !mcp_mode {
#[cfg(feature = "websocket")]
{
let ws_killpill_rx = killpill_rx.resubscribe();
@@ -448,9 +455,41 @@ pub async fn run_server(
}
}
let listener = tokio::net::TcpListener::bind(addr)
.await
.context("binding main windmill server")?;
let port = listener.local_addr().map(|x| x.port()).unwrap_or(8000);
let ip = listener
.local_addr()
.map(|x| x.ip().to_string())
.unwrap_or("localhost".to_string());
// Setup MCP server
#[allow(unused_variables)]
let (mcp_router, mcp_main_ct, mcp_service_ct) = {
#[cfg(feature = "mcp")]
if server_mode || mcp_mode {
let (mcp_sse_server, mcp_router) = setup_mcp_server(addr, "/api/mcp/w/:workspace_id")?;
#[cfg(feature = "mcp")]
let mcp_main_ct = mcp_sse_server.config.ct.clone(); // Token to signal shutdown *to* MCP
#[cfg(feature = "mcp")]
let mcp_service_ct = mcp_sse_server.with_service(McpRunner::new); // Token to wait for MCP *service* shutdown
(mcp_router, Some(mcp_main_ct), Some(mcp_service_ct))
} else {
(Router::new(), None, None)
}
#[cfg(not(feature = "mcp"))]
(Router::new(), None::<()>, None::<()>)
};
#[cfg(feature = "agent_worker_server")]
let (agent_workers_router, agent_workers_bg_processor, agent_workers_killpill_tx) =
agent_workers_ee::workspaced_service(db.clone(), _base_internal_url.clone());
if server_mode {
agent_workers_ee::workspaced_service(db.clone(), _base_internal_url.clone())
} else {
(Router::new(), vec![], None)
};
#[cfg(feature = "agent_worker_server")]
let agent_cache = Arc::new(AgentCache::new());
@@ -531,26 +570,6 @@ pub async fn run_server(
.nest("/ai", ai::global_service())
.route_layer(from_extractor::<ApiAuthed>())
.route_layer(from_extractor::<users::Tokened>())
.nest("/agent_workers", {
#[cfg(feature = "agent_worker_server")]
{
agent_workers_ee::global_service().layer(Extension(agent_cache.clone()))
}
#[cfg(not(feature = "agent_worker_server"))]
{
Router::new()
}
})
.nest("/w/:workspace_id/agent_workers", {
#[cfg(feature = "agent_worker_server")]
{
agent_workers_router.layer(Extension(agent_cache.clone()))
}
#[cfg(not(feature = "agent_worker_server"))]
{
Router::new()
}
})
.nest("/jobs", jobs::global_root_service())
.nest(
"/srch/w/:workspace_id/index",
@@ -586,6 +605,28 @@ pub async fn run_server(
.layer(from_extractor::<OptAuthed>())
.layer(cors.clone()),
)
.nest("/mcp/w/:workspace_id", mcp_router)
.layer(from_extractor::<OptAuthed>())
.nest("/agent_workers", {
#[cfg(feature = "agent_worker_server")]
{
agent_workers_ee::global_service().layer(Extension(agent_cache.clone()))
}
#[cfg(not(feature = "agent_worker_server"))]
{
Router::new()
}
})
.nest("/w/:workspace_id/agent_workers", {
#[cfg(feature = "agent_worker_server")]
{
agent_workers_router.layer(Extension(agent_cache.clone()))
}
#[cfg(not(feature = "agent_worker_server"))]
{
Router::new()
}
})
.nest(
"/w/:workspace_id/jobs_u",
jobs::workspace_unauthed_service().layer(cors.clone()),
@@ -694,14 +735,6 @@ pub async fn run_server(
.on_failure(MyOnFailure {}),
)
};
let listener = tokio::net::TcpListener::bind(addr)
.await
.context("binding main windmill server")?;
let port = listener.local_addr().map(|x| x.port()).unwrap_or(8000);
let ip = listener
.local_addr()
.map(|x| x.ip().to_string())
.unwrap_or("localhost".to_string());
let server = axum::serve(listener, app.into_make_service());
@@ -719,14 +752,28 @@ pub async fn run_server(
let server = server.with_graceful_shutdown(async move {
killpill_rx.recv().await.ok();
#[cfg(feature = "agent_worker_server")]
if let Err(e) = agent_workers_killpill_tx.kill().await {
tracing::error!("Error killing agent workers: {e:#}");
if let Some(agent_workers_killpill_tx) = agent_workers_killpill_tx {
if let Err(e) = agent_workers_killpill_tx.kill().await {
tracing::error!("Error killing agent workers: {e:#}");
}
}
tracing::info!("Graceful shutdown of server");
#[cfg(feature = "mcp")]
{
if let Some(mcp_main_ct) = mcp_main_ct {
tracing::info!("Received shutdown signal, cancelling MCP server...");
mcp_main_ct.cancel();
}
if let Some(mcp_service_ct) = mcp_service_ct {
tracing::info!("Waiting for MCP service cancellation...");
mcp_service_ct.cancelled().await;
tracing::info!("MCP service cancelled.");
}
}
});
server.await?;
#[cfg(feature = "agent_worker_server")]
for (i, bg_processor) in agent_workers_bg_processor.into_iter().enumerate() {
tracing::info!("server off. shutting down agent worker bg processor {i}");

File diff suppressed because it is too large Load Diff

View File

@@ -30,10 +30,9 @@ use windmill_common::{
};
use super::{
create_logical_replication_slot_query, create_publication_query,
drop_logical_replication_slot_query, drop_publication_query, generate_random_string,
get_database_connection, get_raw_postgres_connection, ERROR_PUBLICATION_NAME_NOT_EXISTS,
ERROR_REPLICATION_SLOT_NOT_EXISTS,
create_logical_replication_slot_query, create_publication_query, drop_publication_query,
generate_random_string, get_database_connection, get_raw_postgres_connection,
ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS,
};
use lazy_static::lazy_static;
@@ -396,7 +395,7 @@ pub async fn create_postgres_trigger(
"Missing replication slot name".to_string(),
));
}
(publication_name.unwrap(), replication_slot_name.unwrap(), )
(publication_name.unwrap(), replication_slot_name.unwrap())
};
let mut tx = user_db.begin(&authed).await?;

View File

@@ -124,11 +124,13 @@ export async function main(
transaction_type: "insert" | "update" | "delete",
schema_name: string,
table_name: string,
row: {}
row: {},
old_row?: {}
) {{
}}
"#,
struct_definition
&struct_definition,
&struct_definition
)
}
}

View File

@@ -47,7 +47,7 @@ impl RelationConverter {
.ok_or(RelationConversionError::FailToFindMatchingTable)
}
pub fn body_to_json(
pub fn row_to_json(
&self,
to_decode: (Oid, Vec<TupleData>),
) -> Result<Map<String, Value>, RelationConversionError> {

View File

@@ -650,37 +650,79 @@ async fn listen_to_transactions(
None
}
Insert(insert) => {
Some((insert.o_id, relations.body_to_json((insert.o_id, insert.tuple)), "insert"))
Some((insert.o_id, Ok(None), relations.row_to_json((insert.o_id, insert.tuple)), "insert"))
}
Update(update) => {
Some((update.o_id, relations.body_to_json((update.o_id, update.new_tuple)), "update"))
let old_row = update.old_tuple.map(|old_tuple| relations.row_to_json((update.o_id, old_tuple))).transpose();
let row = relations.row_to_json((update.o_id, update.new_tuple));
Some((update.o_id, old_row, row, "update"))
}
Delete(delete) => {
let body = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap());
Some((delete.o_id, relations.body_to_json((delete.o_id, body)), "delete"))
let row = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap());
Some((delete.o_id, Ok(None), relations.row_to_json((delete.o_id, row)), "delete"))
}
};
if let Some((o_id, Ok(body), transaction_type)) = json {
let relation = match relations.get_relation(o_id) {
Ok(relation) => relation,
Err(err) => {
tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string());
continue;
match json {
Some((o_id, Ok(old_row), Ok(row), transaction_type)) => {
let relation = match relations.get_relation(o_id) {
Ok(relation) => relation,
Err(err) => {
tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string());
continue;
}
};
let database_info = HashMap::from([
("schema_name".to_string(), to_raw_value(&relation.namespace)),
("table_name".to_string(), to_raw_value(&relation.name)),
("transaction_type".to_string(), to_raw_value(&transaction_type)),
("old_row".to_string(), to_raw_value(&old_row)),
("row".to_string(), to_raw_value(&row)),
]);
let extra = Some(HashMap::from([(
"wm_trigger".to_string(),
to_raw_value(&serde_json::json!({"kind": "postgres", })),
)]));
let _ = pg.handle(&db, Some(database_info), extra).await;
}
Some((o_id, old_row, row, transaction_type)) => {
let relation = match relations.get_relation(o_id) {
Ok(relation) => relation,
Err(err) => {
tracing::error!("Postgres trigger named: {}, error: {}", pg.get_path(), err.to_string());
continue;
}
};
if let Err(err) = old_row {
tracing::error!(
transaction_type = ?transaction_type,
schema = %relation.namespace,
table = %relation.name,
error = %err,
"Failed to decode OLD row for {} transaction on {}.{}",
transaction_type,
relation.namespace,
relation.name,
);
}
};
let database_info = HashMap::from([
("schema_name".to_string(), to_raw_value(&relation.namespace)),
("table_name".to_string(), to_raw_value(&relation.name)),
("transaction_type".to_string(), to_raw_value(&transaction_type)),
("row".to_string(), to_raw_value(&body)),
]);
let extra = Some(HashMap::from([(
"wm_trigger".to_string(),
to_raw_value(&serde_json::json!({"kind": "postgres", })),
)]));
let _ = pg.handle(&db, Some(database_info), extra).await;
if let Err(err) = row {
tracing::error!(
transaction_type = ?transaction_type,
schema = %relation.namespace,
table = %relation.name,
error = %err,
"Failed to decode NEW row for {} transaction on {}.{}",
transaction_type,
relation.namespace,
relation.name,
);
}
}
_ => {}
}
}

View File

@@ -40,6 +40,7 @@ use std::{
};
use windmill_audit::audit_ee::audit_log;
use windmill_audit::ActionKind;
use windmill_worker::process_relative_imports;
use windmill_common::error::to_anyhow;
@@ -264,9 +265,12 @@ async fn list_scripts(
if lq.show_archived.unwrap_or(false) {
sqlb.and_where_eq(
"o.created_at",
"(select max(created_at) from script where o.path = path
AND workspace_id = ?)"
"o.ctid",
"(SELECT ctid FROM script
WHERE path = o.path
AND workspace_id = ?
ORDER BY created_at DESC
LIMIT 1)"
.bind(&w_id),
);
sqlb.and_where_eq("archived", true);
@@ -649,8 +653,13 @@ async fn create_script_internal<'c>(
) {
Some(String::new())
} else {
ns.lock
.and_then(|e| if e.is_empty() { None } else { Some(e) })
ns.lock.as_ref().and_then(|e| {
if e.is_empty() {
None
} else {
Some(e.to_string())
}
})
};
let needs_lock_gen = lock.is_none() && codebase.is_none();
@@ -894,6 +903,40 @@ async fn create_script_internal<'c>(
.await?;
Ok((hash, new_tx))
} else {
let db2 = db.clone();
let w_id2 = w_id.clone();
let authed2 = authed.clone();
let permissioned_as2 = permissioned_as.clone();
let script_path2 = script_path.clone();
let parent_path = p_path_opt.clone();
let lock = ns.lock.clone();
let deployment_message = ns.deployment_message.clone();
let content = ns.content.clone();
let language = ns.language.clone();
tokio::spawn(async move {
// wait for 10 seconds to make sure the script is deployed and that the CLI sync that pushed it (f one) is complete
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
if let Err(e) = process_relative_imports(
&db2,
None,
None,
&w_id2,
&script_path2,
parent_path,
deployment_message,
&content,
&Some(language),
&authed2.email,
&authed2.username,
&permissioned_as2,
lock,
)
.await
{
tracing::error!(%e, "error processing relative imports");
}
});
handle_deployment_metadata(
&authed.email,
&authed.username,
@@ -949,7 +992,7 @@ async fn get_script_by_path(
AND favorite.usr = $3
WHERE s.path = $1
AND s.workspace_id = $2
AND s.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)",
ORDER BY s.created_at DESC LIMIT 1",
)
.bind(path)
.bind(w_id)
@@ -958,9 +1001,7 @@ async fn get_script_by_path(
.await?
} else {
sqlx::query_as::<_, ScriptWithStarred>(
"SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 \
AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
workspace_id = $2)",
"SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
)
.bind(path)
.bind(w_id)
@@ -1000,9 +1041,8 @@ async fn get_script_by_path_w_draft(
let script_o = sqlx::query_as::<_, ScriptWDraft>(
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, has_preprocessor, on_behalf_of_email FROM script LEFT JOIN draft ON
script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script'
WHERE script.path = $1 AND script.workspace_id = $2 \
AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
workspace_id = $2)",
WHERE script.path = $1 AND script.workspace_id = $2
ORDER BY script.created_at DESC LIMIT 1",
)
.bind(path)
.bind(w_id)
@@ -1024,7 +1064,7 @@ async fn get_script_history(
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
WHERE s.workspace_id = $1 AND s.path = $2
ORDER by created_at DESC",
ORDER by s.created_at DESC",
w_id,
path.to_path(),
)
@@ -1052,7 +1092,7 @@ async fn get_latest_version(
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
WHERE s.workspace_id = $1 AND s.path = $2
ORDER by created_at DESC",
ORDER by s.created_at DESC LIMIT 1",
w_id,
path.to_path(),
)
@@ -1148,7 +1188,15 @@ async fn toggle_workspace_error_handler(
match error_handler_maybe {
Some(_) => {
sqlx::query_scalar!(
"UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)",
"UPDATE script
SET ws_error_handler_muted = $3
WHERE ctid = (
SELECT ctid FROM script
WHERE path = $1 AND workspace_id = $2
ORDER BY created_at DESC
LIMIT 1
)
",
path.to_path(),
w_id,
req.muted,
@@ -1169,6 +1217,7 @@ async fn toggle_workspace_error_handler(
async fn get_tokened_raw_script_by_path(
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, token, path)): Path<(String, String, StripPath)>,
Extension(cache): Extension<Arc<AuthCache>>,
) -> Result<String> {
@@ -1176,7 +1225,13 @@ async fn get_tokened_raw_script_by_path(
.get_authed(Some(w_id.clone()), &token)
.await
.ok_or_else(|| Error::NotAuthorized("Invalid token".to_string()))?;
return raw_script_by_path(authed, Extension(user_db), Path((w_id, path))).await;
return raw_script_by_path(
authed,
Extension(user_db),
Extension(db),
Path((w_id, path)),
)
.await;
}
async fn get_empty_ts_script_by_path() -> String {
@@ -1186,22 +1241,25 @@ async fn get_empty_ts_script_by_path() -> String {
async fn raw_script_by_path(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> Result<String> {
raw_script_by_path_internal(path, user_db, authed, w_id, false).await
raw_script_by_path_internal(path, user_db, db, authed, w_id, false).await
}
async fn raw_script_by_path_unpinned(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> Result<String> {
raw_script_by_path_internal(path, user_db, authed, w_id, true).await
raw_script_by_path_internal(path, user_db, db, authed, w_id, true).await
}
async fn raw_script_by_path_internal(
path: StripPath,
user_db: UserDB,
db: DB,
authed: ApiAuthed,
w_id: String,
unpin: bool,
@@ -1227,10 +1285,7 @@ async fn raw_script_by_path_internal(
let mut tx = user_db.begin(&authed).await?;
let content_o = sqlx::query_scalar!(
"SELECT content FROM script WHERE path = $1 AND workspace_id = $2 \
AND
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND \
workspace_id = $2)",
"SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1",
path,
w_id
)
@@ -1238,6 +1293,22 @@ async fn raw_script_by_path_internal(
.await?;
tx.commit().await?;
if content_o.is_none() {
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
path,
w_id
)
.fetch_one(&db)
.await?;
if exists.unwrap_or(false) {
return Err(Error::NotFound(format!(
"Script {path} not visible to {} but exists",
authed.username
)));
}
}
let content = not_found_if_none(content_o, "Script", path)?;
if unpin {
@@ -1254,8 +1325,7 @@ async fn exists_script_by_path(
let path = path.to_path();
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))",
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
path,
w_id
)
@@ -1372,9 +1442,7 @@ pub async fn require_is_writer(authed: &ApiAuthed, path: &str, w_id: &str, db: D
path,
w_id,
db,
"SELECT extra_perms FROM script WHERE path = $1 AND workspace_id = $2 \
AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
workspace_id = $2)",
"SELECT extra_perms FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
"script",
)
.await;

View File

@@ -1,6 +1,7 @@
use crate::db::DB;
use axum::Router;
use serde::{Deserialize, Serialize};
use windmill_common::auth::aws::AwsAuthResourceType;
pub fn workspaced_service() -> Router {
@@ -14,6 +15,7 @@ pub fn start_sqs(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SqsTrigger {
pub queue_url: String,
pub aws_auth_resource_type: AwsAuthResourceType,
pub aws_resource_path: String,
pub message_attributes: Option<Vec<String>>,
pub path: String,

View File

@@ -33,6 +33,8 @@ impl<B> OnResponse<B> for MyOnResponse {
let status = response.status().as_u16();
if response.status().is_success() || response.status().is_redirection() {
tracing::info!(latency = latency, status = status, "response")
} else if response.status().as_u16() == 404 {
tracing::warn!(latency = latency, status = status, "response")
} else {
tracing::error!(latency = latency, status = status, "response")
}

View File

@@ -501,7 +501,7 @@ pub(crate) async fn tarball_workspace(
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
app.extra_perms, app_version.value,
app_version.created_at, app_version.created_by from app, app_version
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)]",
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND app_version.raw_app IS false",
)
.bind(&w_id)
.fetch_all(&mut *tx)
@@ -577,13 +577,35 @@ pub(crate) async fn tarball_workspace(
#[cfg(feature = "websocket")]
{
let websocket_triggers = sqlx::query_as!(
crate::websocket_triggers::WebsocketTrigger,
"SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger
WHERE workspace_id = $1",
&w_id
)
.fetch_all(&mut *tx)
.await?;
crate::websocket_triggers::WebsocketTrigger,
r#"
SELECT
workspace_id,
path,
url,
script_path,
is_flow,
edited_by,
email,
edited_at,
server_id,
last_server_ping,
extra_perms,
error,
enabled,
filters AS "filters: _",
initial_messages AS "initial_messages: _",
url_runnable_args AS "url_runnable_args: _",
can_return_message
FROM
websocket_trigger
WHERE
workspace_id = $1
"#,
&w_id
)
.fetch_all(&mut *tx)
.await?;
for trigger in websocket_triggers {
let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap();
@@ -622,8 +644,29 @@ pub(crate) async fn tarball_workspace(
{
let sqs_triggers = sqlx::query_as!(
crate::sqs_triggers_ee::SqsTrigger,
"SELECT * FROM sqs_trigger
WHERE workspace_id = $1",
r#"
SELECT
aws_auth_resource_type AS "aws_auth_resource_type: _",
aws_resource_path,
message_attributes,
queue_url,
workspace_id,
path,
script_path,
is_flow,
edited_by,
email,
edited_at,
server_id,
last_server_ping,
extra_perms,
error,
enabled
FROM
sqs_trigger
WHERE
workspace_id = $1
"#,
&w_id
)
.fetch_all(&mut *tx)
@@ -649,6 +692,7 @@ pub(crate) async fn tarball_workspace(
workspace_id,
delivery_type AS "delivery_type: _",
delivery_config AS "delivery_config: _",
subscription_mode AS "subscription_mode: _",
path,
script_path,
is_flow,

View File

@@ -13,6 +13,7 @@ prometheus = ["dep:prometheus"]
loki = ["dep:tracing-loki"]
benchmark = []
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"]
aws_auth = ["dep:aws-sdk-sts", "dep:aws-config"]
otel = ["dep:opentelemetry-semantic-conventions", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk",
"dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-appender-tracing", "dep:tonic"]
smtp = ["dep:mail-send"]
@@ -75,6 +76,8 @@ quick_cache.workspace = true
pin-project-lite.workspace = true
futures.workspace = true
tempfile.workspace = true
systemstat.workspace = true
size.workspace = true
opentelemetry-semantic-conventions = { workspace = true, optional = true }
opentelemetry-otlp = { workspace = true, optional = true }

View File

@@ -6,6 +6,8 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// Id in the `app_script` table.
@@ -21,3 +23,8 @@ pub struct ListAppQuery {
pub include_draft_only: Option<bool>,
pub with_deployment_msg: Option<bool>,
}
#[derive(Deserialize)]
pub struct RawAppValue {
pub files: HashMap<String, String>,
}

View File

@@ -1,4 +1,5 @@
use anyhow::Context;
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@@ -10,6 +11,48 @@ use crate::{
DB,
};
#[derive(Debug)]
pub struct IdToken {
token: String,
expiration: DateTime<Utc>,
}
pub fn has_expired(expiration_time: DateTime<Utc>, take: Option<Duration>) -> bool {
let now = Utc::now();
let expiration = match take {
Some(duration) => expiration_time - duration,
None => expiration_time,
};
now > expiration
}
impl From<IdToken> for String {
fn from(value: IdToken) -> Self {
value.token
}
}
impl ToString for IdToken {
fn to_string(&self) -> String {
self.token.clone()
}
}
impl IdToken {
pub fn new(token: String, expiration: DateTime<Utc>) -> Self {
Self { token, expiration }
}
pub fn token(&self) -> &str {
&self.token
}
pub fn expiration(&self) -> &DateTime<Utc> {
&self.expiration
}
}
#[derive(Deserialize, Serialize)]
pub struct JWTAuthClaims {
pub email: String,
@@ -267,3 +310,110 @@ pub async fn create_token_for_owner(
Ok(format!("jwt_{}", token))
}
#[cfg(feature = "aws_auth")]
pub mod aws {
use crate::error::to_anyhow;
use super::*;
use crate::utils::empty_string_as_none;
use aws_config::{BehaviorVersion, Region};
use aws_sdk_sts::{
config::Credentials as AwsCredentials,
operation::{
assume_role_with_saml::AssumeRoleWithSamlOutput,
assume_role_with_web_identity::AssumeRoleWithWebIdentityOutput,
},
types::Credentials,
Client,
};
pub const AWS_OIDC_AUDIENCE: &'static str = "sts.amazonaws.com";
pub trait GetAuthenticationOutput {
fn get_credentials(&self) -> Result<&Credentials>;
}
impl GetAuthenticationOutput for AssumeRoleWithSamlOutput {
fn get_credentials(&self) -> Result<&Credentials> {
let credentials = self.credentials.as_ref().ok_or(Error::BadGateway(
"Error fetching credentials from AWS STS".to_string(),
))?;
Ok(credentials)
}
}
impl GetAuthenticationOutput for AssumeRoleWithWebIdentityOutput {
fn get_credentials(&self) -> Result<&Credentials> {
let credentials = self.credentials.as_ref().ok_or(Error::BadGateway(
"Error fetching credentials from AWS STS".to_string(),
))?;
Ok(credentials)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "AWS_AUTH_RESOURCE_TYPE", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum AwsAuthResourceType {
Credentials,
Oidc,
}
#[derive(Debug, Deserialize)]
pub struct CredentialsAuth {
#[serde(deserialize_with = "empty_string_as_none")]
pub region: Option<String>,
#[serde(rename = "awsAccessKeyId")]
pub aws_access_key_id: String,
#[serde(rename = "awsSecretAccessKey")]
pub aws_secret_access_key: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct OidcAuth {
#[serde(deserialize_with = "empty_string_as_none")]
pub region: Option<String>,
#[serde(rename = "roleArn")]
pub role_arn: String,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum AWSAuthConfig {
Credentials(CredentialsAuth),
Oidc(OidcAuth),
}
pub async fn get_oidc_authentication_data(
oidc_auth: OidcAuth,
role_session_name: Option<impl ToString>,
token: String,
) -> Result<AssumeRoleWithWebIdentityOutput> {
let region = oidc_auth.region.unwrap_or_else(|| "us-east-1".to_string());
let credentials = AwsCredentials::new("", "", None, None, "UserInput");
let config = aws_config::defaults(BehaviorVersion::latest())
.credentials_provider(credentials)
.region(Region::new(region.clone()))
.load()
.await;
let assume_role_with_web_identity_fluent_builder = Client::new(&config)
.assume_role_with_web_identity()
.set_role_arn(Some(oidc_auth.role_arn))
.set_role_session_name(role_session_name.map(|str| str.to_string()))
.set_web_identity_token(Some(token));
let resp = assume_role_with_web_identity_fluent_builder
.clone()
.send()
.await
.map_err(to_anyhow)?;
Ok(resp)
}
}

View File

@@ -570,15 +570,18 @@ pub mod script {
let fut = CACHE.get_or_insert_async(hash, async move {
match conn {
Connection::Sql(db) => fetch_script_from_db(&db, hash, loc).await,
Connection::Http(_) => Err(error::Error::InternalErr(format!(
"Cannot fetch script in HTTP mode"
))),
Connection::Http(client) => {
let r = client
.get::<RawScriptApi>(&format!("/api/agent_workers/script/{}", hash.0))
.await?;
Ok(r.into())
}
}
});
fut.map_ok(|ScriptFull { data, meta }| (data, meta))
}
async fn fetch_script_from_db(
pub async fn fetch_script_from_db(
db: &DB,
hash: ScriptHash,
loc: &'static Location<'_>,

View File

@@ -98,3 +98,9 @@ pub async fn worker_groups_alerts(_db: &DB) {}
#[cfg(feature = "enterprise")]
pub async fn jobs_waiting_alerts(_db: &DB) {}
#[cfg(feature = "enterprise")]
pub async fn low_disk_alerts(_db: &DB, _server_mode: bool, _worker_mode: bool, _workers: Vec<String>) {
// Implementation is not open source
}

View File

@@ -22,7 +22,8 @@ use crate::{
error::Error,
more_serde::{default_empty_string, default_id, default_null, default_true, is_default},
scripts::{Schema, ScriptHash, ScriptLang},
worker::{to_raw_value, Connection}, DB,
worker::{to_raw_value, Connection},
DB,
};
#[derive(Serialize, Deserialize, sqlx::FromRow)]

View File

@@ -37,6 +37,7 @@ pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url";
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
pub const CRITICAL_ALERT_MUTE_UI_SETTING: &str = "critical_alert_mute_ui";
pub const CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING: &str = "critical_alerts_on_db_oversize";
pub const DEV_INSTANCE_SETTING: &str = "dev_instance";
pub const JWT_SECRET_SETTING: &str = "jwt_secret";
pub const EMAIL_DOMAIN_SETTING: &str = "email_domain";

View File

@@ -115,6 +115,7 @@ lazy_static::lazy_static! {
pub static ref CRITICAL_ERROR_CHANNELS: Arc<RwLock<Vec<CriticalErrorChannel>>> = Arc::new(RwLock::new(vec![]));
pub static ref CRITICAL_ALERTS_ON_DB_OVERSIZE: Arc<RwLock<Option<f32>>> = Arc::new(RwLock::new(None));
pub static ref JOB_RETENTION_SECS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));

View File

@@ -476,3 +476,7 @@ impl CredentialProvider for AwsCredentialAdapter {
pub fn bundle(w_id: &str, hash: &str) -> String {
format!("script_bundle/{}/{}", w_id, hash)
}
pub fn raw_app(w_id: &str, version: &i64) -> String {
format!("/home/rfiszel/raw_app/{}/{}", w_id, version)
}

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