Compare commits

...

203 Commits

Author SHA1 Message Date
Ruben Fiszel
a974e48988 all 2024-08-12 18:36:30 +02:00
Ruben Fiszel
e7d08359fc fix compile 2024-08-12 10:36:37 +02:00
Ruben Fiszel
22c2d248b2 Merge branch 'main' into rf/bundle 2024-08-12 09:55:50 +02:00
Guilhem
6ba3fda65d dev(frontend) move component options into editor menu (#4221)
* dev(frontend) move component options into editor menu

* dev(frontend) move component options into editor menu

* dev(frontend) add editor menu popovers

---------

Co-authored-by: Guilhem Le Mouel <guilhem.le-mouel.ext@altran.com>
2024-08-12 09:52:04 +02:00
Ruben Fiszel
30b90e4951 all 2024-08-12 09:37:53 +02:00
HugoCasa
0655803860 fix: improve license key ui (#4220)
* fix: improve license key ui

* fix: stop schedules on key expiration

* update ee ref
2024-08-11 01:01:24 +02:00
Ruben Fiszel
c288d73c13 specify that windmill can be embedded if using same host 2024-08-10 22:58:46 +02:00
Ruben Fiszel
c03c0f6172 fix(frontend): user resource picker for app use lightweight component 2024-08-10 22:49:48 +02:00
Ruben Fiszel
bc512bd307 chore: improve ci to not tag PR result 2024-08-10 20:34:20 +02:00
Ruben Fiszel
e5e03b5baa chore: improve ci to not tag PR result 2024-08-10 20:08:23 +02:00
Ruben Fiszel
02fdd58e47 chore: improve ci to not tag PR result 2024-08-10 19:19:54 +02:00
Ruben Fiszel
6272c9ff41 fix: allow user resources in app to work within iframes 2024-08-10 19:19:54 +02:00
Faton Ramadani
7115a35779 fix(frontend): improve default id of the components of the topbar (#4222) 2024-08-09 16:49:37 +02:00
Faton Ramadani
c0853eafc9 fix(frontend): support plus sign in emails format fields #4223 2024-08-09 16:48:57 +02:00
Ruben Fiszel
d56a956b9a feat: windmill embed 2024-08-09 09:57:43 +02:00
Ruben Fiszel
7349d0a320 add external config to codebase 2024-08-09 08:50:54 +02:00
Ruben Fiszel
70d1e6c720 feat(cli): add customerBundler support 2024-08-09 08:13:32 +02:00
Ruben Fiszel
b78c3fe574 revert customer loader change 2024-08-09 07:50:19 +02:00
Ruben Fiszel
71d23db5cb fix fill height for deployed apps 2024-08-08 23:23:03 +02:00
Ruben Fiszel
832df264b4 chore(main): release 1.377.1 (#4216)
* chore(main): release 1.377.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-08 18:59:53 +02:00
Faton Ramadani
2334802384 fix(frontend): fix timezone issues for Date only inputs (#4215) 2024-08-08 18:08:14 +02:00
Ruben Fiszel
8ea2906d09 chore(main): release 1.377.0 (#4208) 2024-08-08 15:10:38 +02:00
Ruben Fiszel
88d0e97ce0 chore: update vite to 5.4.0 2024-08-08 15:08:47 +02:00
HugoCasa
d1e119e052 fix: handle snowflake partitions (#4214) 2024-08-08 14:50:31 +02:00
Faton Ramadani
bf629f39cc fix(frontend): fix flow warnings (#4213)
* fix(frontend): Correctly compute the array of all modules ids to build the step warnings

* fix(frontend): Correctly compute the array of all modules ids to build the step warnings
2024-08-08 14:34:57 +02:00
Faton Ramadani
c90fb8d293 fix(frontend): fix decision tree debug menu (#4212) 2024-08-08 14:17:43 +02:00
HugoCasa
55926f957f fix: case insensitive encoding for email triggers (#4211)
* fix: case insensitive encoding for email triggers

* update ee ref
2024-08-08 11:59:20 +02:00
Ruben Fiszel
2eb6a7a742 support in list_flows, flows that have no last flow_version 2024-08-08 10:08:25 +02:00
Lewis J Ellis
a77f74cb7e fix(cli): Add esbuild loader for .node files for codebase 2024-08-08 08:50:58 +02:00
Ruben Fiszel
413ad2c925 feat(app): add user resource select component 2024-08-08 00:19:37 +02:00
HugoCasa
2c3d4920e0 fix: azure git sync test connection (#4209) 2024-08-07 19:13:46 +02:00
HugoCasa
0029dc11e9 fix: handle >1 num workers stats (#4177)
* fix: handle >1 num workers stats

* fix: sqlx

* fix: ee ref test

* fix: sqlx + ee ref

* update ee ref
2024-08-07 17:52:39 +02:00
Ruben Fiszel
dd17a6bd14 fix tests 2024-08-07 17:51:41 +02:00
wendrul
4f9193c036 fix: Additional tracing when pulling and uploading search index #4207 2024-08-07 16:25:41 +02:00
Ruben Fiszel
843b14fe99 keep bin in local cache for go even in cloud 2024-08-07 16:23:13 +02:00
Ruben Fiszel
b60f42e387 chore(main): release 1.376.1 (#4202)
* chore(main): release 1.376.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-07 12:04:44 +02:00
Henri Courdent
c01e179d8f JavaScript spelling (#4206) 2024-08-07 12:04:26 +02:00
Ruben Fiszel
15d5c60d90 improve webhook panel for non root paths 2024-08-07 11:09:55 +02:00
Ruben Fiszel
d88674a7af fix non root goto with ? prefix 2024-08-07 11:05:45 +02:00
HugoCasa
6e9c350b4a fix: email triggers sqlx CE and improve email parsing (#4203)
* fix: CE sqlx

* update ee ref
2024-08-06 23:05:36 +02:00
HugoCasa
e7148f6723 fix: mysql params starting with underscore (#4201) 2024-08-06 22:06:01 +02:00
Ruben Fiszel
a84c54d663 chore(main): release 1.376.0 (#4195)
* chore(main): release 1.376.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-06 22:03:21 +02:00
HugoCasa
cfa20ae0d7 fix: mysql support for underscore in named param (#4200) 2024-08-06 21:59:29 +02:00
HugoCasa
b54edf153e feat: secure ctx variables in runnable inputs (#4142)
* feat: secure ctx variables

* feat: add on behalf of email

* oups

* fix: from onBehalfOfEmail to author
2024-08-06 17:06:02 +02:00
HugoCasa
5e3a3e2103 fix(frontend): disable email triggers by default (#4199) 2024-08-06 16:59:12 +02:00
HugoCasa
a87f34fb4a feat: email triggers (#4163)
* feat: email triggers v0

* update docker compose to nginx with tcp reverse proxy + move smtp to private

* fix: open source build

* test: update ee ref for testing

* feat: use caddy with layer4

* fix: nit

* feat: configurable email domain

* fix: nit

* fix: nit

* fix: get l4 from main

* fix: default email domain to mail.domain

* update ee ref
2024-08-06 16:17:33 +02:00
Ruben Fiszel
7886f8f471 fix: fix native scripts access to reserved variables 2024-08-06 16:15:09 +02:00
Ruben Fiszel
1e7de238cf improve rawvalue handling for lightweightarg input 2024-08-06 15:39:23 +02:00
Fred Reimer
9e78a51adc bump go version (#4192)
* bump go version

* update go version in lsp
2024-08-06 15:07:24 +02:00
Faton Ramadani
b9b30e66ec fix(frontend): Hide AgChart background to make styling work (#4197)
* fix(frontend): Hide AgChart background to make styling work

* fix(frontend): Fix dark theme
2024-08-06 12:20:16 +02:00
Faton Ramadani
6749f2c136 fix(frontend): Remove full height for the event handlers of runnables (#4196) 2024-08-06 10:59:49 +02:00
wendrul
39dc6857eb Remove admin requirement to cancel job selection (#4188) 2024-08-06 10:48:53 +02:00
wendrul
b6ab184889 Add warning of job search parse error + front fixes (#4187) 2024-08-06 10:48:22 +02:00
Faton Ramadani
5104dba631 fix(frontend): Fr/improve suspend drawer (#4189)
* fix(frontend): add missing info about the cancel url in the suspend drawer

* fix(frontend): add missing info about the cancel url in the suspend drawer
2024-08-06 10:48:08 +02:00
Ruben Fiszel
a1c40d7fd0 add timeout for app editor log panel 2024-08-06 10:39:13 +02:00
wendrul
7b3128171e feat: Tag filter on Runs page (#4193)
* Add warning of job search parse error + front fixes

* Add filter tag and add filters to small screen

* Fill missing property
2024-08-06 10:31:08 +02:00
Faton Ramadani
657f03bc67 fix(frontend): fix the app created from a script or flow with the new topbar (#4194) 2024-08-06 10:16:29 +02:00
Ruben Fiszel
fbcac1e0a6 support tarbundle in vscode extension 2024-08-06 01:02:41 +02:00
Ruben Fiszel
1c83876775 chore(main): release 1.375.0 (#4181)
* chore(main): release 1.375.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-05 13:34:54 +02:00
Ruben Fiszel
adecf4b2d1 fix codebase with start_worker 2024-08-05 13:31:05 +02:00
wendrul
f2f8bbe1d4 feat: deployment UI filter deployable items (#4183)
* Add column to workspace settings table

* Add endpoint to change the deploy UI settings

* Make frontend page to edit UI settings

* Prepare sqlx

* Add deployment restrictions to frontend

* Fix function name

* Change dependency to minimatch, CE compatibility

* Remove default include_path

* Remove picomatch types (old dep)

* Use empty list instead of globstar as default

* All deployable if config is null
2024-08-05 13:27:13 +02:00
Henri Courdent
f1fa6bca84 Changelog add native runtime support of npm packages (#4185) 2024-08-05 13:21:14 +02:00
Faton Ramadani
704e75e893 fix(frontend): fix tutorial for apps with the new topbar (#4186) 2024-08-05 13:20:56 +02:00
Ruben Fiszel
d771e38bfd add global created_at index 2024-08-05 13:12:15 +02:00
Ruben Fiszel
cc111ba7dc feat: improve indices of completed_runs for faster load 2024-08-05 12:46:07 +02:00
Faton Ramadani
43a89ee1cc fix(frontend): fix style panel for compoentn without custom css (#4182) 2024-08-05 11:06:42 +02:00
Faton Ramadani
947dd21933 fix(frontend): add missing truncate for branch predicates (#4180) 2024-08-05 09:11:24 +02:00
Ruben Fiszel
989c9e6102 chore(main): release 1.374.0 (#4175)
* chore(main): release 1.374.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-04 23:20:09 +02:00
HugoCasa
811de58712 feat: caddy with l4 image (#4178) 2024-08-04 23:17:24 +02:00
Ruben Fiszel
3508b6d793 feat: add support for assets using tar for codebase deploy 2024-08-04 23:02:51 +02:00
Ruben Fiszel
86e23f2aed fix: fix delete job 2024-08-03 08:26:29 +02:00
Ruben Fiszel
a168107d29 chore(main): release 1.373.1 (#4171)
* chore(main): release 1.373.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-02 17:36:44 +02:00
Ruben Fiszel
63abd5b146 fix: fix run_flow_async from call 2024-08-02 16:37:17 +02:00
Ruben Fiszel
c55d15c0a9 whitelabel custom settings 2024-08-02 13:47:28 +02:00
Faton Ramadani
91e364bde5 fix(frontend): Add support for array of objects in th UI (#4170) 2024-08-02 12:58:43 +02:00
Ruben Fiszel
1723e989cd chore(main): release 1.373.0 (#4168)
* chore(main): release 1.373.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-01 22:49:05 +02:00
Ruben Fiszel
1a25aca45c fix edit button newtab 2024-08-01 22:11:30 +02:00
Ruben Fiszel
cd61fc1e8c fix: app forms default values changes gets propagated 2024-08-01 21:54:21 +02:00
Ruben Fiszel
86a6a909d9 add rpi4 build 2024-08-01 18:50:33 +02:00
Ruben Fiszel
6cd698eeda update sqlx 2024-08-01 18:19:21 +02:00
wendrul
edcee6d861 feat: Indexing improvements (#4167) 2024-08-01 16:57:42 +02:00
Ruben Fiszel
e6d5689bf9 chore(main): release 1.372.0 (#4165)
* chore(main): release 1.372.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-08-01 16:28:17 +02:00
Ruben Fiszel
efcf0e40f6 fix: fix raw_deps handling 2024-08-01 16:24:30 +02:00
Ruben Fiszel
42e3ae92e3 fix: improve cancel_selection job for running jobs 2024-08-01 16:08:17 +02:00
Ruben Fiszel
b5464e2906 feat: variables created by password fields expire after 7 days 2024-08-01 16:05:03 +02:00
Ruben Fiszel
2c27083e2a avoir mistaken editor edit/deploy interaction 2024-08-01 10:07:15 +02:00
Ruben Fiszel
2b6f84e5d8 more clearly display loading when jobs filter change 2024-08-01 09:25:18 +02:00
Ruben Fiszel
c6b07561da nit fix occupancy rate 2024-08-01 09:15:55 +02:00
Ruben Fiszel
1a47325055 improve queue indices 2024-07-31 23:44:40 +02:00
Ruben Fiszel
dd1a7a1b3d chore(main): release 1.371.4 (#4158)
* chore(main): release 1.371.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-31 18:08:01 +02:00
Henri Courdent
79851101ae doc_link_fixes (#4157) 2024-07-31 18:03:49 +02:00
Henri Courdent
6808ee5a70 Order components + names alignment (#4160) 2024-07-31 18:03:23 +02:00
Faton Ramadani
efeb65be4c fix(frontend): If multiple recompute all present, interval is now in sync (#4162) 2024-07-31 17:45:52 +02:00
Faton Ramadani
10c699759e fix(frontend): fix recompute all (#4161)
* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): fix recompute all

* fix(frontend): fix recompute all

* fix(frontend): clean up

* fix(frontend): fix how the onClick callback is called
2024-07-31 17:27:38 +02:00
Ruben Fiszel
d69aa8d484 fix: improve index usage and runs page performance 2024-07-31 13:08:28 +02:00
Ruben Fiszel
b95337fcb9 improve job result deser 2024-07-31 02:32:33 +02:00
Ruben Fiszel
9921731fdf add limits to variable list 2024-07-31 01:37:51 +02:00
Ruben Fiszel
e7cccd099e chore(main): release 1.371.3 (#4156)
* chore(main): release 1.371.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-30 21:54:23 +02:00
Ruben Fiszel
be8cedfe7f fix: for codebase bundle, use cjs exports instead 2024-07-30 21:48:40 +02:00
Ruben Fiszel
1a548f32a1 chore(main): release 1.371.2 (#4155)
* chore(main): release 1.371.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-30 21:28:46 +02:00
Ruben Fiszel
72ff465300 improve banner for esbuild bundle 2024-07-30 21:23:31 +02:00
Ruben Fiszel
716bb7118b fix: improve codebase handling by ignoring creating lock and bundle 2024-07-30 20:22:34 +02:00
Ruben Fiszel
4fe797eeab chore: update backend tests to 1.79 2024-07-30 18:34:59 +02:00
Ruben Fiszel
459a51c111 chore(main): release 1.371.1 (#4153)
* chore(main): release 1.371.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-30 18:21:29 +02:00
Ruben Fiszel
8c81e47564 fix: make SLEEP_QUEUE adaptative to number of subworkers 2024-07-30 17:57:14 +02:00
Ruben Fiszel
f59046a924 fix: database connections now scale linearly with number of subworkers 2024-07-30 17:45:03 +02:00
Ruben Fiszel
e5c73d66d3 chore(main): release 1.371.0 (#4151)
* chore(main): release 1.371.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-30 16:28:16 +02:00
Ruben Fiszel
cf3512ee03 update rust image to 1.79 2024-07-30 15:46:43 +02:00
Ruben Fiszel
28277da35b fix: submit and form persistence on app on render change 2024-07-30 15:43:37 +02:00
Ruben Fiszel
f63b176328 add proxy to helper 2024-07-30 12:37:24 +02:00
Ruben Fiszel
ccb892feab add proxy and useragent support for native scripts 2024-07-30 12:31:47 +02:00
Faton Ramadani
5992b82180 feat(frontend): support array of objects in schema (#4106)
* feat(frontend): Support array of objets

* feat(frontend): add support for LightweightArgInput

* feat(frontend): support objects and arrays of objects in LightweightArgInput
2024-07-30 11:42:49 +02:00
Ruben Fiszel
6a33624416 fix: fix bunnative lock creation 2024-07-30 10:01:29 +02:00
Ruben Fiszel
06433a6ec6 fix: always consider electron as external for bundles 2024-07-30 09:38:19 +02:00
Ruben Fiszel
e02e644d4c fix: support bunnative in the CLI 2024-07-30 08:58:32 +02:00
Ruben Fiszel
9942e86765 bump deno and deno_core to 1.45.4 2024-07-30 08:43:52 +02:00
Ruben Fiszel
9f8862680c chore(main): release 1.370.0 (#4150)
* chore(main): release 1.370.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-29 21:36:30 +02:00
Ruben Fiszel
6e91005daa fix: improve native runtime with axios support 2024-07-29 21:13:02 +02:00
HugoCasa
5031a8cb01 feat: get completed flow node result by api/download + eval list result json path optim (#4108)
* feat: get completed flow node result by api

* fix: missing file

* feat: fallback to completed job + fix dfs

* feat: download all results + eval improvement

* fix: nit regex

* fix: nit
2024-07-29 20:30:55 +02:00
Ruben Fiszel
28810910b1 chore(main): release 1.369.1 (#4149)
* chore(main): release 1.369.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-29 20:22:26 +02:00
Faton Ramadani
71badb959d fix(frontend): Fix how new items are processed in App select (#4109)
* fix(frontend): Fix how new items are procesed

* fix(frontend): Fix regex

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

* fix(frontend): avoid duplicates

* fix(frontend): simplify code

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2024-07-29 20:21:39 +02:00
Ruben Fiszel
bc94acb378 fix: fix lang picker artefact for script editor 2024-07-29 20:18:56 +02:00
Ruben Fiszel
ca345d4755 chore(main): release 1.369.0 (#4147)
* chore(main): release 1.369.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-29 19:55:34 +02:00
Lewis J Ellis
b250890b1f Add platform: 'node' to codebase esbuild (#4148) 2024-07-29 19:49:59 +02:00
Ruben Fiszel
18f22be2ba feat: remove nativets in favor of bun with native pragma 2024-07-29 19:26:26 +02:00
Ruben Fiszel
b02baa2cfb feat: remove nativets in favor of bun with native pragma 2024-07-29 19:20:00 +02:00
Faton Ramadani
fb89eed8fa feat(frontend): App bar as components (#4103)
* feat(frontend): wip

* feat(frontend): add migration to the new top bar

* feat(frontend): fix topbar styling

* feat(frontend): fix migration code

* feat(frontend): fix migration code

* feat(frontend): fix migration code

* feat(frontend): wip

* feat(frontend): done

* feat(frontend): done

* feat(frontend): remove migration

* feat(frontend): fix top bar styling

* feat(frontend): fix sync issues

* feat(frontend): improve style

* feat(frontend): Redesign Recompute all

* feat(frontend): change icon + make the default title font bigger

* feat(frontend): Remove unecesary clearInterval

* feat(frontend): Fix dropdown menu button style
2024-07-29 17:26:18 +02:00
HugoCasa
eb6557a6be feat: add support for text/plain webhook (#4146) 2024-07-29 15:56:25 +02:00
Ruben Fiszel
4f114cbcab nit less cloning 2024-07-28 23:31:50 +02:00
Ruben Fiszel
2cc24b675e simplify noop code handling 2024-07-28 20:10:59 +02:00
Ruben Fiszel
40caa795fb chore(main): release 1.368.3 (#4143)
* chore(main): release 1.368.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-28 19:34:13 +02:00
Ruben Fiszel
e45a8a4591 fix: update bun to 1.1.21 2024-07-28 14:55:47 +02:00
Ruben Fiszel
3f1ed35f3e clarify //nobundling 2024-07-28 14:31:23 +02:00
Ruben Fiszel
9deba3e715 chore(main): release 1.368.2 (#4141)
* chore(main): release 1.368.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-28 11:09:31 +02:00
Ruben Fiszel
1e2e907982 fix: disable prebundling for nodejs mode scripts 2024-07-28 11:06:43 +02:00
Ruben Fiszel
c3848e2e30 fix: add the nobundling option for bun 2024-07-28 10:56:04 +02:00
Ruben Fiszel
35d567e2e0 small nit BASE_URL 2024-07-28 10:25:33 +02:00
Ruben Fiszel
7099ec48e6 chore(main): release 1.368.1 (#4140)
* chore(main): release 1.368.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-27 12:46:47 +02:00
Ruben Fiszel
df8cf402c6 improve slightly concurrency run min started_at query 2024-07-27 12:43:24 +02:00
Ruben Fiszel
2b67fbcd98 improve slightly concurrency run min started_at query 2024-07-27 12:41:01 +02:00
Ruben Fiszel
ec9aad4dae improve slightly concurrency run min started_at query 2024-07-27 12:28:55 +02:00
Ruben Fiszel
99623f31af fix: improve runs page performance through pg indices 2024-07-27 12:16:13 +02:00
Ruben Fiszel
0e908ec71f reload servers on metrics setting change 2024-07-27 07:57:49 +02:00
Ruben Fiszel
c52ca177ea chore(main): release 1.368.0 (#4129)
* chore(main): release 1.368.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-26 23:27:34 +02:00
Ruben Fiszel
545a57fd2c fix: allow colors for bun and log error directly 2024-07-26 22:42:24 +02:00
Ruben Fiszel
f1d5be8a2a feat: add FORCE_<env> to allow to override db settings 2024-07-26 21:47:06 +02:00
Ruben Fiszel
6167fbb9a5 add loading timing 2024-07-26 21:05:48 +02:00
Ruben Fiszel
07c764c167 fix tests 2024-07-26 19:26:00 +02:00
Ruben Fiszel
78fd99cb68 feat: pre-bundle bun scripts (#4132) 2024-07-26 19:00:30 +02:00
Henri Courdent
54e4397a0b Upated thumbnail repo and content of readme (#4136) 2024-07-26 18:24:53 +02:00
wendrul
c270ab1171 fix: search modal improvements (#4128)
* Message when runs filter is empty

* Improve height and width of modal
2024-07-26 11:08:16 +02:00
HugoCasa
12f9e56154 feat: job view audit logging based on env variable (#4131)
* feat: job view audit logging based on env variable

* Update jobs.rs

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-07-26 11:07:15 +02:00
HugoCasa
6df9eca2cf feat: multi sql statement with pg fix (#4134)
* Revert "Revert "feat: multi statement sql (#4104)" (#4133)"

This reverts commit 38f0949ce3.

* fix: sort pg args
2024-07-26 09:23:39 +02:00
Ruben Fiszel
38f0949ce3 Revert "feat: multi statement sql (#4104)" (#4133)
This reverts commit 23108b4688.
2024-07-26 01:24:03 +02:00
HugoCasa
23108b4688 feat: multi statement sql (#4104)
* feat: multi statement pg

* fix: add other flavors

* feat: make pg params start at 1 and sequential

* fix: improve sql statement parsing

* add tests

* fix: allow no semi in last statement

* fix: merge conflict

* fix: minor improvement

* fix: parser version
2024-07-25 20:46:04 +02:00
Ruben Fiszel
8daccfc876 improve s3 bucket tests 2024-07-25 13:02:59 +02:00
Ruben Fiszel
d87ae08362 fix npm_mode check from raw_deps 2024-07-25 12:58:04 +02:00
Henri Courdent
7102dd81c0 Changelog update frontend 25 07 (#4130) 2024-07-25 11:21:17 +02:00
Ruben Fiszel
8e615c900d fix: support npmjs mode for raw deps of package.json from CLI 2024-07-25 09:53:18 +02:00
Ruben Fiszel
9300ee6f47 fix docker-compose 2024-07-24 19:05:31 +02:00
Ruben Fiszel
01f2eeb3fe chore(main): release 1.367.2 (#4127)
* chore(main): release 1.367.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-24 18:28:56 +02:00
Ruben Fiszel
92dac027f2 fix: move bun cache to non mounted volume to benefit from cache optimization 2024-07-24 18:25:21 +02:00
Ruben Fiszel
9c63623436 avoid loops in symlinks 2024-07-24 16:45:04 +02:00
Ruben Fiszel
289766281c only use symlinks from buntar to job 2024-07-24 16:04:52 +02:00
Ruben Fiszel
9fd3ec5b03 only use symlinks from buntar to job 2024-07-24 15:54:46 +02:00
Ruben Fiszel
80d4fb0352 fix: use symlink and straight copy as fallback methods for buntar 2024-07-24 15:45:08 +02:00
Ruben Fiszel
aaf067eedb chore(main): release 1.367.1 (#4126)
* chore(main): release 1.367.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-24 15:27:14 +02:00
Ruben Fiszel
204e2fafda fix: delete buntar if any issue while creating it 2024-07-24 15:23:06 +02:00
Ruben Fiszel
d1012ea930 remove shared from hardlinks 2024-07-24 15:14:27 +02:00
Ruben Fiszel
7fbd0028c1 fix: generate lockfile with npm when npm mode is used 2024-07-24 14:48:25 +02:00
Ruben Fiszel
eead10bb80 chore(main): release 1.367.0 (#4118)
* chore(main): release 1.367.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-24 13:55:40 +02:00
Ruben Fiszel
d49b2d0f06 fix: fix webhooks urls after BASE_URL change 2024-07-24 13:31:06 +02:00
Ruben Fiszel
83d3f491b3 chore: update sqlx to 0.8.0 2024-07-24 11:51:54 +02:00
Ruben Fiszel
6d1fd24d37 chore: update sqlx to 0.8.0 2024-07-24 11:48:33 +02:00
wendrul
8fcda68af0 improve search backup on s3 2024-07-24 11:31:37 +02:00
Ruben Fiszel
20390c53e8 fix: respect sorting when downloading csv f rom auto table 2024-07-24 11:12:51 +02:00
Ruben Fiszel
c0f3a40c07 better split tantivy 2024-07-24 09:03:54 +02:00
Ruben Fiszel
7047e2975d better split tantivy 2024-07-24 08:15:14 +02:00
wendrul
5355af427d Prepare sqlx (#4122) 2024-07-23 20:03:30 +02:00
wendrul
eceb472d16 Prepare sqlx (#4121) 2024-07-23 19:54:31 +02:00
wendrul
cac39a1051 feat: job search index backed up and loaded from s3 (#4100)
* Add fn to get logs from disk and store as string

* Update dependencies

* Add async

* Update dependecies

* Add missing async

* Move JOB_RETENTION_SECS to windmill_common

* Update ee repo ref
2024-07-23 19:35:29 +02:00
HugoCasa
14a4f12826 feat: apply workspace specific tags only to some workspaces (#4107) 2024-07-23 19:12:11 +02:00
Ruben Fiszel
97f6b2e7ad fix: make result_json path stable for python executors 2024-07-23 17:39:40 +02:00
HugoCasa
c6dc06bf70 fix: sqlx build (#4120) 2024-07-23 17:14:36 +02:00
Ruben Fiszel
9ab5b2e32d fix: preserve force json across code preview 2024-07-23 16:42:29 +02:00
HugoCasa
13e9e8de9e fix: cgroupv1 mem limit + granular memory reporting (#4119)
* fix: cgroupv1 mem limit + granular memory reporting

* update ee ref

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-07-23 16:24:22 +02:00
HugoCasa
73decb24b2 fix: filter audit logs end user by username + complete resource filter (#4105)
* fix: filter audit logs end user by username + complete resource filter

* update ee ref

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-07-23 16:23:33 +02:00
HugoCasa
d096704b27 feat: use jwks for external jwt auth (#4089)
* feat: use jwks for external jwts

* fix: build

* update ee ref

* fix: nit error msg
2024-07-23 16:22:36 +02:00
Ruben Fiszel
48e9f089be feat: use hardlinks instead of tar to improve bun cache performances 2024-07-23 15:30:09 +02:00
Ruben Fiszel
99509ac683 chore(main): release 1.366.6 (#4117)
* chore(main): release 1.366.6

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-23 12:50:06 +02:00
Ruben Fiszel
ae4cbb0401 fix: fix copilot completion after base_url change 2024-07-23 12:21:12 +02:00
Ruben Fiszel
05388053e8 improve scrollbar styling for chrome 2024-07-23 08:57:04 +02:00
Ruben Fiszel
a117a4d017 chore(main): release 1.366.5 (#4116)
* chore(main): release 1.366.5

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-22 22:54:50 +02:00
Ruben Fiszel
bb861cfede fix: fix BASE_URL build conf 2024-07-22 22:51:08 +02:00
Ruben Fiszel
0f91539977 chore(main): release 1.366.4 (#4115)
* chore(main): release 1.366.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-22 22:39:39 +02:00
Ruben Fiszel
ab686ea69e fix sveltekit conf 2024-07-22 22:36:56 +02:00
Ruben Fiszel
e7cdc2b934 fix sveltekit conf 2024-07-22 22:31:53 +02:00
Ruben Fiszel
7d4dc8df26 fix sveltekit conf 2024-07-22 22:25:53 +02:00
Ruben Fiszel
6e33e4e0b9 fix: fix BASE_URL build conf 2024-07-22 21:53:08 +02:00
Ruben Fiszel
8a89652255 nit fix 2024-07-22 21:24:53 +02:00
Ruben Fiszel
f4c9ba00f1 chore(main): release 1.366.3 (#4114)
* chore(main): release 1.366.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-07-22 21:14:30 +02:00
Ruben Fiszel
25d230d8d8 nit fix 2024-07-22 21:13:43 +02:00
Ruben Fiszel
f6a948ba77 fix: fix BASE_URL build conf 2024-07-22 21:11:14 +02:00
312 changed files with 11255 additions and 3642 deletions

View File

@@ -1,5 +1,5 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.78-slim-bookworm
ARG RUST_IMAGE=rust:1.79-slim-bookworm
ARG PYTHON_IMAGE=python:3.11.4-slim-bookworm
FROM ${DEBIAN_IMAGE} as downloader

View File

@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.78.0
toolchain: 1.79.0
# - uses: Swatinem/rust-cache@v2
# with:
# workspaces: |

View File

@@ -0,0 +1,45 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/caddy-l4
name: Build caddy-l4
on:
workflow_dispatch:
permissions: write-all
jobs:
build_ee:
runs-on: ubicloud
steps:
- uses: actions/checkout@v4
- uses: depot/setup-action@v1
- name: Docker meta
id: meta-ee-public
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push publicly
uses: depot/build-push-action@v1
with:
context: ./docker
file: ./docker/DockerfileCaddyL4
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ steps.meta-ee-public.outputs.tags }}
labels: |
${{ steps.meta-ee-public.outputs.labels }}

76
.github/workflows/docker-image-rpi4.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}-rpi
name: Build windmill without jemalloc
on:
workflow_dispatch:
concurrency:
group: windmill-without-jemalloc
cancel-in-progress: true
permissions: write-all
jobs:
build:
runs-on: ubicloud
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read EE repo commit hash
run: |
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
repository: windmill-labs/windmill-ee-private
path: ./windmill-ee-private
ref: ${{ env.ee_repo_ref }}
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 0
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- uses: depot/setup-action@v1
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Substitute EE code (EE logic is behind feature flag)
run: |
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Docker meta
id: meta-public
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=false
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push publicly
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ steps.meta-public.outputs.tags }}
labels: |
${{ steps.meta-public.outputs.labels }}
org.opencontainers.image.licenses=AGPLv3

View File

@@ -1,6 +1,7 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.repository || 'windmill-labs/windmill-test' }}
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}', github.event.number) }}
name: Build windmill:main
on:
@@ -62,7 +63,6 @@ jobs:
flavor: |
latest=false
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -75,7 +75,7 @@ jobs:
build-args: |
features=embedding,parquet,openidconnect,jemalloc
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
${{ steps.meta-public.outputs.tags }}
labels: |
${{ steps.meta-public.outputs.labels }}
@@ -113,7 +113,6 @@ jobs:
flavor: |
latest=false
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -137,7 +136,7 @@ jobs:
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}
labels: |
${{ steps.meta-ee-public.outputs.labels }}
@@ -176,7 +175,6 @@ jobs:
flavor: |
latest=false
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -250,25 +248,25 @@ jobs:
- run: |
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
- run: |
# Checks the image is in docker prior to running actions-docker-extract. It fails if not
# Also useful to visually check that the arch is the right opencontainers
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
- uses: shrink/actions-docker-extract@v3
id: extract
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
path: "/usr/src/app/windmill"
- uses: shrink/actions-docker-extract@v3
id: extract-ee
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
path: "/usr/src/app/windmill"
- name: Rename binary with corresponding architecture
@@ -294,25 +292,25 @@ jobs:
# - run: |
# # pulling docker image with desired arch so that actions-docker-extract doesn't do it
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
# - run: |
# # Checks the image is in docker prior to running actions-docker-extract. It fails if not
# # Also useful to visually check that the arch is the right opencontainers
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
# - uses: shrink/actions-docker-extract@v3
# id: extract
# with:
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
# path: "/usr/src/app/windmill"
# - uses: shrink/actions-docker-extract@v3
# id: extract-ee
# with:
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
# path: "/usr/src/app/windmill"
# - name: Rename binary with corresponding architecture
@@ -354,6 +352,7 @@ jobs:
tag_latest:
runs-on: ubicloud
needs: [run_integration_test, build]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
with:
@@ -366,12 +365,13 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag main and latest
run: |
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
tag_latest_ee:
runs-on: ubicloud
needs: [run_integration_test, build_ee]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
with:
@@ -384,8 +384,8 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag main and latest for ee
run: |
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
verify_ee_image_vulnerabilities:
runs-on: ubicloud
@@ -432,6 +432,7 @@ jobs:
build_ee_nsjail:
needs: [build_ee]
runs-on: ubicloud
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
with:
@@ -451,8 +452,6 @@ jobs:
flavor: |
latest=false
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
@@ -480,6 +479,7 @@ jobs:
build_ee_reports_privately:
needs: [build_ee_nsjail]
runs-on: ubicloud
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
with:
@@ -503,8 +503,6 @@ jobs:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-reports
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
@@ -589,7 +587,6 @@ jobs:
flavor: |
latest=false
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -636,7 +633,6 @@ jobs:
flavor: |
latest=false
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

View File

@@ -1,5 +1,299 @@
# Changelog
## [1.377.1](https://github.com/windmill-labs/windmill/compare/v1.377.0...v1.377.1) (2024-08-08)
### Bug Fixes
* **frontend:** fix timezone issues for Date only inputs ([#4215](https://github.com/windmill-labs/windmill/issues/4215)) ([2334802](https://github.com/windmill-labs/windmill/commit/2334802384e86c4269e1cbfd4f6f98a9a1e58f68))
## [1.377.0](https://github.com/windmill-labs/windmill/compare/v1.376.1...v1.377.0) (2024-08-08)
### Features
* **app:** add user resource select component ([413ad2c](https://github.com/windmill-labs/windmill/commit/413ad2c9259fd30871b5184bc4f7c3fece60918d))
### Bug Fixes
* Additional tracing when pulling and uploading search index [#4207](https://github.com/windmill-labs/windmill/issues/4207) ([4f9193c](https://github.com/windmill-labs/windmill/commit/4f9193c03664c336095e9aa2ea6232a4815c7387))
* azure git sync test connection ([#4209](https://github.com/windmill-labs/windmill/issues/4209)) ([2c3d492](https://github.com/windmill-labs/windmill/commit/2c3d4920e0b575019dd18d0a4a9609e6f66be60e))
* case insensitive encoding for email triggers ([#4211](https://github.com/windmill-labs/windmill/issues/4211)) ([55926f9](https://github.com/windmill-labs/windmill/commit/55926f957f83cf87a52070577ed79bed15e38836))
* **cli:** Add esbuild loader for .node files for codebase ([a77f74c](https://github.com/windmill-labs/windmill/commit/a77f74cb7ea5ed3633564075169a5aeedae06152))
* **frontend:** fix decision tree debug menu ([#4212](https://github.com/windmill-labs/windmill/issues/4212)) ([c90fb8d](https://github.com/windmill-labs/windmill/commit/c90fb8d293ef3aaa8264c2a2984e4203e4851f5c))
* **frontend:** fix flow warnings ([#4213](https://github.com/windmill-labs/windmill/issues/4213)) ([bf629f3](https://github.com/windmill-labs/windmill/commit/bf629f39ccc411b6ed98081f414a8c3238d52957))
* handle &gt;1 num workers stats ([#4177](https://github.com/windmill-labs/windmill/issues/4177)) ([0029dc1](https://github.com/windmill-labs/windmill/commit/0029dc11e9f0828c4a129586ac90a18b3f4dfe79))
* handle snowflake partitions ([#4214](https://github.com/windmill-labs/windmill/issues/4214)) ([d1e119e](https://github.com/windmill-labs/windmill/commit/d1e119e0526933cf39320eecf7c4836cd45d0eb0))
## [1.376.1](https://github.com/windmill-labs/windmill/compare/v1.376.0...v1.376.1) (2024-08-07)
### Bug Fixes
* email triggers sqlx CE and improve email parsing ([#4203](https://github.com/windmill-labs/windmill/issues/4203)) ([6e9c350](https://github.com/windmill-labs/windmill/commit/6e9c350b4aa27355d03cb409da36e8f44ff548dc))
* mysql params starting with underscore ([#4201](https://github.com/windmill-labs/windmill/issues/4201)) ([e7148f6](https://github.com/windmill-labs/windmill/commit/e7148f672303ccf2b5c49bd9d75ee9764657490e))
## [1.376.0](https://github.com/windmill-labs/windmill/compare/v1.375.0...v1.376.0) (2024-08-06)
### Features
* email triggers ([#4163](https://github.com/windmill-labs/windmill/issues/4163)) ([a87f34f](https://github.com/windmill-labs/windmill/commit/a87f34fb4a5e54da38e2dbabf2ac551d35b021ee))
* secure ctx variables in runnable inputs ([#4142](https://github.com/windmill-labs/windmill/issues/4142)) ([b54edf1](https://github.com/windmill-labs/windmill/commit/b54edf153ec90b9bafad9ed7bc92c29345951fe6))
* Tag filter on Runs page ([#4193](https://github.com/windmill-labs/windmill/issues/4193)) ([7b31281](https://github.com/windmill-labs/windmill/commit/7b3128171ea7193efea569297a099bb9ee6935e1))
### Bug Fixes
* fix native scripts access to reserved variables ([7886f8f](https://github.com/windmill-labs/windmill/commit/7886f8f471bcb33f7cc640eab64ffc0dd3cb1726))
* **frontend:** disable email triggers by default ([#4199](https://github.com/windmill-labs/windmill/issues/4199)) ([5e3a3e2](https://github.com/windmill-labs/windmill/commit/5e3a3e2103ce6218a60c570546355ef6ca574df2))
* **frontend:** fix the app created from a script or flow with the new topbar ([#4194](https://github.com/windmill-labs/windmill/issues/4194)) ([657f03b](https://github.com/windmill-labs/windmill/commit/657f03bc67f84a8593b28483684c2ad8ad432865))
* **frontend:** Fr/improve suspend drawer ([#4189](https://github.com/windmill-labs/windmill/issues/4189)) ([5104dba](https://github.com/windmill-labs/windmill/commit/5104dba63140e2186dc1eed7f0bf4e14e357bf3d))
* **frontend:** Hide AgChart background to make styling work ([#4197](https://github.com/windmill-labs/windmill/issues/4197)) ([b9b30e6](https://github.com/windmill-labs/windmill/commit/b9b30e66ec485cc019561b092fc27d08aa4666b3))
* **frontend:** Remove full height for the event handlers of runnables ([#4196](https://github.com/windmill-labs/windmill/issues/4196)) ([6749f2c](https://github.com/windmill-labs/windmill/commit/6749f2c1367bdbecb12941baaa87b63c4b4c6f20))
* mysql support for underscore in named param ([#4200](https://github.com/windmill-labs/windmill/issues/4200)) ([cfa20ae](https://github.com/windmill-labs/windmill/commit/cfa20ae0d740014baea2e534cea5f787acaeb028))
## [1.375.0](https://github.com/windmill-labs/windmill/compare/v1.374.0...v1.375.0) (2024-08-05)
### Features
* deployment UI filter deployable items ([#4183](https://github.com/windmill-labs/windmill/issues/4183)) ([f2f8bbe](https://github.com/windmill-labs/windmill/commit/f2f8bbe1d4364540854fef3bf11df5bb4252c17d))
* improve indices of completed_runs for faster load ([cc111ba](https://github.com/windmill-labs/windmill/commit/cc111ba7dcd7fe6c4280d394d4eb0e723e9d8b19))
### Bug Fixes
* **frontend:** add missing truncate for branch predicates ([#4180](https://github.com/windmill-labs/windmill/issues/4180)) ([947dd21](https://github.com/windmill-labs/windmill/commit/947dd219335244728213e95a46e577428ac65464))
* **frontend:** fix style panel for compoentn without custom css ([#4182](https://github.com/windmill-labs/windmill/issues/4182)) ([43a89ee](https://github.com/windmill-labs/windmill/commit/43a89ee1ccbeeac11211277f8005d85541c71dc3))
* **frontend:** fix tutorial for apps with the new topbar ([#4186](https://github.com/windmill-labs/windmill/issues/4186)) ([704e75e](https://github.com/windmill-labs/windmill/commit/704e75e893a94e71091c840aa5608837c0617526))
## [1.374.0](https://github.com/windmill-labs/windmill/compare/v1.373.1...v1.374.0) (2024-08-04)
### Features
* add support for assets using tar for codebase deploy ([3508b6d](https://github.com/windmill-labs/windmill/commit/3508b6d793b0ce21c0c264a50bb000cd61c21920))
* caddy with l4 image ([#4178](https://github.com/windmill-labs/windmill/issues/4178)) ([811de58](https://github.com/windmill-labs/windmill/commit/811de58712f619045a3653fe5b98ff0c7599f392))
### Bug Fixes
* fix delete job ([86e23f2](https://github.com/windmill-labs/windmill/commit/86e23f2aed234a64fce46e1b6253f788546876af))
## [1.373.1](https://github.com/windmill-labs/windmill/compare/v1.373.0...v1.373.1) (2024-08-02)
### Bug Fixes
* fix run_flow_async from call ([63abd5b](https://github.com/windmill-labs/windmill/commit/63abd5b14606869eafbd25d8469638b85b96e199))
* **frontend:** Add support for array of objects in th UI ([#4170](https://github.com/windmill-labs/windmill/issues/4170)) ([91e364b](https://github.com/windmill-labs/windmill/commit/91e364bde5459447945d7dae65c1cab2858c69ac))
## [1.373.0](https://github.com/windmill-labs/windmill/compare/v1.372.0...v1.373.0) (2024-08-01)
### Features
* Indexing improvements ([#4167](https://github.com/windmill-labs/windmill/issues/4167)) ([edcee6d](https://github.com/windmill-labs/windmill/commit/edcee6d8611ff96bfbca7ba811e5e67beaa80f5a))
### Bug Fixes
* app forms default values changes gets propagated ([cd61fc1](https://github.com/windmill-labs/windmill/commit/cd61fc1e8c00cc3a57af0228cdf958b2fb3a2699))
## [1.372.0](https://github.com/windmill-labs/windmill/compare/v1.371.4...v1.372.0) (2024-08-01)
### Features
* variables created by password fields expire after 7 days ([b5464e2](https://github.com/windmill-labs/windmill/commit/b5464e2906bdaedb4752e198e40b7e8c4dbb1338))
### Bug Fixes
* fix raw_deps handling ([efcf0e4](https://github.com/windmill-labs/windmill/commit/efcf0e40f68342f8330cf5d610979eb7fe92ce09))
* improve cancel_selection job for running jobs ([42e3ae9](https://github.com/windmill-labs/windmill/commit/42e3ae92e3841579f8ac0821b58ebb72c4204c98))
## [1.371.4](https://github.com/windmill-labs/windmill/compare/v1.371.3...v1.371.4) (2024-07-31)
### Bug Fixes
* **frontend:** fix recompute all ([#4161](https://github.com/windmill-labs/windmill/issues/4161)) ([10c6997](https://github.com/windmill-labs/windmill/commit/10c699759e1d06d009783b9f5cba2cbf3ba6a7da))
* **frontend:** If multiple recompute all present, interval is now in sync ([#4162](https://github.com/windmill-labs/windmill/issues/4162)) ([efeb65b](https://github.com/windmill-labs/windmill/commit/efeb65be4c91491bb4fbedfbc160da7517ebe1d6))
* improve index usage and runs page performance ([d69aa8d](https://github.com/windmill-labs/windmill/commit/d69aa8d484de016ae7e7bbd190ce6c254818c861))
## [1.371.3](https://github.com/windmill-labs/windmill/compare/v1.371.2...v1.371.3) (2024-07-30)
### Bug Fixes
* for codebase bundle, use cjs exports instead ([be8cedf](https://github.com/windmill-labs/windmill/commit/be8cedfe7fe42105a375c92536ec7f5e5a1343bf))
## [1.371.2](https://github.com/windmill-labs/windmill/compare/v1.371.1...v1.371.2) (2024-07-30)
### Bug Fixes
* improve codebase handling by ignoring creating lock and bundle ([716bb71](https://github.com/windmill-labs/windmill/commit/716bb7118b0d5b8dd86799d5514f37b068613256))
## [1.371.1](https://github.com/windmill-labs/windmill/compare/v1.371.0...v1.371.1) (2024-07-30)
### Bug Fixes
* database connections now scale linearly with number of subworkers ([f59046a](https://github.com/windmill-labs/windmill/commit/f59046a9249962ba7e1739de579cd8d885ec63a8))
## [1.371.0](https://github.com/windmill-labs/windmill/compare/v1.370.0...v1.371.0) (2024-07-30)
### Features
* **frontend:** support array of objects in schema ([#4106](https://github.com/windmill-labs/windmill/issues/4106)) ([5992b82](https://github.com/windmill-labs/windmill/commit/5992b821803d9cdc45fa0dee100f159dd3ed557f))
### Bug Fixes
* always consider electron as external for bundles ([06433a6](https://github.com/windmill-labs/windmill/commit/06433a6ec65534e1a11085cefd6a67699e4eb0a0))
* fix bunnative lock creation ([6a33624](https://github.com/windmill-labs/windmill/commit/6a33624416cc48e2a1ab21afbd2ac4ab48be2559))
* submit and form persistence on app on render change ([28277da](https://github.com/windmill-labs/windmill/commit/28277da35bae1c18f16e85d093989e0d419b3601))
* support bunnative in the CLI ([e02e644](https://github.com/windmill-labs/windmill/commit/e02e644d4ca16773bae3cc2a6b52ac08417d3905))
## [1.370.0](https://github.com/windmill-labs/windmill/compare/v1.369.1...v1.370.0) (2024-07-29)
### Features
* get completed flow node result by api/download + eval list result json path optim ([#4108](https://github.com/windmill-labs/windmill/issues/4108)) ([5031a8c](https://github.com/windmill-labs/windmill/commit/5031a8cb015e3ba8d668f5de769b8d67da82c116))
### Bug Fixes
* improve native runtime with axios support ([6e91005](https://github.com/windmill-labs/windmill/commit/6e91005daa4bbe2f0bd1f8b1c2cce1f68485f9ef))
## [1.369.1](https://github.com/windmill-labs/windmill/compare/v1.369.0...v1.369.1) (2024-07-29)
### Bug Fixes
* fix lang picker artefact for script editor ([bc94acb](https://github.com/windmill-labs/windmill/commit/bc94acb378b1310271dd3dda80614e0e38b5285f))
## [1.369.0](https://github.com/windmill-labs/windmill/compare/v1.368.3...v1.369.0) (2024-07-29)
### Features
* add support for text/plain webhook ([#4146](https://github.com/windmill-labs/windmill/issues/4146)) ([eb6557a](https://github.com/windmill-labs/windmill/commit/eb6557a6beb5d3b2324b30f2abc117b1a122c3f7))
* **frontend:** App bar as components ([#4103](https://github.com/windmill-labs/windmill/issues/4103)) ([fb89eed](https://github.com/windmill-labs/windmill/commit/fb89eed8fa43568ffe3a22c5a6dd0b9f38890a37))
* remove nativets in favor of bun with native pragma ([18f22be](https://github.com/windmill-labs/windmill/commit/18f22be2bae3136f1d9ff06c74f3751504bef60b))
* remove nativets in favor of bun with native pragma ([b02baa2](https://github.com/windmill-labs/windmill/commit/b02baa2cfb242f7f6ed64e9861a8562272fb2974))
## [1.368.3](https://github.com/windmill-labs/windmill/compare/v1.368.2...v1.368.3) (2024-07-28)
### Bug Fixes
* update bun to 1.1.21 ([e45a8a4](https://github.com/windmill-labs/windmill/commit/e45a8a4591649de240a4bff0a25301a2586618bf))
## [1.368.2](https://github.com/windmill-labs/windmill/compare/v1.368.1...v1.368.2) (2024-07-28)
### Bug Fixes
* add the nobundling option for bun ([c3848e2](https://github.com/windmill-labs/windmill/commit/c3848e2e3011c7e74d5ae6eb85605476ef2998f4))
* disable prebundling for nodejs mode scripts ([1e2e907](https://github.com/windmill-labs/windmill/commit/1e2e907982eab2ea7e713d9cfbec046527616fa2))
## [1.368.1](https://github.com/windmill-labs/windmill/compare/v1.368.0...v1.368.1) (2024-07-27)
### Bug Fixes
* improve runs page performance through pg indices ([99623f3](https://github.com/windmill-labs/windmill/commit/99623f31af109463b065688058d0cab8ea9910de))
## [1.368.0](https://github.com/windmill-labs/windmill/compare/v1.367.2...v1.368.0) (2024-07-26)
### Features
* add FORCE_&lt;env&gt; to allow to override db settings ([f1d5be8](https://github.com/windmill-labs/windmill/commit/f1d5be8a2a76d5c0d37c55ba762f2cd7a6f6982f))
* job view audit logging based on env variable ([#4131](https://github.com/windmill-labs/windmill/issues/4131)) ([12f9e56](https://github.com/windmill-labs/windmill/commit/12f9e56154b32d839f49dd967ada4952ca4c395d))
* multi sql statement with pg fix ([#4134](https://github.com/windmill-labs/windmill/issues/4134)) ([6df9eca](https://github.com/windmill-labs/windmill/commit/6df9eca2cffef754ee27b3219f18d0ad69615c83))
* multi statement sql ([#4104](https://github.com/windmill-labs/windmill/issues/4104)) ([23108b4](https://github.com/windmill-labs/windmill/commit/23108b4688ec9c9683371ee70a50658f6baae42f))
* pre-bundle bun scripts ([#4132](https://github.com/windmill-labs/windmill/issues/4132)) ([78fd99c](https://github.com/windmill-labs/windmill/commit/78fd99cb6843c7f8045acd6a6a8df851ec6b35d1))
### Bug Fixes
* allow colors for bun and log error directly ([545a57f](https://github.com/windmill-labs/windmill/commit/545a57fd2c425f02d867f4183aac37131e17210f))
* search modal improvements ([#4128](https://github.com/windmill-labs/windmill/issues/4128)) ([c270ab1](https://github.com/windmill-labs/windmill/commit/c270ab11718f8d41fe764d5d2116bb3b9aa20cb2))
* support npmjs mode for raw deps of package.json from CLI ([8e615c9](https://github.com/windmill-labs/windmill/commit/8e615c900de46632c52d78a1ec4b6ccc2cec79ff))
## [1.367.2](https://github.com/windmill-labs/windmill/compare/v1.367.1...v1.367.2) (2024-07-24)
### Bug Fixes
* move bun cache to non mounted volume to benefit from cache optimization ([92dac02](https://github.com/windmill-labs/windmill/commit/92dac027f26dfa880e22b1896b48605ae2ef2794))
* use symlink and straight copy as fallback methods for buntar ([80d4fb0](https://github.com/windmill-labs/windmill/commit/80d4fb0352606948b01f4918e11f80a0bef544f4))
## [1.367.1](https://github.com/windmill-labs/windmill/compare/v1.367.0...v1.367.1) (2024-07-24)
### Bug Fixes
* delete buntar if any issue while creating it ([204e2fa](https://github.com/windmill-labs/windmill/commit/204e2fafda681347813ab91f96cee4194db2e156))
* generate lockfile with npm when npm mode is used ([7fbd002](https://github.com/windmill-labs/windmill/commit/7fbd0028c1f2dfbd6a5cde12e6a0e314ed31552a))
## [1.367.0](https://github.com/windmill-labs/windmill/compare/v1.366.6...v1.367.0) (2024-07-24)
### Features
* apply workspace specific tags only to some workspaces ([#4107](https://github.com/windmill-labs/windmill/issues/4107)) ([14a4f12](https://github.com/windmill-labs/windmill/commit/14a4f1282671d1c6a2481123cd5a4297fbd89607))
* job search index backed up and loaded from s3 ([#4100](https://github.com/windmill-labs/windmill/issues/4100)) ([cac39a1](https://github.com/windmill-labs/windmill/commit/cac39a105187bd005dee442c4968ebbd9ba163d7))
* use hardlinks instead of tar to improve bun cache performances ([48e9f08](https://github.com/windmill-labs/windmill/commit/48e9f089beff10828eef7a35fb341894eb50d25d))
* use jwks for external jwt auth ([#4089](https://github.com/windmill-labs/windmill/issues/4089)) ([d096704](https://github.com/windmill-labs/windmill/commit/d096704b27f169672d8949f8570b2b5cba25e78c))
### Bug Fixes
* cgroupv1 mem limit + granular memory reporting ([#4119](https://github.com/windmill-labs/windmill/issues/4119)) ([13e9e8d](https://github.com/windmill-labs/windmill/commit/13e9e8de9e49f9513ad0cd8ac5a3b8b356de02a9))
* filter audit logs end user by username + complete resource filter ([#4105](https://github.com/windmill-labs/windmill/issues/4105)) ([73decb2](https://github.com/windmill-labs/windmill/commit/73decb24b23e1a998957455cf4f4148be5990cce))
* fix webhooks urls after BASE_URL change ([d49b2d0](https://github.com/windmill-labs/windmill/commit/d49b2d0f06842e70180f931ac7b4ac683c1c65b6))
* make result_json path stable for python executors ([97f6b2e](https://github.com/windmill-labs/windmill/commit/97f6b2e7ad9962c99e566238bde40cdba0ffc3a8))
* preserve force json across code preview ([9ab5b2e](https://github.com/windmill-labs/windmill/commit/9ab5b2e32d78056752f654769154b90872a9ab85))
* respect sorting when downloading csv f rom auto table ([20390c5](https://github.com/windmill-labs/windmill/commit/20390c53e82686ac6a8b7a9bc1bf9fb8e014e3c5))
* sqlx build ([#4120](https://github.com/windmill-labs/windmill/issues/4120)) ([c6dc06b](https://github.com/windmill-labs/windmill/commit/c6dc06bf702b501225a51813a2489ce9ea3c4636))
## [1.366.6](https://github.com/windmill-labs/windmill/compare/v1.366.5...v1.366.6) (2024-07-23)
### Bug Fixes
* fix copilot completion after base_url change ([ae4cbb0](https://github.com/windmill-labs/windmill/commit/ae4cbb0401b01e1e378b2a982d7a610648c96239))
## [1.366.5](https://github.com/windmill-labs/windmill/compare/v1.366.4...v1.366.5) (2024-07-22)
### Bug Fixes
* fix BASE_URL build conf ([bb861cf](https://github.com/windmill-labs/windmill/commit/bb861cfedef1e5531f31b62dd73907b89ddd473a))
## [1.366.4](https://github.com/windmill-labs/windmill/compare/v1.366.3...v1.366.4) (2024-07-22)
### Bug Fixes
* fix BASE_URL build conf ([6e33e4e](https://github.com/windmill-labs/windmill/commit/6e33e4e0b9fce86407cc7cfc9a4a57e03ec5199b))
## [1.366.3](https://github.com/windmill-labs/windmill/compare/v1.366.2...v1.366.3) (2024-07-22)
### Bug Fixes
* fix BASE_URL build conf ([f6a948b](https://github.com/windmill-labs/windmill/commit/f6a948ba7705b5b59af87bff7d17252ec1c8e739))
## [1.366.2](https://github.com/windmill-labs/windmill/compare/v1.366.1...v1.366.2) (2024-07-22)

View File

@@ -1,3 +1,13 @@
{
layer4 {
:25 {
proxy {
to windmill_server:2525
}
}
}
}
{$BASE_URL} {
bind {$ADDRESS}
reverse_proxy /ws/* http://lsp:3001

View File

@@ -1,5 +1,5 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.78-slim-bookworm
ARG RUST_IMAGE=rust:1.79-slim-bookworm
ARG PYTHON_IMAGE=python:3.11.8-slim-bookworm
FROM ${RUST_IMAGE} AS rust_base
@@ -41,7 +41,7 @@ COPY /typescript-client/docs/ /frontend/static/tsdocs/
RUN npm run generate-backend-client
ENV NODE_OPTIONS "--max-old-space-size=8192"
ARG BASE_URL ""
ARG VITE_BASE_URL ""
RUN npm run build
@@ -127,13 +127,13 @@ RUN set -eux; \
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
case "$arch" in \
'amd64') \
targz='go1.21.6.linux-amd64.tar.gz'; \
targz='go1.22.5.linux-amd64.tar.gz'; \
;; \
'arm64') \
targz='go1.21.6.linux-arm64.tar.gz'; \
targz='go1.22.5.linux-arm64.tar.gz'; \
;; \
'armhf') \
targz='go1.21.6.linux-armv6l.tar.gz'; \
targz='go1.22.5.linux-armv6l.tar.gz'; \
;; \
*) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \
esac; \
@@ -155,9 +155,9 @@ RUN /usr/local/bin/python3 -m pip install pip-tools
COPY --from=builder /frontend/build /static_frontend
COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
COPY --from=denoland/deno:1.44.4 --chmod=755 /usr/bin/deno /usr/bin/deno
COPY --from=denoland/deno:1.45.4 --chmod=755 /usr/bin/deno /usr/bin/deno
COPY --from=oven/bun:1.1.18 /usr/local/bin/bun /usr/bin/bun
COPY --from=oven/bun:1.1.21 /usr/local/bin/bun /usr/bin/bun
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
@@ -173,4 +173,4 @@ RUN windmill cache
EXPOSE 8000
CMD ["windmill"]
CMD ["windmill"]

View File

@@ -3,7 +3,7 @@
</p>
<p align=center>
Open-source developer infrastructure for internal tools (APIs, background jobs, workflows and UIs). Self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs and custom UIs to trigger workflows and scripts as internal apps.
Open-source developer infrastructure for internal tools (APIs, background jobs, workflows and UIs). Self-hostable alternative to Retool, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs and custom UIs to trigger workflows and scripts as internal apps.
<p align=center>
Scripts are turned into sharable UIs automatically, and can be composed together into flows or used into richer apps built with low-code. Supported script languages supported are: Python, TypeScript, Go, Bash, SQL, and GraphQL.
@@ -110,8 +110,8 @@ You can build your entire infra on top of Windmill!
```typescript
//import any dependency from npm
import * as wmill from "https://deno.land/x/windmill@v1.136.0/mod.ts";
import cowsay from "npm:cowsay@1.5.0";
import * as wmill from "windmill-client"
import * as cowsay from 'cowsay@1.5.0';
// fill the type, or use the +Resource type to get a type-safe reference to a resource
type Postgresql = {
@@ -131,7 +131,7 @@ export async function main(
e = { nested: "object" }
//f: wmill.Base64
) {
const email = Deno.env.get("WM_EMAIL");
const email = process.env["WM_EMAIL"];
// variables are permissioned and by path
let variable = await wmill.getVariable("f/company-folder/my_secret");
const lastTimeRun = await wmill.getState();
@@ -146,10 +146,8 @@ export async function main(
## CLI
We have a powerful CLI to interact with the windmill platform and sync your
scripts from local files, github repos and to run scripts and flows on the
instance from local commands. See
[more details](https://github.com/windmill-labs/windmill/tree/main/cli).
We have a powerful CLI to interact with the windmill platform and sync your scripts from local files, GitHub repos and to run scripts and flows on the instance from local commands. See
[more details](https://www.windmill.dev/docs/advanced/cli).
![CLI Screencast](./cli/vhs/output/setup.gif)
@@ -165,22 +163,21 @@ Code extension: <https://www.windmill.dev/docs/cli_local_dev/vscode-extension>.
## Stack
- Postgres as the database
- backend in Rust with the following highly-available and horizontally scalable
architecture:
- stateless API backend
- workers that pull jobs from a queue in Postgres (and later, Kafka or Redis.
Upvote [#173](#https://github.com/windmill-labs/windmill/issues/173) if
interested )
- frontend in Svelte
- scripts executions are sandboxed using google's
[nsjail](https://github.com/google/nsjail)
- javascript runtime is the
- Postgres as the database.
- Backend in Rust with the following highly-available and horizontally scalable.
Architecture:
- Stateless API backend.
- Workers that pull jobs from a queue in Postgres (and later, Kafka or Redis.
Upvote [#173](#https://github.com/windmill-labs/windmill/issues/173) if interested).
- Frontend in Svelte.
- Scripts executions are sandboxed using Google's
[nsjail](https://github.com/google/nsjail).
- Javascript runtime is the
[deno_core rust library](https://denolib.gitbook.io/guide/) (which itself uses
the [rusty_v8](https://github.com/denoland/rusty_v8) and hence V8 underneath)
- typescript runtime is deno
- python runtime is python3
- golang runtime is 1.19.1
the [rusty_v8](https://github.com/denoland/rusty_v8) and hence V8 underneath).
- TypeScript runtime is Bun and deno.
- Python runtime is python3.
- Golang runtime is 1.19.1.
## Fastest Self-Hostable Workflow Engine
@@ -399,4 +396,4 @@ running options.
## Copyright
Windmill Labs, Inc 2023
Windmill Labs, Inc 2023

View File

@@ -0,0 +1,8 @@
export default {
plugins: {
"postcss-import": {},
"tailwindcss/nesting": "postcss-nesting",
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -0,0 +1,950 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [],
theme: {
extend: {},
},
plugins: [],
};
const plugin = require("tailwindcss/plugin");
const lightTheme = {
surface: "#ffffff",
surfaceSecondary: "#f3f4f6",
surfaceHover: "#e5e7eb",
surfaceDisabled: "#f9fafb",
surfaceSelected: "#d1d5db",
textPrimary: "#2d3748",
textSecondary: "#4a5568",
textTertiary: "#505c70",
textDisabled: "#a0aec0",
border: "#ddd",
borderHover: "#ccc",
};
const lightThemeRgb = makeRgb(lightTheme);
const darkTheme = {
surface: "#2e3440",
surfaceSecondary: "#3b4252",
surfaceHover: "#454F64",
surfaceDisabled: "#212732",
surfaceSelected: "#434c5e",
textPrimary: "#EEEEEE",
textSecondary: "#C2C9D1",
textTertiary: "#A8AEB7",
textDisabled: "#989DA5",
border: "#3e4c60",
borderHover: "#3e4c60",
};
const darkThemeRgb = makeRgb(darkTheme);
function makeRgb(theme) {
return Object.fromEntries(
Object.entries(theme).map(([key, value]) => {
if (typeof value === "string" && value.startsWith("#")) {
return [key, hexToRgb(value)];
}
return [key, value];
})
);
}
function hexToRgb(hex) {
// Remove '#' symbol from the beginning of the hex value
hex = hex.replace("#", "");
// Convert the hex value to decimal
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
// Return the RGB string format
return `${r} ${g} ${b}`;
}
/** @type {import('tailwindcss').Config} */
const config = {
content: [
"./src/**/*.{html,js,svelte,ts}",
"./node_modules/windmill-components/**/*.{html,js,svelte,ts}",
],
safelist: [
"hljs",
"splitpanes__pane",
"splitpanes__splitter",
"wm-tab",
"autocomplete-list",
"autocomplete-list-item",
"autocomplete-list-item-create",
"selected",
"wm-tab-selected",
...(process.env.NODE_ENV === "production"
? [
{ pattern: /^m(\w?)-.*$/ },
{ pattern: /^p(\w?)-.*$/ },
{ pattern: /^rounded-.*$/ },
{ pattern: /^shadow-.*$/, variants: ["hover"] },
{ pattern: /^text-[^/]*$/, variants: ["hover", "active", "focus"] },
{ pattern: /^bg-[^/]*$/, variants: ["hover", "active", "focus"] },
{ pattern: /^border-[^/]*$/, variants: ["hover", "active", "focus"] },
{ pattern: /^ring-[^/]*$/, variants: ["hover", "active", "focus"] },
]
: []),
],
theme: {
colors: {
current: "currentcolor",
transparent: "transparent",
white: "#ffffff",
black: "#000000",
slate: {
50: "#f8fafc",
100: "#f1f5f9",
200: "#e2e8f0",
300: "#cbd5e1",
400: "#94a3b8",
500: "#64748b",
600: "#475569",
700: "#334155",
800: "#1e293b",
900: "#0f172a",
},
zinc: {
50: "#fafafa",
100: "#f4f4f5",
200: "#e4e4e7",
300: "#d4d4d8",
400: "#a1a1aa",
500: "#71717a",
600: "#52525b",
700: "#3f3f46",
800: "#27272a",
900: "#18181b",
},
neutral: {
50: "#fafafa",
100: "#f5f5f5",
200: "#e5e5e5",
300: "#d4d4d4",
400: "#a3a3a3",
500: "#737373",
600: "#525252",
700: "#404040",
800: "#262626",
900: "#171717",
},
stone: {
50: "#fafaf9",
100: "#f5f5f4",
200: "#e7e5e4",
300: "#d6d3d1",
400: "#a8a29e",
500: "#78716c",
600: "#57534e",
700: "#44403c",
800: "#292524",
900: "#1c1917",
},
orange: {
50: "#fff7ed",
100: "#ffedd5",
200: "#fed7aa",
300: "#fdba74",
400: "#fb923c",
500: "#f97316",
600: "#ea580c",
700: "#c2410c",
800: "#9a3412",
900: "#7c2d12",
},
amber: {
50: "#fffbeb",
100: "#fef3c7",
200: "#fde68a",
300: "#fcd34d",
400: "#fbbf24",
500: "#f59e0b",
600: "#d97706",
700: "#b45309",
800: "#92400e",
900: "#78350f",
},
lime: {
50: "#f7fee7",
100: "#ecfccb",
200: "#d9f99d",
300: "#bef264",
400: "#a3e635",
500: "#84cc16",
600: "#65a30d",
700: "#4d7c0f",
800: "#3f6212",
900: "#365314",
},
emerald: {
50: "#ecfdf5",
100: "#d1fae5",
200: "#a7f3d0",
300: "#6ee7b7",
400: "#34d399",
500: "#10b981",
600: "#059669",
700: "#047857",
800: "#065f46",
900: "#064e3b",
},
teal: {
50: "#f0fdfa",
100: "#ccfbf1",
200: "#99f6e4",
300: "#5eead4",
400: "#2dd4bf",
500: "#14b8a6",
600: "#0d9488",
700: "#0f766e",
800: "#115e59",
900: "#134e4a",
},
cyan: {
50: "#ecfeff",
100: "#cffafe",
200: "#a5f3fc",
300: "#67e8f9",
400: "#22d3ee",
500: "#06b6d4",
600: "#0891b2",
700: "#0e7490",
800: "#155e75",
900: "#164e63",
},
sky: {
50: "#f0f9ff",
100: "#e0f2fe",
200: "#bae6fd",
300: "#7dd3fc",
400: "#38bdf8",
500: "#0ea5e9",
600: "#0284c7",
700: "#0369a1",
800: "#075985",
900: "#0c4a6e",
},
violet: {
50: "#f5f3ff",
100: "#ede9fe",
200: "#ddd6fe",
300: "#c4b5fd",
400: "#a78bfa",
500: "#8b5cf6",
600: "#7c3aed",
700: "#6d28d9",
800: "#5b21b6",
900: "#4c1d95",
},
purple: {
50: "#faf5ff",
100: "#f3e8ff",
200: "#e9d5ff",
300: "#d8b4fe",
400: "#c084fc",
500: "#a855f7",
600: "#9333ea",
700: "#7e22ce",
800: "#6b21a8",
900: "#581c87",
},
fuchsia: {
50: "#fdf4ff",
100: "#fae8ff",
200: "#f5d0fe",
300: "#f0abfc",
400: "#e879f9",
500: "#d946ef",
600: "#c026d3",
700: "#a21caf",
800: "#86198f",
900: "#701a75",
},
pink: {
50: "#fdf2f8",
100: "#fce7f3",
200: "#fbcfe8",
300: "#f9a8d4",
400: "#f472b6",
500: "#ec4899",
600: "#db2777",
700: "#be185d",
800: "#9d174d",
900: "#831843",
},
rose: {
50: "#fff1f2",
100: "#ffe4e6",
200: "#fecdd3",
300: "#fda4af",
400: "#fb7185",
500: "#f43f5e",
600: "#e11d48",
700: "#be123c",
800: "#9f1239",
900: "#881337",
},
gray: {
50: "#f9fafb",
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
400: "#9ca3af",
500: "#6b7280",
600: "#4b5563",
700: "#374151",
800: "#1f2937",
900: "#111827",
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
},
orange: {
100: "#ffedd5",
200: "#ffedd5",
300: "#FDC089",
400: "#fb923c",
500: "#f97316",
600: "#ea580c",
700: "#c2410c",
800: "#c2410c",
},
yellow: {
50: "#fefce8",
100: "#fef9c3",
200: "#fef08a",
300: "#fde047",
400: "#facc15",
500: "#eab308",
600: "#ca8a04",
700: "#a16207",
800: "#854d0e",
900: "#713f12",
},
green: {
50: "#f0fdf4",
100: "#dcfce7",
200: "#bbf7d0",
300: "#86efac",
400: "#4ade80",
500: "#22c55e",
600: "#16a34a",
700: "#15803d",
800: "#166534",
900: "#14532d",
},
blue: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
},
indigo: {
50: "#eef2ff",
100: "#e0e7ff",
200: "#c7d2fe",
300: "#a5b4fc",
400: "#818cf8",
500: "#6366f1",
600: "#4f46e5",
700: "#4338ca",
800: "#3730a3",
900: "#312e81",
},
frost: {
50: "#f5f7fa",
100: "#eaeef4",
200: "#cfd9e8",
300: "#a6bad3",
400: "#7594bb",
500: "#5e81ac",
600: "#415f88",
700: "#364d6e",
800: "#2f425d",
900: "#2b394f",
950: "#1d2534",
},
marine: {
50: "#E9E9F4",
100: "#859AC7",
200: "#586F9E",
300: "#4A5F8A",
400: "#394A6D",
500: "#323F5B",
},
surface: "rgb(var(--color-surface) / <alpha-value>)",
"surface-secondary":
"rgb(var(--color-surface-secondary) / <alpha-value>)",
"surface-hover": "rgb(var(--color-surface-hover) / <alpha-value>)",
"surface-disabled": "rgb(var(--color-surface-disabled) / <alpha-value>)",
"surface-selected": "rgb(var(--color-surface-selected) / <alpha-value>)",
primary: "rgb(var(--color-text-primary) / <alpha-value>)",
secondary: "rgb(var(--color-text-secondary) / <alpha-value>)",
tertiary: "rgb(var(--color-text-tertiary) / <alpha-value>)",
disabled: "rgb(var(--color-text-disabled) / <alpha-value>)",
"surface-inverse": "rgb(var(--color-surface-inverse) / <alpha-value>)",
"surface-secondary-inverse":
"rgb(var(--color-surface-secondary-inverse) / <alpha-value>)",
"surface-hover-inverse":
"rgb(var(--color-surface-hover-inverse) / <alpha-value>)",
"surface-disabled-inverse":
"rgb(var(--color-surface-disabled-inverse) / <alpha-value>)",
"surface-selected-inverse":
"rgb(var(--color-surface-selected-inverse) / <alpha-value>)",
"primary-inverse":
"rgb(var(--color-text-primary-inverse) / <alpha-value>)",
"secondary-inverse":
"rgb(var(--color-text-secondary-inverse) / <alpha-value>)",
"tertiary-inverse":
"rgb(var(--color-text-tertiary-inverse) / <alpha-value>)",
"disabled-inverse":
"rgb(var(--color-text-disabled-inverse) / <alpha-value>)",
},
fontFamily: {
// add double quotes if there is space in font name
main: ["Inter", "sans-serif"],
mono: [
"ui-monospace",
"SFMono-Regular",
"Menlo",
"Monaco",
"Consolas",
'"Liberation Mono"',
'"Courier New"',
"monospace",
],
},
extend: {
border: {
color: "red",
},
borderRadius: {
component: "0.250rem",
},
maxHeight: {
"1/2": "50vh",
"2/3": "66vh",
"3/4": "75vh",
},
minWidth: {
"1/4": "25%",
"1/3": "33%",
"1/2": "50%",
"2/3": "66%",
"3/4": "75%",
},
minHeight: {
"1/2": "50vh",
},
height: {
"2/3": "66vh",
},
transitionProperty: {
height: "height",
},
fontSize: {
"2xs": "0.7rem",
},
screens: {
fhd: "1900px",
qhd: "2500px",
"4k": "3800px",
},
animation: {
"spin-counter-clockwise": "spin-counter-clockwise 1s linear infinite",
},
keyframes: {
"spin-counter-clockwise": {
to: { transform: "rotate(-360deg)" },
},
},
},
},
plugins: [
require("@tailwindcss/forms"),
require("@tailwindcss/typography"),
// scopedPreflightStyles({
// isolationStrategy: isolateInsideOfContainer("#windmill-app"),
// }),
plugin(({ addBase, addComponents, addUtilities, theme }) => {
addBase({
fontFamily: theme("fontFamily.main"),
fontSize: theme("fontSize.base"),
fontWeight: theme("fontWeight.normal"),
backgroundColor: "rgb(var(--color-surface) / <alpha-value>)",
color: lightTheme.textPrimary,
"--color-surface": lightThemeRgb.surface,
"--color-surface-secondary": lightThemeRgb.surfaceSecondary,
"--color-surface-hover": lightThemeRgb.surfaceHover,
"--color-surface-disabled": lightThemeRgb.surfaceDisabled,
"--color-surface-selected": lightThemeRgb.surfaceSelected,
"--color-text-primary": lightThemeRgb.textPrimary,
"--color-text-secondary": lightThemeRgb.textSecondary,
"--color-text-tertiary": lightThemeRgb.textTertiary,
"--color-text-disabled": lightThemeRgb.textDisabled,
"--color-surface-inverse": darkThemeRgb.surface,
"--color-surface-secondary-inverse": darkThemeRgb.surfaceSecondary,
"--color-surface-hover-inverse": darkThemeRgb.surfaceHover,
"--color-surface-disabled-inverse": darkThemeRgb.surfaceDisabled,
"--color-surface-selected-inverse": darkThemeRgb.surfaceSelected,
"--color-text-primary-inverse": darkThemeRgb.textPrimary,
"--color-text-secondary-inverse": darkThemeRgb.textSecondary,
"--color-text-tertiary-inverse": darkThemeRgb.textTertiary,
"--color-text-disabled-inverse": darkThemeRgb.textDisabled,
"--color-border": lightThemeRgb.border,
"--color-border-hover": lightThemeRgb.borderHover,
"--vscode-editorSuggestWidget-background": "#f3f3f3",
"--vscode-editorHoverWidget-foreground": "#616161",
"--vscode-editorHoverWidget-border": "#c8c8c8",
"--vscode-editorHoverWidget-statusBarBackground": "#e7e7e7",
"--vscode-editorSuggestWidget-foreground": "#eeffff",
"--vscode-editorSuggestWidget-highlightForeground": "#80cbc4",
"--vscode-editorSuggestWidget-selectedBackground":
"rgba(0, 0, 0, 0.31)",
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: theme("fontSize.lg"),
},
"&.dark": {
backgroundColor: darkTheme.surface,
color: darkTheme.textPrimary,
"--color-surface": darkThemeRgb.surface,
"--color-surface-secondary": darkThemeRgb.surfaceSecondary,
"--color-surface-hover": darkThemeRgb.surfaceHover,
"--color-surface-disabled": darkThemeRgb.surfaceDisabled,
"--color-surface-selected": darkThemeRgb.surfaceSelected,
"--color-text-primary": darkThemeRgb.textPrimary,
"--color-text-secondary": darkThemeRgb.textSecondary,
"--color-text-tertiary": darkThemeRgb.textTertiary,
"--color-text-disabled": darkThemeRgb.textDisabled,
"--color-surface-inverse": lightThemeRgb.surface,
"--color-surface-secondary-inverse": lightThemeRgb.surfaceSecondary,
"--color-surface-hover-inverse": lightThemeRgb.surfaceHover,
"--color-surface-disabled-inverse": lightThemeRgb.surfaceDisabled,
"--color-surface-selected-inverse": lightThemeRgb.surfaceSelected,
"--color-text-primary-inverse": lightThemeRgb.textPrimary,
"--color-text-secondary-inverse": lightThemeRgb.textSecondary,
"--color-text-tertiary-inverse": lightThemeRgb.textTertiary,
"--color-text-disabled-inverse": lightThemeRgb.textDisabled,
"--color-border": darkThemeRgb.border,
"--color-border-hover": darkThemeRgb.borderHover,
"--vscode-editorSuggestWidget-background": "#252526",
"--vscode-editorHoverWidget-foreground": "#cccccc",
"--vscode-editorHoverWidget-border": "#454545",
"--vscode-editorHoverWidget-statusBarBackground": "#2c2c2d",
},
h1: {
fontSize: "24px",
fontWeight: theme("fontWeight.extrabold"),
lineHeight: "1.05",
[`@media (min-width: ${theme("screens.lg")})`]: {
fontSize: "26px",
},
[`@media (min-width: ${theme("screens.fhd")})`]: {
fontSize: "29px",
},
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "34px",
},
},
h2: {
fontSize: "20px",
fontWeight: theme("fontWeight.extrabold"),
lineHeight: "1.1",
[`@media (min-width: ${theme("screens.fhd")})`]: {
fontSize: "22px",
},
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "25px",
},
},
mark: {
backgroundColor: theme("colors.yellow.200"),
borderRadius: theme("borderRadius.sm"),
},
h3: {
fontSize: "18px",
fontWeight: theme("fontWeight.bold"),
lineHeight: "1.2",
[`@media (min-width: ${theme("screens.fhd")})`]: {
fontSize: "20px",
},
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "22px",
},
},
h4: {
fontSize: "18px",
fontWeight: theme("fontWeight.semibold"),
lineHeight: "1.3",
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "20px",
},
},
h5: {
fontSize: "16px",
fontWeight: theme("fontWeight.semibold"),
lineHeight: "1.5",
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "18px",
},
},
h6: {
fontSize: "16px",
fontWeight: theme("fontWeight.medium"),
lineHeight: "1.5",
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "18px",
},
},
button: {
fontWeight: theme("fontWeight.semibold"),
},
a: {
color: theme("colors.blue.500"),
},
".dark input::placeholder": {
color: theme("colors.gray.400"),
},
"[type='checkbox']:checked": {
backgroundImage: `url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");`,
},
".dark [type='checkbox']:checked": {
backgroundImage: `url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");`,
},
'input:not(.windmillapp),input[type="text"]:not(.windmillapp),input[type="email"]:not(.windmillapp),input[type="url"]:not(.windmillapp),input[type="password"]:not(.windmillapp),input[type="number"]:not(.windmillapp),input[type="date"]:not(.windmillapp),input[type="datetime-local"]:not(.windmillapp),input[type="month"]:not(.windmillapp),input[type="search"]:not(.windmillapp),input[type="tel"]:not(.windmillapp),input[type="time"]:not(.windmillapp),input[type="week"]:not(.windmillapp),textarea:not(.windmillapp):not(.monaco-mouse-cursor-text),select:not(.windmillapp)':
{
display: "block",
fontSize: theme("fontSize.sm"),
boxShadow: theme("boxShadow.sm"),
width: "100%",
padding: `${theme("spacing.1")} ${theme("spacing.2")}`,
border: `1px solid ${theme("colors.gray.300")}`,
borderRadius: theme("borderRadius.md"),
"&:focus": {
"--tw-ring-color": theme("colors.frost.100"),
"--tw-ring-offset-shadow":
"var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)",
"--tw-ring-shadow":
"var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)",
boxShadow:
"var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)",
},
"&:disabled,[disabled]": {
backgroundColor: theme("colors.gray.100") + " !important",
".dark &": {
backgroundColor: theme("colors.gray.700") + " !important",
},
},
},
'.dark input:not(.windmillapp),.dark input[type="text"]:not(.windmillapp),.dark input[type="email"]:not(.windmillapp),.dark input[type="url"]:not(.windmillapp),.dark input[type="password"]:not(.windmillapp),.dark input[type="number"]:not(.windmillapp),.dark input[type="date"]:not(.windmillapp),.dark input[type="datetime-local"]:not(.windmillapp),.dark input[type="month"]:not(.windmillapp),.dark input[type="search"]:not(.windmillapp),.dark input[type="tel"]:not(.windmillapp),.dark input[type="time"]:not(.windmillapp),.dark input[type="week"]:not(.windmillapp),.dark textarea:not(.windmillapp):not(.monaco-mouse-cursor-text),.dark select:not(.windmillapp)':
{
backgroundColor: theme("colors.gray.700"),
color: theme("colors.gray.200"),
borderColor: theme("colors.gray.600"),
"&:focus": {
"--tw-ring-color": theme("colors.frost.700"),
},
},
"button:disabled,button[disabled=true],a:disabled,a[disabled=true]": {
pointerEvents: "none",
cursor: "default",
opacity: "0.90",
},
"pre code.hljs": {
padding: "0px !important",
fontFamily: theme("fontFamily.mono"),
fontSize: theme("fontSize.xs") + " !important",
lineHeight: theme("lineHeight.4") + " !important",
},
".h1-textarea": {
fontSize: "24px !important",
fontWeight: `${theme("fontWeight.extrabold")} !important`,
lineHeight: "1.05 !important",
color: theme("!colors.gray.800"),
[`@media (min-width: ${theme("screens.lg")})`]: {
fontSize: "26px !important",
},
[`@media (min-width: ${theme("screens.fhd")})`]: {
fontSize: "29px !important",
},
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "34px !important",
},
},
".h3-textarea": {
fontSize: "18px !important",
fontWeight: `${theme("fontWeight.bold")} !important`,
lineHeight: "1.2 !important",
color: theme("!colors.gray.600"),
[`@media (min-width: ${theme("screens.fhd")})`]: {
fontSize: "20px !important",
},
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "22px !important",
},
},
".p-textarea": {
fontSize: "16px !important",
fontWeight: `${theme("fontWeight.normal")} !important`,
lineHeight: "1.5 !important",
color: theme("!colors.gray.600"),
[`@media (min-width: ${theme("screens.fhd")})`]: {
fontSize: "18px !important",
},
[`@media (min-width: ${theme("screens.qhd")})`]: {
fontSize: "20px !important",
},
},
input: {
backgroundColor: theme("colors.surface-secondary") + " !important",
},
textarea: {
backgroundColor: theme("colors.surface-secondary") + " !important",
},
});
addComponents({
".table-custom": {
"& th": {
paddingTop: theme("spacing.3"),
paddingRight: theme("spacing.1"),
paddingLeft: theme("spacing.1"),
paddingBottom: theme("spacing.3"),
fontSize: theme("fontSize.sm"),
textAlign: "left",
fontWeight: theme("fontWeight.semibold"),
color: theme("colors.gray.900"),
textTransform: "capitalize",
},
".dark & th": {
color: theme("colors.gray.200"),
},
"& td": {
paddingLeft: theme("spacing.1"),
paddingRight: theme("spacing.1"),
paddingTop: theme("spacing.2"),
paddingBottom: theme("spacing.2"),
fontSize: theme("fontSize.sm"),
color: theme("colors.gray.700"),
},
".dark & td ": {
color: theme("colors.gray.200"),
},
"& tbody > :not([hidden]) ~ :not([hidden])": {
borderTop: `1px solid ${theme("colors.gray.200")}`,
},
".dark & tbody > :not([hidden]) ~ :not([hidden])": {
borderTop: `1px solid ${theme("colors.gray.700")}`,
},
"& tbody > tr:hover": {
backgroundColor: theme("colors.gray.50"),
},
".dark & tbody > tr:hover": {
backgroundColor: theme("colors.gray.800"),
},
},
".commit-hash": {
fontSize: theme("fontSize.2xs"),
color: theme("colors.gray.500"),
backgroundColor: theme("colors.gray.200"),
fontFamily: theme("fontFamily.mono"),
},
".input-error": {
borderColor: `${theme("colors.red.500")} !important`,
},
".box": {
borderWidth: "1px",
borderRadius: theme("borderRadius.sm"),
boxShadow: theme("boxShadow.sm"),
padding: theme("spacing.4"),
},
".animate-skeleton": {
animation: theme("animation.pulse"),
backgroundColor: theme("colors.blue.100"),
borderRadius: theme("borderRadius.DEFAULT"),
},
".text-black-gradient": {
color: "transparent",
backgroundClip: "text",
backgroundImage: `linear-gradient(to right, ${theme(
"colors.black"
)}, ${theme("colors.gray.600")})`,
},
".splitpanes__pane": {
backgroundColor: lightTheme.surface + " !important",
overflow: "auto !important",
},
".dark .splitpanes__pane": {
backgroundColor: darkTheme.surface + " !important",
overflow: "auto !important",
},
".splitpanes__splitter": {
backgroundColor: lightTheme.border + " !important",
margin: "0 !important",
border: "none !important",
"&::after": {
backgroundColor: lightTheme.border + " !important",
margin: "0 !important",
transform: "none !important",
transition: "opacity 200ms !important",
opacity: "0",
"--splitter-hover-size": "5px",
"--splitter-hover-adjustment": "-2px",
},
"&:hover::after": {
opacity: "1",
zIndex: "1001 !important",
},
},
".dark .splitpanes__splitter": {
backgroundColor: darkTheme.border + " !important",
"&::after": {
backgroundColor: darkTheme.border + " !important",
},
},
".splitpanes--vertical>.splitpanes__splitter": {
width: "1px !important",
"&::before": {
left: "1px !important",
width: "0px !important",
marginLeft: "0 !important",
},
"&::after": {
top: "0 !important",
height: "100% !important",
left: "var(--splitter-hover-adjustment) !important",
width: "var(--splitter-hover-size) !important",
},
},
".splitpanes--horizontal>.splitpanes__splitter": {
height: "1px !important",
"&::before": {
top: "1px !important",
height: "0px !important",
marginTop: "0 !important",
},
"&::after": {
top: "var(--splitter-hover-adjustment) !important",
height: "var(--splitter-hover-size) !important",
left: "0 !important",
width: "100% !important",
},
},
// Windmill Tab classes
".wm-tab-active": {
borderColor: darkTheme.border,
color: lightTheme.textPrimary,
},
".dark .wm-tab-active": {
borderColor: lightTheme.border,
color: darkTheme.textPrimary,
},
});
addUtilities({
".separator": {
backgroundColor: `${lightTheme.border} !important`,
},
".dark .separator": {
backgroundColor: `${darkTheme.border} !important`,
},
".center-center": {
display: "flex",
justifyContent: "center",
alignItems: "center",
},
".inner-border": {
boxShadow: `inset 0 0 0 1px ${lightTheme.border}`,
},
".dark .inner-border": {
boxShadow: `inset 0 0 0 1px ${darkTheme.border}`,
},
".z5000": {
zIndex: "5000 !important",
},
".ellipsize": {
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
},
/** Set the '-webkit-line-clamp' property to the desired number of lines.
*
* Eg.: `class="ellipsize-multi-line [-webkit-line-clamp:3]"`
*/
".ellipsize-multi-line": {
display: "-webkit-box",
"-webkit-box-orient": "vertical",
overflow: "hidden",
},
".disabled": {
pointerEvents: "none",
cursor: "default",
filter: "grayscale(1)",
},
".scrollbar-hidden": {
"-ms-overflow-style": "none",
"scrollbar-width": "none",
"&::-webkit-scrollbar": {
display: "none",
width: "0px",
},
},
});
}),
],
darkMode: "class",
important: ".windmill-app",
};
module.exports = config;

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT flow_status FROM completed_job WHERE id = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "flow_status",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
true
]
},
"hash": "061ff848f258dc880bec81d923370c905e689f37c6d931ee4559c3cfd394e168"
}

View File

@@ -55,7 +55,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069"
}

View File

@@ -112,6 +112,11 @@
"ordinal": 21,
"name": "default_scripts",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "deploy_ui",
"type_info": "Jsonb"
}
],
"parameters": {
@@ -141,6 +146,7 @@
true,
true,
false,
true,
true
]
},

View File

@@ -49,7 +49,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)",
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
"describe": {
"columns": [],
"parameters": {
@@ -11,10 +11,11 @@
"Bool",
"Varchar",
"Int4",
"Bool"
"Bool",
"Timestamptz"
]
},
"nullable": []
},
"hash": "2e4115bb2e6c8c85ad1492ad135d6b0454b342126cb5fa17e58caf71b32ee755"
"hash": "2be66f23536223549db9b50025932b6b1bad90b8fa47d97acb7d75aa3c37ef86"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')",
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
"describe": {
"columns": [
{
@@ -10,11 +10,13 @@
}
],
"parameters": {
"Left": []
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "2ae96e8af2dddd533a0377983ec36e30091089d0a96a37d8c625ddaf85d16a83"
"hash": "51a7e36ba1cc1616a19485eb5930cf90df758e85a8cac6037d044d5bd7440681"
}

View File

@@ -112,6 +112,11 @@
"ordinal": 21,
"name": "default_scripts",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "deploy_ui",
"type_info": "Jsonb"
}
],
"parameters": {
@@ -141,6 +146,7 @@
true,
true,
false,
true,
true
]
},

View File

@@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "57d830e07e40317150b24cf78248480c3a8b33e3e240385cae5703ccb9b14bec"
}

View File

@@ -69,7 +69,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -5,7 +5,7 @@
"columns": [
{
"ordinal": 0,
"name": "?column?",
"name": "bool",
"type_info": "Bool"
}
],

View File

@@ -54,7 +54,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -35,7 +35,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT created_at FROM script WHERE workspace_id = $1 AND hash = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Int8"
]
},
"nullable": [
false
]
},
"hash": "8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b"
}

View File

@@ -1,20 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT MAX(created_at) FROM completed_job",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "max",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47"
}

View File

@@ -54,7 +54,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -49,7 +49,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT worker, worker_instance, vcpus, ping_at, started_at FROM worker_ping WHERE ping_at > now() - interval '30 days'",
"query": "SELECT worker, worker_instance, vcpus, memory, ping_at, started_at FROM worker_ping WHERE ping_at > now() - interval '30 days' ORDER BY started_at",
"describe": {
"columns": [
{
@@ -20,11 +20,16 @@
},
{
"ordinal": 3,
"name": "memory",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "ping_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "started_at",
"type_info": "Timestamptz"
}
@@ -36,9 +41,10 @@
false,
false,
true,
true,
false,
false
]
},
"hash": "8480792eefdd0f31a49afec0f91262a6973c068da5413b154eff9b9da90db95f"
"hash": "a51007ca7b509b92faa6fe7aa59fa738594a2b7eaab8e38f3ca30e548f9a49a7"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26"
}

View File

@@ -1,26 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_user_count as (SELECT count(email)::INT as count from active_users),\n active_author_count as (SELECT count(distinct email)::INT as count FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users))\n SELECT active_author_count.count as author_count, active_user_count.count - active_author_count.count as operator_count FROM active_user_count, active_author_count",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "author_count",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "operator_count",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null
]
},
"hash": "ad8d9c00bd2d3515ad4afa87aa24b243615484bbad5a793438e23908c3f20c2f"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM variable WHERE expires_at IS NOT NULL AND expires_at > now() RETURNING path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "bac12eff366f6b76f97d1a44041e0c1b5fdcb2c4205c1c1c0026b71ce404f089"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Text"
]
},
"nullable": []
},
"hash": "c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5"
}

View File

@@ -1,30 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT leaf_jobs->$1::text as leaf_jobs, parent_job FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "leaf_jobs",
"type_info": "Jsonb"
},
{
"ordinal": 1,
"name": "parent_job",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text"
]
},
"nullable": [
null,
true
]
},
"hash": "c8ac658002423906f2a6e43e423a9e24561dfaa6b07ce89063d0910d2342a83b"
}

View File

@@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "authors",
"type_info": "VarcharArray"
},
{
"ordinal": 1,
"name": "operators",
"type_info": "VarcharArray"
},
{
"ordinal": 2,
"name": "author_count",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "operator_count",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c"
}

View File

@@ -75,7 +75,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -54,7 +54,8 @@
"graphql",
"powershell",
"mssql",
"php"
"php",
"bunnative"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM queue WHERE created_at <= $1",
"query": "SELECT id FROM queue",
"describe": {
"columns": [
{
@@ -10,13 +10,11 @@
}
],
"parameters": {
"Left": [
"Timestamptz"
]
"Left": []
},
"nullable": [
false
]
},
"hash": "8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890"
"hash": "e207bbcdd478758562e3082330836d6efe1dd6c4f719bc415222eba78d5e63fc"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
"query": "UPDATE script SET lock = $1, created_at = now() WHERE hash = $2 AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
@@ -12,5 +12,5 @@
},
"nullable": []
},
"hash": "d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8"
"hash": "ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT CONCAT(coalesce(completed_job.logs, ''), coalesce(job_logs.logs, '')) as logs FROM completed_job\n LEFT JOIN job_logs ON job_logs.job_id = completed_job.id\n WHERE completed_job.id = $1 AND completed_job.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "logs",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
null
]
},
"hash": "f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9"
}

View File

@@ -0,0 +1,41 @@
{
"db_name": "PostgreSQL",
"query": "SELECT created_by, CONCAT(coalesce(completed_job.logs, ''), coalesce(job_logs.logs, '')) as logs, job_logs.log_offset, job_logs.log_file_index\n FROM completed_job\n LEFT JOIN job_logs ON job_logs.job_id = completed_job.id\n WHERE completed_job.id = $1 AND completed_job.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "logs",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "log_offset",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "log_file_index",
"type_info": "TextArray"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
false,
null,
false,
true
]
},
"hash": "f04fa0262091c5a4abf7dddafeec1b34ed806dda70592b0030067aed46a104d9"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false\n GROUP BY script_path), $3) as min_started_at, now() AS now",
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND concurrent_limit > 0), $3) as min_started_at, now() AS now",
"describe": {
"columns": [
{
@@ -26,5 +26,5 @@
null
]
},
"hash": "3901cce744c9b246b661c817e068bdb3b1ab504ff8070fcccf6c909ad75f1f6d"
"hash": "f3d20b0fa17836538ec93b84b08ffc6b555371d6eeeadfc54f5bf0bcbe93d8b4"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE metrics SET value = $1 WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb"
]
},
"nullable": []
},
"hash": "f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225"
}

1155
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.366.2"
version = "1.377.1"
authors.workspace = true
edition.workspace = true
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "1.366.2"
version = "1.377.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -39,7 +39,7 @@ incremental = true
[features]
default = []
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-indexer/enterprise"]
enterprise_saml = ["windmill-api/enterprise_saml"]
stripe = ["windmill-api/stripe"]
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark"]
@@ -47,13 +47,14 @@ flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
loki = ["windmill-common/loki"]
pg_embed = ["dep:pg-embed"]
embedding = ["windmill-api/embedding"]
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet"]
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "windmill-indexer/parquet"]
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus"]
flow_testing = ["windmill-worker/flow_testing"]
openidconnect = ["windmill-api/openidconnect"]
cloud = ["windmill-queue/cloud", "windmill-worker/cloud"]
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
tantivy = ["windmill-indexer/tantivy"]
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy"]
sqlx = ["windmill-worker/sqlx"]
[dependencies]
anyhow.workspace = true
@@ -64,7 +65,7 @@ windmill-common = { workspace = true, default-features = false }
windmill-git-sync.workspace = true
windmill-api = { workspace = true, default-features = false }
windmill-worker.workspace = true
windmill-indexer.workspace = true
windmill-indexer = { workspace = true, optional = true }
futures.workspace = true
tracing.workspace = true
sqlx.workspace = true
@@ -155,7 +156,7 @@ mail-send = { version = "0.4.0", features = ["builder"], default-features=false
urlencoding = "^2"
url = "^2"
async-oauth2 = "^0"
reqwest = { version = "^0.12", features = ["json", "stream"] }
reqwest = { version = "^0.12", features = ["json", "stream", "gzip"] }
time = "0.3.16"
serde_urlencoded = "^0"
tokio-tar = "^0"
@@ -164,24 +165,25 @@ tokio-util = { version = "^0", features = ["io"] }
json-pointer = "^0"
itertools = "^0"
regex = "^1"
deno_fetch = "0.182.0"
deno_tls = "0.145.0"
deno_console = "0.158.0"
deno_url = "0.158.0"
deno_webidl = "0.158.0"
deno_web = "0.189.0"
deno_net = "0.150.0"
deno_core = "0.290.0"
deno_ast = { version = "=0.39.2", features = ["transpiling"] }
deno_fetch = "0.187.0"
deno_tls = "0.150.0"
deno_console = "0.163.0"
deno_url = "0.163.0"
deno_webidl = "0.163.0"
deno_web = "0.194.0"
deno_net = "0.155.0"
deno_core = "0.299.0"
deno_ast = { version = "=0.40.0", features = ["transpiling"] }
async-recursion = "^1"
swc_common = "=0.33.26"
swc_ecma_parser = "=0.144.3"
swc_ecma_ast = "=0.113.7"
swc_ecma_visit = "=0.99.1"
base64 = "0.21.0"
base32 = "^0"
hmac = "0.12.1"
sha2 = "0.10.6"
sqlx = { version = "0.7.3", features = [
sqlx = { version = "0.8.0", features = [
"macros",
"migrate",
"uuid",
@@ -240,6 +242,9 @@ candle-nn = "0.3.0"
tiberius = { git = "https://github.com/prisma/tiberius", rev = "8f66a699dfa041e7b5f736c7e94f92c945453c9e", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]}
pin-project = "1"
indexmap = { version = "2.2.5", features = ["serde"]}
tokio-native-tls = "^0"
openssl = "=0.10"
mail-parser = "^0"
datafusion = "39.0.0"
object_store = { version = "0.10.0", features = ["aws", "azure"] }

View File

@@ -1 +1 @@
75e2aa1b2a8cff3cdcd64bab33eadab036e0c3fe
80d9e507977b55d0b76272905c969c3d6e830083

View File

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

View File

@@ -0,0 +1 @@
CREATE INDEX IF NOT EXISTS queue_concurrency_started_at ON queue (workspace_id, script_path, started_at) WHERE concurrent_limit > 0 AND running = true AND job_kind != 'dependencies' AND canceled = false;-- Add up migration script here

View File

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

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'bunnative';

View File

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

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
alter table queue drop constraint if exists queue_workspace_id_fkey;
alter table completed_job drop constraint if exists completed_job_workspace_id_fkey;

View File

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

View File

@@ -0,0 +1,4 @@
-- Add up migration script here
DROP INDEX IF EXISTS queue_sort;
CREATE INDEX IF NOT EXISTS queue_sort ON queue (running, tag, priority DESC NULLS LAST, scheduled_for, created_at) WHERE running = false;
CREATE INDEX IF NOT EXISTS queue_sort_2 ON queue (running, priority DESC NULLS LAST, scheduled_for, created_at) WHERE running = false;

View File

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

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE variable ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP WITH TIME ZONE;
UPDATE variable SET expires_at = (now() + '7 days'::interval) WHERE path LIKE 'u/%/secret_arg/%'

View File

@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE workspace_settings DROP COLUMN deploy_ui;

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS deploy_ui JSONB;

View File

@@ -58,6 +58,7 @@ fn parse_bash_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: default.clone().map(|x| json!(x)),
otyp: None,
has_default: default.is_some(),
oidx: None,
});
} else {
break;
@@ -94,6 +95,7 @@ fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: default.clone(),
otyp: None,
has_default: default.is_some(),
oidx: None,
});
}
}
@@ -130,35 +132,40 @@ non_required="${5:-}"
name: "token".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "image".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "digest".to_string(),
typ: Typ::Str(None),
default: Some(json!("latest with spaces")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "text".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "non_required".to_string(),
typ: Typ::Str(None),
default: Some(json!("")),
has_default: true
has_default: true,
oidx: None
}
],
no_main_func: None

View File

@@ -27,7 +27,14 @@ pub fn parse_go_sig(code: &str) -> anyhow::Result<MainArgSignature> {
.iter()
.map(|param| {
let (otyp, typ) = parse_go_typ(&param.typ);
Arg { name: get_name(param), otyp, typ, default: None, has_default: false }
Arg {
name: get_name(param),
otyp,
typ,
default: None,
has_default: false,
oidx: None,
}
})
.collect_vec();
Ok(MainArgSignature {
@@ -179,28 +186,32 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
name: "x".to_string(),
typ: Typ::Int,
has_default: false,
default: None
default: None,
oidx: None
},
Arg {
otyp: Some("string".to_string()),
name: "y".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: Some("bool".to_string()),
name: "z".to_string(),
typ: Typ::Bool,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: Some("[]string".to_string()),
name: "l".to_string(),
typ: Typ::List(Box::new(Typ::Str(None))),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: Some("struct { Name string `json:\"name\"` }".to_string()),
@@ -210,21 +221,24 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam
typ: Box::new(Typ::Str(None))
},]),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: Some("interface{}".to_string()),
name: "n".to_string(),
typ: Typ::Object(vec![]),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: Some("map[string]interface{}".to_string()),
name: "m".to_string(),
typ: Typ::Object(vec![]),
default: None,
has_default: false
has_default: false,
oidx: None
},
],
no_main_func: Some(false)

View File

@@ -51,6 +51,7 @@ fn parse_graphql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: parsed_default,
otyp: Some(typ.unwrap()),
has_default,
oidx: None,
});
}
@@ -93,21 +94,24 @@ query($i: Int, $arr: [String]!, $wahoo: String = "wahoo") {
name: "i".to_string(),
typ: Typ::Int,
default: None,
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: Some("[String]".to_string()),
name: "arr".to_string(),
typ: Typ::List(Box::new(Typ::Str(None))),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: Some("String".to_string()),
name: "wahoo".to_string(),
typ: Typ::Str(None),
default: Some(json!("wahoo")),
has_default: true
has_default: true,
oidx: None
}
],
no_main_func: None

View File

@@ -85,6 +85,7 @@ pub fn parse_php_signature(
typ,
has_default: default.is_some(),
default,
oidx: None,
}
})
.collect();
@@ -135,35 +136,40 @@ function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, f
name: "input1".to_string(),
typ: Typ::Str(None),
has_default: true,
default: Some(Value::String("hey".to_string()))
default: Some(Value::String("hey".to_string())),
oidx: None
},
Arg {
otyp: None,
name: "input2".to_string(),
typ: Typ::Bool,
has_default: true,
default: Some(Value::Bool(false))
default: Some(Value::Bool(false)),
oidx: None
},
Arg {
otyp: None,
name: "input3".to_string(),
typ: Typ::Int,
has_default: true,
default: Some(Value::Number(Number::from(3)))
default: Some(Value::Number(Number::from(3))),
oidx: None
},
Arg {
otyp: None,
name: "input4".to_string(),
typ: Typ::Float,
has_default: true,
default: Some(Value::Number(Number::from_f64(f64::from(4.5)).unwrap()))
default: Some(Value::Number(Number::from_f64(f64::from(4.5)).unwrap())),
oidx: None
},
Arg {
otyp: None,
name: "resource".to_string(),
typ: Typ::Resource("stripe".to_string()),
has_default: false,
default: None
default: None,
oidx: None
}
],
no_main_func: Some(false)

View File

@@ -114,6 +114,7 @@ pub fn parse_python_signature(
typ,
has_default: default.is_some(),
default,
oidx: None,
}
})
.collect(),
@@ -271,49 +272,56 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
name: "test1".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "name".to_string(),
typ: Typ::Unknown,
default: Some(json!("<function call>")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "byte".to_string(),
typ: Typ::Bytes,
default: Some(json!("<function call>")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "f".to_string(),
typ: Typ::Str(None),
default: Some(json!("wewe")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "g".to_string(),
typ: Typ::Int,
default: Some(json!(21)),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "h".to_string(),
typ: Typ::List(Box::new(Typ::Int)),
default: Some(json!([1, 2])),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "i".to_string(),
typ: Typ::Bool,
default: Some(json!(true)),
has_default: true
has_default: true,
oidx: None
},
],
no_main_func: Some(false),
@@ -352,28 +360,32 @@ def main(test1: str,
name: "test1".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "name".to_string(),
typ: Typ::Unknown,
default: Some(json!("<function call>")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "byte".to_string(),
typ: Typ::Bytes,
default: Some(json!("<function call>")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "resource".to_string(),
typ: Typ::Resource("postgresql".to_string()),
default: Some(json!("$res:g/all/resource")),
has_default: true
has_default: true,
oidx: None
}
],
no_main_func: Some(false),
@@ -407,28 +419,32 @@ def main(test1: str,
name: "test1".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "s3o".to_string(),
typ: Typ::Resource("S3Object".to_string()),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "name".to_string(),
typ: Typ::Str(None),
default: Some(json!("test")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "byte".to_string(),
typ: Typ::Bytes,
default: Some(json!("<function call>")),
has_default: true
has_default: true,
oidx: None
}
],
no_main_func: Some(false),
@@ -459,7 +475,8 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
name: "test1".to_string(),
typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
@@ -469,7 +486,8 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu
"bar".to_string()
])))),
default: None,
has_default: false
has_default: false,
oidx: None
}
],
no_main_func: Some(false),
@@ -499,7 +517,8 @@ def main(test1: DynSelect_foo): return
name: "test1".to_string(),
typ: Typ::DynSelect("foo".to_string()),
default: None,
has_default: false
has_default: false,
oidx: None
}],
no_main_func: Some(false),
}

View File

@@ -4,7 +4,11 @@ use anyhow::anyhow;
use regex::Regex;
use serde_json::json;
use std::collections::HashMap;
use std::{
collections::{HashMap, HashSet},
iter::Peekable,
str::CharIndices,
};
pub use windmill_parser::{Arg, MainArgSignature, Typ};
pub fn parse_mysql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
@@ -62,9 +66,32 @@ pub fn parse_db_resource(code: &str) -> Option<String> {
cap.map(|x| x.get(1).map(|x| x.as_str().to_string()).unwrap())
}
pub fn parse_sql_blocks(code: &str) -> Vec<&str> {
let mut blocks = vec![];
let mut last_idx = 0;
run_on_sql_statement_matches(
code,
|char, _| char == ';',
|idx, _| {
blocks.push(&code[last_idx..=idx]);
last_idx = idx + 1;
},
);
if last_idx < code.len() {
let last_block = &code[last_idx..];
if RE_NONEMPTY_SQL_BLOCK.is_match(last_block) {
blocks.push(last_block);
}
}
blocks
}
lazy_static::lazy_static! {
static ref RE_CODE_PGSQL: Regex = Regex::new(r#"(?m)\$(\d+)(?:::(\w+(?:\[\])?))?"#).unwrap();
static ref RE_NONEMPTY_SQL_BLOCK: Regex = Regex::new(r#"(?m)^\s*[^\s](?:[^-]|$)"#).unwrap();
static ref RE_DB: Regex = Regex::new(r#"(?m)^-- database (\S+) *(?:\r|\n|$)"#).unwrap();
// -- $1 name (type) = default
@@ -117,6 +144,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: parsed_default,
otyp: Some(typ),
has_default,
oidx: None,
});
}
@@ -140,6 +168,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: parsed_default,
otyp: Some(typ),
has_default,
oidx: None,
});
}
}
@@ -147,7 +176,105 @@ fn parse_mysql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
Ok(Some(args))
}
enum ParserState {
Normal,
InSingleQuote,
InDoubleQuote,
InSingleLineComment,
InMultiLineComment,
}
fn run_on_sql_statement_matches<
F1: FnMut(char, &mut Peekable<CharIndices>) -> bool,
F2: FnMut(usize, &mut Peekable<CharIndices>) -> (),
>(
code: &str,
mut cond: F1,
mut case: F2,
) {
let mut chars = code.char_indices().peekable();
let mut state = ParserState::Normal;
while let Some((idx, char)) = chars.next() {
match (&state, char) {
(ParserState::Normal, '\'') => {
state = ParserState::InSingleQuote;
}
(ParserState::Normal, '"') => {
state = ParserState::InDoubleQuote;
}
(ParserState::Normal, '-')
if chars.peek().is_some_and(|&(_, next_char)| next_char == '-') =>
{
state = ParserState::InSingleLineComment;
}
(ParserState::Normal, '/')
if chars.peek().is_some_and(|&(_, next_char)| next_char == '*') =>
{
state = ParserState::InMultiLineComment;
}
(ParserState::Normal, _) if cond(char, &mut chars) => {
case(idx, &mut chars);
}
(ParserState::InSingleQuote, '\'') => {
if chars
.peek()
.is_some_and(|&(_, next_char)| next_char == '\'')
{
chars.next(); // skip the escaped single quote
} else {
state = ParserState::Normal;
}
}
(ParserState::InDoubleQuote, '"') => {
if chars.peek().is_some_and(|&(_, next_char)| next_char == '"') {
chars.next(); // skip the escaped single quote
} else {
state = ParserState::Normal;
}
}
(ParserState::InSingleLineComment, '\n') => {
state = ParserState::Normal;
}
(ParserState::InMultiLineComment, '*')
if chars.peek().is_some_and(|&(_, next_char)| next_char == '/') =>
{
state = ParserState::Normal;
}
_ => {}
}
}
}
pub fn parse_pg_statement_arg_indices(code: &str) -> HashSet<i32> {
let mut arg_indices = HashSet::new();
run_on_sql_statement_matches(
code,
|char, chars| {
char == '$'
&& chars
.peek()
.is_some_and(|&(_, next_char)| next_char.is_ascii_digit())
},
|_, chars| {
let mut arg_idx = String::new();
while let Some(&(_, char)) = chars.peek() {
if char.is_ascii_digit() {
arg_idx.push(char);
chars.next();
} else {
break;
}
}
if let Ok(arg_idx) = arg_idx.parse::<i32>() {
arg_indices.insert(arg_idx);
}
},
);
arg_indices
}
fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut args = vec![];
let mut hm: HashMap<i32, String> = HashMap::new();
for cap in RE_CODE_PGSQL.captures_iter(code) {
hm.insert(
@@ -159,39 +286,74 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
.unwrap_or_else(|| "text".to_string()),
);
}
let mut args = vec![];
for i in 1..50 {
if hm.contains_key(&i) {
let typ = hm.get(&i).unwrap().to_lowercase();
args.push(Arg {
name: format!("${}", i),
typ: parse_pg_typ(typ.as_str()),
default: None,
otyp: Some(typ),
has_default: false,
});
} else {
break;
}
for (i, v) in hm.iter() {
let typ = v.to_lowercase();
args.push(Arg {
name: format!("${}", i),
typ: parse_pg_typ(typ.as_str()),
default: None,
otyp: Some(typ),
has_default: false,
oidx: Some(*i),
});
}
args.sort_by(|a, b| a.oidx.unwrap().cmp(&b.oidx.unwrap()));
for cap in RE_ARG_PGSQL.captures_iter(code) {
let i = cap.get(1).and_then(|x| x.as_str().parse::<i32>().ok());
if i.is_none() || i.unwrap() as usize > args.len() {
continue;
}
let name = cap.get(2).map(|x| x.as_str().to_string()).unwrap();
let default = cap.get(3).map(|x| x.as_str().to_string());
let has_default = default.is_some();
let oarg = args[(i.unwrap() - 1) as usize].clone();
let parsed_default = default.and_then(|x| parsed_default(&oarg.typ, x));
let i = cap
.get(1)
.and_then(|x| x.as_str().parse::<i32>().ok())
.map(|x| x);
args[(i.unwrap() - 1) as usize] =
Arg { name, typ: oarg.typ, default: parsed_default, otyp: oarg.otyp, has_default };
if let Some(arg_pos) = args
.iter()
.position(|x| i.is_some_and(|i| x.oidx.unwrap() == i))
{
let name = cap.get(2).map(|x| x.as_str().to_string()).unwrap();
let default = cap.get(3).map(|x| x.as_str().to_string());
let has_default = default.is_some();
let oarg = args[arg_pos].clone();
let parsed_default = default.and_then(|x| parsed_default(&oarg.typ, x));
args[arg_pos] = Arg {
name,
typ: oarg.typ,
default: parsed_default,
otyp: oarg.otyp,
has_default,
oidx: oarg.oidx,
};
}
}
Ok(Some(args))
}
pub fn parse_sql_statement_named_params(code: &str, prefix: char) -> HashSet<String> {
let mut arg_names = HashSet::new();
run_on_sql_statement_matches(
code,
|char, chars| {
char == prefix
&& chars
.peek()
.is_some_and(|&(_, next_char)| next_char.is_alphanumeric() || next_char == '_')
},
|_, chars| {
let mut arg_name = String::new();
while let Some(&(_, char)) = chars.peek() {
if char.is_alphanumeric() || char == '_' {
arg_name.push(char);
chars.next();
} else {
break;
}
}
arg_names.insert(arg_name);
},
);
arg_names
}
fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut args: Vec<Arg> = vec![];
@@ -213,6 +375,7 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: parsed_default,
otyp: Some(typ),
has_default,
oidx: None,
});
}
@@ -240,6 +403,7 @@ fn parse_snowflake_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: parsed_default,
otyp: Some(typ),
has_default,
oidx: None,
});
}
@@ -267,6 +431,7 @@ fn parse_mssql_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
default: parsed_default,
otyp: Some(typ),
has_default,
oidx: None,
});
}
@@ -372,7 +537,7 @@ mod tests {
use super::*;
#[test]
fn test_parse_sql_sig() -> anyhow::Result<()> {
fn test_parse_pgsql_sig() -> anyhow::Result<()> {
let code = r#"
SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
"#;
@@ -388,14 +553,237 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
name: "$1".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: Some(1),
},
Arg {
otyp: Some("bigint".to_string()),
name: "$2".to_string(),
typ: Typ::Int,
default: None,
has_default: false
has_default: false,
oidx: Some(2),
},
],
no_main_func: None
}
);
Ok(())
}
#[test]
fn test_parse_pgsql_mutli_sig() -> anyhow::Result<()> {
let code = r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT $3::TEXT, $1::BIGINT;
SELECT $2::TEXT;
"#;
//println!("{}", serde_json::to_string()?);
assert_eq!(
parse_pgsql_sig(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: Some("bigint".to_string()),
name: "param1".to_string(),
typ: Typ::Int,
default: None,
has_default: false,
oidx: Some(1),
},
Arg {
otyp: Some("text".to_string()),
name: "param2".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: Some(2),
},
Arg {
otyp: Some("text".to_string()),
name: "param3".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: Some(3),
},
],
no_main_func: None
}
);
Ok(())
}
#[test]
fn test_parse_sql_blocks_multi_2semi() -> anyhow::Result<()> {
let code = r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT;
-- ;
SELECT $2::TEXT;
"#;
assert_eq!(parse_sql_blocks(code).len(), 2);
Ok(())
}
#[test]
fn test_parse_sql_blocks_multi_1semi() -> anyhow::Result<()> {
let code = r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT;
-- ;
SELECT $2::TEXT
"#;
assert_eq!(
parse_sql_blocks(code),
vec![
r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT;"#,
r#"
-- ;
SELECT $2::TEXT
"#
]
);
Ok(())
}
#[test]
fn test_parse_sql_blocks_single_1semi() -> anyhow::Result<()> {
let code = r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT;
-- hey
"#;
assert_eq!(
parse_sql_blocks(code),
vec![
r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT;"#,
]
);
Ok(())
}
#[test]
fn test_parse_sql_blocks_single_nosemi() -> anyhow::Result<()> {
let code = r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT
"#;
assert_eq!(
parse_sql_blocks(code),
vec![
r#"
-- $1 param1
-- $2 param2
-- $3 param3
SELECT '--', ';' $3::TEXT, $1::BIGINT
"#
]
);
Ok(())
}
#[test]
fn test_parse_mysql_positional_sig() -> anyhow::Result<()> {
let code = r#"
-- ? param1 (int) = 3
-- ? param2 (text)
SELECT ?, ?;
"#;
assert_eq!(
parse_mysql_sig(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: Some("int".to_string()),
name: "param1".to_string(),
typ: Typ::Int,
default: Some(json!(3)),
has_default: true,
oidx: None,
},
Arg {
otyp: Some("text".to_string()),
name: "param2".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
},
],
no_main_func: None
}
);
Ok(())
}
#[test]
fn test_parse_mysql_sig() -> anyhow::Result<()> {
let code = r#"
-- :param1 (int) = 3
-- :param2 (text)
-- :param_3 (text)
SELECT :param_3, :param1;
SELECT :param2;
"#;
assert_eq!(
parse_mysql_sig(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: Some("int".to_string()),
name: "param1".to_string(),
typ: Typ::Int,
default: Some(json!(3)),
has_default: true,
oidx: None,
},
Arg {
otyp: Some("text".to_string()),
name: "param2".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
},
Arg {
otyp: Some("text".to_string()),
name: "param_3".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
},
],
no_main_func: None
@@ -408,9 +796,10 @@ SELECT * FROM table WHERE token=$1::TEXT AND image=$2::BIGINT
#[test]
fn test_parse_bigquery_sig() -> anyhow::Result<()> {
let code = r#"
-- @token (string)
-- @token (string) = abc
-- @image (int64)
SELECT * FROM table WHERE token=@token AND image=@image
SELECT * FROM table WHERE token=@token AND image=@image;
SELECT @token;
"#;
//println!("{}", serde_json::to_string()?);
assert_eq!(
@@ -423,15 +812,111 @@ SELECT * FROM table WHERE token=@token AND image=@image
otyp: Some("string".to_string()),
name: "token".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
default: Some(json!("abc")),
has_default: true,
oidx: None,
},
Arg {
otyp: Some("int64".to_string()),
name: "image".to_string(),
typ: Typ::Int,
default: None,
has_default: false
has_default: false,
oidx: None,
},
],
no_main_func: None
}
);
Ok(())
}
#[test]
fn test_parse_snowflake_sig() -> anyhow::Result<()> {
let code = r#"
-- ? param1 (int) = 3
-- ? param2 (varchar)
SELECT ?, ?;
-- ? param3 (varchar)
SELECT ?;
"#;
assert_eq!(
parse_snowflake_sig(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: Some("int".to_string()),
name: "param1".to_string(),
typ: Typ::Int,
default: Some(json!(3)),
has_default: true,
oidx: None,
},
Arg {
otyp: Some("varchar".to_string()),
name: "param2".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
},
Arg {
otyp: Some("varchar".to_string()),
name: "param3".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
}
],
no_main_func: None
}
);
Ok(())
}
#[test]
fn test_parse_mssql_sig() -> anyhow::Result<()> {
let code = r#"
-- @p1 param1 (int) = 3
-- @p2 param2 (varchar)
-- @p3 param3 (varchar)
SELECT @p3, @p1;
SELECT @p2;
"#;
assert_eq!(
parse_mssql_sig(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: Some("int".to_string()),
name: "param1".to_string(),
typ: Typ::Int,
default: Some(json!(3)),
has_default: true,
oidx: None,
},
Arg {
otyp: Some("varchar".to_string()),
name: "param2".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
},
Arg {
otyp: Some("varchar".to_string()),
name: "param3".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false,
oidx: None,
},
],
no_main_func: None

View File

@@ -204,6 +204,7 @@ fn parse_param(
typ,
default: None,
has_default: ident.id.optional || nullable,
oidx: None,
})
}
// Pat::Object(ObjectPat { ... }) = todo!()
@@ -250,13 +251,13 @@ fn parse_param(
if typ == Typ::Unknown && dflt.is_some() {
typ = json_to_typ(dflt.as_ref().unwrap());
}
Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true })
Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true, oidx: None })
}
Pat::Object(ObjectPat { type_ann, .. }) => {
let (typ, nullable) = eval_type_ann(&type_ann);
*counter += 1;
let name = format!("anon{}", counter);
Ok(Arg { otyp: None, name, typ, default: None, has_default: nullable })
Ok(Arg { otyp: None, name, typ, default: None, has_default: nullable, oidx: None })
}
_ => Err(anyhow::anyhow!(
"parameter syntax unsupported: `{}`: {:#?}",

View File

@@ -3,7 +3,7 @@
"collaborators": [
"Ruben Fiszel <ruben@windmill.dev>"
],
"version": "1.364.4",
"version": "1.367.2",
"files": [
"windmill_parser_wasm_bg.wasm",
"windmill_parser_wasm.js",
@@ -14,4 +14,4 @@
"sideEffects": [
"./snippets/*"
]
}
}

View File

@@ -6,6 +6,20 @@ heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let WASM_VECTOR_LEN = 0;
let cachedUint8Memory0 = null;
@@ -84,20 +98,6 @@ function getInt32Memory0() {
return cachedInt32Memory0;
}
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let cachedFloat64Memory0 = null;
function getFloat64Memory0() {
@@ -585,6 +585,13 @@ async function __wbg_load(module, imports) {
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_eval_aa725d466edcea2c = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
@@ -593,13 +600,6 @@ function __wbg_get_imports() {
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_eval_2ea6d5f9a10f336a = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0);
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;

View File

@@ -28,77 +28,88 @@ export function main(test1?: string, test2: string = \"burkina\",
name: "test1".to_string(),
typ: Typ::Str(None),
default: None,
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "test2".to_string(),
typ: Typ::Str(None),
default: Some(json!("burkina")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "test3".to_string(),
typ: Typ::Resource("postgres".to_string()),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "b64".to_string(),
typ: Typ::Bytes,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "ls".to_string(),
typ: Typ::List(Box::new(Typ::Bytes)),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "email".to_string(),
typ: Typ::Email,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "literal".to_string(),
typ: Typ::Str(Some(vec!["test".to_string()])),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "literal_union".to_string(),
typ: Typ::Str(Some(vec!["test".to_string(), "test2".to_string()])),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "opt_type".to_string(),
typ: Typ::Str(None),
default: None,
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "opt_type_union".to_string(),
typ: Typ::Str(None),
default: None,
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "opt_type_union_union2".to_string(),
typ: Typ::Str(None),
default: None,
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
@@ -108,21 +119,24 @@ export function main(test1?: string, test2: string = \"burkina\",
ObjectProperty { key: "b".to_string(), typ: Box::new(Typ::Float) }
]),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "literals_with_undefined".to_string(),
typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])),
default: None,
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "dyn_select".to_string(),
typ: Typ::DynSelect("foo".to_string()),
default: None,
has_default: false
has_default: false,
oidx: None
}
],
no_main_func: Some(false)
@@ -154,35 +168,40 @@ export function main(test2 = \"burkina\",
name: "test2".to_string(),
typ: Typ::Str(None),
default: Some(json!("burkina")),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "bool".to_string(),
typ: Typ::Bool,
default: Some(json!(true)),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "float".to_string(),
typ: Typ::Float,
default: Some(json!(4.2)),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "int".to_string(),
typ: Typ::Int,
default: Some(json!(42)),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
name: "ls".to_string(),
typ: Typ::List(Box::new(Typ::Str(None))),
default: Some(json!(["test"])),
has_default: true
has_default: true,
oidx: None
},
Arg {
otyp: None,
@@ -192,7 +211,8 @@ export function main(test2 = \"burkina\",
ObjectProperty { key: "b".to_string(), typ: Box::new(Typ::Int) }
]),
default: Some(json!({"a": "test", "b": 42})),
has_default: true
has_default: true,
oidx: None
}
],
no_main_func: Some(false)
@@ -224,21 +244,24 @@ export function main(foo: FooBar, {a, b}: FooBar, {c, d}: FooBar = {a: \"foo\",
otyp: None,
typ: Typ::Resource("foo_bar".to_string()),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
name: "anon1".to_string(),
otyp: None,
typ: Typ::Resource("foo_bar".to_string()),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
name: "anon2".to_string(),
otyp: None,
typ: Typ::Resource("foo_bar".to_string()),
default: Some(json!({"a": "foo", "b": 42})),
has_default: true
has_default: true,
oidx: None
}
],
no_main_func: Some(false)
@@ -268,7 +291,8 @@ export function main(foo: (\"foo\" | \"bar\")[]) {
"bar".to_string()
])))),
default: None,
has_default: false
has_default: false,
oidx: None
}],
no_main_func: Some(false)
}
@@ -349,56 +373,64 @@ Write-Output 'Testing...'
name: "test_none".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_string".to_string(),
typ: Typ::Str(None),
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_int".to_string(),
typ: Typ::Int,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_decimal".to_string(),
typ: Typ::Float,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_double".to_string(),
typ: Typ::Float,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_single".to_string(),
typ: Typ::Float,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_datetime_lower".to_string(),
typ: Typ::Datetime,
default: None,
has_default: false
has_default: false,
oidx: None
},
Arg {
otyp: None,
name: "test_datetime_upper".to_string(),
typ: Typ::Datetime,
default: None,
has_default: false
has_default: false,
oidx: None
}
],
no_main_func: None,

View File

@@ -9,5 +9,5 @@ name = "windmill_parser"
path = "./src/lib.rs"
[dependencies]
serde.workspace = true
serde = { version = "1.0", features = ["derive"] }
serde_json.workspace = true

View File

@@ -57,6 +57,7 @@ pub struct Arg {
pub typ: Typ,
pub default: Option<serde_json::Value>,
pub has_default: bool,
pub oidx: Option<i32>,
}
pub fn json_to_typ(js: &Value) -> Typ {

View File

@@ -16,5 +16,4 @@ wasm-bindgen-test.workspace = true
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql.workspace = true
wasm-bindgen.workspace = true
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen.workspace = true

View File

@@ -23,11 +23,11 @@ use windmill_common::ee::schedule_key_renewal;
use windmill_common::{
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
OAUTH_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,
},
@@ -54,18 +54,18 @@ use windmill_common::METRICS_ADDR;
use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
use windmill_worker::{
BUN_CACHE_DIR, BUN_TAR_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
GO_BIN_CACHE_DIR, GO_CACHE_DIR, HUB_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR,
POWERSHELL_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR,
BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_DEPSTAR_CACHE_DIR, DENO_CACHE_DIR,
DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, HUB_CACHE_DIR,
LOCK_CACHE_DIR, PIP_CACHE_DIR, POWERSHELL_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR,
};
use crate::monitor::{
initial_load, load_keep_job_dir, load_metrics_debug_enabled, load_require_preexisting_user,
load_tag_per_workspace_enabled, monitor_db, monitor_pool, reload_base_url_setting,
reload_bunfig_install_scopes_setting, reload_critical_error_channels_setting,
reload_extra_pip_index_url_setting, reload_hub_base_url_setting,
reload_job_default_timeout_setting, reload_jwt_secret_setting, reload_license_key,
reload_npm_config_registry_setting, reload_pip_index_url_setting,
load_tag_per_workspace_enabled, load_tag_per_workspace_workspaces, monitor_db, monitor_pool,
reload_base_url_setting, reload_bunfig_install_scopes_setting,
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting,
reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting,
reload_retention_period_setting, reload_scim_token_setting, reload_server_config,
reload_worker_config,
};
@@ -365,28 +365,35 @@ Windmill Community Edition {GIT_VERSION}
#[cfg(feature = "tantivy")]
let should_index_jobs =
mode == Mode::Indexer || (enable_standalone_indexer && mode == Mode::Standalone);
#[cfg(not(feature = "tantivy"))]
let should_index_jobs = false;
mode == Mode::Indexer || (enable_standalone_indexer && mode == Mode::Standalone);
#[cfg(feature = "tantivy")]
let (index_reader, index_writer) = if should_index_jobs {
let (r, w) = windmill_indexer::indexer_ee::init_index()?;
let (r, w) = windmill_indexer::indexer_ee::init_index().await?;
(Some(r), Some(w))
} else {
(None, None)
};
let indexer_rx = killpill_rx.resubscribe();
let index_writer2 = index_writer.clone();
let indexer_f = async {
if let Some(index_writer) = index_writer2 {
windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx)
.await;
#[cfg(feature = "tantivy")]
let indexer_f = {
let indexer_rx = killpill_rx.resubscribe();
let index_writer2 = index_writer.clone();
async {
if let Some(index_writer) = index_writer2 {
windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx)
.await;
}
Ok(())
}
Ok(())
};
#[cfg(not(feature = "tantivy"))]
let (index_reader, index_writer) = (None, None);
#[cfg(not(feature = "tantivy"))]
let indexer_f = async { Ok(()) as anyhow::Result<()> };
let server_f = async {
if !is_agent {
windmill_api::run_server(
@@ -398,6 +405,7 @@ Windmill Community Edition {GIT_VERSION}
server_killpill_rx,
base_internal_tx,
server_mode,
base_internal_url.clone(),
)
.await?;
} else {
@@ -504,9 +512,14 @@ Windmill Community Edition {GIT_VERSION}
},
DEFAULT_TAGS_PER_WORKSPACE_SETTING => {
if let Err(e) = load_tag_per_workspace_enabled(&db).await {
tracing::error!("Error loading default tag per workpsace: {e:#}");
tracing::error!("Error loading default tag per workspace: {e:#}");
}
},
DEFAULT_TAGS_WORKSPACES_SETTING => {
if let Err(e) = load_tag_per_workspace_workspaces(&db).await {
tracing::error!("Error loading default tag per workspace workspaces: {e:#}");
}
}
RETENTION_PERIOD_SECS_SETTING => {
reload_retention_period_setting(&db).await
},
@@ -539,14 +552,12 @@ Windmill Community Edition {GIT_VERSION}
load_require_preexisting_user(&db).await;
},
EXPOSE_METRICS_SETTING => {
if worker_mode {
tracing::info!("Metrics setting changed, restarting");
// we wait a bit randomly to avoid having all serverss and workers shutdown at same time
let rd_delay = rand::thread_rng().gen_range(0..4);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
}
tracing::info!("Metrics setting changed, restarting");
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
let rd_delay = rand::thread_rng().gen_range(0..40);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
}
},
EXPOSE_DEBUG_METRICS_SETTING => {
@@ -766,7 +777,8 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
DENO_CACHE_DIR_DEPS,
DENO_CACHE_DIR_NPM,
BUN_CACHE_DIR,
BUN_TAR_CACHE_DIR,
BUN_DEPSTAR_CACHE_DIR,
BUN_BUNDLE_CACHE_DIR,
GO_CACHE_DIR,
GO_BIN_CACHE_DIR,
HUB_CACHE_DIR,
@@ -779,6 +791,10 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
.expect("could not create initial worker dir");
}
tracing::info!(
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
*windmill_worker::SLEEP_QUEUE
);
for i in 1..(num_workers + 1) {
let db1 = db.clone();
let instance_name = instance_name.clone();
@@ -791,7 +807,9 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
let hostname = hostname.clone();
handles.push(tokio::spawn(async move {
tracing::info!(worker = %worker_name, "starting worker");
if num_workers > 1 {
tracing::info!(worker = %worker_name, "starting worker {i}");
}
let f = windmill_worker::run_worker(
&db1,

View File

@@ -28,10 +28,11 @@ use windmill_common::{
flow_status::FlowStatusModule,
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
DEFAULT_TAGS_PER_WORKSPACE_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_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, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_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,
},
@@ -41,10 +42,10 @@ use windmill_common::{
users::truncate_token,
utils::{now_from_db, rd_string},
worker::{
load_worker_config, reload_custom_tags_setting, DEFAULT_TAGS_PER_WORKSPACE, SERVER_CONFIG,
WORKER_CONFIG,
load_worker_config, make_pull_query, make_suspended_pull_query, reload_custom_tags_setting,
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SERVER_CONFIG, WORKER_CONFIG,
},
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL,
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
METRICS_DEBUG_ENABLED, METRICS_ENABLED,
};
use windmill_queue::cancel_job;
@@ -104,8 +105,6 @@ lazy_static::lazy_static! {
"Number of jobs in the queue",
&["tag"]
).unwrap();
static ref JOB_RETENTION_SECS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));
}
pub async fn initial_load(
@@ -127,6 +126,10 @@ pub async fn initial_load(
tracing::error!("Error loading default tag per workpsace: {e:#}");
}
if let Err(e) = load_tag_per_workspace_workspaces(db).await {
tracing::error!("Error loading default tag per workpsace workspaces: {e:#}");
}
if server_mode {
load_require_preexisting_user(db).await;
}
@@ -204,6 +207,28 @@ pub async fn load_tag_per_workspace_enabled(db: &DB) -> error::Result<()> {
Ok(())
}
pub async fn load_tag_per_workspace_workspaces(db: &DB) -> error::Result<()> {
let workspaces = load_value_from_global_settings(db, DEFAULT_TAGS_WORKSPACES_SETTING).await;
match workspaces {
Ok(Some(serde_json::Value::Array(t))) => {
let workspaces = t
.iter()
.filter_map(|x| x.as_str())
.map(|x| x.to_string())
.collect::<Vec<String>>();
let mut w = DEFAULT_TAGS_WORKSPACES.write().await;
*w = Some(workspaces);
}
Ok(None) => {
let mut w = DEFAULT_TAGS_WORKSPACES.write().await;
*w = None;
}
_ => (),
};
Ok(())
}
pub async fn load_metrics_debug_enabled(db: &DB) -> error::Result<()> {
let metrics_enabled = load_value_from_global_settings(db, EXPOSE_DEBUG_METRICS_SETTING).await;
match metrics_enabled {
@@ -381,6 +406,21 @@ pub async fn delete_expired_items(db: &DB) -> () {
Err(e) => tracing::error!("Error deleting cache resource {}", e.to_string()),
}
let deleted_expired_variables = sqlx::query_scalar!(
"DELETE FROM variable WHERE expires_at IS NOT NULL AND expires_at > now() RETURNING path",
)
.fetch_all(db)
.await;
match deleted_expired_variables {
Ok(res) => {
if res.len() > 0 {
tracing::info!("deleted {} expired variables {:?}", res.len(), res)
}
}
Err(e) => tracing::error!("Error deleting cache resource {}", e.to_string()),
}
let job_retention_secs = *JOB_RETENTION_SECS.read().await;
if job_retention_secs > 0 {
match db.begin().await {
@@ -650,6 +690,16 @@ pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
std_env_var: &str,
lock: Arc<RwLock<Option<T>>>,
) -> error::Result<()> {
let force_value = std::env::var(format!("FORCE_{}", std_env_var))
.ok()
.and_then(|x| x.parse::<T>().ok());
if let Some(force_value) = force_value {
let mut l = lock.write().await;
*l = Some(force_value);
return Ok(());
}
let q = load_value_from_global_settings(db, setting_name).await?;
let mut value = std::env::var(std_env_var)
@@ -913,6 +963,8 @@ pub async fn reload_worker_config(
let mut wc = WORKER_CONFIG.write().await;
tracing::info!("Reloading worker config...");
make_suspended_pull_query(&config).await;
make_pull_query(&config).await;
*wc = config
}
}

View File

@@ -71,7 +71,7 @@ if [ "$REVERT" == "YES" ]; then
ce_file="${ee_file/${EE_CODE_DIR}/.}"
ce_file="${root_dirpath}/backend/${ce_file}"
if [ "$REVERT_PREVIOUS" == "YES" ]; then
git checkout HEAD@{9} ${ce_file} || true
git checkout HEAD@{3} ${ce_file} || true
else
git restore --staged ${ce_file} || true
git restore ${ce_file} || true

View File

@@ -22,7 +22,7 @@ use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs};
use serde::Serialize;
use windmill_common::auth::JWT_SECRET;
use windmill_common::worker::{PriorityTags, WORKER_CONFIG};
use windmill_common::worker::WORKER_CONFIG;
use windmill_common::{
flow_status::{FlowStatus, FlowStatusModule, RestartedFrom},
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
@@ -128,6 +128,7 @@ impl ApiServer {
rx,
port_tx,
false,
format!("http://localhost:{}", addr.port()),
));
_port_rx.await.unwrap();
@@ -1007,8 +1008,12 @@ fn spawn_test_worker(
{
let mut wc = WORKER_CONFIG.write().await;
(*wc).worker_tags = windmill_common::worker::DEFAULT_TAGS.clone();
(*wc).priority_tags_sorted =
vec![PriorityTags { priority: 0, tags: (*wc).worker_tags.clone() }]
(*wc).priority_tags_sorted = vec![windmill_common::worker::PriorityTags {
priority: 0,
tags: (*wc).worker_tags.clone(),
}];
windmill_common::worker::make_suspended_pull_query(&wc).await;
windmill_common::worker::make_pull_query(&wc).await;
}
windmill_worker::run_worker::<rsmq_async::MultiplexedRsmq>(
&db,

View File

@@ -18,6 +18,7 @@ embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core"
parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet"]
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus"]
openidconnect = ["dep:openidconnect"]
tantivy = ["dep:windmill-indexer"]
[dependencies]
windmill-queue.workspace = true
@@ -27,7 +28,7 @@ windmill-parser.workspace = true
windmill-parser-py-imports.workspace = true
windmill-parser-ts.workspace = true
windmill-git-sync.workspace = true
windmill-indexer.workspace = true
windmill-indexer = { workspace = true, optional = true }
tokio.workspace = true
anyhow.workspace = true
argon2.workspace = true
@@ -50,6 +51,7 @@ chrono.workspace = true
chrono-tz.workspace = true
hex.workspace = true
base64.workspace = true
base32.workspace = true
serde_urlencoded.workspace = true
cron.workspace = true
mime_guess.workspace = true
@@ -58,6 +60,10 @@ tracing-subscriber.workspace = true
quick_cache.workspace = true
rand.workspace = true
time.workspace = true
native-tls.workspace = true
tokio-native-tls.workspace = true
openssl.workspace = true
mail-parser = { workspace = true, features = ["serde_support"] }
magic-crypt.workspace = true
tempfile.workspace = true
tokio-util.workspace = true

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.366.2
version: 1.377.1
title: Windmill API
contact:
@@ -845,6 +845,12 @@ paths:
operationId: renewLicenseKey
tags:
- setting
parameters:
- name: license_key
in: query
required: false
schema:
type: string
responses:
"200":
description: status
@@ -859,6 +865,12 @@ paths:
operationId: createCustomerPortalSession
tags:
- setting
parameters:
- name: license_key
in: query
required: false
schema:
type: string
responses:
"200":
description: url to portal
@@ -1507,6 +1519,8 @@ paths:
$ref: "#/components/schemas/LargeFileStorage"
git_sync:
$ref: "#/components/schemas/WorkspaceGitSyncSettings"
deploy_ui:
$ref: "#/components/schemas/WorkspaceDeployUISettings"
default_app:
type: string
default_scripts:
@@ -1896,6 +1910,32 @@ paths:
application/json:
schema: {}
/w/{workspace}/workspaces/edit_deploy_ui_config:
post:
summary: edit workspace deploy ui settings
operationId: editWorkspaceDeployUISettings
tags:
- workspace
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: Workspace deploy UI settings
required: true
content:
application/json:
schema:
type: object
properties:
deploy_ui_settings:
$ref: "#/components/schemas/WorkspaceDeployUISettings"
responses:
"200":
description: status
content:
application/json:
schema: {}
/w/{workspace}/workspaces/edit_default_app:
post:
summary: edit default app for workspace
@@ -2444,6 +2484,8 @@ paths:
in: query
schema:
type: string
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PerPage"
responses:
"200":
description: variable list
@@ -9770,6 +9812,9 @@ components:
type: boolean
is_refreshed:
type: boolean
expires_at:
type: string
format: date-time
required:
- workspace_id
- path
@@ -9808,6 +9853,9 @@ components:
type: integer
is_oauth:
type: boolean
expires_at:
type: string
format: date-time
required:
- path
- value
@@ -11070,6 +11118,25 @@ components:
items:
$ref: "#/components/schemas/GitRepositorySettings"
WorkspaceDeployUISettings:
type: object
properties:
include_path:
type: array
items:
type: string
include_type:
type: array
items:
type: string
enum:
- script
- flow
- app
- resource
- variable
- secret
WorkspaceDefaultScripts:
type: object
properties:

View File

@@ -29,6 +29,7 @@ use sha2::{Digest, Sha256};
use sql_builder::{bind::Bind, SqlBuilder};
use sqlx::{types::Uuid, FromRow};
use std::str;
use tower_http::cors::{Any, CorsLayer};
use windmill_audit::audit_ee::audit_log;
use windmill_audit::ActionKind;
use windmill_common::{
@@ -65,10 +66,24 @@ pub fn workspaced_service() -> Router {
}
pub fn unauthed_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([http::Method::GET])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new()
.route("/execute_component/*path", post(execute_component))
.route("/public_app/:secret", get(get_public_app_by_secret))
.route("/public_resource/*path", get(get_public_resource))
.route(
"/execute_component/*path",
post(execute_component).layer(cors.clone()),
)
.route(
"/public_app/:secret",
get(get_public_app_by_secret).layer(cors.clone()),
)
.route(
"/public_resource/*path",
get(get_public_resource).layer(cors.clone()),
)
}
pub fn global_service() -> Router {
@@ -1383,7 +1398,30 @@ async fn build_args(
let arg_str = v.get();
if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
if arg_str.starts_with("\"$ctx:") {
let prop = arg_str.trim_start_matches("\"$ctx:").trim_end_matches("\"");
let value = match prop {
"username" => authed.as_ref().map(|a| {
serde_json::to_value(a.username_override.as_ref().unwrap_or(&a.username))
}),
"email" => authed.as_ref().map(|a| serde_json::to_value(&a.email)),
"workspace" => Some(serde_json::to_value(&w_id)),
"groups" => authed.as_ref().map(|a| serde_json::to_value(&a.groups)),
"author" => Some(serde_json::to_value(&policy.on_behalf_of_email)),
_ => {
return Err(Error::BadRequest(format!(
"context variable {} not allowed",
prop
)))
}
};
safe_args.insert(
k.to_string(),
to_raw_value(&value.unwrap_or(Ok(serde_json::Value::Null)).map_err(|e| {
Error::InternalErr(format!("failed to serialize ctx variable for {}: {}", k, e))
})?),
);
} else if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
safe_args.insert(k.to_string(), v);
} else {
safe_args.insert(

View File

@@ -249,14 +249,57 @@ async fn fix_flow_versioning_migration(
}
async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
// let has_done_migration = sqlx::query_scalar!(
// "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
// )
// .fetch_one(db)
// .await?
// .unwrap_or(false);
// if !has_done_migration {
// tracing::info!("Applying fix_job_completed_index migration");
// let mut tx = db.begin().await?;
// let mut r = false;
// while !r {
// r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
// .fetch_one(&mut *tx)
// .await
// .map_err(|e| {
// tracing::error!("Error acquiring fix_job_completed_index lock: {e:#}");
// sqlx::migrate::MigrateError::Execute(e)
// })?
// .unwrap_or(false);
// if !r {
// tracing::info!("PG fix_job_completed_index_migration lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
// tokio::time::sleep(std::time::Duration::from_secs(5)).await;
// }
// }
// // sqlx::query(
// // "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new ON completed_job (workspace_id, job_kind, is_skipped, is_flow_step, created_at DESC, started_at DESC)"
// // ).execute(db).await?;
// sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
// .execute(db)
// .await?;
// sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING")
// .execute(&mut *tx)
// .await?;
// let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
// .execute(&mut *tx)
// .await?;
// tx.commit().await?;
// }
let migration_job_name = "fix_job_completed_index_2";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying fix_job_completed_index migration");
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
@@ -264,30 +307,266 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring fix_job_completed_index lock: {e:#}");
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG fix_job_completed_index_migration lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
sqlx::query(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new ON completed_job (workspace_id, job_kind, is_skipped, is_flow_step, created_at DESC, started_at DESC)"
).execute(db).await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
// ).execute(db).await?;
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
// ).execute(db).await?;
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at",
)
.execute(db)
.await?;
sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING")
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new",
)
.execute(db)
.await?;
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
let migration_job_name = "fix_job_completed_index_3";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_job_on_schedule_path")
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_queue")
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index")
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_on_created")
.execute(db)
.await?;
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
let migration_job_name = "fix_job_completed_index_4";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
let mut i = 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_3 ON completed_job (workspace_id, created_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_8 ON completed_job (workspace_id, created_at DESC) where job_kind in ('deploymentcallback') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_9 ON completed_job (workspace_id, created_at DESC) where job_kind in ('dependencies', 'flowdependencies', 'appdependencies') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_5 ON completed_job (workspace_id, created_at DESC) where job_kind in ('preview', 'flowpreview') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_6 ON completed_job (workspace_id, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_7 ON completed_job (workspace_id, success, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON completed_job (workspace_id, started_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists root_job_index_by_path_2 ON completed_job (workspace_id, script_path, created_at desc) WHERE parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_created_at ON completed_job (created_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path")
.execute(db)
.await?;
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
Ok(())

View File

@@ -1,4 +1,8 @@
use anyhow::anyhow;
#[cfg(feature = "enterprise")]
use std::sync::Arc;
#[cfg(feature = "enterprise")]
use tokio::sync::RwLock;
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<String> {
// Implementation is not open source
@@ -9,8 +13,20 @@ pub async fn validate_license_key(_license_key: String) -> anyhow::Result<String
pub async fn jwt_ext_auth(
_w_id: Option<&String>,
_token: &str,
) -> Option<(crate::db::ApiAuthed, usize)> {
_external_jwks: Option<Arc<RwLock<ExternalJwks>>>,
) -> anyhow::Result<(crate::db::ApiAuthed, usize)> {
// Implementation is not open source
None
Err(anyhow!("External JWT auth is not open source"))
}
#[cfg(feature = "enterprise")]
pub struct ExternalJwks;
#[cfg(feature = "enterprise")]
impl ExternalJwks {
pub async fn load() -> Option<Arc<RwLock<Self>>> {
// Implementation is not open source
None
}
}

View File

@@ -8,6 +8,7 @@
use axum::body::Body;
use axum::http::HeaderValue;
use quick_cache::sync::Cache;
use serde_json::value::RawValue;
use sqlx::Pool;
use std::collections::HashMap;
@@ -183,7 +184,10 @@ pub fn workspaced_service() -> Router {
.layer(ce_headers.clone()),
)
.route("/run/preview", post(run_preview_script))
.route("/run/preview_bundle", post(run_bundle_preview_script))
.route(
"/run/preview_bundle",
post(run_bundle_preview_script).layer(axum::extract::DefaultBodyLimit::disable()),
)
.route("/add_batch_jobs/:n", post(add_batch_jobs))
.route("/run/preview_flow", post(run_preview_flow_job))
.route(
@@ -297,11 +301,17 @@ struct JsonPath {
pub approver: Option<String>,
}
async fn get_result_by_id(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path((w_id, flow_id, node_id)): Path<(String, Uuid, String)>,
Query(JsonPath { json_path, .. }): Query<JsonPath>,
) -> windmill_common::error::JsonResult<Box<JsonRawValue>> {
let res = windmill_queue::get_result_by_id(db, w_id, flow_id, node_id, json_path).await?;
let res =
windmill_queue::get_result_by_id(db.clone(), w_id.clone(), flow_id, node_id, json_path)
.await?;
log_job_view(&db, Some(&authed), &w_id, &flow_id).await?;
Ok(Json(res))
}
@@ -622,6 +632,9 @@ async fn get_flow_job_debug_info(
jobs.insert(job.id().to_string(), job);
}
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(Json(jobs).into_response())
} else {
Err(error::Error::NotFound(format!(
@@ -651,6 +664,9 @@ async fn get_job(
)
.await?;
job.fetch_outstanding_wait_time(&db).await?;
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(Json(job).into_response())
}
@@ -871,6 +887,9 @@ async fn get_job_logs(
));
}
let logs = record.logs.unwrap_or_default();
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
if let Some(r) = get_logs_from_store(record.log_offset, &logs, &record.log_file_index).await
{
@@ -900,6 +919,9 @@ async fn get_job_logs(
));
}
let logs = text.logs.unwrap_or_default();
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
if let Some(r) = get_logs_from_store(text.log_offset, &logs, &text.log_file_index).await {
return r.map(content_plain);
@@ -938,6 +960,9 @@ async fn get_args(
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(Json(record.args.map(|x| x.0).unwrap_or_default()))
} else {
let record = sqlx::query_as::<_, RawArgs>(
@@ -955,6 +980,9 @@ async fn get_args(
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(Json(record.args.map(|x| x.0).unwrap_or_default()))
}
}
@@ -1006,7 +1034,7 @@ pub struct ListableCompletedJob {
pub labels: Option<serde_json::Value>,
}
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, Default)]
pub struct RunJobQuery {
scheduled_for: Option<chrono::DateTime<chrono::Utc>>,
scheduled_in_secs: Option<i64>,
@@ -1368,7 +1396,7 @@ async fn cancel_jobs(
sqlx::query!(
"DELETE FROM queue WHERE id = any($1) AND workspace_id = $2",
&jobs,
&trivial_jobs,
w_id
)
.execute(&mut *tx)
@@ -1434,7 +1462,6 @@ async fn cancel_selection(
Path(w_id): Path<String>,
Json(jobs): Json<Vec<Uuid>>,
) -> error::JsonResult<Vec<Uuid>> {
require_admin(authed.is_admin, &authed.username)?;
let mut tx = user_db.begin(&authed).await?;
let jobs_to_cancel = sqlx::query_scalar!(
@@ -1944,7 +1971,7 @@ pub async fn get_suspended_job_flow(
Job::QueuedJob(job) => &job.email,
Job::CompletedJobWithFormattedResult(job) => &job.cj.email,
};
conditionally_require_authed_user(authed, flow_status.clone(), trigger_email)?;
conditionally_require_authed_user(authed.clone(), flow_status.clone(), trigger_email)?;
let approvers_from_status = match flow_module_status {
FlowStatusModule::Success { approvers, .. } => approvers.to_owned(),
@@ -1971,6 +1998,8 @@ pub async fn get_suspended_job_flow(
approvers_from_status
};
log_job_view(&db, authed.as_ref(), &w_id, &job).await?;
Ok(Json(SuspendedJobFlow { job: flow, approvers }).into_response())
}
@@ -2584,6 +2613,7 @@ enum PreviewKind {
Http,
Noop,
Bundle,
Tarbundle,
}
#[derive(Deserialize)]
@@ -2721,6 +2751,23 @@ pub async fn run_flow_by_path(
Path((w_id, flow_path)): Path<(String, StripPath)>,
Query(run_query): Query<RunJobQuery>,
args: PushArgsOwned,
) -> error::Result<(StatusCode, String)> {
run_flow_by_path_inner(
authed, db, user_db, rsmq, w_id, flow_path, run_query, args, None,
)
.await
}
pub async fn run_flow_by_path_inner(
authed: ApiAuthed,
db: DB,
user_db: UserDB,
rsmq: Option<rsmq_async::MultiplexedRsmq>,
w_id: String,
flow_path: StripPath,
run_query: RunJobQuery,
args: PushArgsOwned,
label_prefix: Option<String>,
) -> error::Result<(StatusCode, String)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -2754,7 +2801,9 @@ pub async fn run_flow_by_path(
&w_id,
JobPayload::Flow { path: flow_path.to_string(), dedicated_worker },
PushArgs { args: &args.args, extra: args.extra },
authed.display_username(),
&label_prefix
.map(|x| x + authed.display_username())
.unwrap_or_else(|| authed.display_username().to_string()),
&authed.email,
username_to_permissioned_as(&authed.username),
scheduled_for,
@@ -2880,6 +2929,31 @@ pub async fn run_script_by_path(
Path((w_id, script_path)): Path<(String, StripPath)>,
Query(run_query): Query<RunJobQuery>,
args: PushArgsOwned,
) -> error::Result<(StatusCode, String)> {
run_script_by_path_inner(
authed,
db,
user_db,
rsmq,
w_id,
script_path,
run_query,
args,
None,
)
.await
}
pub async fn run_script_by_path_inner(
authed: ApiAuthed,
db: DB,
user_db: UserDB,
rsmq: Option<rsmq_async::MultiplexedRsmq>,
w_id: String,
script_path: StripPath,
run_query: RunJobQuery,
args: PushArgsOwned,
label_prefix: Option<String>,
) -> error::Result<(StatusCode, String)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -2905,7 +2979,9 @@ pub async fn run_script_by_path(
&w_id,
job_payload,
PushArgs { args: &args.args, extra: args.extra },
authed.display_username(),
&label_prefix
.map(|x| x + authed.display_username())
.unwrap_or_else(|| authed.display_username().to_string()),
&authed.email,
username_to_permissioned_as(&authed.username),
scheduled_for,
@@ -3256,6 +3332,76 @@ lazy_static::lazy_static! {
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(200);
static ref JOB_VIEW_AUDIT_LOGS: bool = std::env::var("JOB_VIEW_AUDIT_LOGS")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(false);
static ref JOB_VIEW_CACHE: JobViewCache = JobViewCache::new(50000);
}
struct JobViewCache {
cache: Cache<String, std::time::Instant>,
}
impl JobViewCache {
fn new(items_capacity: usize) -> Self {
Self { cache: Cache::new(items_capacity) }
}
fn get_or_insert(&self, key: &str) -> Option<std::time::Instant> {
match self.cache.get(key) {
Some(t) if t < std::time::Instant::now() => {
self.cache.insert(
key.to_string(),
std::time::Instant::now() + std::time::Duration::from_secs(60),
);
None
}
v => {
self.cache.insert(
key.to_string(),
std::time::Instant::now() + std::time::Duration::from_secs(60),
);
v
}
}
}
}
async fn log_job_view(
db: &DB,
opt_authed: Option<&ApiAuthed>,
w_id: &str,
job_id: &Uuid,
) -> error::Result<()> {
if *JOB_VIEW_AUDIT_LOGS {
let audit_author = match opt_authed {
Some(authed) => AuditAuthor::from(authed),
None => AuditAuthor {
username: "anonymous".to_string(),
username_override: None,
email: "anonymous".to_string(),
},
};
if JOB_VIEW_CACHE
.get_or_insert(&format!("{}_{}", job_id, audit_author.email))
.is_none()
{
audit_log(
db,
&audit_author,
"jobs.view",
ActionKind::Execute,
w_id,
Some(&job_id.to_string()),
None,
)
.await?;
};
}
Ok(())
}
pub async fn run_wait_result_job_by_path_get(
@@ -3713,6 +3859,8 @@ async fn run_bundle_preview_script(
Query(run_query): Query<RunJobQuery>,
mut multipart: axum::extract::Multipart,
) -> error::Result<(StatusCode, String)> {
use windmill_common::scripts::PREVIEW_IS_TAR_CODEBASE_HASH;
check_license_key_valid().await?;
check_scopes(&authed, || format!("runscript"))?;
@@ -3725,9 +3873,12 @@ async fn run_bundle_preview_script(
let mut job_id = None;
let mut tx = None;
let mut uploaded = false;
let mut is_tar = false;
while let Some(field) = multipart.next_field().await.unwrap() {
let name = field.name().unwrap().to_string();
let data = field.bytes().await.unwrap();
let data = field.bytes().await;
let data = data.map_err(to_anyhow)?;
if name == "preview" {
let preview: Preview = serde_json::from_slice(&data).map_err(to_anyhow)?;
@@ -3739,27 +3890,33 @@ async fn run_bundle_preview_script(
let args = preview.args.unwrap_or_default();
is_tar = match preview.kind {
Some(PreviewKind::Tarbundle) => true,
_ => false,
};
// tracing::info!("is_tar 1: {is_tar}");
// hmap.insert("")
let (uuid, ntx) = push(
&db,
ltx,
&w_id,
match preview.kind {
Some(PreviewKind::Identity) => JobPayload::Identity,
Some(PreviewKind::Noop) => JobPayload::Noop,
_ => JobPayload::Code(RawCode {
hash: Some(PREVIEW_IS_CODEBASE_HASH),
content: preview.content.unwrap_or_default(),
path: preview.path,
language: preview.language.unwrap_or(ScriptLang::Deno),
lock: preview.lock,
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
concurrency_time_window_s: None, // TODO(gbouv): same as above
cache_ttl: None,
dedicated_worker: preview.dedicated_worker,
custom_concurrency_key: None,
}),
},
JobPayload::Code(RawCode {
hash: if is_tar {
Some(PREVIEW_IS_TAR_CODEBASE_HASH)
} else {
Some(PREVIEW_IS_CODEBASE_HASH)
},
content: preview.content.unwrap_or_default(),
path: preview.path,
language: preview.language.unwrap_or(ScriptLang::Deno),
lock: preview.lock,
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
concurrency_time_window_s: None, // TODO(gbouv): same as above
cache_ttl: None,
dedicated_worker: preview.dedicated_worker,
custom_concurrency_key: None,
}),
PushArgs::from(&args),
authed.display_username(),
&authed.email,
@@ -3784,7 +3941,7 @@ async fn run_bundle_preview_script(
tx = Some(ntx);
}
if name == "file" {
let id = job_id
let mut id = job_id
.as_ref()
.ok_or_else(|| {
Error::BadRequest(
@@ -3793,6 +3950,12 @@ async fn run_bundle_preview_script(
})?
.to_string();
// tracing::info!("is_tar 2: {is_tar}");
if is_tar {
id = format!("{}.tar", id);
}
uploaded = true;
if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
@@ -3875,22 +4038,27 @@ async fn run_dependencies_job(
let raw_script = req.raw_scripts[0].clone();
let script_path = raw_script.script_path;
let ehm = HashMap::new();
let raw_code = raw_script.raw_code.unwrap_or_else(|| "".to_string());
let language = raw_script.language;
let (args, raw_code) = if let Some(deps) = req.raw_deps {
let mut hm = HashMap::new();
hm.insert(
"raw_deps".to_string(),
JsonRawValue::from_string("true".to_string()).unwrap(),
);
if language == ScriptLang::Bun {
let annotation = windmill_common::worker::get_annotation(&raw_code);
hm.insert(
"npm_mode".to_string(),
JsonRawValue::from_string(annotation.npm_mode.to_string()).unwrap(),
);
}
(PushArgs { extra: Some(hm), args: &ehm }, deps)
} else {
(
PushArgs::from(&ehm),
raw_script.raw_code.unwrap_or_else(|| "".to_string()),
)
(PushArgs::from(&ehm), raw_code)
};
let language = raw_script.language;
let (uuid, tx) = push(
&db,
PushIsolationLevel::IsolatedRoot(db.clone(), rsmq),
@@ -4230,6 +4398,31 @@ pub async fn run_job_by_hash(
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
Query(run_query): Query<RunJobQuery>,
args: PushArgsOwned,
) -> error::Result<(StatusCode, String)> {
run_job_by_hash_inner(
authed,
db,
user_db,
rsmq,
w_id,
script_hash,
run_query,
args,
None,
)
.await
}
pub async fn run_job_by_hash_inner(
authed: ApiAuthed,
db: DB,
user_db: UserDB,
rsmq: Option<rsmq_async::MultiplexedRsmq>,
w_id: String,
script_hash: ScriptHash,
run_query: RunJobQuery,
args: PushArgsOwned,
label_prefix: Option<String>,
) -> error::Result<(StatusCode, String)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -4276,7 +4469,9 @@ pub async fn run_job_by_hash(
priority,
},
PushArgs { args: &args.args, extra: args.extra },
authed.display_username(),
&label_prefix
.map(|x| x + authed.display_username())
.unwrap_or_else(|| authed.display_username().to_string()),
&authed.email,
username_to_permissioned_as(&authed.username),
scheduled_for,
@@ -4402,6 +4597,7 @@ async fn get_job_update(
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
Ok(Json(JobUpdate {
running: if !running && record.running {
Some(true)
@@ -4437,6 +4633,7 @@ async fn get_job_update(
.to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
Ok(Json(JobUpdate {
running: Some(false),
completed: Some(true),
@@ -4688,7 +4885,7 @@ async fn get_completed_job<'a>(
schedule_path, permissioned_as, flow_status, raw_flow, is_flow_step, language, started_at, is_skipped,
raw_lock, email, visible_to_owner, mem_peak, tag, priority, result->'wm_labels' as labels FROM completed_job WHERE id = $1 AND workspace_id = $2")
.bind(id)
.bind(w_id)
.bind(&w_id)
.fetch_optional(&db)
.await?;
@@ -4710,6 +4907,9 @@ async fn get_completed_job<'a>(
// )
// .fetch_optional(db)
// .await.ok().flatten().flatten();
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(response)
}
@@ -4804,6 +5004,8 @@ async fn get_completed_job_result(
raw_result.result.map(|x| x.0),
);
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(Json(result).into_response())
}
@@ -4845,6 +5047,9 @@ async fn get_completed_job_result_maybe(
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
Ok(Json(CompletedJobResult {
started: Some(true),
success: Some(res.success),
@@ -4891,7 +5096,7 @@ async fn delete_completed_job<'a>(
require_admin(authed.is_admin, &authed.username)?;
let job_o = sqlx::query_as::<_, CompletedJob>(
"UPDATE completed_job SET args = null, logs = '', result = null, deleted = true WHERE id = $1 AND workspace_id = $2 \
RETURNING *",
RETURNING *, null as labels",
)
.bind(id)
.bind(&w_id)

View File

@@ -7,9 +7,12 @@
*/
use crate::db::ApiAuthed;
#[cfg(feature = "enterprise")]
use crate::ee::ExternalJwks;
#[cfg(feature = "embedding")]
use crate::embeddings::load_embeddings_db;
use crate::oauth2_ee::AllClients;
use crate::smtp_server_ee::SmtpServer;
use crate::tracing_init::MyOnFailure;
use crate::{
oauth2_ee::SlackVerifier,
@@ -74,6 +77,7 @@ mod schedule;
mod scim_ee;
mod scripts;
mod settings;
pub mod smtp_server_ee;
mod static_assets;
mod stripe_ee;
mod tracing_init;
@@ -141,15 +145,27 @@ pub async fn add_webhook_allowed_origin(
next.run(req).await
}
#[cfg(not(feature = "tantivy"))]
type IndexReader = ();
#[cfg(not(feature = "tantivy"))]
type IndexWriter = ();
#[cfg(feature = "tantivy")]
type IndexReader = windmill_indexer::indexer_ee::IndexReader;
#[cfg(feature = "tantivy")]
type IndexWriter = windmill_indexer::indexer_ee::IndexWriter;
pub async fn run_server(
db: DB,
rsmq: Option<rsmq_async::MultiplexedRsmq>,
index_reader: Option<windmill_indexer::indexer_ee::IndexReader>,
index_writer: Option<windmill_indexer::indexer_ee::IndexWriter>,
index_reader: Option<IndexReader>,
index_writer: Option<IndexWriter>,
addr: SocketAddr,
mut rx: tokio::sync::broadcast::Receiver<()>,
port_tx: tokio::sync::oneshot::Sender<String>,
server_mode: bool,
base_internal_url: String,
) -> anyhow::Result<()> {
if let Some(mut rsmq) = rsmq.clone() {
for tag in ALL_TAGS.read().await.iter() {
@@ -164,9 +180,13 @@ pub async fn run_server(
}
let user_db = UserDB::new(db.clone());
#[cfg(feature = "enterprise")]
let ext_jwks = ExternalJwks::load().await;
let auth_cache = Arc::new(users::AuthCache::new(
db.clone(),
std::env::var("SUPERADMIN_SECRET").ok(),
#[cfg(feature = "enterprise")]
ext_jwks,
));
let argon2 = Arc::new(Argon2::default());
@@ -177,8 +197,8 @@ pub async fn run_server(
let middleware_stack = ServiceBuilder::new()
.layer(Extension(db.clone()))
.layer(Extension(rsmq))
.layer(Extension(user_db))
.layer(Extension(rsmq.clone()))
.layer(Extension(user_db.clone()))
.layer(Extension(auth_cache.clone()))
.layer(Extension(index_reader))
.layer(Extension(index_writer))
@@ -197,7 +217,18 @@ pub async fn run_server(
if server_mode {
#[cfg(feature = "embedding")]
load_embeddings_db(&db)
load_embeddings_db(&db);
let smtp_server = Arc::new(SmtpServer {
db: db.clone(),
user_db: user_db,
auth_cache: auth_cache.clone(),
rsmq: rsmq,
base_internal_url: base_internal_url.clone(),
});
if let Err(err) = smtp_server.start_listener_thread(addr).await {
tracing::error!("Error starting SMTP server: {err:#}");
}
}
let job_helpers_service = {
@@ -303,7 +334,7 @@ pub async fn run_server(
)
.nest(
"/w/:workspace_id/capture_u",
capture::global_service().layer(cors),
capture::global_service().layer(cors.clone()),
)
.nest(
"/auth",
@@ -315,7 +346,7 @@ pub async fn run_server(
)
.route("/version", get(git_v))
.route("/uptodate", get(is_up_to_date))
.route("/ee_license", get(ee_license))
.route("/ee_license", get(ee_license).layer(cors.clone()))
.route("/openapi.yaml", get(openapi))
.route("/openapi.json", get(openapi_json)),
)

View File

@@ -52,7 +52,7 @@ use windmill_common::{
utils::{
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
},
worker::to_raw_value,
worker::{get_annotation, to_raw_value},
HUB_BASE_URL,
};
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
@@ -352,13 +352,16 @@ async fn create_snapshot_script(
let mut script_hash = None;
let mut tx = None;
let mut uploaded = false;
while let Some(field) = multipart.next_field().await.unwrap() {
let name = field.name().unwrap().to_string();
let data = field.bytes().await.unwrap();
if name == "script" {
let ns = Some(serde_json::from_slice(&data).map_err(to_anyhow)?);
let ns: NewScript = Some(serde_json::from_slice(&data).map_err(to_anyhow)?).unwrap();
let is_tar = ns.codebase.as_ref().is_some_and(|x| x.ends_with(".tar"));
let (new_hash, ntx) = create_script_internal(
ns.unwrap(),
ns,
w_id.clone(),
authed.clone(),
db.clone(),
@@ -367,7 +370,8 @@ async fn create_snapshot_script(
webhook.clone(),
)
.await?;
script_hash = Some(new_hash.to_string());
let nh = new_hash.to_string();
script_hash = Some(if is_tar { format!("{nh}.tar") } else { nh });
tx = Some(ntx);
}
if name == "file" {
@@ -567,9 +571,12 @@ async fn create_script_internal<'c>(
.as_ref()
.map(|v| v.perms.clone())
.unwrap_or(json!({}));
let lock = if !(ns.language == ScriptLang::Python3
let lock = if ns.codebase.is_some() {
Some(String::new())
} else if !(ns.language == ScriptLang::Python3
|| ns.language == ScriptLang::Go
|| ns.language == ScriptLang::Bun
|| ns.language == ScriptLang::Bunnative
|| ns.language == ScriptLang::Deno
|| ns.language == ScriptLang::Php)
{
@@ -578,13 +585,25 @@ async fn create_script_internal<'c>(
ns.lock
.and_then(|e| if e.is_empty() { None } else { Some(e) })
};
let needs_lock_gen = lock.is_none();
let needs_lock_gen = lock.is_none() && codebase.is_none();
let envs = ns.envs.as_ref().map(|x| x.as_slice());
let envs = if ns.envs.is_none() || ns.envs.as_ref().unwrap().is_empty() {
None
} else {
envs
};
let lang = if &ns.language == &ScriptLang::Bun || &ns.language == &ScriptLang::Bunnative {
let anns = get_annotation(&ns.content);
if anns.native_mode {
ScriptLang::Bunnative
} else {
ScriptLang::Bun
}
} else {
ns.language.clone()
};
sqlx::query!(
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
@@ -604,7 +623,7 @@ async fn create_script_internal<'c>(
ns.is_template.unwrap_or(false),
extra_perms,
lock,
ns.language.clone() as ScriptLang,
lang as ScriptLang,
ns.kind.unwrap_or(ScriptKind::Script) as ScriptKind,
ns.tag,
ns.draft_only,

View File

@@ -24,7 +24,10 @@ use axum::{
use serde::Deserialize;
use windmill_common::{
error::{self, JsonResult, Result},
global_settings::{AUTOMATE_USERNAME_CREATION_SETTING, ENV_SETTINGS, HUB_BASE_URL_SETTING},
global_settings::{
AUTOMATE_USERNAME_CREATION_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
HUB_BASE_URL_SETTING,
},
server::Smtp,
utils::send_email,
};
@@ -116,14 +119,17 @@ pub async fn test_s3_bucket(
let client = build_object_store_from_settings(test_s3_bucket).await?;
let mut list = client.list(Some(&object_store::path::Path::from("".to_string())));
let first_file = list
.next()
.await
.ok_or_else(|| {
error::Error::InternalErr("Failed to list files in blob storage".to_string())
})?
.map_err(|e| anyhow::anyhow!("error listing bucket: {e:#}"))?;
tracing::info!("Listed files: {:?}", first_file);
let first_file = list.next().await;
if first_file.is_some() {
if let Err(e) = first_file.as_ref().unwrap() {
tracing::error!("error listing bucket: {e:#}");
error::Error::InternalErr(format!("Failed to list files in blob storage: {e:#}"));
}
tracing::info!("Listed files: {:?}", first_file.unwrap());
} else {
tracing::info!("No files in blob storage");
}
let path = object_store::path::Path::from(format!(
"/test-s3-bucket-{uuid}",
uuid = uuid::Uuid::new_v4()
@@ -253,6 +259,7 @@ pub async fn get_global_setting(
&& !key.starts_with("default_recovery_handler_")
&& key != AUTOMATE_USERNAME_CREATION_SETTING
&& key != HUB_BASE_URL_SETTING
&& key != EMAIL_DOMAIN_SETTING
{
require_super_admin(&db, &authed.email).await?;
}
@@ -343,7 +350,7 @@ pub async fn renew_license_key() -> Result<String> {
pub async fn renew_license_key(Extension(db): Extension<DB>, authed: ApiAuthed) -> Result<String> {
require_super_admin(&db, &authed.email).await?;
windmill_common::stats_ee::send_stats(&"manual".to_string(), &HTTP_CLIENT, &db).await?;
let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db).await;
let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db, None).await;
if result != "success" {
return Err(error::Error::BadRequest(format!(
@@ -364,7 +371,7 @@ pub async fn create_customer_portal_session() -> Result<String> {
#[cfg(feature = "enterprise")]
pub async fn create_customer_portal_session() -> Result<String> {
let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT).await?;
let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT, None).await?;
return Ok(url);
}

View File

@@ -0,0 +1,17 @@
use crate::{db::DB, users::AuthCache};
use std::{net::SocketAddr, sync::Arc};
use windmill_common::db::UserDB;
pub struct SmtpServer {
pub auth_cache: Arc<AuthCache>,
pub db: DB,
pub user_db: UserDB,
pub rsmq: Option<rsmq_async::MultiplexedRsmq>,
pub base_internal_url: String,
}
impl SmtpServer {
pub async fn start_listener_thread(self: Arc<Self>, _addr: SocketAddr) -> anyhow::Result<()> {
Err(anyhow::anyhow!("Implementation not open source"))
}
}

View File

@@ -12,6 +12,8 @@ use std::sync::Arc;
use crate::db::ApiAuthed;
#[cfg(feature = "enterprise")]
use crate::ee::ExternalJwks;
use crate::oauth2_ee::{check_nb_of_user, InstanceEvent};
use crate::utils::{
generate_instance_wide_unique_username, get_and_delete_pending_username_or_generate,
@@ -39,7 +41,10 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use time::OffsetDateTime;
#[cfg(feature = "enterprise")]
use tokio::sync::RwLock;
use tower_cookies::{Cookie, Cookies};
use tower_http::cors::{Any, CorsLayer};
use tracing::{Instrument, Span};
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
use windmill_audit::ActionKind;
@@ -62,6 +67,11 @@ const COOKIE_NAME: &str = "token";
const COOKIE_PATH: &str = "/";
pub fn workspaced_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([http::Method::GET])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new()
.route("/list", get(list_users))
.route("/list_usage", get(list_user_usage))
@@ -72,16 +82,21 @@ pub fn workspaced_service() -> Router {
.route("/delete/:user", delete(delete_workspace_user))
.route("/is_owner/*path", get(is_owner_of_path))
.route("/whois/:username", get(whois))
.route("/whoami", get(whoami))
.route("/whoami", get(whoami).layer(cors))
.route("/leave", post(leave_workspace))
.route("/username_to_email/:username", get(username_to_email))
}
pub fn global_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([http::Method::GET])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new()
.route("/exists/:email", get(exists_email))
.route("/email", get(get_email))
.route("/whoami", get(global_whoami))
.route("/whoami", get(global_whoami).layer(cors))
.route("/list_invites", get(list_invites))
.route("/decline_invite", post(decline_invite))
.route("/accept_invite", post(accept_invite))
@@ -114,8 +129,13 @@ pub fn global_service() -> Router {
}
pub fn make_unauthed_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([http::Method::POST])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new()
.route("/login", post(login))
.route("/login", post(login).layer(cors))
.route("/logout", post(logout))
.route("/logout", get(logout))
}
@@ -145,11 +165,23 @@ pub struct AuthCache {
cache: Cache<(String, String), ExpiringAuthCache>,
db: DB,
superadmin_secret: Option<String>,
#[cfg(feature = "enterprise")]
ext_jwks: Option<Arc<RwLock<ExternalJwks>>>,
}
impl AuthCache {
pub fn new(db: DB, superadmin_secret: Option<String>) -> Self {
AuthCache { cache: Cache::new(300), db, superadmin_secret }
pub fn new(
db: DB,
superadmin_secret: Option<String>,
#[cfg(feature = "enterprise")] ext_jwks: Option<Arc<RwLock<ExternalJwks>>>,
) -> Self {
AuthCache {
cache: Cache::new(300),
db,
superadmin_secret,
#[cfg(feature = "enterprise")]
ext_jwks,
}
}
pub async fn invalidate(&self, w_id: &str, token: String) {
@@ -168,9 +200,19 @@ impl AuthCache {
}
#[cfg(feature = "enterprise")]
_ if token.starts_with("jwt_ext_") => {
let authed_and_exp =
crate::ee::jwt_ext_auth(w_id.as_ref(), token.trim_start_matches("jwt_ext_"))
.await;
let authed_and_exp = match crate::ee::jwt_ext_auth(
w_id.as_ref(),
token.trim_start_matches("jwt_ext_"),
self.ext_jwks.clone(),
)
.await
{
Ok(r) => Some(r),
Err(e) => {
tracing::error!("JWT_EXT auth error: {:?}", e);
None
}
};
if let Some((authed, exp)) = authed_and_exp.clone() {
self.cache.insert(

View File

@@ -25,7 +25,7 @@ use windmill_audit::ActionKind;
use windmill_common::{
db::UserDB,
error::{Error, JsonResult, Result},
utils::{not_found_if_none, StripPath},
utils::{not_found_if_none, paginate, Pagination, StripPath},
variables::{
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
},
@@ -92,7 +92,10 @@ async fn list_variables(
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Query(lq): Query<ListVariableQuery>,
Query(pagination): Query<Pagination>,
) -> JsonResult<Vec<ListableVariable>> {
let (per_page, offset) = paginate(pagination);
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query_as::<_, ListableVariable>(
@@ -100,17 +103,22 @@ async fn list_variables(
is_secret, variable.description, variable.extra_perms, account, is_oauth, (now() > account.expires_at) as is_expired,
account.refresh_error,
resource.path IS NOT NULL as is_linked,
account.refresh_token != '' as is_refreshed
account.refresh_token != '' as is_refreshed,
variable.expires_at
from variable
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%'
AND variable.path LIKE $3 || '%'
ORDER BY path",
ORDER BY path
LIMIT $4 OFFSET $5
",
)
.bind(&w_id)
.bind(&authed.username)
.bind(&lq.path_start.unwrap_or_default())
.bind(per_page as i32)
.bind(offset as i32)
.fetch_all(&mut *tx)
.await?;
@@ -310,8 +318,8 @@ async fn create_variable(
sqlx::query!(
"INSERT INTO variable
(workspace_id, path, value, is_secret, description, account, is_oauth)
VALUES ($1, $2, $3, $4, $5, $6, $7)",
(workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
&w_id,
variable.path,
value,
@@ -319,6 +327,7 @@ async fn create_variable(
variable.description,
variable.account,
variable.is_oauth.unwrap_or(false),
variable.expires_at
)
.execute(&mut *tx)
.await?;

View File

@@ -21,6 +21,8 @@ use crate::{
webhook_util::WebhookShared,
};
use tower_http::cors::{Any, CorsLayer};
use axum::{
extract::{Extension, Path, Query},
response::IntoResponse,
@@ -42,7 +44,7 @@ use windmill_common::schedule::Schedule;
use windmill_common::users::username_to_permissioned_as;
use windmill_common::variables::build_crypt;
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
use windmill_common::workspaces::WorkspaceGitSyncSettings;
use windmill_common::workspaces::{WorkspaceDeploymentUISettings, WorkspaceGitSyncSettings};
use windmill_common::{
error::{to_anyhow, Error, JsonResult, Result},
flows::Flow,
@@ -97,6 +99,7 @@ pub fn workspaced_service() -> Router {
post(edit_large_file_storage_config),
)
.route("/edit_git_sync_config", post(edit_git_sync_config))
.route("/edit_deploy_ui_config", post(edit_deploy_ui_config))
.route("/edit_default_app", post(edit_default_app))
.route("/default_app", get(get_default_app))
.route(
@@ -123,10 +126,15 @@ pub fn workspaced_service() -> Router {
router
}
pub fn global_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([http::Method::GET])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new()
.route("/list_as_superadmin", get(list_workspaces_as_super_admin))
.route("/list", get(list_workspaces))
.route("/users", get(user_workspaces))
.route("/users", get(user_workspaces).layer(cors.clone()))
.route("/create", post(create_workspace))
.route("/exists", post(exists_workspace))
.route("/exists_username", post(exists_username))
@@ -169,6 +177,7 @@ pub struct WorkspaceSettings {
pub error_handler_muted_on_cancel: Option<bool>,
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
pub deploy_ui: Option<serde_json::Value>, // effectively: WorkspaceDeploymentUISettings
pub default_app: Option<String>,
pub automatic_billing: bool,
pub default_scripts: Option<serde_json::Value>,
@@ -1052,6 +1061,72 @@ async fn edit_git_sync_config(
Ok(format!("Edit git sync config for workspace {}", &w_id))
}
#[derive(Deserialize)]
struct EditDeployUIConfig {
deploy_ui_settings: Option<WorkspaceDeploymentUISettings>,
}
#[cfg(not(feature = "enterprise"))]
async fn edit_deploy_ui_config(
_authed: ApiAuthed,
Extension(_db): Extension<DB>,
Path(_w_id): Path<String>,
Json(_new_config): Json<EditDeployUIConfig>,
) -> Result<String> {
return Err(Error::BadRequest(
"Deployment UI is only available on Windmill Enterprise Edition".to_string(),
));
}
#[cfg(feature = "enterprise")]
async fn edit_deploy_ui_config(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
ApiAuthed { is_admin, username, .. }: ApiAuthed,
Json(new_config): Json<EditDeployUIConfig>,
) -> Result<String> {
require_admin(is_admin, &username)?;
let mut tx = db.begin().await?;
let args_for_audit = format!("{:?}", new_config.deploy_ui_settings);
audit_log(
&mut *tx,
&authed,
"workspaces.edit_deploy_ui_config",
ActionKind::Update,
&w_id,
Some(&authed.email),
Some([("deployment_ui_settings", args_for_audit.as_str())].into()),
)
.await?;
if let Some(deploy_ui_settings) = new_config.deploy_ui_settings {
let serialized_config =
serde_json::to_value::<WorkspaceDeploymentUISettings>(deploy_ui_settings)
.map_err(|err| Error::InternalErr(err.to_string()))?;
sqlx::query!(
"UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2",
serialized_config,
&w_id
)
.execute(&mut *tx)
.await?;
} else {
sqlx::query!(
"UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1",
&w_id,
)
.execute(&mut *tx)
.await?;
}
tx.commit().await?;
Ok(format!("Edit deployment UI config for workspace {}", &w_id))
}
#[derive(Deserialize)]
pub struct EditDefaultApp {
pub default_app_path: Option<String>,
@@ -2498,7 +2573,7 @@ async fn tarball_workspace(
ScriptLang::Mssql => "ms.sql",
ScriptLang::Graphql => "gql",
ScriptLang::Nativets => "fetch.ts",
ScriptLang::Bun => {
ScriptLang::Bun | ScriptLang::Bunnative => {
if default_ts.as_ref().is_some_and(|x| x == "bun") {
"ts"
} else {
@@ -2599,9 +2674,9 @@ async fn tarball_workspace(
if !skip_variables.unwrap_or(false) {
let variables =
sqlx::query_as::<_, ExportableListableVariable>(if !skip_secrets.unwrap_or(false) {
"SELECT * FROM variable WHERE workspace_id = $1 AND path NOT LIKE 'u/%/secret_arg/%'"
"SELECT * FROM variable WHERE workspace_id = $1 AND expires_at IS NULL"
} else {
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND path NOT LIKE 'u/%/secret_arg/%'"
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND expires_at IS NULL"
})
.bind(&w_id)
.fetch_all(&mut *tx)

View File

@@ -11,7 +11,7 @@ jemalloc = ["dep:tikv-jemalloc-ctl"]
prometheus = ["dep:prometheus"]
flamegraph = ["dep:tracing-flame"]
loki = ["dep:tracing-loki"]
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts", "dep:bytes"]
parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"]
[lib]
name = "windmill_common"
@@ -48,8 +48,10 @@ prometheus = { workspace = true, optional = true }
aws-config = { workspace = true, optional = true }
aws-sdk-sts = { workspace = true, optional = true }
indexmap.workspace = true
bytes = { workspace = true, optional = true }
bytes.workspace = true
mail-send.workspace = true
futures-core.workspace = true
async-stream.workspace = true
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemalloc-ctl = { optional = true, workspace = true }

View File

@@ -34,7 +34,11 @@ pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::d
}
#[cfg(feature = "enterprise")]
pub async fn renew_license_key(_http_client: &reqwest::Client, _db: &crate::db::DB) -> String {
pub async fn renew_license_key(
_http_client: &reqwest::Client,
_db: &crate::db::DB,
_key: Option<String>,
) -> String {
// Implementation is not open source
"".to_string()
}
@@ -42,6 +46,7 @@ pub async fn renew_license_key(_http_client: &reqwest::Client, _db: &crate::db::
#[cfg(feature = "enterprise")]
pub async fn create_customer_portal_session(
_http_client: &reqwest::Client,
_key: Option<String>,
) -> error::Result<String> {
// Implementation is not open source
Ok("".to_string())

View File

@@ -38,22 +38,26 @@ pub struct Flow {
pub dedicated_worker: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(skip_serializing_if = "is_none_or_false")]
pub ws_error_handler_muted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(skip_serializing_if = "is_none_or_false")]
pub visible_to_runner_only: Option<bool>,
}
fn is_none_or_false(b: &Option<bool>) -> bool {
b.is_none() || !b.unwrap()
}
#[derive(Serialize, sqlx::FromRow)]
pub struct ListableFlow {
pub workspace_id: String,
pub path: String,
pub summary: String,
pub description: String,
pub edited_by: String,
pub edited_at: chrono::DateTime<chrono::Utc>,
pub edited_by: Option<String>,
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
pub archived: bool,
pub extra_perms: serde_json::Value,
pub starred: bool,
@@ -406,7 +410,7 @@ pub enum FlowModuleValue {
lock: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(skip_serializing_if = "is_none_or_empty")]
tag: Option<String>,
language: ScriptLang,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -419,6 +423,10 @@ pub enum FlowModuleValue {
Identity,
}
fn is_none_or_empty(expr: &Option<String>) -> bool {
expr.is_none() || expr.as_ref().unwrap().is_empty()
}
#[derive(Deserialize)]
struct UntaggedFlowModuleValue {
#[serde(rename = "type")]

View File

@@ -1,5 +1,6 @@
pub const CUSTOM_TAGS_SETTING: &str = "custom_tags";
pub const DEFAULT_TAGS_PER_WORKSPACE_SETTING: &str = "default_tags_per_workspace";
pub const DEFAULT_TAGS_WORKSPACES_SETTING: &str = "default_tags_workspaces";
pub const BASE_URL_SETTING: &str = "base_url";
pub const OAUTH_SETTING: &str = "oauths";
pub const RETENTION_PERIOD_SECS_SETTING: &str = "retention_period_secs";
@@ -27,6 +28,7 @@ pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
pub const DEV_INSTANCE_SETTING: &str = "dev_instance";
pub const JWT_SECRET_SETTING: &str = "jwt_secret";
pub const EMAIL_DOMAIN_SETTING: &str = "email_domain";
pub const ENV_SETTINGS: [&str; 50] = [
"DISABLE_NSJAIL",

View File

@@ -1,20 +1,23 @@
use std::collections::HashMap;
use bytes::Bytes;
use futures_core::Stream;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use sqlx::{types::Json, Pool, Postgres, Transaction};
use tokio::io::AsyncReadExt;
use uuid::Uuid;
pub const ENTRYPOINT_OVERRIDE: &str = "_ENTRYPOINT_OVERRIDE";
use crate::{
error::{self, Error},
error::{self, to_anyhow, Error},
flow_status::{FlowStatus, RestartedFrom},
flows::{FlowValue, Retry},
get_latest_deployed_hash_for_path,
scripts::{ScriptHash, ScriptLang},
worker::to_raw_value,
worker::{to_raw_value, TMP_DIR},
};
#[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Clone)]
@@ -580,3 +583,75 @@ pub fn format_completed_job_result(mut cj: CompletedJob) -> CompletedJobWithForm
cj.result = None; // very important to avoid sending the result twice
CompletedJobWithFormattedResult { cj, result: Some(sql_result) }
}
pub async fn get_logs_from_disk(
log_offset: i32,
logs: &str,
log_file_index: &Option<Vec<String>>,
) -> Option<impl Stream<Item = Result<Bytes, anyhow::Error>>> {
if log_offset > 0 {
if let Some(file_index) = log_file_index.clone() {
for file_p in &file_index {
if !tokio::fs::metadata(format!("{TMP_DIR}/{file_p}"))
.await
.is_ok()
{
return None;
}
}
let logs = logs.to_string();
let stream = async_stream::stream! {
for file_p in file_index.clone() {
let mut file = tokio::fs::File::open(format!("{TMP_DIR}/{file_p}")).await.map_err(to_anyhow)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await.map_err(to_anyhow)?;
yield Ok(bytes::Bytes::from(buffer)) as anyhow::Result<bytes::Bytes>;
}
yield Ok(bytes::Bytes::from(logs))
};
return Some(stream);
}
}
return None;
}
#[cfg(all(feature = "enterprise", feature = "parquet"))]
pub async fn get_logs_from_store(
log_offset: i32,
logs: &str,
log_file_index: &Option<Vec<String>>,
) -> Option<impl Stream<Item = Result<Bytes, object_store::Error>>> {
use crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS;
if log_offset > 0 {
if let Some(file_index) = log_file_index.clone() {
tracing::debug!("Getting logs from store: {file_index:?}");
if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
tracing::debug!("object store client present, streaming from there");
let logs = logs.to_string();
let stream = async_stream::stream! {
for file_p in file_index.clone() {
let file_p_2 = file_p.clone();
let file = os.get(&object_store::path::Path::from(file_p)).await;
if let Ok(file) = file {
if let Ok(bytes) = file.bytes().await {
yield Ok(bytes::Bytes::from(bytes)) as object_store::Result<bytes::Bytes>;
}
} else {
tracing::debug!("error getting file from store: {file_p_2}: {}", file.err().unwrap());
}
}
yield Ok(bytes::Bytes::from(logs))
};
return Some(stream);
} else {
tracing::debug!("object store client not present, cannot stream logs from store");
}
}
}
return None;
}

View File

@@ -79,6 +79,9 @@ lazy_static::lazy_static! {
pub static ref CRITICAL_ERROR_CHANNELS: Arc<RwLock<Vec<CriticalErrorChannel>>> = Arc::new(RwLock::new(vec![]));
pub static ref JOB_RETENTION_SECS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));
}
pub async fn shutdown_signal(
@@ -219,11 +222,12 @@ pub async fn connect_db(
DEFAULT_MAX_CONNECTIONS_INDEXER
} else {
DEFAULT_MAX_CONNECTIONS_WORKER
* std::env::var("NUM_WORKERS")
+ std::env::var("NUM_WORKERS")
.ok()
.map(|x| x.parse().ok())
.flatten()
.unwrap_or(1)
- 1
}
}
}

View File

@@ -34,6 +34,7 @@ pub enum ScriptLang {
Powershell,
Postgresql,
Bun,
Bunnative,
Mysql,
Bigquery,
Snowflake,
@@ -46,6 +47,7 @@ impl ScriptLang {
pub fn as_str(&self) -> &'static str {
match self {
ScriptLang::Bun => "bun",
ScriptLang::Bunnative => "bunnative",
ScriptLang::Nativets => "nativets",
ScriptLang::Deno => "deno",
ScriptLang::Python3 => "python3",
@@ -131,6 +133,7 @@ impl Display for ScriptKind {
}
pub const PREVIEW_IS_CODEBASE_HASH: i64 = -42;
pub const PREVIEW_IS_TAR_CODEBASE_HASH: i64 = -43;
#[derive(Serialize, sqlx::FromRow)]
pub struct Script {
@@ -148,6 +151,7 @@ pub struct Script {
pub deleted: bool,
pub is_template: bool,
pub extra_perms: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub lock: Option<String>,
pub lock_error_logs: Option<String>,
pub language: ScriptLang,

View File

@@ -40,10 +40,10 @@ pub struct ListableVariable {
pub is_refreshed: Option<bool>,
pub refresh_error: Option<String>,
pub is_linked: Option<bool>,
pub expires_at: Option<chrono::DateTime<Utc>>,
}
#[derive(Serialize, Deserialize, sqlx::FromRow)]
pub struct ExportableListableVariable {
pub workspace_id: String,
pub path: String,
@@ -51,8 +51,16 @@ pub struct ExportableListableVariable {
pub is_secret: bool,
pub description: String,
pub extra_perms: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub account: Option<i32>,
#[serde(skip_serializing_if = "is_none_or_false")]
pub is_oauth: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<chrono::DateTime<Utc>>,
}
fn is_none_or_false(b: &Option<bool>) -> bool {
b.is_none() || !b.unwrap()
}
#[derive(Deserialize)]
@@ -63,6 +71,7 @@ pub struct CreateVariable {
pub description: String,
pub account: Option<i32>,
pub is_oauth: Option<bool>,
pub expires_at: Option<chrono::DateTime<Utc>>,
}
pub async fn build_crypt(db: &DB, w_id: &str) -> crate::error::Result<MagicCrypt256> {

View File

@@ -43,6 +43,7 @@ lazy_static::lazy_static! {
];
pub static ref DEFAULT_TAGS_PER_WORKSPACE: AtomicBool = AtomicBool::new(false);
pub static ref DEFAULT_TAGS_WORKSPACES: Arc<RwLock<Option<Vec<String>>>> = Arc::new(RwLock::new(None));
pub static ref WORKER_CONFIG: Arc<RwLock<WorkerConfig>> = Arc::new(RwLock::new(WorkerConfig {
@@ -56,6 +57,10 @@ lazy_static::lazy_static! {
env_vars: Default::default(),
}));
pub static ref WORKER_PULL_QUERIES: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(vec![]));
pub static ref WORKER_SUSPENDED_PULL_QUERY: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
pub static ref SERVER_CONFIG: Arc<RwLock<ServerConfig>> = Arc::new(RwLock::new(ServerConfig { smtp: Default::default(), timeout_wait_result: 20 }));
@@ -75,6 +80,63 @@ lazy_static::lazy_static! {
}
pub async fn make_suspended_pull_query(wc: &WorkerConfig) {
let query = format!(
"UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
, last_ping = now()
, suspend_until = null
WHERE id = (
SELECT id
FROM queue
WHERE suspend_until IS NOT NULL AND (suspend <= 0 OR suspend_until <= now()) AND tag IN ({})
ORDER BY priority DESC NULLS LAST, created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for,
running, script_hash, script_path, args, null as logs, raw_code, canceled, canceled_by,
canceled_reason, last_ping, job_kind, schedule_path, permissioned_as,
flow_status, raw_flow, is_flow_step, language, suspend, suspend_until,
same_worker, raw_lock, pre_run_error, email, visible_to_owner, mem_peak,
root_job, leaf_jobs, tag, concurrent_limit, concurrency_time_window_s,
timeout, flow_step_id, cache_ttl, priority", wc.worker_tags.iter().map(|x| format!("'{x}'")).join(", "));
let mut l = WORKER_SUSPENDED_PULL_QUERY.write().await;
*l = query;
}
pub async fn make_pull_query(wc: &WorkerConfig) {
let mut queries = vec![];
for tags in wc.priority_tags_sorted.iter() {
let query = format!("UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
, last_ping = now()
, suspend_until = null
WHERE id = (
SELECT id
FROM queue
WHERE running = false AND tag IN ({}) AND scheduled_for <= now()
ORDER BY priority DESC NULLS LAST, scheduled_for
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for,
running, script_hash, script_path, args, null as logs, raw_code, canceled, canceled_by,
canceled_reason, last_ping, job_kind, schedule_path, permissioned_as,
flow_status, raw_flow, is_flow_step, language, suspend, suspend_until,
same_worker, raw_lock, pre_run_error, email, visible_to_owner, mem_peak,
root_job, leaf_jobs, tag, concurrent_limit, concurrency_time_window_s,
timeout, flow_step_id, cache_ttl, priority", tags.tags.iter().map(|x| format!("'{x}'")).join(", "));
queries.push(query);
}
let mut l = WORKER_PULL_QUERIES.write().await;
*l = queries;
}
pub const TMP_DIR: &str = "/tmp/windmill";
pub async fn reload_custom_tags_setting(db: &DB) -> error::Result<()> {
@@ -152,6 +214,144 @@ fn parse_file<T: FromStr>(path: &str) -> Option<T> {
.flatten()
}
pub struct Annotations {
pub npm_mode: bool,
pub nodejs_mode: bool,
pub native_mode: bool,
pub nobundling: bool,
}
pub fn get_annotation(inner_content: &str) -> Annotations {
let annotations = inner_content
.lines()
.take_while(|x| x.starts_with("//"))
.map(|x| x.to_string().replace("//", "").trim().to_string())
.collect_vec();
let nodejs_mode: bool = annotations.contains(&"nodejs".to_string());
let npm_mode: bool = annotations.contains(&"npm".to_string());
let native_mode: bool = annotations.contains(&"native".to_string());
//TODO: remove || npm_mode when bun build is more powerful
let nobundling: bool = annotations.contains(&"nobundling".to_string()) || nodejs_mode;
Annotations { npm_mode, nodejs_mode, native_mode, nobundling }
}
pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) {
if tokio::fs::metadata(&bin_path).await.is_ok() {
(true, format!("loaded from local cache: {}\n", bin_path))
} else {
#[cfg(all(feature = "enterprise", feature = "parquet"))]
if let Some(os) = crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
.read()
.await
.clone()
{
let started = std::time::Instant::now();
use crate::s3_helpers::attempt_fetch_bytes;
if let Ok(mut x) = attempt_fetch_bytes(os, _remote_path).await {
if let Err(e) = write_binary_file(bin_path, &mut x) {
tracing::error!("could not write bundle/bin file locally: {e:?}");
return (
false,
"error writing bundle/bin file from object store".to_string(),
);
}
tracing::info!("loaded from object store {}", bin_path);
return (
true,
format!(
"loaded bin/bundle from object store {} in {}ms",
bin_path,
started.elapsed().as_millis()
),
);
}
}
(false, "".to_string())
}
}
pub async fn exists_in_cache(bin_path: &str, _remote_path: &str) -> bool {
if tokio::fs::metadata(&bin_path).await.is_ok() {
return true;
} else {
#[cfg(all(feature = "enterprise", feature = "parquet"))]
if let Some(os) = crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
.read()
.await
.clone()
{
return os
.get(&object_store::path::Path::from(_remote_path))
.await
.is_ok();
}
return false;
}
}
pub async fn save_cache(
local_cache_path: &str,
_remote_cache_path: &str,
origin: &str,
) -> crate::error::Result<String> {
let mut _cached_to_s3 = false;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
if let Some(os) = crate::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
.read()
.await
.clone()
{
use object_store::path::Path;
if let Err(e) = os
.put(
&Path::from(_remote_cache_path),
std::fs::read(origin)?.into(),
)
.await
{
tracing::error!(
"Failed to put go bin to object store: {_remote_cache_path}. Error: {:?}",
e
);
} else {
_cached_to_s3 = true;
}
}
// if !*CLOUD_HOSTED {
if true {
std::fs::copy(origin, local_cache_path)?;
Ok(format!(
"\nwrote cached binary: {} (backed by EE distributed object store: {_cached_to_s3})\n",
local_cache_path
))
} else if _cached_to_s3 {
Ok(format!(
"wrote cached binary to object store {}\n",
local_cache_path
))
} else {
Ok("".to_string())
}
}
#[cfg(all(feature = "enterprise", feature = "parquet"))]
fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Result<()> {
use std::fs::{File, Permissions};
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
let mut file = File::create(main_path)?;
file.write_all(byts)?;
file.set_permissions(Permissions::from_mode(0o755))?;
file.flush()?;
Ok(())
}
fn get_cgroupv2_path() -> Option<String> {
let cgroup_path: String = parse_file("/proc/self/cgroup")?;
@@ -186,9 +386,9 @@ pub fn get_vcpus() -> Option<i64> {
}
pub fn get_memory() -> Option<i64> {
if Path::new("/sys/fs/cgroup/memory/memory.max").exists() {
if Path::new("/sys/fs/cgroup/memory/memory.limit_in_bytes").exists() {
// cgroup v1
parse_file("/sys/fs/cgroup/memory/memory.max")
parse_file("/sys/fs/cgroup/memory/memory.limit_in_bytes")
} else {
// cgroup v2
let cgroup_path = get_cgroupv2_path()?;

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