Compare commits

..

75 Commits

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

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

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

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

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

* Apply automatic changes

---------

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

* Apply automatic changes

---------

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

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

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

* empty

* more specific build conditions

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

* fix(frontend): Add reference for XSS

* fix(frontend): Require approval only in apps

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

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

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

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

* fix wrapping content

* import capitalize util

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

* feat(frontend): Fix build

* feat(frontend): revert implementation

* feat(frontend): Fix error displa

* feat(frontend): Fix error display

* feat(frontend): Fix preview mode

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

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

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

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

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

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

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

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

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

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

* feat(frontend): Fix build

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

* fix(frontend): restore placeholder
2023-01-23 05:37:38 -08:00
Ruben Fiszel
fedb9fe74c update pricing page 2023-01-22 18:34:05 +01:00
Ruben Fiszel
5408da829c update pricing page 2023-01-22 18:20:06 +01:00
Ruben Fiszel
448e45b7e4 fix privilege on cloud 2023-01-22 00:53:29 +01:00
Ruben Fiszel
156f257619 add import_map to nsjail 2023-01-21 23:28:11 +01:00
Ruben Fiszel
7f40373fd6 feat(deno): allow overriding deno sandboxing with DENO_FLAGS' 2023-01-21 22:54:14 +01:00
Ruben Fiszel
f2ec03ba1b implement relative imports for deno 2023-01-21 19:12:22 +01:00
Ruben Fiszel
9a79c5537e fix compile 2023-01-21 18:49:26 +01:00
Ruben Fiszel
cf156b0591 support BigInt serialization 2023-01-21 18:41:58 +01:00
Ruben Fiszel
832ddabdf2 feat(deno): add support for DENO_AUTH_TOKENS 2023-01-21 18:36:17 +01:00
Ruben Fiszel
22080312f0 description at bottom 2023-01-21 17:33:23 +01:00
126 changed files with 2561 additions and 944 deletions

View File

@@ -14,13 +14,13 @@ services:
windmill:
image: '${WINDMILL_IMAGE}'
privileged: false
restart: unless-stopped
ports:
- 8000:8000
entrypoint: ['/bin/sh', '-c']
command: 'echo ${OAUTH_JSON_BASE64} | base64 --decode > /usr/src/app/oauth.json && ./windmill'
environment:
- DATABASE_URL=postgres://postgres:changeme@localhost/windmill?sslmode=disable
- BASE_URL=http://localhost
- BASE_URL=${EXPECTED_URL}
- BASE_INTERNAL_URL=http://localhost:8000
- RUST_LOG=info
- NUM_WORKERS=3
@@ -28,12 +28,16 @@ services:
- DENO_PATH=/usr/bin/deno
- PYTHON_PATH=/usr/local/bin/python3
- METRICS_ADDR=false
- OAUTH_JSON_BASE64=${OAUTH_JSON_BASE64}
volumes:
- worker_dependency_cache:/tmp/windmill/cache
deploy:
resources:
limits:
memory: 250M
lsp:
image: '${LSP_IMAGE}'
restart: unless-stopped
ports:
- 3001:3001

View File

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

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

@@ -0,0 +1,47 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
name: Build and push windmill with python 3.10 and openbb
on: workflow_dispatch
concurrency:
group: ${{ github.ref }}-openbb
cancel-in-progress: true
permissions:
contents: read
id-token: write
packages: write
jobs:
build_ee:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- uses: depot/setup-action@v1
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push publicly ee
uses: depot/build-push-action@v1
with:
context: .
push: true
file: ./docker/DockerfileOpenbb
build-args: |
features=enterprise
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:openbb
labels: |
org.opencontainers.image.licenses=Windmill-Enterprise-License

View File

@@ -4,6 +4,8 @@ on:
types: [opened,synchronize,reopened,closed]
paths:
- "backend/**"
- ".github/uffizzi/**"
- ".github/workflows/**"
workflow_dispatch:
jobs:

View File

@@ -47,22 +47,35 @@ jobs:
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
cat event.json >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Read PR Number From Event Object
id: pr
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
- name: Predict Deployment URL
id: url
# Replace dots in the repo name with the plus sign
run: |
REPO=$(echo ${{ github.repository }} | sed 's/\./+/g')
echo "EXPECTED_URL=https://app.uffizzi.com/github.com/$REPO/pull/$PR_NUMBER" >> $GITHUB_ENV
- name: Re-Render Compose File
run: |
OAUTH_JSON_BASE64=${{ secrets.OAUTH_JSON_BASE64 }}
export OAUTH_JSON_BASE64
envsubst '${OAUTH_JSON_BASE64} ${EXPECTED_URL}' < docker-compose.rendered.yml > docker-compose.uffizzi.yml
# cat docker-compose.uffizzi.yml
- name: Hash Rendered Compose File
id: hash
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.uffizzi.yml | awk '{ print $1 }')" >> $GITHUB_ENV
- name: Cache Rendered Compose File
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
uses: actions/cache@v3
with:
path: docker-compose.rendered.yml
path: docker-compose.uffizzi.yml
key: ${{ env.COMPOSE_FILE_HASH }}
- name: Read PR Number From Event Object
id: pr
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
- name: DEBUG - Print Job Outputs
if: ${{ runner.debug }}
run: |
@@ -80,7 +93,7 @@ jobs:
# If this workflow was triggered by a PR close event, cache-key will be an empty string
# and this reusable workflow will delete the preview deployment.
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
compose-file-cache-path: docker-compose.rendered.yml
compose-file-cache-path: docker-compose.uffizzi.yml
server: https://app.uffizzi.com
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
permissions:

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

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

View File

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

View File

@@ -306,6 +306,11 @@ upcoming CLI tool.
| SUPERADMIN_SECRET | None | A token that would let the caller act as a virtual superadmin superadmin@windmill.dev | Server |
| TIMEOUT_WAIT_RESULT | 20 | The number of seconds to wait before timeout on the 'run_wait_result' endpoint | Worker |
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again |
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |

377
backend/Cargo.lock generated
View File

