Compare commits

...

127 Commits

Author SHA1 Message Date
Ruben Fiszel
7739c4beaa chore(main): release 1.11.0 (#112)
* chore(main): release 1.11.0

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-06-14 01:45:10 +02:00
Ruben Fiszel
f1ee5f3130 feat: add ResourceType<'name'> as deno signature arg type 2022-06-14 01:43:24 +02:00
Ruben Fiszel
a59b92706b fix(frontend): loadItems not called in script picker 2022-06-13 20:47:27 +02:00
Ruben Fiszel
9f235c404e fix: force c_ prefix for adding resource type 2022-06-12 16:09:54 +02:00
Ruben Fiszel
95d98fc8fe remove exec_fd for compatibility with older kernels 2022-06-12 14:42:31 +02:00
Ruben Fiszel
8c4999d528 fix DISABLE_NUSER 2022-06-12 13:55:04 +02:00
Ruben Fiszel
a72d6dcc40 chore(deps): update backend dependencies 2022-06-12 13:48:05 +02:00
Ruben Fiszel
cce46f9440 feat: add DISABLE_NUSER for older kernels 2022-06-12 13:30:40 +02:00
Ruben Fiszel
5afcb2b274 rm unecessary Caddyfile 2022-06-12 03:52:17 +02:00
Ruben Fiszel
0da602d2c7 chore(main): release 1.10.1 (#111)
* chore(main): release 1.10.1

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-06-12 03:45:23 +02:00
Ruben Fiszel
295e28fd43 fix: python-client verify ssl 2022-06-12 03:42:44 +02:00
Ruben Fiszel
c3526d3172 simplify dockerfile - remove unecessary caddy 2022-06-12 03:05:39 +02:00
Ruben Fiszel
c3d2fd6e52 chore(main): release 1.10.0 (#105)
* chore(main): release 1.10.0

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-06-12 02:01:27 +02:00
Ruben Fiszel
1a61d50076 feat: alpha hub integration + frontend user store fixes + script client base_url fix 2022-06-12 01:55:05 +02:00
Ruben Fiszel
f691f53224 chore(main): release 1.9.0 (#63)
* chore(main): release 1.9.0

* Apply automatic changes

* Update CHANGELOG.md

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-06-05 13:43:51 +02:00
Ruben Fiszel
55ec20f1de bump svelte-preprocess 2022-06-05 13:30:49 +02:00
Ruben Fiszel
f2348b5526 fix: remove annoying transitions for scripts and flows 2022-06-05 13:16:00 +02:00
Ruben Fiszel
75cdb228dc fix login bug 2022-06-03 21:00:13 +02:00
Ruben Fiszel
26b8fd159a fix login bug 2022-06-03 20:52:14 +02:00
Faton Ramadani
fc8b078101 Setup Cypress e2e tests (#91)
* Setup Cypress e2e tests

* Add login function

* Cypress github action setup

* Fix CI github action

* Properly setup node and install dependencies

* Wait on localhost to respond before running the tests

* Install missing dependencies

* Remove rust setup

* Stop caddy after installation

* Remove Caddy from CI

* Properly connect to DB

* CI clean up

* Run cypress after build

* Testing CI

* Restore commented code

* Fix docker image tag

* Fix tags

* Fix tag

* Fix tag

* Fix node_modules

* Fix postgres host name

* Bind

* Fix port

* Logs

* Fix DB Host

* Test GA

* Create docker network

* Get IP from container

* Try removing custom wait-on

* Correctly run cypress tests

* Print IP

* Add logs

* Debug docker

* Add logs

* Logs

* logs

* Fix DB hostname

* tring my way

* tring my way

* tring my way

* tring my way

* works

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-06-03 20:22:12 +02:00
Ruben Fiszel
20cabe3335 minor fixes 2022-06-03 19:39:37 +02:00
Ruben Fiszel
0fe276b564 fix login button 2022-06-02 12:12:42 +02:00
Ruben Fiszel
8a8dbcb582 contributors section in README 2022-06-01 20:28:20 +02:00
dependabot[bot]
587ce379d4 chore(deps-dev): bump @sveltejs/kit in /frontend (#88)
Bumps [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit) from 1.0.0-next.342 to 1.0.0-next.347.
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@1.0.0-next.347/packages/kit)

---
updated-dependencies:
- dependency-name: "@sveltejs/kit"
  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>
2022-06-01 10:01:30 +02:00
dependabot[bot]
c8eedf7d77 chore(deps-dev): bump @typescript-eslint/parser in /frontend (#89)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.26.0 to 5.27.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.27.0/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-01 10:01:14 +02:00
dependabot[bot]
ca436d1d2a chore(deps-dev): bump @sveltejs/adapter-static in /frontend (#87)
Bumps [@sveltejs/adapter-static](https://github.com/sveltejs/kit/tree/HEAD/packages/adapter-static) from 1.0.0-next.31 to 1.0.0-next.34.
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/master/packages/adapter-static/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/adapter-static@1.0.0-next.34/packages/adapter-static)

---
updated-dependencies:
- dependency-name: "@sveltejs/adapter-static"
  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>
2022-06-01 09:54:28 +02:00
dependabot[bot]
9876b22d62 chore(deps-dev): bump postcss-load-config in /frontend (#85)
Bumps [postcss-load-config](https://github.com/postcss/postcss-load-config) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/postcss/postcss-load-config/releases)
- [Changelog](https://github.com/postcss/postcss-load-config/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-load-config/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: postcss-load-config
  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>
2022-06-01 09:54:02 +02:00
dependabot[bot]
04093a9a14 chore(deps-dev): bump @typescript-eslint/eslint-plugin in /frontend (#86)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.26.0 to 5.27.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.27.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>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-01 09:53:42 +02:00
dependabot[bot]
1f6946f09b chore(deps): bump @zerodevx/svelte-toast in /frontend (#80)
Bumps [@zerodevx/svelte-toast](https://github.com/zerodevx/svelte-toast) from 0.7.1 to 0.7.2.
- [Release notes](https://github.com/zerodevx/svelte-toast/releases)
- [Commits](https://github.com/zerodevx/svelte-toast/compare/v0.7.1...v0.7.2)

---
updated-dependencies:
- dependency-name: "@zerodevx/svelte-toast"
  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>
2022-06-01 09:52:46 +02:00
dependabot[bot]
5a14d4b7d8 chore(deps-dev): bump eslint-plugin-svelte3 in /frontend (#79)
Bumps [eslint-plugin-svelte3](https://github.com/sveltejs/eslint-plugin-svelte3) from 3.4.1 to 4.0.0.
- [Release notes](https://github.com/sveltejs/eslint-plugin-svelte3/releases)
- [Changelog](https://github.com/sveltejs/eslint-plugin-svelte3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/eslint-plugin-svelte3/compare/v3.4.1...v4.0.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-svelte3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-01 09:51:47 +02:00
dependabot[bot]
645e01a970 chore(deps): bump regex from 1.5.5 to 1.5.6 in /backend (#74)
Bumps [regex](https://github.com/rust-lang/regex) from 1.5.5 to 1.5.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.5.5...1.5.6)

---
updated-dependencies:
- dependency-name: regex
  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>
2022-06-01 09:50:53 +02:00
dependabot[bot]
ee9d9d25bc chore(deps-dev): bump @sveltejs/adapter-node in /frontend (#84)
Bumps [@sveltejs/adapter-node](https://github.com/sveltejs/kit/tree/HEAD/packages/adapter-node) from 1.0.0-next.73 to 1.0.0-next.78.
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/master/packages/adapter-node/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/adapter-node@1.0.0-next.78/packages/adapter-node)

---
updated-dependencies:
- dependency-name: "@sveltejs/adapter-node"
  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>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-06-01 09:49:04 +02:00
dependabot[bot]
fc19c3c247 chore(deps-dev): bump cssnano from 5.1.9 to 5.1.10 in /frontend (#82)
Bumps [cssnano](https://github.com/cssnano/cssnano) from 5.1.9 to 5.1.10.
- [Release notes](https://github.com/cssnano/cssnano/releases)
- [Commits](https://github.com/cssnano/cssnano/compare/cssnano@5.1.9...cssnano@5.1.10)

---
updated-dependencies:
- dependency-name: cssnano
  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>
2022-06-01 09:48:17 +02:00
dependabot[bot]
54ca6362d6 chore(deps-dev): bump typescript from 4.6.4 to 4.7.2 in /frontend (#83)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.6.4 to 4.7.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.6.4...v4.7.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-01 09:47:02 +02:00
Ruben Fiszel
772c5806c9 login or signup whiter font 2022-06-01 09:45:51 +02:00
Ruben Fiszel
3d7a03af5f cloudflare function uses manual redirect 2022-05-30 12:48:08 +02:00
Ruben Fiszel
ac1cbba238 frontend: small fixes 2022-05-29 14:28:11 +02:00
Ruben Fiszel
d2078f175e frontend: small fixes 2022-05-29 14:27:24 +02:00
Ruben Fiszel
720093962a frontend: small fixes 2022-05-29 10:29:13 +02:00
Ruben Fiszel
e471a1d646 ci: more consistent docker image names 2022-05-26 00:49:41 +02:00
Faton Ramadani
9e6ab11484 Authentication refactor (#65)
* Refactor login logic

* Derive username from user + fix initial redirection if logged in

* Simplify how login navigation works

* Restore redirection

* Redirect to login page when not logged in

* Fix PR issues

* Add missing refreshSuperadmin when reloading a page with a valid token

* Explicitly clearing stores when logging out.

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-05-24 17:05:40 +02:00
dependabot[bot]
06eb50fbf2 chore(deps-dev): bump openapi-typescript-codegen in /frontend (#70)
Bumps [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) from 0.11.8 to 0.22.0.
- [Release notes](https://github.com/ferdikoomen/openapi-typescript-codegen/releases)
- [Changelog](https://github.com/ferdikoomen/openapi-typescript-codegen/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ferdikoomen/openapi-typescript-codegen/commits/v0.22.0)

---
updated-dependencies:
- dependency-name: openapi-typescript-codegen
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 16:44:39 +02:00
dependabot[bot]
40a380e9ec chore(deps-dev): bump postcss-load-config in /frontend (#71)
Bumps [postcss-load-config](https://github.com/postcss/postcss-load-config) from 3.1.4 to 4.0.0.
- [Release notes](https://github.com/postcss/postcss-load-config/releases)
- [Changelog](https://github.com/postcss/postcss-load-config/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-load-config/compare/v3.1.4...v4.0.0)

---
updated-dependencies:
- dependency-name: postcss-load-config
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 16:41:06 +02:00
dependabot[bot]
c638f7a132 chore(deps-dev): bump @sveltejs/kit from 1.0.0-next.338 to 1.0.0-next.342 in /frontend (#78)
* chore(deps-dev): bump @sveltejs/kit in /frontend

Bumps [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit) from 1.0.0-next.338 to 1.0.0-next.342.
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@1.0.0-next.342/packages/kit)

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

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

* breaking changes

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-05-24 16:40:21 +02:00
Ruben Fiszel
d2f4a552c9 update eslint + prettify 2022-05-24 16:30:32 +02:00
Ruben Fiszel
479a12f33c feat: update postgres 13->14 in docker-compose 2022-05-24 16:21:57 +02:00
Ruben Fiszel
58e2a5c179 add cloudflare pages redirection 2022-05-24 14:24:15 +02:00
Ruben Fiszel
281fbc3671 edit .nvmrc 2022-05-24 14:04:26 +02:00
Ruben Fiszel
ffc58ab6c2 add .nvmrc 2022-05-24 13:58:20 +02:00
dependabot[bot]
0ea96f82d1 chore(deps-dev): bump eslint from 7.32.0 to 8.16.0 in /frontend (#69)
Bumps [eslint](https://github.com/eslint/eslint) from 7.32.0 to 8.16.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.32.0...v8.16.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 12:57:33 +02:00
Ruben Fiszel
e905d65ca6 fix: update monaco language-client for better lsp support 2022-05-23 13:23:37 +02:00
Ruben Fiszel
dc70dfcf74 fix: bypass RLS for admin at init-db.sql 2022-05-23 12:00:56 +02:00
Ruben Fiszel
9b79cc9870 fix: update monaco language-client for better lsp support 2022-05-21 10:56:54 +02:00
Ruben Fiszel
68a3e1b333 fix: update monaco language-client for better lsp support 2022-05-21 10:50:04 +02:00
Ruben Fiszel
917717373f fix: update monaco language-client for better lsp support 2022-05-21 10:36:53 +02:00
Ruben Fiszel
b61fb6dc30 fix: update monaco language-client for better lsp support 2022-05-21 09:05:03 +02:00
Ruben Fiszel
42aa386119 fix: update monaco language-client for better lsp support 2022-05-20 18:40:17 +02:00
Ruben Fiszel
d601ef9439 chore(main): release 1.8.6 (#62)
* chore(main): release 1.8.6

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-18 23:34:48 +02:00
Ruben Fiszel
d31cd3c52c fix: re-release 2022-05-18 23:33:33 +02:00
Ruben Fiszel
eb613c35c1 chore(main): release 1.8.5 (#61) 2022-05-18 23:28:42 +02:00
Ruben Fiszel
33fed8e04d fix: language field broke flow too 2022-05-18 23:28:09 +02:00
Ruben Fiszel
37afd486fd chore(main): release 1.8.4 (#60)
* chore(main): release 1.8.4

* Apply automatic changes

* Update CHANGELOG.md

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-18 22:58:34 +02:00
Ruben Fiszel
f76eede3b0 rebuild v8 is lockfile changed 2022-05-18 22:53:46 +02:00
Ruben Fiszel
7564d2cb1e fix: run scirpt 2022-05-18 22:50:56 +02:00
Ruben Fiszel
f12fe85fef chore(main): release 1.8.3 (#59)
* chore(main): release 1.8.3

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-18 10:13:12 +02:00
Ruben Fiszel
fd9285563a add v8.snap to .gitignore 2022-05-18 10:10:56 +02:00
Ruben Fiszel
605c2b4d11 fix: clean exported deno-client api 2022-05-18 10:09:41 +02:00
Ruben Fiszel
18b4ab2e73 fix publish pypi 2022-05-18 09:53:46 +02:00
Ruben Fiszel
02fb2b3806 chore(main): release 1.8.2 (#58)
* chore(main): release 1.8.2

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-18 09:50:12 +02:00
Ruben Fiszel
563ba3e7f7 fix: deno client 2022-05-18 09:48:41 +02:00
Ruben Fiszel
3eed59fcb1 fix: deno lsp client 2022-05-18 01:32:00 +02:00
Ruben Fiszel
7365a8e87b fix: starting deno script is now async 2022-05-17 23:15:16 +02:00
Ruben Fiszel
dbd6142997 align jsonrpc 2022-05-17 23:01:02 +02:00
Ruben Fiszel
865d728224 fix: deno lsp uses wss instead of ws 2022-05-17 22:39:57 +02:00
Ruben Fiszel
8861e19564 ci: add deno 2022-05-17 22:21:45 +02:00
Ruben Fiszel
92b502d9ba chore(main): release 1.8.1 (#57)
* chore(main): release 1.8.1

* Apply automatic changes

* Apply automatic changes

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-17 21:49:10 +02:00
Ruben Fiszel
297a3e60e2 ci: fix change version 2022-05-17 21:47:36 +02:00
Ruben Fiszel
1decaafde0 remove poetry locks 2022-05-17 21:43:15 +02:00
Ruben Fiszel
a7ef616c0d ci: fix change version 2022-05-17 21:39:10 +02:00
Ruben Fiszel
481685a73e ci: fix change version 2022-05-17 21:36:49 +02:00
Ruben Fiszel
a356e7b7d3 ci: use python poetry for change versions 2022-05-17 21:35:10 +02:00
Ruben Fiszel
f793bc46d9 fix: frontend dependencies update 2022-05-17 21:30:10 +02:00
Ruben Fiszel
c49e4930bc update frontend 2022-05-17 21:28:24 +02:00
dependabot[bot]
7b6ae612a5 chore(deps): bump @codingame/monaco-jsonrpc in /frontend (#55)
Bumps [@codingame/monaco-jsonrpc](https://github.com/CodinGame/monaco-jsonrpc) from 0.3.1 to 0.4.0.
- [Release notes](https://github.com/CodinGame/monaco-jsonrpc/releases)
- [Commits](https://github.com/CodinGame/monaco-jsonrpc/commits)

---
updated-dependencies:
- dependency-name: "@codingame/monaco-jsonrpc"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 21:20:40 +02:00
dependabot[bot]
d179d6efc3 chore(deps): bump @zerodevx/svelte-toast in /frontend (#56)
Bumps [@zerodevx/svelte-toast](https://github.com/zerodevx/svelte-toast) from 0.6.3 to 0.7.1.
- [Release notes](https://github.com/zerodevx/svelte-toast/releases)
- [Commits](https://github.com/zerodevx/svelte-toast/compare/v0.6.3...v0.7.1)

---
updated-dependencies:
- dependency-name: "@zerodevx/svelte-toast"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 21:19:08 +02:00
Ruben Fiszel
f02e5b19ac update frontend + lock python client 2022-05-17 21:16:36 +02:00
Ruben Fiszel
e114d0f426 chore(main): release 1.8.0 (#52)
* chore(main): release 1.8.0

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-17 20:53:14 +02:00
Ruben Fiszel
03ec38e001 update cargo 2022-05-17 20:52:42 +02:00
Ruben Fiszel
2e1d43033f feat: Typescript support for scripts (alpha)
* typescript support

* frontend

* type inference

* type inference

* v0 works

* v0 typescript

* v0 typescript

* deno-client v0

* deno-client v0

* build_deno

* rm autogenerated files

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* test workflow

* on tags

* createResource

* createResource

* createResource2

* typescript support

* templates

* include version
2022-05-17 20:42:05 +02:00
Ruben Fiszel
ec528fce67 chore(main): release 1.7.0 (#45)
* chore(main): release 1.7.0

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-14 14:58:31 +02:00
Tomasz Wsuł
5b413d7e04 feat: self host github oauth (#46) 2022-05-14 14:54:53 +02:00
Ruben Fiszel
02c8bea084 fix: better error message when saving script 2022-05-11 13:29:21 +02:00
Ruben Fiszel
bb31c80378 fix README docker-compose reference 2022-05-11 13:05:22 +02:00
Ruben Fiszel
91045e73cc BUG_ISSUE instructions 2022-05-11 08:10:51 +02:00
dependabot[bot]
9219b651a3 chore(deps-dev): bump @sveltejs/kit in /frontend (#25)
Bumps [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit) from 1.0.0-next.324 to 1.0.0-next.326.
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@1.0.0-next.326/packages/kit)

---
updated-dependencies:
- dependency-name: "@sveltejs/kit"
  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>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-05-11 01:27:27 +02:00
Ruben Fiszel
7f21d03d00 chore(main): release 1.6.1 (#34)
* chore(main): release 1.6.1

* Apply automatic changes

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-10 21:38:59 +02:00
dependabot[bot]
a62e6e5ee3 chore(deps): bump serde_json from 1.0.79 to 1.0.81 in /backend (#26)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.79 to 1.0.81.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.79...v1.0.81)

---
updated-dependencies:
- dependency-name: serde_json
  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>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-05-10 21:32:22 +02:00
Ruben Fiszel
2c28031e44 fix: also store and display "started at" for completed jobs (#33) 2022-05-10 21:32:07 +02:00
Ruben Fiszel
ca8de69126 run prettier 2022-05-10 21:29:54 +02:00
dependabot[bot]
98071bd68b chore(deps): bump tower-http from 0.2.5 to 0.3.3 in /backend (#27)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.2.5 to 0.3.3.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.2.5...tower-http-0.3.3)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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>
2022-05-10 21:18:04 +02:00
dependabot[bot]
128dde4fb3 chore(deps): bump thiserror from 1.0.30 to 1.0.31 in /backend (#30)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.30 to 1.0.31.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.30...1.0.31)

---
updated-dependencies:
- dependency-name: thiserror
  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>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-05-10 21:07:45 +02:00
dependabot[bot]
f090945b27 chore(deps): bump serde from 1.0.136 to 1.0.137 in /backend (#32) 2022-05-10 21:07:29 +02:00
dependabot[bot]
60729d80b9 chore(deps): bump mhart/alpine-node from 14 to 16 (#21)
Bumps mhart/alpine-node from 14 to 16.

---
updated-dependencies:
- dependency-name: mhart/alpine-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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>
2022-05-10 17:28:49 +02:00
Ruben Fiszel
e228beec2a ci: push to private registry builded image no matter what 2022-05-10 17:15:16 +02:00
dependabot[bot]
4dbf562fb7 chore(deps): bump GoogleCloudPlatform/release-please-action from 2 to 3 (#20) 2022-05-10 14:41:11 +02:00
dependabot[bot]
4952290296 chore(deps): bump actions/checkout from 2 to 3 (#19)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-10 14:24:17 +02:00
Juan Calderon-Perez
f53eb71e4a ci: add support for dependabot (#9)
* Add support for dependabot

* Add dependabot support for Python clients

* move to a weekly schedule

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2022-05-10 12:14:38 +00:00
Ruben Fiszel
96f54f5f44 chore: release 1.6.0 (#6)
* Apply automatic changes

* Update version.txt

* Apply automatic changes

* Update CHANGELOG.md

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2022-05-10 12:48:04 +02:00
Ruben Fiszel
0863e12e6a ci: add codeowners 2022-05-10 09:41:44 +02:00
Ruben Fiszel
d03266b0a4 ci: add CLA 2022-05-10 09:12:24 +02:00
Ruben Fiszel
4a4eaa90e2 ci: add CLA 2022-05-10 09:02:28 +02:00
Ruben Fiszel
5e7c14b722 ci: add CLA 2022-05-10 08:52:11 +02:00
Ruben Fiszel
55b5695673 fix: display more than default 30 workspaces as superadmin 2022-05-09 15:18:28 +02:00
Ruben Fiszel
8596ac50b9 delete starter script without lock files 2022-05-08 17:56:16 +02:00
Ruben Fiszel
13fb52117b feat: self host minimal 2 2022-05-08 17:51:33 +02:00
Ruben Fiszel
2c70a15594 feat: self host minimal 2022-05-08 17:26:51 +02:00
Ruben Fiszel
7a51f842f0 feat: superadmin settings 2022-05-08 17:03:13 +02:00
Ruben Fiszel
a130806e19 feat: user settings is now at workspace level 2022-05-08 12:58:58 +02:00
Ruben Fiszel
fd1f05dd16 ci: refactor + dockerhub 2022-05-08 11:57:37 +02:00
Ruben Fiszel
48e51733e0 docs: add main ci badge 2022-05-06 14:59:42 +02:00
Ruben Fiszel
e7817e6c9f alpha.windmill -> app.windmill 2022-05-06 13:55:14 +02:00
Ruben Fiszel
51ad6edfcb docs: typos 2022-05-05 15:59:59 +02:00
Ruben Fiszel
315f7edd64 docs: windmill imgs 2022-05-05 15:53:40 +02:00
Ruben Fiszel
a2c3deab74 docs: README general idea 2022-05-05 15:24:35 +02:00
Ruben Fiszel
891b7eb93a docs: architecture diagram 2022-05-05 13:22:13 +02:00
Ruben Fiszel
7efd87be79 docs: architecture diagram 2022-05-05 13:20:42 +02:00
Ruben Fiszel
5acbc8b48c Create FUNDING.yml 2022-05-05 10:50:54 +02:00
203 changed files with 9368 additions and 5506 deletions

6
.env
View File

@@ -1,3 +1,5 @@
SITE_URL=localhost
DB_PASSWORD=changeme
POSTGRES_VERSION=13.3.0
# GitHub OAuth- https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app
GITHUB_OAUTH_CLIENT_ID=yours_client_id
GITHUB_OAUTH_CLIENT_SECRET=yours_client_sected

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @rubenfiszel

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [rubenfiszel]

View File

@@ -1,38 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
title: 'bug:'
labels: 'bug'
assignees: 'rubenfiszel'
---
**Describe the bug**
A clear and concise description of what the bug is.
**Describe the bug** A clear and concise description of what the bug is.
**To Reproduce** Steps to reproduce the behavior:
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Expected behavior** A clear and concise description of what you expected to
happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Screenshots** If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Windmill version** Go on the left menu -> <user> -> User Settings and copy the
printed version in "Running windmill version (backend): XXX".
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
**Additional context** Add any other context about the problem here.

View File

@@ -0,0 +1,8 @@
---
name: Feature Request
about: Create a feature request
title: 'feature: '
labels: 'feature'
assignees: 'rubenfiszel'
---

View File

@@ -9,8 +9,10 @@ sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" frontend/package.json
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" python-client/wmill/pyproject.toml
sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill/pyproject.toml
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" python-client/wmill_pg/pyproject.toml
sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" Pipfile
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" Pipfile
# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" lsp/Pipfile
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" lsp/Pipfile
sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" backend/Cargo.lock
cd frontend && npm i --package-lock-only

39
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
# Basic set up for three package managers
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Maintain dependencies for npm
- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "weekly"
# Maintain dependencies for cargo
- package-ecosystem: "cargo"
directory: "/backend"
schedule:
interval: "weekly"
# Maintain dependencies for Docker
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
# Maintain dependencies for wmill python client
- package-ecosystem: "pip"
directory: "/python-client/wmill"
schedule:
interval: "weekly"
# Maintain dependencies for wmill_pg python client
- package-ecosystem: "pip"
directory: "/python-client/wmill_pg"
schedule:
interval: "weekly"

View File

@@ -7,8 +7,9 @@ on:
jobs:
change_version:
runs-on: ubuntu-latest
container: node:18
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Change versions
run: ./.github/change-versions.sh "$(cat version.txt)"
- uses: stefanzweifel/git-auto-commit-action@v4

49
.github/workflows/deno_on_release.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Publish deno-client
on:
push:
tags:
- "v*"
env:
repo: windmill-deno-client
jobs:
build_deno_and_push_to_repo:
runs-on: ubuntu-latest
container: openapitools/openapi-generator-cli:v6.0.0-beta
steps:
- uses: actions/checkout@v3
- name: generate_deno
run: |
cd deno-client
rm .gitignore
./generate.sh
- name: Pushes to another repository
id: push_directory
uses: cpina/github-action-push-to-another-repository@devel
env:
API_TOKEN_GITHUB: ${{ secrets.DENO_PAT }}
with:
source-directory: deno-client/
destination-github-username: ${{ github.repository_owner }}
destination-repository-name: ${{ env.repo }}
user-email: ruben@windmill.dev
commit-message: See ORIGIN_COMMIT from $GITHUB_REF
target-branch: main
tag_repo:
needs: [build_deno_and_push_to_repo]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
repository: ${{ github.repository_owner }}/${{ env.repo }}
token: ${{ secrets.DENO_PAT }}
path: ./client
- name: Push client
run: |
cd ./client
git config --global user.email "ruben@windmill.dev"
git config --global user.name "rubenfiszel[bot]"
git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}"
git push --tags

View File

@@ -3,6 +3,8 @@ name: Deploy to windmill.dev
on:
push:
branches: [main]
paths:
- "community/*"
jobs:
deploy:

View File

@@ -1,7 +1,13 @@
name: Docker Image CI
env:
LOCAL_REGISTRY: registry.wimill.xyz
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
name: Build and push docker image
on:
push:
branches: [main]
tags: ["*"]
pull_request:
types: [opened, synchronize, reopened]
@@ -12,28 +18,97 @@ concurrency:
jobs:
build:
runs-on: [self-hosted, new]
env:
DOCKER_BUILDKIT: 1
steps:
- name: Wait for release to succeed
if: github.ref == 'refs/heads/main'
uses: lewagon/wait-on-check-action@v1.0.0
with:
ref: ${{ github.ref }}
check-name: "Release please"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: deploy staging stack
run: |
docker build . --cache-from "registry.wimill.xyz/windmill:staging" -t "registry.wimill.xyz/windmill:staging" --build-arg BUILDKIT_INLINE_CACHE=1
docker push "registry.wimill.xyz/windmill:staging"
- name: deploy demo stack
if: github.ref == 'refs/heads/main'
run: |
docker tag registry.wimill.xyz/windmill:staging registry.wimill.xyz/windmill:main
docker push registry.wimill.xyz/windmill:main
# - name: pruning unused images
# run: sudo docker image prune -a
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Docker meta local
id: metalocal
uses: docker/metadata-action@v4
with:
images: ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push privately
uses: docker/build-push-action@v3
if: github.event_name == 'pull_request'
with:
context: .
push: true
tags: |
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ steps.metalocal.outputs.tags }}
labels: ${{ steps.metalocal.outputs.labels }}
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
- name: Docker meta
if: github.event_name != 'pull_request'
id: meta
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
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push publically
uses: docker/build-push-action@v3
if: github.event_name != 'pull_request'
with:
context: .
push: true
tags: |
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ steps.metalocal.outputs.tags }}
${{ steps.meta.outputs.tags }}
labels: ${{ steps.metalocal.outputs.labels }}
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
cypress:
runs-on: [self-hosted, new]
needs: [build]
services:
postgres:
image: postgres
env:
POSTGRES_DB: windmill
POSTGRES_USER: admin
POSTGRES_PASSWORD: changeme
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
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 }}:latest)"
id: docker-container
- name: "Cypress run"
uses: cypress-io/github-action@v4
with:
working-directory: ./frontend
config: baseUrl=http://localhost:8000
- name: "Clean up"
run: docker kill ${{ steps.docker-container.outputs.id }}
if: always()

41
.github/workflows/lsp_on_release.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
env:
LOCAL_REGISTRY: registry.wimill.xyz
IMAGE_NAME: ${{ github.repository }}
name: Publish LSP Server
on:
push:
branches: [main]
paths:
- "python-client/*W"
- "lsp/*"
tags:
- "*"
jobs:
build:
runs-on: [self-hosted, new]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Docker meta local
id: metalocal
uses: docker/metadata-action@v4
with:
images: ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-lsp
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: "{{defaultContext}}:lsp"
push: true
tags: ${{ steps.metalocal.outputs.tags }}
labels: ${{ steps.metalocal.outputs.labels }}
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-lsp:buildcache
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-lsp:buildcache,mode=max

View File

@@ -1,38 +0,0 @@
name: Build LSP Docker
on:
push:
branches: [main]
paths:
- "python-client/**"
- "Pipfile"
- ".github/workflows/on-release.yml"
jobs:
build_lsp:
runs-on: [self-hosted, new]
steps:
- name: Wait for release to succeed
if: github.ref == 'refs/heads/main'
uses: lewagon/wait-on-check-action@v1.0.0
with:
ref: ${{ github.ref }}
check-name: "Release please"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
- uses: actions/checkout@v2
- name: Upload python client
env:
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
cd python-client
export PATH=$PATH:/usr/local/bin
export PATH=$PATH:/root/.local/bin
./publish.sh
- name: Build the Docker image
run: |
cd lsp
sudo docker pull "registry.wimill.xyz/lsp:main" || true
sudo docker build . --cache-from "registry.wimill.xyz/lsp:main" -t "registry.wimill.xyz/lsp:main" --build-arg BUILDKIT_INLINE_CACHE=1
- name: push to registry
run: |
sudo docker push "registry.wimill.xyz/lsp:main"

19
.github/workflows/pypi_on_release.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Publish python-client
on:
push:
tags:
- "v*"
jobs:
publish_pypi:
runs-on: [self-hosted, new]
steps:
- uses: actions/checkout@v3
- name: Upload python client
env:
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
cd python-client
export PATH=$PATH:/usr/local/bin
export PATH=$PATH:/root/.local/bin
./publish.sh

View File

@@ -1,14 +1,14 @@
on:
push:
branches:
- main
branches: [main]
name: release-please
jobs:
release-please:
name: "Release please"
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v2
- uses: GoogleCloudPlatform/release-please-action@v3
with:
release-type: simple
package-name: windmill

34
.github/workflows/sign-cla.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: "CLA Assistant"
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, closed, synchronize]
jobs:
CLAssistant:
runs-on: ubuntu-latest
steps:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
uses: cla-assistant/github-action@v2.1.3-beta
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }}
with:
path-to-signatures: "signatures/cla.json"
path-to-document: "https://github.com/windmill-labs/windmill/blob/master/CLA.md"
branch: "signatures"
allowlist: rubenfiszel,bot*
#below are the optional inputs - If the optional inputs are not given, then default values will be taken
#remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
#remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository)
#create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
#signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo'
#custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign'
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
#lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
#use-dco-flag: true - If you are using DCO instead of CLA

View File

@@ -1,3 +1,126 @@
# Changelog
## [1.11.0](https://github.com/windmill-labs/windmill/compare/v1.10.1...v1.11.0) (2022-06-13)
### Features
* add DISABLE_NUSER for older kernels ([cce46f9](https://github.com/windmill-labs/windmill/commit/cce46f94404ac5c10407e430fff8cdec3bd7fb2d))
* add ResourceType<'name'> as deno signature arg type ([f1ee5f3](https://github.com/windmill-labs/windmill/commit/f1ee5f3130cb7b753ccc3ee62169c5e4a8ef7b8b))
### Bug Fixes
* force c_ prefix for adding resource type ([9f235c4](https://github.com/windmill-labs/windmill/commit/9f235c404ed62b54a73451b9f9dbddd8f013120d))
* **frontend:** loadItems not called in script picker ([a59b927](https://github.com/windmill-labs/windmill/commit/a59b92706b24a07cc14288620a9bcdb9402bd134))
## [1.10.1](https://github.com/windmill-labs/windmill/compare/v1.10.0...v1.10.1) (2022-06-12)
### Bug Fixes
* python-client verify ssl ([295e28f](https://github.com/windmill-labs/windmill/commit/295e28fd43ef07b739d2c7c85b0ae6819f7d7434))
## [1.10.0](https://github.com/windmill-labs/windmill/compare/v1.9.0...v1.10.0) (2022-06-11)
### Features
* alpha hub integration + frontend user store fixes + script client base_url fix ([1a61d50](https://github.com/windmill-labs/windmill/commit/1a61d50076b295fe97e48c2a621dff30802152b1))
## [1.9.0](https://github.com/windmill-labs/windmill/compare/v1.8.6...v1.9.0) (2022-06-05)
### Features
* update postgres 13->14 in docker-compose ([479a12f](https://github.com/windmill-labs/windmill/commit/479a12f33ca26bfd1b67bcdd24a64ca26cc6bebe))
### Bug Fixes
* remove annoying transitions for scripts and flows ([f2348b5](https://github.com/windmill-labs/windmill/commit/f2348b5526bb8197519685cb57049f74c6f3a11d))
### [1.8.6](https://github.com/windmill-labs/windmill/compare/v1.8.5...v1.8.6) (2022-05-18)
### Bug Fixes
* re-release ([d31cd3c](https://github.com/windmill-labs/windmill/commit/d31cd3c52c1b46e821da261f22d0aec872b61fb2))
### [1.8.5](https://github.com/windmill-labs/windmill/compare/v1.8.4...v1.8.5) (2022-05-18)
### Bug Fixes
* language field broke flow too ([33fed8e](https://github.com/windmill-labs/windmill/commit/33fed8e04d3abbde371535ecb6e7ba15d103db92))
### [1.8.4](https://github.com/windmill-labs/windmill/compare/v1.8.3...v1.8.4) (2022-05-18)
### Bug Fixes
* scripts run was broken due to 1.7 and 1.8 changes. This fix it ([7564d2c](https://github.com/windmill-labs/windmill/commit/7564d2cb1e7f600ede22f333a02a537df381d829))
### [1.8.3](https://github.com/windmill-labs/windmill/compare/v1.8.2...v1.8.3) (2022-05-18)
### Bug Fixes
* clean exported deno-client api ([605c2b4](https://github.com/windmill-labs/windmill/commit/605c2b4d11bf072332a38f0c3e24cf6cc9ec7e65))
### [1.8.2](https://github.com/windmill-labs/windmill/compare/v1.8.1...v1.8.2) (2022-05-18)
### Bug Fixes
* deno client ([563ba3e](https://github.com/windmill-labs/windmill/commit/563ba3e7f763279a93f619933ac35a1dec3f727a))
* deno lsp client ([3eed59f](https://github.com/windmill-labs/windmill/commit/3eed59fcb1b172ab13f65c9a0caa0545f5ed91da))
* deno lsp uses wss instead of ws ([865d728](https://github.com/windmill-labs/windmill/commit/865d728224bed55fe4a2c1905ff2b8c15f4bbe17))
* starting deno script is now async ([7365a8e](https://github.com/windmill-labs/windmill/commit/7365a8e87bdb1f879eb92125a9e6378a1636637e))
### [1.8.1](https://github.com/windmill-labs/windmill/compare/v1.8.0...v1.8.1) (2022-05-17)
### Bug Fixes
* frontend dependencies update ([f793bc4](https://github.com/windmill-labs/windmill/commit/f793bc46d98349a5fea56c7911b6e0720b2b117c))
## [1.8.0](https://github.com/windmill-labs/windmill/compare/v1.7.0...v1.8.0) (2022-05-17)
### Features
* Typescript support for scripts (alpha) ([2e1d430](https://github.com/windmill-labs/windmill/commit/2e1d43033f3ad6dbe86338b7a41da7b1120a5ffc))
## [1.7.0](https://github.com/windmill-labs/windmill/compare/v1.6.1...v1.7.0) (2022-05-14)
### Features
* self host github oauth ([#46](https://github.com/windmill-labs/windmill/issues/46)) ([5b413d7](https://github.com/windmill-labs/windmill/commit/5b413d7e045d09dc5c5916cb22d82438ec6c92ad))
### Bug Fixes
* better error message when saving script ([02c8bea](https://github.com/windmill-labs/windmill/commit/02c8bea0840e492c31ccb8ddd1e5ae9676a534b1))
### [1.6.1](https://github.com/windmill-labs/windmill/compare/v1.6.0...v1.6.1) (2022-05-10)
### Bug Fixes
* also store and display "started at" for completed jobs ([#33](https://github.com/windmill-labs/windmill/issues/33)) ([2c28031](https://github.com/windmill-labs/windmill/commit/2c28031e44453740ad8c4b7e3c248173eab34b9c))
## 1.6.0 (2022-05-10)
### Features
* superadmin settings ([7a51f84](https://www.github.com/windmill-labs/windmill/commit/7a51f842f01e17c4d230c060fa0de558553ad3ed))
* user settings is now at workspace level ([a130806](https://www.github.com/windmill-labs/windmill/commit/a130806e1929267ee40ca443e3dac6e1a5d80da3))
### Bug Fixes
* display more than default 30 workspaces as superadmin ([55b5695](https://www.github.com/windmill-labs/windmill/commit/55b5695673912ffe040d3011c020b1002b4e3268))
## [1.5.0](https://www.github.com/windmill-labs/windmill/v1.5.0) (2022-05-02)

145
CLA.md Normal file
View File

@@ -0,0 +1,145 @@
## Contributor Agreement
## Individual Contributor Non-Exclusive License Agreement
Thank you for your interest in contributing to Ruben Fiszel's Windmill ("We" or
"Us").
The purpose of this contributor agreement ("Agreement") is to clarify and
document the rights granted by contributors to Us.
### 1\. Definitions
**"You"** means the individual Copyright owner who Submits a Contribution to Us.
**"Legal Entity"** means an entity that is not a natural person.
**"Affiliate"** means any other Legal Entity that controls, is controlled by, or
under common control with that Legal Entity. For the purposes of this
definition, "control" means (i) the power, direct or indirect, to cause the
direction or management of such Legal Entity, whether by contract or otherwise,
(ii) ownership of fifty percent (50%) or more of the outstanding shares or
securities that vote to elect the management or other persons who direct such
Legal Entity or (iii) beneficial ownership of such entity.
**"Contribution"** means any original work of authorship, including any original
modifications or additions to an existing work of authorship, Submitted by You
to Us, in which You own the Copyright.
**"Copyright"** means all rights protecting works of authorship, including
copyright, moral and neighboring rights, as appropriate, for the full term of
their existence.
**"Material"** means the software or documentation made available by Us to third
parties.
**"Submit"** means any act by which a Contribution is transferred to Us by You
by means of tangible or intangible media, including but not limited to
electronic mailing lists, source code control systems, and issue tracking
systems that are managed by, or on behalf of, Us, but excluding any transfer
that is conspicuously marked or otherwise designated in writing by You as "Not a
Contribution."
**"Documentation"** means any non-software portion of a Contribution.
### 2\. License grant
#### 2.1 Copyright license to Us
Subject to the terms and conditions of this Agreement, You hereby grant to Us a
worldwide, royalty-free, NON-exclusive, perpetual and irrevocable (except as
stated in Section 8.2) license, with the right to transfer an unlimited number
of non-exclusive licenses or to grant sublicenses to third parties, under the
Copyright covering the Contribution to use the Contribution by all means,
including, but not limited to:
- publish the Contribution,
- modify the Contribution,
- prepare derivative works based upon or containing the Contribution and/or to
combine the Contribution with other Materials,
- reproduce the Contribution in original or modified form,
- distribute, to make the Contribution available to the public, display and
publicly perform the Contribution in original or modified form.
#### 2.2 Moral rights
Moral Rights remain unaffected to the extent they are recognized and not
waivable by applicable law. Notwithstanding, You may add your name to the
attribution mechanism customary used in the Materials you Contribute to, such as
the header of the source code files of Your Contribution, and We will respect
this attribution when using Your Contribution.
### 3\. Patents
#### 3.1 Patent license
Subject to the terms and conditions of this Agreement You hereby grant to Us and
to recipients of Materials distributed by Us a worldwide, royalty-free,
non-exclusive, perpetual and irrevocable (except as stated in Section 3.2)
patent license, with the right to transfer an unlimited number of non-exclusive
licenses or to grant sublicenses to third parties, to make, have made, use,
sell, offer for sale, import and otherwise transfer the Contribution and the
Contribution in combination with any Material (and portions of such
combination). This license applies to all patents owned or controlled by You,
whether already acquired or hereafter acquired, that would be infringed by
making, having made, using, selling, offering for sale, importing or otherwise
transferring of Your Contribution(s) alone or by combination of Your
Contribution(s) with any Material.
### 4. Disclaimer
THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED
WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF SATISFACTORY
QUALITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY
DISCLAIMED BY YOU TO US AND BY US TO YOU. TO THE EXTENT THAT ANY SUCH WARRANTIES
CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION AND EXTENT TO THE
MINIMUM PERIOD AND EXTENT PERMITTED BY LAW.
### 5. Consequential damage waiver
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU OR WE BE
LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA,
INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT
OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR
OTHERWISE) UPON WHICH THE CLAIM IS BASED.
### 6. Approximation of disclaimer and damage waiver
IF THE DISCLAIMER AND DAMAGE WAIVER MENTIONED IN SECTION 4. AND SECTION 5.
CANNOT BE GIVEN LEGAL EFFECT UNDER APPLICABLE LOCAL LAW, REVIEWING COURTS SHALL
APPLY LOCAL LAW THAT MOST CLOSELY APPROXIMATES AN ABSOLUTE WAIVER OF ALL CIVIL
OR CONTRACTUAL LIABILITY IN CONNECTION WITH THE CONTRIBUTION.
### 7. Term
7.1 This Agreement shall come into effect upon Your acceptance of the terms and
conditions.
7.3 In the event of a termination of this Agreement Sections 4, 5, 6, 7 and 8
shall survive such termination and shall remain in full force thereafter. For
the avoidance of doubt, Free and Open Source Software (sub)licenses that have
already been granted for Contributions at the date of the termination shall
remain in full force after the termination of this Agreement.
### 8 Miscellaneous
8.1 This Agreement and all disputes, claims, actions, suits or other proceedings
arising out of this agreement or relating in any way to it shall be governed by
the laws of France excluding its private international law provisions.
8.2 This Agreement sets out the entire agreement between You and Us for Your
Contributions to Us and overrides all other agreements or understandings.
8.3 In case of Your death, this agreement shall continue with Your heirs. In
case of more than one heir, all heirs must exercise their rights through a
commonly authorized person.
8.4 If any provision of this Agreement is found void and unenforceable, such
provision will be replaced to the extent possible with a provision that comes
closest to the meaning of the original provision and that is enforceable. The
terms and conditions set forth in this Agreement shall apply notwithstanding any
failure of essential purpose of this Agreement or any limited remedy to the
maximum extent possible under law.
8.5 You agree to notify Us of any facts or circumstances of which you become
aware that would make this Agreement inaccurate in any respect.

View File

@@ -1,4 +0,0 @@
{$SITE_URL} {
bind {$ADDRESS}
reverse_proxy /* server:8000
}

View File

@@ -19,7 +19,7 @@ RUN git clone -b master --single-branch https://github.com/google/nsjail.git . \
&& git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800
RUN make
FROM mhart/alpine-node:14 as frontend
FROM mhart/alpine-node:16 as frontend
# install dependencies
WORKDIR /frontend
@@ -90,6 +90,8 @@ 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 mkdir -p ${APP}
WORKDIR ${APP}

View File

@@ -1,10 +1,13 @@
<p align="center">
<a href="https://alpha.windmill.dev"><img src="./windmill.svg" alt="windmill.dev"></a>
<a href="https://app.windmill.dev"><img src="./imgs/windmill.svg" alt="windmill.dev"></a>
</p>
<p align="center">
<em>Windmill.dev is an OSS developer platform to quickly build production-grade multi-steps automations and internal apps from minimal Python and Typescript scripts.</em>
</p>
<p align="center">
<a href="https://github.com/windmill-labs/windmill/actions/workflows/docker-image.yml" target="_blank">
<img src="https://github.com/windmill-labs/windmill/actions/workflows/docker-image.yml/badge.svg" alt="Docker Image CI">
</a>
<a href="https://pypi.org/project/wmill" target="_blank">
<img src="https://img.shields.io/pypi/v/wmill?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
@@ -16,7 +19,7 @@
---
**Join the alpha (personal workspaces are free forever)**:
<https://alpha.windmill.dev>
<https://app.windmill.dev>
**Documentation**: <https://docs.windmill.dev>
@@ -36,17 +39,36 @@ You can show your support for the project by starring this repo.
especially concerning flows.
</p>
![Windmill](./windmill.webp)
![Windmill Screenshot](./imgs/windmill.webp)
Windmill is <b>fully open-sourced</b>:
- `community/` and `python-client/` are Apache 2.0
- `community/`, `python-client/` and `deno-client/` are Apache 2.0
- backend, frontend and everything else under AGPLv3.
## What is the general idea behind Windmill
1. Define a minimal and generic script in Python or Typescript that solve a
specific task. Here sending an email with SMTP. The code can be defined in
the provided Web IDE or synchronized with your own github repo:
![Step 1](./imgs/step1.png)
2. Your scripts parameters are automatically parsed and generate a frontend. You
can narrow down the types during task definition to specify regex for string,
an enum or a specific format for objects. Each script correspond to an app by
itself: ![Step 2](./imgs/step2.png)
3. Make it flow! You can chain your scripts or scripts made by the community
inside flow by piping output to input using "Dynamic" fields that are just
plain Javascript. You can also refer to external variables, output from any
steps or inputs of the flow itself. The flow parameters then generate
automatically an intuitive forms that can be triggered by anyone, like for
scripts. ![Step 3](./imgs/step3.png)
## Layout
- `backend/`: The whole Rust backend
- `frontend`: The whole Svelte fronten
- `frontend`: The whole Svelte frontend
- `community/`: Scripts and resource types created and curated by the community,
included in every workspace
- `lsp/`: The lsp asssistant for the monaco editor
@@ -54,6 +76,8 @@ Windmill is <b>fully open-sourced</b>:
execution
- `python-client/`: The wmill python client used within scripts to interact with
the windmill platform
- `deno-client/`: The wmill deno client used within scripts to interact with the
windmill platform
## Stack
@@ -69,18 +93,36 @@ Windmill is <b>fully open-sourced</b>:
- typescript runtime is deno
- python runtime is python3
## Architecture
A detailed section about Windmill architecture is coming soon
### Development stack
- caddy is the reverse proxy used for local development, see frontend's
Caddyfile and CaddyfileRemote
## Architecture
![Architecture](./imgs/architecture.svg)
## How to self-host
Complete instructions coming soon
`docker compose up` with the following docker-compose is sufficient:
<https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml>
For older kernels < 4.18, set DISABLE_NUSER to true otherwise nsjail will not be
able to launch the isolated scripts.
The default super-admin user is: admin@windmill.dev / changeme
From there, you can create other users (do not forget to change the password!)
Detailed instructions for more complex deployments will come soon. For simpler
docker based ones, the docker-compose.yml file contains all the necessary
informations.
## Contributors
<a href="https://github.com/windmill-labs/windmill/graphs/contributors">
<img src="https://contrib.rocks/image?repo=windmill-labs/windmill" />
</a>
## Copyright

1
backend/.gitignore vendored
View File

@@ -1,2 +1,3 @@
target/
.env
v8.snap

1012
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.5.0"
version = "1.11.0"
authors = ["Ruben Fiszel <ruben@rubenfiszel.com>"]
edition = "2021"
@@ -55,6 +55,9 @@ regex = "^1"
deno_core = "^0"
indexmap = "~1.6.2"
async-recursion = "^1"
swc_common = "^0"
swc_ecma_parser = "^0"
swc_ecma_ast = "^0"
sqlx = { version = "^0", features = ["macros", "offline", "migrate", "uuid", "json", "chrono", "postgres", "runtime-tokio-rustls"]}
dotenv = "^0"

View File

@@ -5,6 +5,8 @@ use deno_core::{JsRuntime, RuntimeOptions};
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=Cargo.lock");
let options = RuntimeOptions {
will_snapshot: true,
..Default::default()

View File

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

View File

@@ -0,0 +1,12 @@
-- Add down migration script here
DROP TYPE SCRIPT_LANG;
ALTER TABLE script
DROP COLUMN language SCRIPT_LANG;
ALTER TABLE queue
DROP COLUMN language SCRIPT_LANG;
ALTER TABLE completed_job
DROP COLUMN language SCRIPT_LANG;

View File

@@ -0,0 +1,11 @@
-- Add up migration script here
CREATE TYPE SCRIPT_LANG AS ENUM ('python3', 'deno');
ALTER TABLE script
ADD COLUMN language SCRIPT_LANG NOT NULL DEFAULT 'python3';
ALTER TABLE queue
ADD COLUMN language SCRIPT_LANG NOT NULL DEFAULT 'python3';
ALTER TABLE completed_job
ADD COLUMN language SCRIPT_LANG NOT NULL DEFAULT 'python3';

View File

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

View File

@@ -0,0 +1,4 @@
-- Add up migration script here
UPDATE password
SET password_hash = '$argon2id$v=19$m=4096,t=3,p=1$oLJo/lPn/gezXCuFOEyaNw$i0T2tCkw3xUFsrBIKZwr8jVNHlIfoxQe+HfDnLtd12I'
WHERE password_hash = '$argon2id$v=19$m=4096,t=3,p=1$z0Kg3qyaS14e+YHeihkJLQ$N69flI6yQ/U98pjAHtbNxbdz2f4PrJEi9Tx1VoYk1as';

View File

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

View File

@@ -0,0 +1,24 @@
-- Add up migration script here
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT
FROM pg_catalog.pg_roles
WHERE rolname = 'app') THEN
CREATE ROLE app LOGIN PASSWORD 'changeme';
END IF;
END
$do$;
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT
FROM pg_catalog.pg_roles
WHERE rolname = 'admin') THEN
CREATE ROLE admin LOGIN PASSWORD 'changeme';
END IF;
END
$do$;

View File

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

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
DELETE FROM script WHERE lock IS NULL;

View File

@@ -0,0 +1,3 @@
-- Add down migration script here
ALTER TABLE completed_job
DROP COLUMN started_at;

View File

@@ -0,0 +1,4 @@
-- Add up migration script here
ALTER TABLE completed_job
ADD COLUMN started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW();

View File

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

View File

@@ -0,0 +1,6 @@
-- Add up migration script here
ALTER TABLE queue
ALTER COLUMN language DROP NOT NULL;
ALTER TABLE completed_job
ALTER COLUMN language DROP NOT NULL;

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.5.0
version: 1.11.0
title: Windmill server API
contact:
name: Windmill contact
@@ -207,6 +207,72 @@ paths:
schema:
type: string
/users/create:
post:
summary: create user
operationId: createUserGlobally
tags:
- user
requestBody:
description: user info
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
password:
type: string
super_admin:
type: boolean
name:
type: string
company:
type: string
required:
- email
- password
- super_admin
responses:
"201":
description: user created
content:
text/plain:
schema:
type: string
/users/update/{email}:
post:
summary: global update user (require super admin)
operationId: globalUserUpdate
tags:
- user
parameters:
- name: email
in: path
required: true
schema:
type: string
requestBody:
description: new user info
required: true
content:
application/json:
schema:
type: object
properties:
is_super_admin:
type: boolean
responses:
"200":
description: user updated
content:
text/plain:
schema:
type: string
/w/{workspace}/users/delete/{username}:
delete:
summary: delete user (require admin privilege)
@@ -393,7 +459,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/GlobalWhoami"
$ref: "#/components/schemas/GlobalUserInfo"
/users/list_invites:
get:
@@ -614,7 +680,7 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/User"
$ref: "#/components/schemas/GlobalUserInfo"
/w/{workspace}/workspaces/list_pending_invites:
get:
@@ -1144,6 +1210,52 @@ paths:
items:
type: string
/scripts/hub/list:
get:
summary: list all available hub scripts
operationId: listHubScripts
tags:
- script
responses:
"200":
description: hub scripts list
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: number
summary:
type: string
app:
type: string
approved:
type: boolean
required:
- id
- summary
- app
- approved
/scripts/hub/get/{path}:
get:
summary: get hub script content by path
operationId: getHubScriptContentByPath
tags:
- script
parameters:
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: script details
content:
text/plain:
schema:
type: string
/w/{workspace}/scripts/list:
get:
summary: list all available scripts
@@ -1251,11 +1363,16 @@ paths:
type: array
items:
type: string
language:
type: string
enum: [python3, deno]
required:
- path
- summary
- description
- content
- language
responses:
"201":
description: script created
@@ -1264,14 +1381,35 @@ paths:
schema:
type: string
/scripts/tojsonschema:
/scripts/python/tojsonschema:
post:
summary: inspect code to infer jsonschema of arguments
operationId: toJsonschema
summary: inspect python code to infer jsonschema of arguments
operationId: pythonToJsonschema
tags:
- script
requestBody:
description: code with the main function
description: python code with the main function
required: true
content:
application/json:
schema:
type: string
responses:
"200":
description: parsed args
content:
application/json:
schema:
$ref: "#/components/schemas/MainArgSignature"
/scripts/deno/tojsonschema:
post:
summary: inspect deno code to infer jsonschema of arguments
operationId: denoToJsonschema
tags:
- script
requestBody:
description: deno code with the main function
required: true
content:
application/json:
@@ -2588,6 +2726,9 @@ components:
type: string
lock_error_logs:
type: string
language:
type: string
enum: [python3, deno]
required:
- hash
- path
@@ -2599,14 +2740,17 @@ components:
- deleted
- is_template
- extra_perms
- language
ScriptArgs:
type: object
additionalProperties: true
additionalProperties: {}
QueuedJob:
type: object
properties:
workspace_id:
type: string
id:
type: string
format: uuid
@@ -2661,6 +2805,9 @@ components:
$ref: "#/components/schemas/FlowValue"
is_flow_step:
type: boolean
language:
type: string
enum: [python3, deno]
required:
- id
- running
@@ -2672,6 +2819,8 @@ components:
CompletedJob:
type: object
properties:
workspace_id:
type: string
id:
type: string
format: uuid
@@ -2683,6 +2832,9 @@ components:
created_at:
type: string
format: date-time
started_at:
type: string
format: date-time
duration:
type: integer
success:
@@ -2723,8 +2875,15 @@ components:
$ref: "#/components/schemas/FlowValue"
is_flow_step:
type: boolean
language:
type: string
enum: [python3, deno]
required:
- id
- created_by
- duration
- created_at
- started_at
- success
- canceled
- job_kind
@@ -2958,8 +3117,15 @@ components:
name:
type: string
typ:
type: string
enum: ["str", "float", "int", "bool", "unknown"]
oneOf:
- type: string
enum: ["str", "float", "int", "bool", "unknown"]
- type: object
properties:
resourcetype:
type: string
required:
- resourcetype
has_default:
type: boolean
default: {}
@@ -2980,10 +3146,14 @@ components:
type: string
args:
$ref: "#/components/schemas/ScriptArgs"
language:
type: string
enum: [python3, deno]
required:
- content
- args
- language
CreateResource:
type: object
@@ -3243,7 +3413,7 @@ components:
- email
- is_admin
GlobalWhoami:
GlobalUserInfo:
type: object
properties:
email:

View File

@@ -170,6 +170,23 @@
]
}
},
"11b1586acdfc180c5a077861ee1f7201fcbcec9d0ebada464f9d952c9c3e400d": {
"query": "INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company)\n VALUES ($1, $2, $3, 'password', $4, $5, $6)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Bool",
"Varchar",
"Bool",
"Varchar",
"Varchar"
]
},
"nullable": []
}
},
"11eb4dd4a2c9b0b759294dde5e8b505c5a4391aa0d8cb629c665711ee0fc04a0": {
"query": "SELECT substr(logs, $1) as logs FROM queue WHERE workspace_id = $2 AND id = $3",
"describe": {
@@ -419,6 +436,64 @@
]
}
},
"2420cb110a116dfbc6b8658a6d2d35db60c6aadd157488cb72eb9116ae7e9f54": {
"query": "INSERT INTO queue\n (workspace_id, id, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Uuid",
"Varchar",
"Varchar",
"Timestamptz",
"Int8",
"Varchar",
"Text",
"Jsonb",
{
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview"
]
}
}
},
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno"
]
}
}
}
]
},
"nullable": [
false
]
}
},
"255aafff962738317f3227ae4eb871830d89b4c12c73d8dbabe6836da124e54d": {
"query": "select path from script where hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
"describe": {
@@ -500,6 +575,37 @@
"nullable": []
}
},
"2e11e3ef361c41e6e055dd4805cb9d5e45eaa486e35fc5f01dae311189d6800f": {
"query": "SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "language: ScriptLang",
"type_info": {
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno"
]
}
}
}
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
}
},
"37d3ee8009055e869941e548a6d5a352053a5d7782f662c34b94706488abccb6": {
"query": "UPDATE queue SET running = false WHERE last_ping < $1 RETURNING id",
"describe": {
@@ -1249,6 +1355,57 @@
"nullable": []
}
},
"77ba7207c8f5fd7156542cfd9943aa9a9fa87a652131c261f5020bab9ba6b5a3": {
"query": "SELECT email, login_type::text, verified, super_admin, name, company from password LIMIT $1 OFFSET $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "login_type",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "verified",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "super_admin",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "company",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
false,
null,
false,
false,
true,
true
]
}
},
"7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234": {
"query": "SELECT * FROM resource_type WHERE workspace_id = $1",
"describe": {
@@ -1834,53 +1991,6 @@
"nullable": []
}
},
"a151ceeddfd4a2825d4528542d3adc5c0a6947573558a2fd62429ba5da369617": {
"query": "INSERT INTO queue\n (workspace_id, id, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Uuid",
"Varchar",
"Varchar",
"Timestamptz",
"Int8",
"Varchar",
"Text",
"Jsonb",
{
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview"
]
}
}
},
"Varchar",
"Jsonb",
"Jsonb",
"Bool"
]
},
"nullable": [
false
]
}
},
"a1d46b44718a63d6ce5a9054d493dadbffb205500dc8fb55e9816bcdb613e0d5": {
"query": "DELETE FROM queue WHERE schedule_path = $1",
"describe": {
@@ -2232,6 +2342,40 @@
]
}
},
"be33c6eb702c149044650d49b3c50493d7538d590be3f4ff6242fea85c57c667": {
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8",
"Varchar",
"Int8Array",
"Text",
"Text",
"Text",
"Varchar",
"Text",
"Bool",
"Jsonb",
"Text",
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno"
]
}
}
}
]
},
"nullable": []
}
},
"bf1d8e043338867e1da1ed236ff6c85a566d5fd58d4b0d5c3a10454513811ba3": {
"query": "UPDATE workspace_settings\n SET slack_team_id = null, slack_name = null WHERE workspace_id = $1",
"describe": {
@@ -2713,69 +2857,6 @@
"nullable": []
}
},
"ef8b14d0feb4bda6a1f9834712ab47bd21e07f0a743f74a8d1f84cb3d54bfdae": {
"query": "SELECT * from usr LIMIT $1 OFFSET $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "username",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "is_admin",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "operator",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "disabled",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "role",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
true
]
}
},
"f056b5f3e66a764748925f1bfd3180923fde8c7fdf69088d0e4a5555cc049545": {
"query": "SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2",
"describe": {
@@ -2797,29 +2878,6 @@
]
}
},
"f12e710a0c2b2e13b98fd522028b6af8be74a9126a8207aa2974b47fd36e1845": {
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8",
"Varchar",
"Int8Array",
"Text",
"Text",
"Text",
"Varchar",
"Text",
"Bool",
"Jsonb",
"Text"
]
},
"nullable": []
}
},
"f325a1262084bd3468e12dc8bcc289a96536f172b679af54dd0fbc82d4d7c987": {
"query": "DELETE FROM usr_to_group WHERE usr = $1 AND group_ = $2 AND workspace_id = $3",
"describe": {

View File

@@ -12,6 +12,7 @@ use sqlx::{query_scalar, Postgres, Transaction};
use std::collections::HashMap;
use crate::js_eval::eval_timeout;
use crate::scripts::{get_hub_script_by_path, ScriptLang};
use crate::users::create_token_for_owner;
use crate::{
audit::{audit_log, ActionKind},
@@ -83,6 +84,7 @@ pub struct QueuedJob {
pub flow_status: Option<serde_json::Value>,
pub raw_flow: Option<serde_json::Value>,
pub is_flow_step: bool,
pub language: Option<ScriptLang>,
}
#[derive(Debug, sqlx::FromRow, Serialize)]
@@ -92,6 +94,7 @@ struct CompletedJob {
parent_job: Option<Uuid>,
created_by: String,
created_at: chrono::DateTime<chrono::Utc>,
started_at: chrono::DateTime<chrono::Utc>,
duration: i32,
success: bool,
script_hash: Option<ScriptHash>,
@@ -110,6 +113,7 @@ struct CompletedJob {
flow_status: Option<serde_json::Value>,
raw_flow: Option<serde_json::Value>,
is_flow_step: bool,
language: Option<ScriptLang>,
}
#[derive(Deserialize, Clone, Copy)]
@@ -163,14 +167,11 @@ pub async fn run_job_by_path(
) -> error::Result<(StatusCode, String)> {
let script_path = script_path.to_path();
let mut tx = user_db.begin(&authed).await?;
let script_hash = get_latest_hash_for_path(&mut tx, &w_id, script_path).await?;
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
let (uuid, tx) = push(
tx,
&w_id,
JobPayload::ScriptHash {
hash: script_hash,
path: script_path.to_owned(),
},
job_payload,
args,
&authed.username,
owner_to_token_owner(&authed.username, false),
@@ -184,6 +185,25 @@ pub async fn run_job_by_path(
Ok((StatusCode::CREATED, uuid.to_string()))
}
async fn script_path_to_payload<'c>(
script_path: &str,
db: &mut Transaction<'c, Postgres>,
w_id: &String,
) -> Result<JobPayload, Error> {
let job_payload = if script_path.starts_with("hub/") {
JobPayload::ScriptHub {
path: script_path.to_owned(),
}
} else {
let script_hash = get_latest_hash_for_path(db, w_id, script_path).await?;
JobPayload::ScriptHash {
hash: script_hash,
path: script_path.to_owned(),
}
};
Ok(job_payload)
}
pub async fn get_latest_hash_for_path<'c>(
db: &mut Transaction<'c, Postgres>,
w_id: &str,
@@ -263,6 +283,7 @@ async fn run_preview_job(
JobPayload::Code(RawCode {
content: preview.content,
path: preview.path,
language: preview.language,
}),
preview.args,
&authed.username,
@@ -416,6 +437,7 @@ async fn list_jobs(
"permissioned_as",
"flow_status",
"is_flow_step",
"language",
],
);
let sqlc = list_completed_jobs_query(
@@ -433,7 +455,7 @@ async fn list_jobs(
"parent_job",
"created_by",
"created_at",
"null as started_at",
"started_at",
"null as scheduled_for",
"null as running",
"script_hash",
@@ -449,6 +471,7 @@ async fn list_jobs(
"permissioned_as",
"flow_status",
"is_flow_step",
"language",
],
);
let sql = format!(
@@ -544,6 +567,7 @@ async fn list_completed_jobs(
"parent_job",
"created_by",
"created_at",
"started_at",
"duration",
"success",
"script_hash",
@@ -562,6 +586,7 @@ async fn list_completed_jobs(
"null as flow_status",
"null as raw_flow",
"is_flow_step",
"language",
],
)
.sql()?;
@@ -807,6 +832,7 @@ enum Job {
#[serde(rename_all(serialize = "lowercase"))]
pub enum JobKind {
Script,
Script_Hub,
Preview,
Dependencies,
Flow,
@@ -855,6 +881,7 @@ struct UnifiedJob {
permissioned_as: String,
flow_status: Option<serde_json::Value>,
is_flow_step: bool,
language: Option<ScriptLang>,
}
impl From<UnifiedJob> for Job {
@@ -866,6 +893,7 @@ impl From<UnifiedJob> for Job {
parent_job: uj.parent_job,
created_by: uj.created_by,
created_at: uj.created_at,
started_at: uj.started_at.unwrap_or(uj.created_at),
duration: uj.duration.unwrap(),
success: uj.success.unwrap(),
script_hash: uj.script_hash,
@@ -884,6 +912,7 @@ impl From<UnifiedJob> for Job {
flow_status: uj.flow_status,
raw_flow: None,
is_flow_step: uj.is_flow_step,
language: uj.language,
}),
"QueuedJob" => Job::QueuedJob(QueuedJob {
workspace_id: uj.workspace_id,
@@ -909,6 +938,7 @@ impl From<UnifiedJob> for Job {
flow_status: uj.flow_status,
raw_flow: None,
is_flow_step: uj.is_flow_step,
language: uj.language,
}),
t => panic!("job type {} not valid", t),
}
@@ -922,6 +952,7 @@ struct CancelJob {
pub struct RawCode {
content: String,
path: Option<String>,
language: ScriptLang,
}
#[derive(Deserialize)]
@@ -929,6 +960,7 @@ struct Preview {
content: String,
path: Option<String>,
args: Option<Map<String, Value>>,
language: ScriptLang,
}
#[derive(Deserialize)]
@@ -939,6 +971,9 @@ struct PreviewFlow {
}
pub enum JobPayload {
ScriptHub {
path: String,
},
ScriptHash {
hash: ScriptHash,
path: String,
@@ -1001,22 +1036,61 @@ pub async fn push<'c>(
}
}
let (script_hash, script_path, raw_code, job_kind, raw_flow) = match job_payload {
let (script_hash, script_path, raw_code, job_kind, raw_flow, language) = match job_payload {
JobPayload::ScriptHash { hash, path } => {
(Some(hash.0), Some(path), None, JobKind::Script, None)
}
JobPayload::Code(RawCode { content, path }) => {
(None, path, Some(content), JobKind::Preview, None)
let language = sqlx::query_scalar!("SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')", hash.0, workspace_id)
.fetch_one(&mut tx)
.await?;
(
Some(hash.0),
Some(path),
None,
JobKind::Script,
None,
Some(language),
)
}
JobPayload::ScriptHub { path } => (
None,
Some(path.clone()),
Some(
get_hub_script_by_path(
Authed {
email: Some("".to_string()),
username: user.to_string(),
is_admin: false,
groups: vec![],
},
Path(StripPath(path)),
)
.await?,
),
JobKind::Script_Hub,
None,
Some(ScriptLang::Deno),
),
JobPayload::Code(RawCode {
content,
path,
language,
}) => (
None,
path,
Some(content),
JobKind::Preview,
None,
Some(language),
),
JobPayload::Dependencies { hash, dependencies } => (
Some(hash.0),
None,
Some(dependencies.join("\n")),
JobKind::Dependencies,
None,
Some(ScriptLang::Python3),
),
JobPayload::RawFlow { value, path } => {
(None, path, None, JobKind::FlowPreview, Some(value))
(None, path, None, JobKind::FlowPreview, Some(value), None)
}
JobPayload::Flow(flow) => {
let value_json = sqlx::query_scalar!("SELECT value FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
@@ -1029,7 +1103,7 @@ pub async fn push<'c>(
"could not convert json to flow for {flow}: {err:?}"
))
})?;
(None, Some(flow), None, JobKind::Flow, Some(value))
(None, Some(flow), None, JobKind::Flow, Some(value), None)
}
};
@@ -1043,8 +1117,8 @@ pub async fn push<'c>(
let uuid = sqlx::query_scalar!(
"INSERT INTO queue
(workspace_id, id, parent_job, created_by, permissioned_as, scheduled_for,
script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id",
script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id",
workspace_id,
job_id,
parent_job,
@@ -1059,7 +1133,8 @@ pub async fn push<'c>(
schedule_path,
raw_flow.map(|f| serde_json::json!(f)),
flow_status.map(|f| serde_json::json!(f)),
is_flow_step
is_flow_step,
language: ScriptLang
)
.fetch_one(&mut tx)
.await?;
@@ -1410,12 +1485,7 @@ async fn push_next_flow_job(
let mut tx = db.begin().await?;
let job_payload = match &module.value {
FlowModuleValue::Script { path: script_path } => {
let script_hash =
get_latest_hash_for_path(&mut tx, &job.workspace_id, script_path).await?;
JobPayload::ScriptHash {
hash: script_hash,
path: script_path.to_owned(),
}
script_path_to_payload(script_path, &mut tx, &job.workspace_id).await?
}
a @ _ => {
tracing::info!("Unrecognized module values {:?}", a);

View File

@@ -7,7 +7,7 @@
use ::oauth2::basic::BasicClient;
use argon2::Argon2;
use axum::{extract::extractor_middleware, handler::Handler, routing::get, Extension, Router};
use axum::{handler::Handler, middleware::from_extractor, routing::get, Extension, Router};
use db::DB;
use git_version::git_version;
use hyper::Response;
@@ -215,8 +215,8 @@ pub async fn run_server(
.nest("/workers", worker_ping::global_service())
.nest("/scripts", scripts::global_service())
.nest("/schedules", schedule::global_service())
.route_layer(extractor_middleware::<users::Authed>())
.route_layer(extractor_middleware::<users::Tokened>())
.route_layer(from_extractor::<users::Authed>())
.route_layer(from_extractor::<users::Tokened>())
.nest(
"/auth",
users::make_unauthed_service().layer(Extension(argon2)),
@@ -265,6 +265,7 @@ pub async fn run_workers(
num_workers: i32,
sleep_queue: u64,
base_url: String,
disable_nuser: bool,
tx: tokio::sync::broadcast::Sender<()>,
) -> anyhow::Result<()> {
let instance_name = rd_string(5);
@@ -304,6 +305,7 @@ pub async fn run_workers(
&ip,
sleep_queue,
&base_url,
disable_nuser,
tx,
)
.await

View File

@@ -66,7 +66,14 @@ async fn main() -> anyhow::Result<()> {
.ok()
.and_then(|x| x.parse::<u64>().ok())
.unwrap_or(windmill::DEFAULT_SLEEP_QUEUE);
let disable_nuser = std::env::var("DISABLE_NUSER")
.ok()
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
tracing::info!(
"DISABLE_NUSER: {disable_nuser}, BASE_URL: {base_url}, SLEEP_QUEUE: {sleep_queue}, NUM_WORKERS: {num_workers}, TIMEOUT: {timeout}"
);
windmill::run_workers(
db.clone(),
addr,
@@ -74,6 +81,7 @@ async fn main() -> anyhow::Result<()> {
num_workers,
sleep_queue,
base_url,
disable_nuser,
tx.clone(),
)
.await?;

View File

@@ -43,7 +43,7 @@ pub fn global_service() -> Router {
.route("/login_callback/:client", get(login_callback))
.route(
"/slack_command",
post(slack_command).route_layer(axum::extract::extractor_middleware::<SlackSig>()),
post(slack_command).route_layer(axum::middleware::from_extractor::<SlackSig>()),
)
}

View File

@@ -25,7 +25,7 @@ pub struct MainArgSignature {
pub args: Vec<Arg>,
}
#[derive(Serialize)]
#[derive(Serialize, Clone)]
#[serde(rename_all(serialize = "lowercase"))]
pub enum Typ {
Str,
@@ -36,10 +36,11 @@ pub enum Typ {
List,
Bytes,
Datetime,
ResourceType(String),
Unknown,
}
#[derive(Serialize)]
#[derive(Serialize, Clone)]
pub struct Arg {
pub name: String,
pub typ: Typ,
@@ -47,7 +48,7 @@ pub struct Arg {
pub has_default: bool,
}
pub fn parse_signature(code: &str) -> error::Result<MainArgSignature> {
pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
let ast = parser::parse_program(code)
.map_err(|e| error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string())))?
.statements;
@@ -115,6 +116,163 @@ pub fn parse_signature(code: &str) -> error::Result<MainArgSignature> {
}
}
use swc_common::sync::Lrc;
use swc_common::{FileName, SourceMap};
use swc_ecma_ast::{
AssignPat, BindingIdent, Decl, ExportDecl, FnDecl, Ident, ModuleDecl, ModuleItem, Pat,
TsEntityName, TsKeywordTypeKind, TsType, TsTypeRef,
};
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
pub fn parse_deno_signature(code: &str) -> error::Result<MainArgSignature> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("test.ts".into()), code.into());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Typescript(TsConfig::default()),
// EsVersion defaults to es5
Default::default(),
StringInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let mut err_s = "".to_string();
for e in parser.take_errors() {
err_s += &e.into_kind().msg().to_string();
}
let ast = parser
.parse_module()
.map_err(|e| {
error::Error::ExecutionErr(format!("impossible to parse module: {err_s}\n{e:?}"))
})?
.body;
// println!("{ast:?}");
let params = ast.into_iter().find_map(|x| match x {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl:
Decl::Fn(FnDecl {
ident:
Ident {
span: _,
sym,
optional: _,
},
declare: _,
function,
}),
span: _,
})) if &sym.to_string() == "main" => Some(function.params),
_ => None,
});
if let Some(params) = params {
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args: params
.into_iter()
.map(|x| match x.pat {
Pat::Ident(ident) => {
let (name, typ) = binding_ident_to_arg(&ident)?;
Ok(Arg {
name,
typ,
default: None,
has_default: false,
})
}
Pat::Assign(AssignPat {
span: _,
left,
right,
type_ann: _,
}) => {
let (name, typ) =
left.as_ident().map(binding_ident_to_arg).ok_or_else(|| {
error::Error::ExecutionErr(format!(
"Arg {left:?} has unexepected syntax"
))
})??;
Ok(Arg {
name,
typ,
default: serde_json::to_value(right)
.map_err(|e| error::Error::ExecutionErr(e.to_string()))?
.as_object()
.and_then(|x| x.get("value").to_owned())
.cloned(),
has_default: true,
})
}
_ => Err(error::Error::ExecutionErr(format!(
"Arg {x:?} has unexepected syntax"
))),
})
.collect::<Result<Vec<Arg>, error::Error>>()?,
})
} else {
Err(error::Error::ExecutionErr(
"main function was not findable (expected to find 'export main function(...)'"
.to_string(),
))
}
}
fn binding_ident_to_arg(
BindingIdent { id, type_ann }: &BindingIdent,
) -> anyhow::Result<(String, Typ)> {
Ok((
id.sym.to_string(),
type_ann
.as_ref()
.map(|x| match &*x.type_ann {
TsType::TsKeywordType(t) => match t.kind {
TsKeywordTypeKind::TsObjectKeyword => Typ::Dict,
TsKeywordTypeKind::TsBooleanKeyword => Typ::Bool,
TsKeywordTypeKind::TsBigIntKeyword => Typ::Int,
TsKeywordTypeKind::TsNumberKeyword => Typ::Float,
TsKeywordTypeKind::TsStringKeyword => Typ::Str,
_ => Typ::Unknown,
},
// TODO: we can do better here and extract the inner type of array
TsType::TsArrayType(_) => Typ::List,
TsType::TsTypeRef(TsTypeRef {
span: _,
type_name:
TsEntityName::Ident(Ident {
span: _,
sym,
optional: _,
}),
type_params,
}) => {
println!("N{sym:?}\nP{type_params:?}");
match sym.to_string().as_str() {
"ResourceType" => Typ::ResourceType(
type_params
.as_ref()
.and_then(|x| {
x.params.get(0).and_then(|y| {
y.as_ts_lit_type().and_then(|z| {
z.lit.as_str().map(|a| a.to_owned().value.to_string())
})
})
})
.unwrap_or_else(|| "unknown".to_string()),
),
_ => Typ::Unknown,
}
}
_ => Typ::Unknown,
})
.unwrap_or(Typ::Unknown),
))
}
const STDIMPORTS: [&str; 301] = [
"__future__",
"_abc",
@@ -468,7 +626,7 @@ fn to_value(et: &ExpressionType) -> Option<serde_json::Value> {
}
}
pub fn parse_imports(code: &str) -> error::Result<Vec<String>> {
pub fn parse_python_imports(code: &str) -> error::Result<Vec<String>> {
let find_requirements = code
.lines()
.find_position(|x| x.starts_with("#requirements:"));
@@ -527,7 +685,7 @@ mod tests {
use super::*;
#[test]
fn test_parse_sig() -> anyhow::Result<()> {
fn test_parse_python_sig() -> anyhow::Result<()> {
//let code = "print(2 + 3, fd=sys.stderr)";
let code = "
@@ -540,13 +698,13 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
return {\"len\": len(name), \"splitted\": name.split() }
";
println!("{}", serde_json::to_string(&parse_signature(code)?)?);
println!("{}", serde_json::to_string(&parse_python_signature(code)?)?);
Ok(())
}
#[test]
fn test_parse_imports() -> anyhow::Result<()> {
fn test_parse_python_imports() -> anyhow::Result<()> {
//let code = "print(2 + 3, fd=sys.stderr)";
let code = "
@@ -559,14 +717,14 @@ def main():
pass
";
let r = parse_imports(code)?;
let r = parse_python_imports(code)?;
println!("{}", serde_json::to_string(&r)?);
assert_eq!(r, vec!["wmill", "zanzibar", "matplotlib"]);
Ok(())
}
#[test]
fn test_parse_imports2() -> anyhow::Result<()> {
fn test_parse_python_imports2() -> anyhow::Result<()> {
//let code = "print(2 + 3, fd=sys.stderr)";
let code = "
#requirements:
@@ -583,10 +741,25 @@ def main():
pass
";
let r = parse_imports(code)?;
let r = parse_python_imports(code)?;
println!("{}", serde_json::to_string(&r)?);
assert_eq!(r, vec!["burkina=0.4", "nigeria"]);
Ok(())
}
#[test]
fn test_parse_deno_sig() -> anyhow::Result<()> {
let code = "
export function main(test1: string, test2: string = \"burkina\",
test3: ResourceType<'rt'>, email: email_string) {
console.log(42)
}
";
println!("{}", serde_json::to_string(&parse_deno_signature(code)?)?);
Ok(())
}
}

View File

@@ -11,7 +11,7 @@ use sql_builder::prelude::*;
use crate::{
audit::{audit_log, ActionKind},
db::{UserDB, DB},
error::{Error, JsonResult, Result},
error::{to_anyhow, Error, JsonResult, Result},
jobs, parser,
users::{owner_to_token_owner, truncate_token, Authed, Tokened},
utils::{require_admin, Pagination, StripPath},
@@ -35,7 +35,14 @@ use std::{
const MAX_HASH_HISTORY_LENGTH_STORED: usize = 20;
pub fn global_service() -> Router {
Router::new().route("/tojsonschema", post(parse_code_to_jsonschema))
Router::new()
.route(
"/python/tojsonschema",
post(parse_python_code_to_jsonschema),
)
.route("/deno/tojsonschema", post(parse_deno_code_to_jsonschema))
.route("/hub/list", get(list_hub_scripts))
.route("/hub/get/*path", get(get_hub_script_by_path))
}
pub fn workspaced_service() -> Router {
@@ -50,6 +57,13 @@ pub fn workspaced_service() -> Router {
.route("/deployment_status/h/:hash", get(get_deployment_status))
}
#[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Clone, Hash)]
#[sqlx(type_name = "SCRIPT_LANG", rename_all = "lowercase")]
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
pub enum ScriptLang {
Deno,
Python3,
}
#[derive(sqlx::Type, PartialEq, Debug, Hash, Clone, Copy)]
#[sqlx(transparent)]
pub struct ScriptHash(pub i64);
@@ -113,6 +127,7 @@ pub struct Script {
pub extra_perms: serde_json::Value,
pub lock: Option<String>,
pub lock_error_logs: Option<String>,
pub language: ScriptLang,
}
#[derive(Serialize, Deserialize, sqlx::Type, Debug)]
@@ -138,6 +153,7 @@ pub struct NewScript {
pub schema: Option<Schema>,
pub is_template: Option<bool>,
pub lock: Option<Vec<String>>,
pub language: ScriptLang,
}
#[derive(Deserialize)]
@@ -181,6 +197,7 @@ async fn list_scripts(
"extra_perms",
"null as lock",
"CASE WHEN lock_error_logs IS NOT NULL THEN 'error' ELSE null END as lock_error_logs",
"language",
])
.order_by("created_at", lq.order_desc.unwrap_or(true))
.and_where("workspace_id = ? OR workspace_id = 'starter'".bind(&w_id))
@@ -226,6 +243,41 @@ async fn list_scripts(
Ok(Json(rows))
}
#[derive(Deserialize, Serialize)]
struct SearchData {
asks: Vec<ScriptSearch>,
}
#[derive(Deserialize, Serialize)]
struct ScriptSearch {
id: i32,
summary: String,
app: String,
approved: bool,
}
async fn list_hub_scripts(
Authed {
email, username, ..
}: Authed,
) -> JsonResult<Vec<ScriptSearch>> {
let http_client = reqwest::ClientBuilder::new()
.user_agent("windmill/beta")
.build()
.map_err(to_anyhow)?;
let rows = http_client
.get("https://hub.windmill.dev/searchData?approved=true")
.header("X-email", email.unwrap_or_else(|| "".to_string()))
.header("X-username", username)
.send()
.await
.map_err(to_anyhow)?
.json::<SearchData>()
.await
.map_err(to_anyhow)?
.asks;
Ok(Json(rows))
}
fn hash_script(ns: &NewScript) -> i64 {
let mut dh = DefaultHasher::new();
ns.hash(&mut dh);
@@ -344,10 +396,16 @@ async fn create_script(
.map(|v| v.1.clone())
.unwrap_or(json!({}));
let lock = if ns.language == ScriptLang::Deno {
Some("".to_string())
} else {
ns.lock.as_ref().map(|x| x.join("\n"))
};
//::text::json is to ensure we use serde_json with preserve order
sqlx::query!(
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, \
created_by, schema, is_template, extra_perms, lock) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12)",
created_by, schema, is_template, extra_perms, lock, language) VALUES \
($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13)",
&w_id,
&hash.0,
ns.path,
@@ -359,13 +417,14 @@ async fn create_script(
ns.schema.and_then(|x| serde_json::to_string(&x.0).ok()),
ns.is_template.unwrap_or(false),
extra_perms,
ns.lock.as_ref().map(|x| x.join("\n"))
lock,
ns.language: ScriptLang
)
.execute(&mut tx)
.await?;
let mut tx = if ns.lock.is_none() {
let dependencies = parser::parse_imports(&ns.content)?;
let mut tx = if ns.lock.is_none() && ns.language == ScriptLang::Python3 {
let dependencies = parser::parse_python_imports(&ns.content)?;
let (_, tx) = jobs::push(
tx,
&w_id,
@@ -426,6 +485,34 @@ async fn create_script(
Ok((StatusCode::CREATED, format!("{}", hash)))
}
pub async fn get_hub_script_by_path(
Authed {
email, username, ..
}: Authed,
Path(path): Path<StripPath>,
) -> Result<String> {
let path = path
.to_path()
.strip_prefix("hub/")
.ok_or_else(|| Error::BadRequest("Impossible to remove prefix hex".to_string()))?;
let http_client = reqwest::ClientBuilder::new()
.user_agent("windmill/beta")
.build()
.map_err(to_anyhow)?;
let content = http_client
.get(format!("https://hub.windmill.dev/raw/{path}.ts"))
.header("X-email", email.unwrap_or_else(|| "".to_string()))
.header("X-username", username)
.send()
.await
.map_err(to_anyhow)?
.text()
.await
.map_err(to_anyhow)?;
Ok(content)
}
async fn get_script_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
@@ -593,10 +680,16 @@ async fn delete_script_by_hash(
Ok(Json(script))
}
async fn parse_code_to_jsonschema(
async fn parse_python_code_to_jsonschema(
Json(code): Json<String>,
) -> JsonResult<parser::MainArgSignature> {
parser::parse_signature(&code).map(Json)
parser::parse_python_signature(&code).map(Json)
}
async fn parse_deno_code_to_jsonschema(
Json(code): Json<String>,
) -> JsonResult<parser::MainArgSignature> {
parser::parse_deno_signature(&code).map(Json)
}
pub fn to_i64(s: &str) -> Result<i64> {

View File

@@ -59,6 +59,7 @@ pub fn global_service() -> Router {
.route("/accept_invite", post(accept_invite))
.route("/list_as_super_admin", get(list_users_as_super_admin))
.route("/setpassword", post(set_password))
.route("/create", post(create_user))
.route("/update/:user", post(update_user))
.route("/logout", post(logout))
.route("/tokens/create", post(create_token))
@@ -316,6 +317,18 @@ pub struct User {
pub role: Option<String>
}
#[derive(FromRow, Serialize)]
pub struct GlobalUserInfo {
email: String,
login_type: Option<String>,
super_admin: bool,
verified: bool,
name: Option<String>,
company: Option<String>,
}
#[derive(Serialize)]
pub struct UserInfo {
pub workspace_id: String,
@@ -368,10 +381,10 @@ pub struct NewUser {
pub email: String,
pub password: String,
pub super_admin: bool,
pub name: Option<String>,
pub company: Option<String>
}
#[derive(Deserialize)]
pub struct AcceptInvite {
pub workspace_id: String,
@@ -385,7 +398,6 @@ pub struct DeclineInvite {
#[derive(Deserialize)]
pub struct EditUser {
pub email: String,
pub is_super_admin: Option<bool>,
}
@@ -482,12 +494,12 @@ async fn list_users_as_super_admin(
authed: Authed,
Extension(db): Extension<DB>,
Query(pagination): Query<Pagination>
) -> JsonResult<Vec<User>> {
) -> JsonResult<Vec<GlobalUserInfo>> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, authed.email).await?;
let (per_page, offset) = crate::utils::paginate(pagination);
let rows = sqlx::query_as!(User, "SELECT * from usr LIMIT $1 OFFSET $2", per_page as i32, offset as i32)
let rows = sqlx::query_as!(GlobalUserInfo, "SELECT email, login_type::text, verified, super_admin, name, company from password LIMIT $1 OFFSET $2", per_page as i32, offset as i32)
.fetch_all(&mut tx)
.await?;
tx.commit().await?;
@@ -588,16 +600,6 @@ async fn whoami(
}
}
#[derive(FromRow, Serialize)]
pub struct GlobalUserInfo {
email: String,
login_type: Option<String>,
super_admin: bool,
verified: bool,
name: Option<String>,
company: Option<String>,
}
async fn global_whoami(
Extension(db): Extension<DB>,
Authed { email, .. }: Authed,
@@ -837,6 +839,7 @@ async fn update_workspace_user(
async fn update_user(
Authed { email, .. }: Authed,
Path(email_to_update): Path<String>,
Extension(db): Extension<DB>,
Json(eu): Json<EditUser>,
) -> Result<String> {
@@ -848,7 +851,7 @@ async fn update_user(
sqlx::query_scalar!(
"UPDATE password SET super_admin = $1 WHERE email = $2",
sa,
&eu.email
&email_to_update
)
.execute(&mut tx)
.await?;
@@ -860,14 +863,53 @@ async fn update_user(
"users.update",
ActionKind::Update,
"global",
Some(&eu.email),
Some(&email_to_update),
None,
)
.await?;
tx.commit().await?;
Ok(format!("email {} updated", eu.email))
Ok(format!("email {} updated", &email_to_update))
}
async fn create_user(
Authed { email, .. }: Authed,
Extension(db): Extension<DB>,
Extension(argon2): Extension<Arc<Argon2<'_>>>,
Json(nu): Json<NewUser>,
) -> Result<(StatusCode, String)> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, email.clone()).await?;
sqlx::query!(
"INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company)
VALUES ($1, $2, $3, 'password', $4, $5, $6)",
&nu.email,
true,
&hash_password(argon2, nu.password)?,
&nu.super_admin,
nu.name,
nu.company
)
.execute(&mut tx)
.await?;
audit_log(
&mut tx,
&email.unwrap(),
"users.update",
ActionKind::Update,
"global",
Some(&nu.email),
None,
)
.await?;
tx.commit().await?;
Ok((StatusCode::CREATED, format!("email {} created", nu.email)))
}
pub fn owner_to_token_owner(user: &str, is_group: bool) -> String {
let prefix = if is_group { 'g' } else { 'u' };
format!("{}/{}", prefix, user)
@@ -909,7 +951,6 @@ async fn delete_user(
)
.await?;
tx.commit().await?;
Ok(format!("username {} deleted", username_to_delete))
}

View File

@@ -20,11 +20,15 @@ pub struct Pagination {
pub per_page: Option<usize>,
}
#[derive(Deserialize)]
pub struct StripPath(String);
pub struct StripPath(pub String);
impl StripPath {
pub fn to_path(&self) -> &str {
self.0.strip_prefix('/').unwrap()
if self.0.starts_with('/') {
self.0.strip_prefix('/').unwrap()
} else {
&self.0
}
}
}

View File

@@ -24,7 +24,7 @@ use crate::{
QueuedJob,
},
parser::{self, Typ},
scripts::ScriptHash,
scripts::{ScriptHash, ScriptLang},
users::{create_token_for_owner, get_email_from_username},
variables,
};
@@ -44,11 +44,14 @@ use tokio::sync::mpsc;
const TMP_DIR: &str = "/tmp/windmill";
const PIP_CACHE_DIR: &str = "/tmp/windmill/cache/pip";
const DENO_CACHE_DIR: &str = "/tmp/windmill/cache/deno";
const NUM_SECS_ENV_CHECK: u64 = 15;
const INCLUDE_DEPS_SH_CONTENT: &str = include_str!("../../nsjail/download_deps.sh");
const NSJAIL_CONFIG_DOWNLOAD_CONTENT: &str = include_str!("../../nsjail/download.config.proto");
const NSJAIL_CONFIG_RUN_CONTENT: &str = include_str!("../../nsjail/run.config.proto");
const NSJAIL_CONFIG_RUN_PYTHON3_CONTENT: &str =
include_str!("../../nsjail/run.python3.config.proto");
const NSJAIL_CONFIG_RUN_DENO_CONTENT: &str = include_str!("../../nsjail/run.deno.config.proto");
pub async fn run_worker(
db: &DB,
@@ -61,22 +64,19 @@ pub async fn run_worker(
ip: &str,
sleep_queue: u64,
base_url: &str,
disable_nuser: bool,
tx: tokio::sync::broadcast::Sender<()>,
) {
let worker_dir = format!("{TMP_DIR}/{worker_name}");
tracing::debug!(worker_dir = %worker_dir, worker_name = %worker_name, "Creating worker dir");
DirBuilder::new()
.recursive(true)
.create(&worker_dir)
.await
.expect("could not create initial worker dir");
DirBuilder::new()
.recursive(true)
.create(&PIP_CACHE_DIR)
.await
.expect("could not create initial worker dir");
for x in [&worker_dir, PIP_CACHE_DIR, DENO_CACHE_DIR] {
DirBuilder::new()
.recursive(true)
.create(x)
.await
.expect("could not create initial worker dir");
}
let _ = write_file(&worker_dir, "download_deps.sh", INCLUDE_DEPS_SH_CONTENT).await;
@@ -107,10 +107,17 @@ pub async fn run_worker(
tracing::info!(worker = %worker_name, id = %job.id, "Fetched job");
let job2 = job.clone();
if let Some(err) =
handle_queued_job(job, db, timeout, &worker_name, &worker_dir, base_url)
.await
.err()
if let Some(err) = handle_queued_job(
job,
db,
timeout,
&worker_name,
&worker_dir,
base_url,
disable_nuser,
)
.await
.err()
{
let err_string = err.to_string().clone();
let _ = add_completed_job_error(
@@ -162,6 +169,7 @@ async fn handle_queued_job(
worker_name: &str,
worker_dir: &str,
base_url: &str,
disable_nuser: bool,
) -> crate::error::Result<()> {
let job_id = job.id;
let w_id = &job.workspace_id.clone();
@@ -198,6 +206,7 @@ async fn handle_queued_job(
&mut logs,
&mut last_line,
base_url,
disable_nuser,
)
.await;
@@ -273,6 +282,7 @@ async fn handle_job(
mut logs: &mut String,
mut last_line: &mut String,
base_url: &str,
disable_nuser: bool,
) -> Result<JobResult, Error> {
tracing::info!(
worker = %worker_name,
@@ -289,6 +299,7 @@ async fn handle_job(
.expect("could not create initial job dir");
let mut status: Result<ExitStatus, Error>;
if matches!(job.job_kind, JobKind::Dependencies) {
let requirements = job
.raw_code
@@ -344,12 +355,23 @@ async fn handle_job(
.await?;
}
} else {
let (inner_content, requirements_o) = if matches!(job.job_kind, JobKind::Preview) {
let (inner_content, requirements_o, language) = if matches!(job.job_kind, JobKind::Preview)
|| matches!(job.job_kind, JobKind::Script_Hub)
{
let code = (job.raw_code.as_ref().unwrap_or(&"no raw code".to_owned())).to_owned();
let reqs = parser::parse_imports(&code)?.join("\n");
(code, Some(reqs))
let reqs = if job
.language
.as_ref()
.map(|x| matches!(x, ScriptLang::Python3))
.unwrap_or(false)
{
Some(parser::parse_python_imports(&code)?.join("\n"))
} else {
None
};
(code, reqs, job.language.to_owned())
} else {
sqlx::query_as::<_, (String, Option<String>)>("SELECT content, lock FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')")
sqlx::query_as::<_, (String, Option<String>, Option<ScriptLang>)>("SELECT content, lock, language FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')")
.bind(&job.script_hash.unwrap_or(ScriptHash(0)).0)
.bind(&job.workspace_id)
.fetch_optional(db)
@@ -357,69 +379,82 @@ async fn handle_job(
.ok_or_else(|| Error::InternalErr(format!("expected content and lock")))?
};
let requirements =
requirements_o.ok_or_else(|| Error::InternalErr(format!("lockfile missing")))?;
match language {
None => {
return Err(Error::ExecutionErr(
"Require language to be not null".to_string(),
))?;
}
Some(ScriptLang::Python3) => {
let requirements = requirements_o
.ok_or_else(|| Error::InternalErr(format!("lockfile missing")))?;
let _ = write_file(
&job_dir,
"download.config.proto",
&NSJAIL_CONFIG_DOWNLOAD_CONTENT
.replace("{JOB_DIR}", &job_dir)
.replace("{WORKER_DIR}", &worker_dir)
.replace("{CACHE_DIR}", PIP_CACHE_DIR),
)
.await?;
let _ = write_file(&job_dir, "requirements.txt", &requirements).await?;
let _ = write_file(
&job_dir,
"download.config.proto",
&NSJAIL_CONFIG_DOWNLOAD_CONTENT
.replace("{JOB_DIR}", &job_dir)
.replace("{WORKER_DIR}", &worker_dir)
.replace("{CACHE_DIR}", PIP_CACHE_DIR)
.replace("{CLONE_NEWUSER}", &(!disable_nuser).to_string()),
)
.await?;
let _ = write_file(&job_dir, "requirements.txt", &requirements).await?;
let child = Command::new("nsjail")
.current_dir(&job_dir)
.args(vec!["--config", "download.config.proto"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let child = Command::new("nsjail")
.current_dir(&job_dir)
.args(vec!["--config", "download.config.proto"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
logs.push_str("\n--- DEPENDENCIES INSTALL ---\n");
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
if status.is_ok() {
logs.push_str("\n\n--- CODE EXECUTION ---\n");
logs.push_str("\n--- PIP DEPENDENCIES INSTALL ---\n");
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
set_logs(logs, job.id, db).await;
if status.is_ok() {
logs.push_str("\n\n--- PTHON CODE EXECUTION ---\n");
let _ = write_file(&job_dir, "inner.py", &inner_content).await?;
set_logs(logs, job.id, db).await;
let sig = crate::parser::parse_signature(&inner_content)?;
let transforms = sig.args.into_iter().map(|x| match x.typ {
let _ = write_file(&job_dir, "inner.py", &inner_content).await?;
let sig = crate::parser::parse_python_signature(&inner_content)?;
let transforms = sig.args.into_iter().map(|x| match x.typ {
Typ::Bytes => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = base64.b64decode(kwargs[\"{}\"])\n", x.name, x.name, x.name, x.name),
Typ::Datetime => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = datetime.strptime(kwargs[\"{}\"], '%Y-%m-%dT%H:%M')\n", x.name, x.name, x.name, x.name),
_ => "".to_string()
}).collect::<Vec<String>>().join("");
let tx = db.begin().await?;
let tx = db.begin().await?;
let token = create_token_for_owner(
&db,
&job.workspace_id,
&job.permissioned_as,
crate::users::NewToken {
label: Some("ephemeral-script".to_string()),
expiration: Some(
chrono::Utc::now() + chrono::Duration::seconds((timeout * 2).into()),
),
},
&job.created_by,
)
.await?;
let token = create_token_for_owner(
&db,
&job.workspace_id,
&job.permissioned_as,
crate::users::NewToken {
label: Some("ephemeral-script".to_string()),
expiration: Some(
chrono::Utc::now()
+ chrono::Duration::seconds((timeout * 2).into()),
),
},
&job.created_by,
)
.await?;
let args = if let Some(args) = &job.args {
Some(transform_json_value(&token, &job.workspace_id, base_url, args.clone()).await)
} else {
None
};
let ser_args = serde_json::to_string(&args)
.map_err(|e| Error::ExecutionErr(e.to_string()))?
.replace("\\\"", "\\\\\"");
let wrapper_content: String = format!(
r#"
let args = if let Some(args) = &job.args {
Some(
transform_json_value(&token, &job.workspace_id, base_url, args.clone())
.await,
)
} else {
None
};
let ser_args = serde_json::to_string(&args)
.map_err(|e| Error::ExecutionErr(e.to_string()))?
.replace("\\\"", "\\\\\"");
let wrapper_content: String = format!(
r#"
import json
import base64
from datetime import datetime
@@ -443,44 +478,153 @@ print()
print("result:")
print(res_json)
"#,
);
write_file(&job_dir, "main.py", &wrapper_content).await?;
);
write_file(&job_dir, "main.py", &wrapper_content).await?;
tx.commit().await?;
let reserved_variables = variables::get_reserved_variables(
&job.workspace_id,
&token,
&get_email_from_username(&job.created_by, db)
.await?
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
&job.created_by,
&job.id.to_string(),
)
.into_iter()
.map(|rv| (rv.name, rv.value));
tx.commit().await?;
let reserved_variables = variables::get_reserved_variables(
&job.workspace_id,
&token,
&get_email_from_username(&job.created_by, db)
.await?
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
&job.created_by,
&job.id.to_string(),
)
.into_iter()
.map(|rv| (rv.name, rv.value));
let _ = write_file(
&job_dir,
"run.config.proto",
&NSJAIL_CONFIG_RUN_PYTHON3_CONTENT
.replace("{JOB_DIR}", &job_dir)
.replace("{CLONE_NEWUSER}", &(!disable_nuser).to_string()),
)
.await?;
let _ = write_file(
&job_dir,
"run.config.proto",
&NSJAIL_CONFIG_RUN_CONTENT.replace("{JOB_DIR}", &job_dir),
)
.await?;
let child = Command::new("nsjail")
.current_dir(&job_dir)
.envs(reserved_variables)
.args(vec![
"--config",
"run.config.proto",
"--",
"/usr/local/bin/python3",
"-u",
"/tmp/main.py",
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
}
}
Some(ScriptLang::Deno) => {
logs.push_str("\n\n--- DENO CODE EXECUTION ---\n");
let child = Command::new("nsjail")
.current_dir(&job_dir)
.envs(reserved_variables)
.args(vec![
"--config",
set_logs(logs, job.id, db).await;
let _ = write_file(&job_dir, "inner.ts", &inner_content).await?;
let sig = crate::parser::parse_deno_signature(&inner_content)?;
// let transforms = sig.args.clone().into_iter().map(|x| match x.typ {
// Typ::Bytes => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = base64.b64decode(kwargs[\"{}\"])\n", x.name, x.name, x.name, x.name),
// Typ::Datetime => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = datetime.strptime(kwargs[\"{}\"], '%Y-%m-%dT%H:%M')\n", x.name, x.name, x.name, x.name),
// _ => "".to_string()
// }).collect::<Vec<String>>().join("");
let tx = db.begin().await?;
let token = create_token_for_owner(
&db,
&job.workspace_id,
&job.permissioned_as,
crate::users::NewToken {
label: Some("ephemeral-script".to_string()),
expiration: Some(
chrono::Utc::now() + chrono::Duration::seconds((timeout * 2).into()),
),
},
&job.created_by,
)
.await?;
let args = if let Some(args) = &job.args {
Some(
transform_json_value(&token, &job.workspace_id, base_url, args.clone())
.await,
)
} else {
None
};
let ser_args = serde_json::to_string(&args)
.map_err(|e| Error::ExecutionErr(e.to_string()))?
.replace("\\\"", "\\\\\"");
let spread = sig.args.into_iter().map(|x| x.name).join(",");
let wrapper_content: String = format!(
r#"
import {{ main }} from "./inner.ts";
const {{{spread}}} = JSON.parse(`{ser_args}`);
async function run() {{
let res: any = await main({spread});
if (res == undefined) {{
res = {{}}
}}
if (typeof res !== 'object') {{
res = {{ res1: res }}
}}
const res_json = JSON.stringify(res);
console.log();
console.log("result:");
console.log(res_json);
}}
run();
"#,
);
write_file(&job_dir, "main.ts", &wrapper_content).await?;
tx.commit().await?;
let reserved_variables = variables::get_reserved_variables(
&job.workspace_id,
&token,
&get_email_from_username(&job.created_by, db)
.await?
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
&job.created_by,
&job.id.to_string(),
)
.into_iter()
.map(|rv| (rv.name, rv.value));
let _ = write_file(
&job_dir,
"run.config.proto",
"--",
"/usr/local/bin/python3",
"-u",
"/tmp/main.py",
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
&NSJAIL_CONFIG_RUN_DENO_CONTENT
.replace("{JOB_DIR}", &job_dir)
.replace("{CACHE_DIR}", DENO_CACHE_DIR)
.replace("{CLONE_NEWUSER}", &(!disable_nuser).to_string()),
)
.await?;
let child = Command::new("nsjail")
.current_dir(&job_dir)
.envs(reserved_variables)
.args(vec![
"--config",
"run.config.proto",
"--",
"/usr/bin/deno",
"run",
"--v8-flags=--max-heap-size=2048",
"-A",
"/tmp/main.ts",
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
}
}
}
tokio::fs::remove_dir_all(job_dir).await?;

Binary file not shown.

1
deno-client/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
windmill-api

5
deno-client/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true
}

5
deno-client/generate.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
set -e
/usr/local/bin/docker-entrypoint.sh generate -i ../backend/openapi.yaml -g typescript --additional-properties platform=deno -o windmill-api
sed -i 's/this\.type = "Job";//' windmill-api/models/Job.ts

70
deno-client/index.ts Normal file
View File

@@ -0,0 +1,70 @@
import { ResourceApi, VariableApi, ServerConfiguration } from './windmill-api/index.ts'
import { createConfiguration, type Configuration as Configuration } from './windmill-api/configuration.ts'
export {
AdminApi, AuditApi, FlowApi, GranularAclApi, GroupApi,
JobApi, ResourceApi, VariableApi, ScriptApi, ScheduleApi, SettingsApi,
UserApi, WorkspaceApi
} from './windmill-api/index.ts'
export type string_regex<S extends string> = String
export type string_email = String
export type ResourceType<S extends string> = {}
/**
* Create a client configuration from env variables
* @returns client configuration
*/
export function createConf(): Configuration & { workspace_id: string } {
const token = Deno.env.get("WM_TOKEN") ?? 'no_token'
const base_url = Deno.env.get("BASE_INTERNAL_URL") ?? 'http://localhost:8000'
return {
...createConfiguration({
baseServer: new ServerConfiguration(`${base_url}/api`, {}),
authMethods: { bearerAuth: { tokenProvider: { getToken() { return token } } } },
}), workspace_id: Deno.env.get("WM_WORKSPACE") ?? 'no_workspace'
}
}
/**
* Get a resource value by path
* @param path path of the resource
* @returns resource value
*/
export async function getResource(path: string): Promise<any> {
const conf = createConf()
const resource = await new ResourceApi(conf).getResource(conf.workspace_id, path)
return await transformLeaves(resource.value)
}
/**
* Get a variable by path
* @param path path of the variable
* @returns variable value
*/
export async function getVariable(path: string): Promise<string | undefined> {
const conf = createConf()
const variable = await new VariableApi(conf).getVariable(conf.workspace_id, path)
return variable.value
}
async function transformLeaves(d: { [key: string]: any }): Promise<{ [key: string]: any }> {
for (const k in d) {
d[k] = await _transformLeaf(d[k])
}
return d
}
const VAR_RESOURCE_PREFIX = "$var:"
async function _transformLeaf(v: any): Promise<any> {
if (typeof v === 'object') {
return transformLeaves(v)
}
else if (typeof v === 'string' && v.startsWith(VAR_RESOURCE_PREFIX)) {
const varName = v.substring(VAR_RESOURCE_PREFIX.length)
return await getVariable(varName)
} else {
return v
}
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "6.0.0-beta"
}
}

View File

@@ -3,10 +3,11 @@ version: '3.7'
services:
db:
image: postgres:13
image: postgres:14
restart: always
volumes:
- db_data:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/create_tables.sql
ports:
- 5432:5432
environment:
@@ -18,30 +19,26 @@ services:
timeout: 5s
retries: 5
windmill:
image: windmill:main
image: ghcr.io/windmill-labs/windmill:main
privileged: true
restart: unless-stopped
ports:
- 8000:8000
- 80:8000
environment:
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
- VARIABLES_KEY=changeme
- APP_USER_PASSWORD=changeme
- BASE_URL=http://localhost
- BASE_INTERNAL_URL=http://localhost:8000
- RUST_LOG=info
- NUM_WORKERS=3
- RUST_BACKTRACE=1
- GITHUB_OAUTH_CLIENT_ID=${GITHUB_OAUTH_CLIENT_ID}
- GITHUB_OAUTH_CLIENT_SECRET=${GITHUB_OAUTH_CLIENT_SECRET}
- DISABLE_NUSER=false
depends_on:
db:
condition: service_healthy
caddy:
image: caddy
restart: unless-stopped
environment:
- SITE_URL=${SITE_URL}
ports:
- 80:80
- 443:443
volumes:
- $PWD/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
volumes:
caddy_data:
external: true
db_data: null

View File

@@ -20,4 +20,4 @@ module.exports = {
rules: {
'no-console': ['log', { allow: ['warn', 'error'] }]
}
};
}

2
frontend/.gitignore vendored
View File

@@ -5,4 +5,4 @@ node_modules
/package
/src/gen
CaddyfileRemoteRuben
cypress/videos

1
frontend/.nvmrc Normal file
View File

@@ -0,0 +1 @@
v17.9.0

View File

@@ -2,5 +2,6 @@
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
"printWidth": 100,
"semi": false
}

View File

@@ -5,4 +5,8 @@
bind {$ADDRESS}
reverse_proxy /api/* http://localhost:8000
reverse_proxy /* http://localhost:3000
reverse_proxy /ws/* http://localhost:3001 {
lb_policy header "Authorization"
}
}

View File

@@ -1,16 +0,0 @@
http://localhost {
bind {$ADDRESS}
reverse_proxy /api/* https://demo.windmill.dev {
header_up Host {http.reverse_proxy.upstream.hostport}
}
reverse_proxy /* http://localhost:3000
}
https://localhost {
bind {$ADDRESS}
reverse_proxy /ws/* https://demo.windmill.dev {
header_up Host {http.reverse_proxy.upstream.hostport}
}
}

View File

@@ -1,6 +1,6 @@
http://localhost {
bind {$ADDRESS}
reverse_proxy /api/* https://alpha.windmill.dev {
reverse_proxy /api/* https://app.windmill.dev {
header_up Host {http.reverse_proxy.upstream.hostport}
}
reverse_proxy /* http://localhost:3000
@@ -9,7 +9,7 @@ http://localhost {
https://localhost {
bind {$ADDRESS}
reverse_proxy /ws/* https://alpha.windmill.dev {
reverse_proxy /ws/* https://app.windmill.dev {
header_up Host {http.reverse_proxy.upstream.hostport}
}
}

6
frontend/cypress.json Normal file
View File

@@ -0,0 +1,6 @@
{
"pluginsFile": false,
"env": {
"baseUrl": "http://localhost:8000"
}
}

View File

@@ -0,0 +1,18 @@
// TODO: Should correctly handle exceptions
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})
describe('Authentication', () => {
it('can login using email and password', () => {
cy.login('admin@windmill.dev', 'changeme')
cy.contains('Select a workspace')
})
it('should redirect to login page if user is not logged in', () => {
cy.visit(`${Cypress.env('baseUrl')}/user/workspaces`)
cy.url().should('include', '/user/login')
})
})

View File

@@ -0,0 +1,7 @@
Cypress.Commands.add('login', (email: string, password: string) => {
cy.visit(`${Cypress.env('baseUrl')}/user/login`)
cy.get('#showPassword').click()
cy.get('#email').type('admin@windmill.dev')
cy.get('#password').type('changeme')
cy.get('.flex > .default-button').click()
})

View File

@@ -0,0 +1,8 @@
import './commands'
declare global {
namespace Cypress {
interface Chainable {
login: (email: string, password: string) => Chainable<Element>
}
}
}

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
},
"include": ["**/*.ts"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.5.0",
"version": "1.11.0",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
@@ -9,49 +9,51 @@
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .",
"generate-backend-client": "openapi --input ../backend/openapi.yaml --output ./src/gen --useOptions"
"generate-backend-client": "openapi --input ../backend/openapi.yaml --output ./src/gen --useOptions",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
"devDependencies": {
"@sveltejs/adapter-node": "^1.0.0-next.55",
"@sveltejs/adapter-static": "^1.0.0-next.21",
"@sveltejs/adapter-node": "^1.0.0-next.78",
"@sveltejs/adapter-static": "^1.0.0-next.34",
"@sveltejs/kit": "next",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/forms": "^0.5.1",
"@tailwindcss/typography": "^0.5.0",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"autoprefixer": "^10.4.1",
"cssnano": "^5.0.8",
"eslint": "^7.32.0",
"cssnano": "^5.1.10",
"cypress": "^9.7.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^3.2.1",
"openapi-typescript-codegen": "^0.11.8",
"eslint-plugin-svelte3": "^4.0.0",
"openapi-typescript-codegen": "^0.22.0",
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.0",
"postcss-load-config": "^4.0.1",
"prettier": "^2.4.1",
"prettier-plugin-svelte": "^2.4.0",
"simple-svelte-autocomplete": "^2.2.4",
"stylelint-config-recommended": "^6.0.0",
"stylelint-config-recommended": "^7.0.0",
"svelte": "^3.42.6",
"svelte-awesome": "^2.4.2",
"svelte-check": "^2.2.6",
"svelte-highlight": "^5.1.3",
"svelte-highlight": "^6.0.1",
"svelte-preprocess": "^4.9.8",
"tailwindcss": "^3.0.11",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
"typescript": "^4.7.2"
},
"type": "module",
"dependencies": {
"@codingame/monaco-jsonrpc": "^0.3.1",
"@codingame/monaco-languageclient": "^0.17.0",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@codingame/monaco-jsonrpc": "^0.4.0",
"@fortawesome/free-brands-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@types/vscode": "^1.63.0",
"@zerodevx/svelte-toast": "^0.6.2",
"@zerodevx/svelte-toast": "^0.7.2",
"fuse.js": "^6.4.6",
"highlight.js": "^11.5.1",
"monaco-editor": "^0.30.0",
"svelte-awesome": "^2.4.2",
"monaco-editor": "^0.33.0",
"monaco-languageclient": "^1.0.1",
"svelte-awesome": "^3.0.0",
"svelte-markdown": "^0.2.1",
"svelte-split-pane": "^0.1.2"
}

View File

@@ -1,9 +1,9 @@
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const tailwindcss = require('tailwindcss')
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const mode = process.env.NODE_ENV
const dev = mode === 'development'
const config = {
plugins: [
@@ -16,6 +16,6 @@ const config = {
preset: 'default'
})
]
};
}
module.exports = config;
module.exports = config

2
frontend/src/.d.ts vendored
View File

@@ -1,5 +1,5 @@
declare namespace svelte.JSX {
interface DOMAttributes<T> {
onclick_outside?: CompositionEventHandler<T>;
onclick_outside?: CompositionEventHandler<T>
}
}

View File

@@ -6,11 +6,11 @@
<link rel="icon" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>windmill.dev</title>
%svelte.head%
%sveltekit.head%
</head>
<body class="outline-none focus:outline-none">
%svelte.body%
%sveltekit.body%
</body>
</html>

View File

@@ -1,24 +0,0 @@
export function pathToMeta(path) {
const splitted = path.split('/');
let ownerKind;
if (splitted[0] == 'g') {
ownerKind = 'group';
}
else if (splitted[0] == 'u') {
ownerKind = 'user';
}
else {
console.error('Not recognized owner:' + splitted[0]);
return {
ownerKind: 'user',
owner: '',
name: ''
};
}
return {
ownerKind,
owner: splitted[1],
name: splitted.slice(2).join('/')
};
}
//# sourceMappingURL=common.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"common.js","sourceRoot":"","sources":["common.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChC,IAAI,SAAoB,CAAA;IACxB,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE;QACvB,SAAS,GAAG,OAAO,CAAA;KACnB;SAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE;QAC9B,SAAS,GAAG,MAAM,CAAA;KAClB;SAAM;QACN,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QACpD,OAAO;YACN,SAAS,EAAE,MAAM;YACjB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,EAAE;SACR,CAAA;KACD;IACD,OAAO;QACN,SAAS;QACT,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QAClB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;KACjC,CAAA;AACF,CAAC"}

View File

@@ -2,16 +2,15 @@ export type OwnerKind = 'group' | 'user'
export type ActionKind = 'Create' | 'Update' | 'Delete' | 'Execute'
export interface SchemaProperty {
type: string | undefined
description: string
pattern?: string
default?: any
enum?: string[]
contentEncoding?: "base64" | "binary"
contentEncoding?: 'base64' | 'binary'
format?: string
items?: { type?: "string" | "number" }
items?: { type?: 'string' | 'number' }
}
export type Schema = {
@@ -23,7 +22,6 @@ export type Schema = {
export type Meta = { ownerKind: OwnerKind; owner: string; name: string }
export function pathToMeta(path: string): Meta {
const splitted = path.split('/')
let ownerKind: OwnerKind

View File

@@ -1,8 +0,0 @@
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
ssr: false
});
return response;
}
//# sourceMappingURL=hooks.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["hooks.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE;IAC3C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,KAAK;KACb,CAAC,CAAA;IAEF,OAAO,QAAQ,CAAA;AACnB,CAAC"}

View File

@@ -1,8 +1,8 @@
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
ssr: false
})
const response = await resolve(event, {
ssr: false
})
return response
return response
}

View File

@@ -1,66 +0,0 @@
import { ScriptService } from "./gen";
import { sendUserToast } from "./utils";
export async function inferArgs(code, schema) {
try {
const inferedSchema = await ScriptService.toJsonschema({
requestBody: code
});
schema.required = [];
const oldProperties = Object.assign({}, schema.properties);
schema.properties = {};
for (const arg of inferedSchema.args) {
if (!(arg.name in oldProperties)) {
schema.properties[arg.name] = { description: '', type: '' };
}
else {
schema.properties[arg.name] = oldProperties[arg.name];
}
pythonToJsonSchemaType(arg.typ, schema.properties[arg.name]);
schema.properties[arg.name].default = arg.default;
if (!arg.has_default) {
schema.required.push(arg.name);
}
}
}
catch (err) {
console.error(err);
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true);
}
}
function array_move(arr, fromIndex, toIndex) {
var element = arr[fromIndex];
arr.splice(fromIndex, 1);
arr.splice(toIndex, 0, element);
}
function pythonToJsonSchemaType(t, s) {
if (t === 'int') {
s.type = 'integer';
}
else if (t === 'float') {
s.type = 'number';
}
else if (t === 'bool') {
s.type = 'boolean';
}
else if (t === 'str') {
s.type = 'string';
}
else if (t === 'dict') {
s.type = 'object';
}
else if (t === 'list') {
s.type = 'array';
}
else if (t === 'bytes') {
s.type = 'string';
s.contentEncoding = 'base64';
}
else if (t === 'datetime') {
s.type = 'string';
s.format = 'date-time';
}
else {
s.type = undefined;
}
}
//# sourceMappingURL=infer.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"infer.js","sourceRoot":"","sources":["infer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEvC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,MAAc;IACxD,IAAI;QACA,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,YAAY,CAAC;YACnD,WAAW,EAAE,IAAI;SACpB,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAA;QACpB,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;QAC1D,MAAM,CAAC,UAAU,GAAG,EAAE,CAAA;QAEtB,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,IAAI,EAAE;YAClC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,aAAa,CAAC,EAAE;gBAC9B,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;aAC9D;iBAAM;gBACH,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;aACxD;YACD,sBAAsB,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;YAEjD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE;gBAClB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;aACjC;SACJ;KACJ;IAAC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClB,aAAa,CAAC,2BAA2B,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,EAAE,IAAI,CAAC,CAAA;KACpE;AACL,CAAC;AAED,SAAS,UAAU,CAAI,GAAQ,EAAE,SAAiB,EAAE,OAAe;IAC/D,IAAI,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5B,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;IACxB,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,sBAAsB,CAAC,CAAS,EAAE,CAAiB;IACxD,IAAI,CAAC,KAAK,KAAK,EAAE;QACb,CAAC,CAAC,IAAI,GAAG,SAAS,CAAA;KACrB;SAAM,IAAI,CAAC,KAAK,OAAO,EAAE;QACtB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;KACpB;SAAM,IAAI,CAAC,KAAK,MAAM,EAAE;QACrB,CAAC,CAAC,IAAI,GAAG,SAAS,CAAA;KACrB;SAAM,IAAI,CAAC,KAAK,KAAK,EAAE;QACpB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;KACpB;SAAM,IAAI,CAAC,KAAK,MAAM,EAAE;QACrB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;KACpB;SAAM,IAAI,CAAC,KAAK,MAAM,EAAE;QACrB,CAAC,CAAC,IAAI,GAAG,OAAO,CAAA;KACnB;SAAM,IAAI,CAAC,KAAK,OAAO,EAAE;QACtB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;QACjB,CAAC,CAAC,eAAe,GAAG,QAAQ,CAAA;KAC/B;SAAM,IAAI,CAAC,KAAK,UAAU,EAAE;QACzB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;QACjB,CAAC,CAAC,MAAM,GAAG,WAAW,CAAA;KACzB;SAAM;QACH,CAAC,CAAC,IAAI,GAAG,SAAS,CAAA;KACrB;AACL,CAAC"}

View File

@@ -1,61 +1,72 @@
import type { Schema, SchemaProperty } from "./common"
import { ScriptService } from "./gen"
import { sendUserToast } from "./utils"
import type { Schema, SchemaProperty } from './common'
import { ScriptService, type MainArgSignature } from './gen'
import { sendUserToast } from './utils'
export async function inferArgs(code: string, schema: Schema): Promise<void> {
try {
const inferedSchema = await ScriptService.toJsonschema({
requestBody: code
})
schema.required = []
const oldProperties = Object.assign({}, schema.properties)
schema.properties = {}
export async function inferArgs(
language: 'python3' | 'deno',
code: string,
schema: Schema
): Promise<void> {
try {
let inferedSchema: MainArgSignature
if (language == 'python3') {
inferedSchema = await ScriptService.pythonToJsonschema({
requestBody: code
})
} else if (language == 'deno') {
inferedSchema = await ScriptService.denoToJsonschema({
requestBody: code
})
} else {
return
}
for (const arg of inferedSchema.args) {
if (!(arg.name in oldProperties)) {
schema.properties[arg.name] = { description: '', type: '' }
} else {
schema.properties[arg.name] = oldProperties[arg.name]
}
pythonToJsonSchemaType(arg.typ, schema.properties[arg.name])
schema.properties[arg.name].default = arg.default
schema.required = []
const oldProperties = Object.assign({}, schema.properties)
schema.properties = {}
if (!arg.has_default) {
schema.required.push(arg.name)
}
}
} catch (err) {
console.error(err)
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true)
}
for (const arg of inferedSchema.args) {
if (!(arg.name in oldProperties)) {
schema.properties[arg.name] = { description: '', type: '' }
} else {
schema.properties[arg.name] = oldProperties[arg.name]
}
argSigToJsonSchemaType(arg.typ, schema.properties[arg.name])
schema.properties[arg.name].default = arg.default
if (!arg.has_default) {
schema.required.push(arg.name)
}
}
} catch (err) {
console.error(err)
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true)
}
}
function array_move<T>(arr: T[], fromIndex: number, toIndex: number) {
var element = arr[fromIndex]
arr.splice(fromIndex, 1)
arr.splice(toIndex, 0, element)
}
function pythonToJsonSchemaType(t: string, s: SchemaProperty): void {
if (t === 'int') {
s.type = 'integer'
} else if (t === 'float') {
s.type = 'number'
} else if (t === 'bool') {
s.type = 'boolean'
} else if (t === 'str') {
s.type = 'string'
} else if (t === 'dict') {
s.type = 'object'
} else if (t === 'list') {
s.type = 'array'
} else if (t === 'bytes') {
s.type = 'string'
s.contentEncoding = 'base64'
} else if (t === 'datetime') {
s.type = 'string'
s.format = 'date-time'
} else {
s.type = undefined
}
function argSigToJsonSchemaType(t: string | { resourcetype: string }, s: SchemaProperty): void {
if (t === 'int') {
s.type = 'integer'
} else if (t === 'float') {
s.type = 'number'
} else if (t === 'bool') {
s.type = 'boolean'
} else if (t === 'str') {
s.type = 'string'
} else if (t === 'dict') {
s.type = 'object'
} else if (t === 'list') {
s.type = 'array'
} else if (t === 'bytes') {
s.type = 'string'
s.contentEncoding = 'base64'
} else if (t === 'datetime') {
s.type = 'string'
s.format = 'date-time'
} else if (typeof t !== 'string' && t.resourcetype != undefined) {
s.type = 'object'
s.format = `resource-${t.resourcetype}`
} else {
s.type = undefined
}
}

View File

@@ -1 +1,92 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { SvelteToast } from '@zerodevx/svelte-toast'
import { onMount } from 'svelte'
import { WorkspaceService } from '../gen'
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '../stores'
import {
getUserExt,
logout,
logoutWithRedirect,
refreshSuperadmin,
sendUserToast
} from '../utils'
// Default toast options
const toastOptions = {
duration: 4000, // duration of progress bar tween to the `next` value
initial: 1, // initial progress bar value
next: 0, // next progress value
pausable: false, // pause progress bar tween on mouse hover
dismissable: true, // allow dismiss with close button
reversed: false, // insert new toast to bottom of stack
intro: { x: 256 }, // toast intro fly animation settings
theme: {} // css var overrides
}
const monacoEditorUnhandledErrors = [
'Model not found',
'Connection is disposed.',
'Connection got disposed.'
]
async function loadUser() {
try {
$usersWorkspaceStore = await WorkspaceService.listUserWorkspaces()
await refreshSuperadmin()
if ($workspaceStore) {
if ($userStore) {
console.log(`Welcome ${$userStore.email}`)
} else if ($superadmin) {
console.log('You are a superadmin, you can go wherever you please')
} else {
$userStore = await getUserExt($workspaceStore)
throw Error('Not logged in')
}
} else {
goto('/user/workspaces')
}
} catch {
logoutWithRedirect($page.url.pathname)
}
}
onMount(() => {
loadUser()
window.onunhandledrejection = (event: PromiseRejectionEvent) => {
event.preventDefault()
if (event.reason?.message) {
const { message, body, status } = event.reason
// Unhandled errors from Monaco Editor don't logout the user
if (monacoEditorUnhandledErrors.includes(message)) {
return
}
if (status == '401') {
sendUserToast('Logged out after a request was unauthorized', true)
logout($page.url.pathname)
} else {
sendUserToast(`${message}: ${body ?? ''}`, true)
}
} else {
console.log('Caught unhandled promise rejection without message', event)
}
}
})
</script>
<slot />
<SvelteToast options={toastOptions} />
<style>
:root {
--toastBackground: #eff6ff;
--toastBarBackground: #eff6ff;
--toastColor: #123456;
}
</style>

View File

@@ -1,146 +1,70 @@
<script lang="ts">
import '../app.css';
import { OpenAPI, UserService, WorkspaceService } from '../gen';
import { logout, clickOutside, sendUserToast, logoutWithRedirect, getUser } from '../utils';
import { onDestroy, onMount } from 'svelte';
import Icon from 'svelte-awesome';
import { faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'
import {
faScroll,
faPlay,
faWallet,
faEye,
faChevronDown,
faChevronRight,
faChevronLeft,
faBookOpen,
faCubes,
faCalendar,
faRobot,
faChevronDown,
faChevronLeft,
faChevronRight,
faCog,
faUser,
faCrown,
faCubes,
faEye,
faPlay,
faRobot,
faScroll,
faUser,
faUsersCog,
faWallet,
faWind
} from '@fortawesome/free-solid-svg-icons';
import { SvelteToast } from '@zerodevx/svelte-toast';
import { faDiscord, faGithub, faPython } from '@fortawesome/free-brands-svg-icons';
import { page } from '$app/stores';
import {
superadmin,
usernameStore,
userStore,
usersWorkspaceStore,
workspaceStore
} from '../stores';
import { goto } from '$app/navigation';
} from '@fortawesome/free-solid-svg-icons'
import { onMount } from 'svelte'
import Icon from 'svelte-awesome'
import '../app.css'
import { OpenAPI, ScriptService } from '../gen'
import { hubScripts, superadmin, userStore, usersWorkspaceStore, workspaceStore } from '../stores'
import { clickOutside, logout } from '../utils'
OpenAPI.WITH_CREDENTIALS = true;
OpenAPI.WITH_CREDENTIALS = true
// Default toast options
const toastOptions = {
duration: 4000, // duration of progress bar tween to the `next` value
initial: 1, // initial progress bar value
next: 0, // next progress value
pausable: false, // pause progress bar tween on mouse hover
dismissable: true, // allow dismiss with close button
reversed: false, // insert new toast to bottom of stack
intro: { x: 256 }, // toast intro fly animation settings
theme: {} // css var overrides
};
let menuOpen = false;
let workspacePickerOpen = false;
let isMobile = false;
let viewportWidth = 3000;
let isCollapsed = false;
let menuOpen = false
let workspacePickerOpen = false
let isMobile = false
let viewportWidth = 3000
let isCollapsed = false
function openMenu(): void {
menuOpen = true;
menuOpen = true
}
function handleClickOutside(event: any): void {
if (isMobile || viewportWidth < 640) {
isCollapsed = true;
isCollapsed = true
}
}
function handleClickOutsideMenu(event: any): void {
menuOpen = false;
menuOpen = false
}
function handleClickOutsideWorkspacePicker(event: any): void {
workspacePickerOpen = false;
workspacePickerOpen = false
}
async function loadUserInfo() {
if ($superadmin == undefined) {
UserService.globalWhoami().then((x) => {
if (x.super_admin) {
superadmin.set(x.email);
} else {
superadmin.set(false);
}
});
}
if (!$usersWorkspaceStore) {
try {
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
} catch {}
}
if ($usersWorkspaceStore) {
if (!$workspaceStore) {
workspaceStore.set(localStorage.getItem('workspace')?.toString());
}
if ($workspaceStore && $usernameStore) {
await getUser($workspaceStore);
} else if ($superadmin) {
console.log('You are a superadmin, you can go wherever you please');
} else {
goto('/user/workspaces');
}
} else {
logoutWithRedirect($page.url.pathname);
}
}
$: {
if ($workspaceStore) {
localStorage.setItem('workspace', $workspaceStore);
}
async function loadSearchData() {
const scripts = await ScriptService.listHubScripts()
$hubScripts = scripts.map((x) => ({
path: `hub/${x.id}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
summary: `${x.summary} (${x.app})`,
approved: x.approved
}))
}
onMount(() => {
loadUserInfo();
window.onunhandledrejection = (e) => {
if (e.reason && e.reason.message) {
if (
['Model not found', 'Connection is disposed.', 'Connection got disposed.'].includes(
e.reason.message
)
) {
// monaco editor promise cancelation
console.log('caught expected error');
} else {
if (e.reason.status == '401') {
sendUserToast('Logged out after a request was unauthorized', true);
logout($page.url.pathname);
} else {
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`;
sendUserToast(message, true);
}
}
} else {
console.log('unexpected error ignored', e);
}
e.preventDefault();
return false;
};
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
//Mobile
isCollapsed = isMobile;
});
isCollapsed = isMobile
loadSearchData()
})
</script>
<div bind:clientWidth={viewportWidth} class="h-full max-w-screen">
@@ -155,7 +79,7 @@
<button
class="w-full flex flex-row-reverse transform hover:translate-x-1 transition-transform ease-in duration-200"
on:click={() => {
isCollapsed = !isCollapsed;
isCollapsed = !isCollapsed
}}
>
<div class="pt-1 pr-3">
@@ -181,7 +105,7 @@
<div
class="flex flex-row items-center w-full justify-content"
on:click={() => {
workspacePickerOpen = true;
workspacePickerOpen = true
}}
>
<span class:hidden={isCollapsed} class="pr-2 font-mono text-xs flex"
@@ -203,8 +127,8 @@
{#each $usersWorkspaceStore?.workspaces ?? [] as workspace}
<button
on:click={() => {
workspaceStore.set(workspace.id);
workspacePickerOpen = false;
workspaceStore.set(workspace.id)
workspacePickerOpen = false
}}
class="block px-4 py-2 text-xs text-gray-500 "
role="menuitem"
@@ -231,7 +155,7 @@
tabindex="-1"
id="user-menu-item-2"
on:click={() => {
localStorage.removeItem('workspace');
localStorage.removeItem('workspace')
}}
>
See all workspaces & invites</a
@@ -259,7 +183,7 @@
<div class="mx-auto">
<span class:hidden={isCollapsed} class="px-2 font-mono text-xs whitespace-nowrap">
<Icon class="text-white" data={faUser} scale={0.6} />
{$usernameStore ?? $superadmin ?? '___'}
{$userStore?.username ?? $superadmin ?? '___'}
{#if $userStore?.is_admin}
<Icon class="text-white" data={faCrown} scale={0.6} />
{/if}
@@ -278,7 +202,7 @@
>
<span class="block px-4 py-2 text-sm text-gray-500">{$usersWorkspaceStore?.email}</span>
<a
href="/settings"
href="/user/settings"
class="block px-4 py-2 text-sm text-gray-700"
role="menuitem"
tabindex="-1"
@@ -405,7 +329,7 @@
<button
class="h-12 flex flex-row text-sm font-medium min-w-full px-5 items-center transform hover:translate-x-1 transition-transform ease-in duration-200"
on:click={() => {
isCollapsed = !isCollapsed;
isCollapsed = !isCollapsed
}}
>
<div class="w-full -ml-4">
@@ -426,7 +350,6 @@
: 'pl-44'} pr-8 flex h-full max-w-screen flex-col items-center"
>
<slot />
<SvelteToast {toastOptions} />
</div>
</div>
@@ -437,10 +360,4 @@
.menu-link {
@apply flex flex-row h-10 transform hover:translate-x-1 transition-transform ease-in duration-200 text-gray-200 hover:text-white;
}
:root {
--toastBackground: #eff6ff;
--toastBarBackground: #eff6ff;
--toastColor: #123456;
}
</style>

View File

@@ -1,34 +1,34 @@
<script lang="ts">
import { AuditService, AuditLog, UserService } from '../gen';
import type { ActionKind } from '../common';
import { page } from '$app/stores';
import { displayDate, sendUserToast } from '../utils';
import { goto } from '$app/navigation';
import PageHeader from './components/PageHeader.svelte';
import { usernameStore, userStore, workspaceStore } from '../stores';
import TableCustom from './components/TableCustom.svelte';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
import { faCross, faEdit, faPlay, faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons';
import { AuditService, AuditLog, UserService } from '../gen'
import type { ActionKind } from '../common'
import { page } from '$app/stores'
import { displayDate, sendUserToast } from '../utils'
import { goto } from '$app/navigation'
import PageHeader from './components/PageHeader.svelte'
import { userStore, workspaceStore } from '../stores'
import TableCustom from './components/TableCustom.svelte'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import { faCross, faEdit, faPlay, faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'
let logs: AuditLog[];
let usernames: string[];
let logs: AuditLog[]
let usernames: string[]
// Get all page params
let username: string | undefined = $page.url.searchParams.get('username') ?? undefined;
let pageIndex: number | undefined = Number($page.url.searchParams.get('page')) || undefined;
let before: string | undefined = $page.url.searchParams.get('before') ?? undefined;
let after: string | undefined = $page.url.searchParams.get('after') ?? undefined;
let perPage: number | undefined = Number($page.url.searchParams.get('perPage')) || undefined;
let operation: string | undefined = $page.url.searchParams.get('operation') ?? undefined;
let resource: string | undefined = $page.url.searchParams.get('resource') ?? undefined;
let username: string | undefined = $page.url.searchParams.get('username') ?? undefined
let pageIndex: number | undefined = Number($page.url.searchParams.get('page')) || undefined
let before: string | undefined = $page.url.searchParams.get('before') ?? undefined
let after: string | undefined = $page.url.searchParams.get('after') ?? undefined
let perPage: number | undefined = Number($page.url.searchParams.get('perPage')) || undefined
let operation: string | undefined = $page.url.searchParams.get('operation') ?? undefined
let resource: string | undefined = $page.url.searchParams.get('resource') ?? undefined
let actionKind: ActionKind | undefined =
($page.url.searchParams.get('actionKind') as ActionKind) ?? undefined;
($page.url.searchParams.get('actionKind') as ActionKind) ?? undefined
async function loadLogs(username: string | undefined, page: number | undefined): Promise<void> {
try {
if (username == 'all') {
username = undefined;
username = undefined
}
logs = await AuditService.listAuditLogs({
workspace: $workspaceStore!,
@@ -40,50 +40,48 @@
operation,
resource,
actionKind
});
})
} catch (err) {
sendUserToast(`Could not load users: ${err}`, true);
sendUserToast(`Could not load users: ${err}`, true)
}
}
async function loadUsers() {
try {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
} catch (err) {
sendUserToast(`Could not load users: ${err}`, true);
sendUserToast(`Could not load users: ${err}`, true)
}
}
async function gotoUsername(username: string | undefined): Promise<void> {
goto(`?username=` + (username ? encodeURIComponent(username) : ''));
goto(`?username=` + (username ? encodeURIComponent(username) : ''))
}
async function gotoPage(index: number): Promise<void> {
pageIndex = index;
goto(`?page=${index}` + (username ? `&username=${encodeURIComponent(username)}` : ''));
pageIndex = index
goto(`?page=${index}` + (username ? `&username=${encodeURIComponent(username)}` : ''))
}
function kindToIcon(kind: string) {
if (kind == 'Execute') {
return faPlay;
return faPlay
} else if (kind == 'Delete') {
return faCross;
return faCross
} else if (kind == 'Update') {
return faEdit;
return faEdit
} else if (kind == 'Create') {
return faPlus;
return faPlus
}
return faQuestion;
return faQuestion
}
$: {
if ($workspaceStore) {
loadUsers();
loadLogs(username, pageIndex);
}
if ($usernameStore) {
username = $usernameStore;
loadUsers()
loadLogs(username, pageIndex)
}
username = $userStore?.username
}
</script>
@@ -115,10 +113,10 @@
<TableCustom
on:next={() => {
gotoPage((pageIndex ?? 1) + 1);
gotoPage((pageIndex ?? 1) + 1)
}}
on:previous={() => {
gotoPage((pageIndex ?? 1) - 1);
gotoPage((pageIndex ?? 1) - 1)
}}
currentPage={pageIndex}
>

View File

@@ -1,26 +1,26 @@
<script lang="ts">
import { truncate } from '../../utils';
import Modal from './Modal.svelte';
import Tooltip from './Tooltip.svelte';
import json from 'svelte-highlight/src/languages/json';
import github from 'svelte-highlight/src/styles/github';
import { Highlight } from 'svelte-highlight';
import { ResourceService, type Resource } from '../../gen';
import { workspaceStore } from '../../stores';
import { truncate } from '../../utils'
import Modal from './Modal.svelte'
import Tooltip from './Tooltip.svelte'
import json from 'svelte-highlight/languages/json'
import github from 'svelte-highlight/styles/github'
import { Highlight } from 'svelte-highlight'
import { ResourceService, type Resource } from '../../gen'
import { workspaceStore } from '../../stores'
export let value: any;
let resourceViewer: Modal;
let resource: Resource;
export let value: any
let resourceViewer: Modal
let resource: Resource
function isString(value: any) {
return typeof value === 'string' || value instanceof String;
return typeof value === 'string' || value instanceof String
}
async function getResource(path) {
resource = await ResourceService.getResource({ workspace: $workspaceStore!, path });
resource = await ResourceService.getResource({ workspace: $workspaceStore!, path })
}
let asJson: string = JSON.stringify(value, null, 4);
let asJson: string = JSON.stringify(value, null, 4)
</script>
<svelte:head>
@@ -43,8 +43,8 @@
<button
class="text-xs text-blue-500"
on:click={async () => {
await getResource(value.substring('$res:'.length));
resourceViewer.openModal();
await getResource(value.substring('$res:'.length))
resourceViewer.openModal()
}}>{value}</button
>{:else if asJson.length > 40}
{truncate(asJson, 40)}<Tooltip>{asJson}</Tooltip>

View File

@@ -1,115 +1,115 @@
<script lang="ts">
import Tooltip from './Tooltip.svelte';
import Tooltip from './Tooltip.svelte'
import { slide } from 'svelte/transition';
import { slide } from 'svelte/transition'
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import StringTypeNarrowing from './StringTypeNarrowing.svelte';
import Icon from 'svelte-awesome';
import ResourcePicker from './ResourcePicker.svelte';
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte';
import ObjectResourceInput from './ObjectResourceInput.svelte';
import FieldHeader from './FieldHeader.svelte';
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
import Icon from 'svelte-awesome'
import ResourcePicker from './ResourcePicker.svelte'
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
import ObjectResourceInput from './ObjectResourceInput.svelte'
import FieldHeader from './FieldHeader.svelte'
export let label: string = '';
export let value: any;
export let defaultValue: any = undefined;
export let description: string = '';
export let format: string = '';
export let contentEncoding = '';
export let type: string | undefined = undefined;
export let required = false;
export let pattern: undefined | string;
export let valid = required ? false : true;
export let minRows = 1;
export let maxRows = 10;
export let enum_: string[] | undefined = undefined;
export let disabled = false;
export let editableSchema = false;
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined;
export let displayHeader = true;
export let label: string = ''
export let value: any
export let defaultValue: any = undefined
export let description: string = ''
export let format: string = ''
export let contentEncoding = ''
export let type: string | undefined = undefined
export let required = false
export let pattern: undefined | string
export let valid = required ? false : true
export let minRows = 1
export let maxRows = 10
export let enum_: string[] | undefined = undefined
export let disabled = false
export let editableSchema = false
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined
export let displayHeader = true
let seeEditable: boolean = enum_ != undefined || pattern != undefined;
let seeEditable: boolean = enum_ != undefined || pattern != undefined
$: minHeight = `${1 + minRows * 1.2}em`;
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
$: minHeight = `${1 + minRows * 1.2}em`
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
$: validateInput(pattern, value);
$: validateInput(pattern, value)
let error: string = '';
let error: string = ''
let rawValue: string | undefined;
let rawValue: string | undefined
$: {
if (rawValue) {
try {
value = JSON.parse(rawValue);
value = JSON.parse(rawValue)
} catch (err) {
error = err.toString();
error = err.toString()
}
}
}
$: {
if (!type || type == 'object' || (type == 'array' && itemsType?.type == undefined)) {
evalValueToRaw();
evalValueToRaw()
}
if (defaultValue) {
let stringified = JSON.stringify(defaultValue, null, 4);
let stringified = JSON.stringify(defaultValue, null, 4)
if (stringified.length > 50) {
minRows = 3;
minRows = 3
}
if (type != 'string') {
minRows = Math.max(minRows, Math.min(stringified.split(/\r\n|\r|\n/).length + 1, maxRows));
minRows = Math.max(minRows, Math.min(stringified.split(/\r\n|\r|\n/).length + 1, maxRows))
}
}
}
export function evalValueToRaw() {
rawValue = JSON.stringify(value, null, 4);
rawValue = JSON.stringify(value, null, 4)
}
function fileChanged(e: any) {
let t = e.target;
let t = e.target
if (t && 'files' in t && t.files.length > 0) {
let reader = new FileReader();
let reader = new FileReader()
reader.onload = (e: any) => {
value = e.target.result.split('base64,')[1];
};
reader.readAsDataURL(t.files[0]);
value = e.target.result.split('base64,')[1]
}
reader.readAsDataURL(t.files[0])
} else {
value = undefined;
value = undefined
}
}
function validateInput(pattern: string | undefined, v: any): void {
if (required && v == undefined) {
error = 'This field is required';
valid = false;
error = 'This field is required'
valid = false
} else {
if (pattern && !testRegex(pattern, v)) {
error = `Should match ${pattern}`;
valid = false;
error = `Should match ${pattern}`
valid = false
} else {
error = '';
valid = true;
error = ''
valid = true
}
}
}
function testRegex(pattern: string, value: any): boolean {
try {
const regex = new RegExp(pattern);
return regex.test(value);
const regex = new RegExp(pattern)
return regex.test(value)
} catch (err) {
return false;
return false
}
}
$: {
if (value == undefined) {
value = defaultValue;
value = defaultValue
}
}
</script>
@@ -124,7 +124,7 @@
<span
class="underline"
on:click={() => {
seeEditable = !seeEditable;
seeEditable = !seeEditable
}}
>Customize argument<Icon
class="ml-2"
@@ -198,9 +198,9 @@
<button
class="default-button-secondary mx-6"
on:click={() => {
value = value.filter((el) => el != v);
value = value.filter((el) => el != v)
if (value.length == 0) {
value = undefined;
value = undefined
}
}}><Icon data={faMinus} class="mb-1" /></button
>
@@ -210,9 +210,9 @@
class="default-button-secondary mt-1"
on:click={() => {
if (value == undefined) {
value = [];
value = []
}
value = value.concat('');
value = value.concat('')
}}>Add item &nbsp;<Icon data={faPlus} class="mb-1" /></button
><span class="ml-2">{(value ?? []).length} item(s)</span>
{:else if type == 'object' && format?.startsWith('resource')}

View File

@@ -1,11 +1,11 @@
<script lang="ts">
export let value: string;
export let placeholder = '';
export let minRows = 1;
export let maxRows = 20;
export let value: string
export let placeholder = ''
export let minRows = 1
export let maxRows = 20
$: minHeight = `${1 + minRows * 1.2}em`;
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
$: minHeight = `${1 + minRows * 1.2}em`
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
</script>
<div class="container">

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import Tooltip from './Tooltip.svelte';
export let twBgColor = 'bg-blue-200';
export let twTextColor = 'text-gray-700';
export let tooltip: string | undefined = undefined;
import Tooltip from './Tooltip.svelte'
export let twBgColor = 'bg-blue-200'
export let twTextColor = 'text-gray-700'
export let tooltip: string | undefined = undefined
</script>
<span class="{twBgColor} {twTextColor} text-2xs rounded px-1 whitespace-nowrap">

View File

@@ -6,19 +6,19 @@
faPlay,
faShare,
faTrash
} from '@fortawesome/free-solid-svg-icons';
import { createEventDispatcher } from 'svelte';
import Icon from 'svelte-awesome';
} from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
export let category: 'delete' | 'list' | 'run' | 'add' | 'edit' | 'archive' | 'share';
export let disabled: boolean = false;
const dispatch = createEventDispatcher();
export let category: 'delete' | 'list' | 'run' | 'add' | 'edit' | 'archive' | 'share'
export let disabled: boolean = false
const dispatch = createEventDispatcher()
</script>
<button
class="{$$props.class} inline-flex items-center default-button py-0 px-1 {category} default-button-secondary"
on:click={() => {
dispatch('click');
dispatch('click')
}}
{disabled}
>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import { faSortDown } from '@fortawesome/free-solid-svg-icons';
import type { DropdownItem } from '../../utils';
import { createEventDispatcher } from 'svelte';
import Icon from 'svelte-awesome';
import Dropdown from './Dropdown.svelte';
import { faSortDown } from '@fortawesome/free-solid-svg-icons'
import type { DropdownItem } from '../../utils'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import Dropdown from './Dropdown.svelte'
export let dropdownItems: DropdownItem[] = [];
export let dropdownItems: DropdownItem[] = []
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
</script>
<div class="bg-blue-500 rounded-sm {$$props.class} inline-block py-0 my-0 ">
<button
class="inline pl-2 text-white text-sm py-0 my-0"
on:click={() => {
dispatch('clickMain');
dispatch('clickMain')
}}><slot name="name">Add script</slot></button
>
<Dropdown

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome';
import Icon from 'svelte-awesome'
export let text: string;
export let text: string
export let viewOptions = false;
export let viewOptions = false
</script>
<button
type="submit"
class="mr-6 text-sm underline text-gray-700 inline-flex items-center"
on:click={() => {
viewOptions = !viewOptions;
viewOptions = !viewOptions
}}
>
<div>

View File

@@ -1,56 +1,56 @@
<script lang="ts">
import { Highlight } from 'svelte-highlight';
import github from 'svelte-highlight/src/styles/github';
import { json } from 'svelte-highlight/src/languages';
import TableCustom from './TableCustom.svelte';
import { Highlight } from 'svelte-highlight'
import github from 'svelte-highlight/styles/github'
import { json } from 'svelte-highlight/languages'
import TableCustom from './TableCustom.svelte'
export let result: any;
export let result: any
let resultKind: 'json' | 'table-col' | 'table-row' | 'png' | 'file' | undefined =
inferResultKind(result);
inferResultKind(result)
function isArray(obj: any) {
return Object.prototype.toString.call(obj) === '[object Array]';
return Object.prototype.toString.call(obj) === '[object Array]'
}
function isRectangularArray(obj: any) {
if (!isArray(obj) || obj.length == 0) {
return false;
return false
}
if (
!Object.values(obj)
.map(isArray)
.reduce((a, b) => a && b)
) {
return false;
return false
}
let innerSize = obj[0].length;
let innerSize = obj[0].length
return Object.values(obj)
.map((x: any) => x.length == innerSize)
.reduce((a, b) => a && b);
.reduce((a, b) => a && b)
}
function asListOfList(obj: any): ArrayLike<ArrayLike<any>> {
return obj as ArrayLike<ArrayLike<any>>;
return obj as ArrayLike<ArrayLike<any>>
}
function inferResultKind(result: any) {
if (result) {
try {
let keys = Object.keys(result);
let keys = Object.keys(result)
if (keys.length == 1 && isRectangularArray(result[keys[0]])) {
return 'table-row';
return 'table-row'
} else if (keys.map((k) => isArray(result[k])).reduce((a, b) => a && b)) {
return 'table-col';
return 'table-col'
} else if (keys.length == 1 && keys[0] == 'png') {
return 'png';
return 'png'
} else if (keys.length == 1 && keys[0] == 'file') {
return 'file';
return 'file'
}
} catch (err) {}
}
return 'json';
return 'json'
}
</script>

View File

@@ -1,28 +1,28 @@
<script lang="ts">
import { clickOutside } from '../../utils';
import type { DropdownItem } from '../../utils';
import { createEventDispatcher } from 'svelte';
import Icon from 'svelte-awesome';
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
import { clickOutside } from '../../utils'
import type { DropdownItem } from '../../utils'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
let open = false;
let open = false
export let dropdownItems: DropdownItem[];
export let name: string | undefined = undefined;
export let dropdownItems: DropdownItem[]
export let name: string | undefined = undefined
// The dropdown is positioned versus its first relatively positioned partent
// By default, the dropdown is positioned relative to its button
// This can cause the dropdown to be hidden if it is in an overflow-hidden div.
// In that case, set relative to false and control the dropdown positioning from the div
export let relative = true;
export let relative = true
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
function openMenu() {
open = true;
open = true
}
function handleClickOutsideMenu(event: Event) {
open = false;
open = false
}
</script>
@@ -57,9 +57,9 @@
<button
on:click={() => {
if (!item.disabled) {
open = false;
item.action && item.action();
dispatch('click', { item: item?.eventName });
open = false
item.action && item.action()
dispatch('click', { item: item?.eventName })
}
}}
class="block hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30 px-4 py-2 text-sm text-gray-700 text-left{item.separatorTop
@@ -86,7 +86,7 @@
href={item.href}
on:click={() => {
if (!item.disabled) {
open = false;
open = false
}
}}
class="block px-4 py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30"

View File

@@ -1,201 +1,262 @@
<script lang="ts">
import { page } from '$app/stores';
import type monaco from 'monaco-editor';
import { browser, mode } from '$app/env';
import { page } from '$app/stores'
import type monaco from 'monaco-editor'
import { browser } from '$app/env'
import { listen } from '@codingame/monaco-jsonrpc';
import { onDestroy, onMount } from 'svelte';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
import {
RequestType,
toSocket,
WebSocketMessageReader,
WebSocketMessageWriter
} from '@codingame/monaco-jsonrpc'
import { onDestroy, onMount } from 'svelte'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import type { DocumentUri, MessageTransports } from 'monaco-languageclient'
import * as vscode from 'vscode'
let divEl: HTMLDivElement | null = null
let editor: monaco.editor.IStandaloneCodeEditor
let divEl: HTMLDivElement | null = null;
let editor: monaco.editor.IStandaloneCodeEditor;
let monaco;
export let lang = 'python';
export let code: string;
export let readOnly = false;
export let hash: string = (Math.random() + 1).toString(36).substring(2);
export let cmdEnterAction: (() => void) | undefined = undefined;
export let formatAction: (() => void) | undefined = undefined;
export let automaticLayout = true;
export let websocketAlive = { pyright: false, black: false };
let websockets: WebSocket[] = [];
let disposeMethod: () => void | undefined;
export let deno = false
export let lang = deno ? 'typescript' : 'python'
export let code: string
export let hash: string = (Math.random() + 1).toString(36).substring(2)
export let cmdEnterAction: (() => void) | undefined = undefined
export let formatAction: (() => void) | undefined = undefined
export let automaticLayout = true
export let websocketAlive = { pyright: false, black: false, deno: false }
let websockets: WebSocket[] = []
let uri: string = ''
let disposeMethod: () => void | undefined
if (browser) {
// @ts-ignore
self.MonacoEnvironment = {
getWorker: function (_moduleId: any, label: string) {
if (label === 'json') {
return new jsonWorker();
return new jsonWorker()
} else if (label === 'typescript' || label === 'javascript') {
return new tsWorker()
} else {
return new editorWorker()
}
if (label === 'typescript' || label === 'javascript') {
return new tsWorker();
}
return new editorWorker();
}
};
}
}
export function getCode(): string {
return editor?.getValue();
return editor?.getValue()
}
export function insertAtCursor(code: string): void {
if (editor) {
editor.trigger('keyboard', 'type', { text: code });
editor.trigger('keyboard', 'type', { text: code })
}
}
export function insertAtBeginning(code: string): void {
if (editor) {
const range = new monaco.Range(1, 1, 1, 1);
const op = { range: range, text: code, forceMoveMarkers: true };
editor.executeEdits('external', [op]);
const range = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }
const op = { range: range, text: code, forceMoveMarkers: true }
editor.executeEdits('external', [op])
}
}
export function setCode(ncode: string): void {
if (editor) {
return editor.setValue(ncode);
return editor.setValue(ncode)
} else {
code = ncode;
code = ncode
}
}
function format() {
if (editor) {
editor.getAction('editor.action.formatDocument').run();
editor.getAction('editor.action.formatDocument').run()
if (formatAction) {
formatAction();
formatAction()
}
}
}
export async function reloadWebsocket() {
closeWebsockets();
if (lang == 'python') {
closeWebsockets()
if (lang == 'python' || deno) {
// install Monaco language client services
const { MonacoLanguageClient, CloseAction, ErrorAction, createConnection } = await import(
'@codingame/monaco-languageclient'
);
const { MonacoLanguageClient } = await import('monaco-languageclient')
const { CloseAction, ErrorAction } = await import('vscode-languageclient')
function createLanguageClient(connection: any, name: string, initializationOptions?: any) {
return new MonacoLanguageClient({
function createLanguageClient(
transports: MessageTransports,
name: string,
initializationOptions?: any
) {
const client = new MonacoLanguageClient({
name: name,
clientOptions: {
documentSelector: ['python'],
documentSelector: deno ? ['typescript'] : ['python'],
errorHandler: {
error: () => ErrorAction.Shutdown,
closed: () => CloseAction.Restart
error: () => ({ action: ErrorAction.Shutdown }),
closed: () => ({
action: CloseAction.Restart
})
},
markdown: {
isTrusted: true
},
// workspaceFolder: { uri: Uri.parse(`/tmp/${name}`), name: 'tmp', index: 0 },
initializationOptions
initializationOptions,
middleware: {
workspace: {
configuration: (params, token, configuration) => {
return [
{
enable: true
}
]
}
}
}
},
connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
get: () => {
return Promise.resolve(transports)
}
}
});
})
return client
}
function connectToLanguageServer(url: string, name: string, options?: any) {
async function connectToLanguageServer(url: string, name: string, options?: any) {
try {
const webSocket = new WebSocket(url);
websockets.push(webSocket);
// listen when the web socket is opened
listen({
webSocket,
onConnection: (connection) => {
// create and start the language client
const languageClient = createLanguageClient(connection, name, options);
const disposable = languageClient.start();
websocketAlive[name] = true;
const webSocket = new WebSocket(url)
connection.onClose(() => {
websocketAlive[name] = false;
try {
disposable.dispose();
} catch (err) {
console.error('error disposing websocket', err);
}
});
}
});
webSocket.onopen = () => {
websockets.push(webSocket)
const socket = toSocket(webSocket)
const reader = new WebSocketMessageReader(socket)
const writer = new WebSocketMessageWriter(socket)
const languageClient = createLanguageClient({ reader, writer }, name, options)
languageClient.start()
socket.onClose((_code, _reason) => {
websocketAlive[name] = false
})
vscode.commands.registerCommand('deno.cache', (uris: DocumentUri[] = []) => {
languageClient.sendRequest(new RequestType('deno/cache'), {
referrer: { uri },
uris: uris.map((uri) => ({ uri }))
})
})
websocketAlive[name] = true
}
} catch (err) {
console.error(`connection to ${name} language server failed`);
console.error(`connection to ${name} language server failed`)
}
}
connectToLanguageServer(`wss://${$page.url.host}/ws/pyright`, 'pyright', {
executionEnvironments: [
{
root: '/tmp/pyright',
pythonVersion: '3.7',
pythonPlatform: 'platform',
extraPaths: []
}
]
});
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
formatters: {
black: {
command: 'black',
args: ['--quiet', '-']
if (deno) {
await connectToLanguageServer(`wss://${$page.url.host}/ws/deno`, 'deno', {
certificateStores: null,
enablePaths: [],
config: null,
importMap: null,
internalDebug: false,
lint: false,
path: null,
tlsCertificate: null,
unsafelyIgnoreCertificateErrors: null,
unstable: false,
enable: true,
cache: null,
codeLens: {
implementations: true,
references: true
},
suggest: {
autoImports: true,
completeFunctionCalls: false,
names: true,
paths: true,
imports: {
autoDiscover: true,
hosts: {
'https://deno.land': true
}
}
}
},
formatFiletypes: {
python: 'black'
}
});
})
} else {
await connectToLanguageServer(`wss://${$page.url.host}/ws/pyright`, 'pyright', {
executionEnvironments: [
{
root: '/tmp/pyright',
pythonVersion: '3.7',
pythonPlatform: 'platform',
extraPaths: []
}
]
})
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
formatters: {
black: {
command: 'black',
args: ['--quiet', '-']
}
},
formatFiletypes: {
python: 'black'
}
})
}
}
}
function closeWebsockets() {
websockets.forEach((x) => {
for (const x of websockets) {
try {
x.close();
x.close()
} catch (err) {
console.log('error disposing websocket', err);
console.log('error disposing websocket', err)
}
});
}
websockets = []
}
async function loadMonaco() {
monaco = await import('monaco-editor');
async function loadMonaco() {
const monaco = await import('monaco-editor')
if (lang == 'python') {
monaco.languages.register({
id: 'python',
extensions: ['.py'],
aliases: ['python'],
mimetypes: ['application/text']
});
})
}
let path: string = 'unknown';
let path: string = 'unknown'
if (lang == 'python') {
path = `${hash}.py`;
path = `${hash}.py`
} else if (lang == 'json') {
path = `${hash}.json`;
path = `${hash}.json`
} else if (lang == 'javascript') {
path = `${hash}.js`;
path = `${hash}.js`
} else if (lang == 'typescript') {
path = `${hash}.ts`;
path = `${hash}.ts`
}
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${path}`));
model.updateOptions({ tabSize: 4, insertSpaces: true });
uri = `file:///${path}`
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(uri))
model.updateOptions({ tabSize: 4, insertSpaces: true })
editor = monaco.editor.create(divEl as HTMLDivElement, {
model: model,
value: code,
language: lang,
automaticLayout,
readOnly: readOnly,
readOnly: false,
autoDetectHighContrast: true,
//lineNumbers: 'off',
//lineDecorationsWidth: 0,
@@ -206,21 +267,21 @@
minimap: {
enabled: false
}
});
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
format();
});
format()
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
if (cmdEnterAction) {
cmdEnterAction();
cmdEnterAction()
}
});
})
editor.onDidChangeModelContent((event) => {
code = getCode();
});
code = getCode()
})
if (lang == 'json') {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
@@ -228,19 +289,24 @@
allowComments: false,
schemas: [],
enableSchemaRequest: true
});
})
}
if (lang == 'typescript') {
// compiler options
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES6,
target: monaco.languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true,
noLib: true
});
monaco.languages.typescript.typescriptDefaults.addExtraLib(
`
})
if (deno) {
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
noSuggestionDiagnostics: true,
noSyntaxValidation: true
})
} else {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
`
/**
* get variable (including secret) at path
* @param {string} path - path of the variable (e.g: g/all/pretty_secret)
@@ -276,40 +342,41 @@ export const previous_result: any;
*/
export const params: any;
`,
'file:///node_modules/@types/windmill/index.d.ts'
);
'file:///node_modules/@types/windmill/index.d.ts'
)
}
}
if (lang == 'python' || deno) {
const { MonacoServices } = await import('monaco-languageclient')
MonacoServices.install(monaco)
}
if (lang == 'python') {
const { MonacoServices } = await import('@codingame/monaco-languageclient');
MonacoServices.install(monaco);
}
reloadWebsocket();
reloadWebsocket()
return () => {
if (editor) {
try {
editor.dispose();
closeWebsockets()
editor.dispose()
} catch (err) {
console.log('error disposing editor', err);
console.log('error disposing editor', err)
}
}
};
}
}
onMount(() => {
if (browser) {
loadMonaco().then((x) => (disposeMethod = x));
loadMonaco().then((x) => (disposeMethod = x))
}
});
})
onDestroy(() => {
if (disposeMethod) {
disposeMethod();
disposeMethod()
}
});
})
</script>
<!-- <button class="default-button px-6 max-h-8" type="button" on:click={format}>

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import Required from './Required.svelte';
import Required from './Required.svelte'
export let label: string;
export let format: string = '';
export let contentEncoding = '';
export let type: string | undefined = undefined;
export let required = false;
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined;
export let label: string
export let format: string = ''
export let contentEncoding = ''
export let type: string | undefined = undefined
export let required = false
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined
</script>
<h3>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import { type Flow, FlowService } from '../../gen';
import { type Flow, FlowService } from '../../gen'
import { sendUserToast } from '../../utils';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import Path from './Path.svelte';
import SvelteMarkdown from 'svelte-markdown';
import { workspaceStore } from '../../stores';
import ScriptSchema from './ScriptSchema.svelte';
import Required from './Required.svelte';
import FlowEditor from './FlowEditor.svelte';
import { sendUserToast } from '../../utils'
import { page } from '$app/stores'
import { goto } from '$app/navigation'
import Path from './Path.svelte'
import SvelteMarkdown from 'svelte-markdown'
import { workspaceStore } from '../../stores'
import ScriptSchema from './ScriptSchema.svelte'
import Required from './Required.svelte'
import FlowEditor from './FlowEditor.svelte'
export let flow: Flow;
export let initialPath: string = '';
export let flow: Flow
export let initialPath: string = ''
$: step = Number($page.url.searchParams.get('step')) || 1;
$: step = Number($page.url.searchParams.get('step')) || 1
async function saveFlow(): Promise<void> {
try {
@@ -28,7 +28,7 @@
value: flow.value,
schema: flow.schema
}
});
})
} else {
await FlowService.updateFlow({
workspace: $workspaceStore!,
@@ -40,16 +40,16 @@
value: flow.value,
schema: flow.schema
}
});
})
}
sendUserToast(`Success! flow saved at ${flow.path}`);
goto(`/flows/get/${flow.path}`);
sendUserToast(`Success! flow saved at ${flow.path}`)
goto(`/flows/get/${flow.path}`)
} catch (error) {
if (error.status === 400) {
sendUserToast(error.body, true);
sendUserToast(error.body, true)
} else {
sendUserToast(`Ooops.Something bad happened: ${error}`, true);
console.error(error);
sendUserToast(`Ooops.Something bad happened: ${error}`, true)
console.error(error)
}
}
}
@@ -59,12 +59,12 @@
}
async function changeStep(step: number) {
goto(`?step=${step}`);
goto(`?step=${step}`)
}
$: {
$page.url.searchParams.set('state', btoa(JSON.stringify(flow)));
history.replaceState({}, '', $page.url);
$page.url.searchParams.set('state', btoa(JSON.stringify(flow)))
history.replaceState({}, '', $page.url)
}
</script>
@@ -78,7 +78,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(1);
changeStep(1)
}}>Step 1: Metadata</button
>
<button
@@ -86,7 +86,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(2);
changeStep(2)
}}>Step 2: Flow</button
>
<button
@@ -94,7 +94,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(3);
changeStep(3)
}}>Step 3: UI customisation</button
>
</div>
@@ -105,7 +105,7 @@
(flow.path == undefined || flow.path == '' || flow.path.split('/')[2] == '')}
class="default-button px-6 max-h-8"
on:click={() => {
changeStep(step + 1);
changeStep(step + 1)
}}>Next</button
>
{#if step == 2}

View File

@@ -1,22 +1,22 @@
<script lang="ts">
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import type { Schema } from '../../common';
import { emptySchema } from '../../utils';
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import type { Schema } from '../../common'
import { emptySchema } from '../../utils'
import Icon from 'svelte-awesome';
import Icon from 'svelte-awesome'
import { type Flow, FlowModuleValue, ScriptService } from '../../gen';
import SchemaEditor from './SchemaEditor.svelte';
import type SchemaForm from './SchemaForm.svelte';
import { workspaceStore } from '../../stores';
import ModuleStep from './ModuleStep.svelte';
import FlowPreview from './FlowPreview.svelte';
import { type Flow, FlowModuleValue, ScriptService } from '../../gen'
import SchemaEditor from './SchemaEditor.svelte'
import type SchemaForm from './SchemaForm.svelte'
import { workspaceStore } from '../../stores'
import ModuleStep from './ModuleStep.svelte'
import FlowPreview from './FlowPreview.svelte'
export let flow: Flow;
export let flow: Flow
let args: Record<string, any> = {};
let schemas: Schema[] = [];
let schemaForms: (SchemaForm | undefined)[] = [];
let args: Record<string, any> = {}
let schemas: Schema[] = []
let schemaForms: (SchemaForm | undefined)[] = []
export async function loadSchemas() {
await Promise.all(
@@ -25,47 +25,47 @@
const script = await ScriptService.getScriptByPath({
workspace: $workspaceStore!,
path: x.value.path ?? ''
});
})
if (
JSON.stringify(Object.keys(script.schema?.properties ?? {}).sort()) !=
JSON.stringify(Object.keys(x.input_transform).sort())
) {
let it = {};
let it = {}
Object.keys(script.schema?.properties ?? {}).map(
(x) =>
(it[x] = {
type: 'static',
value: ''
})
);
schemaForms[i]?.setArgs(it);
)
schemaForms[i]?.setArgs(it)
}
schemas[i] = script.schema ?? emptySchema();
schemas[i] = script.schema ?? emptySchema()
} else {
schemaForms[i]?.setArgs({});
schemas[i] = emptySchema();
schemaForms[i]?.setArgs({})
schemas[i] = emptySchema()
}
})
);
schemas = schemas;
)
schemas = schemas
if (flow.value.modules.length == 0) {
addModule();
addModule()
}
}
function addModule() {
schemaForms.push(undefined);
schemaForms.push(undefined)
let newModule = {
value: { type: FlowModuleValue.type.SCRIPT, path: '' },
input_transform: {}
};
flow.value.modules = flow.value.modules.concat(newModule);
schemas.push(emptySchema());
}
flow.value.modules = flow.value.modules.concat(newModule)
schemas.push(emptySchema())
}
$: $workspaceStore && loadSchemas();
$: $workspaceStore && loadSchemas()
</script>
<!-- <PageHeader title="Flow" /> -->
@@ -87,8 +87,8 @@
const script = await ScriptService.getScriptByPath({
workspace: $workspaceStore ?? '',
path: flow.value.modules[0].value.path ?? ''
});
flow.schema = script.schema;
})
flow.schema = script.schema
}}
>Copy from step 1's schema
</button>

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