Compare commits

...

187 Commits

Author SHA1 Message Date
Ruben Fiszel
4fceae4f85 progress 2023-02-01 20:54:13 +01:00
Ruben Fiszel
c1cfed840a all 2023-02-01 16:27:50 +01:00
Ruben Fiszel
0921ba0085 feat: add INCLUDE_HEADERS env variable to pass value from request headers 2023-02-01 02:22:28 +01:00
Ruben Fiszel
14b32be8b2 feat(python): add ADDITIONAL_PYTHON_PATHS 2023-02-01 02:04:53 +01:00
Ruben Fiszel
b67550a790 fix nextId for components 2023-01-31 20:49:02 +01:00
dependabot[bot]
06347746c0 chore(deps-dev): bump @sveltejs/package from 1.0.1 to 1.0.2 in /frontend (#1165)
Bumps [@sveltejs/package](https://github.com/sveltejs/kit/tree/HEAD/packages/package) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/master/packages/package/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/package@1.0.2/packages/package)

---
updated-dependencies:
- dependency-name: "@sveltejs/package"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 20:05:16 +01:00
dependabot[bot]
0b4288ad9c chore(deps-dev): bump vite from 4.0.3 to 4.0.4 in /frontend (#1166)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.0.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 20:05:00 +01:00
Ruben Fiszel
a6dcb218ff ci: seperate openbb group from main 2023-01-31 20:04:20 +01:00
Ruben Fiszel
6fe93dac58 chore(main): release 1.61.1 (#1169)
* chore(main): release 1.61.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-01-31 20:02:29 +01:00
Ruben Fiszel
202b04bb08 fix nextId for components 2023-01-31 20:01:48 +01:00
Ruben Fiszel
df8cc1f248 fix(backend): compile issue 2023-01-31 19:50:22 +01:00
Ruben Fiszel
61e3214f9e chore(main): release 1.61.0 (#1091)
* chore(main): release 1.61.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-01-31 18:51:42 +01:00
Ruben Fiszel
d663884a99 more folders permission 2023-01-31 16:01:13 +01:00
Ruben Fiszel
a2ed69f87f fix demoting users 2023-01-31 15:23:58 +01:00
Ruben Fiszel
f377c84f5a feat: add sync webhook for flows 2023-01-31 10:21:48 +01:00
Ruben Fiszel
772c768cda feat(python): add support for extra args in python 2023-01-31 09:52:29 +01:00
Ruben Fiszel
a4f8f9e1cf feat(frontend): add plotly support 2023-01-31 08:04:46 +01:00
dependabot[bot]
dfae34d9b4 chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 (#1163)
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.5 to 1.3.6.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.5...v1.3.6)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 18:32:13 +00:00
Ruben Fiszel
0bc995bd07 clarify exponential backoff 2023-01-30 08:58:09 +01:00
Ruben Fiszel
161e76c500 clarify exponential backoff 2023-01-30 08:49:58 +01:00
Ruben Fiszel
c186db4e3b handle backquotes in template 2023-01-30 08:31:21 +01:00
Ruben Fiszel
652eb65082 allow whitespace in requirements override 2023-01-30 05:06:13 +01:00
Ruben Fiszel
a98ba6dc41 parseJson on resource changes 2023-01-30 03:55:01 +01:00
Ruben Fiszel
080ecb04d7 feat: add resource and resource type from json 2023-01-29 16:27:51 +01:00
Ruben Fiszel
8de3804d4a flow builder args handling improvements 2023-01-28 23:22:51 +01:00
Ruben Fiszel
008bd32ec1 fix entirecachetar path 2023-01-28 18:54:04 +01:00
Ruben Fiszel
a2f679ffc9 add support for openbb 2023-01-28 03:57:25 +01:00
Ruben Fiszel
2d3fe64bc4 add python-310 target 2023-01-28 03:34:40 +01:00
Ruben Fiszel
b7db4c78c4 feat: add PIP_LOCAL_DEPENDENCIES 2023-01-28 03:09:47 +01:00
Ruben Fiszel
d0fc40b483 grant all privilege to table favorite 2023-01-27 22:08:46 +01:00
Ruben Fiszel
1efae55d36 update stripe products references 2023-01-27 20:59:05 +01:00
Ruben Fiszel
791a772199 increase token size for refresh token 2023-01-27 18:56:42 +01:00
Ruben Fiszel
285b896a1d add stripe integration 2023-01-27 07:58:16 +01:00
Ruben Fiszel
098cd96655 add stripe integration 2023-01-27 07:29:51 +01:00
Adam Vollrath
4eee71a8fb Enable GitHub OAuth for Uffizzi Previews. (#1157)
* Enable GitHub OAuth for Uffizzi Previews.

* empty

* more specific build conditions

* Increase memory resources for `windmill` container.
2023-01-26 18:48:28 +01:00
Ádám Kovács
c01bf70f62 feat(frontend): Add HTML result rendering (#1160)
* feat(frontend): Add HTML result rendering

* fix(frontend): Add reference for XSS

* fix(frontend): Require approval only in apps

* fix(frontend): Require approval in public apps
2023-01-26 18:03:25 +01:00
Faton Ramadani
3ed16b88a4 feat(frontend): Add app inputs configurations (#1142)
* feat(frontend): Add app inputs configurations

* feat(frontend): only display resource input by default

* feat(frontend): Fix no resource field empty display

* feat(frontend): open the drawer when forked from the hub
2023-01-26 15:51:38 +01:00
Ádám Kovács
18f30c8286 fix(frontend): Update app scripts pane (#1146)
* fix(frontend): Update app scripts pane

* fix wrapping content

* import capitalize util

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
2023-01-26 15:51:09 +01:00
Ádám Kovács
f221a6c17f fix(frontend): Add highlight to selected workspace (#1159) 2023-01-26 15:11:29 +01:00
Ruben Fiszel
51a710ffa0 do not copy necessarily for the flow object viewer 2023-01-26 03:20:01 +01:00
Ruben Fiszel
ad0c836129 add instructions for triggers on flows 2023-01-26 01:06:53 +01:00
Ruben Fiszel
be6707b461 for python change default editor tab to 4 2023-01-25 20:40:40 +01:00
Ruben Fiszel
c227ba2794 loader for flowjobresult 2023-01-25 20:33:33 +01:00
Ruben Fiszel
d60a770eb7 flow preview status viewer display more info 2023-01-25 14:14:14 +01:00
Ruben Fiszel
30d83a942c restrict number of jobs in the queue for free users 2023-01-25 13:34:08 +01:00
Ruben Fiszel
98bf9355dc implement MAX_FREE_CONCURRENT_RUNS 2023-01-25 12:58:29 +01:00
Ruben Fiszel
a3928101af worker client should use internal url 2023-01-25 00:10:37 +01:00
Ruben Fiszel
6e77757939 nodraft for editing flow from run 2023-01-24 22:57:34 +01:00
Ruben Fiszel
66c5d6e400 fix arg inputs 2023-01-24 22:44:52 +01:00
Ruben Fiszel
b0c184767c update pricing page 2023-01-24 21:58:44 +01:00
Ruben Fiszel
1165cf6e24 UX improvements for flows 2023-01-24 21:53:41 +01:00
Ruben Fiszel
a9640107f3 background scripts now part of the policy 2023-01-24 15:09:27 +01:00
Faton Ramadani
73bc13bb7d feat(frontend): Open debug runs from component (#1155)
* feat(frontend): Add debug runs

* feat(frontend): Fix build

* feat(frontend): revert implementation

* feat(frontend): Fix error displa

* feat(frontend): Fix error display

* feat(frontend): Fix preview mode

* feat(frontend): Fix error display
2023-01-24 14:34:19 +01:00
Ádám Kovács
e98966283d fix(frontend): App table action button cell (#1149)
Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
2023-01-24 12:20:27 +01:00
Ruben Fiszel
8ad2344ec6 improve cancel approval error message 2023-01-24 12:11:39 +01:00
Ruben Fiszel
b77c239f30 fix: improvements for error handling as first step of flow 2023-01-24 11:16:44 +01:00
Ruben Fiszel
15cb83936b error handler improvements 2023-01-24 07:30:07 +01:00
Ruben Fiszel
e2157ee1c0 fix listing runs from schedule 2023-01-24 06:55:11 +01:00
dependabot[bot]
bdfd50b970 chore(deps): bump @leeoniya/ufuzzy from 0.9.0 to 0.9.1 in /frontend (#1153)
Bumps [@leeoniya/ufuzzy](https://github.com/leeoniya/uFuzzy) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/leeoniya/uFuzzy/releases)
- [Commits](https://github.com/leeoniya/uFuzzy/compare/0.9.0...0.9.1)

---
updated-dependencies:
- dependency-name: "@leeoniya/ufuzzy"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 04:04:34 +01:00
dependabot[bot]
4debfd68dc chore(deps-dev): bump @typescript-eslint/eslint-plugin in /frontend (#1151)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.46.1 to 5.49.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.49.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 04:03:59 +01:00
dependabot[bot]
410032060a chore(deps): bump chartjs-adapter-date-fns in /frontend (#1154)
Bumps [chartjs-adapter-date-fns](https://github.com/chartjs/chartjs-adapter-date-fns) from 2.0.0 to 3.0.0.
- [Release notes](https://github.com/chartjs/chartjs-adapter-date-fns/releases)
- [Commits](https://github.com/chartjs/chartjs-adapter-date-fns/compare/v2.0.0...v3.0.0)

---
updated-dependencies:
- dependency-name: chartjs-adapter-date-fns
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 04:03:32 +01:00
Faton Ramadani
22b8fed9d9 feat(frontend): Display error as an icon in order to avoid clutter wh… (#1143)
* feat(frontend): Display error as an icon in order to avoid clutter when an error occures

* feat(frontend): Fix build

* feat(frontend): Fix error icon position
2023-01-23 18:03:31 +01:00
Ruben Fiszel
0172587b12 feat: add sql as a valid type in Python 2023-01-23 15:58:33 +01:00
Faton Ramadani
f05f9e4edb feat(frontend): add selectedRowIndex to the table outputs (#1145) 2023-01-23 15:39:15 +01:00
Faton Ramadani
24b1fa0ae3 fix(frontend): Make sure AppSelect items are an array (#1144)
* fix(frontend): Make sure AppSelect items are an array

* fix(frontend): restore placeholder
2023-01-23 05:37:38 -08:00
Ruben Fiszel
fedb9fe74c update pricing page 2023-01-22 18:34:05 +01:00
Ruben Fiszel
5408da829c update pricing page 2023-01-22 18:20:06 +01:00
Ruben Fiszel
448e45b7e4 fix privilege on cloud 2023-01-22 00:53:29 +01:00
Ruben Fiszel
156f257619 add import_map to nsjail 2023-01-21 23:28:11 +01:00
Ruben Fiszel
7f40373fd6 feat(deno): allow overriding deno sandboxing with DENO_FLAGS' 2023-01-21 22:54:14 +01:00
Ruben Fiszel
f2ec03ba1b implement relative imports for deno 2023-01-21 19:12:22 +01:00
Ruben Fiszel
9a79c5537e fix compile 2023-01-21 18:49:26 +01:00
Ruben Fiszel
cf156b0591 support BigInt serialization 2023-01-21 18:41:58 +01:00
Ruben Fiszel
832ddabdf2 feat(deno): add support for DENO_AUTH_TOKENS 2023-01-21 18:36:17 +01:00
Ruben Fiszel
22080312f0 description at bottom 2023-01-21 17:33:23 +01:00
Ruben Fiszel
165095376d description at bottom 2023-01-21 10:19:05 +01:00
Ruben Fiszel
2a816b9e7b remove square from oauth 2023-01-21 09:37:10 +01:00
Ruben Fiszel
a7d03f4e25 square use req_body_auth 2023-01-21 09:26:39 +01:00
Ruben Fiszel
5e18e580f7 fix superadmins permissions 2023-01-21 08:09:49 +01:00
Felipe Albertao
9b7906e43d Added Square API OAuth (#1141) 2023-01-21 07:41:52 +01:00
Ruben Fiszel
2143bbdc94 add support for python import shopifyAPI 2023-01-20 19:52:18 +01:00
Ruben Fiszel
51a8810aa0 feat: add QUEUE_LIMIT_WAIT_RESULT 2023-01-20 19:39:31 +01:00
Faton Ramadani
8f906026b3 fix(frontend): Add default value for text, number and date input + fix issues with number input + add date input in the settings panel (#1135)
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2023-01-20 18:31:10 +01:00
Faton Ramadani
aa6de3bb57 fix(frontend): Prevent modal from hijacking all keypress event (#1136) 2023-01-20 15:39:37 +01:00
Faton Ramadani
71a443e3c5 fix(frontend): Check if hiddenInlineScripts are undefined before iterating over them (#1134) 2023-01-20 11:49:43 +01:00
Ruben Fiszel
d1526d9c72 fix deno nsjail vmem 2023-01-20 11:49:08 +01:00
Ruben Fiszel
2456e35908 install nodejs only on heavy 2023-01-19 21:01:30 +01:00
Ruben Fiszel
f359015d89 bump default bash tmp size 2023-01-19 20:32:08 +01:00
Ruben Fiszel
b8155f4d27 update npm used in heavy 2023-01-19 20:00:01 +01:00
Ruben Fiszel
2ce903d7e7 add more details about signals termination 2023-01-19 18:20:24 +01:00
Ruben Fiszel
d4765dee16 add nodejs npm to heavy image 2023-01-19 16:22:33 +01:00
Faton Ramadani
a78486d7e0 fix(frontend): Make sure that old apps are rendering properly (#1132) 2023-01-19 15:14:15 +01:00
Faton Ramadani
e969af9e44 feat(frontend): add support for background scripts + add FormButtonCo… (#1124)
* feat(frontend): add support for background scripts + add FormButtonComponnet

* feat(frontend): add background script fields

* feat(frontend): Handle deletion + display of results

* feat(frontend): Fix background script ids: Runnable ids needs to be valid js expression, because they are used in the evaluator.

* feat(frontend): add a portal to display modal

* feat(frontend): fix build

* feat(frontend): Add missing props

* feat(frontend): Fix run button + add tooltip + fix icon
2023-01-19 14:52:32 +01:00
Ruben Fiszel
e371fda1ce relax nsjail limits 2023-01-19 14:51:43 +01:00
Ruben Fiszel
ee14bed3d9 relax nsjail limits 2023-01-19 14:35:06 +01:00
Faton Ramadani
3d873ed51c fix(frontend): add missing condition to properly select first row (#1128) 2023-01-19 14:08:32 +01:00
Ruben Fiszel
d85f550611 handle empty flow module input transforms 2023-01-19 12:22:37 +01:00
Faton Ramadani
6c9daf7002 fix(frontend): fix first row selection (#1125)
* fix(frontend): fix first row selection

* fix(frontend): fix order of conditions
2023-01-19 11:52:34 +01:00
Ádám Kovács
6a88e8c4f4 feat(frontend): Add app preview lock (#1127)
* feat(frontend): Add locked state to app preview

* change color

* add transition
2023-01-19 11:27:16 +01:00
Ruben Fiszel
99246382ee minor UI fixes 2023-01-18 17:56:40 +01:00
Ruben Fiszel
73e1018f65 minor UI fixes 2023-01-18 17:08:34 +01:00
Ruben Fiszel
f1157e3be2 remove vega lite actions 2023-01-18 16:06:56 +01:00
Ruben Fiszel
bd79938bed feat(frontend): add vega-lite component 2023-01-18 15:49:47 +01:00
Ádám Kovács
173093a403 fix(frontend): Prepare app scripts code for export (#1123) 2023-01-18 12:43:24 +01:00
Ruben Fiszel
ed61df6d8a UX nits 2023-01-18 11:30:08 +01:00
Ruben Fiszel
24feb64dfe fix reporting duration above 60s 2023-01-18 11:23:25 +01:00
Ruben Fiszel
b3eaf86e40 HTMLComponent refresh on content change 2023-01-18 10:24:29 +01:00
Ádám Kovács
4dfdf374af feat(frontend): Add tooltip to app recompute (#1122) 2023-01-18 10:10:47 +01:00
Ruben Fiszel
45191621d2 UX nits on schedule 2023-01-18 09:42:49 +01:00
Ruben Fiszel
4d60be738e UX nits on schedule 2023-01-18 09:38:54 +01:00
Ruben Fiszel
eef3bab6e4 feat(backend): add queue_limit + configurable timeout + fix timeout cancel 2023-01-18 09:02:38 +01:00
Ruben Fiszel
14ebb6dc32 fix all cases where schema has been set to undefined 2023-01-18 06:56:44 +01:00
Ruben Fiszel
9e602691b6 do not cancel the job on leaving the preview page 2023-01-18 00:31:55 +01:00
Ruben Fiszel
02045001d2 do not cancel the job on leaving the preview page 2023-01-18 00:14:29 +01:00
Faton Ramadani
3c483f5337 fix(frontend): Select the first row by default, and remove the abilit… (#1121)
* fix(frontend): Select the first row by default, and remove the ability to deselect + fix table header z-index

* fix(frontend): restore z-index, under the floating tags
2023-01-17 19:10:06 +01:00
Ruben Fiszel
bcb5c3697d inline tailwindcss for html component in apps 2023-01-17 17:48:40 +01:00
Ruben Fiszel
0e50579089 frontend(apps): html component uses iframe for security 2023-01-17 17:29:01 +01:00
Ádám Kovács
59c4cc2058 fix(frontend): App table header z-index (#1120) 2023-01-17 14:00:54 +01:00
Ádám Kovács
462adbe42f fix(frontend): Runnable table overflow (#1119) 2023-01-17 12:51:14 +01:00
Ruben Fiszel
d708b6faa5 fix permissioned_as from username 2023-01-17 12:49:24 +01:00
Ruben Fiszel
40d566ab8e remove unecessary badge width 2023-01-17 12:32:30 +01:00
Ruben Fiszel
c5d712c977 ci: build both amd and arm on same workflow leveraging depot 2023-01-17 12:16:36 +01:00
Ádám Kovács
631a3da17f fix(frontend): Show app builder header always on top (#1118)
* fix(frontend): Keep app header always on top

* various fixes

* fix
2023-01-17 12:02:21 +01:00
Ruben Fiszel
ff5145d7bd UI nits 2023-01-17 12:00:17 +01:00
Ruben Fiszel
d1131c1a16 various fixes 2023-01-17 11:30:37 +01:00
Ruben Fiszel
9b89df9ecf various fixes 2023-01-17 11:30:22 +01:00
Ruben Fiszel
acff87a09f all 2023-01-17 08:09:46 +01:00
Ruben Fiszel
a182ce2d2c all 2023-01-17 08:09:39 +01:00
Ruben Fiszel
0954b5a4f2 ci: npm run check generate backend client 2023-01-17 07:40:40 +01:00
Ruben Fiszel
d297223fa5 ci: npm check on frontend change and uffizzi only on backend changes 2023-01-17 07:38:05 +01:00
dependabot[bot]
9bccb90c7a chore(deps-dev): bump prettier from 2.8.1 to 2.8.3 in /frontend (#1111)
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.1 to 2.8.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.1...2.8.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2023-01-17 07:17:42 +01:00
dependabot[bot]
6403259e2a chore(deps): bump @tanstack/svelte-table in /frontend (#1112)
Bumps [@tanstack/svelte-table](https://github.com/tanstack/table) from 8.7.2 to 8.7.6.
- [Release notes](https://github.com/tanstack/table/releases)
- [Commits](https://github.com/tanstack/table/compare/v8.7.2...v8.7.6)

---
updated-dependencies:
- dependency-name: "@tanstack/svelte-table"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2023-01-17 06:53:11 +01:00
Ruben Fiszel
fa367f4128 point uffizzi lsp to 60d image 2023-01-17 06:51:17 +01:00
Ruben Fiszel
164f281dd0 fix uffizzi preview 2023-01-17 06:46:37 +01:00
Adam Vollrath
760cb31bae Use Uffizzi workflow v2 and other minor improvements. (#1114)
* Use Uffizzi workflow `v2` and other minor improvements.

* Specify Uffizzi Preview URL as target for `playwright`.

Answers https://github.com/windmill-labs/windmill/pull/1106#issuecomment-1384334128

* recheck
2023-01-16 20:32:46 +01:00
Faton Ramadani
4f869811fe fix(frontend): Fix id generation when a second action (#1110) 2023-01-16 19:07:22 +01:00
Ruben Fiszel
0f26a3b111 playwright against uffizzi (#1109)
* all

* fix perms

* move build to depot

* login

* revert to docker build

* revert to docker build

* fix playwright
2023-01-16 18:01:45 +01:00
Vibhav Bobade
fadae2d2b7 create preview environments for every PR using Uffizzi (#1106)
* Integrate Uffizzi

* remove lsp build

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2023-01-16 17:03:31 +01:00
Faton Ramadani
12b00a808d feat(frontend): Add Supabase resource (#1107)
* feat(frontend): Add Supabase resource

* feat(frontend): Added an image
2023-01-16 16:54:06 +01:00
Ádám Kovács
f0435f5f81 fix(frontend): Playwright (#1108)
* fix(frontend): Playwright

* fix(frontend): Update test base url

* feat(ci): Add base url to playwright
2023-01-16 16:51:14 +01:00
Ruben Fiszel
f018d025a2 fix schedule permissions 2023-01-16 16:42:42 +01:00
Ruben Fiszel
5d720e96e6 add row to eval input 2023-01-16 13:23:31 +01:00
Ruben Fiszel
e1b77b0d5f migrate more builds to depot.dev 2023-01-16 10:56:20 +01:00
Ruben Fiszel
2db6424f9d add dynamic disabled field to button 2023-01-16 01:52:18 +01:00
Ruben Fiszel
9a58145f72 add selenium-wire exception 2023-01-15 22:48:49 +01:00
Kyle Galbraith
51c1ae937c add packages: write for ghcr (#1105) 2023-01-15 18:29:18 +01:00
Ruben Fiszel
26bd9ac9aa remove comingsoon for UIs 2023-01-15 17:58:40 +01:00
Kyle Galbraith
3ba28b8156 Switch to Depot for Arm image build (#1104)
* switch to Depot for native arm builds without emulation

* set Depot project id
2023-01-15 17:55:25 +01:00
Ruben Fiszel
3f318b4ac4 add license key check 2023-01-15 12:06:37 +01:00
Ruben Fiszel
452c6de7e3 script_helpers nit 2023-01-15 08:32:37 +01:00
Ruben Fiszel
b1a86db58b nit error handling 2023-01-15 07:22:35 +01:00
Ruben Fiszel
b29eccf4bb : 2023-01-15 01:39:07 +01:00
Ruben Fiszel
ba28f6c62a error improvements 2023-01-15 01:28:13 +01:00
Ruben Fiszel
279f671dcf improve default connection for failure module 2023-01-14 19:22:59 +01:00
Ruben Fiszel
954cc1bb95 fix tests wrt to new error handling 2023-01-14 18:47:37 +01:00
Ruben Fiszel
776d4c9ca1 error handling improvements 2023-01-14 17:46:03 +01:00
Ruben Fiszel
6949516e63 improve error handling 2023-01-14 15:08:47 +01:00
Ruben Fiszel
f6c13b0324 avoid annoying panel resizing on value change 2023-01-14 14:29:12 +01:00
Ádám Kovács
d811675882 feat(frontend): Add surreal db logo (#1102) 2023-01-14 14:17:43 +01:00
Ruben Fiszel
735aee952a add hub compatible json export to apps 2023-01-14 09:10:39 +01:00
Ruben Fiszel
9432f4c1fa minor tooltip fix 2023-01-14 07:45:15 +01:00
Ruben Fiszel
5bf5a4f86e sqlx fix 2023-01-13 22:27:18 +01:00
Ruben Fiszel
60ef8f569b run_wait_result improvements: cancel + early tx drop 2023-01-13 21:30:29 +01:00
Ádám Kovács
7bc59d9d26 fix(frontend): App script list panel overflow (#1101) 2023-01-13 20:24:18 +01:00
Ruben Fiszel
bd4c7b7957 minor improvements 2023-01-13 18:52:34 +01:00
Ádám Kovács
172b5dba8f feat(frontend): Update app table component styles (#1100)
* feat(frontend): Update app table component styles

* feat(frontend): Update table search style
2023-01-13 18:44:21 +01:00
Ruben Fiszel
b478ba8c45 clarify run detail page + allow i64 for MAX_LOG_SIZE 2023-01-13 18:39:17 +01:00
Ruben Fiszel
71708b7566 add MAX_LOG_SIZE env params 2023-01-13 14:46:07 +01:00
Ádám Kovács
c50c7406f2 feat(frontend): Add default codes to app editor (#1099) 2023-01-13 13:48:28 +01:00
Ruben Fiszel
00357261d5 add psycopg-pool to psycopg default install 2023-01-13 12:16:48 +01:00
Ruben Fiszel
c121908716 fix table static exports 2023-01-13 12:08:37 +01:00
Ádám Kovács
2191e85231 feat(frontend): Add more integration icons (#1097)
* feat(frontend): Add more integration icons

* fix

* fix
2023-01-13 08:48:31 +01:00
Ruben Fiszel
a85a964819 add psycopg3 to the list of support imports 2023-01-13 07:54:05 +01:00
Ruben Fiszel
2ee418b6bf add polars to lsp 2023-01-12 23:12:07 +01:00
Ruben Fiszel
8f72c32fbe various improvements 2023-01-12 22:20:54 +01:00
Ruben Fiszel
1cc5059179 fix execute_component tokens 2023-01-12 16:53:54 +01:00
Ruben Fiszel
9280a5132e minor app fixes 2023-01-12 13:04:17 +01:00
Ruben Fiszel
a626aa6175 minor app fixes 2023-01-12 12:51:27 +01:00
Ádám Kovács
339742ca77 fix(frontend): App form component display (#1096) 2023-01-12 11:41:05 +01:00
Ádám Kovács
43e069eb96 fix(frontend): Use absolute path on connect images (#1095) 2023-01-12 11:40:41 +01:00
Ruben Fiszel
8e1fecfda0 make openapi viewer rely on local assets only 2023-01-11 21:02:53 +01:00
Ruben Fiszel
3e1c9463d6 make openapi viewer rely on local assets only 2023-01-11 20:41:52 +01:00
Ruben Fiszel
1337811438 feat: add openapi viewer (#1094)
* all

* all

* fix tests

* fix tests

* fix tests

* fix tests

* fix tests

* fix tests

* fix tests

* fix tests

* fix typecheck

* fix tests

* fix tests

* fix tests

* all
2023-01-11 20:28:50 +01:00
Ruben Fiszel
a028772bf4 fix typecheck 2023-01-11 17:21:02 +01:00
Ádám Kovács
0419e7e1c9 fix(frontend): Revert component input panel change (#1092) 2023-01-11 16:56:58 +01:00
Ádám Kovács
bdfc38d954 feat(frontend): Add copy button option to app text display component (#1090)
* feat(frontend): Add copy button to app text

* feat(frontend): Add tooltip to component configs

* fix(frontend): Remove copy button text
2023-01-11 16:05:19 +01:00
Ruben Fiszel
dbd632375c fix backend tests 2023-01-11 14:56:05 +01:00
237 changed files with 7065 additions and 2439 deletions

5
.github/uffizzi/caddy/Caddyfile vendored Normal file
View File

@@ -0,0 +1,5 @@
localhost {
bind 0.0.0.0
reverse_proxy /ws/* http://0.0.0.0:3001
reverse_proxy /* http://0.0.0.0:8000
}

View File

@@ -0,0 +1,53 @@
version: '3.7'
x-uffizzi:
ingress:
service: windmill
port: 8000
services:
db:
image: postgres:14
environment:
POSTGRES_PASSWORD: changeme
POSTGRES_DB: windmill
windmill:
image: '${WINDMILL_IMAGE}'
ports:
- 8000:8000
entrypoint: ['/bin/sh', '-c']
command: 'echo ${OAUTH_JSON_BASE64} | base64 --decode > /usr/src/app/oauth.json && ./windmill'
environment:
- DATABASE_URL=postgres://postgres:changeme@localhost/windmill?sslmode=disable
- BASE_URL=${EXPECTED_URL}
- BASE_INTERNAL_URL=http://localhost:8000
- RUST_LOG=info
- NUM_WORKERS=3
- KEEP_JOB_DIR=false
- DENO_PATH=/usr/bin/deno
- PYTHON_PATH=/usr/local/bin/python3
- METRICS_ADDR=false
- OAUTH_JSON_BASE64=${OAUTH_JSON_BASE64}
volumes:
- worker_dependency_cache:/tmp/windmill/cache
deploy:
resources:
limits:
memory: 250M
lsp:
image: '${LSP_IMAGE}'
ports:
- 3001:3001
# caddy:
# image: caddy:2.5.2-alpine
# restart: unless-stopped
# volumes:
# - ./.github/uffizzi/caddy:/etc/caddy
# environment:
# - BASE_URL=localhost
volumes:
worker_dependency_cache:

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.3.5
uses: dependabot/fetch-metadata@v1.3.6
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs

View File

@@ -40,4 +40,4 @@ jobs:
backend -> target
- name: cargo test
timeout-minutes: 10
run: mkdir frontend/build && cd backend && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill cargo test --all -- --nocapture
run: mkdir frontend/build && cd backend && touch windmill-api/openapi-deref.yaml && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill cargo test --all -- --nocapture

47
.github/workflows/docker-310.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
name: Build and push windmill with python 3.10 and openbb
on: workflow_dispatch
concurrency:
group: ${{ github.ref }}-openbb
cancel-in-progress: true
permissions:
contents: read
id-token: write
packages: write
jobs:
build_ee:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
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: Build and push publicly ee
uses: depot/build-push-action@v1
with:
context: .
push: true
file: ./docker/DockerfileOpenbb
build-args: |
features=enterprise
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:openbb
labels: |
org.opencontainers.image.licenses=Windmill-Enterprise-License

View File

@@ -1,62 +1,60 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
LOCAL_REGISTRY: registry.wimill.xyz
# env:
# REGISTRY: ghcr.io
# IMAGE_NAME: ${{ github.repository }}
name: Build and push arm docker image
on:
push:
branches: [main]
tags: ["*"]
# name: Build and push arm docker image
# on:
# push:
# branches: [main]
# tags: ["*"]
concurrency:
group: ${{ github.ref }}-arm
cancel-in-progress: true
# concurrency:
# group: ${{ github.ref }}-arm
# cancel-in-progress: true
jobs:
publish_arm:
runs-on: [self-hosted, new]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
# permissions:
# contents: read
# id-token: write
# packages: write
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# jobs:
# publish_arm:
# runs-on: ubuntu-22.04
# steps:
# - uses: actions/checkout@v3
# with:
# fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# - uses: depot/setup-action@v1
- name: Docker meta
id: meta-slim-public
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
# - name: Docker meta
# id: meta-slim-public
# uses: docker/metadata-action@v4
# with:
# images: |
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# tags: |
# type=ref,event=branch
# type=ref,event=pr
# type=semver,pattern={{version}}
# type=semver,pattern={{major}}.{{minor}}
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# - 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: docker/build-push-action@v3
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ steps.meta-slim-public.outputs.tags }}
labels: |
${{ steps.meta-slim-public.outputs.labels }}
org.opencontainers.image.licenses=AGPLv3
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-arm:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-arm:buildcache,mode=max
# - name: Build and push publicly
# uses: depot/build-push-action@v1
# with:
# context: .
# push: true
# platforms: linux/amd64,linux/arm64
# tags: |
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
# ${{ steps.meta-slim-public.outputs.tags }}
# labels: |
# ${{ steps.meta-slim-public.outputs.labels }}
# org.opencontainers.image.licenses=AGPLv3

View File

@@ -9,22 +9,27 @@ on:
push:
branches: [main]
tags: ["*"]
pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
packages: write
jobs:
build:
runs-on: [self-hosted, new]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# - 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
@@ -33,36 +38,8 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta-slim
if: github.event_name == 'pull_request'
uses: docker/metadata-action@v4
with:
images: |
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push publicly
uses: docker/build-push-action@v3
if: github.event_name == 'pull_request'
with:
context: .
push: true
tags: |
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:latest
${{ steps.meta-slim.outputs.tags }}
labels: |
${{ steps.meta-slim.outputs.labels }}
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache,mode=max
- name: Docker meta
id: meta-public
if: github.event_name != 'pull_request'
uses: docker/metadata-action@v4
with:
images: |
@@ -75,47 +52,31 @@ jobs:
- name: Build and push publicly
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v3
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:latest
${{ steps.meta-public.outputs.tags }}
labels: |
${{ steps.meta-public.outputs.labels }}
org.opencontainers.image.licenses=AGPLv3
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache,mode=max
build_ee:
runs-on: [self-hosted, new]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Docker meta
id: meta-slim
if: github.event_name == 'pull_request'
uses: docker/metadata-action@v4
with:
images: |
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- uses: depot/setup-action@v1
- name: Docker meta
id: meta-ee-public
if: github.event_name != 'pull_request'
uses: docker/metadata-action@v4
with:
images: |
@@ -128,15 +89,13 @@ jobs:
- name: Login to registry
uses: docker/login-action@v2
if: github.event_name != 'pull_request'
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push publicly ee
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v3
uses: depot/build-push-action@v1
with:
context: .
push: true
@@ -149,8 +108,7 @@ jobs:
labels: |
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-ee:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-ee:buildcache,mode=max
playwright:
runs-on: [self-hosted, new]
@@ -172,14 +130,14 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: "Docker"
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:latest)"
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
id: docker-container
- uses: actions/setup-node@v3
with:
node-version: 16
- name: "Playwright run"
timeout-minutes: 2
run: cd frontend && npm ci @playwright/test && npx playwright install && npm run test
run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=http://localhost:8000 && npm run test
- name: "Clean up"
run: docker kill ${{ steps.docker-container.outputs.id }}
if: always()
@@ -188,7 +146,6 @@ jobs:
publish_privately_heavy:
needs: [build_ee]
runs-on: [self-hosted, new]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v3
with:
@@ -202,7 +159,6 @@ jobs:
with:
images: |
${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr

17
.github/workflows/frontend-check.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: check frontend build
on:
pull_request:
types: [opened,synchronize,reopened,closed]
paths:
- "frontend/**"
jobs:
npm_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: "npm check"
timeout-minutes: 2
run: cd frontend && npm ci && npm run generate-backend-client && npm run check

View File

@@ -74,6 +74,7 @@ jobs:
${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ steps.metalocal.outputs.tags }}
${{ steps.meta.outputs.tags }}
registry.uffizzi.com/windmill-lsp:60d
labels: ${{ steps.metalocal.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

93
.github/workflows/uffizzi-build.yml vendored Normal file
View File

@@ -0,0 +1,93 @@
name: Build PR Image
on:
pull_request:
types: [opened,synchronize,reopened,closed]
paths:
- "backend/**"
- ".github/uffizzi/**"
- ".github/workflows/**"
workflow_dispatch:
jobs:
build-windmill:
name: Build and Push `windmill`
runs-on: ubuntu-latest
if: ${{ (github.event_name != 'pull_request' || github.event.action != 'closed')}}
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate UUID image name
id: uuid
run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
tags: type=raw,value=60d
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
uses: docker/build-push-action@v2
with:
push: true
context: ./
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
render-compose-file:
name: Render Docker Compose File
# Pass output of this workflow to another triggered by `workflow_run` event.
runs-on: ubuntu-latest
needs:
- build-windmill
outputs:
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Render Compose File
run: |
WINDMILL_IMAGE=${{ needs.build-windmill.outputs.tags }}
export WINDMILL_IMAGE
LSP_IMAGE=registry.uffizzi.com/windmill-lsp:60d
export LSP_IMAGE
envsubst '${WINDMILL_IMAGE} ${LSP_IMAGE}' < ./.github/uffizzi/docker-compose.uffizzi.yml > docker-compose.rendered.yml
cat docker-compose.rendered.yml
- name: Upload Rendered Compose File as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: docker-compose.rendered.yml
retention-days: 2
- name: Serialize PR Event to File
run: |
cat << EOF > event.json
${{ toJSON(github.event) }}
EOF
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: event.json
retention-days: 2
delete-preview:
name: Call for Preview Deletion
runs-on: ubuntu-latest
if: ${{ github.event.action == 'closed' }}
steps:
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
- name: Serialize PR Event to File
run: echo '${{ toJSON(github.event) }}' > event.json
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: event.json
retention-days: 2

115
.github/workflows/uffizzi-preview.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
name: Deploy Uffizzi Preview
on:
workflow_run:
workflows:
- "Build PR Image"
types:
- completed
jobs:
cache-compose-file:
name: Cache Compose File
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
compose-file-cache-key: ${{ env.COMPOSE_FILE_HASH }}
pr-number: ${{ env.PR_NUMBER }}
steps:
- name: 'Download artifacts'
# Fetch output (zip archive) from the workflow run that triggered this workflow.
uses: actions/github-script@v6
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "preview-spec"
})[0];
if (matchArtifact === undefined) {
throw TypeError('Build Artifact not found!');
}
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));
- name: 'Unzip artifact'
run: unzip preview-spec.zip
- name: Read Event into ENV
run: |
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
cat event.json >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Read PR Number From Event Object
id: pr
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
- name: Predict Deployment URL
id: url
# Replace dots in the repo name with the plus sign
run: |
REPO=$(echo ${{ github.repository }} | sed 's/\./+/g')
echo "EXPECTED_URL=https://app.uffizzi.com/github.com/$REPO/pull/$PR_NUMBER" >> $GITHUB_ENV
- name: Re-Render Compose File
run: |
OAUTH_JSON_BASE64=${{ secrets.OAUTH_JSON_BASE64 }}
export OAUTH_JSON_BASE64
envsubst '${OAUTH_JSON_BASE64} ${EXPECTED_URL}' < docker-compose.rendered.yml > docker-compose.uffizzi.yml
# cat docker-compose.uffizzi.yml
- name: Hash Rendered Compose File
id: hash
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.uffizzi.yml | awk '{ print $1 }')" >> $GITHUB_ENV
- name: Cache Rendered Compose File
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
uses: actions/cache@v3
with:
path: docker-compose.uffizzi.yml
key: ${{ env.COMPOSE_FILE_HASH }}
- name: DEBUG - Print Job Outputs
if: ${{ runner.debug }}
run: |
echo "PR number: ${{ env.PR_NUMBER }}"
echo "Compose file hash: ${{ env.COMPOSE_FILE_HASH }}"
cat event.json
deploy-uffizzi-preview:
name: Use Remote Workflow to Preview on Uffizzi
needs:
- cache-compose-file
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2
with:
# If this workflow was triggered by a PR close event, cache-key will be an empty string
# and this reusable workflow will delete the preview deployment.
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
compose-file-cache-path: docker-compose.uffizzi.yml
server: https://app.uffizzi.com
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
permissions:
contents: read
pull-requests: write
id-token: write
playwright:
runs-on: ubuntu-latest
needs:
- deploy-uffizzi-preview
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: "Playwright run"
timeout-minutes: 2
run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=${{ needs.deploy-uffizzi-preview.outputs.url }} && npm run test

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"python.analysis.typeCheckingMode": "basic"
}

View File

@@ -1,6 +1,71 @@
# Changelog
## [1.61.1](https://github.com/windmill-labs/windmill/compare/v1.61.0...v1.61.1) (2023-01-31)
### Bug Fixes
* **backend:** compile issue ([df8cc1f](https://github.com/windmill-labs/windmill/commit/df8cc1f2482b3d8b1530cdaef1361303ff5cadff))
## [1.61.0](https://github.com/windmill-labs/windmill/compare/v1.60.0...v1.61.0) (2023-01-31)
### Features
* add openapi viewer ([#1094](https://github.com/windmill-labs/windmill/issues/1094)) ([1337811](https://github.com/windmill-labs/windmill/commit/1337811438d48e23133f68e9157bd185d5fe4a82))
* add PIP_LOCAL_DEPENDENCIES ([b7db4c7](https://github.com/windmill-labs/windmill/commit/b7db4c78c4629f1fd2dfd7a338f783b16f07b24d))
* add QUEUE_LIMIT_WAIT_RESULT ([51a8810](https://github.com/windmill-labs/windmill/commit/51a8810aa0a9ab7702df459dd270278d42bd3899))
* add resource and resource type from json ([080ecb0](https://github.com/windmill-labs/windmill/commit/080ecb04d7a08d035fe07f179975b52bc0f77297))
* add sql as a valid type in Python ([0172587](https://github.com/windmill-labs/windmill/commit/0172587b129ce54d96dc99336a1f56c66ebdbef5))
* add sync webhook for flows ([f377c84](https://github.com/windmill-labs/windmill/commit/f377c84f5a2148a2bbb7c16e93f13e1d85ceb17e))
* **backend:** add queue_limit + configurable timeout + fix timeout cancel ([eef3bab](https://github.com/windmill-labs/windmill/commit/eef3bab6e4d9f1af1435db868c707a692558ab74))
* **deno:** add support for DENO_AUTH_TOKENS ([832ddab](https://github.com/windmill-labs/windmill/commit/832ddabdf2239521368e5f96df144abce0db31c2))
* **deno:** allow overriding deno sandboxing with DENO_FLAGS' ([7f40373](https://github.com/windmill-labs/windmill/commit/7f40373fd64005d87972854a565c6cf521232982))
* **frontend:** Add app inputs configurations ([#1142](https://github.com/windmill-labs/windmill/issues/1142)) ([3ed16b8](https://github.com/windmill-labs/windmill/commit/3ed16b88a42e4db6e12f8557c5bbaa2d832b1c17))
* **frontend:** Add app preview lock ([#1127](https://github.com/windmill-labs/windmill/issues/1127)) ([6a88e8c](https://github.com/windmill-labs/windmill/commit/6a88e8c4f4d6fa5c393ce27b2040784a74a73b06))
* **frontend:** Add copy button option to app text display component ([#1090](https://github.com/windmill-labs/windmill/issues/1090)) ([bdfc38d](https://github.com/windmill-labs/windmill/commit/bdfc38d954a3c5548fb7f9ee6f80f741eff8cb67))
* **frontend:** Add default codes to app editor ([#1099](https://github.com/windmill-labs/windmill/issues/1099)) ([c50c740](https://github.com/windmill-labs/windmill/commit/c50c7406f267b480af2a01b47e3fcfa1d763db7a))
* **frontend:** Add HTML result rendering ([#1160](https://github.com/windmill-labs/windmill/issues/1160)) ([c01bf70](https://github.com/windmill-labs/windmill/commit/c01bf70f62680a4b77812ac6eb64aca2b15d9a8d))
* **frontend:** Add more integration icons ([#1097](https://github.com/windmill-labs/windmill/issues/1097)) ([2191e85](https://github.com/windmill-labs/windmill/commit/2191e852318f069489f77a4f1c44aadf248c7f53))
* **frontend:** add plotly support ([a4f8f9e](https://github.com/windmill-labs/windmill/commit/a4f8f9e1cf80395d5cd1229c8dd5dda244e2ba7f))
* **frontend:** add selectedRowIndex to the table outputs ([#1145](https://github.com/windmill-labs/windmill/issues/1145)) ([f05f9e4](https://github.com/windmill-labs/windmill/commit/f05f9e4edb928e7a8e3e66a62de9c6487684a14b))
* **frontend:** Add Supabase resource ([#1107](https://github.com/windmill-labs/windmill/issues/1107)) ([12b00a8](https://github.com/windmill-labs/windmill/commit/12b00a808d1f12827a7bc26518cc6f972bdde917))
* **frontend:** add support for background scripts + add FormButtonCo… ([#1124](https://github.com/windmill-labs/windmill/issues/1124)) ([e969af9](https://github.com/windmill-labs/windmill/commit/e969af9e44d1b4409064080e8662552ee3e262e8))
* **frontend:** Add surreal db logo ([#1102](https://github.com/windmill-labs/windmill/issues/1102)) ([d811675](https://github.com/windmill-labs/windmill/commit/d81167588227f2cc433aab64551d96d21a589c5b))
* **frontend:** Add tooltip to app recompute ([#1122](https://github.com/windmill-labs/windmill/issues/1122)) ([4dfdf37](https://github.com/windmill-labs/windmill/commit/4dfdf374af358ef46ee8057373546719c6570067))
* **frontend:** add vega-lite component ([bd79938](https://github.com/windmill-labs/windmill/commit/bd79938bed6da3875a4a2dd72dad14dedbf25ddf))
* **frontend:** Display error as an icon in order to avoid clutter wh… ([#1143](https://github.com/windmill-labs/windmill/issues/1143)) ([22b8fed](https://github.com/windmill-labs/windmill/commit/22b8fed9d904a37aae66f6d957f4987f6ca9955c))
* **frontend:** Open debug runs from component ([#1155](https://github.com/windmill-labs/windmill/issues/1155)) ([73bc13b](https://github.com/windmill-labs/windmill/commit/73bc13bb7d4b1eb25a3a726ac9e6bb80120a495f))
* **frontend:** Update app table component styles ([#1100](https://github.com/windmill-labs/windmill/issues/1100)) ([172b5db](https://github.com/windmill-labs/windmill/commit/172b5dba8f4c3aaf11569c72313ad74845c668a6))
* **python:** add support for extra args in python ([772c768](https://github.com/windmill-labs/windmill/commit/772c768cda094f208a5efb7aab03eee3a8f38f68))
### Bug Fixes
* **frontend:** Add default value for text, number and date input + fix issues with number input + add date input in the settings panel ([#1135](https://github.com/windmill-labs/windmill/issues/1135)) ([8f90602](https://github.com/windmill-labs/windmill/commit/8f906026b3203702c3b6a30bcac9fb2aca985c29))
* **frontend:** Add highlight to selected workspace ([#1159](https://github.com/windmill-labs/windmill/issues/1159)) ([f221a6c](https://github.com/windmill-labs/windmill/commit/f221a6c17f145d0c42f7faf785c37f4037308973))
* **frontend:** add missing condition to properly select first row ([#1128](https://github.com/windmill-labs/windmill/issues/1128)) ([3d873ed](https://github.com/windmill-labs/windmill/commit/3d873ed51c769005981a8d8dfb95faa3ca33bb83))
* **frontend:** App form component display ([#1096](https://github.com/windmill-labs/windmill/issues/1096)) ([339742c](https://github.com/windmill-labs/windmill/commit/339742ca77dd0fda19d5a262617e42c341ef5871))
* **frontend:** App script list panel overflow ([#1101](https://github.com/windmill-labs/windmill/issues/1101)) ([7bc59d9](https://github.com/windmill-labs/windmill/commit/7bc59d9d2650b623a2b481a727ffc495b4216f22))
* **frontend:** App table action button cell ([#1149](https://github.com/windmill-labs/windmill/issues/1149)) ([e989662](https://github.com/windmill-labs/windmill/commit/e98966283dd9b57cc07da34876a90d19210c2927))
* **frontend:** App table header z-index ([#1120](https://github.com/windmill-labs/windmill/issues/1120)) ([59c4cc2](https://github.com/windmill-labs/windmill/commit/59c4cc2058f86deea793b61de59e2936e50e5577))
* **frontend:** Check if hiddenInlineScripts are undefined before iterating over them ([#1134](https://github.com/windmill-labs/windmill/issues/1134)) ([71a443e](https://github.com/windmill-labs/windmill/commit/71a443e3c56d2b8c951de6e3701a411ad1a0ce34))
* **frontend:** fix first row selection ([#1125](https://github.com/windmill-labs/windmill/issues/1125)) ([6c9daf7](https://github.com/windmill-labs/windmill/commit/6c9daf70021859dcd7cef717bc3acdfa88cffd02))
* **frontend:** Fix id generation when a second action ([#1110](https://github.com/windmill-labs/windmill/issues/1110)) ([4f86981](https://github.com/windmill-labs/windmill/commit/4f869811fee73826b2b10965241d2d8dba59dc2a))
* **frontend:** Make sure AppSelect items are an array ([#1144](https://github.com/windmill-labs/windmill/issues/1144)) ([24b1fa0](https://github.com/windmill-labs/windmill/commit/24b1fa0ae327c984841f9ed8b163b3fccc6da258))
* **frontend:** Make sure that old apps are rendering properly ([#1132](https://github.com/windmill-labs/windmill/issues/1132)) ([a78486d](https://github.com/windmill-labs/windmill/commit/a78486d7e08f76e22406063288b35e9030974d7a))
* **frontend:** Playwright ([#1108](https://github.com/windmill-labs/windmill/issues/1108)) ([f0435f5](https://github.com/windmill-labs/windmill/commit/f0435f5f81941c5b49500003aa27956d627daadb))
* **frontend:** Prepare app scripts code for export ([#1123](https://github.com/windmill-labs/windmill/issues/1123)) ([173093a](https://github.com/windmill-labs/windmill/commit/173093a40321f6ad35bf766a5554b21cea388771))
* **frontend:** Prevent modal from hijacking all keypress event ([#1136](https://github.com/windmill-labs/windmill/issues/1136)) ([aa6de3b](https://github.com/windmill-labs/windmill/commit/aa6de3bb5746b9d99c8e3a52e6a9fff10d97bc6a))
* **frontend:** Revert component input panel change ([#1092](https://github.com/windmill-labs/windmill/issues/1092)) ([0419e7e](https://github.com/windmill-labs/windmill/commit/0419e7e1c9239fd3cbc49acf82a73e9c01938153))
* **frontend:** Runnable table overflow ([#1119](https://github.com/windmill-labs/windmill/issues/1119)) ([462adbe](https://github.com/windmill-labs/windmill/commit/462adbe42f823646413a5003fd71f3dd473c0728))
* **frontend:** Select the first row by default, and remove the abilit… ([#1121](https://github.com/windmill-labs/windmill/issues/1121)) ([3c483f5](https://github.com/windmill-labs/windmill/commit/3c483f533759b9b4e589055dbddb31f294bea8fa))
* **frontend:** Show app builder header always on top ([#1118](https://github.com/windmill-labs/windmill/issues/1118)) ([631a3da](https://github.com/windmill-labs/windmill/commit/631a3da17f05a3d29defdf96a50d7e96a9f8baad))
* **frontend:** Update app scripts pane ([#1146](https://github.com/windmill-labs/windmill/issues/1146)) ([18f30c8](https://github.com/windmill-labs/windmill/commit/18f30c8286f8240158643ade8b0ef4607a80fbb0))
* **frontend:** Use absolute path on connect images ([#1095](https://github.com/windmill-labs/windmill/issues/1095)) ([43e069e](https://github.com/windmill-labs/windmill/commit/43e069eb96c0af7d3a1fe1db4f4b69f8e31e7438))
* improvements for error handling as first step of flow ([b77c239](https://github.com/windmill-labs/windmill/commit/b77c239f307a37777acb083b0cdb5c0d214a9dd8))
## [1.60.0](https://github.com/windmill-labs/windmill/compare/v1.59.0...v1.60.0) (2023-01-11)

View File

@@ -51,12 +51,16 @@ COPY frontend .
RUN mkdir /backend
COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml
COPY /openflow.openapi.yaml /openflow.openapi.yaml
COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh
RUN cd /backend/windmill-api && . ./build_openapi.sh
RUN npm run generate-backend-client
ENV NODE_OPTIONS "--max-old-space-size=8192"
RUN npm run build
RUN npm run check
FROM rust_base AS planner
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
@@ -75,6 +79,7 @@ COPY ./openflow.openapi.yaml /openflow.openapi.yaml
COPY ./backend ./
COPY --from=frontend /frontend /frontend
COPY --from=frontend /backend/windmill-api/openapi-deref.yaml ./windmill-api/openapi-deref.yaml
COPY .git/ .git/
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"

View File

@@ -91,7 +91,7 @@ Windmill is <b>fully open-sourced (AGPLv3)</b>:
shared on [WindmillHub](https://hub.windmill.dev).
![Step 4](./imgs/windmill-flow.png)
4. (Coming soon) Build complex UI on top of your scripts and flows.
4. Build complex UI on top of your scripts and flows.
![Step 5](./imgs/windmill-builder.png)
Scripts and flows can also be triggered by a cron schedule '*/5 * * * *' or
@@ -285,6 +285,7 @@ upcoming CLI tool.
| BASE_INTERNAL_URL | http://localhost:8000 | The base url that is reachable by your workers to talk to the Servers. This help avoiding going through the external load balancer for VPC-internal requests. | Worker |
| TIMEOUT | 300 | The timeout in seconds for the execution of a script | Worker |
| SLEEP_QUEUE | 50 | The number of ms to sleep in between the last check for new jobs in the DB. It is multiplied by NUM_WORKERS such that in average, for one worker instance, there is one pull every SLEEP_QUEUE ms. | Worker |
| MAX_LOG_SIZE | 500000 | The maximum number of characters a job can emit (log + result) | Worker |
| DISABLE_NUSER | false | If Nsjail is enabled, disable the nsjail's `clone_newuser` setting | Worker |
| KEEP_JOB_DIR | false | Keep the job directory after the job is done. Useful for debugging. | Worker |
| LICENSE_KEY (EE only) | None | License key checked at startup for the Enterprise Edition of Windmill | Worker |
@@ -303,6 +304,15 @@ upcoming CLI tool.
| HOME | None | The home directory to use for Go and Bash , usually inherited | Worker |
| DATABASE_CONNECTIONS | 50 (Server)/3 (Worker) | The max number of connections in the database connection pool | All |
| SUPERADMIN_SECRET | None | A token that would let the caller act as a virtual superadmin superadmin@windmill.dev | Server |
| TIMEOUT_WAIT_RESULT | 20 | The number of seconds to wait before timeout on the 'run_wait_result' endpoint | Worker |
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again |
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |
## Run a local dev setup

1
backend/.gitignore vendored
View File

@@ -1,3 +1,4 @@
target/
.env
oauth.json
windmill-api/openapi-deref.yaml

842
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.60.0"
version = "1.61.1"
authors.workspace = true
edition.workspace = true
@@ -19,7 +19,7 @@ members = [
]
[workspace.package]
version = "1.60.0"
version = "1.61.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -45,6 +45,9 @@ tokio-metrics.workspace = true
rand.workspace = true
chrono.workspace = true
git-version.workspace = true
rsa.workspace = true
base64.workspace = true
sha2.workspace = true
[dev-dependencies]
serde_json.workspace = true
@@ -80,7 +83,7 @@ chrono = { version = "^0", features = ["serde"] }
tracing = "^0"
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
prometheus = { version = "^0", default-features = false }
cookie = { version = "^0" }
cookie = { version = "0.16.2" }
phf = { version = "0.11", features = ["macros"] }
rust-embed = "^6"
mime_guess = "^2"
@@ -140,3 +143,5 @@ lazy_static = "1.4.0"
serde_derive = "1.0.147"
const_format = { version = "0.2", features = ["rust_1_64", "rust_1_51"] }
dyn-iter = "0.2.0"
rsa = "0.7.2"
async-stripe = { version = "0.14", features = ["runtime-tokio-hyper", "checkout"] }

View File

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

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE workspace_settings ADD COLUMN customer_id VARCHAR(100);
ALTER TABLE workspace_settings ADD COLUMN plan VARCHAR(40);

View File

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

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE account ALTER COLUMN refresh_token TYPE VARCHAR(1500);

View File

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

View File

@@ -0,0 +1,4 @@
-- Add up migration script here
GRANT ALL PRIVILEGES ON TABLE favorite TO windmill_admin;
GRANT ALL PRIVILEGES ON TABLE favorite TO windmill_user;

View File

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

View File

@@ -0,0 +1,25 @@
-- Add up migration script here
CREATE POLICY see_folder_extra_perms_user ON capture FOR ALL
USING (SPLIT_PART(capture.path, '/', 1) = 'f' AND SPLIT_PART(capture.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]))
WITH CHECK (SPLIT_PART(capture.path, '/', 1) = 'f' AND SPLIT_PART(capture.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
DO
$do$
DECLARE
i text;
arr text[] := array['queue', 'completed_job'];
BEGIN
FOREACH i IN ARRAY arr
LOOP
EXECUTE FORMAT(
$$
CREATE POLICY see_folder_extra_perms_user ON %1$I FOR ALL
USING (%1$I.visible_to_owner IS true AND SPLIT_PART(%1$I.script_path, '/', 1) = 'f' AND SPLIT_PART(%1$I.script_path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
$$,
i
);
END LOOP;
END
$do$;

View File

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

View File

@@ -0,0 +1,5 @@
-- Add up migration script here
CREATE POLICY see_folder_extra_perms_user ON account FOR ALL
USING (SPLIT_PART(account.owner, '/', 1) = 'f' AND SPLIT_PART(account.owner, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]))
WITH CHECK (SPLIT_PART(account.owner, '/', 1) = 'f' AND SPLIT_PART(account.owner, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));

View File

@@ -80,7 +80,7 @@ pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
let def_arg_start = params.args.len() - params.defaults.len();
Ok(MainArgSignature {
star_args: params.vararg.is_some(),
star_kwargs: params.vararg.is_some(),
star_kwargs: params.kwarg.is_some(),
args: params
.args
.into_iter()
@@ -104,6 +104,7 @@ pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
"bytes" => Typ::Bytes,
"datetime" => Typ::Datetime,
"datetime.datetime" => Typ::Datetime,
"Sql" | "sql" => Typ::Sql,
_ => Typ::Resource(id),
},
_ => Typ::Unknown,
@@ -135,7 +136,10 @@ fn to_value(et: &ExprKind) -> Option<serde_json::Value> {
.into_iter()
.zip(values)
.map(|(k, v)| {
let key = to_value(&k.node)
let key = k
.as_ref()
.map(|x| x.node.clone())
.and_then(|n| to_value(&n))
.and_then(|x| match x {
serde_json::Value::String(s) => Some(s),
_ => None,
@@ -174,10 +178,14 @@ fn constant_to_value(c: &Constant) -> serde_json::Value {
static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_map! {
"psycopg2" => "psycopg2-binary",
"psycopg" => "psycopg[binary, pool]",
"yaml" => "pyyaml",
"git" => "GitPython",
"u" => "requests",
"f" => "requests"
"f" => "requests",
"shopify" => "ShopifyAPI",
"seleniumwire" => "selenium-wire",
"openbb-terminal" => "openbb[all]",
};
fn replace_import(x: String) -> String {
@@ -189,13 +197,13 @@ fn replace_import(x: String) -> String {
}
lazy_static! {
static ref RE: Regex = Regex::new(r"^\#(\S+)$").unwrap();
static ref RE: Regex = Regex::new(r"^\#\s?(\S+)$").unwrap();
}
pub fn parse_python_imports(code: &str) -> error::Result<Vec<String>> {
let find_requirements = code
.lines()
.find_position(|x| x.starts_with("#requirements:"));
.find_position(|x| x.starts_with("#requirements:") || x.starts_with("# requirements:"));
if let Some((pos, _)) = find_requirements {
let lines = code
.lines()

View File

@@ -548,6 +548,16 @@
"name": "auto_invite_operator",
"ordinal": 6,
"type_info": "Bool"
},
{
"name": "customer_id",
"ordinal": 7,
"type_info": "Varchar"
},
{
"name": "plan",
"ordinal": 8,
"type_info": "Varchar"
}
],
"nullable": [
@@ -557,6 +567,8 @@
true,
false,
true,
true,
true,
true
],
"parameters": {
@@ -1576,6 +1588,20 @@
},
"query": "SELECT substr(logs, $1) as logs FROM completed_job WHERE workspace_id = $2 AND id = $3"
},
"460a3c3161899e172d2d20004e12ad3f331c73f260d099749a9e8fb85ae5614e": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Text"
]
}
},
"query": "UPDATE queue SET canceled = true, canceled_reason = 'http connection broke', canceled_by = $1 WHERE id = $2 AND workspace_id = $3"
},
"46b44de4ea6eb15258c1cb9bbd21cab7fc350b46e8899d59759945eae02a6d40": {
"describe": {
"columns": [
@@ -1745,23 +1771,6 @@
},
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'], $3)\n WHERE id = $4\n "
},
"53f2836a652d34aafe6ffb266a092d7390212bc5532dcc226529a61a53deec42": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Bool"
]
}
},
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)"
},
"541ebd3bac65431237cf3b882dfdcd61ca97c253d9754d05bba59fda89841067": {
"describe": {
"columns": [
@@ -1831,6 +1840,16 @@
"name": "auto_invite_operator",
"ordinal": 6,
"type_info": "Bool"
},
{
"name": "customer_id",
"ordinal": 7,
"type_info": "Varchar"
},
{
"name": "plan",
"ordinal": 8,
"type_info": "Varchar"
}
],
"nullable": [
@@ -1840,6 +1859,8 @@
true,
false,
true,
true,
true,
true
],
"parameters": {
@@ -2037,73 +2058,6 @@
},
"query": "SELECT EXISTS(SELECT 1 FROM app WHERE path = $1 AND workspace_id = $2)"
},
"59fc51efa01e823ec63f55a8081f282db0d7a40716c04f4043925eb84fe4429b": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Uuid",
"Varchar",
"Timestamptz",
"Timestamptz",
"Bool",
"Int8",
"Varchar",
"Jsonb",
"Jsonb",
"Text",
"Text",
"Text",
"Bool",
"Varchar",
"Text",
{
"Custom": {
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies"
]
},
"name": "job_kind"
}
},
"Varchar",
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
"Bool",
{
"Custom": {
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash"
]
},
"name": "script_lang"
}
},
"Numeric",
"Varchar",
"Bool"
]
}
},
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, EXTRACT(milliseconds FROM (now() - $6))), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
},
"5b9b58612ca0f703a5d154a76fab82ac2329aef965fa937bfab2810b6e1336a4": {
"describe": {
"columns": [],
@@ -2117,6 +2071,26 @@
},
"query": "DELETE FROM group_ WHERE name = $1 AND workspace_id = $2"
},
"5ba4b87528ad49f17d72b53c3db30f5ca4b3b0b0afbd5d9721c8b5d692af601b": {
"describe": {
"columns": [
{
"name": "count",
"ordinal": 0,
"type_info": "Int8"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT COUNT(id) FROM queue WHERE email = $1"
},
"5c377fffc224a06f693c125f4c13b0a9ccfc217190ba6cf78246294bbc6c93bc": {
"describe": {
"columns": [],
@@ -3826,6 +3800,73 @@
},
"query": "UPDATE flow SET dependency_job = $1 WHERE path = $2 AND workspace_id = $3"
},
"a46d97b0c65bbbcc29022c2d010a4853541987a58f0098f9c2e3fd6e40b11a52": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Uuid",
"Varchar",
"Timestamptz",
"Timestamptz",
"Bool",
"Int8",
"Varchar",
"Jsonb",
"Jsonb",
"Text",
"Text",
"Text",
"Bool",
"Varchar",
"Text",
{
"Custom": {
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies"
]
},
"name": "job_kind"
}
},
"Varchar",
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
"Bool",
{
"Custom": {
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash"
]
},
"name": "script_lang"
}
},
"Numeric",
"Varchar",
"Bool"
]
}
},
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
},
"a6145b0482c9e5da245059a80b1563cad20318fd2dd8aef33f9ca97de1826b8b": {
"describe": {
"columns": [],
@@ -4063,6 +4104,26 @@
},
"query": "\n SELECT resume_id, approver\n FROM resume_job\n WHERE job = $1\n "
},
"b053117536c067095e2fb2864ce5af33f84b22c24c92fcb870f37f0501f8ea9a": {
"describe": {
"columns": [
{
"name": "count",
"ordinal": 0,
"type_info": "Int8"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT COUNT(id) FROM queue WHERE running = true AND email = $1"
},
"b05c5f62ef4aa21d33369130cced0e9d7d128727eb58a9be7ae69cbb16bcbb27": {
"describe": {
"columns": [],
@@ -4191,6 +4252,24 @@
},
"query": "SELECT count(path) FROM schedule WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2"
},
"b50b8d3367bd1f74986e6f923fe8497bcbfffea2570a3f3d1f8100207bc1557c": {
"describe": {
"columns": [
{
"name": "count",
"ordinal": 0,
"type_info": "Int8"
}
],
"nullable": [
null
],
"parameters": {
"Left": []
}
},
"query": "SELECT COUNT(*) FROM queue WHERE canceled = false AND (scheduled_for <= now()\n OR (suspend_until IS NOT NULL\n AND ( suspend <= 0\n OR suspend_until <= now())))"
},
"b5c9891b5bf3d581e62f8835aaa25b8158fe3cefc849af0ba312a45cf22721ca": {
"describe": {
"columns": [
@@ -4532,6 +4611,26 @@
},
"query": "UPDATE schedule SET script_path = $1 WHERE script_path = $2 AND workspace_id = $3 AND is_flow IS false RETURNING *"
},
"c07c9276945663d062cf0ff5b3323be681a0e2cb07a457ea9aede2daeff551cc": {
"describe": {
"columns": [
{
"name": "customer_id",
"ordinal": 0,
"type_info": "Varchar"
}
],
"nullable": [
true
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT customer_id FROM workspace_settings WHERE workspace_id = $1"
},
"c1149f74ef0cca4a48be80f523d339d78e8e20e2bfacac9ad1b6b75c5ae370fb": {
"describe": {
"columns": [],
@@ -5524,6 +5623,24 @@
},
"query": "DELETE FROM password WHERE email = $1"
},
"e9c0e331c16312bf086b17c91466c5389d41454fd3f18d73c2e9554845ee9a72": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Bool",
"Varchar"
]
}
},
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin, email)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6, $7)"
},
"ea8ebb8d972fe99c960b5a69f794ee2b57bfb1914bf370c5b10313e45fa9b65f": {
"describe": {
"columns": [],

24
backend/src/ee.rs Normal file
View File

@@ -0,0 +1,24 @@
#[cfg(feature = "enterprise")]
use rsa::{pkcs8::DecodePublicKey, signature::Verifier};
#[cfg(feature = "enterprise")]
use sha2::Sha256;
#[cfg(feature = "enterprise")]
pub fn verify_license_key(license_key: Option<String>) -> anyhow::Result<()> {
if let Some(license_key) = license_key {
let splitted_lk = license_key
.split_once(".")
.expect("license_key can be splitted with a .");
let pub_key = rsa::RsaPublicKey::from_public_key_der(
&base64::decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgVShzcLSPiOi+8ET8fggob1kmi47/cE12JaidPkwfGnScZItghkqtiLsct0U4kJhlp5gO89DYTBmIKadvxwY7kMsLlZzmi2emVH7c27cByGASY8QmWDNdG4Ggy/NDflGGBdAtN6gHawZAg4zHv3qpbPQGHH1/6sXIohcXhOnouwIDAQAB")?)?;
let msg = base64::decode(splitted_lk.0)?;
let signature = base64::decode(splitted_lk.1)?;
rsa::pss::VerifyingKey::<Sha256>::new(pub_key)
.verify(&msg, &rsa::pss::Signature::from(signature))
.map_err(|_| anyhow::anyhow!("Invalid license key".to_string()))?;
} else {
panic!("License key is required for the enterprise edition");
}
Ok(())
}

View File

@@ -15,6 +15,8 @@ use windmill_worker::WorkerConfig;
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
mod ee;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
@@ -190,19 +192,12 @@ pub async fn run_workers(
license_key: Option<String>,
) -> anyhow::Result<()> {
#[cfg(feature = "enterprise")]
if let Some(license_key) = license_key {
if license_key != "REQUIRED_DEC1" {
panic!("Invalid license key");
}
} else {
panic!("License key is required for the enterprise edition");
}
ee::verify_license_key(license_key)?;
#[cfg(not(feature = "enterprise"))]
if license_key.is_some() {
panic!("License key is required ONLY for the enterprise edition");
}
#[cfg(not(feature = "enterprise"))]
if !worker_config.disable_nsjail {
tracing::warn!(

View File

@@ -186,7 +186,7 @@ mod suspend_resume {
const secret = await r.text();\
console.log('Secret: ' + secret + ' ' + job + ' ' + token);\
const r2 = await fetch(
`http://localhost:${port}/api/w/test-workspace/jobs/${op}/${job}/0/${secret}?approver=ruben`,\
`http://localhost:${port}/api/w/test-workspace/jobs_u/${op}/${job}/0/${secret}?approver=ruben`,\
{\
method: 'POST',\
body: JSON.stringify('from job'),\
@@ -276,7 +276,7 @@ mod suspend_resume {
/* ImZyb20gdGVzdCIK = base64 "from test" */
reqwest::get(format!(
"http://localhost:{port}/api/w/test-workspace/jobs/resume/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK&approver=ruben"
"http://localhost:{port}/api/w/test-workspace/jobs_u/resume/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK&approver=ruben"
))
.await
.unwrap()
@@ -333,7 +333,7 @@ mod suspend_resume {
server.close().await.unwrap();
assert_eq!(
json!({"error": "Job canceled: approval request disapproved by ruben" }),
json!({"error": {"name": "Canceled", "reason": "approval request disapproved", "message": "Job canceled: approval request disapproved by ruben", "canceler": "ruben"}}),
result
);
}
@@ -379,7 +379,7 @@ mod suspend_resume {
/* ImZyb20gdGVzdCIK = base64 "from test" */
reqwest::get(format!(
"http://localhost:{port}/api/w/test-workspace/jobs/cancel/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK"
"http://localhost:{port}/api/w/test-workspace/jobs_u/cancel/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK"
))
.await
.unwrap()
@@ -395,7 +395,7 @@ mod suspend_resume {
let result = completed_job(flow, &db).await.result.unwrap();
assert_eq!(
json!({"error": "Job canceled: approval request disapproved by unknown" }),
json!( {"error": {"name": "Canceled", "reason": "approval request disapproved", "message": "Job canceled: approval request disapproved by unknown", "canceler": "unknown"}}),
result
);
}
@@ -462,7 +462,7 @@ export async function main(index, port) {
const buf = new Uint8Array([0]);
const sock = await Deno.connect({ port });
await sock.write(new Uint8Array([index]));
if (await sock.read(buf) != 1) throw "read";
if (await sock.read(buf) != 1) throw Error("read");
return buf[0];
}
"#
@@ -581,10 +581,17 @@ def main(last, port):
.unwrap();
assert_eq!(server.close().await, attempts);
assert!(result[1]["error"]
.as_str()
.unwrap()
.contains(r#"Uncaught (in promise) "read""#));
assert!(
result[1]["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
== "read"
);
}
#[sqlx::test(fixtures("base"))]
@@ -615,6 +622,10 @@ def main(last, port):
let result = job.result.unwrap();
assert_eq!(server.close().await, attempts);
assert!(result["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("index out of range"));
@@ -684,9 +695,7 @@ def main(error, port):
result,
json!({
"recv": 42,
"from failure module": {
"error": "Error during execution of the script:\n\nTraceback (most recent call last):\n File \"/tmp/main.py\", line 15, in <module>\n res = inner_script.main(**kwargs)\n File \"/tmp/inner.py\", line 5, in main\n return sock.recv(1)[0]\nIndexError: index out of range",
}
"from failure module": {"error": {"name": "IndexError", "stack": " File \"/tmp/inner.py\", line 5, in main\n return sock.recv(1)[0]\n", "message": "index out of range"}},
})
);
}
@@ -739,9 +748,13 @@ async fn test_iteration(db: Pool<Postgres>) {
.unwrap();
assert!(matches!(result, serde_json::Value::Array(_)));
assert!(result[2]["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("StopIteration: 2"));
.contains("2"));
}
#[sqlx::test(fixtures("base"))]
@@ -792,9 +805,13 @@ async fn test_iteration_parallel(db: Pool<Postgres>) {
let result = job.result.unwrap();
assert!(matches!(result, serde_json::Value::Array(_)));
assert!(result[2]["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("StopIteration: 2"));
.contains("2"));
}
struct RunJob {
@@ -1767,7 +1784,7 @@ async fn test_invalid_first_step(db: Pool<Postgres>) {
assert_eq!(
job.result.unwrap(),
serde_json::json!({"error":"Expected an array value, found: {}"})
serde_json::json!( {"error": {"name": "InternalErr", "message": "Expected an array value, found: {}"}})
);
}
@@ -2083,7 +2100,7 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
assert_eq!(
result,
serde_json::json!([{"error": "Error during execution of the script:\n\nerror: Uncaught (in promise) Error: failure\nexport function main(){ throw Error('failure') }\n ^\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1"}, [1,3]])
serde_json::json!([{"error": {"name": "Error", "stack": "Error: failure\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1", "message": "failure"}}, [1,3]])
);
let flow: FlowValue = serde_json::from_value(json!({
@@ -2117,7 +2134,7 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
assert_eq!(
result,
serde_json::json!([{"error": "Error during execution of the script:\n\nerror: Uncaught (in promise) Error: failure\nexport function main(){ throw Error('failure') }\n ^\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1"}, [1, 2]])
serde_json::json!([ {"error": {"name": "Error", "stack": "Error: failure\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1", "message": "failure"}}, [1, 2]])
);
}
@@ -2256,7 +2273,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
},
"type": "rawscript",
"language": "deno",
"content": "export function main(n, l) { if (n == 0) throw l; return { l: [...l, 0] } }",
"content": "export function main(n, l) { if (n == 0) throw Error(JSON.stringify(l)); return { l: [...l, 0] } }",
},
}, {
"id": "b",
@@ -2267,7 +2284,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
},
"type": "rawscript",
"language": "deno",
"content": "export function main(n, l) { if (n == 1) throw l; return { l: [...l, 1] } }",
"content": "export function main(n, l) { if (n == 1) throw Error(JSON.stringify(l)); return { l: [...l, 1] } }",
},
}, {
"value": {
@@ -2277,7 +2294,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
},
"type": "rawscript",
"language": "deno",
"content": "export function main(n, l) { if (n == 2) throw l; return { l: [...l, 2] } }",
"content": "export function main(n, l) { if (n == 2) throw Error(JSON.stringify(l)); return { l: [...l, 2] } }",
},
}],
"failure_module": {
@@ -2297,10 +2314,15 @@ async fn test_failure_module(db: Pool<Postgres>) {
.await
.result
.unwrap();
assert!(result["from failure module"]["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("Uncaught (in promise) []"));
.contains("[]"));
let result = RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None })
.arg("n", json!(1))
@@ -2308,10 +2330,15 @@ async fn test_failure_module(db: Pool<Postgres>) {
.await
.result
.unwrap();
assert!(result["from failure module"]["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("Uncaught (in promise) [ 0 ]"));
.contains("[0]"));
let result = RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None })
.arg("n", json!(2))
@@ -2319,10 +2346,15 @@ async fn test_failure_module(db: Pool<Postgres>) {
.await
.result
.unwrap();
assert!(result["from failure module"]["error"]
.as_object()
.unwrap()
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("Uncaught (in promise) [ 0, 1 ]"));
.contains("[0,1]"));
let result = RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None })
.arg("n", json!(3))

View File

@@ -69,3 +69,5 @@ hmac.workspace = true
cookie.workspace = true
sha2.workspace = true
urlencoding.workspace = true
async-stripe.workspace = true
lazy_static.workspace = true

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -e
npx @redocly/openapi-cli@latest bundle openapi.yaml > openapi-bundled.yaml
npx @redocly/openapi-cli@latest bundle openapi-bundled.yaml --ext yaml -d > openapi-deref.yaml
rm openapi-bundled.yaml

View File

View File

@@ -1,10 +1,11 @@
openapi: "3.0.3"
info:
version: 1.60.0
title: Windmill server API
version: 1.61.1
title: Windmill API
contact:
name: Windmill contact
name: Windmill Team
email: contact@windmill.dev
url: https://windmill.dev
@@ -12,6 +13,8 @@ info:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
x-logo:
url: https://docs.windmill.dev/img/windmill.svg
externalDocs:
description: documentation portal
url: https://docs.windmill.dev
@@ -876,6 +879,10 @@ paths:
type: string
auto_invite_operator:
type: boolean
plan:
type: string
customer_id:
type: string
/w/{workspace}/workspaces/premium_info:
get:
@@ -2421,6 +2428,45 @@ paths:
type: integer
- $ref: "#/components/parameters/ParentJob"
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
requestBody:
description: script args
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ScriptArgs"
responses:
"200":
description: job result
content:
application/json:
schema: {}
/w/{workspace}/jobs/run_wait_result/f/{path}:
post:
summary: run flow by path and wait until completion
operationId: runWaitResultFlowByPath
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
- name: scheduled_for
description: when to schedule this job (leave empty for immediate run)
in: query
schema:
type: string
format: date-time
- name: scheduled_in_secs
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
requestBody:
description: script args
@@ -4458,6 +4504,13 @@ components:
in: query
schema:
type: string
QueueLimit:
name: queue_limit
description: |
The maximum size of the queue for which the request would get rejected if that job would push it above that limit
in: query
schema:
type: string
ScriptStartPath:
name: script_path_start

View File

@@ -31,7 +31,7 @@ use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
apps::ListAppQuery,
error::{to_anyhow, Error, JsonResult, Result},
users::owner_to_token_owner,
users::username_to_permissioned_as,
utils::{
http_get_from_hub, list_elems_from_hub, not_found_if_none, paginate, Pagination, StripPath,
},
@@ -610,7 +610,7 @@ async fn execute_component(
})?;
(
username.clone(),
owner_to_token_owner(&username, false),
username_to_permissioned_as(&username),
email,
)
}

View File

@@ -230,7 +230,7 @@ async fn create_flow(
serde_json::Map::new(),
&authed.username,
&authed.email,
windmill_common::users::owner_to_token_owner(&authed.username, false),
windmill_common::users::username_to_permissioned_as(&authed.username),
None,
None,
None,
@@ -377,7 +377,7 @@ async fn update_flow(
serde_json::Map::new(),
&authed.username,
&authed.email,
windmill_common::users::owner_to_token_owner(&authed.username, false),
windmill_common::users::username_to_permissioned_as(&authed.username),
None,
None,
None,

View File

@@ -19,7 +19,7 @@ use itertools::Itertools;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{self, Error, JsonResult, Result},
users::owner_to_token_owner,
users::username_to_permissioned_as,
utils::{not_found_if_none, paginate, Pagination},
};
@@ -145,7 +145,7 @@ async fn create_folder(
let mut tx = user_db.begin(&authed).await?;
check_name_conflict(&mut tx, &w_id, &ng.name).await?;
let owner = owner_to_token_owner(&authed.username, false);
let owner = username_to_permissioned_as(&authed.username);
let owners = &ng.owners.unwrap_or(vec![owner.clone()]);
if let Some(extra_perms) = ng.extra_perms.clone() {

View File

@@ -16,9 +16,9 @@ use axum::{
Json, Router,
};
use windmill_audit::{audit_log, ActionKind};
use windmill_common::users::username_to_permissioned_as;
use windmill_common::{
error::{Error, JsonResult, Result},
users::owner_to_token_owner,
utils::{not_found_if_none, paginate, Pagination},
};
@@ -199,7 +199,7 @@ async fn create_group(
w_id,
ng.name,
ng.summary,
serde_json::json!({owner_to_token_owner(&authed.username, false): true})
serde_json::json!({username_to_permissioned_as(&authed.username): true})
)
.execute(&mut tx)
.await?;

View File

@@ -29,7 +29,7 @@ use windmill_common::{
flows::FlowValue,
oauth2::HmacSha256,
scripts::{ScriptHash, ScriptLang},
users::owner_to_token_owner,
users::username_to_permissioned_as,
utils::{not_found_if_none, now_from_db, paginate, require_admin, Pagination, StripPath},
};
use windmill_queue::{get_queued_job, push, JobKind, JobPayload, QueuedJob, RawCode};
@@ -38,7 +38,7 @@ use crate::{
db::{UserDB, DB},
users::{require_owner_of_path, Authed},
variables::get_workspace_key,
BaseUrl,
BaseUrl, QueueLimitWaitResult, TimeoutWaitResult,
};
pub fn workspaced_service() -> Router {
@@ -53,6 +53,10 @@ pub fn workspaced_service() -> Router {
"/run_wait_result/h/:hash",
post(run_wait_result_job_by_hash),
)
.route(
"/run_wait_result/f/*script_path",
post(run_wait_result_flow_by_path),
)
.route("/run/h/:hash", post(run_job_by_hash))
.route("/run/preview", post(run_preview_job))
.route("/run/preview_flow", post(run_preview_flow_job))
@@ -263,6 +267,15 @@ pub struct RunJobQuery {
parent_job: Option<Uuid>,
include_header: Option<String>,
invisible_to_owner: Option<bool>,
queue_limit: Option<i64>,
}
lazy_static::lazy_static! {
static ref INCLUDE_HEADERS: Vec<String> = std::env::var("INCLUDE_HEADERS")
.ok().map(|x| x
.split(',')
.map(|s| s.to_string())
.collect()).unwrap_or_default();
}
impl RunJobQuery {
@@ -285,11 +298,14 @@ impl RunJobQuery {
headers: HeaderMap,
mut args: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
self.include_header
let whitelist = self
.include_header
.as_ref()
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default()
.unwrap_or_default();
whitelist
.iter()
.chain(INCLUDE_HEADERS.iter())
.for_each(|h| {
if let Some(v) = headers.get(h) {
args.insert(
@@ -1139,7 +1155,7 @@ pub async fn run_flow_by_path(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,
@@ -1174,7 +1190,7 @@ pub async fn run_job_by_path(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,
@@ -1188,16 +1204,66 @@ pub async fn run_job_by_path(
Ok((StatusCode::CREATED, uuid.to_string()))
}
struct Guard {
done: bool,
id: Uuid,
w_id: String,
db: UserDB,
authed: Authed,
}
impl Drop for Guard {
fn drop(&mut self) {
if !&self.done {
let id = self.id;
let username = self.authed.username.clone();
let w_id = self.w_id.clone();
let db = self.db.clone();
let authed = self.authed.clone();
tracing::info!("http connection broke, marking job {id} as canceled");
tokio::spawn(async move {
let tx = db.begin(&authed).await.ok();
if let Some(mut tx) = tx {
let _ = sqlx::query!(
"UPDATE queue SET canceled = true, canceled_reason = 'http connection broke', canceled_by = $1 WHERE id = $2 AND workspace_id = $3",
username,
id,
w_id
)
.execute(&mut tx)
.await;
let _ = tx.commit().await;
}
});
}
}
}
async fn run_wait_result<T>(
authed: Authed,
Extension(user_db): Extension<UserDB>,
timeout: i32,
uuid: Uuid,
Path((w_id, _)): Path<(String, T)>,
) -> error::JsonResult<serde_json::Value> {
let mut result = None;
for i in 0..48 {
let iters = if timeout <= 0 {
20
} else if timeout <= 1 {
timeout * 10
} else {
10 + ((timeout - 1) * 2)
};
let mut g = Guard {
done: false,
id: uuid,
w_id: w_id.clone(),
db: user_db.clone(),
authed: authed.clone(),
};
for i in 0..iters {
let mut tx = user_db.clone().begin(&authed).await?;
result = sqlx::query_scalar!(
"SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2",
uuid,
@@ -1206,6 +1272,7 @@ async fn run_wait_result<T>(
.fetch_optional(&mut tx)
.await?
.flatten();
drop(tx);
if result.is_some() {
break;
@@ -1214,20 +1281,45 @@ async fn run_wait_result<T>(
tokio::time::sleep(core::time::Duration::from_millis(delay)).await;
}
if let Some(result) = result {
g.done = true;
Ok(Json(result))
} else {
Err(Error::ExecutionErr("timeout after 20s".to_string()))
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
}
}
pub async fn check_queue_too_long(db: DB, queue_limit: Option<i64>) -> error::Result<()> {
if let Some(limit) = queue_limit {
let count = sqlx::query_scalar!(
"SELECT COUNT(*) FROM queue WHERE canceled = false AND (scheduled_for <= now()
OR (suspend_until IS NOT NULL
AND ( suspend <= 0
OR suspend_until <= now())))",
)
.fetch_one(&db)
.await?
.unwrap_or(0);
if count > queue_limit.unwrap() {
return Err(Error::InternalErr(format!(
"Number of queued job is too high: {count} > {limit}"
)));
}
}
Ok(())
}
pub async fn run_wait_result_job_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
Extension(queue_limit): Extension<Arc<QueueLimitWaitResult>>,
Path((w_id, script_path)): Path<(String, StripPath)>,
Query(run_query): Query<RunJobQuery>,
headers: HeaderMap,
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
) -> error::JsonResult<serde_json::Value> {
check_queue_too_long(db, queue_limit.0.or(run_query.queue_limit)).await?;
let script_path = script_path.to_path();
let mut tx = user_db.clone().begin(&authed).await?;
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
@@ -1242,7 +1334,7 @@ pub async fn run_wait_result_job_by_path(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,
@@ -1254,17 +1346,28 @@ pub async fn run_wait_result_job_by_path(
.await?;
tx.commit().await?;
run_wait_result(authed, Extension(user_db), uuid, Path((w_id, script_path))).await
run_wait_result(
authed,
Extension(user_db),
timeout.0,
uuid,
Path((w_id, script_path)),
)
.await
}
pub async fn run_wait_result_job_by_hash(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
Query(run_query): Query<RunJobQuery>,
headers: HeaderMap,
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
) -> error::JsonResult<serde_json::Value> {
check_queue_too_long(db, run_query.queue_limit).await?;
let hash = script_hash.0;
let mut tx = user_db.clone().begin(&authed).await?;
let path = get_path_for_hash(&mut tx, &w_id, hash).await?;
@@ -1278,7 +1381,7 @@ pub async fn run_wait_result_job_by_hash(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,
@@ -1290,7 +1393,61 @@ pub async fn run_wait_result_job_by_hash(
.await?;
tx.commit().await?;
run_wait_result(authed, Extension(user_db), uuid, Path((w_id, script_hash))).await
run_wait_result(
authed,
Extension(user_db),
timeout.0,
uuid,
Path((w_id, script_hash)),
)
.await
}
pub async fn run_wait_result_flow_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
Path((w_id, flow_path)): Path<(String, StripPath)>,
Query(run_query): Query<RunJobQuery>,
headers: HeaderMap,
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
) -> error::JsonResult<serde_json::Value> {
check_queue_too_long(db, run_query.queue_limit).await?;
let flow_path = flow_path.to_path();
let mut tx = user_db.clone().begin(&authed).await?;
let scheduled_for = run_query.get_scheduled_for(&mut tx).await?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let (uuid, tx) = push(
tx,
&w_id,
JobPayload::Flow(flow_path.to_string()),
args,
&authed.username,
&authed.email,
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,
false,
false,
None,
!run_query.invisible_to_owner.unwrap_or(false),
)
.await?;
tx.commit().await?;
run_wait_result(
authed,
Extension(user_db),
timeout.0,
uuid,
Path((w_id, flow_path)),
)
.await
}
// a similar function exists on the worker
@@ -1332,7 +1489,7 @@ async fn run_preview_job(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
None,
@@ -1365,7 +1522,7 @@ async fn run_preview_flow_job(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
None,
@@ -1400,7 +1557,7 @@ pub async fn run_job_by_hash(
args,
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,

View File

@@ -53,6 +53,8 @@ pub struct IsSecure(bool);
pub struct CookieDomain(Option<String>);
pub struct CloudHosted(bool);
pub struct ContentSecurityPolicy(String);
pub struct TimeoutWaitResult(i32);
pub struct QueueLimitWaitResult(Option<i64>);
pub use users::delete_expired_items_perdiodically;
@@ -91,15 +93,15 @@ pub async fn run_server(
.layer(Extension(auth_cache.clone()))
.layer(Extension(basic_clients))
.layer(Extension(Arc::new(BaseUrl(base_url.to_string()))))
.layer(Extension(Arc::new(ContentSecurityPolicy(
std::env::var("SERVE_CSP").unwrap_or("".to_owned()),
))))
.layer(Extension(Arc::new(CloudHosted(
std::env::var("CLOUD_HOSTED").is_ok(),
))))
.layer(Extension(Arc::new(IsSecure(
base_url.starts_with("https://"),
))))
.layer(Extension(Arc::new(ContentSecurityPolicy(
std::env::var("SERVE_CSP").unwrap_or("".to_owned()),
))))
.layer(Extension(Arc::new(CookieDomain(
std::env::var("COOKIE_DOMAIN").ok(),
))))
@@ -114,7 +116,21 @@ pub async fn run_server(
"/w/:workspace_id",
Router::new()
.nest("/scripts", scripts::workspaced_service())
.nest("/jobs", jobs::workspaced_service())
.nest(
"/jobs",
jobs::workspaced_service()
.layer(Extension(Arc::new(TimeoutWaitResult(
std::env::var("TIMEOUT_WAIT_RESULT")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(20),
))))
.layer(Extension(Arc::new(QueueLimitWaitResult(
std::env::var("QUEUE_LIMIT_WAIT_RESULT")
.ok()
.and_then(|x| x.parse().ok()),
)))),
)
.nest(
"/users",
users::workspaced_service().layer(Extension(argon2.clone())),
@@ -186,7 +202,7 @@ async fn git_v() -> &'static str {
}
async fn openapi() -> &'static str {
include_str!("../openapi.yaml")
include_str!("../openapi-deref.yaml")
}
pub async fn migrate_db(db: &DB) -> anyhow::Result<()> {
db::migrate(db).await?;

View File

@@ -36,7 +36,7 @@ use windmill_common::{
to_i64, HubScript, ListScriptQuery, ListableScript, NewScript, Script, ScriptHash,
ScriptKind, ScriptLang,
},
users::owner_to_token_owner,
users::username_to_permissioned_as,
utils::{
list_elems_from_hub, not_found_if_none, paginate, require_admin, Pagination, StripPath,
},
@@ -374,7 +374,7 @@ async fn create_script(
serde_json::Map::new(),
&authed.username,
&authed.email,
owner_to_token_owner(&authed.username, false),
username_to_permissioned_as(&authed.username),
None,
None,
None,

View File

@@ -18,7 +18,7 @@ use crate::{
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use axum::{
async_trait,
extract::{Extension, FromRequestParts, Path, Query},
extract::{Extension, FromRequestParts, OriginalUri, Path, Query},
http::{self, request::Parts},
response::{IntoResponse, Response},
routing::{delete, get, post},
@@ -177,7 +177,16 @@ impl AuthCache {
})
}
} else {
None
let groups = vec![];
let folders = vec![];
Some(Authed {
email: email
.unwrap_or_else(|| "missing@email.xyz".to_string()),
username: owner,
is_admin: super_admin,
groups,
folders,
})
}
}
(_, Some(email), super_admin) => {
@@ -365,9 +374,15 @@ where
} else {
extract_token(parts, state).await
};
let path_vec: Vec<&str> = parts.uri.path().split("/").collect();
let workspace_id = if path_vec[0] == "" && path_vec[1] == "w" {
Some(path_vec[2].to_owned())
let original_uri = OriginalUri::from_request_parts(parts, state)
.await
.ok()
.map(|x| x.0)
.unwrap_or_default();
let path_vec: Vec<&str> = original_uri.path().split("/").collect();
let workspace_id = if path_vec.len() >= 4 && path_vec[0] == "" && path_vec[2] == "w" {
Some(path_vec[3].to_owned())
} else {
None
};

View File

@@ -6,24 +6,28 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::{str::FromStr, sync::Arc};
use crate::{
db::{UserDB, DB},
folders::Folder,
resources::{Resource, ResourceType},
users::{Authed, WorkspaceInvite},
utils::require_super_admin,
BaseUrl,
};
use axum::{
body::StreamBody,
extract::{Extension, Path, Query},
headers,
response::IntoResponse,
response::{IntoResponse, Redirect},
routing::{delete, get, post},
Json, Router,
};
use stripe::CustomerId;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{Error, JsonResult, Result},
error::{to_anyhow, Error, JsonResult, Result},
flows::Flow,
scripts::{Schema, Script, ScriptLang},
utils::{paginate, rd_string, require_admin, Pagination},
@@ -50,6 +54,8 @@ pub fn workspaced_service() -> Router {
.route("/edit_auto_invite", post(edit_auto_invite))
.route("/tarball", get(tarball_workspace))
.route("/premium_info", get(premium_info))
.route("/checkout", get(stripe_checkout))
.route("/billing_portal", get(stripe_portal))
}
pub fn global_service() -> Router {
Router::new()
@@ -82,6 +88,8 @@ pub struct WorkspaceSettings {
pub slack_email: String,
pub auto_invite_domain: Option<String>,
pub auto_invite_operator: Option<bool>,
pub customer_id: Option<String>,
pub plan: Option<String>,
}
#[derive(FromRow, Serialize, Debug)]
@@ -201,6 +209,148 @@ async fn premium_info(
Ok(Json(row))
}
#[derive(Deserialize)]
struct PlanQuery {
plan: String,
}
async fn stripe_checkout(
authed: Authed,
Path(w_id): Path<String>,
Query(plan): Query<PlanQuery>,
Extension(base_url): Extension<Arc<BaseUrl>>,
) -> Result<Redirect> {
// #[cfg(feature = "enterprise")]
{
require_admin(authed.is_admin, &authed.username)?;
let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
let success_rd = format!("{}/workspace_settings/checkout?success=true", base_url.0);
let failure_rd = format!("{}/workspace_settings/checkout?success=false", base_url.0);
let checkout_session = {
let mut params = stripe::CreateCheckoutSession::new(&failure_rd, &success_rd);
params.mode = Some(stripe::CheckoutSessionMode::Subscription);
params.line_items = match plan.plan.as_str() {
"team" => Some(vec![
stripe::CreateCheckoutSessionLineItems {
quantity: Some(1),
price: Some("price_1MUlrWGU3NdFi9eLE9GBZhoY".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MUlreGU3NdFi9eLi6sOyvVa".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MUlrlGU3NdFi9eLFLggSXZV".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MUlr3GU3NdFi9eLbZYFjR9p".to_string()),
..Default::default()
},
]),
// "enterprise" => Some(vec![
// stripe::CreateCheckoutSessionLineItems {
// quantity: None,
// price: Some("price_1MSdf6GU3NdFi9eLJFRkntlx".to_string()),
// ..Default::default()
// },
// stripe::CreateCheckoutSessionLineItems {
// quantity: None,
// price: Some("price_1MShsNGU3NdFi9eLJMEZUW8b".to_string()),
// ..Default::default()
// },
// ]),
_ => Err(Error::BadRequest("invalid plan".to_string()))?,
};
params.customer_email = Some(&authed.email);
params.client_reference_id = Some(&w_id);
stripe::CheckoutSession::create(&client, params)
.await
.unwrap()
};
let uri = checkout_session
.url
.ok_or_else(|| Error::InternalErr(format!("stripe checkout redirect issue")))?;
Ok(Redirect::to(&uri))
}
}
async fn stripe_portal(
authed: Authed,
Path(w_id): Path<String>,
Extension(db): Extension<DB>,
Extension(base_url): Extension<Arc<BaseUrl>>,
) -> Result<Redirect> {
require_admin(authed.is_admin, &authed.username)?;
let customer_id = sqlx::query_scalar!(
"SELECT customer_id FROM workspace_settings WHERE workspace_id = $1",
w_id
)
.fetch_one(&db)
.await?
.ok_or_else(|| Error::InternalErr(format!("no customer id for workspace {}", w_id)))?;
let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
let success_rd = format!("{}/workspace_settings?tab=premium", base_url.0);
let portal_session = {
let customer_id = CustomerId::from_str(&customer_id).unwrap();
let mut params = stripe::CreateBillingPortalSession::new(customer_id);
params.return_url = Some(&success_rd);
stripe::BillingPortalSession::create(&client, params)
.await
.map_err(to_anyhow)?
};
Ok(Redirect::to(&portal_session.url))
}
// async fn stripe_usage(
// authed: Authed,
// Path(w_id): Path<String>,
// Extension(db): Extension<DB>,
// Extension(base_url): Extension<Arc<BaseUrl>>,
// ) -> Result<Redirect> {
// require_admin(authed.is_admin, &authed.username)?;
// let customer_id = sqlx::query_scalar!(
// "SELECT customer_id FROM workspace_settings WHERE workspace_id = $1",
// w_id
// )
// .fetch_one(&db)
// .await?
// .ok_or_else(|| Error::InternalErr(format!("no customer id for workspace {}", w_id)))?;
// let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
// let success_rd = format!("{}/workspace_settings?tab=premium", base_url.0);
// let portal_session = {
// let customer_id = CustomerId::from_str(&customer_id).unwrap();
// let subscriptions = stripe::Subscription::list(
// &client,
// stripe::ListSubscriptions { customer: Some(customer_id), ..Default::default() },
// )
// .await
// .map_err(to_anyhow)?
// .data[0];
// let getUsage =
// stripe::SubscriptionItem::list(
// &client,
// stripe::ListSubscriptionItems {
// subscription: subscription.id,
// ..Default::default()
// },
// )
// .await
// .map_err(to_anyhow)
// };
// let mut params = stripe::ListSubscriptionItems::new(customer_id);
// params.return_url = Some(&success_rd);
// stripe::BillingPortalSession::create(&client, params)
// .await
// .map_err(to_anyhow)?
// };
// }
async fn exists_workspace(
authed: Authed,
Extension(user_db): Extension<UserDB>,

View File

@@ -58,6 +58,8 @@ pub enum Error {
ExitStatus(i32),
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
#[error("Error: {0:#?}")]
JsonErr(serde_json::Value),
}
impl Error {

View File

@@ -11,7 +11,9 @@ use std::{collections::HashMap, time::Duration};
use serde::{self, Deserialize, Serialize};
use crate::{
more_serde::{default_false, default_id, default_true, is_default},
more_serde::{
default_empty_string, default_false, default_id, default_null, default_true, is_default,
},
scripts::{Schema, ScriptHash, ScriptLang},
};
@@ -172,8 +174,14 @@ impl FlowModule {
rename_all(serialize = "lowercase", deserialize = "lowercase")
)]
pub enum InputTransform {
Static { value: serde_json::Value },
Javascript { expr: String },
Static {
#[serde(default = "default_null")]
value: serde_json::Value,
},
Javascript {
#[serde(default = "default_empty_string")]
expr: String,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]

View File

@@ -18,6 +18,14 @@ pub fn default_false() -> bool {
false
}
pub fn default_null() -> serde_json::Value {
serde_json::Value::Null
}
pub fn default_empty_string() -> String {
String::new()
}
pub fn default_id() -> String {
rd_string(6)
}

View File

@@ -42,7 +42,11 @@ pub fn initialize_tracing() {
match json_fmt {
true => ts_base
.with(json_layer().with_filter(filter_fn(filter_metadata)))
.with(
json_layer()
.flatten_event(true)
.with_filter(filter_fn(filter_metadata)),
)
.init(),
false => ts_base
.with(

View File

@@ -6,7 +6,10 @@
* LICENSE-AGPL for a copy of the license.
*/
pub fn owner_to_token_owner(user: &str, is_group: bool) -> String {
let prefix = if is_group { 'g' } else { 'u' };
format!("{}/{}", prefix, user)
pub fn username_to_permissioned_as(user: &str) -> String {
if user.contains('@') {
user.to_string()
} else {
format!("u/{}", user)
}
}

View File

@@ -72,10 +72,6 @@ pub fn not_found_if_none<T, U: AsRef<str>>(opt: Option<T>, kind: &str, name: U)
}
}
pub fn get_owner_from_path(path: &str) -> String {
path.split('/').take(2).collect::<Vec<_>>().join("/")
}
#[cfg(feature = "reqwest")]
pub async fn list_elems_from_hub(
http_client: reqwest::Client,

View File

@@ -8,6 +8,7 @@
use std::{collections::HashMap, str::FromStr};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Postgres, Transaction};
use tracing::{instrument, Instrument};
@@ -44,6 +45,7 @@ lazy_static::lazy_static! {
}
const MAX_FREE_EXECS: i32 = 1000;
const MAX_FREE_CONCURRENT_RUNS: i32 = 3;
pub async fn cancel_job<'c>(
username: &str,
@@ -172,7 +174,6 @@ pub async fn get_result_by_id(
"Flow result by id",
format!("{}, {}", flow_id, node_id),
)?;
println!("result_id: {:#?}, {node_id}", result_id);
let value = match result_id {
JobResult::ListJob(x) => {
@@ -318,10 +319,38 @@ pub async fn push<'c>(
.fetch_optional(&mut tx)
.await?
.unwrap_or(false);
if !is_super_admin && usage > MAX_FREE_EXECS {
return Err(error::Error::BadRequest(format!(
if !is_super_admin {
if usage > MAX_FREE_EXECS {
return Err(error::Error::BadRequest(format!(
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
)));
}
let in_queue =
sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE email = $1", email)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
if in_queue > MAX_FREE_EXECS.into() {
return Err(error::Error::BadRequest(format!(
"User {email} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
)));
}
let concurrent_runs = sqlx::query_scalar!(
"SELECT COUNT(id) FROM queue WHERE running = true AND email = $1",
email
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
if concurrent_runs > MAX_FREE_CONCURRENT_RUNS.into() {
return Err(error::Error::BadRequest(format!(
"User {email} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
)));
}
}
}
}
@@ -352,7 +381,9 @@ pub async fn push<'c>(
)
}
JobPayload::ScriptHub { path } => {
let script = get_hub_script(path.clone(), email).await?;
let script = get_hub_script(path.clone(), email)
.await
.context("error fetching hub script")?;
(
None,
Some(path),
@@ -545,13 +576,13 @@ pub async fn push<'c>(
Ok((uuid, tx))
}
pub fn canceled_job_to_result(job: &QueuedJob) -> String {
pub fn canceled_job_to_result(job: &QueuedJob) -> serde_json::Value {
let reason = job
.canceled_reason
.as_deref()
.unwrap_or_else(|| "no reason given");
let canceler = job.canceled_by.as_deref().unwrap_or_else(|| "unknown");
format!("Job canceled: {reason} by {canceler}")
serde_json::json!({"message": format!("Job canceled: {reason} by {canceler}"), "name": "Canceled", "reason": reason, "canceler": canceler})
}
pub async fn get_hub_script(path: String, email: &str) -> error::Result<HubScript> {

View File

@@ -13,7 +13,8 @@ use sqlx::{query_scalar, Postgres, Transaction};
use windmill_common::{
error::{self, Result},
schedule::Schedule,
utils::{get_owner_from_path, now_from_db, StripPath},
users::username_to_permissioned_as,
utils::{now_from_db, StripPath},
};
use crate::{push, JobPayload};
@@ -80,7 +81,7 @@ pub async fn push_scheduled_job<'c>(
args,
&schedule_to_user(&schedule.path),
&schedule.email,
get_owner_from_path(&schedule.path),
username_to_permissioned_as(&schedule.edited_by),
Some(next),
Some(schedule.path.clone()),
None,

View File

@@ -3,12 +3,11 @@ name: "bash run script"
mode: ONCE
hostname: "bash"
log_level: ERROR
time_limit: 300
rlimit_as: 2048
rlimit_as: 4096
rlimit_cpu: 1000
rlimit_fsize: 1024
rlimit_nofile: 64
rlimit_fsize: 1000
rlimit_nofile: 10000
cwd: "/tmp"
@@ -55,7 +54,7 @@ mount {
dst: "/tmp"
fstype: "tmpfs"
rw: true
options: "size=500000000"
options: "size=800000000"
}
mount {
@@ -87,4 +86,4 @@ iface_no_lo: true
{SHARED_MOUNT}
envar: "HOME=/tmp"

View File

@@ -3,12 +3,11 @@ name: "deno run script"
mode: ONCE
hostname: "deno"
log_level: ERROR
time_limit: 300
rlimit_as: 16000
rlimit_cpu: 1000
rlimit_fsize: 1024
rlimit_nofile: 64
rlimit_fsize: 1000
rlimit_nofile: 10000
cwd: "/tmp"
@@ -73,6 +72,12 @@ mount {
is_bind: true
}
mount {
src: "{JOB_DIR}/import_map.json"
dst: "/tmp/import_map.json"
is_bind: true
}
mount {
src: "{JOB_DIR}/lock.json"
dst: "/tmp/lock.json"

View File

@@ -3,12 +3,11 @@ name: "go run script"
mode: ONCE
hostname: "go"
log_level: ERROR
time_limit: 300
rlimit_as: 2048
rlimit_as: 4096
rlimit_cpu: 1000
rlimit_fsize: 1024
rlimit_nofile: 64
rlimit_fsize: 1000
rlimit_nofile: 10000
cwd: "/tmp/go"

View File

@@ -3,12 +3,11 @@ name: "python run script"
mode: ONCE
hostname: "python"
log_level: ERROR
time_limit: 300
rlimit_as: 2048
rlimit_as: 4096
rlimit_cpu: 1000
rlimit_fsize: 1024
rlimit_nofile: 64
rlimit_fsize: 1000
rlimit_nofile: 10000
cwd: "/tmp"

View File

@@ -6,7 +6,6 @@
* LICENSE-AGPL for a copy of the license.
*/
use serde_json::{Map, Value};
use sqlx::{Pool, Postgres, Transaction};
use tracing::instrument;
use uuid::Uuid;
@@ -14,36 +13,17 @@ use windmill_common::{error::Error, flow_status::FlowStatusModule, schedule::Sch
use windmill_queue::{delete_job, schedule::get_schedule_opt, JobKind, QueuedJob};
#[instrument(level = "trace", skip_all)]
pub async fn add_completed_job_error<E: ToString + std::fmt::Debug>(
pub async fn add_completed_job_error(
db: &Pool<Postgres>,
queued_job: &QueuedJob,
logs: String,
e: E,
e: serde_json::Value,
metrics: Option<crate::worker::Metrics>,
) -> Result<(Uuid, serde_json::Map<String, serde_json::Value>), Error> {
) -> Result<serde_json::Value, Error> {
metrics.map(|m| m.worker_execution_failed.inc());
let mut output_map = Map::new();
error_to_result(&mut output_map, &e);
let a = add_completed_job(
db,
&queued_job,
false,
false,
serde_json::Value::Object(output_map.clone()),
logs,
)
.await?;
Ok((a, output_map))
}
pub fn error_to_result<E: ToString + std::fmt::Debug>(
output_map: &mut Map<String, Value>,
err: &E,
) {
output_map.insert(
"error".to_string(),
serde_json::Value::String(err.to_string()),
);
let result = serde_json::json!({ "error": e });
let _ = add_completed_job(db, &queued_job, false, false, result.clone(), logs).await?;
Ok(result)
}
fn flatten_jobs(modules: Vec<FlowStatusModule>) -> Vec<Uuid> {
@@ -130,7 +110,7 @@ pub async fn add_completed_job(
, email
, visible_to_owner
)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, EXTRACT(milliseconds FROM (now() - $6))), $7, $8, $9,\
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,\
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)
ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)",
queued_job.workspace_id,

View File

@@ -12,7 +12,10 @@ use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use sqlx::{Pool, Postgres, Transaction};
use std::{borrow::Borrow, collections::HashMap, io, panic, process::Stdio, time::Duration};
use std::{
borrow::Borrow, collections::HashMap, io, os::unix::process::ExitStatusExt, panic,
process::Stdio, time::Duration,
};
use tracing::{trace_span, Instrument};
use uuid::Uuid;
@@ -25,7 +28,7 @@ use windmill_common::{
};
use windmill_queue::{canceled_job_to_result, get_queued_job, pull, JobKind, QueuedJob};
use serde_json::{json, Map, Value};
use serde_json::{json, Value};
use tokio::{
fs::{metadata, symlink, DirBuilder, File},
@@ -46,7 +49,7 @@ use futures::{
use async_recursion::async_recursion;
use crate::{
jobs::{add_completed_job, add_completed_job_error, error_to_result},
jobs::{add_completed_job, add_completed_job_error},
worker_flow::{
handle_flow, update_flow_status_after_job_completion, update_flow_status_in_progress,
},
@@ -130,7 +133,7 @@ async fn copy_cache_to_bucket_as_tar(bucket: &str) {
.current_dir(ROOT_CACHE_DIR)
.arg("-c")
.arg("-f")
.arg(format!("{ROOT_CACHE_DIR}/{TAR_CACHE_FILENAME}"))
.arg(format!("{ROOT_CACHE_DIR}{TAR_CACHE_FILENAME}"))
.args(&["pip", "go", "deno"])
.stdin(Stdio::null())
.stdout(Stdio::null())
@@ -178,7 +181,7 @@ async fn copy_cache_from_bucket_as_tar(bucket: &str) -> bool {
match Command::new("rclone")
.arg("copyto")
.arg(format!(":s3,env_auth=true:{bucket}/{TAR_CACHE_FILENAME}"))
.arg(format!("{ROOT_CACHE_DIR}/{TAR_CACHE_FILENAME}"))
.arg(format!("{ROOT_CACHE_DIR}{TAR_CACHE_FILENAME}"))
.arg("--size-only")
.arg("--fast-list")
.stdin(Stdio::null())
@@ -231,29 +234,27 @@ pub async fn create_token_for_owner<'c>(
owner: &str,
label: &str,
expires_in: i32,
username: &str,
email: &str,
) -> error::Result<(Transaction<'c, Postgres>, String)> {
// TODO: Bad implementation. We should not have access to this DB here.
let token: String = rd_string(30);
let is_super_admin = username.contains('@')
&& sqlx::query_scalar!(
"SELECT super_admin FROM password WHERE email = $1",
owner.split_once('/').map(|x| x.1).unwrap_or("")
)
.fetch_optional(&mut tx)
.await?
.unwrap_or(false);
let is_super_admin = owner.contains('@')
&& sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", owner)
.fetch_optional(&mut tx)
.await?
.unwrap_or(false);
sqlx::query_scalar!(
"INSERT INTO token
(workspace_id, token, owner, label, expiration, super_admin)
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)",
(workspace_id, token, owner, label, expiration, super_admin, email)
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6, $7)",
&w_id,
token,
owner,
label,
expires_in.to_string(),
is_super_admin
is_super_admin,
email
)
.execute(&mut tx)
.await?;
@@ -277,7 +278,6 @@ const NSJAIL_CONFIG_RUN_DENO_CONTENT: &str = include_str!("../nsjail/run.deno.co
const RELATIVE_PYTHON_LOADER: &str = include_str!("../loader.py");
const MAX_LOG_SIZE: u32 = 200000;
const GO_REQ_SPLITTER: &str = "//go.sum";
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
@@ -416,6 +416,26 @@ pub async fn run_worker(
let pip_index_url = std::env::var("PIP_INDEX_URL").ok();
let pip_extra_index_url = std::env::var("PIP_EXTRA_INDEX_URL").ok();
let pip_trusted_host = std::env::var("PIP_TRUSTED_HOST").ok();
let deno_auth_tokens = std::env::var("DENO_AUTH_TOKENS")
.ok()
.map(|x| format!(";{x}"))
.unwrap_or_else(|| String::new());
let max_log_size = std::env::var("MAX_LOG_SIZE")
.ok()
.and_then(|x| x.parse::<i64>().ok())
.unwrap_or(500000);
let deno_flags = std::env::var("DENO_FLAGS")
.ok()
.map(|x| x.split(' ').map(|x| x.to_string()).collect());
let pip_local_dependencies = std::env::var("PIP_LOCAL_DEPENDENCIES")
.ok()
.map(|x| x.split(',').map(|x| x.to_string()).collect());
let pip_local_dependencies = if pip_local_dependencies == Some(vec!["".to_string()]) {
None
} else {
pip_local_dependencies
};
#[cfg(feature = "enterprise")]
let tar_cache_rate = std::env::var("TAR_CACHE_RATE")
@@ -423,6 +443,10 @@ pub async fn run_worker(
.and_then(|x| x.parse::<i32>().ok())
.unwrap_or(100);
let additional_python_paths = std::env::var("ADDITIONAL_PYTHON_PATHS")
.ok()
.map(|x| x.split(':').map(|x| x.to_string()).collect());
let envs = Envs {
deno_path,
go_path,
@@ -433,6 +457,11 @@ pub async fn run_worker(
pip_index_url,
pip_extra_index_url,
pip_trusted_host,
max_log_size,
deno_flags,
deno_auth_tokens,
pip_local_dependencies,
additional_python_paths,
};
WORKER_STARTED.inc();
@@ -567,11 +596,11 @@ pub async fn run_worker(
&job.permissioned_as,
"ephemeral-script",
timeout * 2,
&job.created_by,
&job.email,
)
.await.expect("could not create job token");
tx.commit().await.expect("could not commit job token");
let job_client = windmill_api_client::create_client(&worker_config.base_url, token.clone());
let job_client = windmill_api_client::create_client(&worker_config.base_internal_url, token.clone());
let is_flow = job.job_kind == JobKind::Flow || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::FlowDependencies;
if let Some(err) = handle_queued_job(
@@ -647,26 +676,29 @@ async fn handle_job_error(
keep_job_dir: bool,
base_internal_url: &str,
) {
add_completed_job_error(
db,
&job,
format!("Unexpected error during job execution:\n{err}"),
&err,
metrics.clone(),
)
.await
.map(|(_, m)| m)
.unwrap_or_else(|_| Map::new());
let err = match err {
Error::JsonErr(err) => err,
_ => json!({"message": err.to_string(), "name": "InternalErr"}),
};
let update_job_future = || {
add_completed_job_error(
db,
&job,
format!("Unexpected error during job execution:\n{err}"),
err.clone(),
metrics.clone(),
)
};
if job.is_flow_step || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::Flow {
let (flow, job_status_to_update) = if let Some(parent_job_id) = job.parent_job {
let _ = update_job_future().await;
(parent_job_id, job.id)
} else {
(job.id, Uuid::nil())
};
let mut output_map = serde_json::Map::new();
error_to_result(&mut output_map, &err);
let updated_flow = update_flow_status_after_job_completion(
db,
client,
@@ -674,7 +706,7 @@ async fn handle_job_error(
&job_status_to_update,
&job.workspace_id,
false,
serde_json::Value::Object(output_map),
json!({ "error": err }),
metrics.clone(),
unrecoverable,
same_worker_tx,
@@ -684,9 +716,8 @@ async fn handle_job_error(
None,
)
.await;
if let Err(err) = updated_flow {
println!("error updating flow status: {}", err);
if let Err(err) = updated_flow {
if let Some(parent_job_id) = job.parent_job {
if let Ok(mut tx) = db.begin().await {
if let Ok(Some(parent_job)) =
@@ -696,7 +727,7 @@ async fn handle_job_error(
db,
&parent_job,
format!("Unexpected error during flow job error handling:\n{err}"),
err,
json!({"message": err.to_string(), "name": "InternalErr"}),
metrics.clone(),
)
.await;
@@ -705,7 +736,10 @@ async fn handle_job_error(
}
}
}
tracing::error!(job_id = %job.id, err = err.alt(), "error handling job: {} {} {}", job.id, job.workspace_id, job.created_by);
if job.parent_job.is_none() {
let _ = update_job_future().await;
}
tracing::error!(job_id = %job.id, "error handling job: {err:#?} {} {} {}", job.id, job.workspace_id, job.created_by);
}
async fn insert_initial_ping(
@@ -735,8 +769,16 @@ struct Envs {
pip_index_url: Option<String>,
pip_extra_index_url: Option<String>,
pip_trusted_host: Option<String>,
deno_auth_tokens: String,
deno_flags: Option<Vec<String>>,
max_log_size: i64,
pip_local_dependencies: Option<Vec<String>>,
additional_python_paths: Option<Vec<String>>,
}
fn extract_error_value(log_lines: &str) -> serde_json::Value {
return json!({"message": log_lines.to_string().trim().to_string(), "name": "ExecutionErr"});
}
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_queued_job(
job: QueuedJob,
@@ -754,7 +796,7 @@ async fn handle_queued_job(
base_internal_url: &str,
) -> windmill_common::error::Result<()> {
if job.canceled {
return Err(Error::ExecutionErr(canceled_job_to_result(&job)))?;
return Err(Error::JsonErr(canceled_job_to_result(&job)))?;
}
if let Some(e) = job.pre_run_error {
return Err(Error::ExecutionErr(e));
@@ -857,32 +899,36 @@ async fn handle_queued_job(
}
}
Err(e) => {
let error_message = match e {
let error_value = match e {
Error::ExitStatus(_) => {
let last_10_log_lines = logs
.lines()
.skip(logs.lines().count().max(13) - 13)
.join("\n")
.to_string()
.replace("\n\n", "\n");
let res = read_result(job_dir).await.ok();
let log_lines = last_10_log_lines
.split("CODE EXECUTION ---")
.last()
.unwrap_or(&logs);
format!("Error during execution of the script:\n{}", log_lines)
if res.is_some() && res.clone().unwrap().is_object() {
res.unwrap()
} else {
let last_10_log_lines = logs
.lines()
.skip(logs.lines().count().max(13) - 13)
.join("\n")
.to_string()
.replace("\n\n", "\n");
let log_lines = last_10_log_lines
.split("CODE EXECUTION ---")
.last()
.unwrap_or(&logs);
extract_error_value(log_lines)
}
}
err @ _ => {
json!({"message": format!("error during execution of the script:\n{}", err), "name": "ExecutionErr"})
}
err @ _ => format!("error before termination: {err:#?}"),
};
let (_, output_map) = add_completed_job_error(
db,
&job,
logs,
error_message,
Some(metrics.clone()),
)
.await?;
let result =
add_completed_job_error(db, &job, logs, error_value, Some(metrics.clone()))
.await?;
if job.is_flow_step {
if let Some(parent_job) = job.parent_job {
update_flow_status_after_job_completion(
@@ -892,7 +938,7 @@ async fn handle_queued_job(
&job.id,
&job.workspace_id,
false,
serde_json::Value::Object(output_map),
result,
Some(metrics),
false,
same_worker_tx,
@@ -922,6 +968,7 @@ async fn write_file(dir: &str, path: &str, content: &str) -> error::Result<File>
#[async_recursion]
async fn transform_json_value(
name: &str,
client: &windmill_api_client::Client,
workspace: &str,
v: Value,
@@ -932,32 +979,32 @@ async fn transform_json_value(
let v = client
.get_variable(workspace, path, Some(true))
.await
.map_err(to_anyhow)
.map_err(|_| Error::NotFound(format!("Variable {path} not found for `{name}`")))
.map(|v| v.into_inner())?
.value
.map_or_else(
|| Err(Error::NotFound(format!("Variable {path} not found"))),
|e| Ok(e),
)?;
.unwrap_or_else(|| String::new());
Ok(Value::String(v))
}
Value::String(y) if y.starts_with("$res:") => {
let path = y.strip_prefix("$res:").unwrap();
if path.split("/").count() < 2 {
return Err(Error::InternalErr(
format!("invalid resource path: {path}",),
));
return Err(Error::InternalErr(format!(
"Argument `{name}` is an invalid resource path: {path}",
)));
}
let v = client
.get_resource_value(workspace, path)
.await
.map_err(|_| Error::NotFound(format!("Resource {path} not found")))?
.map_err(|_| Error::NotFound(format!("Resource {path} not found for `{name}`")))?
.into_inner();
transform_json_value(client, workspace, v).await
transform_json_value(name, client, workspace, v).await
}
Value::Object(mut m) => {
for (a, b) in m.clone().into_iter() {
m.insert(a, transform_json_value(client, workspace, b).await?);
m.insert(
a.clone(),
transform_json_value(&a, client, workspace, b).await?,
);
}
Ok(Value::Object(m))
}
@@ -1120,7 +1167,7 @@ mount {{
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_go_job(
WorkerConfig { base_internal_url, disable_nuser, disable_nsjail, base_url, .. }: &WorkerConfig,
Envs { nsjail_path, go_path, path_env, home_env, .. }: &Envs,
Envs { nsjail_path, go_path, path_env, home_env, max_log_size, .. }: &Envs,
logs: &mut String,
job: &QueuedJob,
db: &sqlx::Pool<sqlx::Postgres>,
@@ -1158,6 +1205,7 @@ async fn handle_go_job(
job_dir,
db,
timeout,
*max_log_size,
go_path,
true,
skip_go_mod,
@@ -1278,7 +1326,7 @@ func Run(req Req) (interface{{}}, error){{
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(&job.id, db, logs, timeout, build_go).await?;
handle_child(&job.id, db, logs, timeout, *max_log_size, build_go).await?;
Command::new(nsjail_path)
.current_dir(job_dir)
@@ -1304,14 +1352,14 @@ func Run(req Req) (interface{{}}, error){{
.stderr(Stdio::piped())
.spawn()?
};
handle_child(&job.id, db, logs, timeout, child).await?;
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
read_result(job_dir).await
}
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_bash_job(
WorkerConfig { base_internal_url, disable_nuser, disable_nsjail, base_url, .. }: &WorkerConfig,
Envs { nsjail_path, path_env, home_env, .. }: &Envs,
Envs { nsjail_path, path_env, home_env, max_log_size, .. }: &Envs,
logs: &mut String,
job: &QueuedJob,
db: &sqlx::Pool<sqlx::Postgres>,
@@ -1383,7 +1431,7 @@ async fn handle_bash_job(
.stderr(Stdio::piped())
.spawn()?
};
handle_child(&job.id, db, logs, timeout, child).await?;
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
//for now bash jobs have an empty result object
Ok(serde_json::json!(logs
.lines()
@@ -1403,7 +1451,7 @@ fn capitalize(s: &str) -> String {
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_deno_job(
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
Envs { nsjail_path, deno_path, path_env, .. }: &Envs,
Envs { nsjail_path, deno_path, path_env, max_log_size, deno_auth_tokens, deno_flags, .. }: &Envs,
logs: &mut String,
job: &QueuedJob,
db: &sqlx::Pool<sqlx::Postgres>,
@@ -1446,16 +1494,30 @@ async function run() {{
await Deno.writeTextFile("result.json", res_json);
Deno.exit(0);
}}
run();
run().catch(async (e) => {{
await Deno.writeTextFile("result.json", JSON.stringify({{ message: e.message, name: e.name, stack: e.stack }}));
Deno.exit(1);
}});
"#,
);
write_file(job_dir, "main.ts", &wrapper_content).await?;
let w_id = job.workspace_id.clone();
let import_map = format!(
r#"{{
"imports": {{
"/": "{base_internal_url}/api/w/{w_id}/scripts/raw/p/",
"./": "./"
}}
}}"#
);
write_file(job_dir, "import_map.json", &import_map).await?;
let mut reserved_variables = get_reserved_variables(job, &token, &base_url, db).await?;
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
let hostname_base = base_url.split("://").last().unwrap_or("localhost");
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
let deno_auth_tokens = format!("{token}@{hostname_base};{token}@{hostname_internal}");
let deno_auth_tokens =
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens}",);
let child = async {
Ok(if !disable_nsjail {
let _ = write_file(
@@ -1477,9 +1539,16 @@ run();
if lockfile.is_some() {
args.push("--lock=/tmp/lock.json");
}
args.push("--import-map");
args.push("/tmp/import_map.json");
args.push("--unstable");
args.push("--v8-flags=--max-heap-size=2048");
args.push("-A");
if let Some(deno_flags) = deno_flags {
for flag in deno_flags {
args.push(flag);
}
} else {
args.push("-A");
}
args.push("/tmp/main.ts");
Command::new(nsjail_path)
@@ -1496,13 +1565,18 @@ run();
} else {
let mut args = Vec::new();
let script_path = format!("{job_dir}/main.ts");
let import_map_path = format!("{job_dir}/import_map.json");
args.push("run");
if lockfile.is_some() {
args.push("--lock=/tmp/lock.json");
}
args.push("--import-map");
args.push(&import_map_path);
args.push("--unstable");
args.push("--v8-flags=--max-heap-size=2048");
args.push("-A");
if let Some(deno_flags) = deno_flags {
for flag in deno_flags {
args.push(flag);
}
} else {
args.push("-A");
}
args.push(&script_path);
Command::new(deno_path)
.current_dir(job_dir)
@@ -1521,7 +1595,7 @@ run();
}
.instrument(trace_span!("create_deno_jail"))
.await?;
handle_child(&job.id, db, logs, timeout, child).await?;
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
read_result(job_dir).await
}
@@ -1532,7 +1606,7 @@ async fn create_args_and_out_file(
job_dir: &str,
) -> Result<(), Error> {
let args = if let Some(args) = &job.args {
Some(transform_json_value(client, &job.workspace_id, args.clone()).await?)
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
None
};
@@ -1549,7 +1623,9 @@ lazy_static! {
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_python_job(
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
envs @ Envs { nsjail_path, python_path, path_env, .. }: &Envs,
envs @ Envs {
nsjail_path, python_path, path_env, max_log_size, additional_python_paths, ..
}: &Envs,
requirements_o: Option<String>,
job_dir: &str,
worker_dir: &str,
@@ -1565,7 +1641,8 @@ async fn handle_python_job(
) -> error::Result<serde_json::Value> {
create_dependencies_dir(job_dir).await;
let mut additional_python_paths: Vec<String> = vec![];
let mut additional_python_paths: Vec<String> =
additional_python_paths.to_owned().unwrap_or_else(|| vec![]);
let requirements = match requirements_o {
Some(r) => r,
@@ -1672,12 +1749,23 @@ async fn handle_python_job(
} else {
""
};
let spread = if sig.star_kwargs {
"args = kwargs".to_string()
} else {
sig.args
.into_iter()
.map(|x| format!("args[\"{}\"] = kwargs[\"{}\"]", x.name, x.name))
.join("\n")
};
let wrapper_content: String = format!(
r#"
import json
{import_loader}
{import_base64}
{import_datetime}
import traceback
import sys
inner_script = __import__("inner")
@@ -1686,11 +1774,21 @@ with open("args.json") as f:
for k, v in list(kwargs.items()):
if v == '<function call>':
del kwargs[k]
args = {{}}
{spread}
{transforms}
res = inner_script.main(**kwargs)
res_json = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')
with open("result.json", 'w') as f:
f.write(res_json)
try:
res = inner_script.main(**args)
res_json = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')
with open("result.json", 'w') as f:
f.write(res_json)
except Exception as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
tb = traceback.format_tb(exc_traceback)
with open("result.json", 'w') as f:
err_json = json.dumps({{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }}, separators=(',', ':'), default=str).replace('\n', '')
f.write(err_json)
sys.exit(1)
"#,
);
write_file(job_dir, "main.py", &wrapper_content).await?;
@@ -1770,7 +1868,7 @@ mount {{
.spawn()?
};
handle_child(&job.id, db, logs, timeout, child).await?;
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
read_result(job_dir).await
}
@@ -1966,7 +2064,7 @@ async fn generate_deno_lock(
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
timeout: i32,
Envs { deno_path, .. }: &Envs,
Envs { deno_path, max_log_size, .. }: &Envs,
) -> error::Result<String> {
let _ = write_file(job_dir, "main.ts", code).await?;
@@ -1984,7 +2082,7 @@ async fn generate_deno_lock(
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, timeout, child).await?;
handle_child(job_id, db, logs, timeout, *max_log_size, child).await?;
let path_lock = format!("{job_dir}/lock.json");
let mut file = File::open(path_lock).await?;
@@ -2015,6 +2113,7 @@ async fn capture_dependency_job(
job_dir,
db,
timeout,
envs.max_log_size,
&envs.go_path,
false,
false,
@@ -2033,7 +2132,14 @@ async fn pip_compile(
requirements: &str,
logs: &mut String,
job_dir: &str,
Envs { pip_extra_index_url, pip_index_url, pip_trusted_host, .. }: &Envs,
Envs {
pip_extra_index_url,
pip_index_url,
pip_trusted_host,
max_log_size,
pip_local_dependencies,
..
}: &Envs,
db: &Pool<Postgres>,
timeout: i32,
) -> error::Result<String> {
@@ -2041,6 +2147,15 @@ async fn pip_compile(
set_logs(logs, job_id, db).await;
logs.push_str(&format!("\ncontent of requirements:\n{}", requirements));
let file = "requirements.in";
let requirements = if let Some(pip_local_dependencies) = pip_local_dependencies {
let deps = pip_local_dependencies.clone();
requirements
.lines()
.filter(|s| !deps.contains(&s.to_string()))
.join("\n")
} else {
requirements.to_string()
};
write_file(job_dir, file, &requirements).await?;
let mut args = vec!["-q", "--no-header", file, "--resolver=backtracking"];
@@ -2059,7 +2174,7 @@ async fn pip_compile(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, timeout, child)
handle_child(job_id, db, logs, timeout, *max_log_size, child)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
let path_lock = format!("{job_dir}/requirements.txt");
@@ -2081,6 +2196,7 @@ async fn install_go_dependencies(
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
timeout: i32,
max_log_size: i64,
go_path: &str,
preview: bool,
skip_go_mod: bool,
@@ -2094,7 +2210,7 @@ async fn install_go_dependencies(
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, timeout, child).await?;
handle_child(job_id, db, logs, timeout, max_log_size, child).await?;
}
let child = Command::new(go_path)
.current_dir(job_dir)
@@ -2102,7 +2218,7 @@ async fn install_go_dependencies(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, timeout, child)
handle_child(job_id, db, logs, timeout, max_log_size, child)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
@@ -2184,6 +2300,7 @@ async fn handle_child(
db: &Pool<Postgres>,
logs: &mut String,
timeout: i32,
max_log_size: i64,
mut child: Child,
) -> error::Result<()> {
let timeout = Duration::from_secs(u64::try_from(timeout).expect("invalid timeout"));
@@ -2275,7 +2392,7 @@ async fn handle_child(
/* a future that reads output from the child and appends to the database */
let lines = async move {
/* log_remaining is zero when output limit was reached */
let mut log_remaining = (MAX_LOG_SIZE as usize).saturating_sub(logs.chars().count());
let mut log_remaining = (max_log_size as usize).saturating_sub(logs.chars().count());
let mut result = io::Result::Ok(());
let mut output = output;
/* `do_write` resolves the task, but does not contain the Result.
@@ -2306,7 +2423,7 @@ async fn handle_child(
tracing::info!(%job_id, "Too many logs lines for job {job_id}");
let _ = set_too_many_logs.send(true);
joined.push_str(&format!(
"Job logs or result reached character limit of {MAX_LOG_SIZE}; killing job."
"Job logs or result reached character limit of {max_log_size}; killing job."
));
/* stop reading and drop our streams fairly quickly */
break;
@@ -2390,18 +2507,21 @@ async fn handle_child(
kill_tx.send(()).expect("send should always work");
match wait_result {
_ if *too_many_logs.borrow() => Err(Error::ExecutionErr(
"logs or result reached limit".to_string(),
)),
_ if *too_many_logs.borrow() => Err(Error::ExecutionErr(format!(
"logs or result reached limit. Set MAX_LOG_SIZE higher (current: {max_log_size})"
))),
Ok(Ok(status)) => {
if status.success() {
Ok(())
} else if let Some(code) = status.code() {
Err(error::Error::ExitStatus(code))
} else {
Err(error::Error::ExecutionErr(
"process terminated by signal".to_string(),
))
Err(error::Error::ExecutionErr(format!(
"process terminated by signal: {:#?}, stopped_signal: {:#?}, core_dumped: {}",
status.signal(),
status.stopped_signal(),
status.core_dumped()
)))
}
}
Ok(Err(kill_reason)) => Err(Error::ExecutionErr(format!(
@@ -2568,7 +2688,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, timeout: i32, base_url: &str) {
&job.permissioned_as,
"ephemeral-zombie-jobs",
timeout * 2,
&job.created_by,
&job.email,
)
.await
.expect("could not create job token");
@@ -2601,6 +2721,7 @@ async fn handle_python_reqs(
pip_extra_index_url,
pip_trusted_host,
nsjail_path,
max_log_size,
..
}: &Envs,
job: &QueuedJob,
@@ -2695,7 +2816,7 @@ async fn handle_python_reqs(
.spawn()?
};
let child = handle_child(&job.id, db, logs, timeout, child).await;
let child = handle_child(&job.id, db, logs, timeout, *max_log_size, child).await;
tracing::info!(
worker_name = %worker_name,
job_id = %job.id,

View File

@@ -303,7 +303,6 @@ pub async fn update_flow_status_after_job_completion(
.fetch_one(&mut tx)
.await?;
tracing::info!("setting failure module for flow XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX {parent_module:?}");
sqlx::query!(
"
UPDATE queue
@@ -403,7 +402,7 @@ pub async fn update_flow_status_after_job_completion(
{
true
}
false if has_failure_module(flow, &mut tx).await? => true,
false if has_failure_module(flow, &mut tx).await? && !is_failure_step => true,
false => false,
};
@@ -428,15 +427,17 @@ pub async fn update_flow_status_after_job_completion(
"Flow job canceled".to_string()
} else if stop_early {
format!("Flow job stopped early because of a stop early predicate returning true")
} else if success {
"Flow job completed with success".to_string()
} else {
"Flow job completed".to_string()
"Flow job completed with error".to_string()
};
if flow_job.canceled {
add_completed_job_error(
db,
&flow_job,
logs,
&canceled_job_to_result(&flow_job),
canceled_job_to_result(&flow_job),
metrics.clone(),
)
.await?;
@@ -469,7 +470,7 @@ pub async fn update_flow_status_after_job_completion(
db,
&flow_job,
"Unexpected error during flow chaining:\n".to_string(),
err,
json!({"message": err.to_string(), "name": "InternalError"}),
metrics.clone(),
)
.await;
@@ -687,13 +688,20 @@ async fn transform_input(
}
}
let lresult = last_result.clone();
let error = if let Some(error) = lresult.as_object() {
error.get("error")
} else {
None
};
for (key, val) in input_transforms.into_iter() {
match val {
InputTransform::Static { value: _ } => (),
InputTransform::Javascript { expr } => {
let flow_input = flow_args.clone().unwrap_or_else(|| json!({}));
let previous_result = last_result.clone();
let context = vec![
let mut context = vec![
("params".to_string(), json!(mapped)),
("previous_result".to_string(), previous_result),
("flow_input".to_string(), flow_input),
@@ -705,6 +713,10 @@ async fn transform_input(
("approvers".to_string(), json!(approvers.clone())),
];
if error.is_some() {
context.push(("error".to_string(), error.unwrap().clone()));
}
let v = eval_timeout(
expr.to_string(),
context,
@@ -976,7 +988,7 @@ async fn push_next_flow_job(
let success = false;
let skipped = false;
let logs = "Timed out waiting to be resumed".to_string();
let result = json!({ "error": logs });
let result = json!({ "error": {"message": logs, "name": "SuspendedTimeout"}});
let _uuid =
add_completed_job(db, &flow_job, success, skipped, result, logs).await?;
@@ -1871,7 +1883,7 @@ async fn get_transform_context(
&flow_job.permissioned_as,
"transform-input",
10,
&flow_job.created_by,
&flow_job.email,
)
.await?;
//we need to commit asap otherwise the token won't be valid for auth to check outside of this transaction

View File

@@ -12,7 +12,7 @@ import hub from "./hub.ts";
import { tryResolveVersion } from "./context.ts";
import { GlobalOptions } from "./types.ts";
const VERSION = "v1.60.0";
const VERSION = "v1.61.1";
const command = new Command()
.name("wmill")

View File

@@ -7,6 +7,12 @@ export {
UserService, WorkspaceService
} from './windmill-api/index.ts'
// @ts-ignore: Otherwise BigInt is not supported for export
BigInt.prototype.toJSON = function () {
return this.toString();
};
export { pgSql, pgClient } from './pg.ts'
export { mySql, mysqlClient } from './mysql.ts'

1
depot.json Normal file
View File

@@ -0,0 +1 @@
{ "id": "00zvxx9420" }

View File

@@ -3,6 +3,9 @@ FROM ghcr.io/windmill-labs/windmill-ee
RUN /usr/local/bin/python3 -m pip install nltk
RUN mkdir -p /nsjail_data/python && HOME=/nsjail_data/python /usr/local/bin/python3 -m nltk.downloader vader_lexicon
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - &&\
apt-get install -y nodejs
RUN apt-get update \
&& apt-get install -y tesseract-ocr \
&& rm -rf /var/lib/apt/lists/*

140
docker/DockerfileOpenbb Normal file
View File

@@ -0,0 +1,140 @@
FROM debian:buster-slim as nsjail
WORKDIR /nsjail
ARG nsjail=""
RUN if [ "$nsjail" = "true" ]; then apt-get -y update \
&& apt-get install -y \
bison=2:3.3.* \
flex=2.6.* \
g++=4:8.3.* \
gcc=4:8.3.* \
git=1:2.20.* \
libprotobuf-dev=3.6.* \
libnl-route-3-dev=3.4.* \
make=4.2.* \
pkg-config=0.29-6 \
protobuf-compiler=3.6.*; fi
RUN if [ "$nsjail" = "true" ]; then git clone -b master --single-branch https://github.com/google/nsjail.git . \
&& git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800; fi
RUN if [ "$nsjail" = "true" ]; then make; else touch nsjail; fi
FROM rust:slim-buster AS rust_base
RUN apt-get update && apt-get install -y git libssl-dev pkg-config npm
RUN apt-get -y update \
&& apt-get install -y \
curl lld nodejs npm
RUN rustup component add rustfmt
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef
WORKDIR /windmill
ENV SQLX_OFFLINE=true
ENV CARGO_INCREMENTAL=1
FROM node:19-alpine as frontend
# install dependencies
WORKDIR /frontend
COPY ./frontend/package.json ./frontend/package-lock.json ./
RUN npm ci
# Copy all local files into the image.
COPY frontend .
RUN mkdir /backend
COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml
COPY /openflow.openapi.yaml /openflow.openapi.yaml
COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh
RUN cd /backend/windmill-api && . ./build_openapi.sh
RUN npm run generate-backend-client
ENV NODE_OPTIONS "--max-old-space-size=8192"
RUN npm run build
RUN npm run check
FROM rust_base AS planner
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
COPY ./backend ./
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef prepare --recipe-path recipe.json
FROM rust_base AS builder
ARG features=""
COPY --from=planner /windmill/recipe.json recipe.json
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef cook --release --features "$features" --recipe-path recipe.json
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
COPY ./backend ./
COPY --from=frontend /frontend /frontend
COPY --from=frontend /backend/windmill-api/openapi-deref.yaml ./windmill-api/openapi-deref.yaml
COPY .git/ .git/
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"
FROM python:3.10.8-slim-buster
ARG APP=/usr/src/app
RUN apt-get update \
&& apt-get install -y ca-certificates wget curl git jq libprotobuf-dev libnl-route-3-dev unzip \
&& apt-get install -y ca-certificates wget curl git jq libprotobuf-dev libnl-route-3-dev unzip build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
curl -o rclone.zip "https://downloads.rclone.org/v1.60.1/rclone-v1.60.1-linux-$arch.zip"; \
unzip -p rclone.zip rclone-v1.60.1-linux-$arch/rclone > /usr/bin/rclone; rm rclone.zip; \
chown root:root /usr/bin/rclone; chmod 755 /usr/bin/rclone
RUN set -eux; \
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
url=; \
case "$arch" in \
'amd64') \
targz='go1.19.3.linux-amd64.tar.gz'; \
;; \
'arm64') \
targz='go1.19.3.linux-arm64.tar.gz'; \
;; \
'armhf') \
targz='go1.19.3.linux-armv6l.tar.gz'; \
;; \
*) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \
esac; \
wget "https://golang.org/dl/$targz" -nv && tar -C /usr/local -xzf "$targz" && rm "$targz";
ENV PATH="${PATH}:/usr/local/go/bin"
ENV GO_PATH=/usr/local/go/bin/go
ENV TZ=Etc/UTC
RUN /usr/local/bin/python3 -m pip install pip-tools
COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
COPY --from=nsjail /nsjail/nsjail /bin/nsjail
COPY --from=denoland/deno:latest /usr/bin/deno /usr/bin/deno
RUN /usr/local/bin/python3 -m pip install openbb[all]
RUN mkdir -p ${APP}
WORKDIR ${APP}
EXPOSE 8000
CMD ["./windmill"]

View File

@@ -1,20 +1,20 @@
{
"name": "windmill",
"version": "1.60.0",
"version": "1.61.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill",
"version": "1.60.0",
"version": "1.61.1",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@leeoniya/ufuzzy": "^0.9.0",
"@leeoniya/ufuzzy": "^0.9.1",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.2",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",
@@ -24,6 +24,7 @@
"monaco-yaml": "^4.0.2",
"svelte-autosize": "^1.0.1",
"svelte-chartjs": "^3.1.0",
"svelte-portal": "^2.2.0",
"svelte-select": "^5.0.2",
"vscode-ws-jsonrpc": "^2.0.1"
},
@@ -31,12 +32,12 @@
"@playwright/test": "^1.29.2",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0-next.589",
"@sveltejs/package": "^1.0.1",
"@sveltejs/package": "^1.0.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@types/node": "^18.11.18",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
@@ -51,7 +52,7 @@
"path-browserify": "^1.0.1",
"postcss": "^8.4.18",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
@@ -70,7 +71,7 @@
"tailwindcss": "^3.2.2",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.0.3"
"vite": "^4.0.4"
},
"peerDependencies": {
"svelte": "^3.53.1"
@@ -708,9 +709,9 @@
"dev": true
},
"node_modules/@leeoniya/ufuzzy": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz",
"integrity": "sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg=="
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -827,15 +828,15 @@
}
},
"node_modules/@sveltejs/package": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.1.tgz",
"integrity": "sha512-iYoDz4AEJQUfdKUfBcwtYEGYkf4NMByQL3Sl2ESnu+IXsLNsHvhH0zUDhAmUmAgcrH8fVjiR7FuJeyh+7EQtiw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.2.tgz",
"integrity": "sha512-VY9U+05d9uNFDj7ScKRlHORYlfPSHwJewBjV+V2RsnViexpLFPUrboC9SiPYDCpLnbeqwXerxhO6twGHUBGeIA==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.3",
"kleur": "^4.1.5",
"sade": "^1.8.1",
"svelte2tsx": "~0.5.20"
"svelte2tsx": "~0.6.0"
},
"bin": {
"svelte-package": "svelte-package.js"
@@ -847,6 +848,20 @@
"svelte": "^3.44.0"
}
},
"node_modules/@sveltejs/package/node_modules/svelte2tsx": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
"dev": true,
"dependencies": {
"dedent-js": "^1.0.1",
"pascal-case": "^3.1.1"
},
"peerDependencies": {
"svelte": "^3.55",
"typescript": "^4.9.4"
}
},
"node_modules/@sveltejs/vite-plugin-svelte": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.2.tgz",
@@ -896,11 +911,11 @@
}
},
"node_modules/@tanstack/svelte-table": {
"version": "8.7.2",
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.2.tgz",
"integrity": "sha512-uAZQQXWnwuyT5H7uCyxUYlJIm+6jv34ibzLbuwwGAzvnJa6QZcBsQILAgO6hJYBHIt8PRA86uC5/w3sBpokH8g==",
"version": "8.7.6",
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.6.tgz",
"integrity": "sha512-Nm4rgww6FnRwyvHZ16dzGQPMJOs6Z0DYmixHG6yfa7stAOtEXLRxREmdqiPxTlBTSDVaCcgJeYEyfwvVl7BH8w==",
"dependencies": {
"@tanstack/table-core": "8.7.2"
"@tanstack/table-core": "8.7.6"
},
"engines": {
"node": ">=12"
@@ -914,9 +929,9 @@
}
},
"node_modules/@tanstack/table-core": {
"version": "8.7.2",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.2.tgz",
"integrity": "sha512-Y+3AyuyFvjFlNdxbQ8Mm7muX5yUN31VSABDPyEpNsBIKyJre+AvqcsEdLoPGGAGtHsl+creH3xDOdn2ZPl19Cg==",
"version": "8.7.6",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.6.tgz",
"integrity": "sha512-sqiNTMzB6cpyL8DFH6/VqW48SwiflLqxQqYpo2wNock7rdVGvlm0BLNI8vZUJbr1+fmmWmHwBvi5OMgZw8n1DA==",
"engines": {
"node": ">=12"
},
@@ -1000,14 +1015,14 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz",
"integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/type-utils": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -1134,13 +1149,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz",
"integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1"
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1151,13 +1166,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz",
"integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@@ -1178,9 +1193,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz",
"integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1191,13 +1206,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz",
"integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1218,16 +1233,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz",
"integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@@ -1244,12 +1259,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz",
"integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@@ -1867,11 +1882,12 @@
"peer": true
},
"node_modules/chartjs-adapter-date-fns": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz",
"integrity": "sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
"peerDependencies": {
"chart.js": "^3.0.0"
"chart.js": ">=2.8.0",
"date-fns": ">=2.0.0"
}
},
"node_modules/chartjs-plugin-zoom": {
@@ -5158,9 +5174,9 @@
}
},
"node_modules/prettier": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"bin": {
"prettier": "bin-prettier.js"
},
@@ -6256,6 +6272,11 @@
"svelte": ">=3"
}
},
"node_modules/svelte-portal": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.0.tgz",
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
},
"node_modules/svelte-preprocess": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
@@ -6780,9 +6801,9 @@
}
},
"node_modules/vite": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
"dev": true,
"dependencies": {
"esbuild": "^0.16.3",
@@ -7531,9 +7552,9 @@
"dev": true
},
"@leeoniya/ufuzzy": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz",
"integrity": "sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg=="
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -7618,15 +7639,27 @@
}
},
"@sveltejs/package": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.1.tgz",
"integrity": "sha512-iYoDz4AEJQUfdKUfBcwtYEGYkf4NMByQL3Sl2ESnu+IXsLNsHvhH0zUDhAmUmAgcrH8fVjiR7FuJeyh+7EQtiw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.2.tgz",
"integrity": "sha512-VY9U+05d9uNFDj7ScKRlHORYlfPSHwJewBjV+V2RsnViexpLFPUrboC9SiPYDCpLnbeqwXerxhO6twGHUBGeIA==",
"dev": true,
"requires": {
"chokidar": "^3.5.3",
"kleur": "^4.1.5",
"sade": "^1.8.1",
"svelte2tsx": "~0.5.20"
"svelte2tsx": "~0.6.0"
},
"dependencies": {
"svelte2tsx": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
"dev": true,
"requires": {
"dedent-js": "^1.0.1",
"pascal-case": "^3.1.1"
}
}
}
},
"@sveltejs/vite-plugin-svelte": {
@@ -7665,17 +7698,17 @@
}
},
"@tanstack/svelte-table": {
"version": "8.7.2",
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.2.tgz",
"integrity": "sha512-uAZQQXWnwuyT5H7uCyxUYlJIm+6jv34ibzLbuwwGAzvnJa6QZcBsQILAgO6hJYBHIt8PRA86uC5/w3sBpokH8g==",
"version": "8.7.6",
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.6.tgz",
"integrity": "sha512-Nm4rgww6FnRwyvHZ16dzGQPMJOs6Z0DYmixHG6yfa7stAOtEXLRxREmdqiPxTlBTSDVaCcgJeYEyfwvVl7BH8w==",
"requires": {
"@tanstack/table-core": "8.7.2"
"@tanstack/table-core": "8.7.6"
}
},
"@tanstack/table-core": {
"version": "8.7.2",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.2.tgz",
"integrity": "sha512-Y+3AyuyFvjFlNdxbQ8Mm7muX5yUN31VSABDPyEpNsBIKyJre+AvqcsEdLoPGGAGtHsl+creH3xDOdn2ZPl19Cg=="
"version": "8.7.6",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.6.tgz",
"integrity": "sha512-sqiNTMzB6cpyL8DFH6/VqW48SwiflLqxQqYpo2wNock7rdVGvlm0BLNI8vZUJbr1+fmmWmHwBvi5OMgZw8n1DA=="
},
"@trysound/sax": {
"version": "0.2.0",
@@ -7749,14 +7782,14 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz",
"integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/type-utils": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -7821,41 +7854,41 @@
}
},
"@typescript-eslint/scope-manager": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz",
"integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1"
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0"
}
},
"@typescript-eslint/type-utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz",
"integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz",
"integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz",
"integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -7864,28 +7897,28 @@
}
},
"@typescript-eslint/utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz",
"integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz",
"integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"eslint-visitor-keys": "^3.3.0"
}
},
@@ -8308,9 +8341,9 @@
"peer": true
},
"chartjs-adapter-date-fns": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz",
"integrity": "sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
"requires": {}
},
"chartjs-plugin-zoom": {
@@ -10715,9 +10748,9 @@
"dev": true
},
"prettier": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg=="
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw=="
},
"prettier-plugin-svelte": {
"version": "2.9.0",
@@ -11548,6 +11581,11 @@
"dev": true,
"requires": {}
},
"svelte-portal": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.0.tgz",
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
},
"svelte-preprocess": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
@@ -11899,9 +11937,9 @@
"optional": true
},
"vite": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
"dev": true,
"requires": {
"esbuild": "^0.16.3",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.60.0",
"version": "1.61.1",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -18,12 +18,12 @@
"@playwright/test": "^1.29.2",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0-next.589",
"@sveltejs/package": "^1.0.1",
"@sveltejs/package": "^1.0.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@types/vscode": "~1.74.0",
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
@@ -38,7 +38,7 @@
"path-browserify": "^1.0.1",
"postcss": "^8.4.18",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
@@ -57,17 +57,17 @@
"tailwindcss": "^3.2.2",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.0.3"
"vite": "^4.0.4"
},
"type": "module",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@leeoniya/ufuzzy": "^0.9.0",
"@leeoniya/ufuzzy": "^0.9.1",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.2",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",
@@ -77,6 +77,7 @@
"monaco-yaml": "^4.0.2",
"svelte-autosize": "^1.0.1",
"svelte-chartjs": "^3.1.0",
"svelte-portal": "^2.2.0",
"svelte-select": "^5.0.2",
"vscode-ws-jsonrpc": "^2.0.1"
},

View File

@@ -19,7 +19,7 @@ export interface SchemaProperty {
}
export type Schema = {
$schema: string
$schema: string | undefined
type: string
properties: { [name: string]: SchemaProperty }
required: string[]

View File

@@ -4,6 +4,7 @@
import { emptySchema, emptyString } from '$lib/utils'
import SchemaForm from './SchemaForm.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import Toggle from './Toggle.svelte'
export let resource_type: string
export let args: Record<string, any> | any = {}
@@ -12,7 +13,10 @@
let schema = emptySchema()
let notFound = false
async function loadSchema() {
rawCode = '{}'
viewJsonSchema = false
try {
const rt = await ResourceService.getResourceType({
workspace: $workspaceStore!,
@@ -21,8 +25,6 @@
schema = rt.schema
notFound = false
} catch (e) {
rawCode = '{}'
notFound = true
}
}
@@ -43,17 +45,40 @@
}
let error = ''
let rawCode = ''
let viewJsonSchema = false
$: rawCode && parseJson()
function switchTab(asJson: boolean) {
viewJsonSchema = asJson
if (asJson) {
rawCode = JSON.stringify(args, null, 2)
} else {
parseJson()
}
}
</script>
{#if notFound}
{#if !notFound}
<div class="w-full flex flex-row-reverse">
<Toggle
on:change={(e) => switchTab(e.detail)}
options={{
right: 'As JSON'
}}
/>
</div>
{:else}
<p class="italic text-gray-500 text-xs mb-4"
>No corresponding resource type found in your workspace for {resource_type}. Define the value in
JSON directly</p
>
{/if}
{#if notFound || viewJsonSchema}
{#if !emptyString(error)}<span class="text-red-400 text-xs mb-1 flex flex-row-reverse"
>{error}</span
>{:else}<div class="py-2" />{/if}
<SimpleEditor autoHeight lang="json" bind:code={rawCode} fixedOverflowWidgets={false} />
{:else}
<SchemaForm {password} isValid {schema} bind:args />
<SchemaForm noDelete {password} isValid {schema} bind:args />
{/if}

View File

@@ -1,35 +1,48 @@
<script lang="ts" context="module">
const apiTokenApps: Record<string, { img?: string; instructions: string[]; key?: string }> = {
airtable: {
img: 'airtable_connect.png',
img: '/airtable_connect.png',
instructions: ['Click on the top-right avatar', 'Click on Account', 'Find "Api"']
},
discord_webhook: {
img: 'discord_webhook.png',
img: '/discord_webhook.png',
instructions: ['Click on Server Settings', 'Click on Integration', 'Find "Webhooks"'],
key: 'webhook_url'
},
toggl: {
img: 'toggl_connect.png',
img: '/toggl_connect.png',
instructions: [
'Go to <a href="https://track.toggl.com/profile" target="_blank" rel=”noopener noreferrer”>https://track.toggl.com/profile</a>',
'Find "API Token"'
]
},
mailchimp: {
img: 'mailchimp_connect.png',
img: '/mailchimp_connect.png',
instructions: [
'Go to <a href="https://admin.mailchimp.com/account/api" target="_blank" rel=”noopener noreferrer”>https://admin.mailchimp.com/account/api</a>',
'Find "Your API Keys"'
]
},
sendgrid: {
img: 'sendgrid_connect.png',
img: '/sendgrid_connect.png',
instructions: [
'Go to <a href="https://app.sendgrid.com/settings/api_keys" target="_blank" rel=”noopener noreferrer”>https://app.sendgrid.com/settings/api_keys</a>',
'Create an API key',
'Copy your key'
]
},
supabase: {
img: '/supabase_connect.png',
instructions: ['Go to the API Settings of your app to find the project URL and key']
},
square: {
img: '/square_connect.gif',
instructions: [
'Go to <a href="https://developer.squareup.com/apps" target="_blank" rel=”noopener noreferrer”>https://developer.squareup.com/apps</a>',
'In the left pane, choose Credentials',
'At the top of the page, choose Production mode for a production access token or Sandbox mode for a Sandbox access token.'
]
}
}
</script>
@@ -126,6 +139,7 @@
const availableRts = await ResourceService.listResourceTypeNames({
workspace: $workspaceStore!
})
connectsManual = availableRts
.filter((x) => !Object.keys(connects ?? {}).includes(x))
.map((x) => [
@@ -141,6 +155,7 @@
async function next() {
if (step == 1 && manual) {
step += 1
args = {}
} else if (step == 1 && !manual) {
const url = new URL(`/api/oauth/connect/${resource_type}`, $page.url.origin)
url.searchParams.append('scopes', scopes.join('+'))
@@ -383,9 +398,6 @@
namePlaceholder="my_{resource_type}"
kind="resource"
/>
<h2 class="mt-4 mb-2">Description</h2>
<textarea type="text" autocomplete="off" use:autosize bind:value={description} />
{#if apiTokenApps[resource_type]}
<h2 class="mt-4 mb-2">Instructions</h2>
@@ -399,16 +411,21 @@
</ol>
</div>
{#if apiTokenApps[resource_type].img}
<div class="mt-4 w-full">
<img class="max-h-96 m-auto" alt="connect" src={apiTokenApps[resource_type].img} />
<div class="mt-4 w-full overflow-hidden">
<img class="m-auto max-h-60" alt="connect" src={apiTokenApps[resource_type].img} />
</div>
{/if}
{/if}
<h2 class="mt-8">Value</h2>
<h2 class="mt-4">Value</h2>
<div class="mt-4">
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
{#key resource_type}
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
{/key}
</div>
<h2 class="mt-4 mb-2">Description</h2>
<textarea type="text" autocomplete="off" use:autosize bind:value={description} />
{:else}
<Path
initialPath=""

View File

@@ -157,7 +157,6 @@
}
}
export let inputCat: InputCat = 'string'
$: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)
</script>

View File

@@ -7,11 +7,13 @@
import autosize from 'svelte-autosize'
export let result: any
export let requireHtmlApproval = false
let resultKind:
| 'json'
| 'table-col'
| 'table-row'
| 'html'
| 'png'
| 'file'
| 'jpeg'
@@ -19,9 +21,11 @@
| 'error'
| 'approval'
| undefined
$: resultKind = inferResultKind(result)
let forceJson = false
let enableHtml = false
function isRectangularArray(obj: any) {
if (!Array.isArray(obj) || obj.length == 0) {
@@ -53,13 +57,15 @@
return 'table-row'
} else if (keys.map((k) => Array.isArray(result[k])).reduce((a, b) => a && b)) {
return 'table-col'
} else if (keys.length == 1 && keys[0] == 'html') {
return 'html'
} else if (keys.length == 1 && keys[0] == 'png') {
return 'png'
} else if (keys.length == 1 && keys[0] == 'jpeg') {
return 'jpeg'
} else if (keys.length == 1 && keys[0] == 'file') {
return 'file'
} else if (keys.length == 1 && keys[0] == 'error' && typeof result['error'] == 'string') {
} else if (keys.length == 1 && keys[0] == 'error') {
return 'error'
} else if (
keys.length == 3 &&
@@ -96,7 +102,7 @@
</div>
{#if Array.isArray(result[col])}
{#each result[col] as item}
<div class="px-12 text-left whitespace-nowrap">
<div class="px-12 text-left text-xs whitespace-nowrap">
{typeof item === 'string' ? item : JSON.stringify(item)}
</div>
{/each}
@@ -105,20 +111,48 @@
{/each}
</div>
{:else if !forceJson && resultKind == 'table-row'}<div
class="grid grid-flow-col-dense border border-gray-200 rounded-md "
class="grid grid-flow-col-dense border border-gray-200 "
>
<TableCustom>
<tbody slot="body">
{#each asListOfList(result) as row}
<tr>
{#each row as v}
<td>{truncate(JSON.stringify(v), 200) ?? ''}</td>
<td class="!text-xs">{truncate(JSON.stringify(v), 200) ?? ''}</td>
{/each}
</tr>
{/each}
</tbody>
</TableCustom>
</div>
{:else if !forceJson && resultKind == 'html'}
<div class="h-full">
{#if !requireHtmlApproval || enableHtml}
{@html result.html}
{:else}
<div class="font-main text-sm">
<div class="flex flex-col">
<div class="bg-red-400 py-1 rounded-t text-white font-bold text-center">
Warning
</div>
<p
class="text-gray-600 mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
>Rendering HTML can expose you to <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noreferrer" class="hover:underline">XSS attacks</a>.
Only enable it if you trust the author of the script.
</p>
</div>
<div class="center-center">
<Button
size="sm"
color="dark"
on:click={() => enableHtml = true}
>
Enable HTML rendering
</Button>
</div>
</div>
{/if}
</div>
{:else if !forceJson && resultKind == 'png'}
<div class="h-full"
><img alt="png rendered" class="w-auto h-full" src="data:image/png;base64,{result.png}" />
@@ -141,8 +175,11 @@
>Download</a
>
</div>
{:else if !forceJson && resultKind == 'error'}<div
><pre class="text-sm text-red-500 whitespace-pre-wrap">{result.error}</pre>
{:else if !forceJson && resultKind == 'error'}<div>
<span class="text-red-500 font-semibold text-sm whitespace-pre-wrap"
>{result.error.name}: {result.error.message}</span
>
<pre class="text-sm whitespace-pre-wrap text-gray-900">{result.error.stack ?? ''}</pre>
</div>
{:else if !forceJson && resultKind == 'approval'}<div class="flex flex-col gap-1 mx-4">
<Button

View File

@@ -411,7 +411,8 @@
model.updateOptions(updateOptions)
editor = meditor.create(divEl as HTMLDivElement, {
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets)
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets),
tabSize: lang == 'python' ? 4 : 2
})
let timeoutModel: NodeJS.Timeout | undefined = undefined

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import DisplayResult from './DisplayResult.svelte'
import LogViewer from './LogViewer.svelte'
@@ -6,6 +7,7 @@
export let logs: string
export let col: boolean = false
export let noBorder = false
export let loading
</script>
<div
@@ -14,7 +16,13 @@
>
<div class="bg-white max-h-80 h-full p-1 overflow-auto relative">
<span class="text-gray-500">Result</span>
<DisplayResult {result} />
{#if result}
<DisplayResult {result} />
{:else if loading}
<Loader2 class="animate-spin" />
{:else}
<div class="text-gray-400">No result (result is undefined)</div>
{/if}
</div>
<div class="overflow-auto max-h-80 h-full relative">
<LogViewer content={logs ?? ''} isLoading={false} />

View File

@@ -12,6 +12,19 @@
faBarsStaggered
} from '@fortawesome/free-solid-svg-icons'
import ScheduleEditor from './ScheduleEditor.svelte'
import { onDestroy, onMount } from 'svelte'
let time = Date.now()
let interval
onMount(() => {
interval = setInterval(() => {
time = Date.now()
}, 1000)
})
onDestroy(() => {
interval && clearInterval(interval)
})
export let job: Job
const SMALL_ICON_SCALE = 0.7
@@ -23,14 +36,19 @@
<div class="rounded-md p-3 bg-gray-50 shadow-sm sm:text-sm md:text-base" style="min-height: 150px;">
<JobStatus {job} />
<div>
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span class="mx-2">
Created {displayDaysAgo(job.created_at ?? '')}</span
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span
class="mx-2 text-2xs text-gray-600"
>
{#key time}
Received job {displayDaysAgo(job.created_at ?? '')}
{/key}</span
>
</div>
{#if job && 'started_at' in job && job.started_at}
<div>
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span class="mx-2">
Started {displayDaysAgo(job.started_at ?? '')}</span
{#key time}
Started {displayDaysAgo(job.started_at ?? '')}{/key}</span
>
</div>
{/if}

View File

@@ -13,6 +13,7 @@
import FlowProgressBar from './flows/FlowProgressBar.svelte'
import { flowStateStore } from './flows/flowState'
import CapturePayload from './flows/content/CapturePayload.svelte'
import { Loader2 } from 'lucide-svelte'
let capturePayload: CapturePayload
export let previewMode: 'upTo' | 'whole'
@@ -119,8 +120,9 @@
jobId = undefined
}}
size="md"
btnClasses="w-full"
btnClasses="w-full max-w-lg"
>
<Loader2 class="animate-spin mr-2" />
Cancel
</Button>
{:else}
@@ -129,7 +131,7 @@
startIcon={{ icon: isRunning ? faRefresh : faPlay }}
color="blue"
size="sm"
btnClasses="w-full"
btnClasses="w-full max-w-lg"
disabled={!isValid}
on:click={() => runPreview($previewArgs)}
>
@@ -147,15 +149,18 @@
</div>
<FlowProgressBar {job} bind:reset={jobProgressReset} />
<div class="overflow-y-auto grow flex-col flex divide-y divide-gray-600 ">
<SchemaForm
compact
class="py-4"
schema={$flowStore.schema}
bind:isValid
bind:args={$previewArgs}
/>
<div class="h-full pt-4 grow">
<div class="overflow-y-auto grow divide-y divide-gray-600 ">
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
<SchemaForm
noVariablePicker
compact
class="py-4 max-w-3xl"
schema={$flowStore.schema}
bind:isValid
bind:args={$previewArgs}
/>
</div>
<div class="pt-4 grow">
{#if jobId}
<FlowStatusViewer bind:flowState={$flowStateStore} {jobId} bind:job />
{:else}

View File

@@ -8,15 +8,16 @@
import { createEventDispatcher } from 'svelte'
import { onDestroy } from 'svelte'
import type { FlowState } from './flows/flowState'
import { Button, Tab } from './common'
import { Badge, Button, Tab } from './common'
import DisplayResult from './DisplayResult.svelte'
import Tabs from './common/tabs/Tabs.svelte'
import { FlowGraph, type GraphModuleState } from './graph'
import ModuleStatus from './ModuleStatus.svelte'
import { displayDate, isOwner, pluralize, truncateRev } from '$lib/utils'
import { displayDate, emptyString, isOwner, pluralize, truncateRev } from '$lib/utils'
import JobArgs from './JobArgs.svelte'
import Tooltip from './Tooltip.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import { Loader2 } from 'lucide-svelte'
const dispatch = createEventDispatcher()
@@ -179,7 +180,11 @@
<FlowPreviewStatus {job} />
{#if `result` in job}
<div class="w-full h-full">
<FlowJobResult result={job.result} logs={job.logs ?? ''} />
<FlowJobResult
loading={job['running'] == true}
result={job.result}
logs={job.logs ?? ''}
/>
</div>
{:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS}
<div class="w-full h-full mt-2 text-sm text-gray-600">
@@ -221,6 +226,31 @@
<div class="text-xs p-4 bg-gray-50 overflow-auto max-h-80 border">
<pre class="w-full">{job.logs}</pre>
</div>
{:else if innerModules?.length > 0}
<div class="flex flex-col gap-1">
{#each innerModules as mod, i (mod.id)}
{#if mod.type == FlowStatusModule.type.IN_PROGRESS}
{@const rawMod = job.raw_flow?.modules[i]}
<div
><span class="inline-flex gap-1"
><Badge color="indigo">{mod.id}</Badge>
<span class="font-medium text-gray-900">
{#if !emptyString(rawMod?.summary)}
{rawMod?.summary ?? ''}
{:else if rawMod?.value.type == 'script'}
{rawMod.value.path ?? ''}
{:else if rawMod?.value.type}
{rawMod?.value.type}
{/if}
</span>
<Loader2 class="animate-spin" /></span
></div
>
{/if}
{/each}
</div>
{/if}
</div>
{/if}
@@ -425,7 +455,13 @@
{#if selectedNode}
{@const node = localFlowModuleStates[selectedNode]}
{#if selectedNode == 'end'}
<FlowJobResult noBorder col result={job['result'] ?? {}} logs={job.logs ?? ''} />
<FlowJobResult
loading={job['running']}
noBorder
col
result={job['result']}
logs={job.logs ?? ''}
/>
{:else if selectedNode == 'start'}
{#if job.args}
<div class="p-2">
@@ -452,7 +488,13 @@
</div>
{/if}
</div>
<FlowJobResult noBorder col result={node.result ?? {}} logs={node.logs ?? ''} />
<FlowJobResult
loading={job['running'] == true}
noBorder
col
result={node.result}
logs={node.logs ?? ''}
/>
{:else}
<p class="p-2 text-gray-600 italic"
>The execution of this node has no information attached to it. The job likely did

View File

@@ -137,7 +137,7 @@
{#if perms}
<TableCustom>
<tr slot="header-row">
<th>user</th>
<th>user/group</th>
<th />
<th />
</tr>

View File

@@ -8,16 +8,16 @@
export let width = '24px'
export let center = false
$: name_prefix = name.split('_')[0]
$: iconComponent = APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
</script>
<div class="truncate flex flex-row gap-2 {center ? 'justify-center items-center' : ''} -pl-2">
{#if !silent && !after}
{name}
{/if}
{#if APP_TO_ICON_COMPONENT[name_prefix]}
{#if iconComponent}
<span class="text-gray-700 grayscale">
<svelte:component this={APP_TO_ICON_COMPONENT[name_prefix]} {height} {width} />
<svelte:component this={iconComponent} {height} {width} />
</span>
{:else}
<span style="width: {width}; height: {height}" class="bg-gray-100 rounded-full" />

View File

@@ -33,7 +33,7 @@
let monacoTemplate: TemplateEditor | undefined = undefined
let argInput: ArgInput | undefined = undefined
let inputCat: InputCat = computeInputCat(
$: inputCat = computeInputCat(
schema.properties[argName].type,
schema.properties[argName].format,
schema.properties[argName].items?.type,
@@ -60,7 +60,11 @@
}
if (isCodeInjection(rawValue)) {
arg.expr = getDefaultExpr(argName, previousModuleId, `\`${rawValue}\``)
arg.expr = getDefaultExpr(
argName,
previousModuleId,
`\`${rawValue.toString().replaceAll('`', '\\`')}\``
)
arg.type = 'javascript'
propertyType = 'static'
} else {
@@ -117,9 +121,13 @@
const openBracket = '${'
const closeBracket = '}'
$: schema.properties[argName].default &&
!arg?.value &&
monacoTemplate?.setCode(schema.properties[argName].default)
function setDefaultCode() {
if (!arg?.value) {
monacoTemplate?.setCode(schema.properties[argName].default)
}
}
$: schema.properties[argName].default && setDefaultCode()
</script>
{#if arg != undefined}
@@ -157,7 +165,7 @@
argName,
previousModuleId,
staticTemplate
? `\`${arg?.value ?? ''}\``
? `\`${arg?.value.toString().replaceAll('`', '\\`') ?? ''}\``
: arg.value
? JSON.stringify(arg?.value, null, 4)
: ''
@@ -172,6 +180,7 @@
if (staticTemplate) {
if (arg) {
arg.value = codeToStaticTemplate(arg.expr)
arg.expr = undefined
}
setPropertyType(arg?.value)
} else {
@@ -269,7 +278,6 @@
bind:itemsType={schema.properties[argName].items}
properties={schema.properties[argName].properties}
displayHeader={false}
bind:inputCat
{variableEditor}
{itemPicker}
bind:pickForField

View File

@@ -20,7 +20,7 @@
</tr>
{/each}
{:else if args}
<tr>No arguments</tr>
<tr><div class="text-gray-600 pt-2 pl-1 text-sm">No arguments</div></tr>
{:else}
<tr>
<td>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { ScriptService, type FlowModule, type Job } from '$lib/gen'
import { ScriptService, type FlowModule, type InputTransform, type Job } from '$lib/gen'
import { getScriptByPath, sendUserToast, truncateRev } from '$lib/utils'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import RunForm from './RunForm.svelte'
@@ -8,7 +8,7 @@
import LogViewer from './LogViewer.svelte'
import DisplayResult from './DisplayResult.svelte'
import Button from './common/button/Button.svelte'
import { flowStateStore } from './flows/flowState'
import { flowStateStore, testStepStore } from './flows/flowState'
import { flowStore } from './flows/flowStore'
import { workspaceStore } from '$lib/stores'
import { Loader2 } from 'lucide-svelte'
@@ -21,7 +21,18 @@
let testIsLoading = false
let testJob: Job | undefined = undefined
let stepArgs: Record<string, any> = {}
let stepArgs: Record<string, any> | undefined =
$testStepStore[mod.id] ??
Object.entries(mod.value['input_transforms'] ?? {}).reduce((acc, [k, v]) => {
let t = v as InputTransform
if (t.type == 'static') {
acc[k] = t.value
return acc
}
return acc
}, {})
$: $testStepStore[mod.id] = stepArgs
export function runTestWithStepArgs() {
runTest(stepArgs)
@@ -66,11 +77,12 @@
{/if}
<RunForm
noVariablePicker
loading={testIsLoading}
runnable={{ summary: mod.summary ?? '', schema, description: '' }}
runAction={(_, args) => runTest(args)}
schedulable={false}
buttonText="Test just this step (Ctrl+Enter)"
buttonText="Test (Ctrl+Enter)"
detailed={false}
topButton
bind:args={stepArgs}

View File

@@ -0,0 +1,3 @@
const SIDE = ['auto', 'top', 'bottom', 'left', 'right'] as const
const ALIGN = ['start', 'end'] as const
export type PopoverPlacement = `${typeof SIDE[number]}` | `${typeof SIDE[number]}-${typeof ALIGN[number]}`

View File

@@ -1,10 +1,8 @@
<script lang="ts">
import { createPopperActions } from 'svelte-popperjs'
import type { PopoverPlacement } from './Popover.model'
const SIDE = ['auto', 'top', 'bottom', 'left', 'right'] as const
const ALIGN = ['start', 'end'] as const
export let placement: `${typeof SIDE[number]}` | `${typeof SIDE[number]}-${typeof ALIGN[number]}` = 'auto'
export let placement: PopoverPlacement = 'auto'
export let notClickable = false
export let popupClass = ''
export let disapperTimoout = 100

View File

@@ -13,6 +13,7 @@
import SimpleEditor from './SimpleEditor.svelte'
import { faSave } from '@fortawesome/free-solid-svg-icons'
import SchemaForm from './SchemaForm.svelte'
import Toggle from './Toggle.svelte'
let path = ''
let initialPath = ''
@@ -26,7 +27,7 @@
let args: Record<string, any> = {}
let can_write = true
let loadingSchema = false
let linkedVars: string[] = []
let drawer: Drawer
let rawCode: string | undefined = undefined
@@ -38,6 +39,7 @@
path = p
resourceToEdit = undefined
resourceSchema = undefined
viewJsonSchema = false
loadingSchema = true
drawer.openDrawer?.()
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p })
@@ -48,6 +50,9 @@
can_write =
resourceToEdit.workspace_id == $workspaceStore &&
canWrite(p, resourceToEdit.extra_perms ?? {}, $userStore)
linkedVars = Object.entries(args)
.filter(([_, v]) => typeof v == 'string' && v == `$var:${initialPath}`)
.map(([k, _]) => k)
}
async function editResource(): Promise<void> {
@@ -58,7 +63,7 @@
requestBody: { path, value: args, description }
})
sendUserToast(`Updated resource at ${path}`)
dispatch('refresh')
dispatch('refresh', path)
drawer.closeDrawer?.()
} else {
throw Error('Cannot edit undefined resourceToEdit')
@@ -100,6 +105,24 @@
jsonError = e.message
}
}
$: linkedVars.length > 0 && path && updateArgsFromLinkedVars()
function updateArgsFromLinkedVars() {
linkedVars.forEach((k) => {
args[k] = `$var:${path}`
})
}
let viewJsonSchema = false
function switchTab(asJson: boolean) {
viewJsonSchema = asJson
if (asJson) {
rawCode = JSON.stringify(args, null, 2)
} else {
parseJson()
}
}
</script>
<Drawer bind:this={drawer} size="800px">
@@ -125,21 +148,21 @@
kind="resource"
/>
</div>
<h3>Description <Required required={false} /> </h3>
<textarea
type="text"
disabled={!can_write}
use:autosize
bind:value={description}
placeholder={DESCRIPTION_PLACEHOLDER}
/>
<h3 class="mt-4">Value</h3>
<div class="flex w-full justify-between max-w-lg items-center mt-4">
<h3>Value</h3>
<Toggle
on:change={(e) => switchTab(e.detail)}
options={{
right: 'As JSON'
}}
/>
</div>
<div class="text-sm">
{#if loadingSchema}
<Skeleton layout={[[4]]} />
{:else if resourceSchema && resourceSchema?.properties}
{:else if !viewJsonSchema && resourceSchema && resourceSchema?.properties}
<SchemaForm
noDelete
disabled={!can_write}
compact
schema={resourceSchema}
@@ -149,10 +172,13 @@
{:else if !can_write}
<input type="text" disabled value={rawCode} />
{:else}
<p class="italic text-gray-500 text-xs mb-4"
>No corresponding resource type found in your workspace for {selectedResourceType}.
Define the value in JSON directly</p
>
{#if !viewJsonSchema}
<p class="italic text-gray-500 text-xs mb-4"
>No corresponding resource type found in your workspace for {selectedResourceType}.
Define the value in JSON directly</p
>
{/if}
{#if !emptyString(jsonError)}<span
class="text-red-400 text-xs mb-1 flex flex-row-reverse">{jsonError}</span
>{:else}<div class="py-2" />{/if}
@@ -167,6 +193,14 @@
</div>
{/if}
</div>
<h3 class="mt-4">Description <Required required={false} /> </h3>
<textarea
type="text"
disabled={!can_write}
use:autosize
bind:value={description}
placeholder={DESCRIPTION_PLACEHOLDER}
/>
</div>
</div>
<svelte:fragment slot="actions">

View File

@@ -30,7 +30,7 @@
$: collection = resources.map((x) => ({
value: x.path,
label: `${x.path}${x.description ? ' | ' + x.description : ''}`
label: x.path
}))
let appConnect: AppConnect
let resourceEditor: ResourceEditor
@@ -45,10 +45,20 @@
bind:this={appConnect}
/>
<ResourceEditor bind:this={resourceEditor} on:refresh={() => loadResources(resourceType)} />
<ResourceEditor
bind:this={resourceEditor}
on:refresh={async (e) => {
await loadResources(resourceType)
console.log(e)
if (e.detail) {
value = e.detail
}
}}
/>
<div class="flex flex-row gap-x-1 w-full">
<Select
listAutoWidth={false}
--height="34px"
value={collection.find((x) => x.value == value)}
bind:justValue={value}
@@ -73,3 +83,9 @@
}}><Icon scale={0.8} data={faRotateRight} /></Button
>
</div>
<style>
:global(.svelte-select-list) {
font-size: small !important;
}
</style>

View File

@@ -45,6 +45,7 @@
export let autofocus = false
export let topButton = false
export let loading = false
export let noVariablePicker = false
export let args: Record<string, any> = decodeArgs($page.url.searchParams.get('args') ?? undefined)
@@ -117,52 +118,55 @@
{#if !runnable.schema.properties || Object.keys(runnable.schema.properties).length === 0}
<div class="text-sm p-4">No arguments</div>
{:else}
<SchemaForm {autofocus} schema={runnable.schema} bind:isValid bind:args />
<SchemaForm {noVariablePicker} {autofocus} schema={runnable.schema} bind:isValid bind:args />
{/if}
{:else}
<div class="text-xs text-gray-600">No schema</div>
{/if}
{#if viewOptions}
<div transition:slide class="mt-6">
<h2>Run later</h2>
<div class="border rounded-md p-3 pt-4">
<div class="flex flex-row items-end">
<div class="w-max md:w-2/3 mt-2 mb-1">
<label for="run-time" />
<input
class="inline-block"
type="datetime-local"
id="run-time"
name="run-scheduled-time"
bind:value={scheduledForStr}
min={getToday().toISOString().slice(0, 16)}
/>
</div>
{#if schedulable}
<div class="flex gap-2 items-start flex-wrap justify-between mt-2 md:mt-6 mb-6">
<div class="flex flex-col">
<div>
<Button
variant="border"
color="blue"
color="light"
size="sm"
btnClasses="mx-2 mb-1"
on:click={() => {
scheduledForStr = undefined
}}
endIcon={{ icon: viewOptions ? faChevronUp : faChevronDown }}
on:click={() => (viewOptions = !viewOptions)}
>
Clear
Schedule to run later
</Button>
</div>
{#if viewOptions}
<div transition:slide class="mt-6">
<div class="border rounded-md p-3 pt-4">
<div class="flex flex-row items-end">
<div class="w-max md:w-2/3 mt-2 mb-1">
<label for="run-time" />
<input
class="inline-block"
type="datetime-local"
id="run-time"
name="run-scheduled-time"
bind:value={scheduledForStr}
min={getToday().toISOString().slice(0, 16)}
/>
</div>
<Button
variant="border"
color="blue"
size="sm"
btnClasses="mx-2 mb-1"
on:click={() => {
scheduledForStr = undefined
}}
>
Clear
</Button>
</div>
</div>
</div>
{/if}
</div>
</div>
{/if}
{#if schedulable}
<div class="flex gap-2 items-center flex-wrap justify-between mt-2 md:mt-6 mb-6">
<Button
color="light"
size="sm"
endIcon={{ icon: viewOptions ? faChevronUp : faChevronDown }}
on:click={() => (viewOptions = !viewOptions)}
>
Schedule to run later
</Button>
{#if runnable?.path?.startsWith(`u/${$userStore?.username}`) != true && (runnable?.path?.split('/')?.length ?? 0) > 2}
<div class="flex items-center gap-1">
<Toggle
@@ -183,7 +187,7 @@
disabled={!isValid}
on:click={() => runAction(scheduledForStr, args, invisible_to_owner)}
>
{scheduledForStr ? 'Schedule run to a later time' : buttonText}
{scheduledForStr ? 'Schedule to run later' : buttonText}
</Button>
</div>
</div>

View File

@@ -30,6 +30,7 @@
export let filter: string[] | undefined = undefined
export let noDynamicToggle = false
export let flexWrap = false
export let noDelete = false
let clazz: string = ''
export { clazz as class }
@@ -43,23 +44,32 @@
function removeExtraKey() {
Object.keys(args ?? {}).forEach((key) => {
if (!Object.keys(schema?.properties ?? {}).includes(key)) {
if (!keys.includes(key)) {
delete args[key]
delete inputCheck[key]
console.log('DELETED', key)
}
})
}
$: schema?.properties && removeExtraKey()
let pickForField: string | undefined
let itemPicker: ItemPicker | undefined = undefined
let variableEditor: VariableEditor | undefined = undefined
let keys: string[] = []
$: {
let lkeys = Object.keys(schema?.properties ?? {})
if (schema?.properties && JSON.stringify(lkeys) != JSON.stringify(keys)) {
keys = lkeys
if (!noDelete) {
removeExtraKey()
}
}
}
</script>
<div class="w-full {clazz} {flexWrap ? 'flex flex-row flex-wrap gap-x-6 gap-y-2' : ''}">
{#if Object.keys(schema?.properties ?? {}).length > 0}
{#each Object.keys(schema?.properties ?? {}) as argName, i (argName)}
{#if keys.length > 0}
{#each keys as argName, i (argName)}
{#if !filter || filter.includes(argName)}
<div transition:slide|local>
{#if inputTransform}
@@ -149,5 +159,5 @@
</div>
</ItemPicker>
<VariableEditor bind:this={variableEditor} on:create={itemPicker.openDrawer} />
<VariableEditor bind:this={variableEditor} />
{/if}

View File

@@ -63,6 +63,13 @@
const dispatch = createEventDispatcher()
let drawer: Drawer
function handleKeyUp(event: KeyboardEvent) {
const key = event.key
if (key === 'Enter') {
dispatch('save')
}
}
export function openDrawer(): void {
drawer.openDrawer()
resource_type = property.format?.substring(5)
@@ -97,6 +104,8 @@
}
</script>
<svelte:window on:keyup={handleKeyUp} />
<Drawer bind:this={drawer} placement="right">
<DrawerContent on:close={clearModal} title="Add an argument">
<div class="flex flex-col gap-6">

View File

@@ -6,7 +6,7 @@
import { inferArgs } from '$lib/infer'
import { initialCode, isInitialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import { encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
import { emptySchema, encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
import Path from './Path.svelte'
import RadioButton from './RadioButton.svelte'
import ScriptEditor from './ScriptEditor.svelte'
@@ -58,9 +58,17 @@
try {
$dirtyStore = false
localStorage.removeItem(script.path)
if (!script.schema) {
script.schema = script.schema ?? emptySchema()
try {
await inferArgs(script.language, script.content, script.schema)
} catch (error) {
sendUserToast(
`Impossible to infer the schema. Assuming this is a script without main function`,
true
)
}
const newHash = await ScriptService.createScript({
workspace: $workspaceStore!,
requestBody: {
@@ -84,7 +92,14 @@
async function changeStep(step: number) {
if (step > 1) {
await inferArgs(script.language, script.content, script.schema)
script.schema = script.schema ?? emptySchema()
try {
await inferArgs(script.language, script.content, script.schema)
} catch (error) {
console.info(
'Impossible to infer the schema. Assuming this is a script without main function'
)
}
}
goto(`?step=${step}`)
}

View File

@@ -71,6 +71,7 @@
}
async function inferSchema(code: string) {
schema = schema ?? emptySchema()
let isDefault: string[] = []
Object.entries(args).forEach(([k, v]) => {
if (schema.properties[k].default == v) {

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { ChevronDown, ChevronUp } from 'lucide-svelte'
import { slide } from 'svelte/transition'
import Button from './common/button/Button.svelte'
export let text: string
export let view = false
</script>
<Button color="light" on:click={() => (view = !view)} variant="border"
>{text}
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
>
{#if view}
<div class="my-4 px-2" transition:slide><slot /></div>
{/if}

View File

@@ -382,6 +382,10 @@
export let fixedOverflowWidgets = true
export let fontSize = 16
if (typeof code != 'string') {
code = ''
}
const lang = 'template'
const dispatch = createEventDispatcher()
@@ -589,4 +593,15 @@
})
</script>
<div bind:this={divEl} class="{$$props.class} rounded-lg mx-0.5" bind:clientWidth={width} />
<div
bind:this={divEl}
style="height: 18px;"
class="{$$props.class} template rounded-lg min-h-4 mx-0.5"
bind:clientWidth={width}
/>
<style>
:global(.template .mtk20) {
color: black !important;
}
</style>

View File

@@ -195,6 +195,6 @@
}
onDestroy(async () => {
await clearCurrentJob()
currentId = undefined
})
</script>

View File

@@ -1,16 +1,22 @@
<script lang="ts">
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import type { PopoverPlacement } from './Popover.model'
import Popover from './Popover.svelte'
export let light = false
export let scale = 0.8
export let wrapperClass = ''
export let placement: PopoverPlacement | undefined = undefined
</script>
<Popover notClickable>
<Popover notClickable {placement} class={wrapperClass}>
<Icon
class="{light ? 'text-gray-300' : ' text-gray-500'} font-thin inline-block align-middle w-4"
class="{light
? 'text-gray-300'
: ' text-gray-500'} font-thin inline-block align-middle w-4 {$$props.class}"
data={faInfoCircle}
scale={0.8}
{scale}
/>
<span slot="text"> <slot /> </span>
</Popover>

View File

@@ -15,7 +15,7 @@
let tokens: TruncatedToken[]
let newToken: string | undefined
let newTokenLabel: string | undefined
let newTokenExpiration: string | undefined
let newTokenExpiration: number | undefined
let displayCreateToken = false
let login_type = 'none'
@@ -57,12 +57,13 @@
async function createToken(): Promise<void> {
newToken = undefined
let expirationISO: Date | undefined
let date: Date | undefined
if (newTokenExpiration) {
expirationISO = new Date(newTokenExpiration)
date = new Date()
date.setDate(date.getDate() + newTokenExpiration)
}
newToken = await UserService.createToken({
requestBody: { label: newTokenLabel, expiration: expirationISO?.toISOString() } as NewToken
requestBody: { label: newTokenLabel, expiration: date?.toISOString() } as NewToken
})
listTokens()
displayCreateToken = false
@@ -87,7 +88,7 @@
Windmill {version}
</div>
<h2 class="border-b">User info</h2>
<h2 class="border-b mt-4">User info</h2>
<div class="">
{#if passwordError}
<div class="text-red-600 text-2xs grow">{passwordError}</div>
@@ -164,7 +165,7 @@
>{truncate(newToken ?? '', 10, '****')} <Icon data={faClipboard} />
</button>
</div>
<div class="pt-1 text-xs text-right">
<div class="pt-1 text-xs ml-2">
Make sure to copy your personal access token now. You wont be able to see it again!
</div>
</div>
@@ -185,29 +186,17 @@
</div>
<div class="flex flex-col ">
<label for="expires"
>Expires on <span class="text-xs text-gray-500">(optional)</span>
>Expires In<span class="text-xs text-gray-500">(optional)</span>
</label>
<input
class="block md:w-1/2"
type="date"
id="expires"
name="expiration-date"
bind:value={newTokenExpiration}
min={getToday().getFullYear() +
'/' +
getToday().getMonth() +
'/' +
getToday().getDate()}
max={getToday().getFullYear() +
1 +
'/' +
getToday().getMonth() +
'/' +
getToday().getDate()}
/>
<select bind:value={newTokenExpiration}>
<option value={undefined}>No expiration</option>
<option value={7}>7d</option>
<option value={30}>30d</option>
<option value={90}>90d</option>
</select>
</div>
<div class="flex items-end">
<Button btnClasses="!mt-2" on:click={createToken}>Submit</Button>
<Button btnClasses="!mt-2" on:click={createToken}>New Token</Button>
</div>
</div>
</div>

View File

@@ -1,11 +1,14 @@
<script lang="ts">
import DisplayResult from '$lib/components/DisplayResult.svelte'
import { getContext } from 'svelte'
import type { AppInput } from '../inputType'
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
let result: any = undefined
export const staticOutputs: string[] = ['result', 'loading']
@@ -16,6 +19,6 @@
Results
</div>
<div class="p-2">
<DisplayResult {result} />
<DisplayResult {result} {requireHtmlApproval} />
</div>
</RunnableWrapper>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
import type { InlineScript } from '../types'
import RunnableComponent from './helpers/RunnableComponent.svelte'
export let id: string
export let name: string
export let inlineScript: InlineScript | undefined
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
let result: any = undefined
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableComponent
{id}
bind:fields
bind:result
runnable={{
name,
inlineScript,
type: 'runnableByName'
}}
wrapperClass="hidden"
>
<slot />
</RunnableComponent>

View File

@@ -27,6 +27,7 @@
let color: ButtonType.Color
let size: ButtonType.Size
let runnableComponent: RunnableComponent
let disabled: boolean | undefined = undefined
let isLoading: boolean = false
let ownClick: boolean = false
@@ -50,11 +51,22 @@
})
$: loading = isLoading && ownClick
let errors: Record<string, string> = {}
$: errorsMessage = Object.values(errors)
.filter((x) => x != '')
.join('\n')
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue
row={extraQueryParams['row']}
{id}
input={configuration.disabled}
bind:value={disabled}
bind:error={errors.disabled}
/>
<RunnableWrapper
flexWrap
@@ -65,16 +77,20 @@
autoRefresh={false}
>
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
{#if errorsMessage}
<div class="text-red-500 text-xs">{errorsMessage}</div>
{/if}
<Button
{disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={(e) => {
on:click={async (e) => {
e?.stopPropagation()
e?.preventDefault()
ownClick = true
runnableComponent?.runComponent()
await runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {

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