@@ -135,9 +135,9 @@ dependencies = [
[[package]]
name = "async-recursion"
version = "1.0.0"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796"
dependencies = [
"proc-macro2",
"quote",
@@ -180,9 +180,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.61"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
dependencies = [
"proc-macro2",
"quote",
@@ -217,9 +217,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1304eab461cf02bd70b083ed8273388f9724c549b316ba3d1e213ce0e9e7fb7e"
checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc"
dependencies = [
"async-trait",
"axum-core",
@@ -251,9 +251,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f487e40dc9daee24d8a1779df88522f159a54a980f99cfbe43db0be0bd3444a8"
checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
dependencies = [
"async-trait",
"bytes",
@@ -385,10 +385,20 @@ dependencies = [
]
[[package]]
name = "bumpalo"
version = "3.11.1"
name = "built"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe"
dependencies = [
"cargo-lock",
"git2",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
@@ -403,10 +413,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cc"
version = "1.0.78"
name = "cargo-lock"
version = "8.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996"
dependencies = [
"semver 1.0.16",
"serde",
"toml",
"url",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
@@ -441,9 +466,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.1.1"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"bitflags",
"clap_derive",
@@ -566,9 +591,9 @@ dependencies = [
[[package]]
name = "crc"
version = "3.0.0"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
dependencies = [
"crc-catalog",
]
@@ -636,9 +661,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.86"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579"
checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -648,9 +673,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.86"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70"
checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8"
dependencies = [
"cc",
"codespan-reporting",
@@ -663,15 +688,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.86"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c"
checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971"
[[package]]
name = "cxxbridge-macro"
version = "1.0.86"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5"
checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e"
dependencies = [
"proc-macro2",
"quote",
@@ -885,9 +910,9 @@ checksum = "131726693bce13b09331bee70734fe266666332b6ddfef23e9dca5b8bf6dea66"
[[package]]
name = "either"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
dependencies = [
"serde",
]
@@ -1030,9 +1055,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -1045,9 +1070,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -1055,15 +1080,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -1083,9 +1108,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-lite"
@@ -1104,9 +1129,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@@ -1115,21 +1140,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -1206,6 +1231,19 @@ dependencies = [
"syn",
]
[[package]]
name = "git2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "h2"
version = "0.3.15"
@@ -1554,6 +1592,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "jobserver"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.60"
@@ -1698,12 +1745,36 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libgit2-sys"
version = "0.14.2+1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]]
name = "libz-sys"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
@@ -1873,6 +1944,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -1914,9 +1994,9 @@ dependencies = [
[[package]]
name = "num-complex"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
@@ -1965,18 +2045,18 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.5.7"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -2152,9 +2232,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4257b4a04d91f7e9e6290be5d3da4804dd5784fafde3a497d73eb2b4a158c30a"
checksum = "4ab62d2fa33726dbe6321cc97ef96d8cde531e3eeaf858a058de53a8a6d40d8f"
dependencies = [
"thiserror",
"ucd-trie",
@@ -2170,15 +2250,6 @@ dependencies = [
"indexmap",
]
[[package]]
name = "phf"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_shared 0.10.0",
]
[[package]]
name = "phf"
version = "0.11.1"
@@ -2191,12 +2262,12 @@ dependencies = [
[[package]]
name = "phf_codegen"
version = "0.10.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
"phf_generator 0.11.1",
"phf_shared 0.11.1",
]
[[package]]
@@ -2341,13 +2412,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-crate"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
dependencies = [
"once_cell",
"thiserror",
"toml",
"toml_edit",
]
[[package]]
@@ -2382,9 +2452,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.49"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
@@ -2392,14 +2462,16 @@ dependencies = [
[[package]]
name = "progenitor"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#46d2df0e2a3dcbaea70669df6afb75ae27dea348"
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
dependencies = [
"anyhow",
"built",
"clap",
"openapiv3",
"progenitor-client",
"progenitor-impl",
"progenitor-macro",
"project-root",
"serde",
"serde_json",
"serde_yaml",
@@ -2408,7 +2480,7 @@ dependencies = [
[[package]]
name = "progenitor-client"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#46d2df0e2a3dcbaea70669df6afb75ae27dea348"
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
dependencies = [
"bytes",
"futures-core",
@@ -2422,7 +2494,7 @@ dependencies = [
[[package]]
name = "progenitor-impl"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#46d2df0e2a3dcbaea70669df6afb75ae27dea348"
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
dependencies = [
"getopts",
"heck",
@@ -2444,7 +2516,7 @@ dependencies = [
[[package]]
name = "progenitor-macro"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#46d2df0e2a3dcbaea70669df6afb75ae27dea348"
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
dependencies = [
"openapiv3",
"proc-macro2",
@@ -2458,6 +2530,12 @@ dependencies = [
"syn",
]
[[package]]
name = "project-root"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df"
[[package]]
name = "prometheus"
version = "0.13.3"
@@ -2618,11 +2696,11 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.13"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
dependencies = [
"base64 0.13.1",
"base64 0.21.0",
"bytes",
"encoding_rs",
"futures-core",
@@ -2650,6 +2728,7 @@ dependencies = [
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"winreg",
]
@@ -2775,9 +2854,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.36.6"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
@@ -2811,7 +2890,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#59b191c789c891a500659d2c3b29a645883f780a"
source = "git+https://github.com/RustPython/RustPython#b6e447145800ec7888f035bf266329e4b457e5f9"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2820,7 +2899,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#59b191c789c891a500659d2c3b29a645883f780a"
source = "git+https://github.com/RustPython/RustPython#b6e447145800ec7888f035bf266329e4b457e5f9"
dependencies = [
"bincode",
"bitflags",
@@ -2837,7 +2916,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#59b191c789c891a500659d2c3b29a645883f780a"
source = "git+https://github.com/RustPython/RustPython#b6e447145800ec7888f035bf266329e4b457e5f9"
dependencies = [
"ahash",
"anyhow",
@@ -2847,7 +2926,7 @@ dependencies = [
"log",
"num-bigint",
"num-traits",
"phf 0.10.1",
"phf",
"phf_codegen",
"rustc-hash",
"rustpython-ast",
@@ -2945,9 +3024,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.7.0"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
dependencies = [
"bitflags",
"core-foundation",
@@ -2958,9 +3037,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.6.1"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
dependencies = [
"core-foundation-sys",
"libc",
@@ -2989,6 +3068,9 @@ name = "semver"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
dependencies = [
"serde",
]
[[package]]
name = "semver-parser"
@@ -3138,9 +3220,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.16"
version = "0.9.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834"
checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567"
dependencies = [
"indexmap",
"itoa",
@@ -3489,9 +3571,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swc_atoms"
version = "0.4.33"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a49e93996e1c1cfdb641cd94888da545d440ff6cada04155ef118adee620c1"
checksum = "731cf66bd8e11030f056f91f9d8af77f83ec4377ff04d1670778a57d1607402a"
dependencies = [
"once_cell",
"rustc-hash",
@@ -3503,9 +3585,9 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.29.27"
version = "0.29.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90a2c285d33b47a5e532a662c178dc91956534ff52207892918d3034a534ae12"
checksum = "a97e491d31418cd33fea58e9f893316fc04b30e2b5d0e750c066e2ba4907ae54"
dependencies = [
"ahash",
"ast_node",
@@ -3530,9 +3612,9 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
version = "0.96.1"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "708345b44bedd33ff5dedfc66263070af0cad788d4bb19b51007b4583a64c43f"
checksum = "a887102d5595b557261aa4bde35f3d71906fba674d4b79cd5c59b4155b12ee2d"
dependencies = [
"bitflags",
"is-macro",
@@ -3547,9 +3629,9 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.124.2"
version = "0.124.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec6b89544ad2242a8e19d966a6c3ac8e6eb5ec5060e08bdcb01062c479b41ca"
checksum = "9e75888eabf1ad8a8968e3befc7cd20c10e4721254d3344285bd5c3a42f58dc1"
dependencies = [
"either",
"enum_kind",
@@ -3590,9 +3672,9 @@ dependencies = [
[[package]]
name = "swc_visit"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82f2bcb7223e185c4c7cbf5e0c1207dec6d2bfd5e72e3fb7b3e8d179747e9130"
checksum = "470a1963cf182fdcbbac46e3a7fd2caf7329da0e568d3668202da9501c880e16"
dependencies = [
"either",
"swc_visit_macros",
@@ -3600,9 +3682,9 @@ dependencies = [
[[package]]
name = "swc_visit_macros"
version = "0.5.4"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb1f3561674d84947694d41fb6d5737d19539222779baeac1b3a071a2b29428"
checksum = "6098b717cfd4c85f5cddec734af191dbce461c39975ed567c32ac6d0c6d61a6d"
dependencies = [
"Inflector",
"pmutil",
@@ -3767,9 +3849,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.24.1"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes",
@@ -3871,13 +3953,30 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
[[package]]
name = "toml_edit"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
dependencies = [
"indexmap",
"nom8",
"toml_datetime",
]
[[package]]
name = "toolchain_find"
version = "0.2.0"
@@ -4073,7 +4172,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typify"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#0f173877fa12c7e53c351fd1084e7cab0c6a7be1"
source = "git+https://github.com/oxidecomputer/typify#cd1f95b66b0d0fbb33537d66043f26512ee3268b"
dependencies = [
"typify-impl",
"typify-macro",
@@ -4082,7 +4181,7 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#0f173877fa12c7e53c351fd1084e7cab0c6a7be1"
source = "git+https://github.com/oxidecomputer/typify#cd1f95b66b0d0fbb33537d66043f26512ee3268b"
dependencies = [
"heck",
"log",
@@ -4100,7 +4199,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#0f173877fa12c7e53c351fd1084e7cab0c6a7be1"
source = "git+https://github.com/oxidecomputer/typify#cd1f95b66b0d0fbb33537d66043f26512ee3268b"
dependencies = [
"proc-macro2",
"quote",
@@ -4191,9 +4290,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.8"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-general-category"
@@ -4224,9 +4323,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
version = "1.10.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
@@ -4443,6 +4542,19 @@ version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "wasm-streams"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.60"
@@ -4474,9 +4586,9 @@ dependencies = [
[[package]]
name = "which"
version = "4.3.0"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
@@ -4526,7 +4638,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"axum",
@@ -4553,7 +4665,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"argon2",
@@ -4570,6 +4682,7 @@ dependencies = [
"hmac",
"hyper",
"itertools",
"lazy_static",
"magic-crypt",
"mime_guess",
"rand 0.8.5",
@@ -4605,7 +4718,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"base64 0.21.0",
"chrono",
@@ -4620,7 +4733,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"chrono",
"serde",
@@ -4633,7 +4746,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"axum",
@@ -4657,7 +4770,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"serde",
"serde_json",
@@ -4665,12 +4778,12 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"itertools",
"lazy_static",
"phf 0.11.1",
"phf",
"regex",
"unicode-general-category",
"windmill-common",
@@ -4679,11 +4792,11 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"itertools",
"phf 0.11.1",
"phf",
"unicode-general-category",
"windmill-common",
"windmill-parser",
@@ -4691,12 +4804,12 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"itertools",
"lazy_static",
"phf 0.11.1",
"phf",
"regex",
"rustpython-parser",
"serde_json",
@@ -4706,7 +4819,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"deno_core",
@@ -4720,7 +4833,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"chrono",
@@ -4743,7 +4856,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.60.0"
version = "1.61.1"
dependencies = [
"anyhow",
"async-recursion",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.60.0"
version = "1.61.1"
authors.workspace = true
edition.workspace = true
@@ -19,7 +19,7 @@ members = [
]
[workspace.package]
version = "1.60.0"
version = "1.61.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -83,7 +83,7 @@ chrono = { version = "^0", features = ["serde"] }
tracing = "^0"
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
prometheus = { version = "^0", default-features = false }
cookie = { version = "^0" }
cookie = { version = "0.16.2" }
phf = { version = "0.11", features = ["macros"] }
rust-embed = "^6"
mime_guess = "^2"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -548,6 +548,16 @@
"name": "auto_invite_operator",
"ordinal": 6,
"type_info": "Bool"
},
{
"name": "customer_id",
"ordinal": 7,
"type_info": "Varchar"
},
{
"name": "plan",
"ordinal": 8,
"type_info": "Varchar"
}
],
"nullable": [
@@ -557,6 +567,8 @@
true,
false,
true,
true,
true,
true
],
"parameters": {
@@ -1759,23 +1771,6 @@
},
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'], $3)\n WHERE id = $4\n "
},
"53f2836a652d34aafe6ffb266a092d7390212bc5532dcc226529a61a53deec42": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Bool"
]
}
},
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)"
},
"541ebd3bac65431237cf3b882dfdcd61ca97c253d9754d05bba59fda89841067": {
"describe": {
"columns": [
@@ -1845,6 +1840,16 @@
"name": "auto_invite_operator",
"ordinal": 6,
"type_info": "Bool"
},
{
"name": "customer_id",
"ordinal": 7,
"type_info": "Varchar"
},
{
"name": "plan",
"ordinal": 8,
"type_info": "Varchar"
}
],
"nullable": [
@@ -1854,6 +1859,8 @@
true,
false,
true,
true,
true,
true
],
"parameters": {
@@ -2064,6 +2071,26 @@
},
"query": "DELETE FROM group_ WHERE name = $1 AND workspace_id = $2"
},
"5ba4b87528ad49f17d72b53c3db30f5ca4b3b0b0afbd5d9721c8b5d692af601b": {
"describe": {
"columns": [
{
"name": "count",
"ordinal": 0,
"type_info": "Int8"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT COUNT(id) FROM queue WHERE email = $1"
},
"5c377fffc224a06f693c125f4c13b0a9ccfc217190ba6cf78246294bbc6c93bc": {
"describe": {
"columns": [],
@@ -4077,6 +4104,26 @@
},
"query": "\n SELECT resume_id, approver\n FROM resume_job\n WHERE job = $1\n "
},
"b053117536c067095e2fb2864ce5af33f84b22c24c92fcb870f37f0501f8ea9a": {
"describe": {
"columns": [
{
"name": "count",
"ordinal": 0,
"type_info": "Int8"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT COUNT(id) FROM queue WHERE running = true AND email = $1"
},
"b05c5f62ef4aa21d33369130cced0e9d7d128727eb58a9be7ae69cbb16bcbb27": {
"describe": {
"columns": [],
@@ -4564,6 +4611,26 @@
},
"query": "UPDATE schedule SET script_path = $1 WHERE script_path = $2 AND workspace_id = $3 AND is_flow IS false RETURNING *"
},
"c07c9276945663d062cf0ff5b3323be681a0e2cb07a457ea9aede2daeff551cc": {
"describe": {
"columns": [
{
"name": "customer_id",
"ordinal": 0,
"type_info": "Varchar"
}
],
"nullable": [
true
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "SELECT customer_id FROM workspace_settings WHERE workspace_id = $1"
},
"c1149f74ef0cca4a48be80f523d339d78e8e20e2bfacac9ad1b6b75c5ae370fb": {
"describe": {
"columns": [],
@@ -5556,6 +5623,24 @@
},
"query": "DELETE FROM password WHERE email = $1"
},
"e9c0e331c16312bf086b17c91466c5389d41454fd3f18d73c2e9554845ee9a72": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Bool",
"Varchar"
]
}
},
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin, email)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6, $7)"
},
"ea8ebb8d972fe99c960b5a69f794ee2b57bfb1914bf370c5b10313e45fa9b65f": {
"describe": {
"columns": [],

View File

@@ -262,7 +262,7 @@ mod suspend_resume {
// print_job(second, &db).await;
let tx = db.begin().await.unwrap();
let (tx, token) = windmill_worker::create_token_for_owner(tx, "test-workspace", "u/test-user", "", 100).await.unwrap();
let (tx, token) = windmill_worker::create_token_for_owner(tx, "test-workspace", "u/test-user", "", 100, "").await.unwrap();
tx.commit().await.unwrap();
let secret = reqwest::get(format!(
"http://localhost:{port}/api/w/test-workspace/jobs/job_signature/{second}/0?token={token}&approver=ruben"
@@ -365,7 +365,7 @@ mod suspend_resume {
let second = completed.next().await.unwrap();
let tx = db.begin().await.unwrap();
let (tx, token) = windmill_worker::create_token_for_owner(tx, "test-workspace", "u/test-user", "", 100).await.unwrap();
let (tx, token) = windmill_worker::create_token_for_owner(tx, "test-workspace", "u/test-user", "", 100, "").await.unwrap();
tx.commit().await.unwrap();
let secret = reqwest::get(format!(
"http://localhost:{port}/api/w/test-workspace/jobs/job_signature/{second}/0?token={token}"
@@ -395,7 +395,7 @@ mod suspend_resume {
let result = completed_job(flow, &db).await.result.unwrap();
assert_eq!(
json!( {"error": {"name": "InternalErr", "message": "{\"message\":\"Job canceled: approval request disapproved by unknown\",\"name\":\"Canceled\",\"reason\":\"approval request disapproved\",\"canceler\":\"unknown\"}"}}),
json!( {"error": {"name": "Canceled", "reason": "approval request disapproved", "message": "Job canceled: approval request disapproved by unknown", "canceler": "unknown"}}),
result
);
}

View File

@@ -70,3 +70,4 @@ cookie.workspace = true
sha2.workspace = true
urlencoding.workspace = true
async-stripe.workspace = true
lazy_static.workspace = true

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.60.0
version: 1.61.1
title: Windmill API
contact:
@@ -2445,6 +2445,44 @@ paths:
application/json:
schema: {}
/w/{workspace}/jobs/run_wait_result/f/{path}:
post:
summary: run flow by path and wait until completion
operationId: runWaitResultFlowByPath
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
- name: scheduled_for
description: when to schedule this job (leave empty for immediate run)
in: query
schema:
type: string
format: date-time
- name: scheduled_in_secs
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
- $ref: "#/components/parameters/IncludeHeader"
- $ref: "#/components/parameters/QueueLimit"
requestBody:
description: script args
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ScriptArgs"
responses:
"200":
description: job result
content:
application/json:
schema: {}
/w/{workspace}/jobs/result_by_id/{flow_job_id}/{node_id}:
get:
summary: get job result by id

View File

@@ -53,6 +53,10 @@ pub fn workspaced_service() -> Router {
"/run_wait_result/h/:hash",
post(run_wait_result_job_by_hash),
)
.route(
"/run_wait_result/f/*script_path",
post(run_wait_result_flow_by_path),
)
.route("/run/h/:hash", post(run_job_by_hash))
.route("/run/preview", post(run_preview_job))
.route("/run/preview_flow", post(run_preview_flow_job))
@@ -266,6 +270,14 @@ pub struct RunJobQuery {
queue_limit: Option<i64>,
}
lazy_static::lazy_static! {
static ref INCLUDE_HEADERS: Vec<String> = std::env::var("INCLUDE_HEADERS")
.ok().map(|x| x
.split(',')
.map(|s| s.to_string())
.collect()).unwrap_or_default();
}
impl RunJobQuery {
async fn get_scheduled_for<'c>(
&self,
@@ -286,11 +298,14 @@ impl RunJobQuery {
headers: HeaderMap,
mut args: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
self.include_header
let whitelist = self
.include_header
.as_ref()
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default()
.unwrap_or_default();
whitelist
.iter()
.chain(INCLUDE_HEADERS.iter())
.for_each(|h| {
if let Some(v) = headers.get(h) {
args.insert(
@@ -1388,6 +1403,53 @@ pub async fn run_wait_result_job_by_hash(
.await
}
pub async fn run_wait_result_flow_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
Path((w_id, flow_path)): Path<(String, StripPath)>,
Query(run_query): Query<RunJobQuery>,
headers: HeaderMap,
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
) -> error::JsonResult<serde_json::Value> {
check_queue_too_long(db, run_query.queue_limit).await?;
let flow_path = flow_path.to_path();
let mut tx = user_db.clone().begin(&authed).await?;
let scheduled_for = run_query.get_scheduled_for(&mut tx).await?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let (uuid, tx) = push(
tx,
&w_id,
JobPayload::Flow(flow_path.to_string()),
args,
&authed.username,
&authed.email,
username_to_permissioned_as(&authed.username),
scheduled_for,
None,
run_query.parent_job,
false,
false,
None,
!run_query.invisible_to_owner.unwrap_or(false),
)
.await?;
tx.commit().await?;
run_wait_result(
authed,
Extension(user_db),
timeout.0,
uuid,
Path((w_id, flow_path)),
)
.await
}
// a similar function exists on the worker
pub async fn script_path_to_payload<'c>(
script_path: &str,

View File

@@ -233,28 +233,38 @@ async fn stripe_checkout(
params.line_items = match plan.plan.as_str() {
"team" => Some(vec![
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MSdSyGU3NdFi9eLMdV6cS6F".to_string()),
quantity: Some(1),
price: Some("price_1MUlrWGU3NdFi9eLE9GBZhoY".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MShsNGU3NdFi9eLJMEZUW8b".to_string()),
..Default::default()
},
]),
"enterprise" => Some(vec![
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MSdf6GU3NdFi9eLJFRkntlx".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MShsNGU3NdFi9eLJMEZUW8b".to_string()),
price: Some("price_1MUlreGU3NdFi9eLi6sOyvVa".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MUlrlGU3NdFi9eLFLggSXZV".to_string()),
..Default::default()
},
stripe::CreateCheckoutSessionLineItems {
quantity: None,
price: Some("price_1MUlr3GU3NdFi9eLbZYFjR9p".to_string()),
..Default::default()
},
]),
// "enterprise" => Some(vec![
// stripe::CreateCheckoutSessionLineItems {
// quantity: None,
// price: Some("price_1MSdf6GU3NdFi9eLJFRkntlx".to_string()),
// ..Default::default()
// },
// stripe::CreateCheckoutSessionLineItems {
// quantity: None,
// price: Some("price_1MShsNGU3NdFi9eLJMEZUW8b".to_string()),
// ..Default::default()
// },
// ]),
_ => Err(Error::BadRequest("invalid plan".to_string()))?,
};
params.customer_email = Some(&authed.email);

View File

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

View File

@@ -8,6 +8,7 @@
use std::{collections::HashMap, str::FromStr};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Postgres, Transaction};
use tracing::{instrument, Instrument};
@@ -44,6 +45,7 @@ lazy_static::lazy_static! {
}
const MAX_FREE_EXECS: i32 = 1000;
const MAX_FREE_CONCURRENT_RUNS: i32 = 3;
pub async fn cancel_job<'c>(
username: &str,
@@ -317,10 +319,38 @@ pub async fn push<'c>(
.fetch_optional(&mut tx)
.await?
.unwrap_or(false);
if !is_super_admin && usage > MAX_FREE_EXECS {
return Err(error::Error::BadRequest(format!(
if !is_super_admin {
if usage > MAX_FREE_EXECS {
return Err(error::Error::BadRequest(format!(
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
)));
}
let in_queue =
sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE email = $1", email)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
if in_queue > MAX_FREE_EXECS.into() {
return Err(error::Error::BadRequest(format!(
"User {email} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
)));
}
let concurrent_runs = sqlx::query_scalar!(
"SELECT COUNT(id) FROM queue WHERE running = true AND email = $1",
email
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
if concurrent_runs > MAX_FREE_CONCURRENT_RUNS.into() {
return Err(error::Error::BadRequest(format!(
"User {email} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
)));
}
}
}
}
@@ -351,7 +381,9 @@ pub async fn push<'c>(
)
}
JobPayload::ScriptHub { path } => {
let script = get_hub_script(path.clone(), email).await?;
let script = get_hub_script(path.clone(), email)
.await
.context("error fetching hub script")?;
(
None,
Some(path),

View File

@@ -72,6 +72,12 @@ mount {
is_bind: true
}
mount {
src: "{JOB_DIR}/import_map.json"
dst: "/tmp/import_map.json"
is_bind: true
}
mount {
src: "{JOB_DIR}/lock.json"
dst: "/tmp/lock.json"

View File

@@ -6,7 +6,6 @@
* LICENSE-AGPL for a copy of the license.
*/
use serde_json::{Map, Value};
use sqlx::{Pool, Postgres, Transaction};
use tracing::instrument;
use uuid::Uuid;
@@ -27,16 +26,6 @@ pub async fn add_completed_job_error(
Ok(result)
}
pub fn error_to_result<E: ToString + std::fmt::Debug>(
output_map: &mut Map<String, Value>,
err: &E,
) {
output_map.insert(
"error".to_string(),
serde_json::Value::String(err.to_string()),
);
}
fn flatten_jobs(modules: Vec<FlowStatusModule>) -> Vec<Uuid> {
modules
.into_iter()

View File

@@ -49,7 +49,7 @@ use futures::{
use async_recursion::async_recursion;
use crate::{
jobs::{add_completed_job, add_completed_job_error, error_to_result},
jobs::{add_completed_job, add_completed_job_error},
worker_flow::{
handle_flow, update_flow_status_after_job_completion, update_flow_status_in_progress,
},
@@ -133,7 +133,7 @@ async fn copy_cache_to_bucket_as_tar(bucket: &str) {
.current_dir(ROOT_CACHE_DIR)
.arg("-c")
.arg("-f")
.arg(format!("{ROOT_CACHE_DIR}/{TAR_CACHE_FILENAME}"))
.arg(format!("{ROOT_CACHE_DIR}{TAR_CACHE_FILENAME}"))
.args(&["pip", "go", "deno"])
.stdin(Stdio::null())
.stdout(Stdio::null())
@@ -181,7 +181,7 @@ async fn copy_cache_from_bucket_as_tar(bucket: &str) -> bool {
match Command::new("rclone")
.arg("copyto")
.arg(format!(":s3,env_auth=true:{bucket}/{TAR_CACHE_FILENAME}"))
.arg(format!("{ROOT_CACHE_DIR}/{TAR_CACHE_FILENAME}"))
.arg(format!("{ROOT_CACHE_DIR}{TAR_CACHE_FILENAME}"))
.arg("--size-only")
.arg("--fast-list")
.stdin(Stdio::null())
@@ -234,6 +234,7 @@ pub async fn create_token_for_owner<'c>(
owner: &str,
label: &str,
expires_in: i32,
email: &str,
) -> error::Result<(Transaction<'c, Postgres>, String)> {
// TODO: Bad implementation. We should not have access to this DB here.
let token: String = rd_string(30);
@@ -245,14 +246,15 @@ pub async fn create_token_for_owner<'c>(
sqlx::query_scalar!(
"INSERT INTO token
(workspace_id, token, owner, label, expiration, super_admin)
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)",
(workspace_id, token, owner, label, expiration, super_admin, email)
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6, $7)",
&w_id,
token,
owner,
label,
expires_in.to_string(),
is_super_admin
is_super_admin,
email
)
.execute(&mut tx)
.await?;
@@ -414,10 +416,26 @@ pub async fn run_worker(
let pip_index_url = std::env::var("PIP_INDEX_URL").ok();
let pip_extra_index_url = std::env::var("PIP_EXTRA_INDEX_URL").ok();
let pip_trusted_host = std::env::var("PIP_TRUSTED_HOST").ok();
let deno_auth_tokens = std::env::var("DENO_AUTH_TOKENS")
.ok()
.map(|x| format!(";{x}"))
.unwrap_or_else(|| String::new());
let max_log_size = std::env::var("MAX_LOG_SIZE")
.ok()
.and_then(|x| x.parse::<i64>().ok())
.unwrap_or(500000);
let deno_flags = std::env::var("DENO_FLAGS")
.ok()
.map(|x| x.split(' ').map(|x| x.to_string()).collect());
let pip_local_dependencies = std::env::var("PIP_LOCAL_DEPENDENCIES")
.ok()
.map(|x| x.split(',').map(|x| x.to_string()).collect());
let pip_local_dependencies = if pip_local_dependencies == Some(vec!["".to_string()]) {
None
} else {
pip_local_dependencies
};
#[cfg(feature = "enterprise")]
let tar_cache_rate = std::env::var("TAR_CACHE_RATE")
@@ -425,6 +443,10 @@ pub async fn run_worker(
.and_then(|x| x.parse::<i32>().ok())
.unwrap_or(100);
let additional_python_paths = std::env::var("ADDITIONAL_PYTHON_PATHS")
.ok()
.map(|x| x.split(':').map(|x| x.to_string()).collect());
let envs = Envs {
deno_path,
go_path,
@@ -436,6 +458,10 @@ pub async fn run_worker(
pip_extra_index_url,
pip_trusted_host,
max_log_size,
deno_flags,
deno_auth_tokens,
pip_local_dependencies,
additional_python_paths,
};
WORKER_STARTED.inc();
@@ -570,10 +596,11 @@ pub async fn run_worker(
&job.permissioned_as,
"ephemeral-script",
timeout * 2,
&job.email,
)
.await.expect("could not create job token");
tx.commit().await.expect("could not commit job token");
let job_client = windmill_api_client::create_client(&worker_config.base_url, token.clone());
let job_client = windmill_api_client::create_client(&worker_config.base_internal_url, token.clone());
let is_flow = job.job_kind == JobKind::Flow || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::FlowDependencies;
if let Some(err) = handle_queued_job(
@@ -649,24 +676,29 @@ async fn handle_job_error(
keep_job_dir: bool,
base_internal_url: &str,
) {
let _ = add_completed_job_error(
db,
&job,
format!("Unexpected error during job execution:\n{err}"),
json!({"message": err.to_string(), "name": "InternalErr"}),
metrics.clone(),
)
.await;
let err = match err {
Error::JsonErr(err) => err,
_ => json!({"message": err.to_string(), "name": "InternalErr"}),
};
let update_job_future = || {
add_completed_job_error(
db,
&job,
format!("Unexpected error during job execution:\n{err}"),
err.clone(),
metrics.clone(),
)
};
if job.is_flow_step || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::Flow {
let (flow, job_status_to_update) = if let Some(parent_job_id) = job.parent_job {
let _ = update_job_future().await;
(parent_job_id, job.id)
} else {
(job.id, Uuid::nil())
};
let mut output_map = serde_json::Map::new();
error_to_result(&mut output_map, &err);
let updated_flow = update_flow_status_after_job_completion(
db,
client,
@@ -674,7 +706,7 @@ async fn handle_job_error(
&job_status_to_update,
&job.workspace_id,
false,
serde_json::Value::Object(output_map),
json!({ "error": err }),
metrics.clone(),
unrecoverable,
same_worker_tx,
@@ -684,9 +716,8 @@ async fn handle_job_error(
None,
)
.await;
if let Err(err) = updated_flow {
println!("error updating flow status: {}", err);
if let Err(err) = updated_flow {
if let Some(parent_job_id) = job.parent_job {
if let Ok(mut tx) = db.begin().await {
if let Ok(Some(parent_job)) =
@@ -705,7 +736,10 @@ async fn handle_job_error(
}
}
}
tracing::error!(job_id = %job.id, err = err.alt(), "error handling job: {} {} {}", job.id, job.workspace_id, job.created_by);
if job.parent_job.is_none() {
let _ = update_job_future().await;
}
tracing::error!(job_id = %job.id, "error handling job: {err:#?} {} {} {}", job.id, job.workspace_id, job.created_by);
}
async fn insert_initial_ping(
@@ -735,7 +769,11 @@ struct Envs {
pip_index_url: Option<String>,
pip_extra_index_url: Option<String>,
pip_trusted_host: Option<String>,
deno_auth_tokens: String,
deno_flags: Option<Vec<String>>,
max_log_size: i64,
pip_local_dependencies: Option<Vec<String>>,
additional_python_paths: Option<Vec<String>>,
}
fn extract_error_value(log_lines: &str) -> serde_json::Value {
@@ -758,9 +796,7 @@ async fn handle_queued_job(
base_internal_url: &str,
) -> windmill_common::error::Result<()> {
if job.canceled {
return Err(Error::ExecutionErr(
canceled_job_to_result(&job).to_string(),
))?;
return Err(Error::JsonErr(canceled_job_to_result(&job)))?;
}
if let Some(e) = job.pre_run_error {
return Err(Error::ExecutionErr(e));
@@ -886,7 +922,7 @@ async fn handle_queued_job(
}
}
err @ _ => {
json!({"message": format!("error before termination: {err:#?}"), "name": "ExecutionErr"})
json!({"message": format!("error during execution of the script:\n{}", err), "name": "ExecutionErr"})
}
};
@@ -932,6 +968,7 @@ async fn write_file(dir: &str, path: &str, content: &str) -> error::Result<File>
#[async_recursion]
async fn transform_json_value(
name: &str,
client: &windmill_api_client::Client,
workspace: &str,
v: Value,
@@ -942,7 +979,7 @@ async fn transform_json_value(
let v = client
.get_variable(workspace, path, Some(true))
.await
.map_err(|_| Error::NotFound(format!("Variable {path} not found")))
.map_err(|_| Error::NotFound(format!("Variable {path} not found for `{name}`")))
.map(|v| v.into_inner())?
.value
.unwrap_or_else(|| String::new());
@@ -951,20 +988,23 @@ async fn transform_json_value(
Value::String(y) if y.starts_with("$res:") => {
let path = y.strip_prefix("$res:").unwrap();
if path.split("/").count() < 2 {
return Err(Error::InternalErr(
format!("invalid resource path: {path}",),
));
return Err(Error::InternalErr(format!(
"Argument `{name}` is an invalid resource path: {path}",
)));
}
let v = client
.get_resource_value(workspace, path)
.await
.map_err(|_| Error::NotFound(format!("Resource {path} not found")))?
.map_err(|_| Error::NotFound(format!("Resource {path} not found for `{name}`")))?
.into_inner();
transform_json_value(client, workspace, v).await
transform_json_value(name, client, workspace, v).await
}
Value::Object(mut m) => {
for (a, b) in m.clone().into_iter() {
m.insert(a, transform_json_value(client, workspace, b).await?);
m.insert(
a.clone(),
transform_json_value(&a, client, workspace, b).await?,
);
}
Ok(Value::Object(m))
}
@@ -1411,7 +1451,7 @@ fn capitalize(s: &str) -> String {
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_deno_job(
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
Envs { nsjail_path, deno_path, path_env, max_log_size, .. }: &Envs,
Envs { nsjail_path, deno_path, path_env, max_log_size, deno_auth_tokens, deno_flags, .. }: &Envs,
logs: &mut String,
job: &QueuedJob,
db: &sqlx::Pool<sqlx::Postgres>,
@@ -1461,12 +1501,23 @@ run().catch(async (e) => {{
"#,
);
write_file(job_dir, "main.ts", &wrapper_content).await?;
let w_id = job.workspace_id.clone();
let import_map = format!(
r#"{{
"imports": {{
"/": "{base_internal_url}/api/w/{w_id}/scripts/raw/p/",
"./": "./"
}}
}}"#
);
write_file(job_dir, "import_map.json", &import_map).await?;
let mut reserved_variables = get_reserved_variables(job, &token, &base_url, db).await?;
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
let hostname_base = base_url.split("://").last().unwrap_or("localhost");
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
let deno_auth_tokens = format!("{token}@{hostname_base};{token}@{hostname_internal}");
let deno_auth_tokens =
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens}",);
let child = async {
Ok(if !disable_nsjail {
let _ = write_file(
@@ -1488,9 +1539,16 @@ run().catch(async (e) => {{
if lockfile.is_some() {
args.push("--lock=/tmp/lock.json");
}
args.push("--import-map");
args.push("/tmp/import_map.json");
args.push("--unstable");
args.push("--v8-flags=--max-heap-size=2048");
args.push("-A");
if let Some(deno_flags) = deno_flags {
for flag in deno_flags {
args.push(flag);
}
} else {
args.push("-A");
}
args.push("/tmp/main.ts");
Command::new(nsjail_path)
@@ -1507,13 +1565,18 @@ run().catch(async (e) => {{
} else {
let mut args = Vec::new();
let script_path = format!("{job_dir}/main.ts");
let import_map_path = format!("{job_dir}/import_map.json");
args.push("run");
if lockfile.is_some() {
args.push("--lock=/tmp/lock.json");
}
args.push("--import-map");
args.push(&import_map_path);
args.push("--unstable");
args.push("--v8-flags=--max-heap-size=2048");
args.push("-A");
if let Some(deno_flags) = deno_flags {
for flag in deno_flags {
args.push(flag);
}
} else {
args.push("-A");
}
args.push(&script_path);
Command::new(deno_path)
.current_dir(job_dir)
@@ -1543,7 +1606,7 @@ async fn create_args_and_out_file(
job_dir: &str,
) -> Result<(), Error> {
let args = if let Some(args) = &job.args {
Some(transform_json_value(client, &job.workspace_id, args.clone()).await?)
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
None
};
@@ -1560,7 +1623,9 @@ lazy_static! {
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_python_job(
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
envs @ Envs { nsjail_path, python_path, path_env, max_log_size, .. }: &Envs,
envs @ Envs {
nsjail_path, python_path, path_env, max_log_size, additional_python_paths, ..
}: &Envs,
requirements_o: Option<String>,
job_dir: &str,
worker_dir: &str,
@@ -1576,7 +1641,8 @@ async fn handle_python_job(
) -> error::Result<serde_json::Value> {
create_dependencies_dir(job_dir).await;
let mut additional_python_paths: Vec<String> = vec![];
let mut additional_python_paths: Vec<String> =
additional_python_paths.to_owned().unwrap_or_else(|| vec![]);
let requirements = match requirements_o {
Some(r) => r,
@@ -1683,6 +1749,15 @@ async fn handle_python_job(
} else {
""
};
let spread = if sig.star_kwargs {
"args = kwargs".to_string()
} else {
sig.args
.into_iter()
.map(|x| format!("args[\"{}\"] = kwargs[\"{}\"]", x.name, x.name))
.join("\n")
};
let wrapper_content: String = format!(
r#"
import json
@@ -1699,9 +1774,11 @@ with open("args.json") as f:
for k, v in list(kwargs.items()):
if v == '<function call>':
del kwargs[k]
args = {{}}
{spread}
{transforms}
try:
res = inner_script.main(**kwargs)
res = inner_script.main(**args)
res_json = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')
with open("result.json", 'w') as f:
f.write(res_json)
@@ -2055,7 +2132,14 @@ async fn pip_compile(
requirements: &str,
logs: &mut String,
job_dir: &str,
Envs { pip_extra_index_url, pip_index_url, pip_trusted_host, max_log_size, .. }: &Envs,
Envs {
pip_extra_index_url,
pip_index_url,
pip_trusted_host,
max_log_size,
pip_local_dependencies,
..
}: &Envs,
db: &Pool<Postgres>,
timeout: i32,
) -> error::Result<String> {
@@ -2063,6 +2147,15 @@ async fn pip_compile(
set_logs(logs, job_id, db).await;
logs.push_str(&format!("\ncontent of requirements:\n{}", requirements));
let file = "requirements.in";
let requirements = if let Some(pip_local_dependencies) = pip_local_dependencies {
let deps = pip_local_dependencies.clone();
requirements
.lines()
.filter(|s| !deps.contains(&s.to_string()))
.join("\n")
} else {
requirements.to_string()
};
write_file(job_dir, file, &requirements).await?;
let mut args = vec!["-q", "--no-header", file, "--resolver=backtracking"];
@@ -2595,6 +2688,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, timeout: i32, base_url: &str) {
&job.permissioned_as,
"ephemeral-zombie-jobs",
timeout * 2,
&job.email,
)
.await
.expect("could not create job token");

View File

@@ -427,8 +427,10 @@ pub async fn update_flow_status_after_job_completion(
"Flow job canceled".to_string()
} else if stop_early {
format!("Flow job stopped early because of a stop early predicate returning true")
} else if success {
"Flow job completed with success".to_string()
} else {
"Flow job completed".to_string()
"Flow job completed with error".to_string()
};
if flow_job.canceled {
add_completed_job_error(
@@ -986,7 +988,7 @@ async fn push_next_flow_job(
let success = false;
let skipped = false;
let logs = "Timed out waiting to be resumed".to_string();
let result = json!({ "error": logs });
let result = json!({ "error": {"message": logs, "name": "SuspendedTimeout"}});
let _uuid =
add_completed_job(db, &flow_job, success, skipped, result, logs).await?;
@@ -1881,6 +1883,7 @@ async fn get_transform_context(
&flow_job.permissioned_as,
"transform-input",
10,
&flow_job.email,
)
.await?;
//we need to commit asap otherwise the token won't be valid for auth to check outside of this transaction

View File

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

View File

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

140
docker/DockerfileOpenbb Normal file
View File

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

View File

@@ -1,20 +1,20 @@
{
"name": "windmill",
"version": "1.60.0",
"version": "1.61.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill",
"version": "1.60.0",
"version": "1.61.1",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@leeoniya/ufuzzy": "^0.9.0",
"@leeoniya/ufuzzy": "^0.9.1",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",
@@ -32,12 +32,12 @@
"@playwright/test": "^1.29.2",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0-next.589",
"@sveltejs/package": "^1.0.1",
"@sveltejs/package": "^1.0.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@types/node": "^18.11.18",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
@@ -71,7 +71,7 @@
"tailwindcss": "^3.2.2",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.0.3"
"vite": "^4.0.4"
},
"peerDependencies": {
"svelte": "^3.53.1"
@@ -709,9 +709,9 @@
"dev": true
},
"node_modules/@leeoniya/ufuzzy": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz",
"integrity": "sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg=="
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -828,15 +828,15 @@
}
},
"node_modules/@sveltejs/package": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.1.tgz",
"integrity": "sha512-iYoDz4AEJQUfdKUfBcwtYEGYkf4NMByQL3Sl2ESnu+IXsLNsHvhH0zUDhAmUmAgcrH8fVjiR7FuJeyh+7EQtiw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.2.tgz",
"integrity": "sha512-VY9U+05d9uNFDj7ScKRlHORYlfPSHwJewBjV+V2RsnViexpLFPUrboC9SiPYDCpLnbeqwXerxhO6twGHUBGeIA==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.3",
"kleur": "^4.1.5",
"sade": "^1.8.1",
"svelte2tsx": "~0.5.20"
"svelte2tsx": "~0.6.0"
},
"bin": {
"svelte-package": "svelte-package.js"
@@ -848,6 +848,20 @@
"svelte": "^3.44.0"
}
},
"node_modules/@sveltejs/package/node_modules/svelte2tsx": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
"dev": true,
"dependencies": {
"dedent-js": "^1.0.1",
"pascal-case": "^3.1.1"
},
"peerDependencies": {
"svelte": "^3.55",
"typescript": "^4.9.4"
}
},
"node_modules/@sveltejs/vite-plugin-svelte": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.2.tgz",
@@ -1001,14 +1015,14 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz",
"integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/type-utils": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -1135,13 +1149,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz",
"integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1"
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1152,13 +1166,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz",
"integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@@ -1179,9 +1193,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz",
"integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1192,13 +1206,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz",
"integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1219,16 +1233,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz",
"integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@@ -1245,12 +1259,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz",
"integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@@ -1868,11 +1882,12 @@
"peer": true
},
"node_modules/chartjs-adapter-date-fns": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz",
"integrity": "sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
"peerDependencies": {
"chart.js": "^3.0.0"
"chart.js": ">=2.8.0",
"date-fns": ">=2.0.0"
}
},
"node_modules/chartjs-plugin-zoom": {
@@ -6786,9 +6801,9 @@
}
},
"node_modules/vite": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
"dev": true,
"dependencies": {
"esbuild": "^0.16.3",
@@ -7537,9 +7552,9 @@
"dev": true
},
"@leeoniya/ufuzzy": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz",
"integrity": "sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg=="
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -7624,15 +7639,27 @@
}
},
"@sveltejs/package": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.1.tgz",
"integrity": "sha512-iYoDz4AEJQUfdKUfBcwtYEGYkf4NMByQL3Sl2ESnu+IXsLNsHvhH0zUDhAmUmAgcrH8fVjiR7FuJeyh+7EQtiw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.2.tgz",
"integrity": "sha512-VY9U+05d9uNFDj7ScKRlHORYlfPSHwJewBjV+V2RsnViexpLFPUrboC9SiPYDCpLnbeqwXerxhO6twGHUBGeIA==",
"dev": true,
"requires": {
"chokidar": "^3.5.3",
"kleur": "^4.1.5",
"sade": "^1.8.1",
"svelte2tsx": "~0.5.20"
"svelte2tsx": "~0.6.0"
},
"dependencies": {
"svelte2tsx": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
"dev": true,
"requires": {
"dedent-js": "^1.0.1",
"pascal-case": "^3.1.1"
}
}
}
},
"@sveltejs/vite-plugin-svelte": {
@@ -7755,14 +7782,14 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz",
"integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/type-utils": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -7827,41 +7854,41 @@
}
},
"@typescript-eslint/scope-manager": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz",
"integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1"
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0"
}
},
"@typescript-eslint/type-utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz",
"integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/utils": "5.46.1",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz",
"integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz",
"integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/visitor-keys": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -7870,28 +7897,28 @@
}
},
"@typescript-eslint/utils": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz",
"integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.46.1",
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/typescript-estree": "5.46.1",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz",
"integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==",
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.1",
"@typescript-eslint/types": "5.49.0",
"eslint-visitor-keys": "^3.3.0"
}
},
@@ -8314,9 +8341,9 @@
"peer": true
},
"chartjs-adapter-date-fns": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz",
"integrity": "sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
"requires": {}
},
"chartjs-plugin-zoom": {
@@ -11910,9 +11937,9 @@
"optional": true
},
"vite": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
"dev": true,
"requires": {
"esbuild": "^0.16.3",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.60.0",
"version": "1.61.1",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -18,12 +18,12 @@
"@playwright/test": "^1.29.2",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0-next.589",
"@sveltejs/package": "^1.0.1",
"@sveltejs/package": "^1.0.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@types/node": "^18.11.18",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
@@ -57,17 +57,17 @@
"tailwindcss": "^3.2.2",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.0.3"
"vite": "^4.0.4"
},
"type": "module",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@leeoniya/ufuzzy": "^0.9.0",
"@leeoniya/ufuzzy": "^0.9.1",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",

View File

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

View File

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

View File

@@ -155,6 +155,7 @@
async function next() {
if (step == 1 && manual) {
step += 1
args = {}
} else if (step == 1 && !manual) {
const url = new URL(`/api/oauth/connect/${resource_type}`, $page.url.origin)
url.searchParams.append('scopes', scopes.join('+'))
@@ -410,19 +411,20 @@
</ol>
</div>
{#if apiTokenApps[resource_type].img}
<div class="mt-4 w-full">
<img class="max-h-96 m-auto" alt="connect" src={apiTokenApps[resource_type].img} />
<div class="mt-4 w-full overflow-hidden">
<img class="m-auto max-h-60" alt="connect" src={apiTokenApps[resource_type].img} />
</div>
{/if}
{/if}
<h2 class="mt-4">Value</h2>
<div class="mt-4">
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
{#key resource_type}
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
{/key}
</div>
<h2 class="mt-4 mb-2">Description</h2>
<textarea type="text" autocomplete="off" use:autosize bind:value={description} />
{:else}
<Path

View File

@@ -7,11 +7,13 @@
import autosize from 'svelte-autosize'
export let result: any
export let requireHtmlApproval = false
let resultKind:
| 'json'
| 'table-col'
| 'table-row'
| 'html'
| 'png'
| 'file'
| 'jpeg'
@@ -19,9 +21,11 @@
| 'error'
| 'approval'
| undefined
$: resultKind = inferResultKind(result)
let forceJson = false
let enableHtml = false
function isRectangularArray(obj: any) {
if (!Array.isArray(obj) || obj.length == 0) {
@@ -53,6 +57,8 @@
return 'table-row'
} else if (keys.map((k) => Array.isArray(result[k])).reduce((a, b) => a && b)) {
return 'table-col'
} else if (keys.length == 1 && keys[0] == 'html') {
return 'html'
} else if (keys.length == 1 && keys[0] == 'png') {
return 'png'
} else if (keys.length == 1 && keys[0] == 'jpeg') {
@@ -119,6 +125,34 @@
</tbody>
</TableCustom>
</div>
{:else if !forceJson && resultKind == 'html'}
<div class="h-full">
{#if !requireHtmlApproval || enableHtml}
{@html result.html}
{:else}
<div class="font-main text-sm">
<div class="flex flex-col">
<div class="bg-red-400 py-1 rounded-t text-white font-bold text-center">
Warning
</div>
<p
class="text-gray-600 mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
>Rendering HTML can expose you to <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noreferrer" class="hover:underline">XSS attacks</a>.
Only enable it if you trust the author of the script.
</p>
</div>
<div class="center-center">
<Button
size="sm"
color="dark"
on:click={() => enableHtml = true}
>
Enable HTML rendering
</Button>
</div>
</div>
{/if}
</div>
{:else if !forceJson && resultKind == 'png'}
<div class="h-full"
><img alt="png rendered" class="w-auto h-full" src="data:image/png;base64,{result.png}" />
@@ -142,7 +176,7 @@
>
</div>
{:else if !forceJson && resultKind == 'error'}<div>
<span class="text-red-500 font-semibold text-sm"
<span class="text-red-500 font-semibold text-sm whitespace-pre-wrap"
>{result.error.name}: {result.error.message}</span
>
<pre class="text-sm whitespace-pre-wrap text-gray-900">{result.error.stack ?? ''}</pre>

View File

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

View File

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

View File

@@ -149,8 +149,8 @@
</div>
<FlowProgressBar {job} bind:reset={jobProgressReset} />
<div class="overflow-y-auto grow flex-col flex divide-y divide-gray-600 ">
<div class="max-h-1/2 overflow-auto">
<div class="overflow-y-auto grow divide-y divide-gray-600 ">
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
<SchemaForm
noVariablePicker
compact
@@ -160,8 +160,7 @@
bind:args={$previewArgs}
/>
</div>
<div class="h-full pt-4 grow">
<div class="pt-4 grow">
{#if jobId}
<FlowStatusViewer bind:flowState={$flowStateStore} {jobId} bind:job />
{:else}

View File

@@ -13,7 +13,7 @@
import Tabs from './common/tabs/Tabs.svelte'
import { FlowGraph, type GraphModuleState } from './graph'
import ModuleStatus from './ModuleStatus.svelte'
import { displayDate, isOwner, pluralize, truncateRev } from '$lib/utils'
import { displayDate, emptyString, isOwner, pluralize, truncateRev } from '$lib/utils'
import JobArgs from './JobArgs.svelte'
import Tooltip from './Tooltip.svelte'
import SimpleEditor from './SimpleEditor.svelte'
@@ -180,7 +180,11 @@
<FlowPreviewStatus {job} />
{#if `result` in job}
<div class="w-full h-full">
<FlowJobResult result={job.result} logs={job.logs ?? ''} />
<FlowJobResult
loading={job['running'] == true}
result={job.result}
logs={job.logs ?? ''}
/>
</div>
{:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS}
<div class="w-full h-full mt-2 text-sm text-gray-600">
@@ -226,14 +230,21 @@
<div class="flex flex-col gap-1">
{#each innerModules as mod, i (mod.id)}
{#if mod.type == FlowStatusModule.type.IN_PROGRESS}
{@const rawMod = job.raw_flow?.modules[i]}
<div
><span class="inline-flex gap-1"
><Badge color="indigo">{mod.id}</Badge>
{#if job.raw_flow?.modules[i]?.summary}
<span class="font-medium text-gray-900">
{job.raw_flow?.modules[i]?.summary ?? ''}
</span>
{/if}
<span class="font-medium text-gray-900">
{#if !emptyString(rawMod?.summary)}
{rawMod?.summary ?? ''}
{:else if rawMod?.value.type == 'script'}
{rawMod.value.path ?? ''}
{:else if rawMod?.value.type}
{rawMod?.value.type}
{/if}
</span>
<Loader2 class="animate-spin" /></span
></div
>
@@ -444,7 +455,13 @@
{#if selectedNode}
{@const node = localFlowModuleStates[selectedNode]}
{#if selectedNode == 'end'}
<FlowJobResult noBorder col result={job['result'] ?? {}} logs={job.logs ?? ''} />
<FlowJobResult
loading={job['running']}
noBorder
col
result={job['result']}
logs={job.logs ?? ''}
/>
{:else if selectedNode == 'start'}
{#if job.args}
<div class="p-2">
@@ -471,7 +488,13 @@
</div>
{/if}
</div>
<FlowJobResult noBorder col result={node.result ?? {}} logs={node.logs ?? ''} />
<FlowJobResult
loading={job['running'] == true}
noBorder
col
result={node.result}
logs={node.logs ?? ''}
/>
{:else}
<p class="p-2 text-gray-600 italic"
>The execution of this node has no information attached to it. The job likely did

View File

@@ -60,7 +60,11 @@
}
if (isCodeInjection(rawValue)) {
arg.expr = getDefaultExpr(argName, previousModuleId, `\`${rawValue}\``)
arg.expr = getDefaultExpr(
argName,
previousModuleId,
`\`${rawValue.toString().replaceAll('`', '\\`')}\``
)
arg.type = 'javascript'
propertyType = 'static'
} else {
@@ -117,9 +121,13 @@
const openBracket = '${'
const closeBracket = '}'
$: schema.properties[argName].default &&
!arg?.value &&
monacoTemplate?.setCode(schema.properties[argName].default)
function setDefaultCode() {
if (!arg?.value) {
monacoTemplate?.setCode(schema.properties[argName].default)
}
}
$: schema.properties[argName].default && setDefaultCode()
</script>
{#if arg != undefined}
@@ -157,7 +165,7 @@
argName,
previousModuleId,
staticTemplate
? `\`${arg?.value ?? ''}\``
? `\`${arg?.value.toString().replaceAll('`', '\\`') ?? ''}\``
: arg.value
? JSON.stringify(arg?.value, null, 4)
: ''
@@ -172,6 +180,7 @@
if (staticTemplate) {
if (arg) {
arg.value = codeToStaticTemplate(arg.expr)
arg.expr = undefined
}
setPropertyType(arg?.value)
} else {

View File

@@ -13,6 +13,7 @@
import SimpleEditor from './SimpleEditor.svelte'
import { faSave } from '@fortawesome/free-solid-svg-icons'
import SchemaForm from './SchemaForm.svelte'
import Toggle from './Toggle.svelte'
let path = ''
let initialPath = ''
@@ -38,6 +39,7 @@
path = p
resourceToEdit = undefined
resourceSchema = undefined
viewJsonSchema = false
loadingSchema = true
drawer.openDrawer?.()
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p })
@@ -111,6 +113,16 @@
args[k] = `$var:${path}`
})
}
let viewJsonSchema = false
function switchTab(asJson: boolean) {
viewJsonSchema = asJson
if (asJson) {
rawCode = JSON.stringify(args, null, 2)
} else {
parseJson()
}
}
</script>
<Drawer bind:this={drawer} size="800px">
@@ -136,12 +148,21 @@
kind="resource"
/>
</div>
<h3 class="mt-4">Value</h3>
<div class="flex w-full justify-between max-w-lg items-center mt-4">
<h3>Value</h3>
<Toggle
on:change={(e) => switchTab(e.detail)}
options={{
right: 'As JSON'
}}
/>
</div>
<div class="text-sm">
{#if loadingSchema}
<Skeleton layout={[[4]]} />
{:else if resourceSchema && resourceSchema?.properties}
{:else if !viewJsonSchema && resourceSchema && resourceSchema?.properties}
<SchemaForm
noDelete
disabled={!can_write}
compact
schema={resourceSchema}
@@ -151,10 +172,13 @@
{:else if !can_write}
<input type="text" disabled value={rawCode} />
{:else}
<p class="italic text-gray-500 text-xs mb-4"
>No corresponding resource type found in your workspace for {selectedResourceType}.
Define the value in JSON directly</p
>
{#if !viewJsonSchema}
<p class="italic text-gray-500 text-xs mb-4"
>No corresponding resource type found in your workspace for {selectedResourceType}.
Define the value in JSON directly</p
>
{/if}
{#if !emptyString(jsonError)}<span
class="text-red-400 text-xs mb-1 flex flex-row-reverse">{jsonError}</span
>{:else}<div class="py-2" />{/if}

View File

@@ -8,7 +8,6 @@
import Select from 'svelte-select'
import AppConnect from './AppConnect.svelte'
import ResourceEditor from './ResourceEditor.svelte'
import { truncate } from '$lib/utils'
const dispatch = createEventDispatcher()
let resources: Resource[] = []

View File

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

View File

@@ -60,9 +60,15 @@
localStorage.removeItem(script.path)
script.schema = script.schema ?? emptySchema()
if (!script.schema) {
try {
await inferArgs(script.language, script.content, script.schema)
} catch (error) {
sendUserToast(
`Impossible to infer the schema. Assuming this is a script without main function`,
true
)
}
const newHash = await ScriptService.createScript({
workspace: $workspaceStore!,
requestBody: {
@@ -87,7 +93,13 @@
async function changeStep(step: number) {
if (step > 1) {
script.schema = script.schema ?? emptySchema()
await inferArgs(script.language, script.content, script.schema)
try {
await inferArgs(script.language, script.content, script.schema)
} catch (error) {
console.info(
'Impossible to infer the schema. Assuming this is a script without main function'
)
}
}
goto(`?step=${step}`)
}

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
<script lang="ts">
import type { AppInput } from '../../inputType'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result', 'loading']

View File

@@ -0,0 +1,52 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result', 'loading']
let result: object | undefined = undefined
let divEl: HTMLDivElement | null = null
let Plotly
onMount(async () => {
if (divEl) {
//@ts-ignore
await import('https://cdn.plot.ly/plotly-2.18.0.min.js')
Plotly = window['Plotly']
}
})
let h: number | undefined = undefined
let w: number | undefined = undefined
$: Plotly &&
result &&
divEl &&
h &&
w &&
Plotly.newPlot(
divEl,
[result],
{ width: w, height: h, margin: { l: 40, r: 40, b: 40, t: 40, pad: 4 } },
{ responsive: true, displayModeBar: false }
)
</script>
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
{#if !Plotly}
<div class="p-2">
<Loader2 class="animate-spin" />
</div>
{/if}
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -1,9 +1,7 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { parse } from 'path'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'

View File

@@ -54,7 +54,6 @@
<RunnableWrapper
defaultUserInput
noMinH
bind:runnableComponent
bind:componentInput
{id}

View File

@@ -89,7 +89,6 @@
>
<RunnableWrapper
defaultUserInput
noMinH
bind:runnableComponent
bind:componentInput
{id}

View File

@@ -21,13 +21,21 @@
export let autoRefresh: boolean = true
export let result: any = undefined
export let forceSchemaDisplay: boolean = false
export let noMinH = false
export let defaultUserInput = false
export let flexWrap = false
export let wrapperClass = ''
const { worldStore, runnableComponents, workspace, appPath, isEditor, jobs, noBackend } =
getContext<AppEditorContext>('AppEditorContext')
const {
worldStore,
runnableComponents,
workspace,
appPath,
isEditor,
jobs,
noBackend,
errorByComponent,
mode
} = getContext<AppEditorContext>('AppEditorContext')
onMount(() => {
if (autoRefresh) {
@@ -41,7 +49,6 @@
let args: Record<string, any> = {}
let testIsLoading = false
let runnableInputValues: Record<string, any> = {}
let executeTimeout: NodeJS.Timeout | undefined = undefined
function setDebouncedExecute() {
@@ -245,7 +252,19 @@
export async function runComponent() {
await executeComponent()
}
let lastStartedAt: number = Date.now()
function recordError(error: string) {
if (testJob) {
$errorByComponent[testJob.id] = {
error: error,
componentId: id
}
}
}
$: result?.error && recordError(result.error)
</script>
{#each Object.entries(fields ?? {}) as [key, v]}
@@ -268,6 +287,15 @@
lastStartedAt = startedAt
outputs.result?.set(testJob?.result)
result = testJob.result
const previousJobId = Object.keys($errorByComponent).find(
(key) => $errorByComponent[key].componentId === id
)
if (previousJobId && !result?.error) {
delete $errorByComponent[previousJobId]
$errorByComponent = $errorByComponent
}
}
}
}}
@@ -299,19 +327,21 @@
<Alert type="warning" size="xs" class="mt-2 px-1" title="Missing runnable">
Please select a runnable
</Alert>
{:else if result?.error}
{:else if result?.error && $mode === 'preview'}
<div class="p-2">
<Alert type="error" title="Error during execution">
See "Debug Runs" on the top right for more details
<pre
title={JSON.stringify(result.error, null, 4)}
class=" mt-2 text-2xs whitespace-pre-wrap">{JSON.stringify(result.error, null, 4)}</pre
>
<div class="flex flex-col gap-2">
An error occured, please contact the app author.
<span class="font-semibold">Job id: {testJob?.id}</span>
<pre class=" whitespace-pre-wrap text-gray-900 bg-white border w-full p-4 text-xs"
>{JSON.stringify(result.error, null, 4)}
</pre>
</div>
</Alert>
<slot />
</div>
{:else}
<div class="block w-full h-full">
<div class="block grow max-h-full">
<slot />
</div>
{/if}

View File

@@ -9,7 +9,6 @@
export let componentInput: AppInput | undefined
export let id: string
export let result: any = undefined
export let noMinH = false
export let extraQueryParams: Record<string, any> = {}
export let autoRefresh: boolean = true
@@ -47,7 +46,6 @@
{id}
{extraQueryParams}
{forceSchemaDisplay}
{noMinH}
wrapperClass={runnableClass}
>
<slot />

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import { getContext } from 'svelte'
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
@@ -46,7 +46,7 @@
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
{items}
items={Array.isArray(items) ? items : []}
{value}
placeholder="Select an item"
on:click={() => {

View File

@@ -19,7 +19,13 @@
export let configuration: Record<string, AppInput>
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'search']
export const staticOutputs: string[] = [
'selectedRow',
'loading',
'result',
'search',
'selectedRowIndex'
]
type T = Record<string, any>
@@ -52,6 +58,7 @@
if (selectedRowIndex !== rowIndex) {
selectedRowIndex = rowIndex
outputs?.selectedRow.set(row.original)
outputs?.selectedRowIndex.set(rowIndex)
}
}
@@ -114,6 +121,7 @@
$: search === 'By Component' && (filteredResult = searchInResult(result ?? [], searchValue))
$: (search === 'By Runnable' || search === 'Disabled') && (filteredResult = result ?? [])
$: outputs = $worldStore?.outputsById[id] as {
selectedRowIndex: Output<number>
selectedRow: Output<any>
search: Output<string>
}
@@ -203,18 +211,20 @@
{#if actionButtons.length > 0}
<td
class="flex w-full flex-row gap-2 p-4"
class="p-2 "
on:click={() => toggleRow(row, rowIndex)}
>
{#each actionButtons as actionButton, actionIndex (actionIndex)}
<AppButton
noWFull
{...actionButton}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{/each}
<div class="center-center h-full w-full flex-wrap gap-1">
{#each actionButtons as actionButton, actionIndex (actionIndex)}
<AppButton
noWFull
{...actionButton}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{/each}
</div>
</td>
{/if}
</tr>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import type { AppComponent } from '../types'
import { displayData } from '../utils'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
export let component: AppComponent
export let resourceOnly = false
</script>
{#if component?.componentInput?.type === 'runnable' && Object.keys(component?.componentInput?.fields ?? {}).length > 0}
<div class="mb-8">
<div class="flex justify-between mb-4">
<span class="text-sm font-bold">{component.id}</span>
<span class="text-sm font-bold">{displayData[component.type].name}</span>
</div>
{#if resourceOnly && Object.keys(component.componentInput.fields).filter((fieldKey) => {
if (component?.componentInput?.type === 'runnable') {
const fields = component.componentInput?.fields
const field = fields[fieldKey]
return field.fieldType === 'object' && field.format?.startsWith('resource-')
}
return false
}).length === 0}
<span class="text-sm">No resource input</span>
{:else}
<InputsSpecsEditor
id={component.id}
shouldCapitalize={false}
bind:inputSpecs={component.componentInput.fields}
userInputEnabled={component.type !== 'buttoncomponent'}
{resourceOnly}
/>
{/if}
</div>
{/if}

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
import type { AppComponent } from '../types'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let component: AppComponent
export let resourceOnly: boolean = false
$: fields = resourceOnly ? fields : fields
</script>
<InputsSpecsEditor
id={component.id}
shouldCapitalize={false}
bind:inputSpecs={fields}
userInputEnabled={component.type !== 'buttoncomponent'}
/>

View File

@@ -39,6 +39,7 @@
export let initialMode: EditorMode = 'dnd'
export let policy: Policy
export let summary: string
export let fromHub: boolean = false
const appStore = writable<App>(app)
const worldStore = writable<World | undefined>(undefined)
@@ -54,6 +55,7 @@
})
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
setContext<AppEditorContext>('AppEditorContext', {
worldStore,
@@ -72,7 +74,9 @@
isEditor: true,
jobs: writable([]),
staticExporter: writable({}),
noBackend: false
noBackend: false,
errorByComponent,
openDebugRun: writable(undefined)
})
let timeout: NodeJS.Timeout | undefined = undefined
@@ -122,7 +126,7 @@
{#if !$userStore?.operator}
<UnsavedConfirmationModal />
{#if initialMode !== 'preview'}
<AppEditorHeader {policy} />
<AppEditorHeader {policy} {fromHub} />
{/if}
{#if previewing}

View File

@@ -35,16 +35,24 @@
Loader2,
MoreVertical,
Pencil,
SlidersHorizontal,
Smartphone
} from 'lucide-svelte'
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { appToHubUrl, classNames, copyToClipboard, sendUserToast } from '../../../utils'
import type { AppInput } from '../inputType'
import type {
AppInput,
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '../inputType'
import type { AppComponent, AppEditorContext } from '../types'
import { toStatic } from '../utils'
import AppExportButton from './AppExportButton.svelte'
import AppInputs from './AppInputs.svelte'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
async function hash(message) {
@@ -56,27 +64,60 @@
}
export let policy: Policy
export let fromHub: boolean = false
const {
app,
summary,
mode,
breakpoint,
appPath,
jobs,
staticExporter,
errorByComponent,
openDebugRun
} = getContext<AppEditorContext>('AppEditorContext')
const { app, summary, mode, breakpoint, appPath, jobs, staticExporter } =
getContext<AppEditorContext>('AppEditorContext')
const loading = {
publish: false,
save: false
}
$: if ($openDebugRun == undefined) {
$openDebugRun = (componentId: string) => {
jobsDrawerOpen = true
const job = $jobs.find((job) => job.component === componentId)
if (job) {
selectedJobId = job.job
}
}
}
let newPath: string = ''
let pathError: string | undefined = undefined
let appExport: AppExportButton
let saveDrawerOpen = false
let jobsDrawerOpen = false
let publishDrawerOpen = false
let inputsDrawerOpen = fromHub
function closeSaveDrawer() {
saveDrawerOpen = false
}
function collectStaticFields(
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
) {
return Object.fromEntries(
Object.entries(fields ?? {})
.filter(([k, v]) => v.type == 'static')
.map(([k, v]) => {
return [k, v['value']]
})
)
}
async function computeTriggerables() {
const allTriggers = await Promise.all(
$app.grid
@@ -90,13 +131,7 @@
})
.map(async (input) => {
if (input?.type == 'runnable') {
const staticInputs = Object.fromEntries(
Object.entries(input.fields ?? {})
.filter(([k, v]) => v.type == 'static')
.map(([k, v]) => {
return [k, v['value']]
})
)
const staticInputs = collectStaticFields(input.fields)
if (input.runnable?.type == 'runnableByName') {
let hex = await hash(input.runnable.inlineScript?.content)
return [`rawscript/${hex}`, staticInputs]
@@ -108,8 +143,16 @@
}
return []
})
.concat(
Object.values($app.hiddenInlineScripts).map(async (v) => {
let hex = await hash(v.inlineScript?.content)
const staticInputs = collectStaticFields(v.fields)
return [`rawscript/${hex}`, staticInputs]
})
)
)
policy.triggerables = Object.fromEntries(allTriggers)
policy.triggerables = Object.fromEntries(allTriggers.filter((x) => x.length > 0))
policy.on_behalf_of = `u/${$userStore?.username}`
policy.on_behalf_of_email = $userStore?.email
}
@@ -183,6 +226,7 @@
let testIsLoading = false
$: selectedJobId && testJobLoader?.watchJob(selectedJobId)
$: hasErrors = Object.keys($errorByComponent).length > 0
</script>
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job />
@@ -207,6 +251,12 @@
</DrawerContent>
</Drawer>
<Drawer bind:open={inputsDrawerOpen} size="600px">
<DrawerContent title="App inputs configuration" on:close={() => (inputsDrawerOpen = false)}>
<AppInputs />
</DrawerContent>
</Drawer>
<Drawer bind:open={jobsDrawerOpen} size="900px">
<DrawerContent noPadding title="Debug Runs" on:close={() => (jobsDrawerOpen = false)}>
<Splitpanes class="!overflow-visible">
@@ -218,10 +268,13 @@
{#each $jobs ?? [] as { job, component } (job)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="{classNames(
class={classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedJobId == job ? 'bg-blue-100 text-blue-600' : ''
)},"
$errorByComponent[job] ? 'border border-red-500 bg-red-100' : '',
selectedJobId == job && !$errorByComponent[component]
? 'bg-blue-100 text-blue-600'
: ''
)}
on:click={() => (selectedJobId = job)}
>
<span class="text-xs truncate">{job}</span>
@@ -243,12 +296,14 @@
{:else}
<div class="flex flex-col h-full w-full gap-4 mb-4">
{#if job?.['running']}
<div class="flex flex-row-reverse w-full"
><Button
<div class="flex flex-row-reverse w-full">
<Button
color="red"
variant="border"
on:click={() => testJobLoader?.cancelJob()}>Cancel</Button
on:click={() => testJobLoader?.cancelJob()}
>
Cancel
</Button>
</div>
{/if}
<div class="p-2">
@@ -433,10 +488,18 @@
>
<MoreVertical size={20} />
</Dropdown>
<span class="hidden md:inline">
<Button on:click={() => (inputsDrawerOpen = true)} color="light" size="xs" variant="border">
<span class="flex gap-2">
<SlidersHorizontal size={14} />
App inputs
</span>
</Button>
</span>
<span class="hidden md:inline">
<Button
on:click={() => (jobsDrawerOpen = true)}
color="light"
color={hasErrors ? 'red' : 'light'}
size="xs"
variant="border"
startIcon={{ icon: faBug }}

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import { Alert } from '$lib/components/common'
import Toggle from '$lib/components/Toggle.svelte'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import AppComponentInput from './AppComponentInput.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
const { app, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
let resourceOnly: boolean = true
</script>
<Alert type="info" title="Configurations">
In order to properly configure the app, you need to fill in the inputs below.
</Alert>
<Toggle bind:checked={resourceOnly} options={{ right: 'Resource only' }} />
{#each $lazyGrid as gridItem (gridItem.data.id)}
{#if gridItem.data.type === 'tablecomponent'}
<div>
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
<div class="ml-4 mt-4">
{#each gridItem.data.actionButtons as actionButton (actionButton.id)}
<AppComponentInput bind:component={actionButton.data} {resourceOnly} />
{/each}
</div>
</div>
{:else}
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
{/if}
{/each}
{#if $app?.hiddenInlineScripts?.length > 0}
<span class="font-bold text-sm">Background script inputs</span>
<div class="gap-4 flex flex-col">
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
<span class="text-sm">{script.name}</span>
{#if resourceOnly && Object.keys(script.fields).filter((fieldKey) => {
const fields = script.fields
const field = fields[fieldKey]
return field.fieldType === 'object' && field.format?.startsWith('resource-')
}).length === 0}
<span class="text-sm">No resource input</span>
{:else}
<InputsSpecsEditor
id={`bg_${index}`}
shouldCapitalize={false}
bind:inputSpecs={script.fields}
userInputEnabled={false}
{resourceOnly}
/>
{/if}
{/each}
</div>
{/if}

View File

@@ -59,7 +59,9 @@
isEditor,
jobs: writable([]),
staticExporter: writable({}),
noBackend
noBackend,
errorByComponent: writable({}),
openDebugRun: writable(undefined)
})
let mounted = false
@@ -74,8 +76,10 @@
</script>
<div class="relative">
<div class="{$$props.class} {lockedClasses} h-full max-h-[calc(100%-41px)] overflow-auto
w-full {app.fullscreen ? '' : 'max-w-6xl'} mx-auto">
<div
class="{$$props.class} {lockedClasses} h-full max-h-[calc(100%-41px)] overflow-auto
w-full {app.fullscreen ? '' : 'max-w-6xl'} mx-auto"
>
{#if $appStore.grid}
<div class={classNames('mx-auto pb-4', width)}>
<GridEditor {policy} />
@@ -86,10 +90,10 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
transition:fade|local={{ duration: 200, easing: cubicOut }}
on:click={() => isLocked = false}
on:click={() => (isLocked = false)}
class="absolute inset-0 center-center bg-black/20 z-50 backdrop-blur-[1px] cursor-pointer"
>
<Button on:click={() => isLocked = false}>
<Button on:click={() => (isLocked = false)}>
Unlock preview
<Unlock size={18} class="ml-1" strokeWidth={2.5} />
</Button>

View File

@@ -21,6 +21,7 @@
import AppSliderInputs from '../components/numberInputs/AppSliderInputs.svelte'
import AppFormButton from '../components/form/AppFormButton.svelte'
import VegaLiteHtml from '../components/dataDisplay/VegaLiteHtml.svelte'
import PlotlyHtml from '../components/dataDisplay/PlotlyHtml.svelte'
export let component: AppComponent
export let selected: boolean
@@ -86,6 +87,12 @@
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'plotlycomponent'}
<PlotlyHtml
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'scatterchartcomponent'}
<AppScatterChart
{...component}

View File

@@ -1,8 +1,10 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import type { AppComponent } from '../types'
import { Anchor, Move } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import type { AppComponent, AppEditorContext } from '../types'
import { Anchor, Bug, Move } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { Alert, Button } from '$lib/components/common'
export let component: AppComponent
export let selected: boolean
@@ -11,6 +13,16 @@
export let hover: boolean = false
const dispatch = createEventDispatcher()
const { errorByComponent, openDebugRun } = getContext<AppEditorContext>('AppEditorContext')
$: error = Object.values($errorByComponent).find((e) => e.componentId === component.id)
function openDebugRuns() {
if ($openDebugRun) {
$openDebugRun(component.id)
}
}
</script>
<span
@@ -56,3 +68,32 @@
<Move size={14} />
</span>
{/if}
{#if error}
{@const json = JSON.parse(JSON.stringify(error.error))}
<span
title="Error"
class={classNames(
'text-red-500 px-1 text-2xs py-0.5 font-bold w-fit absolute border border-red-500 -bottom-1 shadow left-1/2 transform -translate-x-1/2 z-50 cursor-pointer',
'bg-red-100/80'
)}
>
<Popover notClickable placement="bottom" popupClass="!bg-white border w-96">
<Bug size={14} />
<span slot="text">
<div class="bg-white">
<Alert type="error" title={`${json?.name}: ${json?.message}`}>
<div class="flex flex-col gap-2">
<div>
<pre class=" whitespace-pre-wrap text-gray-900 bg-white border w-full p-4 text-xs"
>{json?.stack ?? ''}
</pre>
</div>
<Button color="red" variant="border" on:click={openDebugRuns}>Open Debug Runs</Button>
</div>
</Alert>
</div>
</span>
</Popover>
</span>
{/if}

View File

@@ -39,7 +39,8 @@
'piechartcomponent',
'displaycomponent',
'scatterchartcomponent',
'vegalitecomponent'
'vegalitecomponent',
'plotlycomponent'
],
'3:10-6:10': ['tablecomponent']
}
@@ -55,7 +56,10 @@
function addComponent(appComponent: AppComponent) {
$dirtyStore = true
const grid = $app.grid ?? []
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
const id = getNextId(
grid.map((gridItem) => gridItem.data.id),
true
)
appComponent.id = id

View File

@@ -328,6 +328,28 @@ const display: ComponentSet = {
},
card: false
},
{
softWrap: false,
id: 'plotlycomponent',
type: 'plotlycomponent',
componentInput: {
type: 'static',
fieldType: 'object',
value: {
type: 'bar',
x: [1, 2, 3, 4],
y: [5, 10, 2, 8],
marker: {
color: '#C8A2C8',
line: {
width: 2.5
}
}
}
},
configuration: {},
card: false
},
{
softWrap: true,
horizontalAlignment: 'left',

View File

@@ -4,7 +4,7 @@
import { Script, type Preview } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { initialCode } from '$lib/script_helpers'
import { emptySchema } from '$lib/utils'
import { capitalize, emptySchema } from '$lib/utils'
import { createEventDispatcher, getContext } from 'svelte'
import { fly } from 'svelte/transition'
import { defaultCode } from '../../editorUtils'
@@ -61,7 +61,7 @@
<div class="flex gap-2 flex-row flex-wrap">
{#each langs as lang}
<FlowScriptPicker
label={lang}
label={capitalize(lang)}
{lang}
on:click={() => {
createInlineScriptByLanguage(lang, name)

View File

@@ -4,7 +4,7 @@
import { faTrash } from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher, getContext, onMount } from 'svelte'
import type { AppEditorContext, InlineScript } from '../../types'
import { CheckCircle, Code2, X } from 'lucide-svelte'
import { CheckCircle, Code2, Maximize2, Trash2, X } from 'lucide-svelte'
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
import { inferArgs } from '$lib/infer'
import type { Schema } from '$lib/common'
@@ -13,6 +13,7 @@
import Editor from '$lib/components/Editor.svelte'
import { emptySchema, scriptLangToEditorLang } from '$lib/utils'
import Tooltip from '$lib/components/Tooltip.svelte'
import Popover from '../../../Popover.svelte'
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
@@ -57,21 +58,51 @@
<InlineScriptEditorDrawer {editor} bind:this={inlineScriptEditorDrawer} bind:inlineScript />
<div class="h-full flex flex-col gap-1" transition:fly|local={{ duration: 50 }}>
<div class="flex justify-between w-full gap-1 px-2 pt-1 flex-row items-center">
<div class="flex justify-between w-full gap-2 px-2 pt-1 flex-row items-center">
{#if name !== undefined}
<input bind:value={name} placeholder="Inline script name" />
{/if}
<div class="flex w-full flex-row gap-2 items-center justify-end">
{#if validCode}
<Badge color="green">
<Badge color="green" baseClass="!p-0 !h-[30px] aspect-square center-center">
<CheckCircle size={16} />
</Badge>
{:else}
<Badge color="red">
<Badge color="red" baseClass="!p-0 !h-[30px] aspect-square center-center">
<X size={16} />
</Badge>
{/if}
{#if id.startsWith('unused-') || id.startsWith('bg_')}
<Popover notClickable placement="bottom">
<Button
size="xs"
color="red"
variant="border"
btnClasses="!px-1.5"
aria-label="Delete"
on:click={() => dispatch('delete')}
>
<Trash2 size={16} />
</Button>
<svelte:fragment slot="text">Delete</svelte:fragment>
</Popover>
{/if}
<Popover notClickable placement="bottom">
<Button
size="xs"
color="blue"
variant="border"
btnClasses="!px-1.5"
aria-label="Open full editor"
on:click={() => {
inlineScriptEditorDrawer?.openDrawer()
}}
>
<Maximize2 size={16} />
</Button>
<svelte:fragment slot="text">Open full editor</svelte:fragment>
</Popover>
<Button
variant="border"
size="xs"
@@ -80,18 +111,8 @@
editor.format()
}}
>
Format&nbsp;<Tooltip>Ctrl+S</Tooltip>
Format&nbsp;<Tooltip placement="bottom">Ctrl+S</Tooltip>
</Button>
{#if id.startsWith('unused-') || id.startsWith('bg_')}
<Button
size="xs"
color="light"
variant="border"
iconOnly
startIcon={{ icon: faTrash }}
on:click={() => dispatch('delete')}
/>
{/if}
{#if $runnableComponents[id] != undefined}
<Button
loading={runLoading}
@@ -104,22 +125,9 @@
}}
>
Run&nbsp;
<Tooltip light>Ctrl+Enter</Tooltip>
<Tooltip light placement="bottom">Ctrl+Enter</Tooltip>
</Button>
{/if}
<Button
size="xs"
color="blue"
on:click={() => {
inlineScriptEditorDrawer?.openDrawer()
}}
>
<div class="flex gap-1 items-center">
<Code2 size={16} />
Open full editor
</div>
</Button>
</div>
</div>

View File

@@ -1,7 +1,5 @@
<script lang="ts">
import { Badge, Button } from '$lib/components/common'
import Tooltip from '$lib/components/Tooltip.svelte'
import { classNames } from '$lib/utils'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
@@ -9,8 +7,11 @@
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
import { getAppScripts } from './utils'
const PREFIX = 'script-selector-' as const
export let selectedScriptComponentId: string | undefined = undefined
const { app, selectedComponent, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
let list: HTMLElement
function selectInlineScript(id: string) {
selectedScriptComponentId = id
@@ -21,13 +22,20 @@
$: runnables = getAppScripts($lazyGrid)
// When seleced component changes, update selectedScriptComponentId
$: {
if (selectedComponent) {
selectedScriptComponentId = $selectedComponent
}
// When selected component changes, update selectedScriptComponentId
$: if (selectedComponent) {
selectedScriptComponentId = $selectedComponent
}
// Doesn't work for some reason
// $: if (selectedScriptComponentId) {
// const selected = document.getElementById(PREFIX + selectedScriptComponentId)
// if(selected) {
// const top = selected.getBoundingClientRect().top - list.getBoundingClientRect().top + list.scrollTop
// list.scrollTo({ top, behavior: 'smooth' })
// }
// }
function createBackgroundScript() {
let index = 0
let newScriptPath = `Background Script ${index}`
@@ -49,28 +57,27 @@
fields: {}
})
$app.hiddenInlineScripts = $app.hiddenInlineScripts
selectInlineScript(`bg_${$app.hiddenInlineScripts.length - 1}`)
}
</script>
<div class="min-h-full flex flex-col gap-4">
<div bind:this={list} class="min-h-full flex flex-col gap-4">
<PanelSection title="Inline scripts" smallPadding>
<div class="flex flex-col gap-2 w-full">
{#if runnables.inline.length > 0}
<div class="flex gap-2 flex-col ">
{#each runnables.inline as { name, id }, index (index)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="{classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-sm cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedScriptComponentId === id ? 'border-blue-500 border' : ''
)},"
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center p-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectInlineScript(id)}
>
<span class="text-xs truncate">{name}</span>
<div>
<Badge color="dark-indigo">{id}</Badge>
</div>
</div>
</button>
{/each}
</div>
{/if}
@@ -78,17 +85,16 @@
{#if $app.unusedInlineScripts?.length > 0}
<div class="flex gap-2 flex-col ">
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="{classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedScriptComponentId === `unused-${index}` ? 'bg-blue-100 text-blue-600' : ''
)},"
on:click={() => selectInlineScript(`unused-${index}`)}
{@const id = `unused-${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center p-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectInlineScript(id)}
>
<span class="text-xs truncate">{unusedInlineScript.name}</span>
<Badge color="red">Detached</Badge>
</div>
</button>
{/each}
</div>
{/if}
@@ -104,17 +110,15 @@
{#if runnables.imported.length > 0}
<div class="flex gap-2 flex-col ">
{#each runnables.imported as { name, id }, index (index)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="{classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedScriptComponentId === id ? 'bg-blue-100 text-blue-600' : ''
)},"
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center p-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectInlineScript(id)}
>
<span class="text-xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</div>
</button>
{/each}
</div>
{:else}
@@ -123,31 +127,31 @@
</div>
</PanelSection>
<PanelSection title="Background scripts" smallPadding>
<PanelSection
title="Background scripts"
tooltip="Background scripts are triggered upon global refresh or when their input changes. The result
of a background script can be shared among many components."
smallPadding
>
<svelte:fragment slot="action">
<Button size="xs" color="dark" startIcon={{ icon: faPlus }} on:click={createBackgroundScript}>
Add
</Button>
<Tooltip>
Background scripts are triggered upon global refresh or when their input changes. The result
of a background script can be shared among many components.
</Tooltip>
</svelte:fragment>
<div class="flex flex-col gap-2 w-full">
{#if $app.hiddenInlineScripts?.length > 0}
<div class="flex gap-2 flex-col ">
{#each $app.hiddenInlineScripts as { name }, index (index)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="{classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedScriptComponentId === `bg_${index}` ? 'bg-blue-100 text-blue-600' : ''
)},"
on:click={() => selectInlineScript(`bg_${index}`)}
{@const id = `bg_${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center p-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectInlineScript(id)}
>
<span class="text-xs truncate">{name}</span>
<Badge color="yellow">Background</Badge>
</div>
</button>
{/each}
</div>
{:else}

View File

@@ -14,6 +14,7 @@
export let userInputEnabled: boolean = true
export let shouldCapitalize: boolean = true
export let rowColumns = false
export let resourceOnly = false
const { connectingInput } = getContext<AppEditorContext>('AppEditorContext')
</script>
@@ -22,97 +23,99 @@
<div class="w-full flex flex-col gap-4">
{#each Object.keys(inputSpecs) as inputSpecKey (inputSpecKey)}
{@const input = inputSpecs[inputSpecKey]}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize
? capitalize(addWhitespaceBeforeCapitals(inputSpecKey))
: inputSpecKey}
{#if input.tooltip}
<Tooltip>
{input.tooltip}
</Tooltip>
{/if}
</span>
{#if !(resourceOnly && (input.fieldType !== 'object' || !input.format?.startsWith('resource-')))}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize
? capitalize(addWhitespaceBeforeCapitals(inputSpecKey))
: inputSpecKey}
{#if input.tooltip}
<Tooltip>
{input.tooltip}
</Tooltip>
{/if}
</span>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{input.fieldType === 'array' && input.subFieldType
? `${capitalize(fieldTypeToTsType(input.subFieldType))}[]`
: capitalize(fieldTypeToTsType(input.fieldType))}
</Badge>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{input.fieldType === 'array' && input.subFieldType
? `${capitalize(fieldTypeToTsType(input.subFieldType))}[]`
: capitalize(fieldTypeToTsType(input.fieldType))}
</Badge>
{#if !inputSpecs[inputSpecKey].onlyStatic && inputSpecs[inputSpecKey].type != 'eval'}
<ToggleButtonGroup
bind:selected={inputSpecs[inputSpecKey].type}
on:selected={(e) => {
if (e.detail == 'connected' && !inputSpecs[inputSpecKey]['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
{#if !inputSpecs[inputSpecKey].onlyStatic && inputSpecs[inputSpecKey].type != 'eval'}
<ToggleButtonGroup
bind:selected={inputSpecs[inputSpecKey].type}
on:selected={(e) => {
if (e.detail == 'connected' && !inputSpecs[inputSpecKey]['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
}
}
}}
>
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{#if rowColumns}
}}
>
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="row"
startIcon={{ icon: faTableCells }}
size="xs"
>
<Tooltip scale={0.6} placement="top-end" wrapperClass="center-center">
Use the column name to have the value of the cell be passed to the action
</Tooltip>
</ToggleButton>
<svelte:fragment slot="text">Column</svelte:fragment>
</Popover>
{/if}
{#if userInputEnabled && !input.format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="user"
startIcon={{ icon: faUser }}
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">User Input</svelte:fragment>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{/if}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Connect</svelte:fragment>
</Popover>
</ToggleButtonGroup>
{/if}
{#if rowColumns}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="row"
startIcon={{ icon: faTableCells }}
size="xs"
>
<Tooltip scale={0.6} placement="top-end" wrapperClass="center-center">
Use the column name to have the value of the cell be passed to the action
</Tooltip>
</ToggleButton>
<svelte:fragment slot="text">Column</svelte:fragment>
</Popover>
{/if}
{#if userInputEnabled && !input.format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">User Input</svelte:fragment>
</Popover>
{/if}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Connect</svelte:fragment>
</Popover>
</ToggleButtonGroup>
{/if}
</div>
</div>
</div>
<InputsSpecEditor
hasRows={rowColumns}
{id}
bind:componentInput={inputSpecs[inputSpecKey]}
/>
</div>
<InputsSpecEditor
hasRows={rowColumns}
{id}
bind:componentInput={inputSpecs[inputSpecKey]}
/>
</div>
{/if}
{/each}
</div>
{:else}

View File

@@ -147,6 +147,39 @@ def main():
"y": { "field": "b", "type": "quantitative" },
},
}
`
},
plotlycomponent: {
deno:
`
export async function main() {
return {
type: 'bar',
x: [1, 2, 3, 4],
y: [5, 10, 2, 8],
marker: {
color: '#C8A2C8',
line: {
width: 2.5
}
}
};
}
`,
python3:
`
def main():
return {
"type": "bar",
"x": [1, 2, 3, 4],
"y": [5, 10, 2, 8],
"marker": {
"color": "#C8A2C8",
"line": {
"width": 2.5
}
}
}
`
},
piechartcomponent: {

View File

@@ -25,6 +25,7 @@ export type NumberInputComponent = BaseComponent<'numberinputcomponent'>
export type SliderComponent = BaseComponent<'slidercomponent'>
export type HtmlComponent = BaseComponent<'htmlcomponent'>
export type VegaLiteComponent = BaseComponent<'vegalitecomponent'>
export type PlotlyComponent = BaseComponent<'plotlycomponent'>
export type TimeseriesComponent = BaseComponent<'timeseriescomponent'>
export type ButtonComponent = BaseComponent<'buttoncomponent'> & {
recomputeIds: string[] | undefined
@@ -107,6 +108,7 @@ export type AppComponent = BaseAppComponent &
| FormComponent
| FormButtonComponent
| VegaLiteComponent
| PlotlyComponent
)
export type ComponentSet = {
@@ -172,9 +174,13 @@ export type AppEditorContext = {
isEditor: boolean
jobs: Writable<{ job: string; component: string }[]>
noBackend: boolean
errorByComponent: Writable<Record<string, { error: string; componentId: string }>>
openDebugRun: Writable<((componentID: string) => void) | undefined>
}
export type EditorMode = 'dnd' | 'preview'
export type EditorBreakpoint = 'sm' | 'lg'
export const IS_APP_PUBLIC_CONTEXT_KEY = 'isAppPublicContext' as const
type ComponentID = string

View File

@@ -122,6 +122,10 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
name: 'Vega Lite',
icon: PieChart
},
plotlycomponent: {
name: 'Plotly',
icon: PieChart
},
timeseriescomponent: {
name: 'Timeseries',
icon: GripHorizontal

View File

@@ -68,7 +68,7 @@
colorVariants?.[color]?.[variant],
variant === 'border' ? 'border' : '',
ButtonType.FontSizeClasses[size],
ButtonType.SpacingClasses[spacingSize],
ButtonType.SpacingClasses[spacingSize][variant],
'focus:ring-2 font-semibold',
'duration-200 rounded-md',
'justify-center items-center text-center whitespace-nowrap inline-flex',
@@ -79,7 +79,8 @@
target,
tabindex: disabled ? -1 : 0,
type: buttonType,
title
title,
...$$restProps
}
async function onClick(event: MouseEvent) {

View File

@@ -20,12 +20,27 @@ export namespace ButtonType {
xl: 'text-xl'
} as const
export const SpacingClasses: Record<ButtonType.Size, string> = {
xs: 'px-3 py-1.5',
sm: 'px-3 py-1.5',
md: 'px-3 py-1.5',
lg: 'px-4 py-2',
xl: 'px-4 py-2'
export const SpacingClasses: Record<ButtonType.Size, Record<ButtonType.Variant, string>> = {
xs: {
border: 'px-3 py-[6px]',
contained: 'px-3 py-[7px]'
},
sm: {
border: 'px-3 py-[6px]',
contained: 'px-3 py-[7px]'
},
md: {
border: 'px-3 py-[6px]',
contained: 'px-3 py-[7px]'
},
lg: {
border: 'px-4 py-[8px]',
contained: 'px-4 py-[9px]'
},
xl: {
border: 'px-4 py-[8px]',
contained: 'px-4 py-[9px]'
},
} as const
export const IconScale: Record<ButtonType.Size, number> = {

View File

@@ -55,8 +55,10 @@
if (open) {
switch (event.key) {
case 'Escape':
open = false
event.preventDefault()
event.stopPropagation()
event.stopImmediatePropagation()
open = false
break
}
}

View File

@@ -6,7 +6,7 @@
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { flowStore, initFlow } from '$lib/components/flows/flowStore'
import { importFlowStore } from '$lib/components/flows/flowStore'
import { Icon } from 'svelte-awesome'
let drawer: Drawer | undefined = undefined
@@ -14,9 +14,7 @@
async function importJson() {
await goto('/flows/add')
Object.assign($flowStore, JSON.parse(pendingJson))
initFlow($flowStore)
$importFlowStore = JSON.parse(pendingJson)
drawer?.closeDrawer?.()
}
</script>

View File

@@ -39,12 +39,14 @@
editor?.insertAtCursor(detail)
}}
>
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={branch.expr}
class="small-editor"
shouldBindKey={false}
extraLib={stepPropPicker.extraLib}
/>
<div class="border border-gray-400">
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={branch.expr}
class="small-editor border "
shouldBindKey={false}
extraLib={stepPropPicker.extraLib}
/>
</div>
</PropPickerWrapper>

View File

@@ -27,133 +27,143 @@
</script>
<div class="p-4 h-full flex flex-col">
{#if !failureModule}
<div class="center-center">
<div class="max-w-min">
<ToggleButtonGroup bind:selected={kind}>
<ToggleButton position="left" value="script" size="sm" startIcon={{ icon: faCode }}>
Action &nbsp;<Tooltip>
An action script is simply a script that is neither a trigger nor an approval script.
Those are the majority of the scripts.
</Tooltip>
</ToggleButton>
{#if !shouldDisableTriggerScripts}
<ToggleButton position="center" value="trigger" size="sm" startIcon={{ icon: faBolt }}>
Trigger &nbsp;<Tooltip>
Used as a first step most commonly with a state and a schedule to watch for changes
on an external system, compute the diff since last time, set the new state. The
diffs are then treated one by one with a for-loop.
{#if summary == 'Terminate flow'}
<Alert role="info" title="The flow stops here"
>This is an identity step with an early stop that has 'true' for expression</Alert
>
{:else}{#if !failureModule}
<div class="center-center">
<div class="max-w-min">
<ToggleButtonGroup bind:selected={kind}>
<ToggleButton position="left" value="script" size="sm" startIcon={{ icon: faCode }}>
Action &nbsp;<Tooltip>
An action script is simply a script that is neither a trigger nor an approval
script. Those are the majority of the scripts.
</Tooltip>
</ToggleButton>
{/if}
<ToggleButton position="right" value="approval" size="sm" startIcon={{ icon: faCheck }}>
Approval &nbsp;<Tooltip>
An approval step will suspend the execution of a flow until it has been approved
through the resume endpoints or the approval page by and solely by the recipients of
those secret urls. Use `wmill.getResumeUrls()` in Typescript or
`wmill.get_resume_urls()` in Python from the wmill client to generate those URLs.
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
{#if !shouldDisableTriggerScripts}
<ToggleButton
position="center"
value="trigger"
size="sm"
startIcon={{ icon: faBolt }}
>
Trigger &nbsp;<Tooltip>
Used as a first step most commonly with a state and a schedule to watch for
changes on an external system, compute the diff since last time, set the new
state. The diffs are then treated one by one with a for-loop.
</Tooltip>
</ToggleButton>
{/if}
<ToggleButton position="right" value="approval" size="sm" startIcon={{ icon: faCheck }}>
Approval &nbsp;<Tooltip>
An approval step will suspend the execution of a flow until it has been approved
through the resume endpoints or the approval page by and solely by the recipients of
those secret urls. Use `wmill.getResumeUrls()` in Typescript or
`wmill.get_resume_urls()` in Python from the wmill client to generate those URLs.
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
</div>
</div>
</div>
{/if}
{#if kind == 'trigger'}
<div class="mt-2" />
<Alert title="Trigger script automatic schedule" role="info">
A schedule will be automatically attached to this flow to run every 15 minutes. Adjust
frequency in 'Settings -> Schedule'</Alert
>
{/if}
<h3 class="pb-2 pt-4">
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
<Tooltip>
Embed a script directly inside a flow instead of saving the script into your workspace for
reuse. You can always save an inline script to your workspace later.
</Tooltip>
</h3>
<div class="flex flex-row">
<div class="flex flex-row flex-wrap gap-2">
<FlowScriptPicker
label="Typescript"
lang={Script.language.DENO}
on:click={() => {
dispatch('new', {
language: RawScript.language.DENO,
kind,
subkind: 'flow'
})
}}
/>
<FlowScriptPicker
label="Python"
lang={Script.language.PYTHON3}
on:click={() => {
dispatch('new', {
language: RawScript.language.PYTHON3,
kind,
subkind: 'flow'
})
}}
/>
{#if kind != 'approval'}
{/if}
{#if kind == 'trigger'}
<div class="mt-2" />
<Alert title="Trigger script automatic schedule" role="info">
A schedule will be automatically attached to this flow to run every 15 minutes. Adjust
frequency in 'Settings -> Schedule'</Alert
>
{/if}
<h3 class="pb-2 pt-4">
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
<Tooltip>
Embed a script directly inside a flow instead of saving the script into your workspace for
reuse. You can always save an inline script to your workspace later.
</Tooltip>
</h3>
<div class="flex flex-row">
<div class="flex flex-row flex-wrap gap-2">
<FlowScriptPicker
label="Go"
lang={Script.language.GO}
label="Typescript"
lang={Script.language.DENO}
on:click={() => {
dispatch('new', {
language: RawScript.language.GO,
kind,
subkind: 'flow'
})
}}
/>
{/if}
{#if kind == 'script'}
<FlowScriptPicker
label="Bash"
lang={Script.language.BASH}
on:click={() => {
dispatch('new', {
language: RawScript.language.BASH,
language: RawScript.language.DENO,
kind,
subkind: 'flow'
})
}}
/>
{#if !failureModule}
<FlowScriptPicker
label={`PostgreSQL`}
lang="pgsql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
/>
<FlowScriptPicker
label="Python"
lang={Script.language.PYTHON3}
on:click={() => {
dispatch('new', {
language: RawScript.language.PYTHON3,
kind,
subkind: 'flow'
})
}}
/>
{#if kind != 'approval'}
<FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
label="Go"
lang={Script.language.GO}
on:click={() => {
dispatch('new', {
language: RawScript.language.GO,
kind,
subkind: 'flow'
})
}}
/>
{/if}
{/if}
</div>
</div>
<h3 class="mb-2 mt-6"
>Use pre-made <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script</h3
>
{#if pick_existing == 'hub'}
<PickHubScript bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</PickHubScript>
{:else}
<WorkspaceScriptPicker displayLock bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</WorkspaceScriptPicker>
{#if kind == 'script'}
<FlowScriptPicker
label="Bash"
lang={Script.language.BASH}
on:click={() => {
dispatch('new', {
language: RawScript.language.BASH,
kind,
subkind: 'flow'
})
}}
/>
{#if !failureModule}
<FlowScriptPicker
label={`PostgreSQL`}
lang="pgsql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
/>
<FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
/>
{/if}
{/if}
</div>
</div>
<h3 class="mb-2 mt-6"
>Use pre-made <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script</h3
>
{#if pick_existing == 'hub'}
<PickHubScript bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</PickHubScript>
{:else}
<WorkspaceScriptPicker displayLock bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</WorkspaceScriptPicker>
{/if}
{/if}
</div>

View File

@@ -35,9 +35,8 @@
<h2 class="mt-2"
>Retries <Tooltip>
If defined, upon error this step will be retried with a delay and a maximum number of attempts
as defined below. If none of the retries succeed, the step job is a failure and the error will
propagate up in the case of a branch, and the error handler will be called ultimately if not
handled prior.</Tooltip
as defined below. If both static and exponential delay is defined, the static delay attempts
are tried before the exponential ones.</Tooltip
></h2
>
@@ -76,22 +75,25 @@
}
}}
options={{
right: 'Exponential retry enabled'
right: 'Exponential backoff enabled'
}}
/>
{#if flowModule.retry?.exponential}
<span class="text-xs font-bold">Attempts</span>
<input bind:value={flowModule.retry.exponential.attempts} type="number" />
<span class="text-xs font-bold">Mulitplier</span>
<span class="text-xs text-gray-500">additional delay = multiplier * base ^ (# attempts)</span>
<input bind:value={flowModule.retry.exponential.multiplier} type="number" />
<span class="text-xs font-bold">Initial delay (seconds)</span>
<span class="text-xs font-bold">Base</span>
<input bind:value={flowModule.retry.exponential.seconds} type="number" />
{:else}
<span class="text-xs font-bold">Attempts</span>
<input type="number" disabled />
<span class="text-xs font-bold">Mulitplier</span>
<span class="text-xs text-gray-500">additional delay = multiplier * seconds ^ (# attempts)</span
>
<input type="number" disabled />
<span class="text-xs font-bold">Initial delay (seconds)</span>
<span class="text-xs font-bold">Base</span>
<input type="number" disabled />
{/if}
</div>

View File

@@ -14,12 +14,20 @@
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import autosize from 'svelte-autosize'
import Slider from '$lib/components/Slider.svelte'
import { page } from '$app/stores'
import { workspaceStore } from '$lib/stores'
import { copyToClipboard } from '$lib/utils'
import { Icon } from 'svelte-awesome'
import { faClipboard } from '@fortawesome/free-solid-svg-icons'
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
export let initialPath: string
let topHeight = 0
$: url = `${$page.url.hostname}/api/w/${$workspaceStore}/jobs/run/f/${$flowStore?.path}`
</script>
<div class="h-full overflow-hidden">
@@ -32,34 +40,78 @@
<Tab value="settings-graph">Graph</Tab>
<svelte:fragment slot="content">
<TabContent value="settings-metadata" class="p-4">
<Path bind:path={$flowStore.path} {initialPath} namePlaceholder="my_flow" kind="flow" />
<label class="block my-4">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<input
type="text"
bind:value={$flowStore.summary}
placeholder="A short summary of the flow displayed when the flow is listed"
id="flow-summary"
<TabContent value="settings-metadata" class="p-4 h-full">
<div class="overflow-auto h-full">
<Path
bind:path={$flowStore.path}
{initialPath}
namePlaceholder="my_flow"
kind="flow"
/>
</label>
<label class="block my-4" for="inp">
<span class="text-gray-700 text-sm">
Description
<Required required={false} />
<textarea
use:autosize
<label class="block my-4">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<input
type="text"
class="text-sm"
id="inp"
bind:value={$flowStore.description}
placeholder="A description to help users understand what this flow does and how to use it."
rows="3"
bind:value={$flowStore.summary}
placeholder="A short summary of the flow displayed when the flow is listed"
id="flow-summary"
/>
</span>
</label>
</label>
<label class="block my-4" for="inp">
<span class="text-gray-700 text-sm">
Description
<Required required={false} />
<textarea
use:autosize
type="text"
class="text-sm"
id="inp"
bind:value={$flowStore.description}
placeholder="A description to help users understand what this flow does and how to use it."
rows="3"
/>
</span>
</label>
<Slider text="How to trigger from external events?">
<div class="text-sm text-gray-600 border p-4">
There are 2 ways to trigger a flow based on external events:
<ul class="pt-4">
<li
>1. Send a webhook after each event: <a
on:click={(e) => {
e.preventDefault()
copyToClipboard(url)
}}
href={$page.url.protocol + '//' + url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
<span class="text-gray-700 ml-2">
<Icon data={faClipboard} />
</span>
</a>
</li>
<li class="mt-2">
<div class="flex flex-row gap-2">
2. Use a trigger script and schedule this flow to run as frequently as
needed and compare a state persisted in Windmill to the state of the
external system. If a difference is detected, then the rest of the flow is
triggered. Oftentimes, the second step of a flow is a for-loop that will
iterate over every elements. When using a trigger, a default schedule will
be created.
<img
class="shadow-lg border rounded"
alt="static button"
src="/trigger_button.png"
/>
</div>
</li></ul
>
</div>
</Slider>
</div>
</TabContent>
<TabContent value="settings-schedule" class="p-4">
<Alert type="info" title="Primary Schedule">

View File

@@ -32,7 +32,7 @@ export async function loadFlowModuleState(flowModule: FlowModule): Promise<FlowM
export const idMutex = new Mutex()
export function getNextId(currentKeys: string[]): string {
export function getNextId(currentKeys: string[], skipTilde: boolean = false): string {
const max = currentKeys.reduce((acc, key) => {
if (key === 'failure' || key.includes('branch') || key.includes('loop')) {
return acc

View File

@@ -6,6 +6,7 @@ import { numberToChars } from './utils'
export type FlowMode = 'push' | 'pull'
export const importFlowStore = writable<Flow | undefined>(undefined)
export const flowStore = writable<Flow>({
summary: '',
value: { modules: [] },
@@ -53,7 +54,6 @@ export async function initFlow(flow: Flow) {
}
await initFlowState(flow)
flowStore.set(flow)
function migrateFlowModule(mod: FlowModule) {

View File

@@ -85,7 +85,7 @@
</div>
<div>
<FlowModuleSchemaMap bind:modules={branch.modules} />
<FlowModuleSchemaMap parentType="branchall" bind:modules={branch.modules} />
</div>
</div>
{/each}

View File

@@ -69,7 +69,7 @@
</span>
</div>
<div>
<FlowModuleSchemaMap bind:modules={module.value.default} />
<FlowModuleSchemaMap parentType="branchone" bind:modules={module.value.default} />
</div>
{#each module.value.branches ?? [] as branch, branchIndex (branchIndex)}
@@ -108,7 +108,7 @@
</div>
<div>
<FlowModuleSchemaMap bind:modules={branch.modules} />
<FlowModuleSchemaMap parentType="branchone" bind:modules={branch.modules} />
</div>
</div>
{/each}

View File

@@ -23,13 +23,14 @@
export let root: boolean = false
export let modules: FlowModule[] | undefined
export let parentType: 'flow' | 'forloop' | 'branchall' | 'branchone' | undefined = undefined
let indexToRemove: number | undefined = undefined
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
async function insertNewModuleAtIndex(
index: number,
kind: 'script' | 'forloop' | 'branchone' | 'branchall' | 'flow' | 'trigger' | 'approval'
kind: 'script' | 'forloop' | 'branchone' | 'branchall' | 'flow' | 'trigger' | 'approval' | 'end'
): Promise<void> {
await idMutex.runExclusive(async () => {
var module = emptyModule(kind == 'flow')
@@ -50,6 +51,9 @@
flowModule.summary = 'Trigger'
} else if (kind == 'approval') {
flowModule.summary = 'Approval'
} else if (kind == 'end') {
flowModule.summary = 'Terminate flow'
flowModule.stop_after_if = { skip_if_stopped: false, expr: 'true' }
}
select(flowModule.id)
})
@@ -136,10 +140,15 @@
</div>
{/each}
{/if}
<InsertModuleButton
trigger={modules?.length == 0}
on:new={(e) => insertNewModuleAtIndex(modules?.length ?? 0, e.detail)}
/>
{#if !modules || modules[modules?.length - 1]?.summary != 'Terminate flow'}
<InsertModuleButton
stop={!root && parentType == 'branchone'}
trigger={root && modules?.length == 0}
on:new={(e) => insertNewModuleAtIndex(modules?.length ?? 0, e.detail)}
/>
{:else}
<div class="my-2" />
{/if}
</ul>
{#if root}
<div class="sticky bottom-0 bg-gray-50 flex-none px-4 py-1 pb-2 border-t">

View File

@@ -10,10 +10,11 @@
} from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { Repeat } from 'lucide-svelte'
import { Repeat, Square } from 'lucide-svelte'
const dispatch = createEventDispatcher()
export let trigger = false
export let stop = false
</script>
<Menu noMinW placement="bottom-start" let:close>
@@ -113,5 +114,18 @@
<Icon data={faBarsStaggered} scale={0.8} class="mr-2" />
Flow
</button>
{#if stop}
<button
class="w-full text-left p-2 hover:bg-gray-100 inline-flex gap-2.5"
on:click={() => {
close()
dispatch('new', 'end')
}}
role="menuitem"
>
<Square size={14} />
End Flow
</button>
{/if}
</div>
</Menu>

View File

@@ -11,7 +11,7 @@
import Icon from 'svelte-awesome'
import IconedResourceType from '$lib/components/IconedResourceType.svelte'
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
import { Building, Repeat } from 'lucide-svelte'
import { Building, Repeat, Square } from 'lucide-svelte'
export let mod: FlowModule
export let index: number
@@ -107,6 +107,8 @@
<div slot="icon">
{#if mod.value.type === 'rawscript'}
<LanguageIcon lang={mod.value.language} width={16} height={16} />
{:else if mod.summary == 'Terminate flow'}
<Square size={16} />
{:else if mod.value.type === 'identity'}
<Icon data={faLongArrowDown} scale={1.1} />
{:else if mod.value.type === 'script'}

View File

@@ -162,7 +162,7 @@ export function codeToStaticTemplate(code?: string): string | undefined {
if (lines.length == 1) {
const line = lines[0].trim()
if (line[0] == '`' && line.charAt(line.length - 1) == '`') {
return line.slice(1, line.length - 1)
return line.slice(1, line.length - 1).replaceAll('\\`', '`')
}
}
return undefined
@@ -179,7 +179,10 @@ export function emptyFlowModuleState(): FlowModuleState {
const aCharCode = 'a'.charCodeAt(0)
export function numberToChars(n: number) {
export function numberToChars(n: number, skipTilde: boolean = false) {
if (n < 0) {
return "-" + numberToChars(-n, skipTilde)
}
var b = [n],
sp,
out,
@@ -187,11 +190,12 @@ export function numberToChars(n: number) {
div
sp = 0
const basisInc = skipTilde ? -1 : 0
while (sp < b.length) {
if (b[sp] > 25) {
div = Math.floor(b[sp] / 27)
if (b[sp] > (25 + basisInc)) {
div = Math.floor(b[sp] / (27 + basisInc))
b[sp + 1] = div - 1
b[sp] %= 27
b[sp] %= (27 + basisInc)
}
sp += 1
}
@@ -199,17 +203,20 @@ export function numberToChars(n: number) {
out = ''
for (i = 0; i < b.length; i += 1) {
let charCode = aCharCode + b[i]
out = (charCode == 26 ? '-' : String.fromCharCode(charCode)) + out
out = (b[i] == 26 ? "-" : String.fromCharCode(charCode)) + out
}
return out
}
export function charsToNumber(n: string): number {
if (n.charAt(0) == '-') {
return charsToNumber(n.slice(1)) * -1
}
let b = Math.pow(27, n.length - 1)
let res = 0
for (let c of n) {
let charCode = c == '-' ? aCharCode + 26 : c.charCodeAt(0)
let charCode = (c == '-' || c == '_') ? aCharCode + 26 : c.charCodeAt(0)
res += (charCode - aCharCode + 1) * b
b = b / 27
}

View File

@@ -123,7 +123,7 @@
return flowModuleToBranch(
module,
branches,
['Default', ...module.value.branches.map((x) => `If ${truncateRev(x.expr, 20)}`)],
['Default', ...module.value.branches.map((x) => `${truncateRev(x.expr, 20)}`)],
parent,
insideLoop
)
@@ -328,11 +328,11 @@
if (!modules.length) {
items.push(createVirtualNode(branchParent, 'Empty branch', edgesLabel[i]))
} else {
modules.forEach((module) => {
modules.forEach((module, j) => {
const item = getConvertedFlowModule(
module,
items.length ? items : numberToChars(branch.node.id),
edgesLabel[i],
j == 0 ? edgesLabel[i] : undefined,
insideLoop
)
item && items.push(item)
@@ -434,8 +434,9 @@
edgesLabel?: string,
offset?: number
): Node {
const id = -idGenerator.next().value - 1 + (offset ?? 0)
return {
id: -idGenerator.next().value - 1 + (offset ?? 0),
id,
position: { x: -1, y: -1 },
data: {
html: `

View File

@@ -23,6 +23,7 @@
faBarsStaggered
} from '@fortawesome/free-solid-svg-icons'
import { CalendarClock } from 'lucide-svelte'
import { onDestroy, onMount } from 'svelte'
import Icon from 'svelte-awesome'
import { check } from 'svelte-awesome/icons'
import { Badge } from '../common'
@@ -32,6 +33,24 @@
export let job: Job
let scheduleEditor: ScheduleEditor
let time = Date.now()
let interval
onMount(() => {
interval = setInterval(() => {
time = Date.now()
}, 1000)
})
onDestroy(() => {
interval && clearInterval(interval)
})
function endedDate(started_at: string, duration_ms: number): string {
const started = new Date(started_at)
started.setMilliseconds(started.getMilliseconds() + duration_ms)
return displayDaysAgo(started.toString())
}
</script>
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
@@ -152,9 +171,17 @@
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span
class="mx-1.5"
>
Started {displayDaysAgo(job.started_at ?? '')}</span
>
</div>
<span>
{#if job?.['duration_ms']}
Ended {#key time}
{endedDate(job.started_at, job?.['duration_ms'])}{/key}
{:else}
Started {#key time}
{displayDaysAgo(job.started_at ?? '')}{/key}
{/if}</span
>
</span></div
>
{/if}
{#if 'scheduled_for' in job && !job.running && job.scheduled_for && forLater(job.scheduled_for)}
<div class="inline-flex gap-1">

View File

@@ -5,7 +5,6 @@
import { Badge } from '../common'
import { NEVER_TESTED_THIS_FAR } from '../flows/utils'
import { getTypeAsString } from '../flows/utils'
import Popover from '../Popover.svelte'
import { computeKey } from './utils'
import WarningMessage from './WarningMessage.svelte'
@@ -18,6 +17,7 @@
export let rawKey = false
export let topBrackets = false
export let topLevelNode = false
export let allowCopy = true
const collapsedSymbol = '...'
let keys: string | any[]
@@ -39,7 +39,7 @@
const dispatch = createEventDispatcher()
function selectProp(key: string, value: any) {
if (pureViewer) {
if (pureViewer && allowCopy) {
copyToClipboard(value)
}
dispatch('select', rawKey ? key : computeKey(key, isArray, currentPath))

View File

@@ -26,7 +26,7 @@
const EMPTY_STRING = ''
let search = ''
const { propPickerConfig, clearFocus } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
const { propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
$: flowInputsFiltered =
search === EMPTY_STRING
@@ -89,6 +89,7 @@
</div>
<div class="overflow-y-auto mb-2">
<ObjectViewer
allowCopy={false}
pureViewer={!$propPickerConfig}
json={flowInputsFiltered}
on:select={(e) => {
@@ -100,6 +101,7 @@
<span class="font-bold text-sm">Error</span>
<div class="overflow-y-auto mb-2">
<ObjectViewer
allowCopy={false}
pureViewer={!$propPickerConfig}
json={{
error: {
@@ -116,6 +118,7 @@
<span class="font-bold text-sm">Previous Result</span>
<div class="overflow-y-auto mb-2">
<ObjectViewer
allowCopy={false}
topLevelNode
pureViewer={!$propPickerConfig}
json={Object.fromEntries(
@@ -131,6 +134,7 @@
<span class="font-bold text-sm">Resume payloads</span>
<div class="overflow-y-auto mb-2">
<ObjectViewer
allowCopy={false}
topLevelNode
pureViewer={!$propPickerConfig}
json={{
@@ -148,6 +152,7 @@
<span class="font-bold text-sm">All Results</span>
<div class="overflow-y-auto mb-2">
<ObjectViewer
allowCopy={false}
topLevelNode
pureViewer={!$propPickerConfig}
collapsed={true}
@@ -173,6 +178,7 @@
}}>-</Button
>
<ObjectViewer
allowCopy={false}
pureViewer={!$propPickerConfig}
rawKey={true}
json={variables}
@@ -200,6 +206,7 @@
}}>-</Button
>
<ObjectViewer
allowCopy={false}
pureViewer={!$propPickerConfig}
rawKey={true}
json={resources}

View File

@@ -7,6 +7,6 @@
<div class="w-full px-2">
<span class="font-bold text-sm">Result</span>
<div class="overflow-y-auto mb-2 w-full">
<ObjectViewer json={{ result }} on:select />
<ObjectViewer allowCopy={false} json={{ result }} on:select />
</div>
</div>

View File

@@ -96,7 +96,7 @@
Sign out
</button>
</div>
{#if isCloudHosted()}
{#if isCloudHosted() && $premiumStore}
{#if !$premiumStore.premium}
<div class="py-1" role="none">
<span class="text-gray-700 block w-full text-left px-4 py-2 text-sm"

View File

@@ -30,8 +30,10 @@
<table class="w-full">
{#each $userWorkspaces as workspace}
<tr
class="text-xs cursor-pointer hover:bg-gray-100"
class="text-xs
{$workspaceStore === workspace.id ? 'cursor-default bg-blue-50' : 'cursor-pointer hover:bg-gray-100'}"
on:click={() => {
if($workspaceStore === workspace.id) { return }
switchWorkspace(workspace.id)
close()
}}

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